From 0d16b82ad070516008c0f0712271766107e68af8 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Wed, 12 Oct 2022 17:32:29 -0500 Subject: [PATCH] Add sections and autocomplete (QDynamicSelect) on bulk-edit --- src/qqq/components/EntityForm/index.tsx | 19 +-- src/qqq/components/QDynamicForm/index.tsx | 2 + .../QDynamicForm/utils/DynamicFormUtils.ts | 27 ++++ .../components/QDynamicFormField/index.tsx | 2 +- .../QDynamicSelect/QDynamicSelect.tsx | 58 +++++++- src/qqq/components/QRecordSidebar/index.tsx | 14 +- src/qqq/pages/entity-list/index.tsx | 137 +++++++++--------- .../components/ViewContents/index.tsx | 4 +- src/qqq/pages/process-run/index.tsx | 109 +++++++++++--- src/qqq/styles/qqq-override-styles.css | 8 +- src/qqq/utils/QTableUtils.ts | 50 ++++++- 11 files changed, 311 insertions(+), 119 deletions(-) diff --git a/src/qqq/components/EntityForm/index.tsx b/src/qqq/components/EntityForm/index.tsx index 7ee6160..8f6493c 100644 --- a/src/qqq/components/EntityForm/index.tsx +++ b/src/qqq/components/EntityForm/index.tsx @@ -155,6 +155,7 @@ function EntityForm({table, id}: Props): JSX.Element dynamicFormFields, formValidations, } = DynamicFormUtils.getFormData(fieldArray); + DynamicFormUtils.addPossibleValueProps(dynamicFormFields, fieldArray, tableName, record?.displayValues); ///////////////////////////////////// // group the formFields by section // @@ -180,24 +181,6 @@ function EntityForm({table, id}: Props): JSX.Element { sectionDynamicFormFields.push(dynamicFormFields[fieldName]); } - - ///////////////////////////////////////// - // add props for possible value fields // - ///////////////////////////////////////// - if(field.possibleValueSourceName) - { - let initialDisplayValue = null; - if(record && record.displayValues) - { - initialDisplayValue = record.displayValues.get(field.name); - } - dynamicFormFields[fieldName].possibleValueProps = - { - isPossibleValue: true, - tableName: tableName, - initialDisplayValue: initialDisplayValue, - }; - } } if (sectionDynamicFormFields.length === 0) diff --git a/src/qqq/components/QDynamicForm/index.tsx b/src/qqq/components/QDynamicForm/index.tsx index 3e922fb..b89d3f5 100644 --- a/src/qqq/components/QDynamicForm/index.tsx +++ b/src/qqq/components/QDynamicForm/index.tsx @@ -136,6 +136,8 @@ function QDynamicForm(props: Props): JSX.Element fieldLabel={field.label} initialValue={values[fieldName]} initialDisplayValue={field.possibleValueProps.initialDisplayValue} + bulkEditMode={bulkEditMode} + bulkEditSwitchChangeHandler={bulkEditSwitchChanged} /> ); diff --git a/src/qqq/components/QDynamicForm/utils/DynamicFormUtils.ts b/src/qqq/components/QDynamicForm/utils/DynamicFormUtils.ts index dbdead7..cb46b5c 100644 --- a/src/qqq/components/QDynamicForm/utils/DynamicFormUtils.ts +++ b/src/qqq/components/QDynamicForm/utils/DynamicFormUtils.ts @@ -95,6 +95,33 @@ class DynamicFormUtils } return (null); } + + public static addPossibleValueProps(dynamicFormFields: any, qFields: QFieldMetaData[], tableName: string, displayValues: Map) + { + for (let i = 0; i < qFields.length; i++) + { + const field = qFields[i]; + + ///////////////////////////////////////// + // add props for possible value fields // + ///////////////////////////////////////// + if (field.possibleValueSourceName && dynamicFormFields[field.name]) + { + let initialDisplayValue = null; + if (displayValues) + { + initialDisplayValue = displayValues.get(field.name); + } + + dynamicFormFields[field.name].possibleValueProps = + { + isPossibleValue: true, + tableName: tableName, + initialDisplayValue: initialDisplayValue, + }; + } + } + } } export default DynamicFormUtils; diff --git a/src/qqq/components/QDynamicFormField/index.tsx b/src/qqq/components/QDynamicFormField/index.tsx index f4ecdbb..20ab1cc 100644 --- a/src/qqq/components/QDynamicFormField/index.tsx +++ b/src/qqq/components/QDynamicFormField/index.tsx @@ -123,7 +123,7 @@ function QDynamicFormField({ onClick={bulkEditSwitchChanged} /> - + {/* for checkboxes, if we put the whole thing in a label, we get bad overly aggressive toggling of the outer switch... */} {(type == "checkbox" ? field() : diff --git a/src/qqq/components/QDynamicSelect/QDynamicSelect.tsx b/src/qqq/components/QDynamicSelect/QDynamicSelect.tsx index 2f23e15..d9111d2 100644 --- a/src/qqq/components/QDynamicSelect/QDynamicSelect.tsx +++ b/src/qqq/components/QDynamicSelect/QDynamicSelect.tsx @@ -22,9 +22,12 @@ import {QPossibleValue} from "@kingsrook/qqq-frontend-core/lib/model/QPossibleValue"; import {CircularProgress, FilterOptionsState} from "@mui/material"; import Autocomplete from "@mui/material/Autocomplete"; +import Box from "@mui/material/Box"; +import Switch from "@mui/material/Switch"; import TextField from "@mui/material/TextField"; import {useFormikContext} from "formik"; import React, {useEffect, useState} from "react"; +import MDBox from "qqq/components/Temporary/MDBox"; import QClient from "qqq/utils/QClient"; interface Props @@ -35,7 +38,10 @@ interface Props inForm: boolean; initialValue?: any; initialDisplayValue?: string; - onChange?: any + onChange?: any; + isEditable?: boolean; + bulkEditMode?: boolean; + bulkEditSwitchChangeHandler?: any; } QDynamicSelect.defaultProps = { @@ -43,11 +49,16 @@ QDynamicSelect.defaultProps = { initialValue: null, initialDisplayValue: null, onChange: null, + isEditable: true, + bulkEditMode: false, + bulkEditSwitchChangeHandler: () => + { + }, }; const qController = QClient.getInstance(); -function QDynamicSelect({tableName, fieldName, fieldLabel, inForm, initialValue, initialDisplayValue, onChange}: Props) +function QDynamicSelect({tableName, fieldName, fieldLabel, inForm, initialValue, initialDisplayValue, onChange, isEditable, bulkEditMode, bulkEditSwitchChangeHandler}: Props) { const [ open, setOpen ] = useState(false); const [ options, setOptions ] = useState([]); @@ -56,6 +67,8 @@ function QDynamicSelect({tableName, fieldName, fieldLabel, inForm, initialValue, const [defaultValue, _] = useState(initialValue && initialDisplayValue ? {id: initialValue, label: initialDisplayValue} : null); // const loading = open && options.length === 0; const [loading, setLoading] = useState(false); + const [ switchChecked, setSwitchChecked ] = useState(false); + const [ isDisabled, setIsDisabled ] = useState(!isEditable || bulkEditMode); let setFieldValueRef: (field: string, value: any, shouldValidate?: boolean) => void = null; if(inForm) @@ -152,9 +165,18 @@ function QDynamicSelect({tableName, fieldName, fieldLabel, inForm, initialValue, ); } - return ( + const bulkEditSwitchChanged = () => + { + const newSwitchValue = !switchChecked; + setSwitchChecked(newSwitchValue); + setIsDisabled(!newSwitchValue); + bulkEditSwitchChangeHandler(fieldName, newSwitchValue); + }; + + const autocomplete = ( @@ -190,6 +212,7 @@ function QDynamicSelect({tableName, fieldName, fieldLabel, inForm, initialValue, }} renderOption={renderOption} filterOptions={filterOptions} + disabled={isDisabled} renderInput={(params) => ( ); + + + if (bulkEditMode) + { + return ( + + + + + + {autocomplete} + + + ); + } + else + { + return ( + + {autocomplete} + + ); + } + + } export default QDynamicSelect; diff --git a/src/qqq/components/QRecordSidebar/index.tsx b/src/qqq/components/QRecordSidebar/index.tsx index 6892eb5..a15a61f 100644 --- a/src/qqq/components/QRecordSidebar/index.tsx +++ b/src/qqq/components/QRecordSidebar/index.tsx @@ -36,8 +36,14 @@ interface Props metaData?: QTableMetaData; widgetMetaDataList?: QWidgetMetaData[]; light?: boolean; + stickyTop?: string; } +QRecordSidebar.defaultProps = { + light: false, + stickyTop: "100px", +}; + interface SidebarEntry { iconName: string; @@ -45,7 +51,7 @@ interface SidebarEntry label: string; } -function QRecordSidebar({tableSections, widgetMetaDataList, light}: Props): JSX.Element +function QRecordSidebar({tableSections, widgetMetaDataList, light, stickyTop}: Props): JSX.Element { ///////////////////////////////////////////////////////// // insert widgets after identity (first) table section // @@ -65,7 +71,7 @@ function QRecordSidebar({tableSections, widgetMetaDataList, light}: Props): JSX. return ( - borderRadius.lg, position: "sticky", top: "100px"}}> + borderRadius.lg, position: "sticky", top: stickyTop}}> { sidebarEntries ? sidebarEntries.map((entry: SidebarEntry, key: number) => ( @@ -109,8 +115,4 @@ function QRecordSidebar({tableSections, widgetMetaDataList, light}: Props): JSX. ); } -QRecordSidebar.defaultProps = { - light: false, -}; - export default QRecordSidebar; diff --git a/src/qqq/pages/entity-list/index.tsx b/src/qqq/pages/entity-list/index.tsx index e8ae9ba..fe54af3 100644 --- a/src/qqq/pages/entity-list/index.tsx +++ b/src/qqq/pages/entity-list/index.tsx @@ -40,7 +40,6 @@ import Modal from "@mui/material/Modal"; import { DataGridPro, getGridDateOperators, - getGridNumericOperators, GridCallbackDetails, GridColDef, GridColumnOrderChangeParams, @@ -116,12 +115,12 @@ async function getDefaultFilter(tableMetaData: QTableMetaData, searchParams: URL const defaultFilter = {items: []} as GridFilterModel; let id = 1; - for(let i = 0; i < qQueryFilter.criteria.length; i++) + for (let i = 0; i < qQueryFilter.criteria.length; i++) { const criteria = qQueryFilter.criteria[i]; const field = tableMetaData.fields.get(criteria.fieldName); let values = criteria.values; - if(field.possibleValueSourceName) + if (field.possibleValueSourceName) { ////////////////////////////////////////////////////////////////////////////////// // possible-values in query-string are expected to only be their id values. // @@ -129,7 +128,7 @@ async function getDefaultFilter(tableMetaData: QTableMetaData, searchParams: URL // but we need them to be possibleValue objects (w/ id & label) so the label // // can be shown in the filter dropdown. So, make backend call to look them up. // ////////////////////////////////////////////////////////////////////////////////// - if(values && values.length > 0) + if (values && values.length > 0) { values = await qController.possibleValues(tableMetaData.name, field.name, "", values); } @@ -144,7 +143,7 @@ async function getDefaultFilter(tableMetaData: QTableMetaData, searchParams: URL } defaultFilter.linkOperator = GridLinkOperator.And; - if(qQueryFilter.booleanOperator === "OR") + if (qQueryFilter.booleanOperator === "OR") { defaultFilter.linkOperator = GridLinkOperator.Or; } @@ -171,7 +170,7 @@ async function getDefaultFilter(tableMetaData: QTableMetaData, searchParams: URL function EntityList({table, launchProcess}: Props): JSX.Element { const tableName = table.name; - const [searchParams] = useSearchParams(); + const [ searchParams ] = useSearchParams(); const location = useLocation(); const navigate = useNavigate(); @@ -202,10 +201,10 @@ function EntityList({table, launchProcess}: Props): JSX.Element defaultRowsPerPage = JSON.parse(localStorage.getItem(rowsPerPageLocalStorageKey)); } - const [filterModel, setFilterModel] = useState({items: []} as GridFilterModel); - const [columnSortModel, setColumnSortModel] = useState(defaultSort); - const [columnVisibilityModel, setColumnVisibilityModel] = useState(defaultVisibility); - const [rowsPerPage, setRowsPerPage] = useState(defaultRowsPerPage); + const [ filterModel, setFilterModel ] = useState({items: []} as GridFilterModel); + const [ columnSortModel, setColumnSortModel ] = useState(defaultSort); + const [ columnVisibilityModel, setColumnVisibilityModel ] = useState(defaultVisibility); + const [ rowsPerPage, setRowsPerPage ] = useState(defaultRowsPerPage); /////////////////////////////////////////////////////////////////////////////////////////////// // for some reason, if we set the filterModel to what is in local storage, an onChange event // @@ -213,47 +212,47 @@ function EntityList({table, launchProcess}: Props): JSX.Element // when that happens put the default back - it needs to be in state // // const [defaultFilter1] = useState(defaultFilter); // /////////////////////////////////////////////////////////////////////////////////////////////// - const [defaultFilter] = useState({items: []} as GridFilterModel); + const [ defaultFilter ] = useState({items: []} as GridFilterModel); - const [tableState, setTableState] = useState(""); - const [tableMetaData, setTableMetaData] = useState(null as QTableMetaData); - const [defaultFilterLoaded, setDefaultFilterLoaded] = useState(false); - const [, setFiltersMenu] = useState(null); - const [actionsMenu, setActionsMenu] = useState(null); - const [tableProcesses, setTableProcesses] = useState([] as QProcessMetaData[]); - const [allTableProcesses, setAllTableProcesses] = useState([] as QProcessMetaData[]); - const [pageNumber, setPageNumber] = useState(0); - const [totalRecords, setTotalRecords] = useState(0); - const [selectedIds, setSelectedIds] = useState([] as string[]); - const [selectFullFilterState, setSelectFullFilterState] = useState("n/a" as "n/a" | "checked" | "filter"); - const [columns, setColumns] = useState([] as GridColDef[]); - const [rows, setRows] = useState([] as GridRowsProp[]); - const [loading, setLoading] = useState(true); - const [alertContent, setAlertContent] = useState(""); - const [tableLabel, setTableLabel] = useState(""); - const [gridMouseDownX, setGridMouseDownX] = useState(0); - const [gridMouseDownY, setGridMouseDownY] = useState(0); - const [pinnedColumns, setPinnedColumns] = useState({left: ["__check__", "id"]}); + const [ tableState, setTableState ] = useState(""); + const [ tableMetaData, setTableMetaData ] = useState(null as QTableMetaData); + const [ defaultFilterLoaded, setDefaultFilterLoaded ] = useState(false); + const [ , setFiltersMenu ] = useState(null); + const [ actionsMenu, setActionsMenu ] = useState(null); + const [ tableProcesses, setTableProcesses ] = useState([] as QProcessMetaData[]); + const [ allTableProcesses, setAllTableProcesses ] = useState([] as QProcessMetaData[]); + const [ pageNumber, setPageNumber ] = useState(0); + const [ totalRecords, setTotalRecords ] = useState(0); + const [ selectedIds, setSelectedIds ] = useState([] as string[]); + const [ selectFullFilterState, setSelectFullFilterState ] = useState("n/a" as "n/a" | "checked" | "filter"); + const [ columns, setColumns ] = useState([] as GridColDef[]); + const [ rows, setRows ] = useState([] as GridRowsProp[]); + const [ loading, setLoading ] = useState(true); + const [ alertContent, setAlertContent ] = useState(""); + const [ tableLabel, setTableLabel ] = useState(""); + const [ gridMouseDownX, setGridMouseDownX ] = useState(0); + const [ gridMouseDownY, setGridMouseDownY ] = useState(0); + const [ pinnedColumns, setPinnedColumns ] = useState({left: [ "__check__", "id" ]}); - const [activeModalProcess, setActiveModalProcess] = useState(null as QProcessMetaData) - const [launchingProcess, setLaunchingProcess] = useState(launchProcess); + const [ activeModalProcess, setActiveModalProcess ] = useState(null as QProcessMetaData); + const [ launchingProcess, setLaunchingProcess ] = useState(launchProcess); const instance = useRef({timer: null}); //////////////////////////////////////////////////////////////////////////////////////////////////////////////// // use all these states to avoid showing results from an "old" query, that finishes loading after a newer one // //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - const [latestQueryId, setLatestQueryId] = useState(0); - const [countResults, setCountResults] = useState({} as any); - const [receivedCountTimestamp, setReceivedCountTimestamp] = useState(new Date()); - const [queryResults, setQueryResults] = useState({} as any); - const [receivedQueryTimestamp, setReceivedQueryTimestamp] = useState(new Date()); - const [queryErrors, setQueryErrors] = useState({} as any); - const [receivedQueryErrorTimestamp, setReceivedQueryErrorTimestamp] = useState(new Date()); + const [ latestQueryId, setLatestQueryId ] = useState(0); + const [ countResults, setCountResults ] = useState({} as any); + const [ receivedCountTimestamp, setReceivedCountTimestamp ] = useState(new Date()); + const [ queryResults, setQueryResults ] = useState({} as any); + const [ receivedQueryTimestamp, setReceivedQueryTimestamp ] = useState(new Date()); + const [ queryErrors, setQueryErrors ] = useState({} as any); + const [ receivedQueryErrorTimestamp, setReceivedQueryErrorTimestamp ] = useState(new Date()); const {pageHeader, setPageHeader} = useContext(QContext); - const [, forceUpdate] = useReducer((x) => x + 1, 0); + const [ , forceUpdate ] = useReducer((x) => x + 1, 0); const openActionsMenu = (event: any) => setActionsMenu(event.currentTarget); const closeActionsMenu = () => setActionsMenu(null); @@ -269,11 +268,11 @@ function EntityList({table, launchProcess}: Props): JSX.Element // the path for a process looks like: .../table/process // // so if our tableName is in the -2 index, try to open process // ///////////////////////////////////////////////////////////////// - if(pathParts[pathParts.length - 2] === tableName) + if (pathParts[pathParts.length - 2] === tableName) { const processName = pathParts[pathParts.length - 1]; const processList = allTableProcesses.filter(p => p.name.endsWith(processName)); - if(processList.length > 0) + if (processList.length > 0) { setActiveModalProcess(processList[0]); return; @@ -284,7 +283,7 @@ function EntityList({table, launchProcess}: Props): JSX.Element } } } - catch(e) + catch (e) { console.log(e); } @@ -294,7 +293,7 @@ function EntityList({table, launchProcess}: Props): JSX.Element //////////////////////////////////////////////////////////////////////////////////// setActiveModalProcess(null); - }, [location]); + }, [ location ]); const buildQFilter = (filterModel: GridFilterModel) => { @@ -320,7 +319,7 @@ function EntityList({table, launchProcess}: Props): JSX.Element }); qFilter.booleanOperator = "AND"; - if(filterModel.linkOperator == "or") + if (filterModel.linkOperator == "or") { /////////////////////////////////////////////////////////////////////////////////////////// // by default qFilter uses AND - so only if we see linkOperator=or do we need to set it // @@ -350,7 +349,7 @@ function EntityList({table, launchProcess}: Props): JSX.Element if (!defaultFilterLoaded) { setDefaultFilterLoaded(true); - localFilterModel = await getDefaultFilter(tableMetaData, searchParams, filterLocalStorageKey) + localFilterModel = await getDefaultFilter(tableMetaData, searchParams, filterLocalStorageKey); setFilterModel(localFilterModel); return; } @@ -364,7 +363,7 @@ function EntityList({table, launchProcess}: Props): JSX.Element }); setColumnSortModel(columnSortModel); } - setPinnedColumns({left: ["__check__", tableMetaData.primaryKeyField]}); + setPinnedColumns({left: [ "__check__", tableMetaData.primaryKeyField ]}); const qFilter = buildQFilter(localFilterModel); @@ -433,7 +432,7 @@ function EntityList({table, launchProcess}: Props): JSX.Element } setTotalRecords(countResults[latestQueryId]); delete countResults[latestQueryId]; - }, [receivedCountTimestamp]); + }, [ receivedCountTimestamp ]); /////////////////////////// @@ -455,7 +454,7 @@ function EntityList({table, launchProcess}: Props): JSX.Element const results = queryResults[latestQueryId]; delete queryResults[latestQueryId]; - const fields = [...tableMetaData.fields.values()]; + const fields = [ ...tableMetaData.fields.values() ]; const rows = [] as any[]; const columnsToRender = {} as any; results.forEach((record: QRecord) => @@ -539,12 +538,12 @@ function EntityList({table, launchProcess}: Props): JSX.Element const sizeAdornment = field.getAdornment(AdornmentType.SIZE); const width: string = sizeAdornment.getValue("width"); const widths: Map = new Map([ - ["small", 100], - ["medium", 200], - ["large", 400], - ["xlarge", 600] + [ "small", 100 ], + [ "medium", 200 ], + [ "large", 400 ], + [ "xlarge", 600 ] ]); - if(widths.has(width)) + if (widths.has(width)) { columnWidth = widths.get(width); } @@ -588,7 +587,7 @@ function EntityList({table, launchProcess}: Props): JSX.Element setLoading(false); setAlertContent(null); forceUpdate(); - }, [receivedQueryTimestamp]); + }, [ receivedQueryTimestamp ]); ///////////////////////// // display query error // @@ -610,7 +609,7 @@ function EntityList({table, launchProcess}: Props): JSX.Element setLoading(false); setAlertContent(errorMessage); - }, [receivedQueryErrorTimestamp]); + }, [ receivedQueryErrorTimestamp ]); const handlePageChange = (page: number) => @@ -737,7 +736,7 @@ function EntityList({table, launchProcess}: Props): JSX.Element setTableProcesses(QProcessUtils.getProcessesForTable(metaData, tableName)); // these are the ones to show in the dropdown setAllTableProcesses(QProcessUtils.getProcessesForTable(metaData, tableName, true)); // these include hidden ones (e.g., to find the bulks) - if(launchingProcess) + if (launchingProcess) { setLaunchingProcess(null); setActiveModalProcess(launchingProcess); @@ -850,7 +849,7 @@ function EntityList({table, launchProcess}: Props): JSX.Element return ""; } - function getRecordIdsForProcess() : string | QQueryFilter + function getRecordIdsForProcess(): string | QQueryFilter { if (selectFullFilterState === "filter") { @@ -873,7 +872,7 @@ function EntityList({table, launchProcess}: Props): JSX.Element const closeModalProcess = (event: object, reason: string) => { - if(reason === "backdropClick") + if (reason === "backdropClick") { return; } @@ -886,12 +885,12 @@ function EntityList({table, launchProcess}: Props): JSX.Element navigate(newPath.join("/")); updateTable(); - } + }; const openBulkProcess = (processNamePart: "Insert" | "Edit" | "Delete", processLabelPart: "Load" | "Edit" | "Delete") => { const processList = allTableProcesses.filter(p => p.name.endsWith(`.bulk${processNamePart}`)); - if(processList.length > 0) + if (processList.length > 0) { openModalProcess(processList[0]); } @@ -899,7 +898,7 @@ function EntityList({table, launchProcess}: Props): JSX.Element { setAlertContent(`Could not find Bulk ${processLabelPart} process for this table.`); } - } + }; const bulkLoadClicked = () => { @@ -960,7 +959,7 @@ function EntityList({table, launchProcess}: Props): JSX.Element component="div" count={totalRecords === null ? 0 : totalRecords} page={pageNumber} - rowsPerPageOptions={[10, 25, 50, 100, 250]} + rowsPerPageOptions={[ 10, 25, 50, 100, 250 ]} rowsPerPage={rowsPerPage} onPageChange={(event, value) => handlePageChange(value)} onRowsPerPageChange={(event) => handleRowsPerPageChange(Number(event.target.value))} @@ -1077,7 +1076,7 @@ function EntityList({table, launchProcess}: Props): JSX.Element useEffect(() => { updateTable(); - }, [pageNumber, rowsPerPage, columnSortModel]); + }, [ pageNumber, rowsPerPage, columnSortModel ]); /////////////////////////////////////////////////////////////////////////////////////////////////////////////// // for state changes that DO change the filter, call to update the table - and DO clear out the totalRecords // @@ -1086,13 +1085,13 @@ function EntityList({table, launchProcess}: Props): JSX.Element { setTotalRecords(null); updateTable(); - }, [tableState, filterModel]); + }, [ tableState, filterModel ]); useEffect(() => { document.documentElement.scrollTop = 0; document.scrollingElement.scrollTop = 0; - }, [pageNumber, rowsPerPage]); + }, [ pageNumber, rowsPerPage ]); return ( @@ -1160,7 +1159,7 @@ function EntityList({table, launchProcess}: Props): JSX.Element onColumnOrderChange={handleColumnOrderChange} onSelectionModelChange={selectionChanged} onSortModelChange={handleSortChange} - sortingOrder={["asc", "desc"]} + sortingOrder={[ "asc", "desc" ]} sortModel={columnSortModel} getRowClassName={(params) => (params.indexRelativeToCurrentPage % 2 === 0 ? "even" : "odd")} /> @@ -1171,7 +1170,9 @@ function EntityList({table, launchProcess}: Props): JSX.Element { activeModalProcess && closeModalProcess(event, reason)}> - +
+ +
} diff --git a/src/qqq/pages/entity-view/components/ViewContents/index.tsx b/src/qqq/pages/entity-view/components/ViewContents/index.tsx index 0effbbe..abd9e3e 100644 --- a/src/qqq/pages/entity-view/components/ViewContents/index.tsx +++ b/src/qqq/pages/entity-view/components/ViewContents/index.tsx @@ -451,7 +451,9 @@ function ViewContents({id, table, launchProcess}: Props): JSX.Element { activeModalProcess && closeModalProcess(event, reason)}> - +
+ +
} diff --git a/src/qqq/pages/process-run/index.tsx b/src/qqq/pages/process-run/index.tsx index 19a03e6..5428ad7 100644 --- a/src/qqq/pages/process-run/index.tsx +++ b/src/qqq/pages/process-run/index.tsx @@ -26,6 +26,7 @@ import {QFrontendComponent} from "@kingsrook/qqq-frontend-core/lib/model/metaDat import {QFrontendStepMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFrontendStepMetaData"; import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance"; import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData"; +import {QTableSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableSection"; import {QJobComplete} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobComplete"; import {QJobError} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobError"; import {QJobRunning} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobRunning"; @@ -52,6 +53,7 @@ import BaseLayout from "qqq/components/BaseLayout"; import {QCancelButton, QSubmitButton} from "qqq/components/QButtons"; import QDynamicForm from "qqq/components/QDynamicForm"; import DynamicFormUtils from "qqq/components/QDynamicForm/utils/DynamicFormUtils"; +import QRecordSidebar from "qqq/components/QRecordSidebar"; import MDBox from "qqq/components/Temporary/MDBox"; import MDButton from "qqq/components/Temporary/MDButton"; import MDProgress from "qqq/components/Temporary/MDProgress"; @@ -59,6 +61,7 @@ import MDTypography from "qqq/components/Temporary/MDTypography"; import {QGoogleDriveFolderPickerWrapper} from "qqq/pages/process-run/components/QGoogleDriveFolderPickerWrapper"; import QValidationReview from "qqq/pages/process-run/components/QValidationReview"; import QClient from "qqq/utils/QClient"; +import QTableUtils from "qqq/utils/QTableUtils"; import QValueUtils from "qqq/utils/QValueUtils"; import QProcessSummaryResults from "./components/QProcessSummaryResults"; @@ -66,9 +69,9 @@ interface Props { process?: QProcessMetaData; defaultProcessValues?: any; - isModal?: boolean - recordIds?: string | QQueryFilter - closeModalHandler?: (event: object, reason: string) => void + isModal?: boolean; + recordIds?: string | QQueryFilter; + closeModalHandler?: (event: object, reason: string) => void; } const INITIAL_RETRY_MILLIS = 1_500; @@ -95,6 +98,7 @@ function ProcessRun({process, defaultProcessValues, isModal, recordIds, closeMod const [needInitialLoad, setNeedInitialLoad] = useState(true); const [processMetaData, setProcessMetaData] = useState(null); const [tableMetaData, setTableMetaData] = useState(null); + const [tableSections, setTableSections] = useState(null as QTableSection[]); const [qInstance, setQInstance] = useState(null as QInstance); const [processValues, setProcessValues] = useState({} as any); const [processError, _setProcessError] = useState(null as string); @@ -303,6 +307,17 @@ function ProcessRun({process, defaultProcessValues, isModal, recordIds, closeMod ); } + const {formFields, values, errors, touched} = formData; + let localTableSections = tableSections; + if(localTableSections == null) + { + ////////////////////////////////////////////////////////////////////////////////////////////////////// + // if the table sections (ones that actually have fields to edit) haven't been built yet, do so now // + ////////////////////////////////////////////////////////////////////////////////////////////////////// + localTableSections = tableMetaData ? QTableUtils.getSectionsForRecordSidebar(tableMetaData, Object.keys(formFields)) : null; + setTableSections(localTableSections); + } + return ( <> @@ -337,7 +352,61 @@ function ProcessRun({process, defaultProcessValues, isModal, recordIds, closeMod } { component.type === QComponentType.BULK_EDIT_FORM && ( - + tableMetaData && localTableSections ? + + + + + + + {localTableSections.map((section: QTableSection, index: number) => + { + const name = section.name + console.log(formData); + console.log(section.fieldNames); + + const sectionFormFields = {}; + for(let i = 0; i 0) + { + const sectionFormData = { + formFields: sectionFormFields, + values: values, + errors: errors, + touched: touched + }; + + return ( + + + + {section.label} + + + + + + + ); + } + else + { + return (
); + } + } + )} +
+
+ : ) } { @@ -505,7 +574,7 @@ function ProcessRun({process, defaultProcessValues, isModal, recordIds, closeMod } return (rs); - } + }; ////////////////////////////////////////////////////////////////////////////////////////////////////////////// // handle moving to another step in the process - e.g., after the backend told us what screen to show next. // @@ -517,7 +586,7 @@ function ProcessRun({process, defaultProcessValues, isModal, recordIds, closeMod console.log("No process meta data yet, so returning early"); return; } - setPageHeader(processMetaData.label) + setPageHeader(processMetaData.label); let newIndex = null; if (typeof newStep === "number") @@ -560,6 +629,8 @@ function ProcessRun({process, defaultProcessValues, isModal, recordIds, closeMod { let fullFieldList = getFullFieldList(activeStep, processValues); const formData = DynamicFormUtils.getFormData(fullFieldList); + DynamicFormUtils.addPossibleValueProps(formData.dynamicFormFields, fullFieldList, tableMetaData.name, null); + dynamicFormFields = formData.dynamicFormFields; formValidations = formData.formValidations; @@ -592,7 +663,7 @@ function ProcessRun({process, defaultProcessValues, isModal, recordIds, closeMod dynamicFormFields[fieldName] = dynamicFormValue; initialValues[fieldName] = initialValue; formValidations[fieldName] = validation; - } + }; if (doesStepHaveComponent(activeStep, QComponentType.VALIDATION_REVIEW_SCREEN)) { @@ -602,9 +673,9 @@ function ProcessRun({process, defaultProcessValues, isModal, recordIds, closeMod if (doesStepHaveComponent(activeStep, QComponentType.GOOGLE_DRIVE_SELECT_FOLDER)) { - addField("googleDriveAccessToken", {type: "hidden", omitFromQDynamicForm: true}, null, null); - addField("googleDriveFolderId", {type: "hidden", omitFromQDynamicForm: true}, null, null); - addField("googleDriveFolderName", {type: "hidden", omitFromQDynamicForm: true}, null, null); + addField("googleDriveAccessToken", {type: "hidden", omitFromQDynamicForm: true}, "", null); + addField("googleDriveFolderId", {type: "hidden", omitFromQDynamicForm: true}, "", null); + addField("googleDriveFolderName", {type: "hidden", omitFromQDynamicForm: true}, "", null); } if (Object.keys(dynamicFormFields).length > 0) @@ -673,6 +744,8 @@ function ProcessRun({process, defaultProcessValues, isModal, recordIds, closeMod } }); + DynamicFormUtils.addPossibleValueProps(newDynamicFormFields, fullFieldList, tableMetaData.name, null); + setFormFields(newDynamicFormFields); setValidationScheme(Yup.object().shape(newFormValidations)); } @@ -853,9 +926,9 @@ function ProcessRun({process, defaultProcessValues, isModal, recordIds, closeMod // queryStringPairsForInit.push("recordsParam=filterId"); // queryStringPairsForInit.push(`filterId=${urlSearchParams.get("filterId")}`); // } - else if(recordIds) + else if (recordIds) { - if(typeof recordIds === "string") + if (typeof recordIds === "string") { queryStringPairsForInit.push("recordsParam=recordIds"); queryStringPairsForInit.push(`recordIds=${recordIds}`); @@ -991,7 +1064,7 @@ function ProcessRun({process, defaultProcessValues, isModal, recordIds, closeMod const handleCancelClicked = () => { - if(isModal && closeModalHandler) + if (isModal && closeModalHandler) { closeModalHandler(null, "cancelClicked"); return; @@ -1056,8 +1129,8 @@ function ProcessRun({process, defaultProcessValues, isModal, recordIds, closeMod {/*************************************************************************** - ** step content - e.g., the appropriate form or other screen for the step ** - ***************************************************************************/} + ** step content - e.g., the appropriate form or other screen for the step ** + ***************************************************************************/} {getDynamicStepContent( activeStepIndex, activeStep, @@ -1073,8 +1146,8 @@ function ProcessRun({process, defaultProcessValues, isModal, recordIds, closeMod setFieldValue, )} {/******************************** - ** back &| next/submit buttons ** - ********************************/} + ** back &| next/submit buttons ** + ********************************/} {true || activeStepIndex === 0 ? ( @@ -1121,7 +1194,7 @@ function ProcessRun({process, defaultProcessValues, isModal, recordIds, closeMod ); - if(isModal) + if (isModal) { return ( diff --git a/src/qqq/styles/qqq-override-styles.css b/src/qqq/styles/qqq-override-styles.css index d73e359..633c544 100644 --- a/src/qqq/styles/qqq-override-styles.css +++ b/src/qqq/styles/qqq-override-styles.css @@ -176,4 +176,10 @@ input[type=search]::-ms-reveal { display: none; width : 0; height: 0; } input[type="search"]::-webkit-search-decoration, input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-results-button, -input[type="search"]::-webkit-search-results-decoration { display: none; } \ No newline at end of file +input[type="search"]::-webkit-search-results-decoration { display: none; } + +/* Shrink the big margin-bottom on modal processes */ +.modalProcess>.MuiBox-root>.MuiBox-root +{ + margin-bottom: 24px; +} \ No newline at end of file diff --git a/src/qqq/utils/QTableUtils.ts b/src/qqq/utils/QTableUtils.ts index 14e81ed..c852610 100644 --- a/src/qqq/utils/QTableUtils.ts +++ b/src/qqq/utils/QTableUtils.ts @@ -28,16 +28,60 @@ import {QTableSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTa *******************************************************************************/ class QTableUtils { - public static getSectionsForRecordSidebar(tableMetaData: QTableMetaData): QTableSection[] + + /******************************************************************************* + ** + *******************************************************************************/ + public static getSectionsForRecordSidebar(tableMetaData: QTableMetaData, allowedKeys: any = null): QTableSection[] { if (tableMetaData.sections) { - return (tableMetaData.sections); + if (allowedKeys) + { + const allowedKeySet = new Set(); + allowedKeys.forEach((k: string) => allowedKeySet.add(k)); + + const allowedSections: QTableSection[] = []; + + for (let i = 0; i < tableMetaData.sections.length; i++) + { + const section = tableMetaData.sections[i]; + if (section.fieldNames) + { + for (let j = 0; j < section.fieldNames.length; j++) + { + if (allowedKeySet.has(section.fieldNames[j])) + { + allowedSections.push(section); + break; + } + } + } + } + + return (allowedSections); + } + else + { + return (tableMetaData.sections); + } } else { + let fieldNames = [...tableMetaData.fields.keys()]; + if (allowedKeys) + { + fieldNames = []; + for (const fieldName in tableMetaData.fields.keys()) + { + if (allowedKeys[fieldName]) + { + fieldNames.push(fieldName); + } + } + } return ([new QTableSection({ - iconName: "description", label: "All Fields", name: "allFields", fieldNames: [...tableMetaData.fields.keys()], + iconName: "description", label: "All Fields", name: "allFields", fieldNames: [...fieldNames], })]); } }