diff --git a/package.json b/package.json index c4584a1..b5a6189 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "@auth0/auth0-react": "1.10.2", "@emotion/react": "11.7.1", "@emotion/styled": "11.6.0", - "@kingsrook/qqq-frontend-core": "1.0.72", + "@kingsrook/qqq-frontend-core": "1.0.73", "@mui/icons-material": "5.4.1", "@mui/material": "5.11.1", "@mui/styles": "5.11.1", diff --git a/src/qqq/pages/records/query/RecordQuery.tsx b/src/qqq/pages/records/query/RecordQuery.tsx index 63ef5ca..64aa25b 100644 --- a/src/qqq/pages/records/query/RecordQuery.tsx +++ b/src/qqq/pages/records/query/RecordQuery.tsx @@ -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([]); 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 | JSX.Element => + const getPageHeader = (tableMetaData: QTableMetaData, visibleJoinTables: Set, 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(
- {tableMetaData?.label} + {label} emergency + { + tableVariant && + + {tableMetaData.variantTableLabel}: {tableVariant.name} + + settings + + + }
); } else { - return (tableMetaData?.label); + return ( +
+ {label} + { + tableVariant && + + {tableMetaData.variantTableLabel}: {tableVariant.name} + + settings + + + } +
); } }; @@ -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 } + { + tableMetaData && + + { + setTableVariantPromptOpen(false); + setTableVariant(value); + }} /> + } + { columnStatsFieldName && 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) => + { + 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 && ( + keyPressed(e)}> + {props.table.variantTableLabel} + + Select the {props.table.variantTableLabel} to be used on this table: + + { + setDropDownOpen(true); + }} + onClose={() => + { + setDropDownOpen(false); + }} + // @ts-ignore + onChange={handleVariantChange} + isOptionEqualToValue={(option, value) => option.id === value.id} + options={variants} + renderInput={(params) => } + getOptionLabel={(option) => + { + if(typeof option == "object") + { + return (option as QTableVariant).name; + } + return option; + }} + /> + + + ) +} + ////////////////////////////////////////////////////////////////////////////////// // mini-component that is the dialog for the user to enter the selection-subset // ////////////////////////////////////////////////////////////////////////////////// diff --git a/src/qqq/pages/records/view/RecordView.tsx b/src/qqq/pages/records/view/RecordView.tsx index 34e9e26..19e38ad 100644 --- a/src/qqq/pages/records/view/RecordView.tsx +++ b/src/qqq/pages/records/view/RecordView.tsx @@ -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); const [adornmentFieldsMap, setAdornmentFieldsMap] = useState(new Map); @@ -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) diff --git a/src/qqq/styles/globals.scss b/src/qqq/styles/globals.scss index 1f338e3..9b11a10 100644 --- a/src/qqq/styles/globals.scss +++ b/src/qqq/styles/globals.scss @@ -1,9 +1,3 @@ - -::selection { - background: hotpink; - color: white; -} - html, body { padding: 0;