mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-18 05:10:45 +00:00
Merge pull request #10 from Kingsrook/feature/sprint-14
Feature/sprint 14
This commit is contained in:
@ -13,7 +13,7 @@
|
|||||||
"@fullcalendar/interaction": "5.10.0",
|
"@fullcalendar/interaction": "5.10.0",
|
||||||
"@fullcalendar/react": "5.10.0",
|
"@fullcalendar/react": "5.10.0",
|
||||||
"@fullcalendar/timegrid": "5.10.0",
|
"@fullcalendar/timegrid": "5.10.0",
|
||||||
"@kingsrook/qqq-frontend-core": "1.0.25",
|
"@kingsrook/qqq-frontend-core": "1.0.31",
|
||||||
"@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",
|
||||||
@ -34,6 +34,7 @@
|
|||||||
"@types/react": "17.0.38",
|
"@types/react": "17.0.38",
|
||||||
"@types/react-dom": "17.0.11",
|
"@types/react-dom": "17.0.11",
|
||||||
"@types/react-router-hash-link": "2.4.5",
|
"@types/react-router-hash-link": "2.4.5",
|
||||||
|
"ace-builds": "1.12.3",
|
||||||
"chart.js": "3.4.1",
|
"chart.js": "3.4.1",
|
||||||
"chroma-js": "2.4.2",
|
"chroma-js": "2.4.2",
|
||||||
"datejs": "1.0.0-rc3",
|
"datejs": "1.0.0-rc3",
|
||||||
@ -45,6 +46,7 @@
|
|||||||
"html-react-parser": "1.4.8",
|
"html-react-parser": "1.4.8",
|
||||||
"http-proxy-middleware": "2.0.6",
|
"http-proxy-middleware": "2.0.6",
|
||||||
"react": "17.0.2",
|
"react": "17.0.2",
|
||||||
|
"react-ace": "10.1.0",
|
||||||
"react-chartjs-2": "3.0.4",
|
"react-chartjs-2": "3.0.4",
|
||||||
"react-cookie": "4.1.1",
|
"react-cookie": "4.1.1",
|
||||||
"react-dom": "17.0.2",
|
"react-dom": "17.0.2",
|
||||||
@ -73,7 +75,7 @@
|
|||||||
"geff-ham": "rm -rf node_modules/ && rm -rf package-lock.json && npm install --legacy-peer-deps && npm start",
|
"geff-ham": "rm -rf node_modules/ && rm -rf package-lock.json && npm install --legacy-peer-deps && npm start",
|
||||||
"install-legacy-peer-deps": "npm install --legacy-peer-deps",
|
"install-legacy-peer-deps": "npm install --legacy-peer-deps",
|
||||||
"prepublishOnly": "tsc -p ./ --outDir lib/",
|
"prepublishOnly": "tsc -p ./ --outDir lib/",
|
||||||
"start": "react-scripts start",
|
"start": "BROWSER=none react-scripts start",
|
||||||
"test": "react-scripts test",
|
"test": "react-scripts test",
|
||||||
"cypress:open": "cypress open"
|
"cypress:open": "cypress open"
|
||||||
},
|
},
|
||||||
|
BIN
public/integration-logos/deposco.png
Normal file
BIN
public/integration-logos/deposco.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
public/integration-logos/easypost.png
Normal file
BIN
public/integration-logos/easypost.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
BIN
public/integration-logos/infoplus.png
Normal file
BIN
public/integration-logos/infoplus.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
BIN
public/integration-logos/shipstation.png
Normal file
BIN
public/integration-logos/shipstation.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
10
src/App.tsx
10
src/App.tsx
@ -48,7 +48,8 @@ import Overview from "qqq/pages/dashboards/Overview";
|
|||||||
import EntityCreate from "qqq/pages/entity-create";
|
import EntityCreate from "qqq/pages/entity-create";
|
||||||
import EntityEdit from "qqq/pages/entity-edit";
|
import EntityEdit from "qqq/pages/entity-edit";
|
||||||
import EntityList from "qqq/pages/entity-list";
|
import EntityList from "qqq/pages/entity-list";
|
||||||
import EntityView from "qqq/pages/entity-view";
|
import EntityDeveloperView from "qqq/pages/entity-view/EntityDeveloperView";
|
||||||
|
import EntityView from "qqq/pages/entity-view/EntityView";
|
||||||
import ProcessRun from "qqq/pages/process-run";
|
import ProcessRun from "qqq/pages/process-run";
|
||||||
import ReportRun from "qqq/pages/process-run/ReportRun";
|
import ReportRun from "qqq/pages/process-run/ReportRun";
|
||||||
import QClient from "qqq/utils/QClient";
|
import QClient from "qqq/utils/QClient";
|
||||||
@ -262,6 +263,13 @@ export default function App()
|
|||||||
component: <EntityEdit table={table} />,
|
component: <EntityEdit table={table} />,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
routeList.push({
|
||||||
|
name: `${app.label}`,
|
||||||
|
key: `${app.name}.dev`,
|
||||||
|
route: `${path}/:id/dev`,
|
||||||
|
component: <EntityDeveloperView table={table} />,
|
||||||
|
});
|
||||||
|
|
||||||
const processesForTable = QProcessUtils.getProcessesForTable(metaData, table.name, true);
|
const processesForTable = QProcessUtils.getProcessesForTable(metaData, table.name, true);
|
||||||
processesForTable.forEach((process) =>
|
processesForTable.forEach((process) =>
|
||||||
{
|
{
|
||||||
|
37
src/qqq/components/CustomWidthTooltip/CustomWidthTooltip.tsx
Normal file
37
src/qqq/components/CustomWidthTooltip/CustomWidthTooltip.tsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* 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 {tooltipClasses, TooltipProps} from "@mui/material";
|
||||||
|
import {styled} from "@mui/material/styles";
|
||||||
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const CustomWidthTooltip = styled(({className, ...props}: TooltipProps) => (
|
||||||
|
<Tooltip {...props} classes={{popper: className}} />
|
||||||
|
))({
|
||||||
|
[`& .${tooltipClasses.tooltip}`]: {
|
||||||
|
maxWidth: 500,
|
||||||
|
textAlign: "left",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
export default CustomWidthTooltip
|
@ -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 {Capability} from "@kingsrook/qqq-frontend-core/lib/model/metaData/Capability";
|
||||||
import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
|
import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
|
||||||
import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType";
|
import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType";
|
||||||
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||||
@ -73,6 +74,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 [noCapabilityError, setNoCapabilityError] = useState(null as string);
|
||||||
|
|
||||||
const {pageHeader, setPageHeader} = useContext(QContext);
|
const {pageHeader, setPageHeader} = useContext(QContext);
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@ -140,11 +143,21 @@ function EntityForm({table, id}: Props): JSX.Element
|
|||||||
});
|
});
|
||||||
|
|
||||||
setFormValues(formValues);
|
setFormValues(formValues);
|
||||||
|
|
||||||
|
if(!tableMetaData.capabilities.has(Capability.TABLE_UPDATE))
|
||||||
|
{
|
||||||
|
setNoCapabilityError("You may not edit records in this table");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
setFormTitle(`Creating New ${tableMetaData?.label}`);
|
setFormTitle(`Creating New ${tableMetaData?.label}`);
|
||||||
setPageHeader(`Creating New ${tableMetaData?.label}`);
|
setPageHeader(`Creating New ${tableMetaData?.label}`);
|
||||||
|
|
||||||
|
if(!tableMetaData.capabilities.has(Capability.TABLE_INSERT))
|
||||||
|
{
|
||||||
|
setNoCapabilityError("You may not create records in this table");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
setInitialValues(initialValues);
|
setInitialValues(initialValues);
|
||||||
|
|
||||||
@ -168,6 +181,11 @@ function EntityForm({table, id}: Props): JSX.Element
|
|||||||
const section = tableSections[i];
|
const section = tableSections[i];
|
||||||
const sectionDynamicFormFields: any[] = [];
|
const sectionDynamicFormFields: any[] = [];
|
||||||
|
|
||||||
|
if(section.isHidden)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
for (let j = 0; j < section.fieldNames.length; j++)
|
for (let j = 0; j < section.fieldNames.length; j++)
|
||||||
{
|
{
|
||||||
const fieldName = section.fieldNames[j];
|
const fieldName = section.fieldNames[j];
|
||||||
@ -277,6 +295,19 @@ function EntityForm({table, id}: Props): JSX.Element
|
|||||||
|
|
||||||
const formId = id != null ? `edit-${tableMetaData?.name}-form` : `create-${tableMetaData?.name}-form`;
|
const formId = id != null ? `edit-${tableMetaData?.name}-form` : `create-${tableMetaData?.name}-form`;
|
||||||
|
|
||||||
|
if(noCapabilityError)
|
||||||
|
{
|
||||||
|
return <MDBox mb={3}>
|
||||||
|
<Grid container spacing={3}>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<MDBox mb={3}>
|
||||||
|
<Alert severity="error">{noCapabilityError}</Alert>
|
||||||
|
</MDBox>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</MDBox>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MDBox mb={3}>
|
<MDBox mb={3}>
|
||||||
<Grid container spacing={3}>
|
<Grid container spacing={3}>
|
||||||
|
@ -71,7 +71,7 @@ interface QDeleteButtonProps
|
|||||||
export function QDeleteButton({onClickHandler}: QDeleteButtonProps): JSX.Element
|
export function QDeleteButton({onClickHandler}: QDeleteButtonProps): JSX.Element
|
||||||
{
|
{
|
||||||
return (
|
return (
|
||||||
<MDBox ml={3} mr={3} width={standardWidth}>
|
<MDBox ml={3} width={standardWidth}>
|
||||||
<MDButton variant="gradient" color="primary" size="small" onClick={onClickHandler} fullWidth startIcon={<Icon>delete</Icon>}>
|
<MDButton variant="gradient" color="primary" size="small" onClick={onClickHandler} fullWidth startIcon={<Icon>delete</Icon>}>
|
||||||
Delete
|
Delete
|
||||||
</MDButton>
|
</MDButton>
|
||||||
@ -82,7 +82,7 @@ export function QDeleteButton({onClickHandler}: QDeleteButtonProps): JSX.Element
|
|||||||
export function QEditButton(): JSX.Element
|
export function QEditButton(): JSX.Element
|
||||||
{
|
{
|
||||||
return (
|
return (
|
||||||
<MDBox width={standardWidth}>
|
<MDBox ml={3} width={standardWidth}>
|
||||||
<Link to="edit">
|
<Link to="edit">
|
||||||
<MDButton variant="gradient" color="dark" size="small" fullWidth startIcon={<Icon>edit</Icon>}>
|
<MDButton variant="gradient" color="dark" size="small" fullWidth startIcon={<Icon>edit</Icon>}>
|
||||||
Edit
|
Edit
|
||||||
|
@ -59,6 +59,11 @@ function QRecordSidebar({tableSections, widgetMetaDataList, light, stickyTop}: P
|
|||||||
const sidebarEntries = [] as SidebarEntry[];
|
const sidebarEntries = [] as SidebarEntry[];
|
||||||
tableSections && tableSections.forEach((section, index) =>
|
tableSections && tableSections.forEach((section, index) =>
|
||||||
{
|
{
|
||||||
|
if(section.isHidden)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (index === 1 && widgetMetaDataList)
|
if (index === 1 && widgetMetaDataList)
|
||||||
{
|
{
|
||||||
widgetMetaDataList.forEach((widget) =>
|
widgetMetaDataList.forEach((widget) =>
|
||||||
|
@ -18,6 +18,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 {Capability} from "@kingsrook/qqq-frontend-core/lib/model/metaData/Capability";
|
||||||
import {QAppMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppMetaData";
|
import {QAppMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppMetaData";
|
||||||
import {QAppNodeType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppNodeType";
|
import {QAppNodeType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppNodeType";
|
||||||
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
|
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
|
||||||
@ -25,7 +26,7 @@ import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/
|
|||||||
import {QReportMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QReportMetaData";
|
import {QReportMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QReportMetaData";
|
||||||
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||||
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
|
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
|
||||||
import {Icon} from "@mui/material";
|
import {Box, Icon, Typography} 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";
|
||||||
@ -55,6 +56,8 @@ function AppHome({app}: Props): JSX.Element
|
|||||||
const [reports, setReports] = useState([] as QReportMetaData[]);
|
const [reports, setReports] = useState([] as QReportMetaData[]);
|
||||||
const [childApps, setChildApps] = useState([] as QAppMetaData[]);
|
const [childApps, setChildApps] = useState([] as QAppMetaData[]);
|
||||||
const [tableCounts, setTableCounts] = useState(new Map<string, { isLoading: boolean, value: number }>());
|
const [tableCounts, setTableCounts] = useState(new Map<string, { isLoading: boolean, value: number }>());
|
||||||
|
const [tableCountNumbers, setTableCountNumbers] = useState(new Map<string, string>());
|
||||||
|
const [tableCountTexts, setTableCountTexts] = useState(new Map<string, string>());
|
||||||
const [updatedTableCounts, setUpdatedTableCounts] = useState(new Date());
|
const [updatedTableCounts, setUpdatedTableCounts] = useState(new Date());
|
||||||
const [widgets, setWidgets] = useState([] as any[]);
|
const [widgets, setWidgets] = useState([] as any[]);
|
||||||
|
|
||||||
@ -113,15 +116,41 @@ function AppHome({app}: Props): JSX.Element
|
|||||||
setChildApps(newChildApps);
|
setChildApps(newChildApps);
|
||||||
|
|
||||||
const tableCounts = new Map<string, { isLoading: boolean, value: number }>();
|
const tableCounts = new Map<string, { isLoading: boolean, value: number }>();
|
||||||
|
const tableCountNumbers = new Map<string, string>();
|
||||||
|
const tableCountTexts = new Map<string, string>();
|
||||||
newTables.forEach((table) =>
|
newTables.forEach((table) =>
|
||||||
{
|
{
|
||||||
tableCounts.set(table.name, {isLoading: true, value: null});
|
tableCounts.set(table.name, {isLoading: true, value: null});
|
||||||
|
|
||||||
setTimeout(async () =>
|
setTimeout(async () =>
|
||||||
{
|
{
|
||||||
const count = await qController.count(table.name);
|
const tableMetaData = await qController.loadTableMetaData(table.name);
|
||||||
tableCounts.set(table.name, {isLoading: false, value: count});
|
let countResult = null;
|
||||||
|
if(tableMetaData.capabilities.has(Capability.TABLE_COUNT))
|
||||||
|
{
|
||||||
|
countResult = await qController.count(table.name);
|
||||||
|
|
||||||
|
if (countResult !== null && countResult !== undefined)
|
||||||
|
{
|
||||||
|
tableCountNumbers.set(table.name, countResult.toLocaleString());
|
||||||
|
tableCountTexts.set(table.name, countResult === 1 ? "total record" : "total records");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tableCountNumbers.set(table.name, "--");
|
||||||
|
tableCountTexts.set(table.name, " ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tableCountNumbers.set(table.name, "–");
|
||||||
|
tableCountTexts.set(table.name, " ");
|
||||||
|
}
|
||||||
|
|
||||||
|
tableCounts.set(table.name, {isLoading: false, value: countResult});
|
||||||
setTableCounts(tableCounts);
|
setTableCounts(tableCounts);
|
||||||
|
setTableCountNumbers(tableCountNumbers);
|
||||||
|
setTableCountTexts(tableCountTexts);
|
||||||
setUpdatedTableCounts(new Date());
|
setUpdatedTableCounts(new Date());
|
||||||
}, 1);
|
}, 1);
|
||||||
});
|
});
|
||||||
@ -169,9 +198,17 @@ function AppHome({app}: Props): JSX.Element
|
|||||||
{app.sections.map((section) => (
|
{app.sections.map((section) => (
|
||||||
<MDBox key={section.name} mb={3}>
|
<MDBox key={section.name} mb={3}>
|
||||||
<Card sx={{overflow: "visible"}}>
|
<Card sx={{overflow: "visible"}}>
|
||||||
<MDBox p={3}>
|
<Box p={3} display="flex" alignItems="center" gap=".5rem">
|
||||||
<MDTypography variant="h5">{section.label}</MDTypography>
|
{
|
||||||
</MDBox>
|
section.icon &&
|
||||||
|
(
|
||||||
|
section.icon.path && <img src={section.icon.path} alt={section.label} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
<Typography variant="h5">
|
||||||
|
{section.label}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
{
|
{
|
||||||
section.processes ? (
|
section.processes ? (
|
||||||
<MDBox p={3} pl={5} pt={0}>
|
<MDBox p={3} pl={5} pt={0}>
|
||||||
@ -261,8 +298,8 @@ function AppHome({app}: Props): JSX.Element
|
|||||||
<MDBox mb={3}>
|
<MDBox mb={3}>
|
||||||
<MiniStatisticsCard
|
<MiniStatisticsCard
|
||||||
title={{fontWeight: "bold", text: table.label}}
|
title={{fontWeight: "bold", text: table.label}}
|
||||||
count={!tableCounts.has(table.name) || tableCounts.get(table.name).isLoading ? "..." : tableCounts.get(table.name).value.toLocaleString()}
|
count={!tableCounts.has(table.name) || tableCounts.get(table.name).isLoading ? "..." : (tableCountNumbers.get(table.name))}
|
||||||
percentage={{color: "info", text: (!tableCounts.has(table.name) || tableCounts.get(table.name).isLoading ? "" : (tableCounts.get(table.name).value === 1 ? "total record" : "total records"))}}
|
percentage={{color: "info", text: (!tableCounts.has(table.name) || tableCounts.get(table.name).isLoading ? "" : (tableCountTexts.get(table.name)))}}
|
||||||
icon={{color: "info", component: <Icon>{table.iconName || app.iconName}</Icon>}}
|
icon={{color: "info", component: <Icon>{table.iconName || app.iconName}</Icon>}}
|
||||||
direction="right"
|
direction="right"
|
||||||
/>
|
/>
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {AdornmentType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/AdornmentType";
|
import {AdornmentType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/AdornmentType";
|
||||||
|
import {Capability} from "@kingsrook/qqq-frontend-core/lib/model/metaData/Capability";
|
||||||
import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType";
|
import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType";
|
||||||
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";
|
||||||
@ -219,7 +220,7 @@ function EntityList({table, launchProcess}: Props): JSX.Element
|
|||||||
const [tableProcesses, setTableProcesses] = useState([] as QProcessMetaData[]);
|
const [tableProcesses, setTableProcesses] = useState([] as QProcessMetaData[]);
|
||||||
const [allTableProcesses, setAllTableProcesses] = useState([] as QProcessMetaData[]);
|
const [allTableProcesses, setAllTableProcesses] = useState([] as QProcessMetaData[]);
|
||||||
const [pageNumber, setPageNumber] = useState(0);
|
const [pageNumber, setPageNumber] = useState(0);
|
||||||
const [totalRecords, setTotalRecords] = useState(0);
|
const [totalRecords, setTotalRecords] = useState(null);
|
||||||
const [selectedIds, setSelectedIds] = useState([] as string[]);
|
const [selectedIds, setSelectedIds] = useState([] as string[]);
|
||||||
const [selectFullFilterState, setSelectFullFilterState] = useState("n/a" as "n/a" | "checked" | "filter");
|
const [selectFullFilterState, setSelectFullFilterState] = useState("n/a" as "n/a" | "checked" | "filter");
|
||||||
const [columnsModel, setColumnsModel] = useState([] as GridColDef[]);
|
const [columnsModel, setColumnsModel] = useState([] as GridColDef[]);
|
||||||
@ -384,12 +385,15 @@ function EntityList({table, launchProcess}: Props): JSX.Element
|
|||||||
setLatestQueryId(thisQueryId);
|
setLatestQueryId(thisQueryId);
|
||||||
|
|
||||||
console.log(`Issuing query: ${thisQueryId}`);
|
console.log(`Issuing query: ${thisQueryId}`);
|
||||||
qController.count(tableName, qFilter).then((count) =>
|
if (tableMetaData.capabilities.has(Capability.TABLE_COUNT))
|
||||||
{
|
{
|
||||||
countResults[thisQueryId] = count;
|
qController.count(tableName, qFilter).then((count) =>
|
||||||
setCountResults(countResults);
|
{
|
||||||
setReceivedCountTimestamp(new Date());
|
countResults[thisQueryId] = count;
|
||||||
});
|
setCountResults(countResults);
|
||||||
|
setReceivedCountTimestamp(new Date());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
qController.query(tableName, qFilter, rowsPerPage, pageNumber * rowsPerPage).then((results) =>
|
qController.query(tableName, qFilter, rowsPerPage, pageNumber * rowsPerPage).then((results) =>
|
||||||
{
|
{
|
||||||
@ -584,7 +588,7 @@ function EntityList({table, launchProcess}: 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, "query");
|
||||||
if (typeof value !== "string")
|
if (typeof value !== "string")
|
||||||
{
|
{
|
||||||
columnsToRender[field.name] = true;
|
columnsToRender[field.name] = true;
|
||||||
@ -592,9 +596,26 @@ function EntityList({table, launchProcess}: Props): JSX.Element
|
|||||||
row[field.name] = value;
|
row[field.name] = value;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if(!row["id"])
|
||||||
|
{
|
||||||
|
row["id"] = row[tableMetaData.primaryKeyField];
|
||||||
|
}
|
||||||
|
|
||||||
rows.push(row);
|
rows.push(row);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// do this secondary check for columnsToRender - in case we didn't have any rows above, and our check for string isn't enough. //
|
||||||
|
// ... shouldn't this be just based on the field definition anyway... ? plus adornments? //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
fields.forEach((field) =>
|
||||||
|
{
|
||||||
|
if(field.possibleValueSourceName)
|
||||||
|
{
|
||||||
|
columnsToRender[field.name] = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if(columnsModel.length == 0)
|
if(columnsModel.length == 0)
|
||||||
{
|
{
|
||||||
setupGridColumns(columnsToRender);
|
setupGridColumns(columnsToRender);
|
||||||
@ -877,21 +898,6 @@ function EntityList({table, launchProcess}: Props): JSX.Element
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRecordIdsForProcess(): string | QQueryFilter
|
|
||||||
{
|
|
||||||
if (selectFullFilterState === "filter")
|
|
||||||
{
|
|
||||||
return (buildQFilter(filterModel));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedIds.length > 0)
|
|
||||||
{
|
|
||||||
return (selectedIds.join(","));
|
|
||||||
}
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
const openModalProcess = (process: QProcessMetaData = null) =>
|
const openModalProcess = (process: QProcessMetaData = null) =>
|
||||||
{
|
{
|
||||||
if (selectFullFilterState === "filter")
|
if (selectFullFilterState === "filter")
|
||||||
@ -979,7 +985,23 @@ function EntityList({table, launchProcess}: Props): JSX.Element
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const defaultLabelDisplayedRows = ({from, to, count}) =>
|
const defaultLabelDisplayedRows = ({from, to, count}) =>
|
||||||
{
|
{
|
||||||
if (count !== null && count !== undefined)
|
if(tableMetaData && !tableMetaData.capabilities.has(Capability.TABLE_COUNT))
|
||||||
|
{
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// to avoid a non-countable table showing (this is what data-grid did) 91-100 even if there were only 95 records, //
|
||||||
|
// we'll do this... not quite good enough, but better than the original //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
if(rows.length > 0 && rows.length < to - from)
|
||||||
|
{
|
||||||
|
to = from + rows.length;
|
||||||
|
}
|
||||||
|
return (`Showing ${from.toLocaleString()} to ${to.toLocaleString()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// treat -1 as the sentinel that it's set as below -- remember, we did that so that 'to' would have a value in here when there's no count. //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
if (count !== null && count !== undefined && count !== -1)
|
||||||
{
|
{
|
||||||
if (count === 0)
|
if (count === 0)
|
||||||
{
|
{
|
||||||
@ -998,7 +1020,9 @@ function EntityList({table, launchProcess}: Props): JSX.Element
|
|||||||
return (
|
return (
|
||||||
<TablePagination
|
<TablePagination
|
||||||
component="div"
|
component="div"
|
||||||
count={totalRecords === null ? 0 : totalRecords}
|
// note - passing null here makes the 'to' param in the defaultLabelDisplayedRows also be null,
|
||||||
|
// so pass some sentinel value...
|
||||||
|
count={totalRecords === null ? -1 : totalRecords}
|
||||||
page={pageNumber}
|
page={pageNumber}
|
||||||
rowsPerPageOptions={[ 10, 25, 50, 100, 250 ]}
|
rowsPerPageOptions={[ 10, 25, 50, 100, 250 ]}
|
||||||
rowsPerPage={rowsPerPage}
|
rowsPerPage={rowsPerPage}
|
||||||
@ -1111,7 +1135,7 @@ function EntityList({table, launchProcess}: Props): JSX.Element
|
|||||||
selectFullFilterState === "filter" && (
|
selectFullFilterState === "filter" && (
|
||||||
<div className="selectionTool">
|
<div className="selectionTool">
|
||||||
All
|
All
|
||||||
<strong>{` ${totalRecords ? totalRecords.toLocaleString() : "All"} `}</strong>
|
<strong>{` ${totalRecords ? totalRecords.toLocaleString() : ""} `}</strong>
|
||||||
records matching this query are selected.
|
records matching this query are selected.
|
||||||
<Button onClick={() => setSelectFullFilterState("checked")}>
|
<Button onClick={() => setSelectFullFilterState("checked")}>
|
||||||
Select the
|
Select the
|
||||||
@ -1144,25 +1168,41 @@ function EntityList({table, launchProcess}: Props): JSX.Element
|
|||||||
onClose={closeActionsMenu}
|
onClose={closeActionsMenu}
|
||||||
keepMounted
|
keepMounted
|
||||||
>
|
>
|
||||||
<MenuItem onClick={bulkLoadClicked}>
|
{
|
||||||
<ListItemIcon><Icon>library_add</Icon></ListItemIcon>
|
table.capabilities.has(Capability.TABLE_INSERT) &&
|
||||||
Bulk Load
|
<MenuItem onClick={bulkLoadClicked}>
|
||||||
</MenuItem>
|
<ListItemIcon><Icon>library_add</Icon></ListItemIcon>
|
||||||
<MenuItem onClick={bulkEditClicked}>
|
Bulk Load
|
||||||
<ListItemIcon><Icon>edit</Icon></ListItemIcon>
|
</MenuItem>
|
||||||
Bulk Edit
|
}
|
||||||
</MenuItem>
|
{
|
||||||
<MenuItem onClick={bulkDeleteClicked}>
|
table.capabilities.has(Capability.TABLE_UPDATE) &&
|
||||||
<ListItemIcon><Icon>delete</Icon></ListItemIcon>
|
<MenuItem onClick={bulkEditClicked}>
|
||||||
Bulk Delete
|
<ListItemIcon><Icon>edit</Icon></ListItemIcon>
|
||||||
</MenuItem>
|
Bulk Edit
|
||||||
{tableProcesses.length > 0 && <Divider />}
|
</MenuItem>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
table.capabilities.has(Capability.TABLE_DELETE) &&
|
||||||
|
<MenuItem onClick={bulkDeleteClicked}>
|
||||||
|
<ListItemIcon><Icon>delete</Icon></ListItemIcon>
|
||||||
|
Bulk Delete
|
||||||
|
</MenuItem>
|
||||||
|
}
|
||||||
|
{(table.capabilities.has(Capability.TABLE_INSERT) || table.capabilities.has(Capability.TABLE_UPDATE) || table.capabilities.has(Capability.TABLE_DELETE)) && tableProcesses.length > 0 && <Divider />}
|
||||||
{tableProcesses.map((process) => (
|
{tableProcesses.map((process) => (
|
||||||
<MenuItem key={process.name} onClick={() => processClicked(process)}>
|
<MenuItem key={process.name} onClick={() => processClicked(process)}>
|
||||||
<ListItemIcon><Icon>{process.iconName ?? "arrow_forward"}</Icon></ListItemIcon>
|
<ListItemIcon><Icon>{process.iconName ?? "arrow_forward"}</Icon></ListItemIcon>
|
||||||
{process.label}
|
{process.label}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
|
{
|
||||||
|
tableProcesses.length == 0 && !table.capabilities.has(Capability.TABLE_INSERT) && !table.capabilities.has(Capability.TABLE_UPDATE) && !table.capabilities.has(Capability.TABLE_DELETE) &&
|
||||||
|
<MenuItem disabled>
|
||||||
|
<ListItemIcon><Icon>block</Icon></ListItemIcon>
|
||||||
|
<i>No actions available</i>
|
||||||
|
</MenuItem>
|
||||||
|
}
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1224,7 +1264,10 @@ function EntityList({table, launchProcess}: Props): JSX.Element
|
|||||||
{renderActionsMenu}
|
{renderActionsMenu}
|
||||||
</MDBox>
|
</MDBox>
|
||||||
|
|
||||||
<QCreateNewButton />
|
{
|
||||||
|
table.capabilities.has(Capability.TABLE_INSERT) &&
|
||||||
|
<QCreateNewButton />
|
||||||
|
}
|
||||||
|
|
||||||
</MDBox>
|
</MDBox>
|
||||||
<Card>
|
<Card>
|
||||||
@ -1242,6 +1285,7 @@ function EntityList({table, launchProcess}: Props): JSX.Element
|
|||||||
disableSelectionOnClick
|
disableSelectionOnClick
|
||||||
autoHeight
|
autoHeight
|
||||||
rows={rows}
|
rows={rows}
|
||||||
|
// getRowHeight={() => "auto"} // maybe nice? wraps values in cells...
|
||||||
columns={columnsModel}
|
columns={columnsModel}
|
||||||
rowBuffer={10}
|
rowBuffer={10}
|
||||||
rowCount={totalRecords === null ? 0 : totalRecords}
|
rowCount={totalRecords === null ? 0 : totalRecords}
|
117
src/qqq/pages/entity-view/AssociatedScriptEditor.tsx
Normal file
117
src/qqq/pages/entity-view/AssociatedScriptEditor.tsx
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
/*
|
||||||
|
* 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 {Typography} from "@mui/material";
|
||||||
|
import Box from "@mui/material/Box";
|
||||||
|
import Button from "@mui/material/Button";
|
||||||
|
import Card from "@mui/material/Card";
|
||||||
|
import Grid from "@mui/material/Grid";
|
||||||
|
import TextField from "@mui/material/TextField";
|
||||||
|
import React, {useState} from "react";
|
||||||
|
import AceEditor from "react-ace";
|
||||||
|
import {QCancelButton, QSaveButton} from "qqq/components/QButtons";
|
||||||
|
import QClient from "qqq/utils/QClient";
|
||||||
|
|
||||||
|
interface Props
|
||||||
|
{
|
||||||
|
tableName: string;
|
||||||
|
primaryKey: any;
|
||||||
|
fieldName: string;
|
||||||
|
titlePrefix: string;
|
||||||
|
recordLabel: string;
|
||||||
|
scriptName: string;
|
||||||
|
code: string;
|
||||||
|
closeCallback: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const qController = QClient.getInstance();
|
||||||
|
|
||||||
|
function AssociatedScriptEditor({tableName, primaryKey, fieldName, titlePrefix, recordLabel, scriptName, code, closeCallback}: Props): JSX.Element
|
||||||
|
{
|
||||||
|
const [closing, setClosing] = useState(false);
|
||||||
|
const [updatedCode, setUpdatedCode] = useState(code)
|
||||||
|
const [commitMessage, setCommitMessage] = useState("")
|
||||||
|
|
||||||
|
const saveClicked = () =>
|
||||||
|
{
|
||||||
|
setClosing(true);
|
||||||
|
|
||||||
|
(async () =>
|
||||||
|
{
|
||||||
|
const rs = await qController.storeRecordAssociatedScript(tableName, primaryKey, fieldName, updatedCode, commitMessage);
|
||||||
|
closeCallback(null, "saved", "Saved New " + scriptName);
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancelClicked = () =>
|
||||||
|
{
|
||||||
|
setClosing(true);
|
||||||
|
closeCallback(null, "cancelled");
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateCode = (value: string, event: any) =>
|
||||||
|
{
|
||||||
|
setUpdatedCode(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateCommitMessage = (event: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
|
{
|
||||||
|
setCommitMessage(event.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{position: "absolute", overflowY: "auto", height: "100%", width: "100%"}} p={12}>
|
||||||
|
<Card sx={{height: "100%", p: 3}}>
|
||||||
|
<Typography variant="h5" pb={1}>
|
||||||
|
{`${titlePrefix}: ${recordLabel} - ${scriptName}`}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<AceEditor
|
||||||
|
mode="javascript"
|
||||||
|
theme="github"
|
||||||
|
name="editor"
|
||||||
|
editorProps={{$blockScrolling: true}}
|
||||||
|
onChange={updateCode}
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
value={updatedCode}
|
||||||
|
style={{border: "1px solid gray"}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box pt={1}>
|
||||||
|
<Grid container alignItems="flex-end">
|
||||||
|
<Box width="50%">
|
||||||
|
<TextField id="commitMessage" label="Commit Message" variant="standard" fullWidth value={commitMessage} onChange={updateCommitMessage} />
|
||||||
|
</Box>
|
||||||
|
<Grid container justifyContent="flex-end" spacing={3}>
|
||||||
|
<QCancelButton disabled={closing} onClickHandler={cancelClicked} />
|
||||||
|
<QSaveButton disabled={closing} onClickHandler={saveClicked} />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
</Card>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AssociatedScriptEditor;
|
654
src/qqq/pages/entity-view/EntityDeveloperView.tsx
Normal file
654
src/qqq/pages/entity-view/EntityDeveloperView.tsx
Normal file
@ -0,0 +1,654 @@
|
|||||||
|
/*
|
||||||
|
* 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 {QException} from "@kingsrook/qqq-frontend-core/lib/exceptions/QException";
|
||||||
|
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||||
|
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
|
||||||
|
import {Alert, Chip, Icon, ListItem, ListItemAvatar, Typography} from "@mui/material";
|
||||||
|
import Avatar from "@mui/material/Avatar";
|
||||||
|
import Box from "@mui/material/Box";
|
||||||
|
import Button from "@mui/material/Button";
|
||||||
|
import Card from "@mui/material/Card";
|
||||||
|
import Divider from "@mui/material/Divider";
|
||||||
|
import Grid from "@mui/material/Grid";
|
||||||
|
import List from "@mui/material/List";
|
||||||
|
import ListItemIcon from "@mui/material/ListItemIcon";
|
||||||
|
import ListItemText from "@mui/material/ListItemText";
|
||||||
|
import Modal from "@mui/material/Modal";
|
||||||
|
import Snackbar from "@mui/material/Snackbar";
|
||||||
|
import Tab from "@mui/material/Tab";
|
||||||
|
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 Tabs from "@mui/material/Tabs";
|
||||||
|
import TextField from "@mui/material/TextField";
|
||||||
|
import React, {useContext, useReducer, useState} from "react";
|
||||||
|
import AceEditor from "react-ace";
|
||||||
|
import {useParams} from "react-router-dom";
|
||||||
|
import QContext from "QContext";
|
||||||
|
import BaseLayout from "qqq/components/BaseLayout";
|
||||||
|
import CustomWidthTooltip from "qqq/components/CustomWidthTooltip/CustomWidthTooltip";
|
||||||
|
import DataTableBodyCell from "qqq/components/Temporary/DataTable/DataTableBodyCell";
|
||||||
|
import DataTableHeadCell from "qqq/components/Temporary/DataTable/DataTableHeadCell";
|
||||||
|
import MDBox from "qqq/components/Temporary/MDBox";
|
||||||
|
import AssociatedScriptEditor from "qqq/pages/entity-view/AssociatedScriptEditor";
|
||||||
|
import QClient from "qqq/utils/QClient";
|
||||||
|
import QValueUtils from "qqq/utils/QValueUtils";
|
||||||
|
|
||||||
|
import "ace-builds/src-noconflict/mode-java";
|
||||||
|
import "ace-builds/src-noconflict/mode-javascript";
|
||||||
|
import "ace-builds/src-noconflict/mode-json";
|
||||||
|
import "ace-builds/src-noconflict/theme-github";
|
||||||
|
import "ace-builds/src-noconflict/ext-language_tools";
|
||||||
|
|
||||||
|
|
||||||
|
const qController = QClient.getInstance();
|
||||||
|
|
||||||
|
interface TabPanelProps
|
||||||
|
{
|
||||||
|
children?: React.ReactNode;
|
||||||
|
index: number;
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function TabPanel(props: TabPanelProps)
|
||||||
|
{
|
||||||
|
const {children, value, index, ...other} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
role="tabpanel"
|
||||||
|
hidden={value !== index}
|
||||||
|
id={`simple-tabpanel-${index}`}
|
||||||
|
aria-labelledby={`simple-tab-${index}`}
|
||||||
|
{...other}
|
||||||
|
>
|
||||||
|
{value === index && (
|
||||||
|
<Box>
|
||||||
|
<Typography>{children}</Typography>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Declaring props types for ViewForm
|
||||||
|
interface Props
|
||||||
|
{
|
||||||
|
table?: QTableMetaData;
|
||||||
|
}
|
||||||
|
|
||||||
|
EntityDeveloperView.defaultProps =
|
||||||
|
{
|
||||||
|
table: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
function EntityDeveloperView({table}: Props): JSX.Element
|
||||||
|
{
|
||||||
|
const {id} = useParams();
|
||||||
|
|
||||||
|
const tableName = table.name;
|
||||||
|
const [asyncLoadInited, setAsyncLoadInited] = useState(false);
|
||||||
|
const [tableMetaData, setTableMetaData] = useState(null);
|
||||||
|
|
||||||
|
const [record, setRecord] = useState(null as QRecord);
|
||||||
|
const [recordJSON, setRecordJSON] = useState("");
|
||||||
|
const [associatedScripts, setAssociatedScripts] = useState([] as any[]);
|
||||||
|
const [notFoundMessage, setNotFoundMessage] = useState(null);
|
||||||
|
|
||||||
|
const [selectedTabs, setSelectedTabs] = useState({} as any);
|
||||||
|
const [viewingRevisions, setViewingRevisions] = useState({} as any);
|
||||||
|
const [scriptLogs, setScriptLogs] = useState({} as any);
|
||||||
|
|
||||||
|
const [editingScript, setEditingScript] = useState(null as any);
|
||||||
|
const [alertText, setAlertText] = useState(null as string);
|
||||||
|
|
||||||
|
const {setPageHeader} = useContext(QContext);
|
||||||
|
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
||||||
|
|
||||||
|
if (!asyncLoadInited)
|
||||||
|
{
|
||||||
|
setAsyncLoadInited(true);
|
||||||
|
|
||||||
|
(async () =>
|
||||||
|
{
|
||||||
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
// load the full table meta-data (the one we took in is a partial) //
|
||||||
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
const tableMetaData = await qController.loadTableMetaData(tableName);
|
||||||
|
setTableMetaData(tableMetaData);
|
||||||
|
|
||||||
|
//////////////////////////////
|
||||||
|
// load top-level meta-data //
|
||||||
|
//////////////////////////////
|
||||||
|
const metaData = await qController.loadMetaData();
|
||||||
|
QValueUtils.qInstance = metaData;
|
||||||
|
|
||||||
|
/////////////////////
|
||||||
|
// load the record //
|
||||||
|
/////////////////////
|
||||||
|
let record: QRecord;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const developerModeData = await qController.getRecordDeveloperMode(tableName, id);
|
||||||
|
record = new QRecord(developerModeData.record);
|
||||||
|
console.log("Loaded record developer mode.");
|
||||||
|
setRecord(record);
|
||||||
|
|
||||||
|
setAssociatedScripts(developerModeData.associatedScripts);
|
||||||
|
|
||||||
|
const recordJSONObject = {} as any;
|
||||||
|
for (let key of record.values.keys())
|
||||||
|
{
|
||||||
|
recordJSONObject[key] = record.values.get(key);
|
||||||
|
}
|
||||||
|
setRecordJSON(JSON.stringify(recordJSONObject, null, 3));
|
||||||
|
}
|
||||||
|
catch (e)
|
||||||
|
{
|
||||||
|
if (e instanceof QException)
|
||||||
|
{
|
||||||
|
if ((e as QException).status === "404")
|
||||||
|
{
|
||||||
|
setNotFoundMessage(`${tableMetaData.label} ${id} could not be found.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setPageHeader(record.recordLabel + " Developer Mode");
|
||||||
|
|
||||||
|
forceUpdate();
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
const revToColor = (fieldName: string, rev: number): string =>
|
||||||
|
{
|
||||||
|
let hash = 0;
|
||||||
|
let idFactor = 1;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
idFactor = Number(id);
|
||||||
|
}
|
||||||
|
catch (e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
const string = `${fieldName} ${90210 * idFactor * rev}`;
|
||||||
|
for (let i = 0; i < string.length; i += 1)
|
||||||
|
{
|
||||||
|
hash = string.charCodeAt(i) + ((hash << 5) - hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
let color = "#";
|
||||||
|
for (let i = 0; i < 3; i += 1)
|
||||||
|
{
|
||||||
|
const value = (hash >> (i * 8)) & 0xff;
|
||||||
|
color += `00${value.toString(16)}`.slice(-2);
|
||||||
|
}
|
||||||
|
return color;
|
||||||
|
};
|
||||||
|
|
||||||
|
const editScript = (fieldName: string, code: string) =>
|
||||||
|
{
|
||||||
|
const editingScript = {} as any;
|
||||||
|
editingScript.fieldName = fieldName;
|
||||||
|
editingScript.titlePrefix = code ? "Editing Script" : "Creating New Script";
|
||||||
|
editingScript.code = code;
|
||||||
|
setEditingScript(editingScript);
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeEditingScript = (event: object, reason: string, alert: string = null) =>
|
||||||
|
{
|
||||||
|
if (reason === "backdropClick")
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reason === "saved")
|
||||||
|
{
|
||||||
|
setAsyncLoadInited(false);
|
||||||
|
setAssociatedScripts([]);
|
||||||
|
viewingRevisions[editingScript.fieldName] = null;
|
||||||
|
setViewingRevisions(viewingRevisions);
|
||||||
|
forceUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alert)
|
||||||
|
{
|
||||||
|
setAlertText(alert);
|
||||||
|
}
|
||||||
|
|
||||||
|
setEditingScript(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeTab = (newValue: number, fieldName: string) =>
|
||||||
|
{
|
||||||
|
selectedTabs[fieldName] = newValue;
|
||||||
|
setSelectedTabs(selectedTabs);
|
||||||
|
forceUpdate();
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectRevision = (fieldName: string, revisionId: number) =>
|
||||||
|
{
|
||||||
|
viewingRevisions[fieldName] = revisionId;
|
||||||
|
setViewingRevisions(viewingRevisions);
|
||||||
|
|
||||||
|
scriptLogs[revisionId] = null;
|
||||||
|
setScriptLogs(scriptLogs);
|
||||||
|
|
||||||
|
loadRevisionLogs(fieldName, revisionId)
|
||||||
|
|
||||||
|
forceUpdate();
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadRevisionLogs = (fieldName: string, revisionId: number) =>
|
||||||
|
{
|
||||||
|
(async () =>
|
||||||
|
{
|
||||||
|
const rs = await qController.getRecordAssociatedScriptLogs(tableName, id, fieldName, revisionId);
|
||||||
|
scriptLogs[revisionId] = [];
|
||||||
|
if (rs["scriptLogRecords"])
|
||||||
|
{
|
||||||
|
scriptLogs[revisionId] = rs["scriptLogRecords"];
|
||||||
|
}
|
||||||
|
console.log("Script logs:");
|
||||||
|
console.log(scriptLogs[revisionId]);
|
||||||
|
setScriptLogs(scriptLogs);
|
||||||
|
forceUpdate();
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRevisionsList(scriptRevisions: any, fieldName: any, currentScriptRevisionId: any)
|
||||||
|
{
|
||||||
|
return <List sx={{pl: 3, height: "400px", overflow: "auto"}}>
|
||||||
|
{
|
||||||
|
scriptRevisions ? <></> :
|
||||||
|
<Typography variant="body2">
|
||||||
|
There are not any versions of this script.
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
scriptRevisions?.map((revision: any) => (
|
||||||
|
<React.Fragment key={revision.values.id}>
|
||||||
|
<ListItem sx={{p: 1}} alignItems="flex-start" selected={viewingRevisions[fieldName] == revision.values.id} onClick={(event) => selectRevision(fieldName, revision.values.id)}>
|
||||||
|
<ListItemAvatar>
|
||||||
|
<Avatar sx={{bgcolor: revToColor(fieldName, revision.values.sequenceNo)}}>{`${revision.values.sequenceNo}`}</Avatar>
|
||||||
|
</ListItemAvatar>
|
||||||
|
<ListItemText
|
||||||
|
primaryTypographyProps={{fontSize: "1rem"}}
|
||||||
|
secondaryTypographyProps={{fontSize: ".85rem"}}
|
||||||
|
primary={
|
||||||
|
<div style={{whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis"}} title={revision.values.commitMessage}>
|
||||||
|
{revision.values.id == currentScriptRevisionId && <Chip label="CURRENT" color="success" variant="outlined" size="small" sx={{mr: 1, fontSize: "0.75rem"}} />}
|
||||||
|
{revision.values.commitMessage}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
secondary={
|
||||||
|
<>
|
||||||
|
{QValueUtils.formatDateTime(revision.values.createDate)}
|
||||||
|
<br />
|
||||||
|
{revision.values.author}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<ListItemIcon sx={{minWidth: "auto", px: 1}}><Icon>settings</Icon></ListItemIcon>
|
||||||
|
</ListItem>
|
||||||
|
<Divider sx={{my: 0.5}} variant="inset" component="li" />
|
||||||
|
</React.Fragment>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</List>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getScriptLogs(revisionId: number)
|
||||||
|
{
|
||||||
|
const logs = scriptLogs[revisionId] as any[];
|
||||||
|
if (logs === null || logs === undefined)
|
||||||
|
{
|
||||||
|
return <Typography variant="body2" p={3}>Loading...</Typography>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logs.length === 0)
|
||||||
|
{
|
||||||
|
return <Typography variant="body2" p={3}>No logs available for this version.</Typography>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableContainer sx={{boxShadow: "none"}}>
|
||||||
|
<Table>
|
||||||
|
<Box component="thead">
|
||||||
|
<TableRow key="header">
|
||||||
|
<DataTableHeadCell sorted={false}>Timestamp</DataTableHeadCell>
|
||||||
|
<DataTableHeadCell sorted={false} align="right">Run Time (ms)</DataTableHeadCell>
|
||||||
|
<DataTableHeadCell sorted={false}>Had Error?</DataTableHeadCell>
|
||||||
|
<DataTableHeadCell sorted={false}>Input</DataTableHeadCell>
|
||||||
|
<DataTableHeadCell sorted={false}>Output</DataTableHeadCell>
|
||||||
|
<DataTableHeadCell sorted={false}>Logs</DataTableHeadCell>
|
||||||
|
</TableRow>
|
||||||
|
</Box>
|
||||||
|
<TableBody>
|
||||||
|
{
|
||||||
|
logs.map((logRecord) =>
|
||||||
|
{
|
||||||
|
let logs = "";
|
||||||
|
if (logRecord.values.scriptLogLine)
|
||||||
|
{
|
||||||
|
for (let i = 0; i < logRecord.values.scriptLogLine.length; i++)
|
||||||
|
{
|
||||||
|
console.log(" += " + i);
|
||||||
|
logs += (logRecord.values.scriptLogLine[i].values.text + "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableRow key={logRecord.values.id}>
|
||||||
|
<DataTableBodyCell>{QValueUtils.formatDateTime(logRecord.values.startTimestamp)}</DataTableBodyCell>
|
||||||
|
<DataTableBodyCell align="right">{logRecord.values.runTimeMillis?.toLocaleString()}</DataTableBodyCell>
|
||||||
|
<DataTableBodyCell>
|
||||||
|
<div style={{color: logRecord.values.hadError ? "red" : "auto"}}>{QValueUtils.formatBoolean(logRecord.values.hadError)}</div>
|
||||||
|
</DataTableBodyCell>
|
||||||
|
<DataTableBodyCell>{logRecord.values.input}</DataTableBodyCell>
|
||||||
|
<DataTableBodyCell>
|
||||||
|
{logRecord.values.output}
|
||||||
|
{logRecord.values.error}
|
||||||
|
</DataTableBodyCell>
|
||||||
|
<DataTableBodyCell>{logs}</DataTableBodyCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseLayout>
|
||||||
|
<MDBox>
|
||||||
|
<Grid container>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<MDBox mb={3}>
|
||||||
|
{
|
||||||
|
notFoundMessage
|
||||||
|
?
|
||||||
|
<MDBox>{notFoundMessage}</MDBox>
|
||||||
|
:
|
||||||
|
<MDBox pb={3}>
|
||||||
|
{
|
||||||
|
alertText ? (
|
||||||
|
<Snackbar open={alertText !== null && alertText !== ""} autoHideDuration={6000} onClose={() => setAlertText(null)} anchorOrigin={{vertical: "top", horizontal: "center"}}>
|
||||||
|
<Alert color="success" onClose={() => setAlertText(null)}>
|
||||||
|
{alertText}
|
||||||
|
</Alert>
|
||||||
|
</Snackbar>
|
||||||
|
) : ("")
|
||||||
|
}
|
||||||
|
|
||||||
|
<Grid container spacing={3}>
|
||||||
|
<Grid item xs={12} mb={3}>
|
||||||
|
<Card id="jsonView" sx={{mb: 3}}>
|
||||||
|
<Typography variant="h5" p={2}>Record Raw Values as JSON</Typography>
|
||||||
|
<AceEditor
|
||||||
|
mode="json"
|
||||||
|
theme="github"
|
||||||
|
name="recordJSON"
|
||||||
|
editorProps={{$blockScrolling: true}}
|
||||||
|
value={recordJSON}
|
||||||
|
readOnly
|
||||||
|
width="100%"
|
||||||
|
showPrintMargin={false}
|
||||||
|
height="200px"
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{
|
||||||
|
associatedScripts && associatedScripts.map((object) =>
|
||||||
|
{
|
||||||
|
let fieldName = object.associatedScript?.fieldName;
|
||||||
|
let field = tableMetaData.fields.get(fieldName);
|
||||||
|
|
||||||
|
let currentScriptRevisionId = object.script?.values?.currentScriptRevisionId;
|
||||||
|
|
||||||
|
if (!selectedTabs[fieldName])
|
||||||
|
{
|
||||||
|
selectedTabs[fieldName] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!viewingRevisions[fieldName] || viewingRevisions[fieldName] === -1)
|
||||||
|
{
|
||||||
|
console.log(`Defaulting revision for ${fieldName} to ${currentScriptRevisionId}`);
|
||||||
|
viewingRevisions[fieldName] = currentScriptRevisionId;
|
||||||
|
|
||||||
|
if(!scriptLogs[currentScriptRevisionId])
|
||||||
|
{
|
||||||
|
loadRevisionLogs(fieldName, currentScriptRevisionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const viewingRevisionArray = object.scriptRevisions?.filter((rev: any) => rev?.values?.id === viewingRevisions[fieldName]);
|
||||||
|
const code = viewingRevisionArray?.length > 0 ? viewingRevisionArray[0].values.contents : "";
|
||||||
|
const viewingSequenceNo = viewingRevisionArray?.length > 0 ? viewingRevisionArray[0].values.sequenceNo : "";
|
||||||
|
|
||||||
|
let editButtonTooltip = "";
|
||||||
|
let editButtonText = "Create New Script";
|
||||||
|
if (currentScriptRevisionId)
|
||||||
|
{
|
||||||
|
if (currentScriptRevisionId === viewingRevisions[fieldName])
|
||||||
|
{
|
||||||
|
editButtonTooltip = "If you make any changes to this script, a new version will be created when you hit Save.";
|
||||||
|
editButtonText = "Edit";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
editButtonTooltip = "If you want to make this previous Version active, bring up the Edit window, make any changes " +
|
||||||
|
"to the old Version if they are needed, then click Save. A new Version will be created, and set as Current.";
|
||||||
|
editButtonText = "Edit and Activate";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card key={fieldName} id={`associatedScript.${fieldName}`} sx={{mb: 3}}>
|
||||||
|
|
||||||
|
<Box display="flex" alignItems="center" justifyContent="space-between" gap={2}>
|
||||||
|
<Typography variant="h5" p={2}>{field?.label}</Typography>
|
||||||
|
<Tabs
|
||||||
|
sx={{mr: 1}}
|
||||||
|
value={selectedTabs[fieldName]}
|
||||||
|
onChange={(event, newValue) => changeTab(newValue, fieldName)}
|
||||||
|
variant="standard"
|
||||||
|
>
|
||||||
|
<Tab label="Code" id="simple-tab-0" aria-controls="simple-tabpanel-0" sx={{width: "100px"}} />
|
||||||
|
<Tab label="Logs" id="simple-tab-1" aria-controls="simple-tabpanel-1" sx={{width: "100px"}} />
|
||||||
|
<Tab label="Test" id="simple-tab-2" aria-controls="simple-tabpanel-2" sx={{width: "100px"}} />
|
||||||
|
<Tab label="Docs" id="simple-tab-3" aria-controls="simple-tabpanel-3" sx={{width: "100px"}} />
|
||||||
|
</Tabs>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<TabPanel index={0} value={selectedTabs[fieldName]}>
|
||||||
|
<Grid container>
|
||||||
|
<Grid item xs={4}>
|
||||||
|
<Box display="flex" alignItems="center" gap={2} pb={1} height="40px">
|
||||||
|
<Typography variant="h6" pl={3}>Versions</Typography>
|
||||||
|
</Box>
|
||||||
|
{getRevisionsList(object.scriptRevisions, fieldName, currentScriptRevisionId)}
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item xs={8}>
|
||||||
|
<Box display="flex" alignItems="center" gap={2} pb={1} height="40px">
|
||||||
|
{
|
||||||
|
currentScriptRevisionId &&
|
||||||
|
<Typography variant="h6">
|
||||||
|
{
|
||||||
|
currentScriptRevisionId === viewingRevisions[fieldName]
|
||||||
|
? (<>Current Version ({viewingSequenceNo})</>)
|
||||||
|
: (<>Version {viewingSequenceNo}</>)
|
||||||
|
}
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
<CustomWidthTooltip title={editButtonTooltip}>
|
||||||
|
<Button sx={{py: 0}} onClick={() => editScript(fieldName, code)}>
|
||||||
|
{editButtonText}
|
||||||
|
</Button>
|
||||||
|
</CustomWidthTooltip>
|
||||||
|
</Box>
|
||||||
|
{
|
||||||
|
code ? (
|
||||||
|
<>
|
||||||
|
<AceEditor
|
||||||
|
mode="javascript"
|
||||||
|
theme="github"
|
||||||
|
name={`view-${fieldName}`}
|
||||||
|
readOnly
|
||||||
|
editorProps={{$blockScrolling: true}}
|
||||||
|
width="100%"
|
||||||
|
height="400px"
|
||||||
|
value={code}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel index={1} value={selectedTabs[fieldName]}>
|
||||||
|
<Grid container height="440px">
|
||||||
|
<Grid item xs={4}>
|
||||||
|
<Box display="flex" alignItems="center" gap={2} pb={1} height="40px">
|
||||||
|
<Typography variant="h6" pl={3}>Versions</Typography>
|
||||||
|
</Box>
|
||||||
|
{getRevisionsList(object.scriptRevisions, fieldName, currentScriptRevisionId)}
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={8}>
|
||||||
|
<Box display="flex" alignItems="center" gap={2} pb={1} height="40px">
|
||||||
|
<Typography variant="h6" pl={3}>Script Logs (Version {viewingSequenceNo})</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box height="400px" overflow="auto">
|
||||||
|
{getScriptLogs(viewingRevisions[fieldName])}
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel index={2} value={selectedTabs[fieldName]}>
|
||||||
|
<Grid container height="440px" spacing={2}>
|
||||||
|
<Grid item xs={6}>
|
||||||
|
<Box gap={2} pb={1} height="40px" px={2}>
|
||||||
|
<Card sx={{width: "100%", height: "400px"}}>
|
||||||
|
<Box width="100%">
|
||||||
|
<Typography variant="h6" p={2}>Test Input</Typography>
|
||||||
|
<Box px={2} pb={2}>
|
||||||
|
<TextField id="testInput1" label="Ship To Zip" variant="standard" fullWidth sx={{mb: 2}} />
|
||||||
|
<TextField id="testInput1" label="No of Cartons" variant="standard" fullWidth sx={{mb: 2}} />
|
||||||
|
</Box>
|
||||||
|
<div style={{float: "right"}}>
|
||||||
|
<Button>Submit</Button>
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
|
</Card>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={6}>
|
||||||
|
<Box gap={2} pb={1} height="40px">
|
||||||
|
<Card sx={{width: "100%", height: "400px"}}>
|
||||||
|
<Typography variant="h6" pl={3}>Test Output</Typography>
|
||||||
|
</Card>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel index={3} value={selectedTabs[fieldName]}>
|
||||||
|
<Grid container height="440px">
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Box gap={2} pb={1} pl={3}>
|
||||||
|
<Box pb={1}>
|
||||||
|
<Typography variant="h6">Documentation</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{overflow: "auto"}} className="devDocumentation">
|
||||||
|
<Typography variant="body2" sx={{maxWidth: "1200px", margin: "auto"}}>
|
||||||
|
<p>A <b>Deposco Order Optimization Batch Name Script</b> is called when an order is being
|
||||||
|
optimized for shipping within Deposco. It is responsible for determining the order's
|
||||||
|
<b>Batch Name</b> - in other words, an indication of what day the order should be shipped,
|
||||||
|
and whether or not the order is a line haul.</p>
|
||||||
|
|
||||||
|
<p><b>Input</b></p>
|
||||||
|
<p>The input to this type of script is an object named <code>input</code>, with the following fields:</p>
|
||||||
|
<ul>
|
||||||
|
<li><code>warehouseId</code> The id of the warehouse that the order is shipping from. See the <b>Warehouse</b> table for mappings.</li>
|
||||||
|
<li><code>shipToZipCode</code> The zip code that the order is shipping to.</li>
|
||||||
|
<li><code>estimatedNoOfCartons</code> The estimated number of cartons that the order will ship in.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p><b>Output</b></p>
|
||||||
|
<p>The script is responsible only for outputting a single value - a <code>string</code> which will be set as the order's
|
||||||
|
<b>Batch Name</b> in Deposco.</p>
|
||||||
|
|
||||||
|
<p><b>Example</b></p>
|
||||||
|
<code style={{whiteSpace: "pre-wrap"}}>
|
||||||
|
if(today.weekday == 1)
|
||||||
|
(
|
||||||
|
return "TUE-Line-Haul"
|
||||||
|
)
|
||||||
|
</code>
|
||||||
|
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</TabPanel>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
{
|
||||||
|
editingScript &&
|
||||||
|
<Modal open={editingScript as boolean} onClose={(event, reason) => closeEditingScript(event, reason)}>
|
||||||
|
<AssociatedScriptEditor
|
||||||
|
tableName={tableName}
|
||||||
|
primaryKey={id}
|
||||||
|
fieldName={editingScript.fieldName}
|
||||||
|
titlePrefix={editingScript.titlePrefix}
|
||||||
|
recordLabel={record.recordLabel}
|
||||||
|
scriptName={tableMetaData.fields.get(editingScript.fieldName).label}
|
||||||
|
code={editingScript.code}
|
||||||
|
closeCallback={closeEditingScript}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
}
|
||||||
|
|
||||||
|
</MDBox>
|
||||||
|
}
|
||||||
|
</MDBox>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</MDBox>
|
||||||
|
</BaseLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EntityDeveloperView;
|
@ -20,6 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {QException} from "@kingsrook/qqq-frontend-core/lib/exceptions/QException";
|
import {QException} from "@kingsrook/qqq-frontend-core/lib/exceptions/QException";
|
||||||
|
import {Capability} from "@kingsrook/qqq-frontend-core/lib/model/metaData/Capability";
|
||||||
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 {QTableSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableSection";
|
import {QTableSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableSection";
|
||||||
@ -41,8 +42,9 @@ import Menu from "@mui/material/Menu";
|
|||||||
import MenuItem from "@mui/material/MenuItem";
|
import MenuItem from "@mui/material/MenuItem";
|
||||||
import Modal from "@mui/material/Modal";
|
import Modal from "@mui/material/Modal";
|
||||||
import React, {useContext, useEffect, useReducer, useState} from "react";
|
import React, {useContext, useEffect, useReducer, useState} from "react";
|
||||||
import {useLocation, useNavigate, useSearchParams} from "react-router-dom";
|
import {useLocation, useNavigate, useParams, useSearchParams} from "react-router-dom";
|
||||||
import QContext from "QContext";
|
import QContext from "QContext";
|
||||||
|
import BaseLayout from "qqq/components/BaseLayout";
|
||||||
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";
|
||||||
@ -61,18 +63,20 @@ const qController = QClient.getInstance();
|
|||||||
// Declaring props types for ViewForm
|
// Declaring props types for ViewForm
|
||||||
interface Props
|
interface Props
|
||||||
{
|
{
|
||||||
id: string;
|
|
||||||
table?: QTableMetaData;
|
table?: QTableMetaData;
|
||||||
launchProcess?: QProcessMetaData;
|
launchProcess?: QProcessMetaData;
|
||||||
}
|
}
|
||||||
|
|
||||||
ViewContents.defaultProps = {
|
EntityView.defaultProps =
|
||||||
table: null,
|
{
|
||||||
launchProcess: null
|
table: null,
|
||||||
};
|
launchProcess: null
|
||||||
|
};
|
||||||
|
|
||||||
function ViewContents({id, table, launchProcess}: Props): JSX.Element
|
function EntityView({table, launchProcess}: Props): JSX.Element
|
||||||
{
|
{
|
||||||
|
const {id} = useParams();
|
||||||
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
@ -231,6 +235,11 @@ function ViewContents({id, table, launchProcess}: Props): JSX.Element
|
|||||||
for (let i = 0; i < tableSections.length; i++)
|
for (let i = 0; i < tableSections.length; i++)
|
||||||
{
|
{
|
||||||
const section = tableSections[i];
|
const section = tableSections[i];
|
||||||
|
if(section.isHidden)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
sectionFieldElements.set(
|
sectionFieldElements.set(
|
||||||
section.name,
|
section.name,
|
||||||
<MDBox key={section.name} display="flex" flexDirection="column" py={1} pr={2}>
|
<MDBox key={section.name} display="flex" flexDirection="column" py={1} pr={2}>
|
||||||
@ -241,7 +250,7 @@ function ViewContents({id, table, launchProcess}: Props): JSX.Element
|
|||||||
{tableMetaData.fields.get(fieldName).label}:
|
{tableMetaData.fields.get(fieldName).label}:
|
||||||
</MDTypography>
|
</MDTypography>
|
||||||
<MDTypography variant="button" fontWeight="regular" color="text">
|
<MDTypography variant="button" fontWeight="regular" color="text">
|
||||||
{QValueUtils.getDisplayValue(tableMetaData.fields.get(fieldName), record)}
|
{QValueUtils.getDisplayValue(tableMetaData.fields.get(fieldName), record, "view")}
|
||||||
</MDTypography>
|
</MDTypography>
|
||||||
</MDBox>
|
</MDBox>
|
||||||
))
|
))
|
||||||
@ -310,26 +319,37 @@ function ViewContents({id, table, launchProcess}: Props): JSX.Element
|
|||||||
onClose={closeActionsMenu}
|
onClose={closeActionsMenu}
|
||||||
keepMounted
|
keepMounted
|
||||||
>
|
>
|
||||||
<MenuItem onClick={() => navigate("edit")}>
|
|
||||||
<ListItemIcon><Icon>edit</Icon></ListItemIcon>
|
|
||||||
Edit
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem onClick={() =>
|
|
||||||
{
|
{
|
||||||
setActionsMenu(null);
|
table.capabilities.has(Capability.TABLE_UPDATE) &&
|
||||||
handleClickDeleteButton();
|
<MenuItem onClick={() => navigate("edit")}>
|
||||||
}}
|
<ListItemIcon><Icon>edit</Icon></ListItemIcon>
|
||||||
>
|
Edit
|
||||||
<ListItemIcon><Icon>delete</Icon></ListItemIcon>
|
</MenuItem>
|
||||||
Delete
|
}
|
||||||
</MenuItem>
|
{
|
||||||
{tableProcesses.length > 0 && <Divider />}
|
table.capabilities.has(Capability.TABLE_DELETE) &&
|
||||||
|
<MenuItem onClick={() =>
|
||||||
|
{
|
||||||
|
setActionsMenu(null);
|
||||||
|
handleClickDeleteButton();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ListItemIcon><Icon>delete</Icon></ListItemIcon>
|
||||||
|
Delete
|
||||||
|
</MenuItem>
|
||||||
|
}
|
||||||
|
{tableProcesses.length > 0 && (table.capabilities.has(Capability.TABLE_UPDATE) || table.capabilities.has(Capability.TABLE_DELETE)) && <Divider />}
|
||||||
{tableProcesses.map((process) => (
|
{tableProcesses.map((process) => (
|
||||||
<MenuItem key={process.name} onClick={() => processClicked(process)}>
|
<MenuItem key={process.name} onClick={() => processClicked(process)}>
|
||||||
<ListItemIcon><Icon>{process.iconName ?? "arrow_forward"}</Icon></ListItemIcon>
|
<ListItemIcon><Icon>{process.iconName ?? "arrow_forward"}</Icon></ListItemIcon>
|
||||||
{process.label}
|
{process.label}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
|
{tableProcesses.length > 0 && <Divider />}
|
||||||
|
<MenuItem onClick={() => navigate("dev")}>
|
||||||
|
<ListItemIcon><Icon>data_object</Icon></ListItemIcon>
|
||||||
|
Developer Mode
|
||||||
|
</MenuItem>
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -355,111 +375,126 @@ function ViewContents({id, table, launchProcess}: Props): JSX.Element
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
notFoundMessage
|
<BaseLayout>
|
||||||
?
|
<MDBox>
|
||||||
<MDBox>{notFoundMessage}</MDBox>
|
<Grid container>
|
||||||
:
|
<Grid item xs={12}>
|
||||||
<MDBox pb={3}>
|
<MDBox mb={3}>
|
||||||
{
|
{
|
||||||
(searchParams.get("createSuccess") || searchParams.get("updateSuccess")) ? (
|
notFoundMessage
|
||||||
<MDAlert color="success" dismissible>
|
?
|
||||||
{tableMetaData?.label}
|
<MDBox>{notFoundMessage}</MDBox>
|
||||||
{" "}
|
:
|
||||||
successfully
|
<MDBox pb={3}>
|
||||||
{" "}
|
{
|
||||||
{searchParams.get("createSuccess") ? "created" : "updated"}
|
(searchParams.get("createSuccess") || searchParams.get("updateSuccess")) ? (
|
||||||
|
<MDAlert color="success" dismissible>
|
||||||
|
{tableMetaData?.label}
|
||||||
|
{" "}
|
||||||
|
successfully
|
||||||
|
{" "}
|
||||||
|
{searchParams.get("createSuccess") ? "created" : "updated"}
|
||||||
|
|
||||||
</MDAlert>
|
</MDAlert>
|
||||||
) : ("")
|
) : ("")
|
||||||
}
|
}
|
||||||
|
|
||||||
<Grid container spacing={3}>
|
<Grid container spacing={3}>
|
||||||
<Grid item xs={12} lg={3}>
|
<Grid item xs={12} lg={3}>
|
||||||
<QRecordSidebar tableSections={tableSections} widgetMetaDataList={tableWidgets} />
|
<QRecordSidebar tableSections={tableSections} widgetMetaDataList={tableWidgets} />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} lg={9}>
|
<Grid item xs={12} lg={9}>
|
||||||
|
|
||||||
|
<Grid container spacing={3}>
|
||||||
|
<Grid item xs={12} mb={3}>
|
||||||
|
<Card id={t1SectionName}>
|
||||||
|
<MDBox display="flex" p={3} pb={1}>
|
||||||
|
<MDBox mr={1.5}>
|
||||||
|
<Avatar sx={{bgcolor: colors.info.main}}>
|
||||||
|
<Icon>
|
||||||
|
{tableMetaData?.iconName}
|
||||||
|
</Icon>
|
||||||
|
</Avatar>
|
||||||
|
</MDBox>
|
||||||
|
<MDBox display="flex" justifyContent="space-between" width="100%" alignItems="center">
|
||||||
|
<MDTypography variant="h5">
|
||||||
|
{tableMetaData && record ? `Viewing ${tableMetaData?.label}: ${record?.recordLabel}` : ""}
|
||||||
|
</MDTypography>
|
||||||
|
<QActionsMenuButton isOpen={actionsMenu} onClickHandler={openActionsMenu} />
|
||||||
|
{renderActionsMenu}
|
||||||
|
</MDBox>
|
||||||
|
</MDBox>
|
||||||
|
{t1SectionElement ? (<MDBox p={3} pt={0}>{t1SectionElement}</MDBox>) : null}
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
{tableMetaData && tableMetaData.widgets && record && (
|
||||||
|
<DashboardWidgets widgetMetaDataList={tableWidgets} entityPrimaryKey={record.values.get(tableMetaData.primaryKeyField)} />
|
||||||
|
)}
|
||||||
|
{nonT1TableSections.length > 0 ? nonT1TableSections.map(({
|
||||||
|
iconName, label, name, fieldNames, tier,
|
||||||
|
}: any) => (
|
||||||
|
<MDBox mb={3} key={name}>
|
||||||
|
<Card key={name} id={name} sx={{overflow: "visible", scrollMarginTop: "100px"}}>
|
||||||
|
<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}>
|
||||||
|
{
|
||||||
|
table.capabilities.has(Capability.TABLE_DELETE) && <QDeleteButton onClickHandler={handleClickDeleteButton} />
|
||||||
|
}
|
||||||
|
{
|
||||||
|
table.capabilities.has(Capability.TABLE_UPDATE) && <QEditButton />
|
||||||
|
}
|
||||||
|
</Grid>
|
||||||
|
</MDBox>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
{/* Delete confirmation Dialog */}
|
||||||
|
<Dialog
|
||||||
|
open={deleteConfirmationOpen}
|
||||||
|
onClose={handleDeleteConfirmClose}
|
||||||
|
aria-labelledby="alert-dialog-title"
|
||||||
|
aria-describedby="alert-dialog-description"
|
||||||
|
>
|
||||||
|
<DialogTitle id="alert-dialog-title">Confirm Deletion</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText id="alert-dialog-description">
|
||||||
|
Are you sure you want to delete this record?
|
||||||
|
</DialogContentText>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={handleDeleteConfirmClose}>No</Button>
|
||||||
|
<Button onClick={handleDelete} autoFocus>
|
||||||
|
Yes
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
{
|
||||||
|
activeModalProcess &&
|
||||||
|
<Modal open={activeModalProcess !== null} onClose={(event, reason) => closeModalProcess(event, reason)}>
|
||||||
|
<div className="modalProcess">
|
||||||
|
<ProcessRun process={activeModalProcess} isModal={true} recordIds={id} closeModalHandler={closeModalProcess} />
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
}
|
||||||
|
|
||||||
<Grid container spacing={3}>
|
|
||||||
<Grid item xs={12} mb={3}>
|
|
||||||
<Card id={t1SectionName}>
|
|
||||||
<MDBox display="flex" p={3} pb={1}>
|
|
||||||
<MDBox mr={1.5}>
|
|
||||||
<Avatar sx={{bgcolor: colors.info.main}}>
|
|
||||||
<Icon>
|
|
||||||
{tableMetaData?.iconName}
|
|
||||||
</Icon>
|
|
||||||
</Avatar>
|
|
||||||
</MDBox>
|
|
||||||
<MDBox display="flex" justifyContent="space-between" width="100%" alignItems="center">
|
|
||||||
<MDTypography variant="h5">
|
|
||||||
{tableMetaData && record ? `Viewing ${tableMetaData?.label}: ${record?.recordLabel}` : ""}
|
|
||||||
</MDTypography>
|
|
||||||
<QActionsMenuButton isOpen={actionsMenu} onClickHandler={openActionsMenu} />
|
|
||||||
{renderActionsMenu}
|
|
||||||
</MDBox>
|
|
||||||
</MDBox>
|
</MDBox>
|
||||||
{t1SectionElement ? (<MDBox p={3} pt={0}>{t1SectionElement}</MDBox>) : null}
|
}
|
||||||
</Card>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
{tableMetaData && tableMetaData.widgets && record && (
|
|
||||||
<DashboardWidgets widgetMetaDataList={tableWidgets} entityPrimaryKey={record.values.get(tableMetaData.primaryKeyField)} />
|
|
||||||
)}
|
|
||||||
{nonT1TableSections.length > 0 ? nonT1TableSections.map(({
|
|
||||||
iconName, label, name, fieldNames, tier,
|
|
||||||
}: any) => (
|
|
||||||
<MDBox mb={3} key={name}>
|
|
||||||
<Card key={name} id={name} sx={{overflow: "visible", scrollMarginTop: "100px"}}>
|
|
||||||
<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={handleClickDeleteButton} />
|
|
||||||
<QEditButton />
|
|
||||||
</Grid>
|
|
||||||
</MDBox>
|
</MDBox>
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
{/* Delete confirmation Dialog */}
|
|
||||||
<Dialog
|
|
||||||
open={deleteConfirmationOpen}
|
|
||||||
onClose={handleDeleteConfirmClose}
|
|
||||||
aria-labelledby="alert-dialog-title"
|
|
||||||
aria-describedby="alert-dialog-description"
|
|
||||||
>
|
|
||||||
<DialogTitle id="alert-dialog-title">Confirm Deletion</DialogTitle>
|
|
||||||
<DialogContent>
|
|
||||||
<DialogContentText id="alert-dialog-description">
|
|
||||||
Are you sure you want to delete this record?
|
|
||||||
</DialogContentText>
|
|
||||||
</DialogContent>
|
|
||||||
<DialogActions>
|
|
||||||
<Button onClick={handleDeleteConfirmClose}>No</Button>
|
|
||||||
<Button onClick={handleDelete} autoFocus>
|
|
||||||
Yes
|
|
||||||
</Button>
|
|
||||||
</DialogActions>
|
|
||||||
</Dialog>
|
|
||||||
|
|
||||||
{
|
|
||||||
activeModalProcess &&
|
|
||||||
<Modal open={activeModalProcess !== null} onClose={(event, reason) => closeModalProcess(event, reason)}>
|
|
||||||
<div className="modalProcess">
|
|
||||||
<ProcessRun process={activeModalProcess} isModal={true} recordIds={id} closeModalHandler={closeModalProcess} />
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
}
|
|
||||||
|
|
||||||
</MDBox>
|
</MDBox>
|
||||||
|
</BaseLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ViewContents;
|
export default EntityView;
|
@ -1,60 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData";
|
|
||||||
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
|
||||||
import Grid from "@mui/material/Grid";
|
|
||||||
import {useParams} from "react-router-dom";
|
|
||||||
import BaseLayout from "qqq/components/BaseLayout";
|
|
||||||
import MDBox from "qqq/components/Temporary/MDBox";
|
|
||||||
import ViewContents from "./components/ViewContents";
|
|
||||||
|
|
||||||
interface Props
|
|
||||||
{
|
|
||||||
table?: QTableMetaData;
|
|
||||||
launchProcess?: QProcessMetaData;
|
|
||||||
}
|
|
||||||
|
|
||||||
function EntityView({table, launchProcess}: Props): JSX.Element
|
|
||||||
{
|
|
||||||
const {id} = useParams();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BaseLayout>
|
|
||||||
<MDBox>
|
|
||||||
<Grid container>
|
|
||||||
<Grid item xs={12}>
|
|
||||||
<MDBox mb={3}>
|
|
||||||
<ViewContents table={table} id={id} launchProcess={launchProcess}/>
|
|
||||||
</MDBox>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</MDBox>
|
|
||||||
</BaseLayout>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
EntityView.defaultProps = {
|
|
||||||
table: null,
|
|
||||||
launchProcess: null
|
|
||||||
};
|
|
||||||
|
|
||||||
export default EntityView;
|
|
@ -35,6 +35,7 @@ import Tooltip from "@mui/material/Tooltip";
|
|||||||
import React, {useState} from "react";
|
import React, {useState} from "react";
|
||||||
import MDBox from "components/MDBox";
|
import MDBox from "components/MDBox";
|
||||||
import MDTypography from "components/MDTypography";
|
import MDTypography from "components/MDTypography";
|
||||||
|
import CustomWidthTooltip from "qqq/components/CustomWidthTooltip/CustomWidthTooltip";
|
||||||
import {ProcessSummaryLine} from "qqq/pages/process-run/model/ProcessSummaryLine";
|
import {ProcessSummaryLine} from "qqq/pages/process-run/model/ProcessSummaryLine";
|
||||||
import QClient from "qqq/utils/QClient";
|
import QClient from "qqq/utils/QClient";
|
||||||
import QValueUtils from "qqq/utils/QValueUtils";
|
import QValueUtils from "qqq/utils/QValueUtils";
|
||||||
@ -87,15 +88,6 @@ function QValidationReview({
|
|||||||
setPreviewRecordIndex(newIndex);
|
setPreviewRecordIndex(newIndex);
|
||||||
};
|
};
|
||||||
|
|
||||||
const CustomWidthTooltip = styled(({className, ...props}: TooltipProps) => (
|
|
||||||
<Tooltip {...props} classes={{popper: className}} />
|
|
||||||
))({
|
|
||||||
[`& .${tooltipClasses.tooltip}`]: {
|
|
||||||
maxWidth: 500,
|
|
||||||
textAlign: "left",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const buildDoFullValidationRadioListItem = (value: "true" | "false", labelText: string, tooltipHTML: JSX.Element): JSX.Element =>
|
const buildDoFullValidationRadioListItem = (value: "true" | "false", labelText: string, tooltipHTML: JSX.Element): JSX.Element =>
|
||||||
{
|
{
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -261,7 +253,7 @@ function QValidationReview({
|
|||||||
{" "}
|
{" "}
|
||||||
|
|
||||||
{" "}
|
{" "}
|
||||||
{QValueUtils.getDisplayValue(field, previewRecords[previewRecordIndex])}
|
{QValueUtils.getDisplayValue(field, previewRecords[previewRecordIndex], "view")}
|
||||||
</MDBox>
|
</MDBox>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -362,8 +362,11 @@ function ProcessRun({process, defaultProcessValues, isModal, recordIds, closeMod
|
|||||||
{localTableSections.map((section: QTableSection, index: number) =>
|
{localTableSections.map((section: QTableSection, index: number) =>
|
||||||
{
|
{
|
||||||
const name = section.name
|
const name = section.name
|
||||||
console.log(formData);
|
|
||||||
console.log(section.fieldNames);
|
if(section.isHidden)
|
||||||
|
{
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
|
||||||
const sectionFormFields = {};
|
const sectionFormFields = {};
|
||||||
for(let i = 0; i<section.fieldNames.length; i++)
|
for(let i = 0; i<section.fieldNames.length; i++)
|
||||||
@ -424,7 +427,7 @@ function ProcessRun({process, defaultProcessValues, isModal, recordIds, closeMod
|
|||||||
:
|
:
|
||||||
</MDTypography>
|
</MDTypography>
|
||||||
<MDTypography variant="button" fontWeight="regular" color="text">
|
<MDTypography variant="button" fontWeight="regular" color="text">
|
||||||
{QValueUtils.getValueForDisplay(field, processValues[field.name])}
|
{QValueUtils.getValueForDisplay(field, processValues[field.name], "view")}
|
||||||
</MDTypography>
|
</MDTypography>
|
||||||
</MDBox>
|
</MDBox>
|
||||||
))}
|
))}
|
||||||
|
@ -238,3 +238,24 @@ input[type="search"]::-webkit-search-results-decoration { display: none; }
|
|||||||
color: gray;
|
color: gray;
|
||||||
right: 0.125rem;
|
right: 0.125rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.devDocumentation ul>li
|
||||||
|
{
|
||||||
|
margin-left: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.devDocumentation *
|
||||||
|
{
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.devDocumentation p
|
||||||
|
{
|
||||||
|
margin-top: .5rem;
|
||||||
|
margin-bottom: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.devDocumentation code
|
||||||
|
{
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
@ -25,9 +25,9 @@ import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QField
|
|||||||
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
|
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
|
||||||
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
|
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
|
||||||
import "datejs";
|
import "datejs";
|
||||||
import {Chip, Icon} from "@mui/material";
|
import {Chip, Icon, Typography} from "@mui/material";
|
||||||
import {queryByTestId} from "@testing-library/react";
|
|
||||||
import React, {Fragment} from "react";
|
import React, {Fragment} from "react";
|
||||||
|
import AceEditor from "react-ace";
|
||||||
import {Link} from "react-router-dom";
|
import {Link} from "react-router-dom";
|
||||||
import QClient from "qqq/utils/QClient";
|
import QClient from "qqq/utils/QClient";
|
||||||
|
|
||||||
@ -42,9 +42,9 @@ class QValueUtils
|
|||||||
|
|
||||||
private static getQInstance(): QInstance
|
private static getQInstance(): QInstance
|
||||||
{
|
{
|
||||||
if(QValueUtils.qInstance == null)
|
if (QValueUtils.qInstance == null)
|
||||||
{
|
{
|
||||||
if(QValueUtils.loadingQInstance)
|
if (QValueUtils.loadingQInstance)
|
||||||
{
|
{
|
||||||
return (null);
|
return (null);
|
||||||
}
|
}
|
||||||
@ -67,19 +67,19 @@ class QValueUtils
|
|||||||
** When you have a field, and a record - call this method to get a string or
|
** When you have a field, and a record - call this method to get a string or
|
||||||
** element back to display the field's value.
|
** element back to display the field's value.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public static getDisplayValue(field: QFieldMetaData, record: QRecord): string | JSX.Element
|
public static getDisplayValue(field: QFieldMetaData, record: QRecord, usage: "view" | "query" = "view"): string | JSX.Element
|
||||||
{
|
{
|
||||||
const displayValue = record.displayValues ? record.displayValues.get(field.name) : undefined;
|
const displayValue = record.displayValues ? record.displayValues.get(field.name) : undefined;
|
||||||
const rawValue = record.values ? record.values.get(field.name) : undefined;
|
const rawValue = record.values ? record.values.get(field.name) : undefined;
|
||||||
|
|
||||||
return QValueUtils.getValueForDisplay(field, rawValue, displayValue);
|
return QValueUtils.getValueForDisplay(field, rawValue, displayValue, usage);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** When you have a field and a value (either just a raw value, or a raw and
|
** When you have a field and a value (either just a raw value, or a raw and
|
||||||
** display value), call this method to get a string Element to display.
|
** display value), call this method to get a string Element to display.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public static getValueForDisplay(field: QFieldMetaData, rawValue: any, displayValue: any = rawValue): string | JSX.Element
|
public static getValueForDisplay(field: QFieldMetaData, rawValue: any, displayValue: any = rawValue, usage: "view" | "query" = "view"): string | JSX.Element
|
||||||
{
|
{
|
||||||
if (field.hasAdornment(AdornmentType.LINK))
|
if (field.hasAdornment(AdornmentType.LINK))
|
||||||
{
|
{
|
||||||
@ -87,20 +87,20 @@ class QValueUtils
|
|||||||
let href = rawValue;
|
let href = rawValue;
|
||||||
|
|
||||||
const toRecordFromTable = adornment.getValue("toRecordFromTable");
|
const toRecordFromTable = adornment.getValue("toRecordFromTable");
|
||||||
if(toRecordFromTable)
|
if (toRecordFromTable)
|
||||||
{
|
{
|
||||||
if(QValueUtils.getQInstance())
|
if (QValueUtils.getQInstance())
|
||||||
{
|
{
|
||||||
let tablePath = QValueUtils.getQInstance().getTablePathByName(toRecordFromTable);
|
let tablePath = QValueUtils.getQInstance().getTablePathByName(toRecordFromTable);
|
||||||
if(!tablePath)
|
if (!tablePath)
|
||||||
{
|
{
|
||||||
console.log("Couldn't find path for table: " + tablePath);
|
console.log("Couldn't find path for table: " + tablePath);
|
||||||
return ("");
|
return ("");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!tablePath.endsWith("/"))
|
if (!tablePath.endsWith("/"))
|
||||||
{
|
{
|
||||||
tablePath += "/"
|
tablePath += "/";
|
||||||
}
|
}
|
||||||
href = tablePath + rawValue;
|
href = tablePath + rawValue;
|
||||||
}
|
}
|
||||||
@ -113,33 +113,55 @@ class QValueUtils
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!href)
|
if (!href)
|
||||||
{
|
{
|
||||||
return ("");
|
return ("");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(href.startsWith("http"))
|
if (href.startsWith("http"))
|
||||||
{
|
{
|
||||||
return (<a target={adornment.getValue("target") ?? "_self"} href={href} onClick={(e) => e.stopPropagation()}>{displayValue ?? rawValue}</a>)
|
return (<a target={adornment.getValue("target") ?? "_self"} href={href} onClick={(e) => e.stopPropagation()}>{displayValue ?? rawValue}</a>);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return (<Link target={adornment.getValue("target") ?? "_self"} to={href} onClick={(e) => e.stopPropagation()}>{displayValue ?? rawValue}</Link>)
|
return (<Link target={adornment.getValue("target") ?? "_self"} to={href} onClick={(e) => e.stopPropagation()}>{displayValue ?? rawValue}</Link>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field.hasAdornment(AdornmentType.CHIP))
|
if (field.hasAdornment(AdornmentType.CHIP))
|
||||||
{
|
{
|
||||||
if(!displayValue)
|
if (!displayValue)
|
||||||
{
|
{
|
||||||
return (<span/>);
|
return (<span />);
|
||||||
}
|
}
|
||||||
|
|
||||||
const adornment = field.getAdornment(AdornmentType.CHIP);
|
const adornment = field.getAdornment(AdornmentType.CHIP);
|
||||||
const color = adornment.getValue("color." + rawValue) ?? "default"
|
const color = adornment.getValue("color." + rawValue) ?? "default";
|
||||||
const iconName = adornment.getValue("icon." + rawValue) ?? null;
|
const iconName = adornment.getValue("icon." + rawValue) ?? null;
|
||||||
const iconElement = iconName ? <Icon>{iconName}</Icon> : null;
|
const iconElement = iconName ? <Icon>{iconName}</Icon> : null;
|
||||||
return (<Chip label={displayValue} color={color} icon={iconElement} size="small" variant="outlined" sx={{fontWeight: 500}} />);
|
return (<Chip label={displayValue} color={color} icon={iconElement} size="small" variant="outlined" sx={{fontWeight: 500}} />);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (field.hasAdornment(AdornmentType.CODE_EDITOR))
|
||||||
|
{
|
||||||
|
if(usage === "view")
|
||||||
|
{
|
||||||
|
return (<AceEditor
|
||||||
|
mode="javascript"
|
||||||
|
theme="github"
|
||||||
|
name={field.name}
|
||||||
|
editorProps={{$blockScrolling: true}}
|
||||||
|
value={rawValue}
|
||||||
|
readOnly
|
||||||
|
width="100%"
|
||||||
|
showPrintMargin={false}
|
||||||
|
height="200px"
|
||||||
|
/>);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return rawValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (QValueUtils.getUnadornedValueForDisplay(field, rawValue, displayValue));
|
return (QValueUtils.getUnadornedValueForDisplay(field, rawValue, displayValue));
|
||||||
@ -158,8 +180,7 @@ class QValueUtils
|
|||||||
return ("");
|
return ("");
|
||||||
}
|
}
|
||||||
const date = new Date(rawValue);
|
const date = new Date(rawValue);
|
||||||
// @ts-ignore
|
return this.formatDateTime(date);
|
||||||
return (`${date.toString("yyyy-MM-dd hh:mm:ss")} ${date.getHours() < 12 ? "AM" : "PM"} ${date.getTimezone()}`);
|
|
||||||
}
|
}
|
||||||
else if (field.type === QFieldType.DATE)
|
else if (field.type === QFieldType.DATE)
|
||||||
{
|
{
|
||||||
@ -185,6 +206,29 @@ class QValueUtils
|
|||||||
return (returnValue);
|
return (returnValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static formatDateTime(date: Date)
|
||||||
|
{
|
||||||
|
if(!(date instanceof Date))
|
||||||
|
{
|
||||||
|
date = new Date(date)
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
return (`${date.toString("yyyy-MM-dd hh:mm:ss")} ${date.getHours() < 12 ? "AM" : "PM"} ${date.getTimezone()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static formatBoolean(value: any)
|
||||||
|
{
|
||||||
|
if(value === true)
|
||||||
|
{
|
||||||
|
return ("Yes");
|
||||||
|
}
|
||||||
|
else if(value === false)
|
||||||
|
{
|
||||||
|
return ("No");
|
||||||
|
}
|
||||||
|
return (null);
|
||||||
|
}
|
||||||
|
|
||||||
public static getFormattedNumber(n: number): string
|
public static getFormattedNumber(n: number): string
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -206,6 +250,11 @@ class QValueUtils
|
|||||||
|
|
||||||
public static breakTextIntoLines(value: string): JSX.Element
|
public static breakTextIntoLines(value: string): JSX.Element
|
||||||
{
|
{
|
||||||
|
if(!value)
|
||||||
|
{
|
||||||
|
return <Fragment />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
{value.split(/\n/).map((value: string, index: number) => (
|
{value.split(/\n/).map((value: string, index: number) => (
|
||||||
@ -221,28 +270,28 @@ class QValueUtils
|
|||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Take a date-time value, and format it the way the ui's date-times want it
|
** Take a date-time value, and format it the way the ui's date-times want it
|
||||||
** to be.
|
** to be.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public static formatDateTimeValueForForm(value: string): string
|
public static formatDateTimeValueForForm(value: string): string
|
||||||
{
|
{
|
||||||
if(value === null || value === undefined)
|
if (value === null || value === undefined)
|
||||||
{
|
{
|
||||||
return (value);
|
return (value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(value.match(/^\d{4}-\d{2}-\d{2}$/))
|
if (value.match(/^\d{4}-\d{2}-\d{2}$/))
|
||||||
{
|
{
|
||||||
//////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////
|
||||||
// if we just passed in a date (w/o time), attach T00:00 to it. //
|
// if we just passed in a date (w/o time), attach T00:00 to it. //
|
||||||
//////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////
|
||||||
return(value + "T00:00");
|
return (value + "T00:00");
|
||||||
}
|
}
|
||||||
else if(value.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}.*/))
|
else if (value.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}.*/))
|
||||||
{
|
{
|
||||||
///////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
// if we passed in something too long (e.g., w/ seconds and fractions), trim it. //
|
// if we passed in something too long (e.g., w/ seconds and fractions), trim it. //
|
||||||
///////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
return(value.substring(0, 16));
|
return (value.substring(0, 16));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
Reference in New Issue
Block a user