Merged dev into feature/bulk-upload-v2

This commit is contained in:
2024-12-03 10:02:16 -06:00
16 changed files with 20312 additions and 4946 deletions

View File

@ -602,7 +602,7 @@ function EntityForm(props: Props): JSX.Element
///////////////////////////////////////////////////////////////////////////////////////////
if (fieldMetaData.possibleValueSourceName)
{
const results: QPossibleValue[] = await qController.possibleValues(tableName, null, fieldName, null, [initialValues[fieldName]]);
const results: QPossibleValue[] = await qController.possibleValues(tableName, null, fieldName, null, [initialValues[fieldName]], undefined, "form");
if (results && results.length > 0)
{
defaultDisplayValues.set(fieldName, results[0].label);
@ -818,9 +818,9 @@ function EntityForm(props: Props): JSX.Element
{
actions.setSubmitting(true);
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// if there's a callback (e.g., for a modal nested on another create/edit screen), then just pass our data back there anre return. //
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// if there's a callback (e.g., for a modal nested on another create/edit screen), then just pass our data back there and return. //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if (props.onSubmitCallback)
{
props.onSubmitCallback(values);
@ -1290,7 +1290,7 @@ function EntityForm(props: Props): JSX.Element
table={showEditChildForm.table}
defaultValues={showEditChildForm.defaultValues}
disabledFields={showEditChildForm.disabledFields}
onSubmitCallback={submitEditChildForm}
onSubmitCallback={props.onSubmitCallback ? props.onSubmitCallback : submitEditChildForm}
overrideHeading={`${showEditChildForm.rowIndex != null ? "Editing" : "Creating New"} ${showEditChildForm.table.label}`}
/>
</div>

View File

@ -40,16 +40,17 @@ import Snackbar from "@mui/material/Snackbar";
import TextField from "@mui/material/TextField";
import Tooltip from "@mui/material/Tooltip";
import FormData from "form-data";
import React, {useEffect, useReducer, useRef, useState} from "react";
import AceEditor from "react-ace";
import {QCancelButton, QSaveButton} from "qqq/components/buttons/DefaultButtons";
import DynamicSelect from "qqq/components/forms/DynamicSelect";
import ScriptDocsForm from "qqq/components/scripts/ScriptDocsForm";
import ScriptTestForm from "qqq/components/scripts/ScriptTestForm";
import Client from "qqq/utils/qqq/Client";
import "ace-builds/src-noconflict/ace";
import "ace-builds/src-noconflict/mode-javascript";
import "ace-builds/src-noconflict/theme-github";
import React, {useEffect, useReducer, useRef, useState} from "react";
import AceEditor from "react-ace";
import "ace-builds/src-noconflict/ext-language_tools";
export interface ScriptEditorProps
@ -69,15 +70,15 @@ const qController = Client.getInstance();
function buildInitialFileContentsMap(scriptRevisionRecord: QRecord, scriptTypeFileSchemaList: QRecord[]): { [name: string]: string }
{
const rs: {[name: string]: string} = {};
const rs: { [name: string]: string } = {};
if(!scriptTypeFileSchemaList)
if (!scriptTypeFileSchemaList)
{
console.log("Missing scriptTypeFileSchemaList");
}
else
{
let files = scriptRevisionRecord?.associatedRecords?.get("files")
let files = scriptRevisionRecord?.associatedRecords?.get("files");
for (let i = 0; i < scriptTypeFileSchemaList.length; i++)
{
@ -88,7 +89,7 @@ function buildInitialFileContentsMap(scriptRevisionRecord: QRecord, scriptTypeFi
for (let j = 0; j < files?.length; j++)
{
let file = files[j];
if(file.values.get("fileName") == name)
if (file.values.get("fileName") == name)
{
contents = file.values.get("contents");
}
@ -103,9 +104,9 @@ function buildInitialFileContentsMap(scriptRevisionRecord: QRecord, scriptTypeFi
function buildFileTypeMap(scriptTypeFileSchemaList: QRecord[]): { [name: string]: string }
{
const rs: {[name: string]: string} = {};
const rs: { [name: string]: string } = {};
if(!scriptTypeFileSchemaList)
if (!scriptTypeFileSchemaList)
{
console.log("Missing scriptTypeFileSchemaList");
}
@ -125,21 +126,21 @@ function ScriptEditor({title, scriptId, scriptRevisionRecord, closeCallback, tab
{
const [closing, setClosing] = useState(false);
const [apiName, setApiName] = useState(scriptRevisionRecord ? scriptRevisionRecord.values.get("apiName") : null)
const [apiNameLabel, setApiNameLabel] = useState(scriptRevisionRecord ? scriptRevisionRecord.displayValues.get("apiName") : null)
const [apiVersion, setApiVersion] = useState(scriptRevisionRecord ? scriptRevisionRecord.values.get("apiVersion") : null)
const [apiVersionLabel, setApiVersionLabel] = useState(scriptRevisionRecord ? scriptRevisionRecord.displayValues.get("apiVersion") : null)
const [apiName, setApiName] = useState(scriptRevisionRecord ? scriptRevisionRecord.values.get("apiName") : null);
const [apiNameLabel, setApiNameLabel] = useState(scriptRevisionRecord ? scriptRevisionRecord.displayValues.get("apiName") : null);
const [apiVersion, setApiVersion] = useState(scriptRevisionRecord ? scriptRevisionRecord.values.get("apiVersion") : null);
const [apiVersionLabel, setApiVersionLabel] = useState(scriptRevisionRecord ? scriptRevisionRecord.displayValues.get("apiVersion") : null);
const fileNamesFromSchema = scriptTypeFileSchemaList.map((schemaRecord) => schemaRecord.values.get("name"))
const fileNamesFromSchema = scriptTypeFileSchemaList.map((schemaRecord) => schemaRecord.values.get("name"));
const [availableFileNames, setAvailableFileNames] = useState(fileNamesFromSchema);
const [openEditorFileNames, setOpenEditorFileNames] = useState([fileNamesFromSchema[0]])
const [fileContents, setFileContents] = useState(buildInitialFileContentsMap(scriptRevisionRecord, scriptTypeFileSchemaList))
const [fileTypes, setFileTypes] = useState(buildFileTypeMap(scriptTypeFileSchemaList))
const [openEditorFileNames, setOpenEditorFileNames] = useState([fileNamesFromSchema[0]]);
const [fileContents, setFileContents] = useState(buildInitialFileContentsMap(scriptRevisionRecord, scriptTypeFileSchemaList));
const [fileTypes, setFileTypes] = useState(buildFileTypeMap(scriptTypeFileSchemaList));
console.log(`file types: ${JSON.stringify(fileTypes)}`);
const [commitMessage, setCommitMessage] = useState("")
const [commitMessage, setCommitMessage] = useState("");
const [openTool, setOpenTool] = useState(null);
const [errorAlert, setErrorAlert] = useState("")
const [errorAlert, setErrorAlert] = useState("");
const [promptForCommitMessageOpen, setPromptForCommitMessageOpen] = useState(false);
const [, forceUpdate] = useReducer((x) => x + 1, 0);
const ref = useRef();
@ -241,19 +242,19 @@ function ScriptEditor({title, scriptId, scriptRevisionRecord, closeCallback, tab
// need this to make Ace recognize new height.
setTimeout(() =>
{
window.dispatchEvent(new Event("resize"))
window.dispatchEvent(new Event("resize"));
}, 100);
};
const saveClicked = (overrideCommitMessage?: string) =>
{
if(!apiName || !apiVersion)
if (!apiName || !apiVersion)
{
setErrorAlert("You must select a value for both API Name and API Version.")
setErrorAlert("You must select a value for both API Name and API Version.");
return;
}
if(!commitMessage && !overrideCommitMessage)
if (!commitMessage && !overrideCommitMessage)
{
setPromptForCommitMessageOpen(true);
return;
@ -267,18 +268,18 @@ function ScriptEditor({title, scriptId, scriptRevisionRecord, closeCallback, tab
formData.append("scriptId", scriptId);
formData.append("commitMessage", overrideCommitMessage ?? commitMessage);
if(apiName)
if (apiName)
{
formData.append("apiName", apiName);
}
if(apiVersion)
if (apiVersion)
{
formData.append("apiVersion", apiVersion);
}
const fileNamesFromSchema = scriptTypeFileSchemaList.map((schemaRecord) => schemaRecord.values.get("name"))
const fileNamesFromSchema = scriptTypeFileSchemaList.map((schemaRecord) => schemaRecord.values.get("name"));
formData.append("fileNames", fileNamesFromSchema.join(","));
for (let fileName in fileContents)
@ -299,58 +300,58 @@ function ScriptEditor({title, scriptId, scriptRevisionRecord, closeCallback, tab
if (processResult instanceof QJobError)
{
const jobError = processResult as QJobError
setErrorAlert(jobError.userFacingError ?? jobError.error)
const jobError = processResult as QJobError;
setErrorAlert(jobError.userFacingError ?? jobError.error);
setClosing(false);
return;
}
closeCallback(null, "saved", "Saved New Script Version");
}
catch(e)
catch (e)
{
// @ts-ignore
setErrorAlert(e.message ?? "Unexpected error saving script")
setErrorAlert(e.message ?? "Unexpected error saving script");
setClosing(false);
}
})();
}
};
const cancelClicked = () =>
{
setClosing(true);
closeCallback(null, "cancelled");
}
};
const updateCode = (value: string, event: any, index: number) =>
{
fileContents[openEditorFileNames[index]] = value;
forceUpdate();
}
};
const updateCommitMessage = (event: React.ChangeEvent<HTMLInputElement>) =>
{
setCommitMessage(event.target.value);
}
};
const closePromptForCommitMessage = (wasSaveClicked: boolean, message?: string) =>
{
setPromptForCommitMessageOpen(false);
if(wasSaveClicked)
if (wasSaveClicked)
{
setCommitMessage(message)
setCommitMessage(message);
saveClicked(message);
}
else
{
setClosing(false);
}
}
};
const changeApiName = (apiNamePossibleValue?: QPossibleValue) =>
{
if(apiNamePossibleValue)
if (apiNamePossibleValue)
{
setApiName(apiNamePossibleValue.id);
}
@ -358,11 +359,11 @@ function ScriptEditor({title, scriptId, scriptRevisionRecord, closeCallback, tab
{
setApiName(null);
}
}
};
const changeApiVersion = (apiVersionPossibleValue?: QPossibleValue) =>
{
if(apiVersionPossibleValue)
if (apiVersionPossibleValue)
{
setApiVersion(apiVersionPossibleValue.id);
}
@ -370,33 +371,33 @@ function ScriptEditor({title, scriptId, scriptRevisionRecord, closeCallback, tab
{
setApiVersion(null);
}
}
};
const handleSelectingFile = (event: SelectChangeEvent, index: number) =>
{
openEditorFileNames[index] = event.target.value
openEditorFileNames[index] = event.target.value;
setOpenEditorFileNames(openEditorFileNames);
forceUpdate();
}
};
const splitEditorClicked = () =>
{
openEditorFileNames.push(availableFileNames[0])
openEditorFileNames.push(availableFileNames[0]);
setOpenEditorFileNames(openEditorFileNames);
forceUpdate();
}
};
const closeEditorClicked = (index: number) =>
{
openEditorFileNames.splice(index, 1)
openEditorFileNames.splice(index, 1);
setOpenEditorFileNames(openEditorFileNames);
forceUpdate();
}
};
const computeEditorWidth = (): string =>
{
return (100 / openEditorFileNames.length) + "%"
}
return (100 / openEditorFileNames.length) + "%";
};
return (
<Box className="scriptEditor" sx={{position: "absolute", overflowY: "auto", height: "100%", width: "100%"}} p={6}>
@ -408,7 +409,7 @@ function ScriptEditor({title, scriptId, scriptRevisionRecord, closeCallback, tab
{
return;
}
setErrorAlert("")
setErrorAlert("");
}} anchorOrigin={{vertical: "top", horizontal: "center"}}>
<Alert color="error" onClose={() => setErrorAlert("")}>
{errorAlert}
@ -464,19 +465,19 @@ function ScriptEditor({title, scriptId, scriptRevisionRecord, closeCallback, tab
<Box>
{
openEditorFileNames.length > 1 &&
<Tooltip title="Close this editor split" enterDelay={500}>
<IconButton size="small" onClick={() => closeEditorClicked(index)}>
<Icon>close</Icon>
</IconButton>
</Tooltip>
<Tooltip title="Close this editor split" enterDelay={500}>
<IconButton size="small" onClick={() => closeEditorClicked(index)}>
<Icon>close</Icon>
</IconButton>
</Tooltip>
}
{
index == openEditorFileNames.length - 1 &&
<Tooltip title="Open a new editor split" enterDelay={500}>
<IconButton size="small" onClick={splitEditorClicked}>
<Icon>vertical_split</Icon>
</IconButton>
</Tooltip>
<Tooltip title="Open a new editor split" enterDelay={500}>
<IconButton size="small" onClick={splitEditorClicked}>
<Icon>vertical_split</Icon>
</IconButton>
</Tooltip>
}
</Box>
</Box>
@ -526,29 +527,29 @@ function ScriptEditor({title, scriptId, scriptRevisionRecord, closeCallback, tab
</Grid>
</Box>
<CommitMessagePrompt isOpen={promptForCommitMessageOpen} closeHandler={closePromptForCommitMessage}/>
<CommitMessagePrompt isOpen={promptForCommitMessageOpen} closeHandler={closePromptForCommitMessage} />
</Card>
</Box>
);
}
function CommitMessagePrompt(props: {isOpen: boolean, closeHandler: (wasSaveClicked: boolean, message?: string) => void})
function CommitMessagePrompt(props: { isOpen: boolean, closeHandler: (wasSaveClicked: boolean, message?: string) => void })
{
const [commitMessage, setCommitMessage] = useState("No commit message given")
const [commitMessage, setCommitMessage] = useState("No commit message given");
const updateCommitMessage = (event: React.ChangeEvent<HTMLInputElement>) =>
{
setCommitMessage(event.target.value);
}
};
const keyPressHandler = (e: React.KeyboardEvent<HTMLDivElement>) =>
{
if(e.key === "Enter")
if (e.key === "Enter")
{
props.closeHandler(true, commitMessage);
}
}
};
return (
<Dialog
@ -579,10 +580,10 @@ function CommitMessagePrompt(props: {isOpen: boolean, closeHandler: (wasSaveClic
</DialogContent>
<DialogActions>
<QCancelButton onClickHandler={() => props.closeHandler(false)} disabled={false} />
<QSaveButton label="Save" onClickHandler={() => props.closeHandler(true, commitMessage)} disabled={false}/>
<QSaveButton label="Save" onClickHandler={() => props.closeHandler(true, commitMessage)} disabled={false} />
</DialogActions>
</Dialog>
)
);
}
export default ScriptEditor;

View File

@ -18,18 +18,20 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
import {Alert, Skeleton} from "@mui/material";
import Box from "@mui/material/Box";
import Grid from "@mui/material/Grid";
import Modal from "@mui/material/Modal";
import Tab from "@mui/material/Tab";
import Tabs from "@mui/material/Tabs";
import parse from "html-react-parser";
import QContext from "QContext";
import EntityForm from "qqq/components/forms/EntityForm";
import MDTypography from "qqq/components/legacy/MDTypography";
import TabPanel from "qqq/components/misc/TabPanel";
import {BlockData} from "qqq/components/widgets/blocks/BlockModels";
import BarChart from "qqq/components/widgets/charts/barchart/BarChart";
import HorizontalBarChart from "qqq/components/widgets/charts/barchart/HorizontalBarChart";
import DefaultLineChart from "qqq/components/widgets/charts/linechart/DefaultLineChart";
@ -44,7 +46,7 @@ import FieldValueListWidget from "qqq/components/widgets/misc/FieldValueListWidg
import FilterAndColumnsSetupWidget from "qqq/components/widgets/misc/FilterAndColumnsSetupWidget";
import PivotTableSetupWidget from "qqq/components/widgets/misc/PivotTableSetupWidget";
import QuickSightChart from "qqq/components/widgets/misc/QuickSightChart";
import RecordGridWidget from "qqq/components/widgets/misc/RecordGridWidget";
import RecordGridWidget, {ChildRecordListData} from "qqq/components/widgets/misc/RecordGridWidget";
import ScriptViewer from "qqq/components/widgets/misc/ScriptViewer";
import StepperCard from "qqq/components/widgets/misc/StepperCard";
import USMapWidget from "qqq/components/widgets/misc/USMapWidget";
@ -72,9 +74,9 @@ interface Props
childUrlParams?: string;
parentWidgetMetaData?: QWidgetMetaData;
wrapWidgetsInTabPanels: boolean;
actionCallback?: (blockData: BlockData) => boolean;
actionCallback?: (data: any, eventValues?: { [name: string]: any }) => boolean;
initialWidgetDataList: any[];
values?: {[key: string]: any};
values?: { [key: string]: any };
}
DashboardWidgets.defaultProps = {
@ -101,6 +103,11 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco
const [haveLoadedParams, setHaveLoadedParams] = useState(false);
const {accentColor} = useContext(QContext);
/////////////////////////
// modal form controls //
/////////////////////////
const [showEditChildForm, setShowEditChildForm] = useState(null as any);
let initialSelectedTab = 0;
let selectedTabKey: string = null;
if (parentWidgetMetaData && wrapWidgetsInTabPanels)
@ -121,11 +128,11 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco
useEffect(() =>
{
if(initialWidgetDataList && initialWidgetDataList.length > 0)
if (initialWidgetDataList && initialWidgetDataList.length > 0)
{
// todo actually, should this check each element of the array, down in the loop? yeah, when we need to, do it that way.
console.log("We already have initial widget data, so not fetching from backend.");
return
return;
}
setWidgetData([]);
@ -166,7 +173,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco
const reloadWidget = async (index: number, data: string) =>
{
(async () =>
await (async () =>
{
const urlParams = getQueryParams(widgetMetaDataList[index], data);
setCurrentUrlParams(urlParams);
@ -285,6 +292,150 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco
return (rs);
}
/*******************************************************************************
**
*******************************************************************************/
const closeEditChildForm = (event: object, reason: string) =>
{
if (reason === "backdropClick" || reason === "escapeKeyDown")
{
return;
}
setShowEditChildForm(null);
};
/*******************************************************************************
**
*******************************************************************************/
function deleteChildRecord(name: string, widgetIndex: number, rowIndex: number)
{
updateChildRecordList(name, "delete", rowIndex);
actionCallback(widgetData[widgetIndex]);
};
/*******************************************************************************
**
*******************************************************************************/
function openEditChildRecord(name: string, widgetData: any, rowIndex: number)
{
let defaultValues = widgetData.queryOutput.records[rowIndex].values;
let disabledFields = widgetData.disabledFieldsForNewChildRecords;
if (!disabledFields)
{
disabledFields = widgetData.defaultValuesForNewChildRecords;
}
doOpenEditChildForm(name, widgetData.childTableMetaData, rowIndex, defaultValues, disabledFields);
}
/*******************************************************************************
**
*******************************************************************************/
function openAddChildRecord(name: string, widgetData: any)
{
let defaultValues = widgetData.defaultValuesForNewChildRecords;
let disabledFields = widgetData.disabledFieldsForNewChildRecords;
if (!disabledFields)
{
disabledFields = widgetData.defaultValuesForNewChildRecords;
}
doOpenEditChildForm(name, widgetData.childTableMetaData, null, defaultValues, disabledFields);
}
/*******************************************************************************
**
*******************************************************************************/
function doOpenEditChildForm(widgetName: string, table: QTableMetaData, rowIndex: number, defaultValues: any, disabledFields: any)
{
const showEditChildForm: any = {};
showEditChildForm.widgetName = widgetName;
showEditChildForm.table = table;
showEditChildForm.rowIndex = rowIndex;
showEditChildForm.defaultValues = defaultValues;
showEditChildForm.disabledFields = disabledFields;
setShowEditChildForm(showEditChildForm);
}
/*******************************************************************************
**
*******************************************************************************/
function submitEditChildForm(values: any)
{
updateChildRecordList(showEditChildForm.widgetName, showEditChildForm.rowIndex == null ? "insert" : "edit", showEditChildForm.rowIndex, values);
let widgetIndex = determineChildRecordListIndex(showEditChildForm.widgetName);
actionCallback(widgetData[widgetIndex]);
}
/*******************************************************************************
**
*******************************************************************************/
function determineChildRecordListIndex(widgetName: string): number
{
let widgetIndex = -1;
for (var i = 0; i < widgetMetaDataList.length; i++)
{
const widgetMetaData = widgetMetaDataList[i];
if (widgetMetaData.name == widgetName)
{
widgetIndex = i;
break;
}
}
return (widgetIndex);
}
/*******************************************************************************
**
*******************************************************************************/
function updateChildRecordList(widgetName: string, action: "insert" | "edit" | "delete", rowIndex?: number, values?: any)
{
////////////////////////////////////////////////
// find the correct child record widget index //
////////////////////////////////////////////////
let widgetIndex = determineChildRecordListIndex(widgetName);
if (!widgetData[widgetIndex].queryOutput.records)
{
widgetData[widgetIndex].queryOutput.records = [];
}
const newChildListWidgetData: ChildRecordListData = widgetData[widgetIndex];
if (!newChildListWidgetData.queryOutput.records)
{
newChildListWidgetData.queryOutput.records = [];
}
switch (action)
{
case "insert":
newChildListWidgetData.queryOutput.records.push({values: values});
break;
case "edit":
newChildListWidgetData.queryOutput.records[rowIndex] = {values: values};
break;
case "delete":
newChildListWidgetData.queryOutput.records.splice(rowIndex, 1);
break;
}
newChildListWidgetData.totalRows = newChildListWidgetData.queryOutput.records.length;
widgetData[widgetIndex] = newChildListWidgetData;
setWidgetData(widgetData);
setShowEditChildForm(null);
}
const renderWidget = (widgetMetaData: QWidgetMetaData, i: number): JSX.Element =>
{
const labelAdditionalComponentsRight: LabelComponent[] = [];
@ -324,7 +475,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco
)
}
{
widgetMetaData.type === "alert" && widgetData[i]?.html && (
widgetMetaData.type === "alert" && widgetData[i]?.html && !widgetData[i]?.hideWidget && (
<Widget
omitPadding={true}
widgetMetaData={widgetMetaData}
@ -334,7 +485,16 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco
labelAdditionalComponentsRight={labelAdditionalComponentsRight}
labelAdditionalComponentsLeft={labelAdditionalComponentsLeft}
>
<Alert severity={widgetData[i]?.alertType?.toLowerCase()}>{parse(widgetData[i]?.html)}</Alert>
<Alert severity={widgetData[i]?.alertType?.toLowerCase()}>
{parse(widgetData[i]?.html)}
{widgetData[i]?.bulletList && (
<div style={{fontSize: "14px"}}>
{widgetData[i].bulletList.map((bullet: string, index: number) =>
<li key={`widget-${i}-${index}`}>{parse(bullet)}</li>
)}
</div>
)}
</Alert>
</Widget>
)
}
@ -516,9 +676,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco
}
{
widgetMetaData.type === "divider" && (
<Box>
<DividerWidget />
</Box>
<DividerWidget />
)
}
{
@ -552,6 +710,12 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco
widgetMetaData.type === "childRecordList" && (
widgetData && widgetData[i] &&
<RecordGridWidget
disableRowClick={widgetData[i]?.disableRowClick}
allowRecordEdit={widgetData[i]?.allowRecordEdit}
allowRecordDelete={widgetData[i]?.allowRecordDelete}
deleteRecordCallback={(rowIndex) => deleteChildRecord(widgetMetaData.name, i, rowIndex)}
editRecordCallback={(rowIndex) => openEditChildRecord(widgetMetaData.name, widgetData[i], rowIndex)}
addNewRecordCallback={widgetData[i]?.isInProcess ? () => openAddChildRecord(widgetMetaData.name, widgetData[i]) : null}
widgetMetaData={widgetMetaData}
data={widgetData[i]}
/>
@ -653,23 +817,23 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco
if (!omitWrappingGridContainer)
{
const gridProps: {[key: string]: any} = {};
const gridProps: { [key: string]: any } = {};
for(let size of ["xs", "sm", "md", "lg", "xl", "xxl"])
for (let size of ["xs", "sm", "md", "lg", "xl", "xxl"])
{
const key = `gridCols:sizeClass:${size}`
if(widgetMetaData?.defaultValues?.has(key))
const key = `gridCols:sizeClass:${size}`;
if (widgetMetaData?.defaultValues?.has(key))
{
gridProps[size] = widgetMetaData?.defaultValues.get(key);
}
}
if(!gridProps["xxl"])
if (!gridProps["xxl"])
{
gridProps["xxl"] = widgetMetaData.gridColumns ? widgetMetaData.gridColumns : 12;
}
if(!gridProps["xs"])
if (!gridProps["xs"])
{
gridProps["xs"] = 12;
}
@ -725,6 +889,22 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco
</Grid>
)
}
{
showEditChildForm &&
<Modal open={showEditChildForm as boolean} onClose={(event, reason) => closeEditChildForm(event, reason)}>
<div className="modalEditForm">
<EntityForm
isModal={true}
closeModalHandler={closeEditChildForm}
table={showEditChildForm.table}
defaultValues={showEditChildForm.defaultValues}
disabledFields={showEditChildForm.disabledFields}
onSubmitCallback={submitEditChildForm}
overrideHeading={`${showEditChildForm.rowIndex != null ? "Editing" : "Creating New"} ${showEditChildForm.table.label}`}
/>
</div>
</Modal>
}
</>
) : null
);

View File

@ -50,6 +50,7 @@ import DeveloperModeUtils from "qqq/utils/DeveloperModeUtils";
import Client from "qqq/utils/qqq/Client";
import ValueUtils from "qqq/utils/qqq/ValueUtils";
import "ace-builds/src-noconflict/ace";
import "ace-builds/src-noconflict/mode-java";
import "ace-builds/src-noconflict/mode-javascript";
import "ace-builds/src-noconflict/mode-json";

View File

@ -19,13 +19,16 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import Box from "@mui/material/Box";
import Divider from "@mui/material/Divider";
function DividerWidget(): JSX.Element
{
return (
<Divider sx={{padding: "1px", background: "red"}}/>
<Box pl={3} pt={3} pb={3} width="100%">
<Divider sx={{width: "100%", height: "1px", background: "grey"}} />
</Box>
);
}

View File

@ -39,15 +39,15 @@ import {Link, useNavigate} from "react-router-dom";
export interface ChildRecordListData extends WidgetData
{
title: string;
queryOutput: { records: { values: any }[] };
childTableMetaData: QTableMetaData;
tablePath: string;
viewAllLink: string;
totalRows: number;
canAddChildRecord: boolean;
defaultValuesForNewChildRecords: { [fieldName: string]: any };
disabledFieldsForNewChildRecords: { [fieldName: string]: any };
title?: string;
queryOutput?: { records: { values: any }[] };
childTableMetaData?: QTableMetaData;
tablePath?: string;
viewAllLink?: string;
totalRows?: number;
canAddChildRecord?: boolean;
defaultValuesForNewChildRecords?: { [fieldName: string]: any };
disabledFieldsForNewChildRecords?: { [fieldName: string]: any };
}
interface Props
@ -186,7 +186,7 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
setCsv(csv);
setFileName(fileName);
}
}, [data]);
}, [JSON.stringify(data?.queryOutput)]);
///////////////////
// view all link //
@ -305,6 +305,12 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
return (<GridToolbarContainer />);
}
let containerPadding = -3;
if (data?.isInProcess)
{
containerPadding = 0;
}
const grid = (
<DataGridPro
@ -364,7 +370,7 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
labelAdditionalComponentsRight={labelAdditionalComponentsRight}
labelBoxAdditionalSx={{position: "relative", top: "-0.375rem"}}
>
<Box mx={-3} mb={-3}>
<Box mx={containerPadding} mb={containerPadding}>
<Box>
{grid}
</Box>

View File

@ -0,0 +1,96 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
import Modal from "@mui/material/Modal";
import EntityForm from "qqq/components/forms/EntityForm";
import Client from "qqq/utils/qqq/Client";
import React, {useEffect, useReducer, useState} from "react";
////////////////////////////////
// structure of expected data //
////////////////////////////////
export interface ModalEditFormData
{
tableName: string;
defaultValues?: { [key: string]: string };
disabledFields?: { [key: string]: boolean } | string[];
overrideHeading?: string;
onSubmitCallback?: (values: any) => void;
initialShowModalValue?: boolean;
}
const qController = Client.getInstance();
function ModalEditForm({tableName, defaultValues, disabledFields, overrideHeading, onSubmitCallback, initialShowModalValue}: ModalEditFormData,): JSX.Element
{
const [showModal, setShowModal] = useState(initialShowModalValue);
const [table, setTable] = useState(null as QTableMetaData);
const [, forceUpdate] = useReducer((x) => x + 1, 0);
useEffect(() =>
{
if (!tableName)
{
return;
}
(async () =>
{
const tableMetaData = await qController.loadTableMetaData(tableName);
setTable(tableMetaData);
forceUpdate();
})();
}, [tableName]);
/*******************************************************************************
**
*******************************************************************************/
const closeEditChildForm = (event: object, reason: string) =>
{
if (reason === "backdropClick" || reason === "escapeKeyDown")
{
return;
}
setShowModal(null);
};
return (
table && showModal &&
<Modal open={showModal as boolean} onClose={(event, reason) => closeEditChildForm(event, reason)}>
<div className="modalEditForm">
<EntityForm
isModal={true}
closeModalHandler={closeEditChildForm}
table={table}
defaultValues={defaultValues}
disabledFields={disabledFields}
onSubmitCallback={onSubmitCallback}
overrideHeading={overrideHeading}
/>
</div>
</Modal>
);
}
export default ModalEditForm;