mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-18 05:10:45 +00:00
ONE-39: added filters, order by, pagination
This commit is contained in:
@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"printWidth": 100,
|
"printWidth": 100,
|
||||||
"trailingComma": "es5",
|
"trailingComma": "es5",
|
||||||
"tabWidth": 2,
|
|
||||||
"semi": true,
|
"semi": true,
|
||||||
"singleQuote": false,
|
"singleQuote": false,
|
||||||
"endOfLine": "auto"
|
"endOfLine": "auto"
|
||||||
|
60
package-lock.json
generated
60
package-lock.json
generated
@ -20,6 +20,7 @@
|
|||||||
"@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",
|
||||||
|
"@mui/x-data-grid": "5.13.0",
|
||||||
"@react-jvectormap/core": "1.0.1",
|
"@react-jvectormap/core": "1.0.1",
|
||||||
"@react-jvectormap/world": "1.0.0",
|
"@react-jvectormap/world": "1.0.0",
|
||||||
"@testing-library/jest-dom": "5.16.2",
|
"@testing-library/jest-dom": "5.16.2",
|
||||||
@ -3291,6 +3292,31 @@
|
|||||||
"react": "^17.0.0 || ^18.0.0"
|
"react": "^17.0.0 || ^18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@mui/x-data-grid": {
|
||||||
|
"version": "5.13.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-5.13.0.tgz",
|
||||||
|
"integrity": "sha512-x310qsOJFIT0JuqnDusM6DC4hlX6eeL9biEveb/y+hdeLa+VHSwrclS0M7e9UUwmh1MPza6VT4ceEWJjbySf3A==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.17.2",
|
||||||
|
"@mui/utils": "^5.4.1",
|
||||||
|
"clsx": "^1.2.1",
|
||||||
|
"prop-types": "^15.8.1",
|
||||||
|
"reselect": "^4.1.6"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/mui"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@mui/material": "^5.4.1",
|
||||||
|
"@mui/system": "^5.4.1",
|
||||||
|
"react": "^17.0.2 || ^18.0.0",
|
||||||
|
"react-dom": "^17.0.2 || ^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@nodelib/fs.scandir": {
|
"node_modules/@nodelib/fs.scandir": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
@ -5934,9 +5960,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/clsx": {
|
"node_modules/clsx": {
|
||||||
"version": "1.1.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
|
||||||
"integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==",
|
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
@ -14466,6 +14492,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
||||||
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
|
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/reselect": {
|
||||||
|
"version": "4.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.6.tgz",
|
||||||
|
"integrity": "sha512-ZovIuXqto7elwnxyXbBtCPo9YFEr3uJqj2rRbcOOog1bmu2Ag85M4hixSwFWyaBMKXNgvPaJ9OSu9SkBPIeJHQ=="
|
||||||
|
},
|
||||||
"node_modules/resolve": {
|
"node_modules/resolve": {
|
||||||
"version": "1.22.1",
|
"version": "1.22.1",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
|
||||||
@ -19213,6 +19244,18 @@
|
|||||||
"react-is": "^17.0.2"
|
"react-is": "^17.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@mui/x-data-grid": {
|
||||||
|
"version": "5.13.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-5.13.0.tgz",
|
||||||
|
"integrity": "sha512-x310qsOJFIT0JuqnDusM6DC4hlX6eeL9biEveb/y+hdeLa+VHSwrclS0M7e9UUwmh1MPza6VT4ceEWJjbySf3A==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.17.2",
|
||||||
|
"@mui/utils": "^5.4.1",
|
||||||
|
"clsx": "^1.2.1",
|
||||||
|
"prop-types": "^15.8.1",
|
||||||
|
"reselect": "^4.1.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@nodelib/fs.scandir": {
|
"@nodelib/fs.scandir": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
@ -21178,9 +21221,9 @@
|
|||||||
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="
|
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="
|
||||||
},
|
},
|
||||||
"clsx": {
|
"clsx": {
|
||||||
"version": "1.1.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
|
||||||
"integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA=="
|
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg=="
|
||||||
},
|
},
|
||||||
"co": {
|
"co": {
|
||||||
"version": "4.6.0",
|
"version": "4.6.0",
|
||||||
@ -27233,6 +27276,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
||||||
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
|
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
|
||||||
},
|
},
|
||||||
|
"reselect": {
|
||||||
|
"version": "4.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.6.tgz",
|
||||||
|
"integrity": "sha512-ZovIuXqto7elwnxyXbBtCPo9YFEr3uJqj2rRbcOOog1bmu2Ag85M4hixSwFWyaBMKXNgvPaJ9OSu9SkBPIeJHQ=="
|
||||||
|
},
|
||||||
"resolve": {
|
"resolve": {
|
||||||
"version": "1.22.1",
|
"version": "1.22.1",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
|
||||||
|
@ -12,7 +12,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.1",
|
"@kingsrook/qqq-frontend-core": "file:.yalc/@kingsrook/qqq-frontend-core",
|
||||||
"@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",
|
||||||
|
@ -13,7 +13,14 @@ Coded by www.creative-tim.com
|
|||||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState, useEffect, JSXElementConstructor, Key, ReactElement } from "react";
|
import React, {
|
||||||
|
useState,
|
||||||
|
useEffect,
|
||||||
|
JSXElementConstructor,
|
||||||
|
Key,
|
||||||
|
ReactElement,
|
||||||
|
useReducer,
|
||||||
|
} from "react";
|
||||||
|
|
||||||
// react-router components
|
// react-router components
|
||||||
import { Routes, Route, Navigate, useLocation } from "react-router-dom";
|
import { Routes, Route, Navigate, useLocation } from "react-router-dom";
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
/**
|
/**
|
||||||
=========================================================
|
=========================================================
|
||||||
* Material Dashboard 2 PRO React TS - v1.0.0
|
* Material Dashboard 2 PRO React TS - v1.0.0
|
||||||
=========================================================
|
=========================================================
|
||||||
|
|
||||||
* Product Page: https://www.creative-tim.com/product/material-dashboard-2-pro-react-ts
|
* Product Page: https://www.creative-tim.com/product/material-dashboard-2-pro-react-ts
|
||||||
* Copyright 2022 Creative Tim (https://www.creative-tim.com)
|
* Copyright 2022 Creative Tim (https://www.creative-tim.com)
|
||||||
|
|
||||||
Coded by www.creative-tim.com
|
Coded by www.creative-tim.com
|
||||||
|
|
||||||
=========================================================
|
=========================================================
|
||||||
|
|
||||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useEffect, ReactNode } from "react";
|
import { useEffect, ReactNode, useReducer, useState } from "react";
|
||||||
|
|
||||||
// react-router-dom components
|
// react-router-dom components
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
|
@ -42,9 +42,11 @@ import reportsLineChartData from "layouts/dashboards/analytics/data/reportsLineC
|
|||||||
import booking1 from "assets/images/products/product-1-min.jpg";
|
import booking1 from "assets/images/products/product-1-min.jpg";
|
||||||
import booking2 from "assets/images/products/product-2-min.jpg";
|
import booking2 from "assets/images/products/product-2-min.jpg";
|
||||||
import booking3 from "assets/images/products/product-3-min.jpg";
|
import booking3 from "assets/images/products/product-3-min.jpg";
|
||||||
|
import { useReducer } from "react";
|
||||||
|
|
||||||
function Analytics(): JSX.Element {
|
function Analytics(): JSX.Element {
|
||||||
const { sales, tasks } = reportsLineChartData;
|
const { sales, tasks } = reportsLineChartData;
|
||||||
|
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
||||||
|
|
||||||
// Action buttons for the BookingCard
|
// Action buttons for the BookingCard
|
||||||
const actionButtons = (
|
const actionButtons = (
|
||||||
|
@ -15,8 +15,10 @@ import { QFieldMetaData } from "@kingsrook/qqq-frontend-core/lib/model/metaData/
|
|||||||
// @material-ui core components
|
// @material-ui core components
|
||||||
import Card from "@mui/material/Card";
|
import Card from "@mui/material/Card";
|
||||||
import Grid from "@mui/material/Grid";
|
import Grid from "@mui/material/Grid";
|
||||||
|
import { Alert } from "@mui/material";
|
||||||
|
|
||||||
// Material Dashboard 2 PRO React TS components
|
// Material Dashboard 2 PRO React TS components
|
||||||
|
import MDAlert from "components/MDAlert";
|
||||||
import MDBox from "components/MDBox";
|
import MDBox from "components/MDBox";
|
||||||
import MDTypography from "components/MDTypography";
|
import MDTypography from "components/MDTypography";
|
||||||
import MDButton from "../../../components/MDButton";
|
import MDButton from "../../../components/MDButton";
|
||||||
@ -33,6 +35,7 @@ function EntityForm({ id }: Props): JSX.Element {
|
|||||||
const [validations, setValidations] = useState({});
|
const [validations, setValidations] = useState({});
|
||||||
const [initialValues, setInitialValues] = useState({} as { [key: string]: string });
|
const [initialValues, setInitialValues] = useState({} as { [key: string]: string });
|
||||||
const [formFields, setFormFields] = useState({});
|
const [formFields, setFormFields] = useState({});
|
||||||
|
const [alertContent, setAlertContent] = useState("");
|
||||||
|
|
||||||
const [asyncLoadInited, setAsyncLoadInited] = useState(false);
|
const [asyncLoadInited, setAsyncLoadInited] = useState(false);
|
||||||
const [formValues, setFormValues] = useState({} as { [key: string]: string });
|
const [formValues, setFormValues] = useState({} as { [key: string]: string });
|
||||||
@ -85,17 +88,27 @@ function EntityForm({ id }: Props): JSX.Element {
|
|||||||
actions.setSubmitting(true);
|
actions.setSubmitting(true);
|
||||||
await (async () => {
|
await (async () => {
|
||||||
if (id !== null) {
|
if (id !== null) {
|
||||||
await qController.update(tableName, id, values).then((record) => {
|
await qController
|
||||||
window.location.href = `/${tableName}/${record.values.get(
|
.update(tableName, id, values)
|
||||||
tableMetaData.primaryKeyField
|
.then((record) => {
|
||||||
)}`;
|
window.location.href = `/${tableName}/${record.values.get(
|
||||||
});
|
tableMetaData.primaryKeyField
|
||||||
|
)}`;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
setAlertContent(error.response.data.error);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
await qController.create(tableName, values).then((record) => {
|
await qController
|
||||||
window.location.href = `/${tableName}/${record.values.get(
|
.create(tableName, values)
|
||||||
tableMetaData.primaryKeyField
|
.then((record) => {
|
||||||
)}`;
|
window.location.href = `/${tableName}/${record.values.get(
|
||||||
});
|
tableMetaData.primaryKeyField
|
||||||
|
)}`;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
setAlertContent(error.response.data.error);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
};
|
};
|
||||||
@ -106,43 +119,56 @@ function EntityForm({ id }: Props): JSX.Element {
|
|||||||
id != null ? `edit-${tableMetaData?.label}-form` : `create-${tableMetaData?.label}-form`;
|
id != null ? `edit-${tableMetaData?.label}-form` : `create-${tableMetaData?.label}-form`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card id="edit-form-container" sx={{ overflow: "visible" }}>
|
<MDBox mb={3}>
|
||||||
<MDBox p={3}>
|
<Grid container spacing={3}>
|
||||||
<MDTypography variant="h5">{formTitle}</MDTypography>
|
<Grid item xs={12}>
|
||||||
</MDBox>
|
{alertContent ? (
|
||||||
<MDBox pb={3} px={3}>
|
<MDBox mb={3}>
|
||||||
<Grid key="fields-grid" container spacing={3}>
|
<Alert severity="error">{alertContent}</Alert>
|
||||||
<Formik
|
</MDBox>
|
||||||
initialValues={initialValues}
|
) : (
|
||||||
validationSchema={validations}
|
""
|
||||||
onSubmit={handleSubmit}
|
)}
|
||||||
>
|
<Card id="edit-form-container" sx={{ overflow: "visible" }}>
|
||||||
{({ values, errors, touched, isSubmitting }) => (
|
<MDBox p={3}>
|
||||||
<Form id={formId} autoComplete="off">
|
<MDTypography variant="h5">{formTitle}</MDTypography>
|
||||||
<MDBox p={3} width="100%">
|
</MDBox>
|
||||||
{/***************************************************************************
|
<MDBox pb={3} px={3}>
|
||||||
** step content - e.g., the appropriate form or other screen for the step **
|
<Grid key="fields-grid" container spacing={3}>
|
||||||
***************************************************************************/}
|
<Formik
|
||||||
{getDynamicStepContent({
|
initialValues={initialValues}
|
||||||
values,
|
validationSchema={validations}
|
||||||
touched,
|
onSubmit={handleSubmit}
|
||||||
formFields,
|
>
|
||||||
errors,
|
{({ values, errors, touched, isSubmitting }) => (
|
||||||
})}
|
<Form id={formId} autoComplete="off">
|
||||||
<Grid key="buttonGrid" container spacing={3}>
|
<MDBox p={3} width="100%">
|
||||||
<MDBox mt={5} ml="auto">
|
{/***************************************************************************
|
||||||
<MDButton type="submit" variant="gradient" color="dark" size="small">
|
** step content - e.g., the appropriate form or other screen for the step **
|
||||||
save {tableMetaData?.label}
|
***************************************************************************/}
|
||||||
</MDButton>
|
{getDynamicStepContent({
|
||||||
</MDBox>
|
values,
|
||||||
</Grid>
|
touched,
|
||||||
</MDBox>
|
formFields,
|
||||||
</Form>
|
errors,
|
||||||
)}
|
})}
|
||||||
</Formik>
|
<Grid key="buttonGrid" container spacing={3}>
|
||||||
|
<MDBox mt={5} ml="auto">
|
||||||
|
<MDButton type="submit" variant="gradient" color="dark" size="small">
|
||||||
|
save {tableMetaData?.label}
|
||||||
|
</MDButton>
|
||||||
|
</MDBox>
|
||||||
|
</Grid>
|
||||||
|
</MDBox>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</Grid>
|
||||||
|
</MDBox>
|
||||||
|
</Card>
|
||||||
</Grid>
|
</Grid>
|
||||||
</MDBox>
|
</Grid>
|
||||||
</Card>
|
</MDBox>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ import Grid from "@mui/material/Grid";
|
|||||||
import MDBox from "components/MDBox";
|
import MDBox from "components/MDBox";
|
||||||
|
|
||||||
// Settings page components
|
// Settings page components
|
||||||
import CreateForm from "qqq/components/EntityForm";
|
import EntityForm from "qqq/components/EntityForm";
|
||||||
import BaseLayout from "qqq/components/BaseLayout";
|
import BaseLayout from "qqq/components/BaseLayout";
|
||||||
|
|
||||||
function EntityCreate(): JSX.Element {
|
function EntityCreate(): JSX.Element {
|
||||||
@ -29,13 +29,7 @@ function EntityCreate(): JSX.Element {
|
|||||||
<MDBox mt={4}>
|
<MDBox mt={4}>
|
||||||
<Grid container spacing={3}>
|
<Grid container spacing={3}>
|
||||||
<Grid item xs={12} lg={12}>
|
<Grid item xs={12} lg={12}>
|
||||||
<MDBox mb={3}>
|
<EntityForm />
|
||||||
<Grid container spacing={3}>
|
|
||||||
<Grid item xs={12}>
|
|
||||||
<CreateForm />
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</MDBox>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</MDBox>
|
</MDBox>
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
/* eslint-disable react/no-unstable-nested-components */
|
/* eslint-disable react/no-unstable-nested-components */
|
||||||
|
|
||||||
import React, { useEffect, useReducer, useState } from "react";
|
import React, { useEffect, useReducer, useState } from "react";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
|
||||||
// @mui material components
|
// @mui material components
|
||||||
import Card from "@mui/material/Card";
|
import Card from "@mui/material/Card";
|
||||||
@ -23,32 +24,43 @@ import Menu from "@mui/material/Menu";
|
|||||||
import MenuItem from "@mui/material/MenuItem";
|
import MenuItem from "@mui/material/MenuItem";
|
||||||
import Divider from "@mui/material/Divider";
|
import Divider from "@mui/material/Divider";
|
||||||
import Link from "@mui/material/Link";
|
import Link from "@mui/material/Link";
|
||||||
import { makeStyles } from "@mui/material";
|
import { makeStyles, Alert } from "@mui/material";
|
||||||
import {
|
import {
|
||||||
DataGrid,
|
DataGrid,
|
||||||
GridCallbackDetails,
|
GridCallbackDetails,
|
||||||
GridColDef,
|
GridColDef,
|
||||||
|
GridColumnVisibilityModel,
|
||||||
|
GridFilterModel,
|
||||||
GridRowId,
|
GridRowId,
|
||||||
GridRowParams,
|
GridRowParams,
|
||||||
GridRowsProp,
|
GridRowsProp,
|
||||||
GridSelectionModel,
|
GridSelectionModel,
|
||||||
|
GridSortItem,
|
||||||
|
GridSortModel,
|
||||||
|
GridToolbar,
|
||||||
} from "@mui/x-data-grid";
|
} from "@mui/x-data-grid";
|
||||||
|
|
||||||
// Material Dashboard 2 PRO React TS components
|
// Material Dashboard 2 PRO React TS components
|
||||||
import DashboardLayout from "examples/LayoutContainers/DashboardLayout";
|
import DashboardLayout from "examples/LayoutContainers/DashboardLayout";
|
||||||
import DashboardNavbar from "examples/Navbars/DashboardNavbar";
|
import DashboardNavbar from "examples/Navbars/DashboardNavbar";
|
||||||
import MDBox from "components/MDBox";
|
import MDBox from "components/MDBox";
|
||||||
import MDTypography from "components/MDTypography";
|
|
||||||
import MDButton from "components/MDButton";
|
import MDButton from "components/MDButton";
|
||||||
|
|
||||||
// QQQ
|
// QQQ
|
||||||
import { QProcessMetaData } from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData";
|
import { QProcessMetaData } from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData";
|
||||||
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";
|
import { QQueryFilter } from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter";
|
||||||
|
import { QFilterOrderBy } from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterOrderBy";
|
||||||
|
import { QFilterCriteria } from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterCriteria";
|
||||||
|
import { QCriteriaOperator } from "@kingsrook/qqq-frontend-core/lib/model/query/QCriteriaOperator";
|
||||||
|
import { QFieldType } from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType";
|
||||||
import QClient from "qqq/utils/QClient";
|
import QClient from "qqq/utils/QClient";
|
||||||
import Footer from "../../components/Footer";
|
import Footer from "../../components/Footer";
|
||||||
import QProcessUtils from "../../utils/QProcessUtils";
|
import QProcessUtils from "../../utils/QProcessUtils";
|
||||||
|
|
||||||
|
const COLUMN_VISIBILITY_LOCAL_STORAGE_KEY = "qqq.columnVisibility";
|
||||||
|
const COLUMN_SORT_LOCAL_STORAGE_KEY = "qqq.columnSort";
|
||||||
|
|
||||||
// Declaring props types for DefaultCell
|
// Declaring props types for DefaultCell
|
||||||
interface Props {
|
interface Props {
|
||||||
table?: QTableMetaData;
|
table?: QTableMetaData;
|
||||||
@ -69,6 +81,10 @@ function EntityList({ table }: Props): JSX.Element {
|
|||||||
const [columns, setColumns] = useState([] as GridColDef[]);
|
const [columns, setColumns] = useState([] as GridColDef[]);
|
||||||
const [rows, setRows] = useState([] as GridRowsProp[]);
|
const [rows, setRows] = useState([] as GridRowsProp[]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [columnVisibilityModel, setColumnVisibilityModel] = useState({});
|
||||||
|
const [sortModel, setSortModel] = useState([] as GridSortItem[]);
|
||||||
|
const [filterModel, setFilterModel] = useState(null as GridFilterModel);
|
||||||
|
const [alertContent, setAlertContent] = useState("");
|
||||||
|
|
||||||
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
||||||
|
|
||||||
@ -77,14 +93,89 @@ function EntityList({ table }: Props): JSX.Element {
|
|||||||
const openFiltersMenu = (event: any) => setFiltersMenu(event.currentTarget);
|
const openFiltersMenu = (event: any) => setFiltersMenu(event.currentTarget);
|
||||||
const closeFiltersMenu = () => setFiltersMenu(null);
|
const closeFiltersMenu = () => setFiltersMenu(null);
|
||||||
|
|
||||||
|
const translateCriteriaOperator = (operator: string) => {
|
||||||
|
switch (operator) {
|
||||||
|
case "contains":
|
||||||
|
return QCriteriaOperator.CONTAINS;
|
||||||
|
case "starts with":
|
||||||
|
return QCriteriaOperator.STARTS_WITH;
|
||||||
|
case "ends with":
|
||||||
|
return QCriteriaOperator.STARTS_WITH;
|
||||||
|
case "is":
|
||||||
|
case "equals":
|
||||||
|
case "=":
|
||||||
|
return QCriteriaOperator.EQUALS;
|
||||||
|
case "is not":
|
||||||
|
case "!=":
|
||||||
|
return QCriteriaOperator.NOT_EQUALS;
|
||||||
|
case "is after":
|
||||||
|
case ">":
|
||||||
|
return QCriteriaOperator.GREATER_THAN;
|
||||||
|
case "is on or after":
|
||||||
|
case ">=":
|
||||||
|
return QCriteriaOperator.GREATER_THAN_OR_EQUALS;
|
||||||
|
case "is before":
|
||||||
|
case "<":
|
||||||
|
return QCriteriaOperator.LESS_THAN;
|
||||||
|
case "is on or before":
|
||||||
|
case "<=":
|
||||||
|
return QCriteriaOperator.LESS_THAN_OR_EQUALS;
|
||||||
|
case "is empty":
|
||||||
|
return QCriteriaOperator.IS_BLANK;
|
||||||
|
case "is not empty":
|
||||||
|
return QCriteriaOperator.IS_NOT_BLANK;
|
||||||
|
// case "is any of":
|
||||||
|
// TODO: handle this case
|
||||||
|
default:
|
||||||
|
return QCriteriaOperator.EQUALS;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildQFilter = () => {
|
||||||
|
const qFilter = new QQueryFilter();
|
||||||
|
sortModel.forEach((gridSortItem) => {
|
||||||
|
qFilter.addOrderBy(new QFilterOrderBy(gridSortItem.field, gridSortItem.sort === "asc"));
|
||||||
|
});
|
||||||
|
if (filterModel) {
|
||||||
|
filterModel.items.forEach((item) => {
|
||||||
|
qFilter.addCriteria(
|
||||||
|
new QFilterCriteria(item.columnField, translateCriteriaOperator(item.operatorValue), [
|
||||||
|
item.value,
|
||||||
|
])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return qFilter;
|
||||||
|
};
|
||||||
|
|
||||||
const updateTable = () => {
|
const updateTable = () => {
|
||||||
(async () => {
|
(async () => {
|
||||||
const tableMetaData = await QClient.loadTableMetaData(tableName);
|
const tableMetaData = await QClient.loadTableMetaData(tableName);
|
||||||
const count = await QClient.count(tableName);
|
const count = await QClient.count(tableName);
|
||||||
setTotalRecords(count);
|
setTotalRecords(count);
|
||||||
|
|
||||||
|
if (sortModel.length === 0) {
|
||||||
|
sortModel.push({ field: tableMetaData.primaryKeyField, sort: "desc" });
|
||||||
|
setSortModel(sortModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
const qFilter = buildQFilter();
|
||||||
const columns = [] as GridColDef[];
|
const columns = [] as GridColDef[];
|
||||||
const results = await QClient.query(tableName, rowsPerPage, pageNumber * rowsPerPage);
|
|
||||||
|
const results = await QClient.query(
|
||||||
|
tableName,
|
||||||
|
qFilter,
|
||||||
|
rowsPerPage,
|
||||||
|
pageNumber * rowsPerPage
|
||||||
|
).catch((error) => {
|
||||||
|
if (error.message) {
|
||||||
|
setAlertContent(error.message);
|
||||||
|
} else {
|
||||||
|
setAlertContent(error.response.data.error);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
|
||||||
const rows = [] as any[];
|
const rows = [] as any[];
|
||||||
results.forEach((record) => {
|
results.forEach((record) => {
|
||||||
@ -95,19 +186,46 @@ function EntityList({ table }: Props): JSX.Element {
|
|||||||
sortedKeys.forEach((key) => {
|
sortedKeys.forEach((key) => {
|
||||||
const field = tableMetaData.fields.get(key);
|
const field = tableMetaData.fields.get(key);
|
||||||
|
|
||||||
|
let columnType = "string";
|
||||||
|
switch (field.type) {
|
||||||
|
case QFieldType.DECIMAL:
|
||||||
|
case QFieldType.INTEGER:
|
||||||
|
columnType = "number";
|
||||||
|
break;
|
||||||
|
case QFieldType.DATE:
|
||||||
|
columnType = "date";
|
||||||
|
break;
|
||||||
|
case QFieldType.DATE_TIME:
|
||||||
|
columnType = "dateTime";
|
||||||
|
break;
|
||||||
|
case QFieldType.BOOLEAN:
|
||||||
|
columnType = "boolean";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
const column = {
|
const column = {
|
||||||
field: field.name,
|
field: field.name,
|
||||||
|
type: columnType,
|
||||||
headerName: field.label,
|
headerName: field.label,
|
||||||
width: 200,
|
width: 200,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (key === tableMetaData.primaryKeyField) {
|
if (key === tableMetaData.primaryKeyField) {
|
||||||
|
column.width = 75;
|
||||||
columns.splice(0, 0, column);
|
columns.splice(0, 0, column);
|
||||||
} else {
|
} else {
|
||||||
columns.push(column);
|
columns.push(column);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const columnVisibilityModel = localStorage.getItem(COLUMN_VISIBILITY_LOCAL_STORAGE_KEY);
|
||||||
|
if (columnVisibilityModel) {
|
||||||
|
setColumnVisibilityModel(
|
||||||
|
JSON.parse(localStorage.getItem(COLUMN_VISIBILITY_LOCAL_STORAGE_KEY))
|
||||||
|
);
|
||||||
|
}
|
||||||
setColumns(columns);
|
setColumns(columns);
|
||||||
setRows(rows);
|
setRows(rows);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@ -127,6 +245,10 @@ function EntityList({ table }: Props): JSX.Element {
|
|||||||
document.location.href = `/${tableName}/${params.id}`;
|
document.location.href = `/${tableName}/${params.id}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleFilterChange = (filterModel: GridFilterModel) => {
|
||||||
|
setFilterModel(filterModel);
|
||||||
|
};
|
||||||
|
|
||||||
const selectionChanged = (selectionModel: GridSelectionModel, details: GridCallbackDetails) => {
|
const selectionChanged = (selectionModel: GridSelectionModel, details: GridCallbackDetails) => {
|
||||||
const newSelectedIds: string[] = [];
|
const newSelectedIds: string[] = [];
|
||||||
selectionModel.forEach((value: GridRowId) => {
|
selectionModel.forEach((value: GridRowId) => {
|
||||||
@ -135,6 +257,19 @@ function EntityList({ table }: Props): JSX.Element {
|
|||||||
setSelectedIds(newSelectedIds);
|
setSelectedIds(newSelectedIds);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleColumnVisibilityChange = (columnVisibilityModel: GridColumnVisibilityModel) => {
|
||||||
|
setColumnVisibilityModel(columnVisibilityModel);
|
||||||
|
localStorage.setItem(
|
||||||
|
COLUMN_VISIBILITY_LOCAL_STORAGE_KEY,
|
||||||
|
JSON.stringify(columnVisibilityModel)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSortChange = (gridSort: GridSortModel) => {
|
||||||
|
setSortModel(gridSort);
|
||||||
|
localStorage.setItem(COLUMN_SORT_LOCAL_STORAGE_KEY, JSON.stringify(gridSort));
|
||||||
|
};
|
||||||
|
|
||||||
if (tableName !== tableState) {
|
if (tableName !== tableState) {
|
||||||
(async () => {
|
(async () => {
|
||||||
setTableState(tableName);
|
setTableState(tableName);
|
||||||
@ -171,39 +306,28 @@ function EntityList({ table }: Props): JSX.Element {
|
|||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderFiltersMenu = (
|
|
||||||
<Menu
|
|
||||||
anchorEl={filtersMenu}
|
|
||||||
anchorOrigin={{ vertical: "bottom", horizontal: "left" }}
|
|
||||||
transformOrigin={{ vertical: "top", horizontal: "left" }}
|
|
||||||
open={Boolean(filtersMenu)}
|
|
||||||
onClose={closeFiltersMenu}
|
|
||||||
keepMounted
|
|
||||||
>
|
|
||||||
<MenuItem onClick={closeFiltersMenu}>Status: Paid</MenuItem>
|
|
||||||
<MenuItem onClick={closeFiltersMenu}>Status: Refunded</MenuItem>
|
|
||||||
<MenuItem onClick={closeFiltersMenu}>Status: Canceled</MenuItem>
|
|
||||||
<Divider sx={{ margin: "0.5rem 0" }} />
|
|
||||||
<MenuItem onClick={closeFiltersMenu}>
|
|
||||||
<MDTypography variant="button" color="error" fontWeight="regular">
|
|
||||||
Remove Filter
|
|
||||||
</MDTypography>
|
|
||||||
</MenuItem>
|
|
||||||
</Menu>
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
updateTable();
|
updateTable();
|
||||||
}, [pageNumber, rowsPerPage, tableState]);
|
}, [pageNumber, rowsPerPage, tableState, sortModel, filterModel]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardLayout>
|
<DashboardLayout>
|
||||||
<DashboardNavbar />
|
<DashboardNavbar />
|
||||||
<MDBox my={3}>
|
<MDBox my={3}>
|
||||||
|
{alertContent ? (
|
||||||
|
<MDBox mb={3}>
|
||||||
|
<Alert severity="error">{alertContent}</Alert>
|
||||||
|
</MDBox>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
<MDBox display="flex" justifyContent="space-between" alignItems="flex-start" mb={2}>
|
<MDBox display="flex" justifyContent="space-between" alignItems="flex-start" mb={2}>
|
||||||
<MDButton variant="gradient" color="info">
|
<Link href={`/${tableName}/create`}>
|
||||||
<Link href={`/${tableName}/create`}>new {tableName}</Link>
|
<MDButton variant="gradient" color="info">
|
||||||
</MDButton>
|
new {tableName}
|
||||||
|
</MDButton>
|
||||||
|
</Link>
|
||||||
|
|
||||||
<MDBox display="flex">
|
<MDBox display="flex">
|
||||||
{tableProcesses.length > 0 && (
|
{tableProcesses.length > 0 && (
|
||||||
<MDButton
|
<MDButton
|
||||||
@ -216,28 +340,15 @@ function EntityList({ table }: Props): JSX.Element {
|
|||||||
</MDButton>
|
</MDButton>
|
||||||
)}
|
)}
|
||||||
{renderActionsMenu}
|
{renderActionsMenu}
|
||||||
<MDBox ml={1}>
|
|
||||||
<MDButton
|
|
||||||
variant={filtersMenu ? "contained" : "outlined"}
|
|
||||||
color="dark"
|
|
||||||
onClick={openFiltersMenu}
|
|
||||||
>
|
|
||||||
filters
|
|
||||||
<Icon>keyboard_arrow_down</Icon>
|
|
||||||
</MDButton>
|
|
||||||
{renderFiltersMenu}
|
|
||||||
</MDBox>
|
|
||||||
<MDBox ml={1}>
|
|
||||||
<MDButton variant="outlined" color="dark">
|
|
||||||
<Icon>description</Icon>
|
|
||||||
export csv
|
|
||||||
</MDButton>
|
|
||||||
</MDBox>
|
|
||||||
</MDBox>
|
</MDBox>
|
||||||
</MDBox>
|
</MDBox>
|
||||||
<Card>
|
<Card>
|
||||||
<MDBox height="100%">
|
<MDBox height="100%">
|
||||||
<DataGrid
|
<DataGrid
|
||||||
|
components={{ Toolbar: GridToolbar }}
|
||||||
|
paginationMode="server"
|
||||||
|
sortingMode="server"
|
||||||
|
filterMode="server"
|
||||||
page={pageNumber}
|
page={pageNumber}
|
||||||
checkboxSelection
|
checkboxSelection
|
||||||
disableSelectionOnClick
|
disableSelectionOnClick
|
||||||
@ -251,10 +362,18 @@ function EntityList({ table }: Props): JSX.Element {
|
|||||||
onPageSizeChange={handleRowsPerPageChange}
|
onPageSizeChange={handleRowsPerPageChange}
|
||||||
onPageChange={handlePageChange}
|
onPageChange={handlePageChange}
|
||||||
onRowClick={handleRowClick}
|
onRowClick={handleRowClick}
|
||||||
paginationMode="server"
|
|
||||||
density="compact"
|
density="compact"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
|
onFilterModelChange={handleFilterChange}
|
||||||
|
columnVisibilityModel={columnVisibilityModel}
|
||||||
|
onColumnVisibilityModelChange={handleColumnVisibilityChange}
|
||||||
onSelectionModelChange={selectionChanged}
|
onSelectionModelChange={selectionChanged}
|
||||||
|
onSortModelChange={handleSortChange}
|
||||||
|
sortingOrder={["asc", "desc"]}
|
||||||
|
sortModel={sortModel}
|
||||||
|
getRowClassName={(params) =>
|
||||||
|
params.indexRelativeToCurrentPage % 2 === 0 ? "even" : "odd"
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</MDBox>
|
</MDBox>
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
import { QFieldMetaData } from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
|
import { QFieldMetaData } from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
|
||||||
import { QController } from "@kingsrook/qqq-frontend-core/lib/controllers/QController";
|
import { QController } from "@kingsrook/qqq-frontend-core/lib/controllers/QController";
|
||||||
|
import { QQueryFilter } from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter";
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** client wrapper of qqq backend
|
** client wrapper of qqq backend
|
||||||
@ -45,8 +46,12 @@ class QClient {
|
|||||||
return this.getInstance().loadMetaData();
|
return this.getInstance().loadMetaData();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static query(tableName: string, limit: number, skip: number) {
|
public static query(tableName: string, filter: QQueryFilter, limit: number, skip: number) {
|
||||||
return this.getInstance().query(tableName, limit, skip);
|
return this.getInstance()
|
||||||
|
.query(tableName, filter, limit, skip)
|
||||||
|
.catch((error) => {
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static count(tableName: string) {
|
public static count(tableName: string) {
|
||||||
|
Reference in New Issue
Block a user