From 8ed8640b5a39704a0d092aa86768665203166a1b Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Mon, 31 Oct 2022 15:54:17 -0500 Subject: [PATCH] Add initial version of record developer mode, associated scripts --- package.json | 4 +- src/App.tsx | 10 +- .../CustomWidthTooltip/CustomWidthTooltip.tsx | 37 + .../entity-view/AssociatedScriptEditor.tsx | 117 ++++ .../pages/entity-view/EntityDeveloperView.tsx | 654 ++++++++++++++++++ .../ViewContents/index.tsx => EntityView.tsx} | 227 +++--- src/qqq/pages/entity-view/index.tsx | 60 -- .../components/QValidationReview.tsx | 10 +- src/qqq/styles/qqq-override-styles.css | 21 + src/qqq/utils/QValueUtils.tsx | 69 +- 10 files changed, 1010 insertions(+), 199 deletions(-) create mode 100644 src/qqq/components/CustomWidthTooltip/CustomWidthTooltip.tsx create mode 100644 src/qqq/pages/entity-view/AssociatedScriptEditor.tsx create mode 100644 src/qqq/pages/entity-view/EntityDeveloperView.tsx rename src/qqq/pages/entity-view/{components/ViewContents/index.tsx => EntityView.tsx} (65%) delete mode 100644 src/qqq/pages/entity-view/index.tsx diff --git a/package.json b/package.json index d083061..f884563 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "@fullcalendar/interaction": "5.10.0", "@fullcalendar/react": "5.10.0", "@fullcalendar/timegrid": "5.10.0", - "@kingsrook/qqq-frontend-core": "1.0.27", + "@kingsrook/qqq-frontend-core": "1.0.28", "@mui/icons-material": "5.4.1", "@mui/material": "5.4.1", "@mui/styled-engine": "5.4.1", @@ -34,6 +34,7 @@ "@types/react": "17.0.38", "@types/react-dom": "17.0.11", "@types/react-router-hash-link": "2.4.5", + "ace-builds": "1.12.3", "chart.js": "3.4.1", "chroma-js": "2.4.2", "datejs": "1.0.0-rc3", @@ -45,6 +46,7 @@ "html-react-parser": "1.4.8", "http-proxy-middleware": "2.0.6", "react": "17.0.2", + "react-ace": "10.1.0", "react-chartjs-2": "3.0.4", "react-cookie": "4.1.1", "react-dom": "17.0.2", diff --git a/src/App.tsx b/src/App.tsx index c9ccdc5..4efe39a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -48,7 +48,8 @@ import Overview from "qqq/pages/dashboards/Overview"; import EntityCreate from "qqq/pages/entity-create"; import EntityEdit from "qqq/pages/entity-edit"; 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 ReportRun from "qqq/pages/process-run/ReportRun"; import QClient from "qqq/utils/QClient"; @@ -262,6 +263,13 @@ export default function App() component: , }); + routeList.push({ + name: `${app.label}`, + key: `${app.name}.dev`, + route: `${path}/:id/dev`, + component: , + }); + const processesForTable = QProcessUtils.getProcessesForTable(metaData, table.name, true); processesForTable.forEach((process) => { diff --git a/src/qqq/components/CustomWidthTooltip/CustomWidthTooltip.tsx b/src/qqq/components/CustomWidthTooltip/CustomWidthTooltip.tsx new file mode 100644 index 0000000..fe1700b --- /dev/null +++ b/src/qqq/components/CustomWidthTooltip/CustomWidthTooltip.tsx @@ -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 . + */ + +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) => ( + +))({ + [`& .${tooltipClasses.tooltip}`]: { + maxWidth: 500, + textAlign: "left", + }, +}); + + +export default CustomWidthTooltip \ No newline at end of file diff --git a/src/qqq/pages/entity-view/AssociatedScriptEditor.tsx b/src/qqq/pages/entity-view/AssociatedScriptEditor.tsx new file mode 100644 index 0000000..21afce5 --- /dev/null +++ b/src/qqq/pages/entity-view/AssociatedScriptEditor.tsx @@ -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 . + */ + + +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) => + { + setCommitMessage(event.target.value); + } + + return ( + + + + {`${titlePrefix}: ${recordLabel} - ${scriptName}`} + + + + + + + + + + + + + + + + + + ); +} + +export default AssociatedScriptEditor; diff --git a/src/qqq/pages/entity-view/EntityDeveloperView.tsx b/src/qqq/pages/entity-view/EntityDeveloperView.tsx new file mode 100644 index 0000000..5e9126e --- /dev/null +++ b/src/qqq/pages/entity-view/EntityDeveloperView.tsx @@ -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 . + */ + +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 ( + + ); +} + + +// 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 + { + scriptRevisions ? <> : + + There are not any versions of this script. + + } + { + scriptRevisions?.map((revision: any) => ( + + selectRevision(fieldName, revision.values.id)}> + + {`${revision.values.sequenceNo}`} + + + {revision.values.id == currentScriptRevisionId && } + {revision.values.commitMessage} + + } + secondary={ + <> + {QValueUtils.formatDateTime(revision.values.createDate)} +
+ {revision.values.author} + + } + /> + settings +
+ +
+ )) + } +
; + } + + function getScriptLogs(revisionId: number) + { + const logs = scriptLogs[revisionId] as any[]; + if (logs === null || logs === undefined) + { + return Loading...; + } + + if (logs.length === 0) + { + return No logs available for this version.; + } + + return ( + + + + + Timestamp + Run Time (ms) + Had Error? + Input + Output + Logs + + + + { + 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 ( + + {QValueUtils.formatDateTime(logRecord.values.startTimestamp)} + {logRecord.values.runTimeMillis?.toLocaleString()} + +
{QValueUtils.formatBoolean(logRecord.values.hadError)}
+
+ {logRecord.values.input} + + {logRecord.values.output} + {logRecord.values.error} + + {logs} +
+ ); + }) + } +
+
+
+ ); + } + + return ( + + + + + + { + notFoundMessage + ? + {notFoundMessage} + : + + { + alertText ? ( + setAlertText(null)} anchorOrigin={{vertical: "top", horizontal: "center"}}> + setAlertText(null)}> + {alertText} + + + ) : ("") + } + + + + + Record Raw Values as JSON + + + + { + 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 ( + + + + {field?.label} + changeTab(newValue, fieldName)} + variant="standard" + > + + + + + + + + + + + + Versions + + {getRevisionsList(object.scriptRevisions, fieldName, currentScriptRevisionId)} + + + + + { + currentScriptRevisionId && + + { + currentScriptRevisionId === viewingRevisions[fieldName] + ? (<>Current Version ({viewingSequenceNo})) + : (<>Version {viewingSequenceNo}) + } + + } + + + + + { + code ? ( + <> + + + ) : null + } + + + + + + + + Versions + + {getRevisionsList(object.scriptRevisions, fieldName, currentScriptRevisionId)} + + + + Script Logs (Version {viewingSequenceNo}) + + + {getScriptLogs(viewingRevisions[fieldName])} + + + + + + + + + + + Test Input + + + + +
+ +
+
+
+
+
+ + + + Test Output + + + +
+
+ + + + + + Documentation + + + +

A Deposco Order Optimization Batch Name Script is called when an order is being + optimized for shipping within Deposco. It is responsible for determining the order's  + Batch Name - in other words, an indication of what day the order should be shipped, + and whether or not the order is a line haul.

+ +

Input

+

The input to this type of script is an object named input, with the following fields:

+
    +
  • warehouseId The id of the warehouse that the order is shipping from. See the Warehouse table for mappings.
  • +
  • shipToZipCode The zip code that the order is shipping to.
  • +
  • estimatedNoOfCartons The estimated number of cartons that the order will ship in.
  • +
+ +

Output

+

The script is responsible only for outputting a single value - a string which will be set as the order's  + Batch Name in Deposco.

+ +

Example

+ + if(today.weekday == 1) + ( + return "TUE-Line-Haul" + ) + + +
+
+
+
+
+
+
+ ); + }) + } + +
+
+ + { + editingScript && + closeEditingScript(event, reason)}> + + + } + +
+ } +
+
+
+
+
+ ); +} + +export default EntityDeveloperView; diff --git a/src/qqq/pages/entity-view/components/ViewContents/index.tsx b/src/qqq/pages/entity-view/EntityView.tsx similarity index 65% rename from src/qqq/pages/entity-view/components/ViewContents/index.tsx rename to src/qqq/pages/entity-view/EntityView.tsx index abd9e3e..c182d8d 100644 --- a/src/qqq/pages/entity-view/components/ViewContents/index.tsx +++ b/src/qqq/pages/entity-view/EntityView.tsx @@ -41,8 +41,9 @@ import Menu from "@mui/material/Menu"; import MenuItem from "@mui/material/MenuItem"; import Modal from "@mui/material/Modal"; 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 BaseLayout from "qqq/components/BaseLayout"; import DashboardWidgets from "qqq/components/DashboardWidgets"; import {QActionsMenuButton, QDeleteButton, QEditButton} from "qqq/components/QButtons"; import QRecordSidebar from "qqq/components/QRecordSidebar"; @@ -61,18 +62,20 @@ const qController = QClient.getInstance(); // Declaring props types for ViewForm interface Props { - id: string; table?: QTableMetaData; launchProcess?: QProcessMetaData; } -ViewContents.defaultProps = { - table: null, - launchProcess: null -}; +EntityView.defaultProps = + { + 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 navigate = useNavigate(); @@ -330,6 +333,11 @@ function ViewContents({id, table, launchProcess}: Props): JSX.Element {process.label} ))} + + navigate("dev")}> + data_object + Developer Mode + ); @@ -355,111 +363,122 @@ function ViewContents({id, table, launchProcess}: Props): JSX.Element }; return ( - notFoundMessage - ? - {notFoundMessage} - : - - { - (searchParams.get("createSuccess") || searchParams.get("updateSuccess")) ? ( - - {tableMetaData?.label} - {" "} - successfully - {" "} - {searchParams.get("createSuccess") ? "created" : "updated"} + + + + + + { + notFoundMessage + ? + {notFoundMessage} + : + + { + (searchParams.get("createSuccess") || searchParams.get("updateSuccess")) ? ( + + {tableMetaData?.label} + {" "} + successfully + {" "} + {searchParams.get("createSuccess") ? "created" : "updated"} - - ) : ("") - } + + ) : ("") + } - - - - - + + + + + + + + + + + + + + {tableMetaData?.iconName} + + + + + + {tableMetaData && record ? `Viewing ${tableMetaData?.label}: ${record?.recordLabel}` : ""} + + + {renderActionsMenu} + + + {t1SectionElement ? ({t1SectionElement}) : null} + + + + {tableMetaData && tableMetaData.widgets && record && ( + + )} + {nonT1TableSections.length > 0 ? nonT1TableSections.map(({ + iconName, label, name, fieldNames, tier, + }: any) => ( + + + + {label} + + {sectionFieldElements.get(name)} + + + )) : null} + + + + + + + + + + + {/* Delete confirmation Dialog */} + + Confirm Deletion + + + Are you sure you want to delete this record? + + + + + + + + + { + activeModalProcess && + closeModalProcess(event, reason)}> +
+ +
+
+ } - - - - - - - - {tableMetaData?.iconName} - - - - - - {tableMetaData && record ? `Viewing ${tableMetaData?.label}: ${record?.recordLabel}` : ""} - - - {renderActionsMenu} - - {t1SectionElement ? ({t1SectionElement}) : null} - - - - {tableMetaData && tableMetaData.widgets && record && ( - - )} - {nonT1TableSections.length > 0 ? nonT1TableSections.map(({ - iconName, label, name, fieldNames, tier, - }: any) => ( - - - - {label} - - {sectionFieldElements.get(name)} - - - )) : null} - - - - - + } -
- - {/* Delete confirmation Dialog */} - - Confirm Deletion - - - Are you sure you want to delete this record? - - - - - - - - - { - activeModalProcess && - closeModalProcess(event, reason)}> -
- -
-
- } -
- + ); } -export default ViewContents; +export default EntityView; diff --git a/src/qqq/pages/entity-view/index.tsx b/src/qqq/pages/entity-view/index.tsx deleted file mode 100644 index 14e88ef..0000000 --- a/src/qqq/pages/entity-view/index.tsx +++ /dev/null @@ -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 . - */ - -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 ( - - - - - - - - - - - - ); -} - -EntityView.defaultProps = { - table: null, - launchProcess: null -}; - -export default EntityView; diff --git a/src/qqq/pages/process-run/components/QValidationReview.tsx b/src/qqq/pages/process-run/components/QValidationReview.tsx index 94ecce0..8932061 100644 --- a/src/qqq/pages/process-run/components/QValidationReview.tsx +++ b/src/qqq/pages/process-run/components/QValidationReview.tsx @@ -35,6 +35,7 @@ import Tooltip from "@mui/material/Tooltip"; import React, {useState} from "react"; import MDBox from "components/MDBox"; import MDTypography from "components/MDTypography"; +import CustomWidthTooltip from "qqq/components/CustomWidthTooltip/CustomWidthTooltip"; import {ProcessSummaryLine} from "qqq/pages/process-run/model/ProcessSummaryLine"; import QClient from "qqq/utils/QClient"; import QValueUtils from "qqq/utils/QValueUtils"; @@ -87,15 +88,6 @@ function QValidationReview({ setPreviewRecordIndex(newIndex); }; - const CustomWidthTooltip = styled(({className, ...props}: TooltipProps) => ( - - ))({ - [`& .${tooltipClasses.tooltip}`]: { - maxWidth: 500, - textAlign: "left", - }, - }); - const buildDoFullValidationRadioListItem = (value: "true" | "false", labelText: string, tooltipHTML: JSX.Element): JSX.Element => { //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/qqq/styles/qqq-override-styles.css b/src/qqq/styles/qqq-override-styles.css index f8a3c2d..5b85615 100644 --- a/src/qqq/styles/qqq-override-styles.css +++ b/src/qqq/styles/qqq-override-styles.css @@ -238,3 +238,24 @@ input[type="search"]::-webkit-search-results-decoration { display: none; } color: gray; 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; +} diff --git a/src/qqq/utils/QValueUtils.tsx b/src/qqq/utils/QValueUtils.tsx index 338faef..065d134 100644 --- a/src/qqq/utils/QValueUtils.tsx +++ b/src/qqq/utils/QValueUtils.tsx @@ -26,7 +26,6 @@ import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstan import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord"; import "datejs"; import {Chip, Icon} from "@mui/material"; -import {queryByTestId} from "@testing-library/react"; import React, {Fragment} from "react"; import {Link} from "react-router-dom"; import QClient from "qqq/utils/QClient"; @@ -42,9 +41,9 @@ class QValueUtils private static getQInstance(): QInstance { - if(QValueUtils.qInstance == null) + if (QValueUtils.qInstance == null) { - if(QValueUtils.loadingQInstance) + if (QValueUtils.loadingQInstance) { return (null); } @@ -87,20 +86,20 @@ class QValueUtils let href = rawValue; const toRecordFromTable = adornment.getValue("toRecordFromTable"); - if(toRecordFromTable) + if (toRecordFromTable) { - if(QValueUtils.getQInstance()) + if (QValueUtils.getQInstance()) { let tablePath = QValueUtils.getQInstance().getTablePathByName(toRecordFromTable); - if(!tablePath) + if (!tablePath) { console.log("Couldn't find path for table: " + tablePath); return (""); } - if(!tablePath.endsWith("/")) + if (!tablePath.endsWith("/")) { - tablePath += "/" + tablePath += "/"; } href = tablePath + rawValue; } @@ -113,33 +112,33 @@ class QValueUtils } } - if(!href) + if (!href) { return (""); } - if(href.startsWith("http")) + if (href.startsWith("http")) { - return ( e.stopPropagation()}>{displayValue ?? rawValue}) + return ( e.stopPropagation()}>{displayValue ?? rawValue}); } else { - return ( e.stopPropagation()}>{displayValue ?? rawValue}) + return ( e.stopPropagation()}>{displayValue ?? rawValue}); } } if (field.hasAdornment(AdornmentType.CHIP)) { - if(!displayValue) + if (!displayValue) { - return (); + return (); } 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 iconElement = iconName ? {iconName} : null; - return (); + return (); } return (QValueUtils.getUnadornedValueForDisplay(field, rawValue, displayValue)); @@ -158,8 +157,7 @@ class QValueUtils return (""); } const date = new Date(rawValue); - // @ts-ignore - return (`${date.toString("yyyy-MM-dd hh:mm:ss")} ${date.getHours() < 12 ? "AM" : "PM"} ${date.getTimezone()}`); + return this.formatDateTime(date); } else if (field.type === QFieldType.DATE) { @@ -185,6 +183,29 @@ class QValueUtils 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 { try @@ -221,28 +242,28 @@ class QValueUtils /******************************************************************************* ** 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 { - if(value === null || value === undefined) + if (value === null || value === undefined) { 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. // ////////////////////////////////////////////////////////////////// - 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. // /////////////////////////////////////////////////////////////////////////////////// - return(value.substring(0, 16)); + return (value.substring(0, 16)); } else {