mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-18 05:10:45 +00:00
CTLE-436: added variant selection when querying or viewing records
This commit is contained in:
@ -25,12 +25,14 @@ import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QF
|
||||
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
|
||||
import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData";
|
||||
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||
import {QTableVariant} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableVariant";
|
||||
import {QJobComplete} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobComplete";
|
||||
import {QJobError} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobError";
|
||||
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
|
||||
import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter";
|
||||
import {QueryJoin} from "@kingsrook/qqq-frontend-core/lib/model/query/QueryJoin";
|
||||
import {Alert, Collapse, TablePagination} from "@mui/material";
|
||||
import {Alert, Collapse, TablePagination, Typography} from "@mui/material";
|
||||
import Autocomplete from "@mui/material/Autocomplete";
|
||||
import Box from "@mui/material/Box";
|
||||
import Button from "@mui/material/Button";
|
||||
import Card from "@mui/material/Card";
|
||||
@ -78,6 +80,7 @@ const ROWS_PER_PAGE_LOCAL_STORAGE_KEY_ROOT = "qqq.rowsPerPage";
|
||||
const PINNED_COLUMNS_LOCAL_STORAGE_KEY_ROOT = "qqq.pinnedColumns";
|
||||
const SEEN_JOIN_TABLES_LOCAL_STORAGE_KEY_ROOT = "qqq.seenJoinTables";
|
||||
const DENSITY_LOCAL_STORAGE_KEY_ROOT = "qqq.density";
|
||||
const TABLE_VARIANT_LOCAL_STORAGE_KEY_ROOT = "qqq.tableVariant";
|
||||
|
||||
interface Props
|
||||
{
|
||||
@ -134,6 +137,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
const seenJoinTablesLocalStorageKey = `${SEEN_JOIN_TABLES_LOCAL_STORAGE_KEY_ROOT}.${tableName}`;
|
||||
const columnVisibilityLocalStorageKey = `${COLUMN_VISIBILITY_LOCAL_STORAGE_KEY_ROOT}.${tableName}`;
|
||||
const filterLocalStorageKey = `${FILTER_LOCAL_STORAGE_KEY_ROOT}.${tableName}`;
|
||||
const tableVariantLocalStorageKey = `${TABLE_VARIANT_LOCAL_STORAGE_KEY_ROOT}.${tableName}`;
|
||||
let defaultSort = [] as GridSortItem[];
|
||||
let defaultVisibility = {} as { [index: string]: boolean };
|
||||
let didDefaultVisibilityComeFromLocalStorage = false;
|
||||
@ -141,6 +145,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
let defaultDensity = "standard" as GridDensity;
|
||||
let defaultPinnedColumns = {left: ["__check__", "id"]} as GridPinnedColumns;
|
||||
let seenJoinTables: {[tableName: string]: boolean} = {};
|
||||
let defaultTableVariant: QTableVariant = null;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
// set the to be not per table (do as above if we want per table) at a later port //
|
||||
@ -172,11 +177,16 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
{
|
||||
seenJoinTables = JSON.parse(localStorage.getItem(seenJoinTablesLocalStorageKey));
|
||||
}
|
||||
if (localStorage.getItem(tableVariantLocalStorageKey))
|
||||
{
|
||||
defaultTableVariant = JSON.parse(localStorage.getItem(tableVariantLocalStorageKey));
|
||||
}
|
||||
|
||||
const [filterModel, setFilterModel] = useState({items: []} as GridFilterModel);
|
||||
const [lastFetchedQFilterJSON, setLastFetchedQFilterJSON] = useState("");
|
||||
const [columnSortModel, setColumnSortModel] = useState(defaultSort);
|
||||
const [queryFilter, setQueryFilter] = useState(new QQueryFilter());
|
||||
const [tableVariant, setTableVariant] = useState(defaultTableVariant);
|
||||
|
||||
const [columnVisibilityModel, setColumnVisibilityModel] = useState(defaultVisibility);
|
||||
const [shouldSetAllNewJoinFieldsToHidden, setShouldSetAllNewJoinFieldsToHidden] = useState(!didDefaultVisibilityComeFromLocalStorage)
|
||||
@ -204,6 +214,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
const [distinctRecordsOnPageCount, setDistinctRecordsOnPageCount] = useState(null as number);
|
||||
const [selectionSubsetSize, setSelectionSubsetSize] = useState(null as number);
|
||||
const [selectionSubsetSizePromptOpen, setSelectionSubsetSizePromptOpen] = useState(false);
|
||||
const [tableVariantPromptOpen, setTableVariantPromptOpen] = useState(false);
|
||||
const [selectFullFilterState, setSelectFullFilterState] = useState("n/a" as "n/a" | "checked" | "filter" | "filterSubset");
|
||||
const [rowSelectionModel, setRowSelectionModel] = useState<GridSelectionModel>([]);
|
||||
const [columnsModel, setColumnsModel] = useState([] as GridColDef[]);
|
||||
@ -337,6 +348,11 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
|
||||
}, [location, tableMetaData]);
|
||||
|
||||
function promptForTableVariantSelection()
|
||||
{
|
||||
setTableVariantPromptOpen(true);
|
||||
}
|
||||
|
||||
const updateColumnVisibilityModel = () =>
|
||||
{
|
||||
if (localStorage.getItem(columnVisibilityLocalStorageKey))
|
||||
@ -423,8 +439,10 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
return (false);
|
||||
}
|
||||
|
||||
const getPageHeader = (tableMetaData: QTableMetaData, visibleJoinTables: Set<string>): string | JSX.Element =>
|
||||
const getPageHeader = (tableMetaData: QTableMetaData, visibleJoinTables: Set<string>, tableVariant: QTableVariant): string | JSX.Element =>
|
||||
{
|
||||
let label: string = tableMetaData?.label ?? "";
|
||||
|
||||
if (visibleJoinTables.size > 0)
|
||||
{
|
||||
let joinLabels = [];
|
||||
@ -461,15 +479,36 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
|
||||
return(
|
||||
<div>
|
||||
{tableMetaData?.label}
|
||||
{label}
|
||||
<CustomWidthTooltip title={tooltipHTML}>
|
||||
<IconButton sx={{p: 0, fontSize: "0.5rem", mb: 1, color: "#9f9f9f", fontVariationSettings: "'wght' 100"}}><Icon fontSize="small">emergency</Icon></IconButton>
|
||||
</CustomWidthTooltip>
|
||||
{
|
||||
tableVariant &&
|
||||
<Typography variant="h6" color="text" fontWeight="light">
|
||||
{tableMetaData.variantTableLabel}: {tableVariant.name}
|
||||
<Tooltip title={`Change ${tableMetaData.variantTableLabel}`}>
|
||||
<IconButton onClick={promptForTableVariantSelection} sx={{p: 0, m: 0, ml: .5, mb: .5, color: "#9f9f9f", fontVariationSettings: "'wght' 100"}}><Icon fontSize="small">settings</Icon></IconButton>
|
||||
</Tooltip>
|
||||
</Typography>
|
||||
}
|
||||
</div>);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (tableMetaData?.label);
|
||||
return (
|
||||
<div>
|
||||
{label}
|
||||
{
|
||||
tableVariant &&
|
||||
<Typography variant="h6" color="text" fontWeight="light">
|
||||
{tableMetaData.variantTableLabel}: {tableVariant.name}
|
||||
<Tooltip title={`Change ${tableMetaData.variantTableLabel}`}>
|
||||
<IconButton onClick={promptForTableVariantSelection} sx={{p: 0, m: 0, ml: .5, mb: .5, color: "#9f9f9f", fontVariationSettings: "'wght' 100"}}><Icon fontSize="small">settings</Icon></IconButton>
|
||||
</Tooltip>
|
||||
</Typography>
|
||||
}
|
||||
</div>);
|
||||
}
|
||||
};
|
||||
|
||||
@ -480,9 +519,8 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
(async () =>
|
||||
{
|
||||
const tableMetaData = await qController.loadTableMetaData(tableName);
|
||||
|
||||
const visibleJoinTables = getVisibleJoinTables();
|
||||
setPageHeader(getPageHeader(tableMetaData, visibleJoinTables));
|
||||
setPageHeader(getPageHeader(tableMetaData, visibleJoinTables, tableVariant));
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if there's an exposedJoin that we haven't seen before, we want to make sure that all of its fields //
|
||||
@ -543,6 +581,12 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
setTableMetaData(tableMetaData);
|
||||
setTableLabel(tableMetaData.label);
|
||||
|
||||
if(tableMetaData?.usesVariants && ! tableVariant)
|
||||
{
|
||||
promptForTableVariantSelection();
|
||||
return;
|
||||
}
|
||||
|
||||
if (columnsModel.length == 0)
|
||||
{
|
||||
let linkBase = metaData.getTablePath(table);
|
||||
@ -633,7 +677,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
if (tableMetaData.capabilities.has(Capability.TABLE_COUNT))
|
||||
{
|
||||
let includeDistinct = isJoinMany(tableMetaData, getVisibleJoinTables());
|
||||
qController.count(tableName, qFilter, queryJoins, includeDistinct).then(([count, distinctCount]) =>
|
||||
qController.count(tableName, qFilter, queryJoins, includeDistinct, tableVariant).then(([count, distinctCount]) =>
|
||||
{
|
||||
console.log(`Received count results for query ${thisQueryId}: ${count} ${distinctCount}`);
|
||||
countResults[thisQueryId] = [];
|
||||
@ -645,7 +689,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
}
|
||||
|
||||
setLastFetchedQFilterJSON(JSON.stringify(qFilter));
|
||||
qController.query(tableName, qFilter, queryJoins).then((results) =>
|
||||
qController.query(tableName, qFilter, queryJoins, tableVariant).then((results) =>
|
||||
{
|
||||
console.log(`Received results for query ${thisQueryId}`);
|
||||
queryResults[thisQueryId] = results;
|
||||
@ -1762,7 +1806,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
setTotalRecords(null);
|
||||
setDistinctRecords(null);
|
||||
updateTable();
|
||||
}, [columnsModel, tableState]);
|
||||
}, [columnsModel, tableState, tableVariant]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
@ -1949,6 +1993,15 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
</Modal>
|
||||
}
|
||||
|
||||
{
|
||||
tableMetaData &&
|
||||
<TableVariantDialog table={tableMetaData} isOpen={tableVariantPromptOpen} closeHandler={(value: QTableVariant) =>
|
||||
{
|
||||
setTableVariantPromptOpen(false);
|
||||
setTableVariant(value);
|
||||
}} />
|
||||
}
|
||||
|
||||
{
|
||||
columnStatsFieldName &&
|
||||
<Modal open={columnStatsFieldName !== null} onClose={(event, reason) => closeColumnStats(event, reason)}>
|
||||
@ -1971,6 +2024,93 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
|
||||
);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// mini-component that is the dialog for the user to select a variant on tables with variant backends //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
function TableVariantDialog(props: {isOpen: boolean; table: QTableMetaData; closeHandler: (value?: QTableVariant) => void})
|
||||
{
|
||||
const [value, setValue] = useState(null)
|
||||
const [dropDownOpen, setDropDownOpen] = useState(false)
|
||||
const [variants, setVariants] = useState(null);
|
||||
|
||||
const handleVariantChange = (event: React.SyntheticEvent, value: any | any[], reason: string, details?: string) =>
|
||||
{
|
||||
const tableVariantLocalStorageKey = `${TABLE_VARIANT_LOCAL_STORAGE_KEY_ROOT}.${props.table.name}`;
|
||||
if(value != null)
|
||||
{
|
||||
localStorage.setItem(tableVariantLocalStorageKey, JSON.stringify(value));
|
||||
}
|
||||
else
|
||||
{
|
||||
localStorage.removeItem(tableVariantLocalStorageKey);
|
||||
}
|
||||
props.closeHandler(value);
|
||||
};
|
||||
|
||||
const keyPressed = (e: React.KeyboardEvent<HTMLDivElement>) =>
|
||||
{
|
||||
if(e.key == "Enter" && value)
|
||||
{
|
||||
props.closeHandler(value);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
console.log("queryVariants")
|
||||
try
|
||||
{
|
||||
(async () =>
|
||||
{
|
||||
const variants = await qController.tableVariants(props.table.name);
|
||||
console.log(JSON.stringify(variants));
|
||||
setVariants(variants);
|
||||
})();
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
console.log(e);
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
return variants && (
|
||||
<Dialog open={props.isOpen} onKeyPress={(e) => keyPressed(e)}>
|
||||
<DialogTitle>{props.table.variantTableLabel}</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>Select the {props.table.variantTableLabel} to be used on this table:</DialogContentText>
|
||||
<Autocomplete
|
||||
id="tableVariantId"
|
||||
sx={{width: "400px", marginTop: "10px"}}
|
||||
open={dropDownOpen}
|
||||
size="small"
|
||||
onOpen={() =>
|
||||
{
|
||||
setDropDownOpen(true);
|
||||
}}
|
||||
onClose={() =>
|
||||
{
|
||||
setDropDownOpen(false);
|
||||
}}
|
||||
// @ts-ignore
|
||||
onChange={handleVariantChange}
|
||||
isOptionEqualToValue={(option, value) => option.id === value.id}
|
||||
options={variants}
|
||||
renderInput={(params) => <TextField {...params} label={props.table.variantTableLabel} />}
|
||||
getOptionLabel={(option) =>
|
||||
{
|
||||
if(typeof option == "object")
|
||||
{
|
||||
return (option as QTableVariant).name;
|
||||
}
|
||||
return option;
|
||||
}}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
// mini-component that is the dialog for the user to enter the selection-subset //
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -25,6 +25,7 @@ import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstan
|
||||
import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData";
|
||||
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||
import {QTableSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableSection";
|
||||
import {QTableVariant} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableVariant";
|
||||
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
|
||||
import {Alert, Typography} from "@mui/material";
|
||||
import Avatar from "@mui/material/Avatar";
|
||||
@ -74,6 +75,8 @@ RecordView.defaultProps =
|
||||
launchProcess: null,
|
||||
};
|
||||
|
||||
const TABLE_VARIANT_LOCAL_STORAGE_KEY_ROOT = "qqq.tableVariant";
|
||||
|
||||
function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
{
|
||||
const {id} = useParams();
|
||||
@ -83,7 +86,9 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
|
||||
const pathParts = location.pathname.replace(/\/+$/, "").split("/");
|
||||
const tableName = table.name;
|
||||
let tableVariant: QTableVariant = null;
|
||||
|
||||
const tableVariantLocalStorageKey = `${TABLE_VARIANT_LOCAL_STORAGE_KEY_ROOT}.${tableName}`;
|
||||
const [asyncLoadInited, setAsyncLoadInited] = useState(false);
|
||||
const [sectionFieldElements, setSectionFieldElements] = useState(null as Map<string, JSX.Element[]>);
|
||||
const [adornmentFieldsMap, setAdornmentFieldsMap] = useState(new Map<string, boolean>);
|
||||
@ -112,7 +117,10 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
|
||||
const {accentColor, setPageHeader, tableMetaData, setTableMetaData, tableProcesses, setTableProcesses, dotMenuOpen} = useContext(QContext);
|
||||
|
||||
|
||||
if (localStorage.getItem(tableVariantLocalStorageKey))
|
||||
{
|
||||
tableVariant = JSON.parse(localStorage.getItem(tableVariantLocalStorageKey));
|
||||
}
|
||||
|
||||
const reload = () =>
|
||||
{
|
||||
@ -360,7 +368,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
let record: QRecord;
|
||||
try
|
||||
{
|
||||
record = await qController.get(tableName, id);
|
||||
record = await qController.get(tableName, id, tableVariant);
|
||||
setRecord(record);
|
||||
}
|
||||
catch (e)
|
||||
|
@ -1,9 +1,3 @@
|
||||
|
||||
::selection {
|
||||
background: hotpink;
|
||||
color: white;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
padding: 0;
|
||||
|
Reference in New Issue
Block a user