diff --git a/package.json b/package.json index 7ff3342..d20632d 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.25", + "@kingsrook/qqq-frontend-core": "1.0.31", "@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", @@ -73,7 +75,7 @@ "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", "prepublishOnly": "tsc -p ./ --outDir lib/", - "start": "react-scripts start", + "start": "BROWSER=none react-scripts start", "test": "react-scripts test", "cypress:open": "cypress open" }, diff --git a/public/integration-logos/deposco.png b/public/integration-logos/deposco.png new file mode 100644 index 0000000..dfacdc6 Binary files /dev/null and b/public/integration-logos/deposco.png differ diff --git a/public/integration-logos/easypost.png b/public/integration-logos/easypost.png new file mode 100644 index 0000000..577a0c3 Binary files /dev/null and b/public/integration-logos/easypost.png differ diff --git a/public/integration-logos/infoplus.png b/public/integration-logos/infoplus.png new file mode 100644 index 0000000..72380d0 Binary files /dev/null and b/public/integration-logos/infoplus.png differ diff --git a/public/integration-logos/shipstation.png b/public/integration-logos/shipstation.png new file mode 100644 index 0000000..7eebd59 Binary files /dev/null and b/public/integration-logos/shipstation.png differ 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/components/EntityForm/index.tsx b/src/qqq/components/EntityForm/index.tsx index 8f6493c..06616e8 100644 --- a/src/qqq/components/EntityForm/index.tsx +++ b/src/qqq/components/EntityForm/index.tsx @@ -19,6 +19,7 @@ * along with this program. If not, see . */ +import {Capability} from "@kingsrook/qqq-frontend-core/lib/model/metaData/Capability"; import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData"; import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType"; 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 [, forceUpdate] = useReducer((x) => x + 1, 0); + const [noCapabilityError, setNoCapabilityError] = useState(null as string); + const {pageHeader, setPageHeader} = useContext(QContext); const navigate = useNavigate(); @@ -140,11 +143,21 @@ function EntityForm({table, id}: Props): JSX.Element }); setFormValues(formValues); + + if(!tableMetaData.capabilities.has(Capability.TABLE_UPDATE)) + { + setNoCapabilityError("You may not edit records in this table"); + } } else { setFormTitle(`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); @@ -168,6 +181,11 @@ function EntityForm({table, id}: Props): JSX.Element const section = tableSections[i]; const sectionDynamicFormFields: any[] = []; + if(section.isHidden) + { + continue; + } + for (let j = 0; j < section.fieldNames.length; 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`; + if(noCapabilityError) + { + return + + + + {noCapabilityError} + + + + ; + } + return ( diff --git a/src/qqq/components/QButtons/index.tsx b/src/qqq/components/QButtons/index.tsx index 489686b..b813ea9 100644 --- a/src/qqq/components/QButtons/index.tsx +++ b/src/qqq/components/QButtons/index.tsx @@ -71,7 +71,7 @@ interface QDeleteButtonProps export function QDeleteButton({onClickHandler}: QDeleteButtonProps): JSX.Element { return ( - + delete}> Delete @@ -82,7 +82,7 @@ export function QDeleteButton({onClickHandler}: QDeleteButtonProps): JSX.Element export function QEditButton(): JSX.Element { return ( - + edit}> Edit diff --git a/src/qqq/components/QRecordSidebar/index.tsx b/src/qqq/components/QRecordSidebar/index.tsx index a15a61f..a404978 100644 --- a/src/qqq/components/QRecordSidebar/index.tsx +++ b/src/qqq/components/QRecordSidebar/index.tsx @@ -59,6 +59,11 @@ function QRecordSidebar({tableSections, widgetMetaDataList, light, stickyTop}: P const sidebarEntries = [] as SidebarEntry[]; tableSections && tableSections.forEach((section, index) => { + if(section.isHidden) + { + return; + } + if (index === 1 && widgetMetaDataList) { widgetMetaDataList.forEach((widget) => diff --git a/src/qqq/pages/app-home/index.tsx b/src/qqq/pages/app-home/index.tsx index fd17fc6..a6f1d75 100644 --- a/src/qqq/pages/app-home/index.tsx +++ b/src/qqq/pages/app-home/index.tsx @@ -18,6 +18,7 @@ * along with this program. If not, see . */ +import {Capability} from "@kingsrook/qqq-frontend-core/lib/model/metaData/Capability"; import {QAppMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppMetaData"; import {QAppNodeType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppNodeType"; 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 {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; 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 Divider from "@mui/material/Divider"; import Grid from "@mui/material/Grid"; @@ -55,6 +56,8 @@ function AppHome({app}: Props): JSX.Element const [reports, setReports] = useState([] as QReportMetaData[]); const [childApps, setChildApps] = useState([] as QAppMetaData[]); const [tableCounts, setTableCounts] = useState(new Map()); + const [tableCountNumbers, setTableCountNumbers] = useState(new Map()); + const [tableCountTexts, setTableCountTexts] = useState(new Map()); const [updatedTableCounts, setUpdatedTableCounts] = useState(new Date()); const [widgets, setWidgets] = useState([] as any[]); @@ -113,15 +116,41 @@ function AppHome({app}: Props): JSX.Element setChildApps(newChildApps); const tableCounts = new Map(); + const tableCountNumbers = new Map(); + const tableCountTexts = new Map(); newTables.forEach((table) => { tableCounts.set(table.name, {isLoading: true, value: null}); setTimeout(async () => { - const count = await qController.count(table.name); - tableCounts.set(table.name, {isLoading: false, value: count}); + const tableMetaData = await qController.loadTableMetaData(table.name); + 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); + setTableCountNumbers(tableCountNumbers); + setTableCountTexts(tableCountTexts); setUpdatedTableCounts(new Date()); }, 1); }); @@ -169,9 +198,17 @@ function AppHome({app}: Props): JSX.Element {app.sections.map((section) => ( - - {section.label} - + + { + section.icon && + ( + section.icon.path && {section.label} + ) + } + + {section.label} + + { section.processes ? ( @@ -261,8 +298,8 @@ function AppHome({app}: Props): JSX.Element {table.iconName || app.iconName}}} direction="right" /> diff --git a/src/qqq/pages/entity-list/index.tsx b/src/qqq/pages/entity-list/EntityList.tsx similarity index 91% rename from src/qqq/pages/entity-list/index.tsx rename to src/qqq/pages/entity-list/EntityList.tsx index 54206bd..0fe2b7c 100644 --- a/src/qqq/pages/entity-list/index.tsx +++ b/src/qqq/pages/entity-list/EntityList.tsx @@ -20,6 +20,7 @@ */ 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 {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData"; 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 [allTableProcesses, setAllTableProcesses] = useState([] as QProcessMetaData[]); const [pageNumber, setPageNumber] = useState(0); - const [totalRecords, setTotalRecords] = useState(0); + const [totalRecords, setTotalRecords] = useState(null); const [selectedIds, setSelectedIds] = useState([] as string[]); const [selectFullFilterState, setSelectFullFilterState] = useState("n/a" as "n/a" | "checked" | "filter"); const [columnsModel, setColumnsModel] = useState([] as GridColDef[]); @@ -384,12 +385,15 @@ function EntityList({table, launchProcess}: Props): JSX.Element setLatestQueryId(thisQueryId); console.log(`Issuing query: ${thisQueryId}`); - qController.count(tableName, qFilter).then((count) => + if (tableMetaData.capabilities.has(Capability.TABLE_COUNT)) { - countResults[thisQueryId] = count; - setCountResults(countResults); - setReceivedCountTimestamp(new Date()); - }); + qController.count(tableName, qFilter).then((count) => + { + countResults[thisQueryId] = count; + setCountResults(countResults); + setReceivedCountTimestamp(new Date()); + }); + } qController.query(tableName, qFilter, rowsPerPage, pageNumber * rowsPerPage).then((results) => { @@ -584,7 +588,7 @@ function EntityList({table, launchProcess}: Props): JSX.Element const row: any = {}; fields.forEach((field) => { - const value = QValueUtils.getDisplayValue(field, record); + const value = QValueUtils.getDisplayValue(field, record, "query"); if (typeof value !== "string") { columnsToRender[field.name] = true; @@ -592,9 +596,26 @@ function EntityList({table, launchProcess}: Props): JSX.Element row[field.name] = value; }); + if(!row["id"]) + { + row["id"] = row[tableMetaData.primaryKeyField]; + } + 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) { setupGridColumns(columnsToRender); @@ -877,21 +898,6 @@ function EntityList({table, launchProcess}: Props): JSX.Element return ""; } - function getRecordIdsForProcess(): string | QQueryFilter - { - if (selectFullFilterState === "filter") - { - return (buildQFilter(filterModel)); - } - - if (selectedIds.length > 0) - { - return (selectedIds.join(",")); - } - - return ""; - } - const openModalProcess = (process: QProcessMetaData = null) => { if (selectFullFilterState === "filter") @@ -979,7 +985,23 @@ function EntityList({table, launchProcess}: Props): JSX.Element // @ts-ignore 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) { @@ -998,7 +1020,9 @@ function EntityList({table, launchProcess}: Props): JSX.Element return ( All - {` ${totalRecords ? totalRecords.toLocaleString() : "All"} `} + {` ${totalRecords ? totalRecords.toLocaleString() : ""} `} records matching this query are selected. + + + { + 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 61% rename from src/qqq/pages/entity-view/components/ViewContents/index.tsx rename to src/qqq/pages/entity-view/EntityView.tsx index abd9e3e..c552cc2 100644 --- a/src/qqq/pages/entity-view/components/ViewContents/index.tsx +++ b/src/qqq/pages/entity-view/EntityView.tsx @@ -20,6 +20,7 @@ */ 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 {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; 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 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 +63,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(); @@ -231,6 +235,11 @@ function ViewContents({id, table, launchProcess}: Props): JSX.Element for (let i = 0; i < tableSections.length; i++) { const section = tableSections[i]; + if(section.isHidden) + { + continue; + } + sectionFieldElements.set( section.name, @@ -241,7 +250,7 @@ function ViewContents({id, table, launchProcess}: Props): JSX.Element {tableMetaData.fields.get(fieldName).label}: - {QValueUtils.getDisplayValue(tableMetaData.fields.get(fieldName), record)} + {QValueUtils.getDisplayValue(tableMetaData.fields.get(fieldName), record, "view")} )) @@ -310,26 +319,37 @@ function ViewContents({id, table, launchProcess}: Props): JSX.Element onClose={closeActionsMenu} keepMounted > - navigate("edit")}> - edit - Edit - - { - setActionsMenu(null); - handleClickDeleteButton(); - }} - > - delete - Delete - - {tableProcesses.length > 0 && } + table.capabilities.has(Capability.TABLE_UPDATE) && + navigate("edit")}> + edit + Edit + + } + { + table.capabilities.has(Capability.TABLE_DELETE) && + + { + setActionsMenu(null); + handleClickDeleteButton(); + }} + > + delete + Delete + + } + {tableProcesses.length > 0 && (table.capabilities.has(Capability.TABLE_UPDATE) || table.capabilities.has(Capability.TABLE_DELETE)) && } {tableProcesses.map((process) => ( processClicked(process)}> {process.iconName ?? "arrow_forward"} {process.label} ))} + {tableProcesses.length > 0 && } + navigate("dev")}> + data_object + Developer Mode + ); @@ -355,111 +375,126 @@ 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} + + + { + table.capabilities.has(Capability.TABLE_DELETE) && + } + { + table.capabilities.has(Capability.TABLE_UPDATE) && + } + + + + + + + {/* 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..d252f2a 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 => { //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -261,7 +253,7 @@ function QValidationReview({ {" "}   {" "} - {QValueUtils.getDisplayValue(field, previewRecords[previewRecordIndex])} + {QValueUtils.getDisplayValue(field, previewRecords[previewRecordIndex], "view")} )) } diff --git a/src/qqq/pages/process-run/index.tsx b/src/qqq/pages/process-run/index.tsx index 601c9ff..4761d55 100644 --- a/src/qqq/pages/process-run/index.tsx +++ b/src/qqq/pages/process-run/index.tsx @@ -362,8 +362,11 @@ function ProcessRun({process, defaultProcessValues, isModal, recordIds, closeMod {localTableSections.map((section: QTableSection, index: number) => { const name = section.name - console.log(formData); - console.log(section.fieldNames); + + if(section.isHidden) + { + return ; + } const sectionFormFields = {}; for(let i = 0; i - {QValueUtils.getValueForDisplay(field, processValues[field.name])} + {QValueUtils.getValueForDisplay(field, processValues[field.name], "view")} ))} 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..038d5f7 100644 --- a/src/qqq/utils/QValueUtils.tsx +++ b/src/qqq/utils/QValueUtils.tsx @@ -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 {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord"; import "datejs"; -import {Chip, Icon} from "@mui/material"; -import {queryByTestId} from "@testing-library/react"; +import {Chip, Icon, Typography} from "@mui/material"; import React, {Fragment} from "react"; +import AceEditor from "react-ace"; import {Link} from "react-router-dom"; import QClient from "qqq/utils/QClient"; @@ -42,9 +42,9 @@ class QValueUtils private static getQInstance(): QInstance { - if(QValueUtils.qInstance == null) + if (QValueUtils.qInstance == null) { - if(QValueUtils.loadingQInstance) + if (QValueUtils.loadingQInstance) { return (null); } @@ -67,19 +67,19 @@ class QValueUtils ** When you have a field, and a record - call this method to get a string or ** 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 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 ** 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)) { @@ -87,20 +87,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 +113,55 @@ 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 (); + } + + if (field.hasAdornment(AdornmentType.CODE_EDITOR)) + { + if(usage === "view") + { + return (); + } + else + { + return rawValue; + } } return (QValueUtils.getUnadornedValueForDisplay(field, rawValue, displayValue)); @@ -158,8 +180,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 +206,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 @@ -206,6 +250,11 @@ class QValueUtils public static breakTextIntoLines(value: string): JSX.Element { + if(!value) + { + return ; + } + return ( {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 - ** 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 {