From ca39a3497054c1f5f5fd7ddaaab43611bdb39160 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Thu, 11 Aug 2022 10:26:59 -0500 Subject: [PATCH] Feedback from code reviews --- .eslintrc.json | 1 + package.json | 2 +- src/App.tsx | 111 +++++++++--------- src/qqq/components/EntityForm/index.tsx | 57 ++++++--- src/qqq/components/Navbar/index.tsx | 4 +- src/qqq/components/QBreadcrumbs/index.tsx | 9 +- src/qqq/components/QButtons/index.tsx | 20 ++-- src/qqq/components/QDynamicForm/index.tsx | 6 +- .../components/QDynamicFormField/index.tsx | 1 - src/qqq/components/QRecordSidebar/index.tsx | 13 +- src/qqq/pages/entity-create/index.tsx | 1 - src/qqq/pages/entity-list/index.tsx | 19 +-- .../components/ViewContents/index.tsx | 97 +++++++-------- src/qqq/styles/qqq-override-styles.css | 11 +- src/qqq/utils/QTableUtils.ts | 20 +--- 15 files changed, 176 insertions(+), 196 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index b9242a5..d9b248b 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -60,6 +60,7 @@ "max-len": "off", "no-console": "off", "no-constant-condition": "off", + "no-continue": "off", "no-shadow": "off", "no-unused-vars": "off", "no-plusplus": "off", diff --git a/package.json b/package.json index 71758d9..b4b223a 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "@fullcalendar/interaction": "5.10.0", "@fullcalendar/react": "5.10.0", "@fullcalendar/timegrid": "5.10.0", - "@kingsrook/qqq-frontend-core": "1.0.8", + "@kingsrook/qqq-frontend-core": "1.0.9", "@mui/icons-material": "5.4.1", "@mui/material": "5.4.1", "@mui/styled-engine": "5.4.1", diff --git a/src/App.tsx b/src/App.tsx index e16f4c0..0540bec 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -35,7 +35,6 @@ import {setMiniSidenav, setOpenConfigurator, useMaterialUIController} from "cont import nfLogo from "assets/images/nutrifresh_one_icon_white.png"; import {Md5} from "ts-md5/dist/md5"; import {useCookies} from "react-cookie"; -import {QAppMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppMetaData"; import EntityCreate from "./qqq/pages/entity-create"; import EntityList from "./qqq/pages/entity-list"; import EntityView from "./qqq/pages/entity-view"; @@ -51,6 +50,7 @@ import QClient from "./qqq/utils/QClient"; import {QAppNodeType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppNodeType"; import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance"; import QProcessUtils from "qqq/utils/QProcessUtils"; +import {QAppTreeNode} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppTreeNode"; /////////////////////////////////////////////////////////////////////////////////////////////// // define the parts of the nav that are static - before the qqq tables etc get dynamic added // @@ -94,6 +94,7 @@ export default function App() } = useAuth0(); const [loadingToken, setLoadingToken] = useState(false); const [isFullyAuthenticated, setIsFullyAuthenticated] = useState(false); + const [profileRoutes, setProfileRoutes] = useState({}); useEffect(() => { @@ -137,8 +138,6 @@ export default function App() const [sideNavRoutes, setSideNavRoutes] = useState(getStaticRoutes()); const [appRoutes, setAppRoutes] = useState(null as any); - const dynamicAppChildElement = ; - //////////////////////////////////////////// // load qqq meta data to make more routes // //////////////////////////////////////////// @@ -152,7 +151,7 @@ export default function App() (async () => { - function addAppToSideNavList(app: QAppMetaData, appList: any[], parentPath: string, depth: number) + function addAppToSideNavList(app: QAppTreeNode, appList: any[], parentPath: string, depth: number) { const path = `${parentPath}/${app.name}`; if (app.type !== QAppNodeType.APP) @@ -160,62 +159,65 @@ export default function App() return; } - if (app.type === QAppNodeType.APP && depth <= 2) + if (depth > 2) { - const childList: any[] = []; - app.children.forEach((child: QAppMetaData) => - { - addAppToSideNavList(child, childList, path, depth + 1); - }); + console.warn("App depth is greater than 2 - not including app in side nav..."); + return; + } - if (childList.length === 0) - { - if (depth === 0) - { - ///////////////////////////////////////////////////// - // at level 0, the entry must always be a collapse // - ///////////////////////////////////////////////////// - appList.push({ - type: "collapse", - name: app.label, - key: app.name, - route: path, - icon: {app.iconName}, - noCollapse: true, - component: , - }); - } - else - { - appList.push({ - name: app.label, - key: app.name, - route: path, - icon: {app.iconName}, - component: , - }); - } - } - else + const childList: any[] = []; + app.children.forEach((child: QAppTreeNode) => + { + addAppToSideNavList(child, childList, path, depth + 1); + }); + + if (childList.length === 0) + { + if (depth === 0) { + ///////////////////////////////////////////////////// + // at level 0, the entry must always be a collapse // + ///////////////////////////////////////////////////// appList.push({ type: "collapse", name: app.label, key: app.name, - dropdown: true, + route: path, icon: {app.iconName}, - collapse: childList, + noCollapse: true, + component: , + }); + } + else + { + appList.push({ + name: app.label, + key: app.name, + route: path, + icon: {app.iconName}, + component: , }); } } + else + { + appList.push({ + type: "collapse", + name: app.label, + key: app.name, + dropdown: true, + icon: {app.iconName}, + collapse: childList, + }); + } } - function addAppToAppRoutesList(metaData: QInstance, app: QAppMetaData, routeList: any[], parentPath: string, depth: number) + function addAppToAppRoutesList(metaData: QInstance, app: QAppTreeNode, routeList: any[], parentPath: string, depth: number) { const path = `${parentPath}/${app.name}`; if (app.type === QAppNodeType.APP) { - app.children.forEach((child: QAppMetaData) => + app.children.forEach((child: QAppTreeNode) => { addAppToAppRoutesList(metaData, child, routeList, path, depth + 1); }); @@ -261,16 +263,6 @@ export default function App() const processesForTable = QProcessUtils.getProcessesForTable(metaData, table.name, true); processesForTable.forEach((process) => { - //////////////////////////////////////////////////////////////////////////////////////////////////// - // special provision for the standard bulk processes - strip the table name from the process name // - // todo - this var isn't used - is this needed? - //////////////////////////////////////////////////////////////////////////////////////////////////// - let processName = process.name; - if (processName.startsWith(`${process.tableName}.`)) - { - processName = processName.replace(`${process.tableName}.`, ""); - } - routeList.push({ name: process.label, key: process.name, @@ -295,11 +287,11 @@ export default function App() { const metaData = await QClient.getInstance().loadMetaData(); - let profileRoute = {}; + let profileRoutes = {}; const gravatarBase = "http://www.gravatar.com/avatar/"; const hash = Md5.hashStr(user.email); const profilePicture = `${gravatarBase}${hash}`; - profileRoute = { + profileRoutes = { type: "collapse", name: user.name, key: user.name, @@ -319,6 +311,7 @@ export default function App() }, ], }; + setProfileRoutes(profileRoutes); const sideNavAppList = [] as any[]; const appRoutesList = [] as any[]; @@ -331,7 +324,7 @@ export default function App() const newSideNavRoutes = getStaticRoutes(); // @ts-ignore - newSideNavRoutes.unshift(profileRoute); + newSideNavRoutes.unshift(profileRoutes); for (let i = 0; i < sideNavAppList.length; i++) { newSideNavRoutes.push(sideNavAppList[i]); @@ -386,6 +379,9 @@ export default function App() document.scrollingElement.scrollTop = 0; }, [pathname]); + /////////////////////////////////////////////////////////////////////////////////////////// + // convert an object that works for the Sidenav into one that works for the react-router // + /////////////////////////////////////////////////////////////////////////////////////////// const getRoutes = (allRoutes: any[]): any => allRoutes.map( (route: { collapse: any; @@ -452,7 +448,8 @@ export default function App() } /> {appRoutes && getRoutes(appRoutes)} - {sideNavRoutes && getRoutes(sideNavRoutes)} + {getRoutes(getStaticRoutes())} + {profileRoutes && getRoutes([profileRoutes])} ) diff --git a/src/qqq/components/EntityForm/index.tsx b/src/qqq/components/EntityForm/index.tsx index 02c737c..2a2329a 100644 --- a/src/qqq/components/EntityForm/index.tsx +++ b/src/qqq/components/EntityForm/index.tsx @@ -29,6 +29,8 @@ import Avatar from "@mui/material/Avatar"; import Icon from "@mui/material/Icon"; import QRecordSidebar from "qqq/components/QRecordSidebar"; import QTableUtils from "qqq/utils/QTableUtils"; +import colors from "assets/theme/base/colors"; +import {QSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QSection"; interface Props { @@ -46,6 +48,7 @@ function EntityForm({table, id}: Props): JSX.Element const [initialValues, setInitialValues] = useState({} as { [key: string]: string }); const [formFields, setFormFields] = useState(null as Map); const [t1sectionName, setT1SectionName] = useState(null as string); + const [nonT1Sections, setNonT1Sections] = useState([] as QSection[]); const [alertContent, setAlertContent] = useState(""); @@ -53,7 +56,7 @@ function EntityForm({table, id}: Props): JSX.Element const [formValues, setFormValues] = useState({} as { [key: string]: string }); const [tableMetaData, setTableMetaData] = useState(null as QTableMetaData); const [record, setRecord] = useState(null as QRecord); - const [tableSections, setTableSections] = useState(null as any); + const [tableSections, setTableSections] = useState(null as QSection[]); const [, forceUpdate] = useReducer((x) => x + 1, 0); const navigate = useNavigate(); @@ -76,7 +79,7 @@ function EntityForm({table, id}: Props): JSX.Element { return
Loading...
; } - return ; + return ; } if (!asyncLoadInited) @@ -131,6 +134,7 @@ function EntityForm({table, id}: Props): JSX.Element ///////////////////////////////////// const dynamicFormFieldsBySection = new Map(); let t1sectionName; + const nonT1Sections: QSection[] = []; for (let i = 0; i < tableSections.length; i++) { const section = tableSections[i]; @@ -140,6 +144,11 @@ function EntityForm({table, id}: Props): JSX.Element { const fieldName = section.fieldNames[j]; const field = tableMetaData.fields.get(fieldName); + + //////////////////////////////////////////////////////////////////////////////////////////// + // if id !== null - means we're on the edit screen -- show all fields on the edit screen. // + // || (or) we're on the insert screen in which case, only show editable fields. // + //////////////////////////////////////////////////////////////////////////////////////////// if (id !== null || field.isEditable) { sectionDynamicFormFields.push(dynamicFormFields[fieldName]); @@ -153,6 +162,7 @@ function EntityForm({table, id}: Props): JSX.Element //////////////////////////////////////////////////////////////////////////////////////////////// tableSections.splice(i, 1); i--; + continue; } else { @@ -166,8 +176,13 @@ function EntityForm({table, id}: Props): JSX.Element { t1sectionName = section.name; } + else + { + nonT1Sections.push(section); + } } setT1SectionName(t1sectionName); + setNonT1Sections(nonT1Sections); setFormFields(dynamicFormFieldsBySection); setValidations(Yup.object().shape(formValidations)); @@ -177,6 +192,11 @@ function EntityForm({table, id}: Props): JSX.Element const handleCancelClicked = () => { + /////////////////////////////////////////////////////////////////////////////////////// + // todo - we might have rather just done a navigate(-1) (to keep history clean) // + // but if the user used the anchors on the page, this doesn't effectively cancel... // + // what we have here pushed a new history entry (I think?), so could be better // + /////////////////////////////////////////////////////////////////////////////////////// if (id !== null) { const path = `${location.pathname.replace(/\/edit$/, "")}`; @@ -274,7 +294,7 @@ function EntityForm({table, id}: Props): JSX.Element - + {tableMetaData?.iconName} @@ -295,23 +315,22 @@ function EntityForm({table, id}: Props): JSX.Element } - {tableSections && formFields ? tableSections.map((section: any) => (section.name !== t1sectionName - ? ( - - - - {section.label} - - - - { - getFormSection(values, touched, formFields.get(section.name), errors) - } - + {formFields && nonT1Sections.length ? nonT1Sections.map((section: QSection) => ( + + + + {section.label} + + + + { + getFormSection(values, touched, formFields.get(section.name), errors) + } - - - ) : null)) : null} + + + + )) : null} diff --git a/src/qqq/components/Navbar/index.tsx b/src/qqq/components/Navbar/index.tsx index 3639a3c..3b4ac40 100644 --- a/src/qqq/components/Navbar/index.tsx +++ b/src/qqq/components/Navbar/index.tsx @@ -58,7 +58,7 @@ import { } from "context"; // qqq -import QBreadcrumbs from "qqq/components/QBreadcrumbs"; +import QBreadcrumbs, {routeToLabel} from "qqq/components/QBreadcrumbs"; // Declaring prop types for Navbar interface Props @@ -156,7 +156,7 @@ function Navbar({absolute, light, isMini}: Props): JSX.Element }, }); - const breadcrumbTitle = route[route.length - 1].replace(/([A-Z])/g, " $1").trim(); + const breadcrumbTitle = routeToLabel(route[route.length - 1]); return ( return (input.substring(0, 1).toUpperCase() + input.substring(1)); }; -const routeToLabel = (route: string): string => +export const routeToLabel = (route: string): string => { - const label = ucFirst(route.replace(".", " ").replace("-", " ").replace("_", " ").replace(/([A-Z])/g, " $1")); + const label = ucFirst(route + .replace(".", " ") + .replace("-", " ") + .replace("_", " ") + .replace(/([a-z])([A-Z]+)/g, "$1 $2") // transform personUSA => person USA + .replace(/^([A-Z]+)([A-Z])([a-z])/, "$1 $2$3")); // transform USAPerson => USA Person return (label); }; diff --git a/src/qqq/components/QButtons/index.tsx b/src/qqq/components/QButtons/index.tsx index dbe9b1e..867ee66 100644 --- a/src/qqq/components/QButtons/index.tsx +++ b/src/qqq/components/QButtons/index.tsx @@ -61,12 +61,10 @@ interface QDeleteButtonProps export function QDeleteButton({onClickHandler}: QDeleteButtonProps): JSX.Element { return ( - - - delete}> - Delete - - + + delete}> + Delete + ); } @@ -74,13 +72,11 @@ export function QDeleteButton({onClickHandler}: QDeleteButtonProps): JSX.Element export function QEditButton(): JSX.Element { return ( - + - - edit}> - Edit - - + edit}> + Edit + ); diff --git a/src/qqq/components/QDynamicForm/index.tsx b/src/qqq/components/QDynamicForm/index.tsx index fb02131..23c6fc5 100644 --- a/src/qqq/components/QDynamicForm/index.tsx +++ b/src/qqq/components/QDynamicForm/index.tsx @@ -34,7 +34,6 @@ import QDynamicFormField from "qqq/components/QDynamicFormField"; interface Props { formLabel?: string; formData: any; - primaryKeyId?: string; bulkEditMode?: boolean; bulkEditSwitchChangeHandler?: any } @@ -42,7 +41,7 @@ interface Props { function QDynamicForm(props: Props): JSX.Element { const { - formData, formLabel, primaryKeyId, bulkEditMode, bulkEditSwitchChangeHandler, + formData, formLabel, bulkEditMode, bulkEditSwitchChangeHandler, } = props; const { formFields, values, errors, touched, @@ -116,7 +115,7 @@ function QDynamicForm(props: Props): JSX.Element error={errors[fieldName] && touched[fieldName]} bulkEditMode={bulkEditMode} bulkEditSwitchChangeHandler={bulkEditSwitchChanged} - success={!errors[fieldName] && touched[fieldName]} + success={`${values[fieldName]}` !== "" && !errors[fieldName] && touched[fieldName]} /> ); @@ -129,7 +128,6 @@ function QDynamicForm(props: Props): JSX.Element QDynamicForm.defaultProps = { formLabel: undefined, - primaryKeyId: undefined, bulkEditMode: false, bulkEditSwitchChangeHandler: () => {}, diff --git a/src/qqq/components/QDynamicFormField/index.tsx b/src/qqq/components/QDynamicFormField/index.tsx index b1705e2..dc5a9ff 100644 --- a/src/qqq/components/QDynamicFormField/index.tsx +++ b/src/qqq/components/QDynamicFormField/index.tsx @@ -26,7 +26,6 @@ import {ErrorMessage, Field} from "formik"; import MDBox from "components/MDBox"; import MDTypography from "components/MDTypography"; import MDInput from "components/MDInput"; -import QDynamicForm from "qqq/components/QDynamicForm"; import React, {useState} from "react"; import Grid from "@mui/material/Grid"; import Switch from "@mui/material/Switch"; diff --git a/src/qqq/components/QRecordSidebar/index.tsx b/src/qqq/components/QRecordSidebar/index.tsx index 1419652..85c5b04 100644 --- a/src/qqq/components/QRecordSidebar/index.tsx +++ b/src/qqq/components/QRecordSidebar/index.tsx @@ -28,9 +28,10 @@ import MDBox from "components/MDBox"; import MDTypography from "components/MDTypography"; import Card from "@mui/material/Card"; import {Theme} from "@mui/material/styles"; +import {QSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QSection"; interface Props { - tableSections: any; + tableSections: QSection[]; light?: boolean; } @@ -40,11 +41,11 @@ function QRecordSidebar({tableSections, light}: Props): JSX.Element borderRadius.lg, position: "sticky", top: "1%"}}> { - tableSections ? tableSections.map(({icon, label, name}: any, key: number) => ( - + tableSections ? tableSections.map((section: QSection, key: number) => ( + - {icon} + {section.iconName} - {label} + {section.label} )) : null diff --git a/src/qqq/pages/entity-create/index.tsx b/src/qqq/pages/entity-create/index.tsx index 91d1d80..cef402b 100644 --- a/src/qqq/pages/entity-create/index.tsx +++ b/src/qqq/pages/entity-create/index.tsx @@ -29,7 +29,6 @@ import MDBox from "components/MDBox"; import EntityForm from "qqq/components/EntityForm"; import BaseLayout from "qqq/components/BaseLayout"; import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; -import {useParams} from "react-router-dom"; interface Props { diff --git a/src/qqq/pages/entity-list/index.tsx b/src/qqq/pages/entity-list/index.tsx index 879faae..a6b4cf9 100644 --- a/src/qqq/pages/entity-list/index.tsx +++ b/src/qqq/pages/entity-list/index.tsx @@ -134,6 +134,7 @@ function EntityList({table}: Props): JSX.Element const [columnVisibilityModel, setColumnVisibilityModel] = useState(defaultVisibility); const [gridMouseDownX, setGridMouseDownX] = useState(0); const [gridMouseDownY, setGridMouseDownY] = useState(0); + const [pinnedColumns, setPinnedColumns] = useState({left: ["__check__", "id"]}); const instance = useRef({timer: null}); const [, forceUpdate] = useReducer((x) => x + 1, 0); @@ -223,6 +224,7 @@ function EntityList({table}: Props): JSX.Element }); setColumnSortModel(columnSortModel); } + setPinnedColumns({left: ["__check__", tableMetaData.primaryKeyField]}); const qFilter = buildQFilter(); @@ -626,15 +628,8 @@ function EntityList({table}: Props): JSX.Element // eslint-disable-next-line react/no-unstable-nested-components function CustomToolbar() { - function gtcMouseDown(e: React.MouseEvent) - { - console.log(e.target); - } - return ( - gtcMouseDown(e)} - > +
+ diff --git a/src/qqq/styles/qqq-override-styles.css b/src/qqq/styles/qqq-override-styles.css index 6b15f9a..4e9bf35 100644 --- a/src/qqq/styles/qqq-override-styles.css +++ b/src/qqq/styles/qqq-override-styles.css @@ -81,7 +81,8 @@ /* shrink font on in the pagination control */ .MuiTablePagination-displayedRows, .MuiTablePagination-selectLabel, -.MuiTablePagination-select.MuiSelect-select.MuiSelect-standard +.MuiTablePagination-select.MuiSelect-select.MuiSelect-standard, +.MuiDataGrid-selectedRowCount { font-size: 0.85rem !important; } @@ -93,14 +94,8 @@ border-radius: 5px !important; } -/* hide the selected row count (we show our own) */ -.MuiDataGrid-selectedRowCount -{ - visibility: hidden !important; -} - /* move the green check / red x down to align with the calendar icon */ .MuiFormControl-root { - background-position-y: 1.4rem; + background-position-y: 1.4rem !important; } \ No newline at end of file diff --git a/src/qqq/utils/QTableUtils.ts b/src/qqq/utils/QTableUtils.ts index 5ade1c0..d75cc73 100644 --- a/src/qqq/utils/QTableUtils.ts +++ b/src/qqq/utils/QTableUtils.ts @@ -20,6 +20,7 @@ */ import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; +import {QSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QSection"; /******************************************************************************* ** Utility class for working with QQQ Tables @@ -27,27 +28,18 @@ import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QT *******************************************************************************/ class QTableUtils { - public static getSectionsForRecordSidebar(tableMetaData: QTableMetaData): any + public static getSectionsForRecordSidebar(tableMetaData: QTableMetaData): QSection[] { - const tableSections = []; if (tableMetaData.sections) { - for (let i = 0; i < tableMetaData.sections.length; i++) - { - const section = tableMetaData.sections[i]; - tableSections.push({ - icon: section.iconName, label: section.label, name: section.name, fieldNames: section.fieldNames, tier: section.tier, - }); - } + return (tableMetaData.sections); } else { - tableSections.push({ - icon: "description", label: "All Fields", name: "allFields", fieldNames: [...tableMetaData.fields.keys()], - }); + return ([new QSection({ + iconName: "description", label: "All Fields", name: "allFields", fieldNames: [...tableMetaData.fields.keys()], + })]); } - - return (tableSections); } }