mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-17 21:00:45 +00:00
Feedback from code reviews
This commit is contained in:
@ -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",
|
||||
|
@ -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",
|
||||
|
111
src/App.tsx
111
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 = <AppHome />;
|
||||
|
||||
////////////////////////////////////////////
|
||||
// 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: <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
|
||||
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: <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}`;
|
||||
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()
|
||||
<Routes>
|
||||
<Route path="*" element={<Navigate to="/dashboards/analytics" />} />
|
||||
{appRoutes && getRoutes(appRoutes)}
|
||||
{sideNavRoutes && getRoutes(sideNavRoutes)}
|
||||
{getRoutes(getStaticRoutes())}
|
||||
{profileRoutes && getRoutes([profileRoutes])}
|
||||
</Routes>
|
||||
</ThemeProvider>
|
||||
)
|
||||
|
@ -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<string, any>);
|
||||
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 <div>Loading...</div>;
|
||||
}
|
||||
return <QDynamicForm formData={formData} primaryKeyId={tableMetaData.primaryKeyField} />;
|
||||
return <QDynamicForm formData={formData} />;
|
||||
}
|
||||
|
||||
if (!asyncLoadInited)
|
||||
@ -131,6 +134,7 @@ function EntityForm({table, id}: Props): JSX.Element
|
||||
/////////////////////////////////////
|
||||
const dynamicFormFieldsBySection = new Map<string, any>();
|
||||
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
|
||||
<Card id={`${t1sectionName}`} sx={{overflow: "visible"}}>
|
||||
<MDBox display="flex" p={3} pb={1}>
|
||||
<MDBox mr={1.5}>
|
||||
<Avatar sx={{bgcolor: "rgb(26, 115, 232)"}}>
|
||||
<Avatar sx={{bgcolor: colors.info.main}}>
|
||||
<Icon>
|
||||
{tableMetaData?.iconName}
|
||||
</Icon>
|
||||
@ -295,23 +315,22 @@ function EntityForm({table, id}: Props): JSX.Element
|
||||
}
|
||||
</Card>
|
||||
</MDBox>
|
||||
{tableSections && formFields ? tableSections.map((section: any) => (section.name !== t1sectionName
|
||||
? (
|
||||
<MDBox key={`edit-card-${section.name}`} pb={3}>
|
||||
<Card id={section.name} sx={{overflow: "visible"}}>
|
||||
<MDTypography variant="h5" p={3} pb={1}>
|
||||
{section.label}
|
||||
</MDTypography>
|
||||
<MDBox pb={3} px={3}>
|
||||
<MDBox p={3} width="100%">
|
||||
{
|
||||
getFormSection(values, touched, formFields.get(section.name), errors)
|
||||
}
|
||||
</MDBox>
|
||||
{formFields && nonT1Sections.length ? nonT1Sections.map((section: QSection) => (
|
||||
<MDBox key={`edit-card-${section.name}`} pb={3}>
|
||||
<Card id={section.name} sx={{overflow: "visible"}}>
|
||||
<MDTypography variant="h5" p={3} pb={1}>
|
||||
{section.label}
|
||||
</MDTypography>
|
||||
<MDBox pb={3} px={3}>
|
||||
<MDBox p={3} width="100%">
|
||||
{
|
||||
getFormSection(values, touched, formFields.get(section.name), errors)
|
||||
}
|
||||
</MDBox>
|
||||
</Card>
|
||||
</MDBox>
|
||||
) : null)) : null}
|
||||
</MDBox>
|
||||
</Card>
|
||||
</MDBox>
|
||||
)) : null}
|
||||
|
||||
<MDBox component="div" p={3}>
|
||||
<Grid container justifyContent="flex-end" spacing={3}>
|
||||
|
@ -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 (
|
||||
<AppBar
|
||||
|
@ -44,9 +44,14 @@ const ucFirst = (input: string): string =>
|
||||
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);
|
||||
};
|
||||
|
||||
|
@ -61,12 +61,10 @@ interface QDeleteButtonProps
|
||||
export function QDeleteButton({onClickHandler}: QDeleteButtonProps): JSX.Element
|
||||
{
|
||||
return (
|
||||
<MDBox ml={3} mr={3}>
|
||||
<MDBox width={standardWidth}>
|
||||
<MDButton variant="gradient" color="primary" size="small" onClick={onClickHandler} fullWidth startIcon={<Icon>delete</Icon>}>
|
||||
Delete
|
||||
</MDButton>
|
||||
</MDBox>
|
||||
<MDBox ml={3} mr={3} width={standardWidth}>
|
||||
<MDButton variant="gradient" color="primary" size="small" onClick={onClickHandler} fullWidth startIcon={<Icon>delete</Icon>}>
|
||||
Delete
|
||||
</MDButton>
|
||||
</MDBox>
|
||||
);
|
||||
}
|
||||
@ -74,13 +72,11 @@ export function QDeleteButton({onClickHandler}: QDeleteButtonProps): JSX.Element
|
||||
export function QEditButton(): JSX.Element
|
||||
{
|
||||
return (
|
||||
<MDBox>
|
||||
<MDBox width={standardWidth}>
|
||||
<Link to="edit">
|
||||
<MDBox width={standardWidth}>
|
||||
<MDButton variant="gradient" color="dark" size="small" fullWidth startIcon={<Icon>edit</Icon>}>
|
||||
Edit
|
||||
</MDButton>
|
||||
</MDBox>
|
||||
<MDButton variant="gradient" color="dark" size="small" fullWidth startIcon={<Icon>edit</Icon>}>
|
||||
Edit
|
||||
</MDButton>
|
||||
</Link>
|
||||
</MDBox>
|
||||
);
|
||||
|
@ -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]}
|
||||
/>
|
||||
</Grid>
|
||||
);
|
||||
@ -129,7 +128,6 @@ function QDynamicForm(props: Props): JSX.Element
|
||||
|
||||
QDynamicForm.defaultProps = {
|
||||
formLabel: undefined,
|
||||
primaryKeyId: undefined,
|
||||
bulkEditMode: false,
|
||||
bulkEditSwitchChangeHandler: () =>
|
||||
{},
|
||||
|
@ -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";
|
||||
|
@ -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
|
||||
<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"}}>
|
||||
{
|
||||
tableSections ? tableSections.map(({icon, label, name}: any, key: number) => (
|
||||
<MDBox key={`section-${name}`} component="li" pt={key === 0 ? 0 : 1}>
|
||||
tableSections ? tableSections.map((section: QSection, key: number) => (
|
||||
<MDBox key={`section-${section.name}`} component="li" pt={key === 0 ? 0 : 1}>
|
||||
<MDTypography
|
||||
component="a"
|
||||
href={`#${name}`}
|
||||
href={`#${section.name}`}
|
||||
variant="button"
|
||||
fontWeight="regular"
|
||||
sx={({
|
||||
@ -65,9 +66,9 @@ function QRecordSidebar({tableSections, light}: Props): JSX.Element
|
||||
})}
|
||||
>
|
||||
<MDBox mr={1.5} lineHeight={1} color="black">
|
||||
<Icon fontSize="small">{icon}</Icon>
|
||||
<Icon fontSize="small">{section.iconName}</Icon>
|
||||
</MDBox>
|
||||
{label}
|
||||
{section.label}
|
||||
</MDTypography>
|
||||
</MDBox>
|
||||
)) : null
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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<HTMLDivElement>)
|
||||
{
|
||||
console.log(e.target);
|
||||
}
|
||||
|
||||
return (
|
||||
<GridToolbarContainer
|
||||
onMouseDown={(e) => gtcMouseDown(e)}
|
||||
>
|
||||
<GridToolbarContainer>
|
||||
<div>
|
||||
<Button
|
||||
id="refresh-button"
|
||||
@ -711,7 +706,7 @@ function EntityList({table}: Props): JSX.Element
|
||||
<MenuItem onClick={bulkLoadClicked}>Bulk Load</MenuItem>
|
||||
<MenuItem onClick={bulkEditClicked}>Bulk Edit</MenuItem>
|
||||
<MenuItem onClick={bulkDeleteClicked}>Bulk Delete</MenuItem>
|
||||
<MenuItem divider />
|
||||
{tableProcesses.length > 0 && <MenuItem divider />}
|
||||
{tableProcesses.map((process) => (
|
||||
<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" width="150px">
|
||||
{tableProcesses.length > 0 && (
|
||||
<QActionsMenuButton isOpen={actionsMenu} onClickHandler={openActionsMenu} />
|
||||
)}
|
||||
<QActionsMenuButton isOpen={actionsMenu} onClickHandler={openActionsMenu} />
|
||||
{renderActionsMenu}
|
||||
</MDBox>
|
||||
|
||||
@ -774,7 +767,7 @@ function EntityList({table}: Props): JSX.Element
|
||||
<MDBox height="100%">
|
||||
<DataGridPro
|
||||
components={{Toolbar: CustomToolbar, Pagination: CustomPagination, LoadingOverlay: Loading}}
|
||||
pinnedColumns={{left: ["__check__", "id"]}}
|
||||
initialState={{pinnedColumns: pinnedColumns}}
|
||||
pagination
|
||||
paginationMode="server"
|
||||
sortingMode="server"
|
||||
|
@ -52,6 +52,8 @@ import Icon from "@mui/material/Icon";
|
||||
import Avatar from "@mui/material/Avatar";
|
||||
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";
|
||||
|
||||
const qController = QClient.getInstance();
|
||||
|
||||
@ -73,11 +75,13 @@ function ViewContents({id, table}: Props): JSX.Element
|
||||
const [asyncLoadInited, setAsyncLoadInited] = useState(false);
|
||||
const [nameValues, setNameValues] = useState([] as JSX.Element[]);
|
||||
const [sectionFieldElements, setSectionFieldElements] = useState(null as Map<string, JSX.Element[]>);
|
||||
const [t1Section, setT1Section] = useState(null as JSX.Element);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [deleteConfirmationOpen, setDeleteConfirmationOpen] = useState(false);
|
||||
const [tableMetaData, setTableMetaData] = useState(null);
|
||||
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 [actionsMenu, setActionsMenu] = useState(null);
|
||||
const [searchParams] = useSearchParams();
|
||||
@ -120,6 +124,7 @@ function ViewContents({id, table}: Props): JSX.Element
|
||||
// make elements with the values for each section //
|
||||
////////////////////////////////////////////////////
|
||||
const sectionFieldElements = new Map();
|
||||
const nonT1TableSections = [];
|
||||
for (let i = 0; i < tableSections.length; i++)
|
||||
{
|
||||
const section = tableSections[i];
|
||||
@ -145,45 +150,29 @@ function ViewContents({id, table}: Props): JSX.Element
|
||||
|
||||
if (section.tier === "T1")
|
||||
{
|
||||
setT1Section(sectionFieldElements.get(section.name));
|
||||
setT1SectionElement(sectionFieldElements.get(section.name));
|
||||
setT1SectionName(section.name);
|
||||
}
|
||||
else
|
||||
{
|
||||
nonT1TableSections.push(tableSections[i]);
|
||||
}
|
||||
}
|
||||
setSectionFieldElements(sectionFieldElements);
|
||||
|
||||
// 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}
|
||||
:
|
||||
</MDTypography>
|
||||
<MDTypography variant="button" fontWeight="regular" color="text">
|
||||
|
||||
{QValueUtils.getDisplayValue(tableMetaData.fields.get(key), record)}
|
||||
</MDTypography>
|
||||
</MDBox>,
|
||||
);
|
||||
}
|
||||
});
|
||||
setNameValues(nameValues);
|
||||
setNonT1TableSections(nonT1TableSections);
|
||||
|
||||
forceUpdate();
|
||||
})();
|
||||
}
|
||||
|
||||
const handleClickConfirmOpen = () =>
|
||||
const handleClickDeleteButton = () =>
|
||||
{
|
||||
setOpen(true);
|
||||
setDeleteConfirmationOpen(true);
|
||||
};
|
||||
|
||||
const handleClickConfirmClose = () =>
|
||||
const handleDeleteConfirmClose = () =>
|
||||
{
|
||||
setOpen(false);
|
||||
setDeleteConfirmationOpen(false);
|
||||
};
|
||||
|
||||
const handleDelete = (event: { preventDefault: () => void }) =>
|
||||
@ -225,12 +214,12 @@ function ViewContents({id, table}: Props): JSX.Element
|
||||
<MenuItem onClick={() =>
|
||||
{
|
||||
setActionsMenu(null);
|
||||
handleClickConfirmOpen();
|
||||
handleClickDeleteButton();
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</MenuItem>
|
||||
<MenuItem divider />
|
||||
{tableProcesses.length > 0 && <MenuItem divider />}
|
||||
{tableProcesses.map((process) => (
|
||||
<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 item xs={12} mb={3}>
|
||||
<Card>
|
||||
<Card id={t1SectionName}>
|
||||
<MDBox display="flex" p={3} pb={1}>
|
||||
<MDBox mr={1.5}>
|
||||
<Avatar sx={{bgcolor: "rgb(26, 115, 232)"}}>
|
||||
<Avatar sx={{bgcolor: colors.info.main}}>
|
||||
<Icon>
|
||||
{tableMetaData?.iconName}
|
||||
</Icon>
|
||||
@ -274,33 +263,29 @@ function ViewContents({id, table}: Props): JSX.Element
|
||||
<MDTypography variant="h5">
|
||||
{tableMetaData && record ? `Viewing ${tableMetaData?.label}: ${record?.recordLabel}` : ""}
|
||||
</MDTypography>
|
||||
{tableProcesses.length > 0 && (
|
||||
<QActionsMenuButton isOpen={actionsMenu} onClickHandler={openActionsMenu} />
|
||||
)}
|
||||
<QActionsMenuButton isOpen={actionsMenu} onClickHandler={openActionsMenu} />
|
||||
{renderActionsMenu}
|
||||
</MDBox>
|
||||
</MDBox>
|
||||
{t1Section ? (<MDBox p={3} pt={0}>{t1Section}</MDBox>) : null}
|
||||
{t1SectionElement ? (<MDBox p={3} pt={0}>{t1SectionElement}</MDBox>) : null}
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
{tableSections && sectionFieldElements ? tableSections.map(({
|
||||
icon, label, name, fieldNames, tier,
|
||||
}: any) => (tier !== "T1"
|
||||
? (
|
||||
<MDBox mb={3} key={name}>
|
||||
<Card key={name} id={name} sx={{overflow: "visible"}}>
|
||||
<MDTypography variant="h5" p={3} pb={1}>
|
||||
{label}
|
||||
</MDTypography>
|
||||
<MDBox p={3} pt={0} flexDirection="column">{sectionFieldElements.get(name)}</MDBox>
|
||||
</Card>
|
||||
</MDBox>
|
||||
) : null)) : null}
|
||||
|
||||
{nonT1TableSections.length > 0 ? nonT1TableSections.map(({
|
||||
iconName, label, name, fieldNames, tier,
|
||||
}: any) => (
|
||||
<MDBox mb={3} key={name}>
|
||||
<Card key={name} id={name} sx={{overflow: "visible"}}>
|
||||
<MDTypography variant="h5" p={3} pb={1}>
|
||||
{label}
|
||||
</MDTypography>
|
||||
<MDBox p={3} pt={0} flexDirection="column">{sectionFieldElements.get(name)}</MDBox>
|
||||
</Card>
|
||||
</MDBox>
|
||||
)) : null}
|
||||
<MDBox component="form" p={3}>
|
||||
<Grid container justifyContent="flex-end" spacing={3}>
|
||||
<QDeleteButton onClickHandler={handleClickConfirmOpen} />
|
||||
<QDeleteButton onClickHandler={handleClickDeleteButton} />
|
||||
<QEditButton />
|
||||
</Grid>
|
||||
</MDBox>
|
||||
@ -310,8 +295,8 @@ function ViewContents({id, table}: Props): JSX.Element
|
||||
|
||||
{/* Delete confirmation Dialog */}
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={handleClickConfirmClose}
|
||||
open={deleteConfirmationOpen}
|
||||
onClose={handleDeleteConfirmClose}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
@ -322,7 +307,7 @@ function ViewContents({id, table}: Props): JSX.Element
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClickConfirmClose}>No</Button>
|
||||
<Button onClick={handleDeleteConfirmClose}>No</Button>
|
||||
<Button onClick={handleDelete} autoFocus>
|
||||
Yes
|
||||
</Button>
|
||||
|
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user