SPRINT-12: added skeletons throughout widgets, fixed page header under breadcrumb, misc other improvements

This commit is contained in:
Tim Chamberlain
2022-10-04 12:38:53 -05:00
parent e67301dc4b
commit 30f2da17a4
16 changed files with 353 additions and 223 deletions

View File

@ -36,6 +36,7 @@ import {Md5} from "ts-md5/dist/md5";
import {setMiniSidenav, setOpenConfigurator, useMaterialUIController} from "context"; import {setMiniSidenav, setOpenConfigurator, useMaterialUIController} from "context";
import Settings from "layouts/pages/account/settings"; import Settings from "layouts/pages/account/settings";
import ProfileOverview from "layouts/pages/profile/profile-overview"; import ProfileOverview from "layouts/pages/profile/profile-overview";
import QContext from "QContext";
import Sidenav from "qqq/components/Sidenav"; import Sidenav from "qqq/components/Sidenav";
import Configurator from "qqq/components/Temporary/Configurator"; import Configurator from "qqq/components/Temporary/Configurator";
import MDAvatar from "qqq/components/Temporary/MDAvatar"; import MDAvatar from "qqq/components/Temporary/MDAvatar";
@ -469,8 +470,15 @@ export default function App()
</MDBox> </MDBox>
); );
const [pageHeader, setPageHeader] = useState("");
return ( return (
appRoutes && ( appRoutes && (
<QContext.Provider value={{
pageHeader: pageHeader,
setPageHeader: (header: string) => setPageHeader(header)
}}>
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<CssBaseline /> <CssBaseline />
{layout === "dashboard" && ( {layout === "dashboard" && (
@ -494,6 +502,7 @@ export default function App()
{profileRoutes && getRoutes([profileRoutes])} {profileRoutes && getRoutes([profileRoutes])}
</Routes> </Routes>
</ThemeProvider> </ThemeProvider>
</QContext.Provider>
) )
); );
} }

36
src/QContext.tsx Normal file
View File

@ -0,0 +1,36 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {createContext} from "react";
interface QContext
{
pageHeader: string;
setPageHeader?: (header: string) => void;
}
const defaultState = {
pageHeader: ""
};
const QContext = createContext<QContext>(defaultState);
export default QContext;

View File

@ -20,6 +20,7 @@
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance"; import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData"; import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
import {Skeleton} from "@mui/material";
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 parse from "html-react-parser"; import parse from "html-react-parser";
@ -73,6 +74,7 @@ function DashboardWidgets({widgetMetaDataList, entityPrimaryKey}: Props): JSX.El
return; return;
} }
forceUpdate();
for (let i = 0; i < widgetMetaDataList.length; i++) for (let i = 0; i < widgetMetaDataList.length; i++)
{ {
widgetData[i] = {}; widgetData[i] = {};
@ -100,17 +102,19 @@ function DashboardWidgets({widgetMetaDataList, entityPrimaryKey}: Props): JSX.El
}; };
const widgetCount = widgetMetaDataList ? widgetMetaDataList.length : 0; const widgetCount = widgetMetaDataList ? widgetMetaDataList.length : 0;
console.log(JSON.stringify(widgetMetaDataList));
console.log(widgetCount);
return ( return (
widgetCount > 0 ? ( widgetCount > 0 ? (
<Grid item xs={12} lg={12}> <Grid container spacing={3} pb={4}>
<Grid container spacing={3}>
{ {
widgetMetaDataList.map((widgetMetaData, i) => ( widgetMetaDataList.map((widgetMetaData, i) => (
<Grid key={`${i}`} item xs={12} lg={12}> <Grid key={`${i}`} item lg={widgetMetaData.gridColumns ? widgetMetaData.gridColumns : 12} xs={12}>
{ {
widgetMetaData.type === "table" && ( widgetMetaData.type === "table" && (
widgetData && widgetData[i] ? ( widgetData && widgetData[i] ? (
<MDBox>
<TableCard <TableCard
color="info" color="info"
title={widgetMetaData.label} title={widgetMetaData.label}
@ -122,6 +126,7 @@ function DashboardWidgets({widgetMetaDataList, entityPrimaryKey}: Props): JSX.El
dropdownOnChange={handleDropdownOnChange} dropdownOnChange={handleDropdownOnChange}
widgetIndex={i} widgetIndex={i}
/> />
</MDBox>
) : null ) : null
) )
} }
@ -145,17 +150,17 @@ function DashboardWidgets({widgetMetaDataList, entityPrimaryKey}: Props): JSX.El
} }
{ {
widgetMetaData.type === "html" && ( widgetMetaData.type === "html" && (
<MDBox pb={3}> <MDBox>
<Card sx={{marginTop: "0px", paddingTop: "0px"}}> <Card sx={{alignContents: "stretch", marginTop: "0px", paddingTop: "0px"}}>
<MDBox padding="1rem"> <MDBox padding="1rem">
<MDTypography variant="h5" textTransform="capitalize"> <MDTypography variant="h5" textTransform="capitalize">
{widgetMetaData.label} {widgetMetaData.label}
</MDTypography> </MDTypography>
<MDTypography component="div" variant="button" color="text" fontWeight="light"> <MDTypography component="div" variant="button" color="text" fontWeight="light">
{ {
widgetData && widgetData[i] && widgetData[i].html && ( widgetData && widgetData[i] && widgetData[i].html ? (
parse(widgetData[i].html) parse(widgetData[i].html)
) ) : <Skeleton />
} }
</MDTypography> </MDTypography>
</MDBox> </MDBox>
@ -165,23 +170,13 @@ function DashboardWidgets({widgetMetaDataList, entityPrimaryKey}: Props): JSX.El
} }
{ {
widgetMetaData.type === "multiStatistics" && ( widgetMetaData.type === "multiStatistics" && (
widgetData && widgetData[i] ? (
<MultiStatisticsCard <MultiStatisticsCard
color="info" color="info"
title={widgetData[i].title} title={widgetMetaData.label}
data={widgetData[i]} data={widgetData[i]}
/> />
) : null
) )
} }
</Grid>
))
}
</Grid>
<Grid item xs={12} lg={widgetCount === 1 ? 3 : 6}>
{
widgetMetaDataList.map((widgetMetaData, i) => (
<Grid key={`${i}`} item xs={12} lg={widgetCount === 1 ? 12 : 6}>
{ {
widgetMetaData.type === "quickSightChart" && ( widgetMetaData.type === "quickSightChart" && (
widgetData && widgetData[i] ? ( widgetData && widgetData[i] ? (
@ -233,7 +228,6 @@ function DashboardWidgets({widgetMetaDataList, entityPrimaryKey}: Props): JSX.El
)) ))
} }
</Grid> </Grid>
</Grid>
) : null ) : null
); );
} }

View File

@ -29,9 +29,10 @@ import Card from "@mui/material/Card";
import Grid from "@mui/material/Grid"; import Grid from "@mui/material/Grid";
import Icon from "@mui/material/Icon"; import Icon from "@mui/material/Icon";
import {Form, Formik} from "formik"; import {Form, Formik} from "formik";
import React, {useReducer, useState} from "react"; import React, {useContext, useReducer, useState} from "react";
import {useLocation, useNavigate, useParams} from "react-router-dom"; import {useLocation, useNavigate, useParams} from "react-router-dom";
import * as Yup from "yup"; import * as Yup from "yup";
import QContext from "QContext";
import {QCancelButton, QSaveButton} from "qqq/components/QButtons"; import {QCancelButton, QSaveButton} from "qqq/components/QButtons";
import QDynamicForm from "qqq/components/QDynamicForm"; import QDynamicForm from "qqq/components/QDynamicForm";
import DynamicFormUtils from "qqq/components/QDynamicForm/utils/DynamicFormUtils"; import DynamicFormUtils from "qqq/components/QDynamicForm/utils/DynamicFormUtils";
@ -54,6 +55,7 @@ function EntityForm({table, id}: Props): JSX.Element
const tableNameParam = useParams().tableName; const tableNameParam = useParams().tableName;
const tableName = table === null ? tableNameParam : table.name; const tableName = table === null ? tableNameParam : table.name;
const [formTitle, setFormTitle] = useState("");
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(null as Map<string, any>); const [formFields, setFormFields] = useState(null as Map<string, any>);
@ -69,6 +71,8 @@ function EntityForm({table, id}: Props): JSX.Element
const [tableSections, setTableSections] = useState(null as QTableSection[]); const [tableSections, setTableSections] = useState(null as QTableSection[]);
const [, forceUpdate] = useReducer((x) => x + 1, 0); const [, forceUpdate] = useReducer((x) => x + 1, 0);
const {pageHeader, setPageHeader} = useContext(QContext);
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation(); const location = useLocation();
@ -79,7 +83,6 @@ function EntityForm({table, id}: Props): JSX.Element
formData.touched = touched; formData.touched = touched;
formData.errors = errors; formData.errors = errors;
formData.formFields = {}; formData.formFields = {};
console.log(formFields);
for (let i = 0; i < formFields.length; i++) for (let i = 0; i < formFields.length; i++)
{ {
formData.formFields[formFields[i].name] = formFields[i]; formData.formFields[formFields[i].name] = formFields[i];
@ -121,6 +124,8 @@ function EntityForm({table, id}: Props): JSX.Element
{ {
const record = await qController.get(tableName, id); const record = await qController.get(tableName, id);
setRecord(record); setRecord(record);
setFormTitle(`Edit ${tableMetaData?.label}: ${record?.recordLabel}`);
setPageHeader(`Edit ${tableMetaData?.label}: ${record?.recordLabel}`);
tableMetaData.fields.forEach((fieldMetaData, key) => tableMetaData.fields.forEach((fieldMetaData, key) =>
{ {
@ -129,6 +134,11 @@ function EntityForm({table, id}: Props): JSX.Element
setFormValues(formValues); setFormValues(formValues);
} }
else
{
setFormTitle(`Creating New ${tableMetaData?.label}`);
setPageHeader(`Creating New ${tableMetaData?.label}`);
}
setInitialValues(initialValues); setInitialValues(initialValues);
///////////////////////////////////////////////////////// /////////////////////////////////////////////////////////
@ -255,19 +265,6 @@ function EntityForm({table, id}: Props): JSX.Element
})(); })();
}; };
let formTitle = "";
if (tableMetaData)
{
if (id == null)
{
formTitle = `Create new ${tableMetaData?.label}`;
}
else if (record != null)
{
formTitle = `Edit ${tableMetaData?.label}: ${record?.recordLabel}`;
}
}
const formId = id != null ? `edit-${tableMetaData?.name}-form` : `create-${tableMetaData?.name}-form`; const formId = id != null ? `edit-${tableMetaData?.name}-form` : `create-${tableMetaData?.name}-form`;
return ( return (

View File

@ -21,8 +21,9 @@
import {Breadcrumbs as MuiBreadcrumbs} from "@mui/material"; import {Breadcrumbs as MuiBreadcrumbs} from "@mui/material";
import Icon from "@mui/material/Icon"; import Icon from "@mui/material/Icon";
import {ReactNode} from "react"; import {ReactNode, useContext} from "react";
import {Link} from "react-router-dom"; import {Link} from "react-router-dom";
import QContext from "QContext";
import MDBox from "qqq/components/Temporary/MDBox"; import MDBox from "qqq/components/Temporary/MDBox";
import MDTypography from "qqq/components/Temporary/MDTypography"; import MDTypography from "qqq/components/Temporary/MDTypography";
@ -56,11 +57,10 @@ export const routeToLabel = (route: string): string =>
return (label); return (label);
}; };
function QBreadcrumbs({ function QBreadcrumbs({icon, title, route, light}: Props): JSX.Element
icon, title, route, light,
}: Props): JSX.Element
{ {
const routes: string[] | any = route.slice(0, -1); const routes: string[] | any = route.slice(0, -1);
const {pageHeader, setPageHeader} = useContext(QContext);
let pageTitle = "Nutrifresh One"; let pageTitle = "Nutrifresh One";
const fullRoutes: string[] = []; const fullRoutes: string[] = [];
@ -118,7 +118,7 @@ function QBreadcrumbs({
color={light ? "white" : "dark"} color={light ? "white" : "dark"}
noWrap noWrap
> >
{routeToLabel(title)} {pageHeader}
</MDTypography> </MDTypography>
</MDBox> </MDBox>
); );

View File

@ -38,9 +38,11 @@ interface Props
value: any; value: any;
type: string; type: string;
isEditable?: boolean; isEditable?: boolean;
[key: string]: any; [key: string]: any;
bulkEditMode?: boolean; bulkEditMode?: boolean;
bulkEditSwitchChangeHandler?: any bulkEditSwitchChangeHandler?: any;
} }
function QDynamicFormField({ function QDynamicFormField({
@ -71,11 +73,21 @@ function QDynamicFormField({
inputProps.endAdornment = <InputAdornment position="end">%</InputAdornment>; inputProps.endAdornment = <InputAdornment position="end">%</InputAdornment>;
} }
// @ts-ignore
const handleOnWheel = (e) =>
{
if (type.toLowerCase().match("number"))
{
e.target.blur();
}
};
const field = () => const field = () =>
(type == "checkbox" ? (type == "checkbox" ?
<QBooleanFieldSwitch name={name} label={label} value={value} isDisabled={isDisabled} /> : <QBooleanFieldSwitch name={name} label={label} value={value} isDisabled={isDisabled} /> :
<> <>
<Field {...rest} name={name} type={type} as={MDInput} variant="standard" label={label} InputLabelProps={inputLabelProps} InputProps={inputProps} fullWidth disabled={isDisabled} /> <Field {...rest} onWheel={handleOnWheel} name={name} type={type} as={MDInput} variant="standard" label={label} InputLabelProps={inputLabelProps} InputProps={inputProps} fullWidth disabled={isDisabled} />
<MDBox mt={0.75}> <MDBox mt={0.75}>
<MDTypography component="div" variant="caption" color="error" fontWeight="regular"> <MDTypography component="div" variant="caption" color="error" fontWeight="regular">
{!isDisabled && <div className="fieldErrorMessage"><ErrorMessage name={name} /></div>} {!isDisabled && <div className="fieldErrorMessage"><ErrorMessage name={name} /></div>}

View File

@ -29,8 +29,9 @@ import {Icon} from "@mui/material";
import Card from "@mui/material/Card"; import Card from "@mui/material/Card";
import Divider from "@mui/material/Divider"; import Divider from "@mui/material/Divider";
import Grid from "@mui/material/Grid"; import Grid from "@mui/material/Grid";
import React, {useEffect, useState} from "react"; import React, {useContext, useEffect, useState} from "react";
import {Link, useLocation} from "react-router-dom"; import {Link, useLocation} from "react-router-dom";
import QContext from "QContext";
import BaseLayout from "qqq/components/BaseLayout"; import BaseLayout from "qqq/components/BaseLayout";
import DashboardWidgets from "qqq/components/DashboardWidgets"; import DashboardWidgets from "qqq/components/DashboardWidgets";
import ProcessLinkCard from "qqq/components/ProcessLinkCard"; import ProcessLinkCard from "qqq/components/ProcessLinkCard";
@ -57,6 +58,8 @@ function AppHome({app}: Props): JSX.Element
const [updatedTableCounts, setUpdatedTableCounts] = useState(new Date()); const [updatedTableCounts, setUpdatedTableCounts] = useState(new Date());
const [widgets, setWidgets] = useState([] as any[]); const [widgets, setWidgets] = useState([] as any[]);
const {pageHeader, setPageHeader} = useContext(QContext);
const location = useLocation(); const location = useLocation();
useEffect(() => useEffect(() =>
@ -70,6 +73,8 @@ function AppHome({app}: Props): JSX.Element
useEffect(() => useEffect(() =>
{ {
setPageHeader(app.label);
if (!qInstance) if (!qInstance)
{ {
return; return;
@ -153,11 +158,9 @@ function AppHome({app}: Props): JSX.Element
return ( return (
<BaseLayout> <BaseLayout>
<MDBox mt={4}> <MDBox mt={4} mb={4}>
{app.widgets && ( {app.widgets && (
<Grid container spacing={3}>
<DashboardWidgets widgetMetaDataList={widgets} /> <DashboardWidgets widgetMetaDataList={widgets} />
</Grid>
)} )}
<Grid container spacing={3}> <Grid container spacing={3}>
{ {

View File

@ -24,7 +24,8 @@ import {Icon} from "@mui/material";
import Grid from "@mui/material/Grid"; import Grid from "@mui/material/Grid";
import Menu from "@mui/material/Menu"; import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem"; import MenuItem from "@mui/material/MenuItem";
import React, {useEffect, useState} from "react"; import React, {useContext, useEffect, useState} from "react";
import QContext from "QContext";
import DashboardLayout from "qqq/components/DashboardLayout"; import DashboardLayout from "qqq/components/DashboardLayout";
import Footer from "qqq/components/Footer"; import Footer from "qqq/components/Footer";
import Navbar from "qqq/components/Navbar"; import Navbar from "qqq/components/Navbar";
@ -115,12 +116,15 @@ function CarrierPerformance(): JSX.Element
const [qInstance, setQInstance] = useState(null as QInstance); const [qInstance, setQInstance] = useState(null as QInstance);
const [dataLoaded, setDataLoaded] = useState(false); const [dataLoaded, setDataLoaded] = useState(false);
const {pageHeader, setPageHeader} = useContext(QContext);
////////////////////////// //////////////////////////
// load meta data first // // load meta data first //
////////////////////////// //////////////////////////
useEffect(() => useEffect(() =>
{ {
setPageHeader("Carrier Performance");
(async () => (async () =>
{ {
const newQInstance = await qController.loadMetaData(); const newQInstance = await qController.loadMetaData();

View File

@ -23,7 +23,8 @@ import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstan
import Grid from "@mui/material/Grid"; import Grid from "@mui/material/Grid";
import Icon from "@mui/material/Icon"; import Icon from "@mui/material/Icon";
import Tooltip from "@mui/material/Tooltip"; import Tooltip from "@mui/material/Tooltip";
import {useEffect, useState} from "react"; import {useContext, useEffect, useState} from "react";
import QContext from "QContext";
import DashboardLayout from "qqq/components/DashboardLayout"; import DashboardLayout from "qqq/components/DashboardLayout";
import Footer from "qqq/components/Footer"; import Footer from "qqq/components/Footer";
import Navbar from "qqq/components/Navbar"; import Navbar from "qqq/components/Navbar";
@ -66,6 +67,7 @@ function Overview(): JSX.Element
const [warehouseData, setWarehouseData] = useState([] as LocationCardData[]); const [warehouseData, setWarehouseData] = useState([] as LocationCardData[]);
const [qInstance, setQInstance] = useState(null as QInstance); const [qInstance, setQInstance] = useState(null as QInstance);
const {pageHeader, setPageHeader} = useContext(QContext);
////////////////////////// //////////////////////////
@ -73,6 +75,8 @@ function Overview(): JSX.Element
////////////////////////// //////////////////////////
useEffect(() => useEffect(() =>
{ {
setPageHeader("Overview");
(async () => (async () =>
{ {
const newQInstance = await qController.loadMetaData(); const newQInstance = await qController.loadMetaData();

View File

@ -19,6 +19,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {Skeleton} from "@mui/material";
import Card from "@mui/material/Card"; import Card from "@mui/material/Card";
import Divider from "@mui/material/Divider"; import Divider from "@mui/material/Divider";
import Grid from "@mui/material/Grid"; import Grid from "@mui/material/Grid";
@ -76,7 +77,7 @@ function MultiStatisticsCard({title, data}: Props): JSX.Element
</Grid> </Grid>
<Grid container> <Grid container>
{ {
data && data.statisticsGroupData && ( data && data.statisticsGroupData ? (
data.statisticsGroupData.map((statisticsGroup, i1) => data.statisticsGroupData.map((statisticsGroup, i1) =>
<Grid key={`statgroup-${i1}`} item xs={3} lg={3} sx={{textAlign: "center"}}> <Grid key={`statgroup-${i1}`} item xs={3} lg={3} sx={{textAlign: "center"}}>
<MDBox p={3} pt={3} sx={{alignItems: "center"}}> <MDBox p={3} pt={3} sx={{alignItems: "center"}}>
@ -112,6 +113,34 @@ function MultiStatisticsCard({title, data}: Props): JSX.Element
</MDBox> </MDBox>
</Grid> </Grid>
) )
) : (
Array(4).fill(0).map((_, i) =>
<Grid key={`item-${i}`} item xs={3} lg={3} sx={{textAlign: "center"}}>
<MDBox p={3} pt={3} sx={{alignItems: "center"}}>
<MDBox>
<MDTypography variant="h6">
<Icon sx={{fontSize: "30px", margin: "5px", color: "grey"}} fontSize="medium">pending</Icon>
</MDTypography>
</MDBox>
<MDBox>
<MDTypography variant="h6">
<Skeleton />
</MDTypography>
<MDTypography variant="subtitle2">
<Skeleton />
</MDTypography>
</MDBox>
<Divider sx={{margin: "10px"}}></Divider>
<MDBox sx={{alignItems: "center"}}>
<MDBox key={`stat-${i}`}>
<MDTypography variant="subtitle2">
<Skeleton />
</MDTypography>
</MDBox>
</MDBox>
</MDBox>
</Grid>
)
) )
} }
</Grid> </Grid>

View File

@ -65,10 +65,6 @@ function StepperCard({data}: Props): JSX.Element
marginTop: "9px", marginTop: "9px",
marginRight: "30px", marginRight: "30px",
marginLeft: "30px", marginLeft: "30px",
},
"& .MuiStepConnector-completed":
{
color: "red !important"
} }
})(StepConnector); })(StepConnector);
@ -132,7 +128,21 @@ function StepperCard({data}: Props): JSX.Element
</Step> </Step>
)) ))
) : ( ) : (
<Skeleton width="100%" height="80px" />
Array(5).fill(0).map((_, i) =>
<Step key={`step-${i}`}>
<MDBox>
<StepLabel icon={<Pending />} sx={{
color: "#ced4da",
fontSize: "35px",
"& .MuiStepLabel-label.MuiStepLabel-alternativeLabel":
{
color: "#ced4da !important",
}
}}><Skeleton /></StepLabel>
</MDBox>
</Step>
)
) )
} }
</Stepper> </Stepper>

View File

@ -24,10 +24,17 @@ import Card from "@mui/material/Card";
import Grid from "@mui/material/Grid"; import Grid from "@mui/material/Grid";
import Menu from "@mui/material/Menu"; import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem"; import MenuItem from "@mui/material/MenuItem";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableContainer from "@mui/material/TableContainer";
import TableRow from "@mui/material/TableRow";
import parse from "html-react-parser"; import parse from "html-react-parser";
import React, {useEffect, useState} from "react"; import React, {useEffect, useState} from "react";
import {NavLink} from "react-router-dom"; import {NavLink} from "react-router-dom";
import DefaultCell from "layouts/dashboards/sales/components/DefaultCell";
import DataTable, {TableDataInput} from "qqq/components/Temporary/DataTable"; import DataTable, {TableDataInput} from "qqq/components/Temporary/DataTable";
import DataTableBodyCell from "qqq/components/Temporary/DataTable/DataTableBodyCell";
import DataTableHeadCell from "qqq/components/Temporary/DataTable/DataTableHeadCell";
import MDBox from "qqq/components/Temporary/MDBox"; import MDBox from "qqq/components/Temporary/MDBox";
import MDTypography from "qqq/components/Temporary/MDTypography"; import MDTypography from "qqq/components/Temporary/MDTypography";
@ -175,17 +182,30 @@ function TableCard({title, linkText, linkURL, noRowsFoundHTML, data, dropdownOpt
</MDTypography> </MDTypography>
</MDBox> </MDBox>
) : ( ) : (
<MDBox p={3} pt={1} pb={1} sx={{textAlign: "center"}}> <TableContainer sx={{boxShadow: "none"}}>
<MDTypography <Table>
variant="subtitle2" <MDBox component="thead">
color="secondary" <TableRow key="header">
fontWeight="regular" {Array(8).fill(0).map((_, i) =>
> <DataTableHeadCell key={`head-${i}`} sorted={false} width="auto" align="center">
<Skeleton /> <Skeleton width="100%" />
<Skeleton /> </DataTableHeadCell>
<Skeleton /> )}
</MDTypography> </TableRow>
</MDBox> </MDBox>
<TableBody>
{Array(5).fill(0).map((_, i) =>
<TableRow sx={{verticalAlign: "top"}} key={`row-${i}`}>
{Array(8).fill(0).map((_, j) =>
<DataTableBodyCell key={`cell-${i}-${j}`} align="center">
<DefaultCell><Skeleton /></DefaultCell>
</DataTableBodyCell>
)}
</TableRow>
)}
</TableBody>
</Table>
</TableContainer>
) )
} }
</MDBox> </MDBox>

View File

@ -57,8 +57,9 @@ import {
MuiEvent MuiEvent
} from "@mui/x-data-grid-pro"; } from "@mui/x-data-grid-pro";
import {GridFilterOperator} from "@mui/x-data-grid/models/gridFilterOperator"; import {GridFilterOperator} from "@mui/x-data-grid/models/gridFilterOperator";
import React, {useCallback, useEffect, useReducer, useRef, useState} from "react"; import React, {useCallback, useContext, useEffect, useReducer, useRef, useState} from "react";
import {Link, useNavigate, useParams, useSearchParams} from "react-router-dom"; import {Link, useNavigate, useParams, useSearchParams} from "react-router-dom";
import QContext from "QContext";
import DashboardLayout from "qqq/components/DashboardLayout"; import DashboardLayout from "qqq/components/DashboardLayout";
import Footer from "qqq/components/Footer"; import Footer from "qqq/components/Footer";
import Navbar from "qqq/components/Navbar"; import Navbar from "qqq/components/Navbar";
@ -197,6 +198,7 @@ function EntityList({table}: Props): JSX.Element
const [queryErrors, setQueryErrors] = useState({} as any); const [queryErrors, setQueryErrors] = useState({} as any);
const [receivedQueryErrorTimestamp, setReceivedQueryErrorTimestamp] = useState(new Date()); const [receivedQueryErrorTimestamp, setReceivedQueryErrorTimestamp] = useState(new Date());
const {pageHeader, setPageHeader} = useContext(QContext);
const [, forceUpdate] = useReducer((x) => x + 1, 0); const [, forceUpdate] = useReducer((x) => x + 1, 0);
@ -233,6 +235,7 @@ function EntityList({table}: Props): JSX.Element
(async () => (async () =>
{ {
const tableMetaData = await qController.loadTableMetaData(tableName); const tableMetaData = await qController.loadTableMetaData(tableName);
setPageHeader(tableMetaData.label);
//////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////
// we need the table meta data to look up the default filter (if it comes from query string), // // we need the table meta data to look up the default filter (if it comes from query string), //
@ -263,7 +266,7 @@ function EntityList({table}: Props): JSX.Element
// assign a new query id to the query being issued here. then run both the count & query async // // assign a new query id to the query being issued here. then run both the count & query async //
// and when they load, store their results associated with this id. // // and when they load, store their results associated with this id. //
////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////
const thisQueryId = latestQueryId + 1 const thisQueryId = latestQueryId + 1;
setLatestQueryId(thisQueryId); setLatestQueryId(thisQueryId);
console.log(`Issuing query: ${thisQueryId}`); console.log(`Issuing query: ${thisQueryId}`);
@ -291,7 +294,7 @@ function EntityList({table}: Props): JSX.Element
{ {
errorMessage = error.message; errorMessage = error.message;
} }
else if(error && error.response && error.response.data && error.response.data.error) else if (error && error.response && error.response.data && error.response.data.error)
{ {
errorMessage = error.response.data.error; errorMessage = error.response.data.error;
} }
@ -307,7 +310,7 @@ function EntityList({table}: Props): JSX.Element
throw error; throw error;
}); });
})(); })();
} };
/////////////////////////// ///////////////////////////
// display count results // // display count results //
@ -360,7 +363,7 @@ function EntityList({table}: Props): JSX.Element
/////////////////////////// ///////////////////////////
useEffect(() => useEffect(() =>
{ {
if(!queryResults[latestQueryId]) if (!queryResults[latestQueryId])
{ {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// to avoid showing results from an "older" query (e.g., one that was slow, and returned // // to avoid showing results from an "older" query (e.g., one that was slow, and returned //
@ -382,8 +385,8 @@ function EntityList({table}: Props): JSX.Element
const row: any = {}; const row: any = {};
fields.forEach((field) => fields.forEach((field) =>
{ {
const value = QValueUtils.getDisplayValue(field, record) const value = QValueUtils.getDisplayValue(field, record);
if(typeof value !== "string") if (typeof value !== "string")
{ {
columnsToRender[field.name] = true; columnsToRender[field.name] = true;
} }
@ -454,11 +457,11 @@ function EntityList({table}: Props): JSX.Element
} }
} }
if(field.hasAdornment(AdornmentType.SIZE)) if (field.hasAdornment(AdornmentType.SIZE))
{ {
const sizeAdornment = field.getAdornment(AdornmentType.SIZE) const sizeAdornment = field.getAdornment(AdornmentType.SIZE);
const width = sizeAdornment.getValue("width") const width = sizeAdornment.getValue("width");
switch(width) switch (width)
{ {
case "small": case "small":
{ {
@ -482,7 +485,7 @@ function EntityList({table}: Props): JSX.Element
} }
default: default:
{ {
console.log("Unrecognized size.width adornment value: " + width) console.log("Unrecognized size.width adornment value: " + width);
} }
} }
} }
@ -496,7 +499,7 @@ function EntityList({table}: Props): JSX.Element
filterOperators: filterOperators, filterOperators: filterOperators,
}; };
if(columnsToRender[field.name]) if (columnsToRender[field.name])
{ {
column.renderCell = (cellValues: any) => ( column.renderCell = (cellValues: any) => (
(cellValues.value) (cellValues.value)
@ -811,9 +814,9 @@ function EntityList({table}: Props): JSX.Element
// @ts-ignore // @ts-ignore
const defaultLabelDisplayedRows = ({from, to, count}) => const defaultLabelDisplayedRows = ({from, to, count}) =>
{ {
if(count !== null && count !== undefined) if (count !== null && count !== undefined)
{ {
if(count === 0) if (count === 0)
{ {
return (loading ? "Counting records..." : "No rows"); return (loading ? "Counting records..." : "No rows");
} }
@ -823,7 +826,7 @@ function EntityList({table}: Props): JSX.Element
{ {
return ("Counting records..."); return ("Counting records...");
} }
} };
function CustomPagination() function CustomPagination()
{ {

View File

@ -36,8 +36,9 @@ import Grid from "@mui/material/Grid";
import Icon from "@mui/material/Icon"; import Icon from "@mui/material/Icon";
import Menu from "@mui/material/Menu"; import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem"; import MenuItem from "@mui/material/MenuItem";
import React, {useEffect, useReducer, useState} from "react"; import React, {useContext, useEffect, useReducer, useState} from "react";
import {useLocation, useNavigate, useSearchParams} from "react-router-dom"; import {useLocation, useNavigate, useSearchParams} from "react-router-dom";
import QContext from "QContext";
import DashboardWidgets from "qqq/components/DashboardWidgets"; import DashboardWidgets from "qqq/components/DashboardWidgets";
import {QActionsMenuButton, QDeleteButton, QEditButton} from "qqq/components/QButtons"; import {QActionsMenuButton, QDeleteButton, QEditButton} from "qqq/components/QButtons";
import QRecordSidebar from "qqq/components/QRecordSidebar"; import QRecordSidebar from "qqq/components/QRecordSidebar";
@ -80,6 +81,7 @@ function ViewContents({id, table}: Props): JSX.Element
const [actionsMenu, setActionsMenu] = useState(null); const [actionsMenu, setActionsMenu] = useState(null);
const [tableWidgets, setTableWidgets] = useState([] as QWidgetMetaData[]); const [tableWidgets, setTableWidgets] = useState([] as QWidgetMetaData[]);
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const {pageHeader, setPageHeader} = useContext(QContext);
const [, forceUpdate] = useReducer((x) => x + 1, 0); const [, forceUpdate] = useReducer((x) => x + 1, 0);
const openActionsMenu = (event: any) => setActionsMenu(event.currentTarget); const openActionsMenu = (event: any) => setActionsMenu(event.currentTarget);
@ -126,6 +128,7 @@ function ViewContents({id, table}: Props): JSX.Element
///////////////////// /////////////////////
const record = await qController.get(tableName, id); const record = await qController.get(tableName, id);
setRecord(record); setRecord(record);
setPageHeader(record.recordLabel);
/////////////////////////// ///////////////////////////
// load widget meta data // // load widget meta data //

View File

@ -20,9 +20,9 @@
*/ */
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance"; import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData";
import {QReportMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QReportMetaData"; import {QReportMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QReportMetaData";
import React, {useEffect, useState} from "react"; import React, {useContext, useEffect, useState} from "react";
import QContext from "QContext";
import ProcessRun from "qqq/pages/process-run/index"; import ProcessRun from "qqq/pages/process-run/index";
import QClient from "qqq/utils/QClient"; import QClient from "qqq/utils/QClient";
@ -36,10 +36,11 @@ function ReportRun({report}: Props): JSX.Element
// const reportNameParam = useParams().reportName; // const reportNameParam = useParams().reportName;
// const processName = process === null ? processNameParam : process.name; // const processName = process === null ? processNameParam : process.name;
const [metaData, setMetaData] = useState(null as QInstance); const [metaData, setMetaData] = useState(null as QInstance);
const {pageHeader, setPageHeader} = useContext(QContext);
useEffect(() => useEffect(() =>
{ {
if(!metaData) if (!metaData)
{ {
(async () => (async () =>
{ {
@ -49,18 +50,20 @@ function ReportRun({report}: Props): JSX.Element
} }
}); });
if(metaData) if (metaData)
{ {
console.log(`Report Process name is ${report.processName}`) setPageHeader(report.label);
const process = metaData.processes.get(report.processName)
console.log(`Process is ${process.name}`) console.log(`Report Process name is ${report.processName}`);
const defaultProcessValues = {reportName: report.name} const process = metaData.processes.get(report.processName);
console.log(`Process is ${process.name}`);
const defaultProcessValues = {reportName: report.name};
return (<ProcessRun process={process} defaultProcessValues={defaultProcessValues} />); return (<ProcessRun process={process} defaultProcessValues={defaultProcessValues} />);
} }
else else
{ {
// todo - loading? // todo - loading?
return (<div/>); return (<div />);
} }
} }

View File

@ -31,7 +31,7 @@ import {QJobError} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobEr
import {QJobRunning} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobRunning"; import {QJobRunning} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobRunning";
import {QJobStarted} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobStarted"; import {QJobStarted} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobStarted";
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord"; import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
import {Button, Icon, CircularProgress, TablePagination} from "@mui/material"; import {Button, CircularProgress, Icon, TablePagination} from "@mui/material";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import Card from "@mui/material/Card"; import Card from "@mui/material/Card";
import Grid from "@mui/material/Grid"; import Grid from "@mui/material/Grid";
@ -41,12 +41,12 @@ import StepLabel from "@mui/material/StepLabel";
import Stepper from "@mui/material/Stepper"; import Stepper from "@mui/material/Stepper";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import {DataGridPro, GridColDef} from "@mui/x-data-grid-pro"; import {DataGridPro, GridColDef} from "@mui/x-data-grid-pro";
import {GoogleOAuthProvider} from "@react-oauth/google";
import FormData from "form-data"; import FormData from "form-data";
import {Form, Formik} from "formik"; import {Form, Formik} from "formik";
import React, {useEffect, useState} from "react"; import React, {useContext, useEffect, useState} from "react";
import {useLocation, useParams, useNavigate} from "react-router-dom"; import {useLocation, useNavigate, useParams} from "react-router-dom";
import * as Yup from "yup"; import * as Yup from "yup";
import QContext from "QContext";
import BaseLayout from "qqq/components/BaseLayout"; import BaseLayout from "qqq/components/BaseLayout";
import {QCancelButton, QSubmitButton} from "qqq/components/QButtons"; import {QCancelButton, QSubmitButton} from "qqq/components/QButtons";
import QDynamicForm from "qqq/components/QDynamicForm"; import QDynamicForm from "qqq/components/QDynamicForm";
@ -102,6 +102,8 @@ function ProcessRun({process, defaultProcessValues}: Props): JSX.Element
const [showErrorDetail, setShowErrorDetail] = useState(false); const [showErrorDetail, setShowErrorDetail] = useState(false);
const [showFullHelpText, setShowFullHelpText] = useState(false); const [showFullHelpText, setShowFullHelpText] = useState(false);
const {pageHeader, setPageHeader} = useContext(QContext);
////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////
// for setting the processError state - call this function, which will also set the isUserFacingError state // // for setting the processError state - call this function, which will also set the isUserFacingError state //
////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////
@ -302,7 +304,7 @@ function ProcessRun({process, defaultProcessValues}: Props): JSX.Element
component.values.previewText ? component.values.previewText ?
<> <>
<Box mt={1}> <Box mt={1}>
<Button onClick={toggleShowFullHelpText} startIcon={<Icon>{showFullHelpText ? "expand_less" : "expand_more"}</Icon>} sx={{pl: 1}} > <Button onClick={toggleShowFullHelpText} startIcon={<Icon>{showFullHelpText ? "expand_less" : "expand_more"}</Icon>} sx={{pl: 1}}>
{showFullHelpText ? "Hide " : "Show "} {showFullHelpText ? "Hide " : "Show "}
{component.values.previewText} {component.values.previewText}
</Button> </Button>
@ -471,17 +473,17 @@ function ProcessRun({process, defaultProcessValues}: Props): JSX.Element
{ {
let rs: QFieldMetaData[] = []; let rs: QFieldMetaData[] = [];
if(activeStep && activeStep.formFields) if (activeStep && activeStep.formFields)
{ {
for(let i = 0; i<activeStep.formFields.length; i++) for (let i = 0; i < activeStep.formFields.length; i++)
{ {
rs.push(activeStep.formFields[i]); rs.push(activeStep.formFields[i]);
} }
} }
if(processValues.inputFieldList) if (processValues.inputFieldList)
{ {
for(let i = 0; i<processValues.inputFieldList.length; i++) for (let i = 0; i < processValues.inputFieldList.length; i++)
{ {
let inputField = new QFieldMetaData(processValues.inputFieldList[i]); let inputField = new QFieldMetaData(processValues.inputFieldList[i]);
rs.push(inputField); rs.push(inputField);
@ -501,6 +503,7 @@ function ProcessRun({process, defaultProcessValues}: Props): JSX.Element
console.log("No process meta data yet, so returning early"); console.log("No process meta data yet, so returning early");
return; return;
} }
setPageHeader(processMetaData.label)
let newIndex = null; let newIndex = null;
if (typeof newStep === "number") if (typeof newStep === "number")
@ -590,7 +593,7 @@ function ProcessRun({process, defaultProcessValues}: Props): JSX.Element
addField("googleDriveFolderName", {type: "hidden", omitFromQDynamicForm: true}, null, null); addField("googleDriveFolderName", {type: "hidden", omitFromQDynamicForm: true}, null, null);
} }
if(Object.keys(dynamicFormFields).length > 0) if (Object.keys(dynamicFormFields).length > 0)
{ {
/////////////////////////////////////////// ///////////////////////////////////////////
// if there are form fields, set them up // // if there are form fields, set them up //
@ -743,7 +746,7 @@ function ProcessRun({process, defaultProcessValues}: Props): JSX.Element
const qJobError = lastProcessResponse as QJobError; const qJobError = lastProcessResponse as QJobError;
console.log(`Got an error from the backend... ${qJobError.error} : ${qJobError.userFacingError}`); console.log(`Got an error from the backend... ${qJobError.error} : ${qJobError.userFacingError}`);
setJobUUID(null); setJobUUID(null);
if(qJobError.userFacingError) if (qJobError.userFacingError)
{ {
setProcessError(qJobError.userFacingError, true); setProcessError(qJobError.userFacingError, true);
} }
@ -874,9 +877,9 @@ function ProcessRun({process, defaultProcessValues}: Props): JSX.Element
return; return;
} }
if(defaultProcessValues) if (defaultProcessValues)
{ {
for(let key in defaultProcessValues) for (let key in defaultProcessValues)
{ {
queryStringPairsForInit.push(`${key}=${encodeURIComponent(defaultProcessValues[key])}`); queryStringPairsForInit.push(`${key}=${encodeURIComponent(defaultProcessValues[key])}`);
} }