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 (
+
+ {value === index && (
+
+ {children}
+
+ )}
+
+ );
+}
+
+
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 (
-
- {value === index && (
-
- {children}
-
- )}
-
- );
-}
-
-
// 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