Feedback from code reviews

This commit is contained in:
2022-08-11 10:26:59 -05:00
parent 6506115bb0
commit ca39a34970
15 changed files with 176 additions and 196 deletions

View File

@ -60,6 +60,7 @@
"max-len": "off", "max-len": "off",
"no-console": "off", "no-console": "off",
"no-constant-condition": "off", "no-constant-condition": "off",
"no-continue": "off",
"no-shadow": "off", "no-shadow": "off",
"no-unused-vars": "off", "no-unused-vars": "off",
"no-plusplus": "off", "no-plusplus": "off",

View File

@ -13,7 +13,7 @@
"@fullcalendar/interaction": "5.10.0", "@fullcalendar/interaction": "5.10.0",
"@fullcalendar/react": "5.10.0", "@fullcalendar/react": "5.10.0",
"@fullcalendar/timegrid": "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/icons-material": "5.4.1",
"@mui/material": "5.4.1", "@mui/material": "5.4.1",
"@mui/styled-engine": "5.4.1", "@mui/styled-engine": "5.4.1",

View File

@ -35,7 +35,6 @@ import {setMiniSidenav, setOpenConfigurator, useMaterialUIController} from "cont
import nfLogo from "assets/images/nutrifresh_one_icon_white.png"; import nfLogo from "assets/images/nutrifresh_one_icon_white.png";
import {Md5} from "ts-md5/dist/md5"; import {Md5} from "ts-md5/dist/md5";
import {useCookies} from "react-cookie"; import {useCookies} from "react-cookie";
import {QAppMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppMetaData";
import EntityCreate from "./qqq/pages/entity-create"; import EntityCreate from "./qqq/pages/entity-create";
import EntityList from "./qqq/pages/entity-list"; import EntityList from "./qqq/pages/entity-list";
import EntityView from "./qqq/pages/entity-view"; 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 {QAppNodeType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppNodeType";
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance"; import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
import QProcessUtils from "qqq/utils/QProcessUtils"; 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 // // 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(); } = useAuth0();
const [loadingToken, setLoadingToken] = useState(false); const [loadingToken, setLoadingToken] = useState(false);
const [isFullyAuthenticated, setIsFullyAuthenticated] = useState(false); const [isFullyAuthenticated, setIsFullyAuthenticated] = useState(false);
const [profileRoutes, setProfileRoutes] = useState({});
useEffect(() => useEffect(() =>
{ {
@ -137,8 +138,6 @@ export default function App()
const [sideNavRoutes, setSideNavRoutes] = useState(getStaticRoutes()); const [sideNavRoutes, setSideNavRoutes] = useState(getStaticRoutes());
const [appRoutes, setAppRoutes] = useState(null as any); const [appRoutes, setAppRoutes] = useState(null as any);
const dynamicAppChildElement = <AppHome />;
//////////////////////////////////////////// ////////////////////////////////////////////
// load qqq meta data to make more routes // // load qqq meta data to make more routes //
//////////////////////////////////////////// ////////////////////////////////////////////
@ -152,7 +151,7 @@ export default function App()
(async () => (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}`; const path = `${parentPath}/${app.name}`;
if (app.type !== QAppNodeType.APP) if (app.type !== QAppNodeType.APP)
@ -160,62 +159,65 @@ export default function App()
return; return;
} }
if (app.type === QAppNodeType.APP && depth <= 2) if (depth > 2)
{ {
const childList: any[] = []; console.warn("App depth is greater than 2 - not including app in side nav...");
app.children.forEach((child: QAppMetaData) => return;
{ }
addAppToSideNavList(child, childList, path, depth + 1);
});
if (childList.length === 0) const childList: any[] = [];
{ app.children.forEach((child: QAppTreeNode) =>
if (depth === 0) {
{ addAppToSideNavList(child, childList, path, depth + 1);
///////////////////////////////////////////////////// });
// at level 0, the entry must always be a collapse //
///////////////////////////////////////////////////// if (childList.length === 0)
appList.push({ {
type: "collapse", if (depth === 0)
name: app.label,
key: app.name,
route: path,
icon: <Icon fontSize="medium">{app.iconName}</Icon>,
noCollapse: true,
component: <AppHome />,
});
}
else
{
appList.push({
name: app.label,
key: app.name,
route: path,
icon: <Icon fontSize="medium">{app.iconName}</Icon>,
component: <AppHome />,
});
}
}
else
{ {
/////////////////////////////////////////////////////
// at level 0, the entry must always be a collapse //
/////////////////////////////////////////////////////
appList.push({ appList.push({
type: "collapse", type: "collapse",
name: app.label, name: app.label,
key: app.name, key: app.name,
dropdown: true, route: path,
icon: <Icon fontSize="medium">{app.iconName}</Icon>, icon: <Icon fontSize="medium">{app.iconName}</Icon>,
collapse: childList, noCollapse: true,
component: <AppHome />,
});
}
else
{
appList.push({
name: app.label,
key: app.name,
route: path,
icon: <Icon fontSize="medium">{app.iconName}</Icon>,
component: <AppHome />,
}); });
} }
} }
else
{
appList.push({
type: "collapse",
name: app.label,
key: app.name,
dropdown: true,
icon: <Icon fontSize="medium">{app.iconName}</Icon>,
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}`; const path = `${parentPath}/${app.name}`;
if (app.type === QAppNodeType.APP) if (app.type === QAppNodeType.APP)
{ {
app.children.forEach((child: QAppMetaData) => app.children.forEach((child: QAppTreeNode) =>
{ {
addAppToAppRoutesList(metaData, child, routeList, path, depth + 1); addAppToAppRoutesList(metaData, child, routeList, path, depth + 1);
}); });
@ -261,16 +263,6 @@ export default function App()
const processesForTable = QProcessUtils.getProcessesForTable(metaData, table.name, true); const processesForTable = QProcessUtils.getProcessesForTable(metaData, table.name, true);
processesForTable.forEach((process) => 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({ routeList.push({
name: process.label, name: process.label,
key: process.name, key: process.name,
@ -295,11 +287,11 @@ export default function App()
{ {
const metaData = await QClient.getInstance().loadMetaData(); const metaData = await QClient.getInstance().loadMetaData();
let profileRoute = {}; let profileRoutes = {};
const gravatarBase = "http://www.gravatar.com/avatar/"; const gravatarBase = "http://www.gravatar.com/avatar/";
const hash = Md5.hashStr(user.email); const hash = Md5.hashStr(user.email);
const profilePicture = `${gravatarBase}${hash}`; const profilePicture = `${gravatarBase}${hash}`;
profileRoute = { profileRoutes = {
type: "collapse", type: "collapse",
name: user.name, name: user.name,
key: user.name, key: user.name,
@ -319,6 +311,7 @@ export default function App()
}, },
], ],
}; };
setProfileRoutes(profileRoutes);
const sideNavAppList = [] as any[]; const sideNavAppList = [] as any[];
const appRoutesList = [] as any[]; const appRoutesList = [] as any[];
@ -331,7 +324,7 @@ export default function App()
const newSideNavRoutes = getStaticRoutes(); const newSideNavRoutes = getStaticRoutes();
// @ts-ignore // @ts-ignore
newSideNavRoutes.unshift(profileRoute); newSideNavRoutes.unshift(profileRoutes);
for (let i = 0; i < sideNavAppList.length; i++) for (let i = 0; i < sideNavAppList.length; i++)
{ {
newSideNavRoutes.push(sideNavAppList[i]); newSideNavRoutes.push(sideNavAppList[i]);
@ -386,6 +379,9 @@ export default function App()
document.scrollingElement.scrollTop = 0; document.scrollingElement.scrollTop = 0;
}, [pathname]); }, [pathname]);
///////////////////////////////////////////////////////////////////////////////////////////
// convert an object that works for the Sidenav into one that works for the react-router //
///////////////////////////////////////////////////////////////////////////////////////////
const getRoutes = (allRoutes: any[]): any => allRoutes.map( const getRoutes = (allRoutes: any[]): any => allRoutes.map(
(route: { (route: {
collapse: any; collapse: any;
@ -452,7 +448,8 @@ export default function App()
<Routes> <Routes>
<Route path="*" element={<Navigate to="/dashboards/analytics" />} /> <Route path="*" element={<Navigate to="/dashboards/analytics" />} />
{appRoutes && getRoutes(appRoutes)} {appRoutes && getRoutes(appRoutes)}
{sideNavRoutes && getRoutes(sideNavRoutes)} {getRoutes(getStaticRoutes())}
{profileRoutes && getRoutes([profileRoutes])}
</Routes> </Routes>
</ThemeProvider> </ThemeProvider>
) )

View File

@ -29,6 +29,8 @@ import Avatar from "@mui/material/Avatar";
import Icon from "@mui/material/Icon"; import Icon from "@mui/material/Icon";
import QRecordSidebar from "qqq/components/QRecordSidebar"; import QRecordSidebar from "qqq/components/QRecordSidebar";
import QTableUtils from "qqq/utils/QTableUtils"; 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 interface Props
{ {
@ -46,6 +48,7 @@ function EntityForm({table, id}: Props): JSX.Element
const [initialValues, setInitialValues] = useState({} as { [key: string]: string }); const [initialValues, setInitialValues] = useState({} as { [key: string]: string });
const [formFields, setFormFields] = useState(null as Map<string, any>); const [formFields, setFormFields] = useState(null as Map<string, any>);
const [t1sectionName, setT1SectionName] = useState(null as string); const [t1sectionName, setT1SectionName] = useState(null as string);
const [nonT1Sections, setNonT1Sections] = useState([] as QSection[]);
const [alertContent, setAlertContent] = useState(""); const [alertContent, setAlertContent] = useState("");
@ -53,7 +56,7 @@ function EntityForm({table, id}: Props): JSX.Element
const [formValues, setFormValues] = useState({} as { [key: string]: string }); const [formValues, setFormValues] = useState({} as { [key: string]: string });
const [tableMetaData, setTableMetaData] = useState(null as QTableMetaData); const [tableMetaData, setTableMetaData] = useState(null as QTableMetaData);
const [record, setRecord] = useState(null as QRecord); 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 [, forceUpdate] = useReducer((x) => x + 1, 0);
const navigate = useNavigate(); const navigate = useNavigate();
@ -76,7 +79,7 @@ function EntityForm({table, id}: Props): JSX.Element
{ {
return <div>Loading...</div>; return <div>Loading...</div>;
} }
return <QDynamicForm formData={formData} primaryKeyId={tableMetaData.primaryKeyField} />; return <QDynamicForm formData={formData} />;
} }
if (!asyncLoadInited) if (!asyncLoadInited)
@ -131,6 +134,7 @@ function EntityForm({table, id}: Props): JSX.Element
///////////////////////////////////// /////////////////////////////////////
const dynamicFormFieldsBySection = new Map<string, any>(); const dynamicFormFieldsBySection = new Map<string, any>();
let t1sectionName; let t1sectionName;
const nonT1Sections: QSection[] = [];
for (let i = 0; i < tableSections.length; i++) for (let i = 0; i < tableSections.length; i++)
{ {
const section = tableSections[i]; const section = tableSections[i];
@ -140,6 +144,11 @@ function EntityForm({table, id}: Props): JSX.Element
{ {
const fieldName = section.fieldNames[j]; const fieldName = section.fieldNames[j];
const field = tableMetaData.fields.get(fieldName); 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) if (id !== null || field.isEditable)
{ {
sectionDynamicFormFields.push(dynamicFormFields[fieldName]); sectionDynamicFormFields.push(dynamicFormFields[fieldName]);
@ -153,6 +162,7 @@ function EntityForm({table, id}: Props): JSX.Element
//////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////
tableSections.splice(i, 1); tableSections.splice(i, 1);
i--; i--;
continue;
} }
else else
{ {
@ -166,8 +176,13 @@ function EntityForm({table, id}: Props): JSX.Element
{ {
t1sectionName = section.name; t1sectionName = section.name;
} }
else
{
nonT1Sections.push(section);
}
} }
setT1SectionName(t1sectionName); setT1SectionName(t1sectionName);
setNonT1Sections(nonT1Sections);
setFormFields(dynamicFormFieldsBySection); setFormFields(dynamicFormFieldsBySection);
setValidations(Yup.object().shape(formValidations)); setValidations(Yup.object().shape(formValidations));
@ -177,6 +192,11 @@ function EntityForm({table, id}: Props): JSX.Element
const handleCancelClicked = () => 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) if (id !== null)
{ {
const path = `${location.pathname.replace(/\/edit$/, "")}`; const path = `${location.pathname.replace(/\/edit$/, "")}`;
@ -274,7 +294,7 @@ function EntityForm({table, id}: Props): JSX.Element
<Card id={`${t1sectionName}`} sx={{overflow: "visible"}}> <Card id={`${t1sectionName}`} sx={{overflow: "visible"}}>
<MDBox display="flex" p={3} pb={1}> <MDBox display="flex" p={3} pb={1}>
<MDBox mr={1.5}> <MDBox mr={1.5}>
<Avatar sx={{bgcolor: "rgb(26, 115, 232)"}}> <Avatar sx={{bgcolor: colors.info.main}}>
<Icon> <Icon>
{tableMetaData?.iconName} {tableMetaData?.iconName}
</Icon> </Icon>
@ -295,23 +315,22 @@ function EntityForm({table, id}: Props): JSX.Element
} }
</Card> </Card>
</MDBox> </MDBox>
{tableSections && formFields ? tableSections.map((section: any) => (section.name !== t1sectionName {formFields && nonT1Sections.length ? nonT1Sections.map((section: QSection) => (
? ( <MDBox key={`edit-card-${section.name}`} pb={3}>
<MDBox key={`edit-card-${section.name}`} pb={3}> <Card id={section.name} sx={{overflow: "visible"}}>
<Card id={section.name} sx={{overflow: "visible"}}> <MDTypography variant="h5" p={3} pb={1}>
<MDTypography variant="h5" p={3} pb={1}> {section.label}
{section.label} </MDTypography>
</MDTypography> <MDBox pb={3} px={3}>
<MDBox pb={3} px={3}> <MDBox p={3} width="100%">
<MDBox p={3} width="100%"> {
{ getFormSection(values, touched, formFields.get(section.name), errors)
getFormSection(values, touched, formFields.get(section.name), errors) }
}
</MDBox>
</MDBox> </MDBox>
</Card> </MDBox>
</MDBox> </Card>
) : null)) : null} </MDBox>
)) : null}
<MDBox component="div" p={3}> <MDBox component="div" p={3}>
<Grid container justifyContent="flex-end" spacing={3}> <Grid container justifyContent="flex-end" spacing={3}>

View File

@ -58,7 +58,7 @@ import {
} from "context"; } from "context";
// qqq // qqq
import QBreadcrumbs from "qqq/components/QBreadcrumbs"; import QBreadcrumbs, {routeToLabel} from "qqq/components/QBreadcrumbs";
// Declaring prop types for Navbar // Declaring prop types for Navbar
interface Props 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 (
<AppBar <AppBar

View File

@ -44,9 +44,14 @@ const ucFirst = (input: string): string =>
return (input.substring(0, 1).toUpperCase() + input.substring(1)); 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); return (label);
}; };

View File

@ -61,12 +61,10 @@ interface QDeleteButtonProps
export function QDeleteButton({onClickHandler}: QDeleteButtonProps): JSX.Element export function QDeleteButton({onClickHandler}: QDeleteButtonProps): JSX.Element
{ {
return ( return (
<MDBox ml={3} mr={3}> <MDBox ml={3} mr={3} width={standardWidth}>
<MDBox width={standardWidth}> <MDButton variant="gradient" color="primary" size="small" onClick={onClickHandler} fullWidth startIcon={<Icon>delete</Icon>}>
<MDButton variant="gradient" color="primary" size="small" onClick={onClickHandler} fullWidth startIcon={<Icon>delete</Icon>}> Delete
Delete </MDButton>
</MDButton>
</MDBox>
</MDBox> </MDBox>
); );
} }
@ -74,13 +72,11 @@ export function QDeleteButton({onClickHandler}: QDeleteButtonProps): JSX.Element
export function QEditButton(): JSX.Element export function QEditButton(): JSX.Element
{ {
return ( return (
<MDBox> <MDBox width={standardWidth}>
<Link to="edit"> <Link to="edit">
<MDBox width={standardWidth}> <MDButton variant="gradient" color="dark" size="small" fullWidth startIcon={<Icon>edit</Icon>}>
<MDButton variant="gradient" color="dark" size="small" fullWidth startIcon={<Icon>edit</Icon>}> Edit
Edit </MDButton>
</MDButton>
</MDBox>
</Link> </Link>
</MDBox> </MDBox>
); );

View File

@ -34,7 +34,6 @@ import QDynamicFormField from "qqq/components/QDynamicFormField";
interface Props { interface Props {
formLabel?: string; formLabel?: string;
formData: any; formData: any;
primaryKeyId?: string;
bulkEditMode?: boolean; bulkEditMode?: boolean;
bulkEditSwitchChangeHandler?: any bulkEditSwitchChangeHandler?: any
} }
@ -42,7 +41,7 @@ interface Props {
function QDynamicForm(props: Props): JSX.Element function QDynamicForm(props: Props): JSX.Element
{ {
const { const {
formData, formLabel, primaryKeyId, bulkEditMode, bulkEditSwitchChangeHandler, formData, formLabel, bulkEditMode, bulkEditSwitchChangeHandler,
} = props; } = props;
const { const {
formFields, values, errors, touched, formFields, values, errors, touched,
@ -116,7 +115,7 @@ function QDynamicForm(props: Props): JSX.Element
error={errors[fieldName] && touched[fieldName]} error={errors[fieldName] && touched[fieldName]}
bulkEditMode={bulkEditMode} bulkEditMode={bulkEditMode}
bulkEditSwitchChangeHandler={bulkEditSwitchChanged} bulkEditSwitchChangeHandler={bulkEditSwitchChanged}
success={!errors[fieldName] && touched[fieldName]} success={`${values[fieldName]}` !== "" && !errors[fieldName] && touched[fieldName]}
/> />
</Grid> </Grid>
); );
@ -129,7 +128,6 @@ function QDynamicForm(props: Props): JSX.Element
QDynamicForm.defaultProps = { QDynamicForm.defaultProps = {
formLabel: undefined, formLabel: undefined,
primaryKeyId: undefined,
bulkEditMode: false, bulkEditMode: false,
bulkEditSwitchChangeHandler: () => bulkEditSwitchChangeHandler: () =>
{}, {},

View File

@ -26,7 +26,6 @@ import {ErrorMessage, Field} from "formik";
import MDBox from "components/MDBox"; import MDBox from "components/MDBox";
import MDTypography from "components/MDTypography"; import MDTypography from "components/MDTypography";
import MDInput from "components/MDInput"; import MDInput from "components/MDInput";
import QDynamicForm from "qqq/components/QDynamicForm";
import React, {useState} from "react"; import React, {useState} from "react";
import Grid from "@mui/material/Grid"; import Grid from "@mui/material/Grid";
import Switch from "@mui/material/Switch"; import Switch from "@mui/material/Switch";

View File

@ -28,9 +28,10 @@ import MDBox from "components/MDBox";
import MDTypography from "components/MDTypography"; import MDTypography from "components/MDTypography";
import Card from "@mui/material/Card"; import Card from "@mui/material/Card";
import {Theme} from "@mui/material/styles"; import {Theme} from "@mui/material/styles";
import {QSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QSection";
interface Props { interface Props {
tableSections: any; tableSections: QSection[];
light?: boolean; light?: boolean;
} }
@ -40,11 +41,11 @@ function QRecordSidebar({tableSections, light}: Props): JSX.Element
<Card sx={{borderRadius: ({borders: {borderRadius}}) => borderRadius.lg, position: "sticky", top: "1%"}}> <Card sx={{borderRadius: ({borders: {borderRadius}}) => borderRadius.lg, position: "sticky", top: "1%"}}>
<MDBox component="ul" display="flex" flexDirection="column" p={2} m={0} sx={{listStyle: "none"}}> <MDBox component="ul" display="flex" flexDirection="column" p={2} m={0} sx={{listStyle: "none"}}>
{ {
tableSections ? tableSections.map(({icon, label, name}: any, key: number) => ( tableSections ? tableSections.map((section: QSection, key: number) => (
<MDBox key={`section-${name}`} component="li" pt={key === 0 ? 0 : 1}> <MDBox key={`section-${section.name}`} component="li" pt={key === 0 ? 0 : 1}>
<MDTypography <MDTypography
component="a" component="a"
href={`#${name}`} href={`#${section.name}`}
variant="button" variant="button"
fontWeight="regular" fontWeight="regular"
sx={({ sx={({
@ -65,9 +66,9 @@ function QRecordSidebar({tableSections, light}: Props): JSX.Element
})} })}
> >
<MDBox mr={1.5} lineHeight={1} color="black"> <MDBox mr={1.5} lineHeight={1} color="black">
<Icon fontSize="small">{icon}</Icon> <Icon fontSize="small">{section.iconName}</Icon>
</MDBox> </MDBox>
{label} {section.label}
</MDTypography> </MDTypography>
</MDBox> </MDBox>
)) : null )) : null

View File

@ -29,7 +29,6 @@ import MDBox from "components/MDBox";
import EntityForm from "qqq/components/EntityForm"; import EntityForm from "qqq/components/EntityForm";
import BaseLayout from "qqq/components/BaseLayout"; import BaseLayout from "qqq/components/BaseLayout";
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
import {useParams} from "react-router-dom";
interface Props interface Props
{ {

View File

@ -134,6 +134,7 @@ function EntityList({table}: Props): JSX.Element
const [columnVisibilityModel, setColumnVisibilityModel] = useState(defaultVisibility); const [columnVisibilityModel, setColumnVisibilityModel] = useState(defaultVisibility);
const [gridMouseDownX, setGridMouseDownX] = useState(0); const [gridMouseDownX, setGridMouseDownX] = useState(0);
const [gridMouseDownY, setGridMouseDownY] = useState(0); const [gridMouseDownY, setGridMouseDownY] = useState(0);
const [pinnedColumns, setPinnedColumns] = useState({left: ["__check__", "id"]});
const instance = useRef({timer: null}); const instance = useRef({timer: null});
const [, forceUpdate] = useReducer((x) => x + 1, 0); const [, forceUpdate] = useReducer((x) => x + 1, 0);
@ -223,6 +224,7 @@ function EntityList({table}: Props): JSX.Element
}); });
setColumnSortModel(columnSortModel); setColumnSortModel(columnSortModel);
} }
setPinnedColumns({left: ["__check__", tableMetaData.primaryKeyField]});
const qFilter = buildQFilter(); const qFilter = buildQFilter();
@ -626,15 +628,8 @@ function EntityList({table}: Props): JSX.Element
// eslint-disable-next-line react/no-unstable-nested-components // eslint-disable-next-line react/no-unstable-nested-components
function CustomToolbar() function CustomToolbar()
{ {
function gtcMouseDown(e: React.MouseEvent<HTMLDivElement>)
{
console.log(e.target);
}
return ( return (
<GridToolbarContainer <GridToolbarContainer>
onMouseDown={(e) => gtcMouseDown(e)}
>
<div> <div>
<Button <Button
id="refresh-button" id="refresh-button"
@ -711,7 +706,7 @@ function EntityList({table}: Props): JSX.Element
<MenuItem onClick={bulkLoadClicked}>Bulk Load</MenuItem> <MenuItem onClick={bulkLoadClicked}>Bulk Load</MenuItem>
<MenuItem onClick={bulkEditClicked}>Bulk Edit</MenuItem> <MenuItem onClick={bulkEditClicked}>Bulk Edit</MenuItem>
<MenuItem onClick={bulkDeleteClicked}>Bulk Delete</MenuItem> <MenuItem onClick={bulkDeleteClicked}>Bulk Delete</MenuItem>
<MenuItem divider /> {tableProcesses.length > 0 && <MenuItem divider />}
{tableProcesses.map((process) => ( {tableProcesses.map((process) => (
<MenuItem key={process.name} onClick={() => processClicked(process)}>{process.label}</MenuItem> <MenuItem key={process.name} onClick={() => processClicked(process)}>{process.label}</MenuItem>
))} ))}
@ -759,9 +754,7 @@ function EntityList({table}: Props): JSX.Element
<MDBox display="flex" justifyContent="flex-end" alignItems="flex-start" mb={2}> <MDBox display="flex" justifyContent="flex-end" alignItems="flex-start" mb={2}>
<MDBox display="flex" width="150px"> <MDBox display="flex" width="150px">
{tableProcesses.length > 0 && ( <QActionsMenuButton isOpen={actionsMenu} onClickHandler={openActionsMenu} />
<QActionsMenuButton isOpen={actionsMenu} onClickHandler={openActionsMenu} />
)}
{renderActionsMenu} {renderActionsMenu}
</MDBox> </MDBox>
@ -774,7 +767,7 @@ function EntityList({table}: Props): JSX.Element
<MDBox height="100%"> <MDBox height="100%">
<DataGridPro <DataGridPro
components={{Toolbar: CustomToolbar, Pagination: CustomPagination, LoadingOverlay: Loading}} components={{Toolbar: CustomToolbar, Pagination: CustomPagination, LoadingOverlay: Loading}}
pinnedColumns={{left: ["__check__", "id"]}} initialState={{pinnedColumns: pinnedColumns}}
pagination pagination
paginationMode="server" paginationMode="server"
sortingMode="server" sortingMode="server"

View File

@ -52,6 +52,8 @@ import Icon from "@mui/material/Icon";
import Avatar from "@mui/material/Avatar"; import Avatar from "@mui/material/Avatar";
import QRecordSidebar from "qqq/components/QRecordSidebar"; import QRecordSidebar from "qqq/components/QRecordSidebar";
import QTableUtils from "qqq/utils/QTableUtils"; import QTableUtils from "qqq/utils/QTableUtils";
import colors from "assets/theme/base/colors";
import {QSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QSection";
const qController = QClient.getInstance(); const qController = QClient.getInstance();
@ -73,11 +75,13 @@ function ViewContents({id, table}: Props): JSX.Element
const [asyncLoadInited, setAsyncLoadInited] = useState(false); const [asyncLoadInited, setAsyncLoadInited] = useState(false);
const [nameValues, setNameValues] = useState([] as JSX.Element[]); const [nameValues, setNameValues] = useState([] as JSX.Element[]);
const [sectionFieldElements, setSectionFieldElements] = useState(null as Map<string, JSX.Element[]>); const [sectionFieldElements, setSectionFieldElements] = useState(null as Map<string, JSX.Element[]>);
const [t1Section, setT1Section] = useState(null as JSX.Element); const [deleteConfirmationOpen, setDeleteConfirmationOpen] = useState(false);
const [open, setOpen] = useState(false);
const [tableMetaData, setTableMetaData] = useState(null); const [tableMetaData, setTableMetaData] = useState(null);
const [record, setRecord] = useState(null as QRecord); const [record, setRecord] = useState(null as QRecord);
const [tableSections, setTableSections] = useState(null as any); const [tableSections, setTableSections] = useState([] as QSection[]);
const [t1SectionName, setT1SectionName] = useState(null as string);
const [t1SectionElement, setT1SectionElement] = useState(null as JSX.Element);
const [nonT1TableSections, setNonT1TableSections] = useState([] as QSection[]);
const [tableProcesses, setTableProcesses] = useState([] as QProcessMetaData[]); const [tableProcesses, setTableProcesses] = useState([] as QProcessMetaData[]);
const [actionsMenu, setActionsMenu] = useState(null); const [actionsMenu, setActionsMenu] = useState(null);
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
@ -120,6 +124,7 @@ function ViewContents({id, table}: Props): JSX.Element
// make elements with the values for each section // // make elements with the values for each section //
//////////////////////////////////////////////////// ////////////////////////////////////////////////////
const sectionFieldElements = new Map(); const sectionFieldElements = new Map();
const nonT1TableSections = [];
for (let i = 0; i < tableSections.length; i++) for (let i = 0; i < tableSections.length; i++)
{ {
const section = tableSections[i]; const section = tableSections[i];
@ -145,45 +150,29 @@ function ViewContents({id, table}: Props): JSX.Element
if (section.tier === "T1") if (section.tier === "T1")
{ {
setT1Section(sectionFieldElements.get(section.name)); setT1SectionElement(sectionFieldElements.get(section.name));
setT1SectionName(section.name);
}
else
{
nonT1TableSections.push(tableSections[i]);
} }
} }
setSectionFieldElements(sectionFieldElements); setSectionFieldElements(sectionFieldElements);
setNonT1TableSections(nonT1TableSections);
// todo - delete this
const sortedKeys = [...record.values.keys()].sort();
sortedKeys.forEach((key) =>
{
if (key !== tableMetaData.primaryKeyField)
{
nameValues.push(
<MDBox key={key} display="flex" py={1} pr={2}>
<MDTypography variant="button" fontWeight="bold" textTransform="capitalize">
{tableMetaData.fields.get(key).label}
: &nbsp;
</MDTypography>
<MDTypography variant="button" fontWeight="regular" color="text">
&nbsp;
{QValueUtils.getDisplayValue(tableMetaData.fields.get(key), record)}
</MDTypography>
</MDBox>,
);
}
});
setNameValues(nameValues);
forceUpdate(); forceUpdate();
})(); })();
} }
const handleClickConfirmOpen = () => const handleClickDeleteButton = () =>
{ {
setOpen(true); setDeleteConfirmationOpen(true);
}; };
const handleClickConfirmClose = () => const handleDeleteConfirmClose = () =>
{ {
setOpen(false); setDeleteConfirmationOpen(false);
}; };
const handleDelete = (event: { preventDefault: () => void }) => const handleDelete = (event: { preventDefault: () => void }) =>
@ -225,12 +214,12 @@ function ViewContents({id, table}: Props): JSX.Element
<MenuItem onClick={() => <MenuItem onClick={() =>
{ {
setActionsMenu(null); setActionsMenu(null);
handleClickConfirmOpen(); handleClickDeleteButton();
}} }}
> >
Delete Delete
</MenuItem> </MenuItem>
<MenuItem divider /> {tableProcesses.length > 0 && <MenuItem divider />}
{tableProcesses.map((process) => ( {tableProcesses.map((process) => (
<MenuItem key={process.name} onClick={() => processClicked(process)}>{process.label}</MenuItem> <MenuItem key={process.name} onClick={() => processClicked(process)}>{process.label}</MenuItem>
))} ))}
@ -261,10 +250,10 @@ function ViewContents({id, table}: Props): JSX.Element
<Grid container spacing={3}> <Grid container spacing={3}>
<Grid item xs={12} mb={3}> <Grid item xs={12} mb={3}>
<Card> <Card id={t1SectionName}>
<MDBox display="flex" p={3} pb={1}> <MDBox display="flex" p={3} pb={1}>
<MDBox mr={1.5}> <MDBox mr={1.5}>
<Avatar sx={{bgcolor: "rgb(26, 115, 232)"}}> <Avatar sx={{bgcolor: colors.info.main}}>
<Icon> <Icon>
{tableMetaData?.iconName} {tableMetaData?.iconName}
</Icon> </Icon>
@ -274,33 +263,29 @@ function ViewContents({id, table}: Props): JSX.Element
<MDTypography variant="h5"> <MDTypography variant="h5">
{tableMetaData && record ? `Viewing ${tableMetaData?.label}: ${record?.recordLabel}` : ""} {tableMetaData && record ? `Viewing ${tableMetaData?.label}: ${record?.recordLabel}` : ""}
</MDTypography> </MDTypography>
{tableProcesses.length > 0 && ( <QActionsMenuButton isOpen={actionsMenu} onClickHandler={openActionsMenu} />
<QActionsMenuButton isOpen={actionsMenu} onClickHandler={openActionsMenu} />
)}
{renderActionsMenu} {renderActionsMenu}
</MDBox> </MDBox>
</MDBox> </MDBox>
{t1Section ? (<MDBox p={3} pt={0}>{t1Section}</MDBox>) : null} {t1SectionElement ? (<MDBox p={3} pt={0}>{t1SectionElement}</MDBox>) : null}
</Card> </Card>
</Grid> </Grid>
</Grid> </Grid>
{tableSections && sectionFieldElements ? tableSections.map(({ {nonT1TableSections.length > 0 ? nonT1TableSections.map(({
icon, label, name, fieldNames, tier, iconName, label, name, fieldNames, tier,
}: any) => (tier !== "T1" }: any) => (
? ( <MDBox mb={3} key={name}>
<MDBox mb={3} key={name}> <Card key={name} id={name} sx={{overflow: "visible"}}>
<Card key={name} id={name} sx={{overflow: "visible"}}> <MDTypography variant="h5" p={3} pb={1}>
<MDTypography variant="h5" p={3} pb={1}> {label}
{label} </MDTypography>
</MDTypography> <MDBox p={3} pt={0} flexDirection="column">{sectionFieldElements.get(name)}</MDBox>
<MDBox p={3} pt={0} flexDirection="column">{sectionFieldElements.get(name)}</MDBox> </Card>
</Card> </MDBox>
</MDBox> )) : null}
) : null)) : null}
<MDBox component="form" p={3}> <MDBox component="form" p={3}>
<Grid container justifyContent="flex-end" spacing={3}> <Grid container justifyContent="flex-end" spacing={3}>
<QDeleteButton onClickHandler={handleClickConfirmOpen} /> <QDeleteButton onClickHandler={handleClickDeleteButton} />
<QEditButton /> <QEditButton />
</Grid> </Grid>
</MDBox> </MDBox>
@ -310,8 +295,8 @@ function ViewContents({id, table}: Props): JSX.Element
{/* Delete confirmation Dialog */} {/* Delete confirmation Dialog */}
<Dialog <Dialog
open={open} open={deleteConfirmationOpen}
onClose={handleClickConfirmClose} onClose={handleDeleteConfirmClose}
aria-labelledby="alert-dialog-title" aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description" aria-describedby="alert-dialog-description"
> >
@ -322,7 +307,7 @@ function ViewContents({id, table}: Props): JSX.Element
</DialogContentText> </DialogContentText>
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button onClick={handleClickConfirmClose}>No</Button> <Button onClick={handleDeleteConfirmClose}>No</Button>
<Button onClick={handleDelete} autoFocus> <Button onClick={handleDelete} autoFocus>
Yes Yes
</Button> </Button>

View File

@ -81,7 +81,8 @@
/* shrink font on in the pagination control */ /* shrink font on in the pagination control */
.MuiTablePagination-displayedRows, .MuiTablePagination-displayedRows,
.MuiTablePagination-selectLabel, .MuiTablePagination-selectLabel,
.MuiTablePagination-select.MuiSelect-select.MuiSelect-standard .MuiTablePagination-select.MuiSelect-select.MuiSelect-standard,
.MuiDataGrid-selectedRowCount
{ {
font-size: 0.85rem !important; font-size: 0.85rem !important;
} }
@ -93,14 +94,8 @@
border-radius: 5px !important; 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 */ /* move the green check / red x down to align with the calendar icon */
.MuiFormControl-root .MuiFormControl-root
{ {
background-position-y: 1.4rem; background-position-y: 1.4rem !important;
} }

View File

@ -20,6 +20,7 @@
*/ */
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; 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 ** Utility class for working with QQQ Tables
@ -27,27 +28,18 @@ import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QT
*******************************************************************************/ *******************************************************************************/
class QTableUtils class QTableUtils
{ {
public static getSectionsForRecordSidebar(tableMetaData: QTableMetaData): any public static getSectionsForRecordSidebar(tableMetaData: QTableMetaData): QSection[]
{ {
const tableSections = [];
if (tableMetaData.sections) if (tableMetaData.sections)
{ {
for (let i = 0; i < tableMetaData.sections.length; i++) return (tableMetaData.sections);
{
const section = tableMetaData.sections[i];
tableSections.push({
icon: section.iconName, label: section.label, name: section.name, fieldNames: section.fieldNames, tier: section.tier,
});
}
} }
else else
{ {
tableSections.push({ return ([new QSection({
icon: "description", label: "All Fields", name: "allFields", fieldNames: [...tableMetaData.fields.keys()], iconName: "description", label: "All Fields", name: "allFields", fieldNames: [...tableMetaData.fields.keys()],
}); })]);
} }
return (tableSections);
} }
} }