From de630e2bd695bda207ac40a4f5543a0b91dd5927 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Mon, 14 Nov 2022 15:19:54 -0600 Subject: [PATCH] Add record grid widget; move table widgets down into sections --- package.json | 2 +- src/qqq/components/DashboardWidgets.tsx | 270 ++++++++++-------- src/qqq/components/EntityForm/index.tsx | 5 + .../AssociatedScriptEditor.tsx | 4 +- .../ScriptComponents}/ScriptDocsForm.tsx | 0 .../ScriptComponents}/ScriptLogsView.tsx | 0 .../ScriptComponents}/ScriptTestForm.tsx | 0 src/qqq/components/TabPanel/TabPanel.tsx | 54 ++++ .../dashboards/Widgets/RecordGridWidget.tsx | 122 ++++++++ src/qqq/pages/entity-list/EntityList.tsx | 156 +--------- .../pages/entity-view/EntityDeveloperView.tsx | 115 +------- src/qqq/pages/entity-view/EntityView.tsx | 122 +++++--- src/qqq/utils/DataGridUtils.tsx | 203 +++++++++++++ src/qqq/utils/DeveloperModeUtils.tsx | 52 ++++ 14 files changed, 680 insertions(+), 425 deletions(-) rename src/qqq/{pages/entity-view => components/ScriptComponents}/AssociatedScriptEditor.tsx (97%) rename src/qqq/{pages/entity-view => components/ScriptComponents}/ScriptDocsForm.tsx (100%) rename src/qqq/{pages/entity-view => components/ScriptComponents}/ScriptLogsView.tsx (100%) rename src/qqq/{pages/entity-view => components/ScriptComponents}/ScriptTestForm.tsx (100%) create mode 100644 src/qqq/components/TabPanel/TabPanel.tsx create mode 100644 src/qqq/pages/dashboards/Widgets/RecordGridWidget.tsx create mode 100644 src/qqq/utils/DataGridUtils.tsx create mode 100644 src/qqq/utils/DeveloperModeUtils.tsx diff --git a/package.json b/package.json index fdc88f5..1b761c6 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.34", + "@kingsrook/qqq-frontend-core": "1.0.35", "@mui/icons-material": "5.4.1", "@mui/material": "5.4.1", "@mui/styled-engine": "5.4.1", diff --git a/src/qqq/components/DashboardWidgets.tsx b/src/qqq/components/DashboardWidgets.tsx index af6fe87..73f69a8 100644 --- a/src/qqq/components/DashboardWidgets.tsx +++ b/src/qqq/components/DashboardWidgets.tsx @@ -33,6 +33,7 @@ import BarChart from "qqq/pages/dashboards/Widgets/BarChart"; import LineChart from "qqq/pages/dashboards/Widgets/LineChart"; import MultiStatisticsCard from "qqq/pages/dashboards/Widgets/MultiStatisticsCard"; import QuickSightChart from "qqq/pages/dashboards/Widgets/QuickSightChart"; +import RecordGridWidget from "qqq/pages/dashboards/Widgets/RecordGridWidget"; import StepperCard from "qqq/pages/dashboards/Widgets/StepperCard"; import TableCard from "qqq/pages/dashboards/Widgets/TableCard"; import QClient from "qqq/utils/QClient"; @@ -43,14 +44,16 @@ interface Props { widgetMetaDataList: QWidgetMetaData[]; entityPrimaryKey?: string; + omitWrappingGridContainer: boolean; } DashboardWidgets.defaultProps = { widgetMetaDataList: null, - entityPrimaryKey: null + entityPrimaryKey: null, + omitWrappingGridContainer: false }; -function DashboardWidgets({widgetMetaDataList, entityPrimaryKey}: Props): JSX.Element +function DashboardWidgets({widgetMetaDataList, entityPrimaryKey, omitWrappingGridContainer}: Props): JSX.Element { const location = useLocation(); const [qInstance, setQInstance] = useState(null as QInstance); @@ -105,125 +108,158 @@ function DashboardWidgets({widgetMetaDataList, entityPrimaryKey}: Props): JSX.El // console.log(JSON.stringify(widgetMetaDataList)); // console.log(widgetCount); - return ( - widgetCount > 0 ? ( - + const renderWidget = (widgetMetaData: QWidgetMetaData, i: number): JSX.Element => + { + return ( + <> + { + widgetMetaData.type === "table" && ( + + + + ) + } + { + widgetMetaData.type === "stepper" && ( + + + + { + widgetMetaData.label && ( + + {widgetMetaData.label} + + ) + } + + + + + ) + } + { + widgetMetaData.type === "html" && ( + + + + + {widgetMetaData.label} + + + { + widgetData && widgetData[i] && widgetData[i].html ? ( + parse(widgetData[i].html) + ) : + } + + + + + ) + } + { + widgetMetaData.type === "multiStatistics" && ( + + + + ) + } + { + widgetMetaData.type === "quickSightChart" && ( + + + + ) + } + { + widgetMetaData.type === "barChart" && ( + + + + ) + } + { + widgetMetaData.type === "lineChart" && ( + widgetData && widgetData[i] ? ( + + + + { + widgetData[i].lineChartData.datasets.map((dataSet: any) => ( + + )) + } + + + + )} + chart={widgetData[i].lineChartData as { labels: string[]; datasets: { label: string; color: "primary" | "secondary" | "info" | "success" | "warning" | "error" | "light" | "dark"; data: number[]; }[]; }} + /> + + ) : null + ) + } + { + widgetMetaData.type === "childRecordList" && ( + widgetData && widgetData[i] && + + + + ) + } + + ); + } + + const body: JSX.Element = + ( + <> { widgetMetaDataList.map((widgetMetaData, i) => ( - - { - widgetMetaData.type === "table" && ( - - - - ) - } - { - widgetMetaData.type === "stepper" && ( - - - - { - widgetMetaData.label && ( - - {widgetMetaData.label} - - ) - } - - - - - ) - } - { - widgetMetaData.type === "html" && ( - - - - - {widgetMetaData.label} - - - { - widgetData && widgetData[i] && widgetData[i].html ? ( - parse(widgetData[i].html) - ) : - } - - - - - ) - } - { - widgetMetaData.type === "multiStatistics" && ( - - - - ) - } - { - widgetMetaData.type === "quickSightChart" && ( - - - - ) - } - { - widgetMetaData.type === "barChart" && ( - - - - ) - } - { - widgetMetaData.type === "lineChart" && ( - widgetData && widgetData[i] ? ( - - - - { - widgetData[i].lineChartData.datasets.map((dataSet: any) => ( - - )) - } - - - - )} - chart={widgetData[i].lineChartData as { labels: string[]; datasets: { label: string; color: "primary" | "secondary" | "info" | "success" | "warning" | "error" | "light" | "dark"; data: number[]; }[]; }} - /> - - ) : null - ) - } - + omitWrappingGridContainer + ? renderWidget(widgetMetaData, i) + : + + {renderWidget(widgetMetaData, i)} + )) } - + + ); + + return ( + widgetCount > 0 ? ( + omitWrappingGridContainer ? body : + ( + + {body} + + ) ) : null ); } diff --git a/src/qqq/components/EntityForm/index.tsx b/src/qqq/components/EntityForm/index.tsx index 06616e8..722e81e 100644 --- a/src/qqq/components/EntityForm/index.tsx +++ b/src/qqq/components/EntityForm/index.tsx @@ -186,6 +186,11 @@ function EntityForm({table, id}: Props): JSX.Element continue; } + if(!section.fieldNames) + { + continue; + } + for (let j = 0; j < section.fieldNames.length; j++) { const fieldName = section.fieldNames[j]; diff --git a/src/qqq/pages/entity-view/AssociatedScriptEditor.tsx b/src/qqq/components/ScriptComponents/AssociatedScriptEditor.tsx similarity index 97% rename from src/qqq/pages/entity-view/AssociatedScriptEditor.tsx rename to src/qqq/components/ScriptComponents/AssociatedScriptEditor.tsx index f2c4af4..0183552 100644 --- a/src/qqq/pages/entity-view/AssociatedScriptEditor.tsx +++ b/src/qqq/components/ScriptComponents/AssociatedScriptEditor.tsx @@ -31,8 +31,8 @@ import TextField from "@mui/material/TextField"; import React, {useState} from "react"; import AceEditor from "react-ace"; import {QCancelButton, QSaveButton} from "qqq/components/QButtons"; -import ScriptDocsForm from "qqq/pages/entity-view/ScriptDocsForm"; -import ScriptTestForm from "qqq/pages/entity-view/ScriptTestForm"; +import ScriptDocsForm from "qqq/components/ScriptComponents/ScriptDocsForm"; +import ScriptTestForm from "qqq/components/ScriptComponents/ScriptTestForm"; import QClient from "qqq/utils/QClient"; interface AssociatedScriptDefinition diff --git a/src/qqq/pages/entity-view/ScriptDocsForm.tsx b/src/qqq/components/ScriptComponents/ScriptDocsForm.tsx similarity index 100% rename from src/qqq/pages/entity-view/ScriptDocsForm.tsx rename to src/qqq/components/ScriptComponents/ScriptDocsForm.tsx diff --git a/src/qqq/pages/entity-view/ScriptLogsView.tsx b/src/qqq/components/ScriptComponents/ScriptLogsView.tsx similarity index 100% rename from src/qqq/pages/entity-view/ScriptLogsView.tsx rename to src/qqq/components/ScriptComponents/ScriptLogsView.tsx diff --git a/src/qqq/pages/entity-view/ScriptTestForm.tsx b/src/qqq/components/ScriptComponents/ScriptTestForm.tsx similarity index 100% rename from src/qqq/pages/entity-view/ScriptTestForm.tsx rename to src/qqq/components/ScriptComponents/ScriptTestForm.tsx diff --git a/src/qqq/components/TabPanel/TabPanel.tsx b/src/qqq/components/TabPanel/TabPanel.tsx new file mode 100644 index 0000000..5cfa2b0 --- /dev/null +++ b/src/qqq/components/TabPanel/TabPanel.tsx @@ -0,0 +1,54 @@ +/* + * 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 React from "react"; + +interface TabPanelProps +{ + children?: React.ReactNode; + index: number; + value: number; +} + +export default function TabPanel(props: TabPanelProps) +{ + const {children, value, index, ...other} = props; + + return ( + + ); +} + + diff --git a/src/qqq/pages/dashboards/Widgets/RecordGridWidget.tsx b/src/qqq/pages/dashboards/Widgets/RecordGridWidget.tsx new file mode 100644 index 0000000..1cb9f66 --- /dev/null +++ b/src/qqq/pages/dashboards/Widgets/RecordGridWidget.tsx @@ -0,0 +1,122 @@ +/* + * 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 {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; +import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord"; +import Box from "@mui/material/Box"; +import Card from "@mui/material/Card"; +import Typography from "@mui/material/Typography"; +import {DataGridPro, GridValidRowModel} from "@mui/x-data-grid-pro"; +import React, {useEffect, useState} from "react"; +import {Link} from "react-router-dom"; +import MDTypography from "qqq/components/Temporary/MDTypography"; +import DataGridUtils from "qqq/utils/DataGridUtils"; + +interface Props +{ + title: string + data: any; +} + +RecordGridWidget.defaultProps = { +}; + +function RecordGridWidget({title, data}: Props): JSX.Element +{ + const [rows, setRows] = useState([]); + const [columns, setColumns] = useState([]) + + useEffect(() => + { + if(data && data.childTableMetaData && data.queryOutput) + { + const records: QRecord[] = []; + const queryOutputRecords = data.queryOutput.records; + if (queryOutputRecords) + { + for (let i = 0; i < queryOutputRecords.length; i++) + { + records.push(new QRecord(queryOutputRecords[i])); + } + } + + const tableMetaData = new QTableMetaData(data.childTableMetaData); + const {rows, columnsToRender} = DataGridUtils.makeRows(records, tableMetaData); + const columns = DataGridUtils.setupGridColumns(tableMetaData, columnsToRender, data.tablePath); + + setRows(rows); + setColumns(columns); + } + }, [data]) + + return ( + + + + {title} + + { + data.viewAllLink && + + + View All + + + } + + (params.indexRelativeToCurrentPage % 2 === 0 ? "even" : "odd")} + // getRowHeight={() => "auto"} // maybe nice? wraps values in cells... + // components={{Toolbar: CustomToolbar, Pagination: CustomPagination, LoadingOverlay: Loading}} + // pinnedColumns={pinnedColumns} + // onPinnedColumnsChange={handlePinnedColumnsChange} + // pagination + // paginationMode="server" + // sortingMode="server" + // filterMode="server" + // page={pageNumber} + // checkboxSelection + // rowCount={totalRecords === null ? 0 : totalRecords} + // onPageSizeChange={handleRowsPerPageChange} + // onRowClick={handleRowClick} + // onStateChange={handleStateChange} + // density={density} + // loading={loading} + // filterModel={filterModel} + // onFilterModelChange={handleFilterChange} + // columnVisibilityModel={columnVisibilityModel} + // onColumnVisibilityModelChange={handleColumnVisibilityChange} + // onColumnOrderChange={handleColumnOrderChange} + // onSelectionModelChange={selectionChanged} + // onSortModelChange={handleSortChange} + // sortingOrder={[ "asc", "desc" ]} + // sortModel={columnSortModel} + /> + + ) +} + +export default RecordGridWidget; diff --git a/src/qqq/pages/entity-list/EntityList.tsx b/src/qqq/pages/entity-list/EntityList.tsx index f0624fb..55ed266 100644 --- a/src/qqq/pages/entity-list/EntityList.tsx +++ b/src/qqq/pages/entity-list/EntityList.tsx @@ -57,6 +57,7 @@ import MDAlert from "qqq/components/Temporary/MDAlert"; import MDBox from "qqq/components/Temporary/MDBox"; import {buildQGridPvsOperators, QGridBooleanOperators, QGridNumericOperators, QGridStringOperators} from "qqq/pages/entity-list/QGridFilterOperators"; import ProcessRun from "qqq/pages/process-run"; +import DataGridUtils from "qqq/utils/DataGridUtils"; import QClient from "qqq/utils/QClient"; import QFilterUtils from "qqq/utils/QFilterUtils"; import QProcessUtils from "qqq/utils/QProcessUtils"; @@ -442,120 +443,6 @@ function EntityList({table, launchProcess}: Props): JSX.Element })(); }; - const setupGridColumns = (columnsToRender: any) => - { - const sortedKeys: string[] = []; - - for (let i = 0; i < tableMetaData.sections.length; i++) - { - const section = tableMetaData.sections[i]; - for (let j = 0; j < section.fieldNames.length; j++) - { - sortedKeys.push(section.fieldNames[j]); - } - } - - const columns = [] as GridColDef[]; - sortedKeys.forEach((key) => - { - const field = tableMetaData.fields.get(key); - - let columnType = "string"; - let columnWidth = 200; - let filterOperators: GridFilterOperator[] = QGridStringOperators; - - if (field.possibleValueSourceName) - { - filterOperators = buildQGridPvsOperators(tableName, field); - } - else - { - switch (field.type) - { - case QFieldType.DECIMAL: - case QFieldType.INTEGER: - columnType = "number"; - columnWidth = 100; - - if (key === tableMetaData.primaryKeyField && field.label.length < 3) - { - columnWidth = 75; - } - - filterOperators = QGridNumericOperators; - break; - case QFieldType.DATE: - columnType = "date"; - columnWidth = 100; - filterOperators = getGridDateOperators(); - break; - case QFieldType.DATE_TIME: - columnType = "dateTime"; - columnWidth = 200; - filterOperators = getGridDateOperators(true); - break; - case QFieldType.BOOLEAN: - columnType = "string"; // using boolean gives an odd 'no' for nulls. - columnWidth = 75; - filterOperators = QGridBooleanOperators; - break; - default: - // noop - leave as string - } - } - - if (field.hasAdornment(AdornmentType.SIZE)) - { - const sizeAdornment = field.getAdornment(AdornmentType.SIZE); - const width: string = sizeAdornment.getValue("width"); - const widths: Map = new Map([ - [ "small", 100 ], - [ "medium", 200 ], - [ "large", 400 ], - [ "xlarge", 600 ] - ]); - if (widths.has(width)) - { - columnWidth = widths.get(width); - } - else - { - console.log("Unrecognized size.width adornment value: " + width); - } - } - - const column = { - field: field.name, - type: columnType, - headerName: field.label, - width: columnWidth, - renderCell: null as any, - filterOperators: filterOperators, - }; - - if (columnsToRender[field.name]) - { - column.renderCell = (cellValues: any) => ( - (cellValues.value) - ); - } - - if (key === tableMetaData.primaryKeyField) - { - columns.splice(0, 0, column); - column.renderCell = (cellValues: any) => ( - {cellValues.value} - ); - } - else - { - columns.push(column); - } - }); - - setColumnsModel(columns); - }; - /////////////////////////// // display count results // /////////////////////////// @@ -592,47 +479,16 @@ function EntityList({table, launchProcess}: Props): JSX.Element const results = queryResults[latestQueryId]; delete queryResults[latestQueryId]; - const fields = [ ...tableMetaData.fields.values() ]; - const rows = [] as any[]; - const columnsToRender = {} as any; - results.forEach((record: QRecord) => - { - const row: any = {}; - fields.forEach((field) => - { - const value = QValueUtils.getDisplayValue(field, record, "query"); - if (typeof value !== "string") - { - columnsToRender[field.name] = true; - } - 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; - } - }); + const {rows, columnsToRender} = DataGridUtils.makeRows(results, tableMetaData); if(columnsModel.length == 0) { - setupGridColumns(columnsToRender); + const columns = DataGridUtils.setupGridColumns(tableMetaData, columnsToRender); + setColumnsModel(columns); } + setRows(rows); + setLoading(false); setAlertContent(null); forceUpdate(); diff --git a/src/qqq/pages/entity-view/EntityDeveloperView.tsx b/src/qqq/pages/entity-view/EntityDeveloperView.tsx index b7a2afb..b92a446 100644 --- a/src/qqq/pages/entity-view/EntityDeveloperView.tsx +++ b/src/qqq/pages/entity-view/EntityDeveloperView.tsx @@ -20,7 +20,6 @@ */ import {QException} from "@kingsrook/qqq-frontend-core/lib/exceptions/QException"; -import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData"; 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"; @@ -43,13 +42,15 @@ import {useParams} from "react-router-dom"; import QContext from "QContext"; import BaseLayout from "qqq/components/BaseLayout"; import CustomWidthTooltip from "qqq/components/CustomWidthTooltip/CustomWidthTooltip"; +import AssociatedScriptEditor from "qqq/components/ScriptComponents/AssociatedScriptEditor"; +import ScriptDocsForm from "qqq/components/ScriptComponents/ScriptDocsForm"; +import ScriptLogsView from "qqq/components/ScriptComponents/ScriptLogsView"; +import ScriptTestForm from "qqq/components/ScriptComponents/ScriptTestForm"; +import TabPanel from "qqq/components/TabPanel/TabPanel"; import MDBox from "qqq/components/Temporary/MDBox"; -import AssociatedScriptEditor from "qqq/pages/entity-view/AssociatedScriptEditor"; -import ScriptLogsView from "qqq/pages/entity-view/ScriptLogsView"; -import ScriptTestForm from "qqq/pages/entity-view/ScriptTestForm"; +import DeveloperModeUtils from "qqq/utils/DeveloperModeUtils"; import QClient from "qqq/utils/QClient"; import QValueUtils from "qqq/utils/QValueUtils"; -import ScriptDocsForm from "./ScriptDocsForm"; import "ace-builds/src-noconflict/mode-java"; import "ace-builds/src-noconflict/mode-javascript"; @@ -59,35 +60,6 @@ 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 { @@ -115,8 +87,6 @@ function EntityDeveloperView({table}: Props): JSX.Element const [selectedTabs, setSelectedTabs] = useState({} as any); const [viewingRevisions, setViewingRevisions] = useState({} as any); const [scriptLogs, setScriptLogs] = useState({} as any); - const [testInputValues, setTestInputValues] = useState({} as any); - const [testOutputValues, setTestOutputValues] = useState({} as any); const [editingScript, setEditingScript] = useState(null as any); const [alertText, setAlertText] = useState(null as string); @@ -155,23 +125,6 @@ function EntityDeveloperView({table}: Props): JSX.Element setAssociatedScripts(developerModeData.associatedScripts); - const testInputValues = {}; - const testOutputValues = {}; - console.log("@dk - here"); - console.log(developerModeData.associatedScripts); - developerModeData.associatedScripts.forEach((object: any) => - { - const fieldName = object.associatedScript.fieldName; - - // @ts-ignore - testInputValues[fieldName] = {}; - - // @ts-ignore - testOutputValues[fieldName] = {}; - }); - setTestInputValues(testInputValues); - setTestOutputValues(testOutputValues); - const recordJSONObject = {} as any; for (let key of record.values.keys()) { @@ -197,32 +150,6 @@ function EntityDeveloperView({table}: Props): JSX.Element })(); } - 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, object: any) => { const editingScript = {} as any; @@ -233,34 +160,6 @@ function EntityDeveloperView({table}: Props): JSX.Element setEditingScript(editingScript); }; - const testScript = (object: any, fieldName: string) => - { - const viewingRevisionArray = object.scriptRevisions?.filter((rev: any) => rev?.values?.id === viewingRevisions[fieldName]); - const code = viewingRevisionArray?.length > 0 ? viewingRevisionArray[0].values.contents : ""; - - const inputValues = new Map(); - if (object.testInputFields) - { - object.testInputFields.forEach((field: QFieldMetaData) => - { - console.log(`${field.name} = ${testInputValues[fieldName][field.name]}`) - inputValues.set(field.name, testInputValues[fieldName][field.name]); - }); - } - - const newTestOutputValues = JSON.parse(JSON.stringify(testOutputValues)); - newTestOutputValues[fieldName] = {}; - setTestOutputValues(newTestOutputValues); - - (async () => - { - const output = await qController.testScript(tableName, id, fieldName, code, inputValues); - const newTestOutputValues = JSON.parse(JSON.stringify(testOutputValues)); - newTestOutputValues[fieldName] = output.outputValues; - setTestOutputValues(newTestOutputValues); - })(); - }; - const closeEditingScript = (event: object, reason: string, alert: string = null) => { if (reason === "backdropClick") @@ -336,7 +235,7 @@ function EntityDeveloperView({table}: Props): JSX.Element selectRevision(fieldName, revision.values.id)}> - {`${revision.values.sequenceNo}`} + {`${revision.values.sequenceNo}`} - { - const widget = metaData.widgets.get(widgetName); - matchingWidgets.push(widget); - }); - setTableWidgets(matchingWidgets); - ///////////////////////////////////////////////// // define the sections, e.g., for the left-bar // ///////////////////////////////////////////////// @@ -235,28 +222,75 @@ function EntityView({table, launchProcess}: Props): JSX.Element for (let i = 0; i < tableSections.length; i++) { const section = tableSections[i]; - if(section.isHidden) + if (section.isHidden) { continue; } - sectionFieldElements.set( - section.name, - - { - section.fieldNames.map((fieldName: string) => ( - - - {tableMetaData.fields.get(fieldName).label}: - - - {QValueUtils.getDisplayValue(tableMetaData.fields.get(fieldName), record, "view")} - + if (section.widgetName) + { + const widgetMetaData = metaData.widgets.get(section.widgetName); + + //////////////////////////////////////////////////////////////////////////// + // for a section with a widget name, call the dashboard widgets component // + //////////////////////////////////////////////////////////////////////////// + sectionFieldElements.set(section.name, + + + + + + ); + } + else if (section.fieldNames) + { + //////////////////////////////////////////////////////////////////////////////////////////////////////////// + // for a section with field names, render the field values. // + // for the T1 section, the "wrapper" will come out below - but for other sections, produce a wrapper too. // + //////////////////////////////////////////////////////////////////////////////////////////////////////////// + const fields = ( + + { + section.fieldNames.map((fieldName: string) => ( + + + {tableMetaData.fields.get(fieldName).label}: + + + {QValueUtils.getDisplayValue(tableMetaData.fields.get(fieldName), record, "view")} + + + )) + } + + ); + + if (section.tier === "T1") + { + sectionFieldElements.set(section.name, fields); + } + else + { + sectionFieldElements.set(section.name, + + + + + {section.label} + + + {fields} + + - )) - } - , - ); + + ); + } + } + else + { + continue; + } if (section.tier === "T1") { @@ -401,7 +435,7 @@ function EntityView({table, launchProcess}: Props): JSX.Element - + @@ -428,21 +462,15 @@ function EntityView({table, launchProcess}: Props): JSX.Element - {tableMetaData && tableMetaData.widgets && record && ( - - )} - {nonT1TableSections.length > 0 ? nonT1TableSections.map(({ - iconName, label, name, fieldNames, tier, - }: any) => ( - - - - {label} - - {sectionFieldElements.get(name)} - - - )) : null} + + {nonT1TableSections.length > 0 ? nonT1TableSections.map(({ + iconName, label, name, fieldNames, tier, + }: any) => ( + <> + {sectionFieldElements.get(name)} + + )) : null} + { diff --git a/src/qqq/utils/DataGridUtils.tsx b/src/qqq/utils/DataGridUtils.tsx new file mode 100644 index 0000000..7b83ee8 --- /dev/null +++ b/src/qqq/utils/DataGridUtils.tsx @@ -0,0 +1,203 @@ +/* + * 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 {AdornmentType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/AdornmentType"; +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"; +import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord"; +import {getGridDateOperators, GridColDef, GridRowsProp} from "@mui/x-data-grid-pro"; +import {GridFilterOperator} from "@mui/x-data-grid/models/gridFilterOperator"; +import {Link} from "react-router-dom"; +import {buildQGridPvsOperators, QGridBooleanOperators, QGridNumericOperators, QGridStringOperators} from "qqq/pages/entity-list/QGridFilterOperators"; +import QValueUtils from "qqq/utils/QValueUtils"; + +export default class DataGridUtils +{ + + /******************************************************************************* + ** + *******************************************************************************/ + public static makeRows = (results: QRecord[], tableMetaData: QTableMetaData): {rows: GridRowsProp[], columnsToRender: any} => + { + const fields = [ ...tableMetaData.fields.values() ]; + const rows = [] as any[]; + const columnsToRender = {} as any; + results.forEach((record: QRecord) => + { + const row: any = {}; + fields.forEach((field) => + { + const value = QValueUtils.getDisplayValue(field, record, "query"); + if (typeof value !== "string") + { + columnsToRender[field.name] = true; + } + 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; + } + }); + + return ({rows, columnsToRender}); + } + + /******************************************************************************* + ** + *******************************************************************************/ + public static setupGridColumns = (tableMetaData: QTableMetaData, columnsToRender: any, linkBase: string = ""): GridColDef[] => + { + const columns = [] as GridColDef[]; + const sortedKeys: string[] = []; + + for (let i = 0; i < tableMetaData.sections.length; i++) + { + const section = tableMetaData.sections[i]; + if(!section.fieldNames) + { + continue; + } + + for (let j = 0; j < section.fieldNames.length; j++) + { + sortedKeys.push(section.fieldNames[j]); + } + } + + sortedKeys.forEach((key) => + { + const field = tableMetaData.fields.get(key); + + let columnType = "string"; + let columnWidth = 200; + let filterOperators: GridFilterOperator[] = QGridStringOperators; + + if (field.possibleValueSourceName) + { + filterOperators = buildQGridPvsOperators(tableMetaData.name, field); + } + else + { + switch (field.type) + { + case QFieldType.DECIMAL: + case QFieldType.INTEGER: + columnType = "number"; + columnWidth = 100; + + if (key === tableMetaData.primaryKeyField && field.label.length < 3) + { + columnWidth = 75; + } + + filterOperators = QGridNumericOperators; + break; + case QFieldType.DATE: + columnType = "date"; + columnWidth = 100; + filterOperators = getGridDateOperators(); + break; + case QFieldType.DATE_TIME: + columnType = "dateTime"; + columnWidth = 200; + filterOperators = getGridDateOperators(true); + break; + case QFieldType.BOOLEAN: + columnType = "string"; // using boolean gives an odd 'no' for nulls. + columnWidth = 75; + filterOperators = QGridBooleanOperators; + break; + default: + // noop - leave as string + } + } + + if (field.hasAdornment(AdornmentType.SIZE)) + { + const sizeAdornment = field.getAdornment(AdornmentType.SIZE); + const width: string = sizeAdornment.getValue("width"); + const widths: Map = new Map([ + ["small", 100], + ["medium", 200], + ["large", 400], + ["xlarge", 600] + ]); + if (widths.has(width)) + { + columnWidth = widths.get(width); + } + else + { + console.log("Unrecognized size.width adornment value: " + width); + } + } + + const column = { + field: field.name, + type: columnType, + headerName: field.label, + width: columnWidth, + renderCell: null as any, + filterOperators: filterOperators, + }; + + if (columnsToRender[field.name]) + { + column.renderCell = (cellValues: any) => ( + (cellValues.value) + ); + } + + if (key === tableMetaData.primaryKeyField) + { + columns.splice(0, 0, column); + column.renderCell = (cellValues: any) => ( + {cellValues.value} + ); + } + else + { + columns.push(column); + } + }); + + return (columns); + }; + +} + diff --git a/src/qqq/utils/DeveloperModeUtils.tsx b/src/qqq/utils/DeveloperModeUtils.tsx new file mode 100644 index 0000000..e8f25b8 --- /dev/null +++ b/src/qqq/utils/DeveloperModeUtils.tsx @@ -0,0 +1,52 @@ +/* + * 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 . + */ + + +export default class DeveloperModeUtils +{ + + public static revToColor = (fieldName: string, recordId: string, rev: number): string => + { + let hash = 0; + let idFactor = 1; + try + { + idFactor = Number(recordId); + } + 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; + }; + +} \ No newline at end of file