mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-21 22:58:43 +00:00
Compare commits
27 Commits
snapshot-f
...
snapshot-i
Author | SHA1 | Date | |
---|---|---|---|
beb0b415fa | |||
d03e908a9d | |||
dc62f97219 | |||
aac579232d | |||
fe9e20715a | |||
71a1bfaa6b | |||
6469d569c0 | |||
d9e9a0be08 | |||
aefb282a0e | |||
fb57718c1c | |||
ba213b038b | |||
69daf47021 | |||
1d24b9b40c | |||
f44ba8d6d3 | |||
7b562aea50 | |||
3bf1cea9dd | |||
dc131d5189 | |||
2b5cc1610f | |||
a36bdb1474 | |||
c2926d26e8 | |||
eb42a86655 | |||
b7f715f832 | |||
16a08cfd42 | |||
f5919c66ab | |||
d750ef0930 | |||
267ead925b | |||
f925ad9116 |
@ -1,12 +0,0 @@
|
||||
import {defineConfig} from "cypress";
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
viewportHeight: 1000,
|
||||
viewportWidth: 1200,
|
||||
setupNodeEvents(on, config)
|
||||
{
|
||||
// implement node event listeners here
|
||||
},
|
||||
},
|
||||
});
|
20141
package-lock.json
generated
20141
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -6,7 +6,7 @@
|
||||
"@auth0/auth0-react": "1.10.2",
|
||||
"@emotion/react": "11.7.1",
|
||||
"@emotion/styled": "11.6.0",
|
||||
"@kingsrook/qqq-frontend-core": "1.0.100",
|
||||
"@kingsrook/qqq-frontend-core": "1.0.102",
|
||||
"@mui/icons-material": "5.4.1",
|
||||
"@mui/material": "5.11.1",
|
||||
"@mui/styles": "5.11.1",
|
||||
|
@ -36,7 +36,7 @@ import Icon from "@mui/material/Icon";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import {makeStyles} from "@mui/styles";
|
||||
import {Command} from "cmdk";
|
||||
import React, {useContext, useEffect, useRef} from "react";
|
||||
import React, {useContext, useEffect, useRef, useState} from "react";
|
||||
import {useNavigate} from "react-router-dom";
|
||||
import QContext from "QContext";
|
||||
import HistoryUtils, {QHistoryEntry} from "qqq/utils/HistoryUtils";
|
||||
@ -62,8 +62,13 @@ const useStyles = makeStyles((theme: any) => ({
|
||||
}
|
||||
}));
|
||||
|
||||
const A_FIRST = -1;
|
||||
const B_FIRST = 1;
|
||||
|
||||
const CommandMenu = ({metaData}: Props) =>
|
||||
{
|
||||
const [searchString, setSearchString] = useState("");
|
||||
|
||||
const navigate = useNavigate();
|
||||
const pathParts = location.pathname.replace(/\/+$/, "").split("/");
|
||||
|
||||
@ -71,7 +76,7 @@ const CommandMenu = ({metaData}: Props) =>
|
||||
|
||||
const classes = useStyles();
|
||||
|
||||
function evalueKeyPress(e: KeyboardEvent)
|
||||
function evaluateKeyPress(e: KeyboardEvent)
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// if a dot pressed, not from a "text" element, then toggle command menu //
|
||||
@ -107,20 +112,20 @@ const CommandMenu = ({metaData}: Props) =>
|
||||
|
||||
const down = (e: KeyboardEvent) =>
|
||||
{
|
||||
evalueKeyPress(e);
|
||||
}
|
||||
evaluateKeyPress(e);
|
||||
};
|
||||
|
||||
document.addEventListener("keydown", down)
|
||||
document.addEventListener("keydown", down);
|
||||
return () =>
|
||||
{
|
||||
document.removeEventListener("keydown", down)
|
||||
}
|
||||
}, [tableMetaData, dotMenuOpen, keyboardHelpOpen])
|
||||
document.removeEventListener("keydown", down);
|
||||
};
|
||||
}, [tableMetaData, dotMenuOpen, keyboardHelpOpen]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setDotMenuOpen(false);
|
||||
}, [location.pathname])
|
||||
}, [location.pathname]);
|
||||
|
||||
function goToItem(path: string)
|
||||
{
|
||||
@ -162,73 +167,117 @@ const CommandMenu = ({metaData}: Props) =>
|
||||
return (null);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** sort a section (e.g, tables, apps).
|
||||
**
|
||||
** put labels that start-with the search word first.
|
||||
*******************************************************************************/
|
||||
function comparator(labelA: string, labelB: string)
|
||||
{
|
||||
if (searchString != "")
|
||||
{
|
||||
let aStartsWith = labelA.toLowerCase().startsWith(searchString.toLowerCase());
|
||||
let bStartsWith = labelB.toLowerCase().startsWith(searchString.toLowerCase());
|
||||
|
||||
if (aStartsWith && !bStartsWith)
|
||||
{
|
||||
return A_FIRST;
|
||||
}
|
||||
else if (bStartsWith && !aStartsWith)
|
||||
{
|
||||
return B_FIRST;
|
||||
}
|
||||
|
||||
const indexOfSpace = searchString.indexOf(" ");
|
||||
if (indexOfSpace > 0)
|
||||
{
|
||||
aStartsWith = labelA.toLowerCase().startsWith(searchString.substring(0, indexOfSpace).toLowerCase());
|
||||
bStartsWith = labelB.toLowerCase().startsWith(searchString.substring(0, indexOfSpace).toLowerCase());
|
||||
|
||||
if (aStartsWith && !bStartsWith)
|
||||
{
|
||||
return A_FIRST;
|
||||
}
|
||||
else if (bStartsWith && !aStartsWith)
|
||||
{
|
||||
return B_FIRST;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (labelA.localeCompare(labelB));
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
function ActionsSection()
|
||||
{
|
||||
let tableNames : string[]= [];
|
||||
let tableNames: string[] = [];
|
||||
metaData.tables.forEach((value: QTableMetaData, key: string) =>
|
||||
{
|
||||
tableNames.push(value.name);
|
||||
})
|
||||
tableNames = tableNames.sort((a: string, b:string) =>
|
||||
});
|
||||
tableNames = tableNames.sort((a: string, b: string) =>
|
||||
{
|
||||
const labelA = metaData.tables.get(a).label ?? "";
|
||||
const labelB = metaData.tables.get(b).label ?? "";
|
||||
return (labelA.localeCompare(labelB));
|
||||
})
|
||||
return comparator(labelA, labelB);
|
||||
});
|
||||
|
||||
const path = location.pathname;
|
||||
return tableMetaData && !path.endsWith("/edit") && !path.endsWith("/create") && !path.endsWith("#audit") && ! path.endsWith("copy") &&
|
||||
return tableMetaData && !path.endsWith("/edit") && !path.endsWith("/create") && !path.endsWith("#audit") && !path.endsWith("copy") &&
|
||||
(
|
||||
<Command.Group heading={`${tableMetaData.label} Actions`}>
|
||||
{
|
||||
tableMetaData.capabilities.has(Capability.TABLE_INSERT) && tableMetaData.insertPermission &&
|
||||
<Command.Item onSelect={() => goToItem(`${pathParts.slice(0, -1).join("/")}/create`)} key={`${tableMetaData.label}-new`} value="New"><Icon sx={{color: accentColor}}>add</Icon>New</Command.Item>
|
||||
<Command.Item onSelect={() => goToItem(`${pathParts.slice(0, -1).join("/")}/create`)} key={`${tableMetaData.label}-new`} value="New"><Icon sx={{color: accentColor}}>add</Icon>New</Command.Item>
|
||||
}
|
||||
{
|
||||
tableMetaData.capabilities.has(Capability.TABLE_INSERT) && tableMetaData.insertPermission &&
|
||||
<Command.Item onSelect={() => goToItem(`${pathParts.join("/")}/copy`)} key={`${tableMetaData.label}-copy`} value="Copy"><Icon sx={{color: accentColor}}>copy</Icon>Copy</Command.Item>
|
||||
<Command.Item onSelect={() => goToItem(`${pathParts.join("/")}/copy`)} key={`${tableMetaData.label}-copy`} value="Copy"><Icon sx={{color: accentColor}}>copy</Icon>Copy</Command.Item>
|
||||
}
|
||||
{
|
||||
tableMetaData.capabilities.has(Capability.TABLE_UPDATE) && tableMetaData.editPermission &&
|
||||
<Command.Item onSelect={() => goToItem(`${pathParts.join("/")}/edit`)} key={`${tableMetaData.label}-edit`} value="Edit"><Icon sx={{color: accentColor}}>edit</Icon>Edit</Command.Item>
|
||||
<Command.Item onSelect={() => goToItem(`${pathParts.join("/")}/edit`)} key={`${tableMetaData.label}-edit`} value="Edit"><Icon sx={{color: accentColor}}>edit</Icon>Edit</Command.Item>
|
||||
}
|
||||
{
|
||||
metaData && metaData.tables.has("audit") &&
|
||||
<Command.Item onSelect={() => goToItem(`${pathParts.join("/")}#audit`)} key={`${tableMetaData.label}-audit`} value="Audit"><Icon sx={{color: accentColor}}>checklist</Icon>Audit</Command.Item>
|
||||
<Command.Item onSelect={() => goToItem(`${pathParts.join("/")}#audit`)} key={`${tableMetaData.label}-audit`} value="Audit"><Icon sx={{color: accentColor}}>checklist</Icon>Audit</Command.Item>
|
||||
}
|
||||
{
|
||||
tableProcesses && tableProcesses.length > 0 &&
|
||||
(
|
||||
tableProcesses.map((process) => (
|
||||
<Command.Item onSelect={() => goToItem(`${pathParts.join("/")}/${process.name}`)} key={`${process.name}`} value={`${process.label}`}><Icon sx={{color: accentColor}}>{getIconName(process.iconName, "play_arrow")}</Icon>{process.label}</Command.Item>
|
||||
))
|
||||
)
|
||||
(
|
||||
tableProcesses.map((process) => (
|
||||
<Command.Item onSelect={() => goToItem(`${pathParts.join("/")}/${process.name}`)} key={`${process.name}`} value={`${process.label}`}><Icon sx={{color: accentColor}}>{getIconName(process.iconName, "play_arrow")}</Icon>{process.label}</Command.Item>
|
||||
))
|
||||
)
|
||||
}
|
||||
<Command.Separator />
|
||||
</Command.Group>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
function TablesSection()
|
||||
{
|
||||
let tableNames : string[]= [];
|
||||
let tableNames: string[] = [];
|
||||
metaData.tables.forEach((value: QTableMetaData, key: string) =>
|
||||
{
|
||||
tableNames.push(value.name);
|
||||
})
|
||||
tableNames = tableNames.sort((a: string, b:string) =>
|
||||
});
|
||||
tableNames = tableNames.sort((a: string, b: string) =>
|
||||
{
|
||||
const labelA = metaData.tables.get(a).label ?? "";
|
||||
const labelB = metaData.tables.get(b).label ?? "";
|
||||
return (labelA.localeCompare(labelB));
|
||||
})
|
||||
return(
|
||||
return comparator(labelA, labelB);
|
||||
});
|
||||
return (
|
||||
<Command.Group heading="Tables">
|
||||
{
|
||||
tableNames.map((tableName: string, index: number) =>
|
||||
@ -243,6 +292,7 @@ const CommandMenu = ({metaData}: Props) =>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -252,16 +302,16 @@ const CommandMenu = ({metaData}: Props) =>
|
||||
metaData.apps.forEach((value: QAppMetaData, key: string) =>
|
||||
{
|
||||
appNames.push(value.name);
|
||||
})
|
||||
});
|
||||
|
||||
appNames = appNames.sort((a: string, b:string) =>
|
||||
appNames = appNames.sort((a: string, b: string) =>
|
||||
{
|
||||
const labelA = getFullAppLabel(metaData.appTree, a, 1, "") ?? "";
|
||||
const labelB = getFullAppLabel(metaData.appTree, b, 1, "") ?? "";
|
||||
return (labelA.localeCompare(labelB));
|
||||
})
|
||||
return comparator(labelA, labelB);
|
||||
});
|
||||
|
||||
return(
|
||||
return (
|
||||
<Command.Group heading="Apps">
|
||||
{
|
||||
appNames.map((appName: string, index: number) =>
|
||||
@ -276,33 +326,37 @@ const CommandMenu = ({metaData}: Props) =>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
function RecentlyViewedSection()
|
||||
{
|
||||
const history = HistoryUtils.get();
|
||||
const options = [] as any;
|
||||
history.entries.reverse().forEach((entry, index) =>
|
||||
options.push({label: `${entry.label} index`, id: index, key: index, path: entry.path, iconName: entry.iconName})
|
||||
)
|
||||
);
|
||||
|
||||
let appNames: string[] = [];
|
||||
metaData.apps.forEach((value: QAppMetaData, key: string) =>
|
||||
{
|
||||
appNames.push(value.name);
|
||||
})
|
||||
});
|
||||
|
||||
appNames = appNames.sort((a: string, b:string) =>
|
||||
appNames = appNames.sort((a: string, b: string) =>
|
||||
{
|
||||
const labelA = metaData.apps.get(a).label ?? "";
|
||||
const labelB = metaData.apps.get(b).label ?? "";
|
||||
return (labelA.localeCompare(labelB));
|
||||
})
|
||||
return comparator(labelA, labelB);
|
||||
});
|
||||
|
||||
const entryMap = new Map<string, boolean>();
|
||||
return(
|
||||
return (
|
||||
<Command.Group heading="Recently Viewed Records">
|
||||
{
|
||||
history.entries.reverse().map((entry: QHistoryEntry, index: number) =>
|
||||
! entryMap.has(entry.label) && entryMap.set(entry.label, true) && (
|
||||
!entryMap.has(entry.label) && entryMap.set(entry.label, true) && (
|
||||
<Command.Item onSelect={() => goToItem(`${entry.path}`)} key={`${entry.label}-${index}`} value={entry.label}><Icon sx={{color: accentColor}}>{entry.iconName}</Icon>{entry.label}</Command.Item>
|
||||
)
|
||||
)
|
||||
@ -311,29 +365,90 @@ const CommandMenu = ({metaData}: Props) =>
|
||||
);
|
||||
}
|
||||
|
||||
const containerElement = useRef(null)
|
||||
const containerElement = useRef(null);
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
function closeKeyboardHelp()
|
||||
{
|
||||
setKeyboardHelpOpen(false);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
function closeDotMenu()
|
||||
{
|
||||
setDotMenuOpen(false);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** filter function for cmd-k library
|
||||
**
|
||||
*******************************************************************************/
|
||||
function doFilter(value: string, search: string)
|
||||
{
|
||||
setSearchString(search);
|
||||
|
||||
/////////////////////
|
||||
// split on spaces //
|
||||
/////////////////////
|
||||
const searchParts = search.toLowerCase().split(" ");
|
||||
if (searchParts.length == 1)
|
||||
{
|
||||
//////////////////////////////////////////////
|
||||
// if only 1 word, just do an includes test //
|
||||
//////////////////////////////////////////////
|
||||
return (value.toLowerCase().includes(search.toLowerCase()) ? 1 : 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
////////////////////////////////////////
|
||||
// else split the value on spaces too //
|
||||
////////////////////////////////////////
|
||||
const valueParts = value.toLowerCase().split(" ");
|
||||
if (searchParts.length > valueParts.length)
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
// if there are more words in the search than in the value, then it can't match //
|
||||
// e.g. "order c" can't ever match, say "order" //
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
return (0);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// iterate over the search parts - if any don't match the corresponding value parts, then it's a non-match //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
for (let i = 0; i < searchParts.length; i++)
|
||||
{
|
||||
if (!valueParts[i].includes(searchParts[i]))
|
||||
{
|
||||
return (0);
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////
|
||||
// if no failure, return a hit //
|
||||
/////////////////////////////////
|
||||
return (1);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Box ref={containerElement} className="raycast" sx={{position: "relative", zIndex: 10_000}}>
|
||||
{
|
||||
<Dialog open={dotMenuOpen} onClose={closeDotMenu}>
|
||||
<Command.Dialog open={dotMenuOpen} onOpenChange={setDotMenuOpen} container={containerElement.current} label="Test Global Command Menu">
|
||||
<Command.Dialog open={dotMenuOpen} onOpenChange={setDotMenuOpen} container={containerElement.current} filter={(value, search) => doFilter(value, search)}>
|
||||
<Box sx={{display: "flex"}}>
|
||||
<Command.Input placeholder="Search for Tables, Actions, or Recently Viewed Items..."/>
|
||||
<Command.Input placeholder="Search for Tables, Actions, or Recently Viewed Items..." />
|
||||
<Button onClick={closeDotMenu}><Icon>close</Icon></Button>
|
||||
</Box>
|
||||
<Command.Loading />
|
||||
<Command.Loading />
|
||||
<Command.Separator />
|
||||
<Command.List>
|
||||
<Command.Empty>No results found.</Command.Empty>
|
||||
@ -381,6 +496,6 @@ const CommandMenu = ({metaData}: Props) =>
|
||||
</Dialog>
|
||||
}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
export default CommandMenu;
|
||||
|
@ -44,9 +44,9 @@ import MDTypography from "qqq/components/legacy/MDTypography";
|
||||
import HelpContent from "qqq/components/misc/HelpContent";
|
||||
import QRecordSidebar from "qqq/components/misc/RecordSidebar";
|
||||
import DynamicFormWidget from "qqq/components/widgets/misc/DynamicFormWidget";
|
||||
import FilterAndColumnsSetupWidget from "qqq/components/widgets/misc/FilterAndColumnsSetupWidget";
|
||||
import PivotTableSetupWidget from "qqq/components/widgets/misc/PivotTableSetupWidget";
|
||||
import RecordGridWidget, {ChildRecordListData} from "qqq/components/widgets/misc/RecordGridWidget";
|
||||
import ReportSetupWidget from "qqq/components/widgets/misc/ReportSetupWidget";
|
||||
import {FieldRule, FieldRuleAction, FieldRuleTrigger} from "qqq/models/fields/FieldRules";
|
||||
import HtmlUtils from "qqq/utils/HtmlUtils";
|
||||
import Client from "qqq/utils/qqq/Client";
|
||||
@ -88,7 +88,7 @@ EntityForm.defaultProps = {
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
let formikSetFieldValueFunction = (field: string, value: any, shouldValidate?: boolean): void =>
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
function EntityForm(props: Props): JSX.Element
|
||||
{
|
||||
@ -119,11 +119,12 @@ function EntityForm(props: Props): JSX.Element
|
||||
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
||||
|
||||
const [showEditChildForm, setShowEditChildForm] = useState(null as any);
|
||||
const [modalDataChangedCounter, setModalDataChangedCount] = useState(0);
|
||||
|
||||
const [notAllowedError, setNotAllowedError] = useState(null as string);
|
||||
|
||||
const [formValuesJSON, setFormValuesJSON] = useState("");
|
||||
const [formValues, setFormValues] = useState({} as {[name: string]: any});
|
||||
const [formValues, setFormValues] = useState({} as { [name: string]: any });
|
||||
|
||||
const {pageHeader, setPageHeader} = useContext(QContext);
|
||||
|
||||
@ -204,7 +205,7 @@ function EntityForm(props: Props): JSX.Element
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
const deleteChildRecord = (name: string, widgetData: any, rowIndex: number) =>
|
||||
function deleteChildRecord(name: string, widgetData: any, rowIndex: number)
|
||||
{
|
||||
updateChildRecordList(name, "delete", rowIndex);
|
||||
};
|
||||
@ -282,6 +283,8 @@ function EntityForm(props: Props): JSX.Element
|
||||
setRenderedWidgetSections(newRenderedWidgetSections);
|
||||
forceUpdate();
|
||||
|
||||
setModalDataChangedCount(modalDataChangedCounter + 1);
|
||||
|
||||
setShowEditChildForm(null);
|
||||
}
|
||||
|
||||
@ -291,7 +294,7 @@ function EntityForm(props: Props): JSX.Element
|
||||
*******************************************************************************/
|
||||
useEffect(() =>
|
||||
{
|
||||
const newRenderedWidgetSections: {[name: string]: JSX.Element} = {};
|
||||
const newRenderedWidgetSections: { [name: string]: JSX.Element } = {};
|
||||
for (let widgetName in renderedWidgetSections)
|
||||
{
|
||||
const widgetMetaData = metaData.widgets.get(widgetName);
|
||||
@ -351,12 +354,11 @@ function EntityForm(props: Props): JSX.Element
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** if we have a widget that wants to set form-field values, they can take this
|
||||
** function in as a callback, and then call it with their values.
|
||||
*******************************************************************************/
|
||||
function setFormFieldValuesFromWidget(values: {[name: string]: any})
|
||||
function setFormFieldValuesFromWidget(values: { [name: string]: any })
|
||||
{
|
||||
for (let key in values)
|
||||
{
|
||||
@ -370,13 +372,13 @@ function EntityForm(props: Props): JSX.Element
|
||||
*******************************************************************************/
|
||||
function getWidgetSection(widgetMetaData: QWidgetMetaData, widgetData: any): JSX.Element
|
||||
{
|
||||
if(widgetMetaData.type == "childRecordList")
|
||||
if (widgetMetaData.type == "childRecordList")
|
||||
{
|
||||
widgetData.viewAllLink = null;
|
||||
widgetMetaData.showExportButton = false;
|
||||
|
||||
return <RecordGridWidget
|
||||
key={new Date().getTime()} // added so that editing values actually re-renders...
|
||||
return Object.keys(childListWidgetData).length > 0 && (<RecordGridWidget
|
||||
key={`${formValues["tableName"]}-${modalDataChangedCounter}`}
|
||||
widgetMetaData={widgetMetaData}
|
||||
data={widgetData}
|
||||
disableRowClick
|
||||
@ -385,21 +387,30 @@ function EntityForm(props: Props): JSX.Element
|
||||
addNewRecordCallback={() => openAddChildRecord(widgetMetaData.name, widgetData)}
|
||||
editRecordCallback={(rowIndex) => openEditChildRecord(widgetMetaData.name, widgetData, rowIndex)}
|
||||
deleteRecordCallback={(rowIndex) => deleteChildRecord(widgetMetaData.name, widgetData, rowIndex)}
|
||||
/>;
|
||||
/>);
|
||||
}
|
||||
|
||||
if(widgetMetaData.type == "reportSetup")
|
||||
if (widgetMetaData.type == "filterAndColumnsSetup")
|
||||
{
|
||||
return <ReportSetupWidget
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if the widget metadata specifies a table name, set form values to that so widget knows which to use //
|
||||
// (for the case when it is not being specified by a separate field in the record) //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if (widgetMetaData?.defaultValues?.has("tableName"))
|
||||
{
|
||||
formValues["tableName"] = widgetMetaData?.defaultValues.get("tableName");
|
||||
}
|
||||
|
||||
return <FilterAndColumnsSetupWidget
|
||||
key={formValues["tableName"]} // todo, is this good? it was added so that editing values actually re-renders...
|
||||
isEditable={true}
|
||||
widgetMetaData={widgetMetaData}
|
||||
recordValues={formValues}
|
||||
onSaveCallback={setFormFieldValuesFromWidget}
|
||||
/>
|
||||
/>;
|
||||
}
|
||||
|
||||
if(widgetMetaData.type == "pivotTableSetup")
|
||||
if (widgetMetaData.type == "pivotTableSetup")
|
||||
{
|
||||
return <PivotTableSetupWidget
|
||||
key={formValues["tableName"]} // todo, is this good? it was added so that editing values actually re-renders...
|
||||
@ -407,10 +418,10 @@ function EntityForm(props: Props): JSX.Element
|
||||
widgetMetaData={widgetMetaData}
|
||||
recordValues={formValues}
|
||||
onSaveCallback={setFormFieldValuesFromWidget}
|
||||
/>
|
||||
/>;
|
||||
}
|
||||
|
||||
if(widgetMetaData.type == "dynamicForm")
|
||||
if (widgetMetaData.type == "dynamicForm")
|
||||
{
|
||||
return <DynamicFormWidget
|
||||
key={formValues["savedReportId"]} // todo - pull this from the metaData (could do so above too...)
|
||||
@ -420,10 +431,10 @@ function EntityForm(props: Props): JSX.Element
|
||||
recordValues={formValues}
|
||||
record={record}
|
||||
onSaveCallback={setFormFieldValuesFromWidget}
|
||||
/>
|
||||
/>;
|
||||
}
|
||||
|
||||
return (<Box>Unsupported widget type: {widgetMetaData.type}</Box>)
|
||||
return (<Box>Unsupported widget type: {widgetMetaData.type}</Box>);
|
||||
}
|
||||
|
||||
|
||||
@ -449,12 +460,12 @@ function EntityForm(props: Props): JSX.Element
|
||||
function setupFieldRules(tableMetaData: QTableMetaData)
|
||||
{
|
||||
const mdbMetaData = tableMetaData?.supplementalTableMetaData?.get("materialDashboard");
|
||||
if(!mdbMetaData)
|
||||
if (!mdbMetaData)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(mdbMetaData.fieldRules)
|
||||
if (mdbMetaData.fieldRules)
|
||||
{
|
||||
const newFieldRules: FieldRule[] = [];
|
||||
for (let i = 0; i < mdbMetaData.fieldRules.length; i++)
|
||||
@ -469,83 +480,164 @@ function EntityForm(props: Props): JSX.Element
|
||||
//////////////////
|
||||
// initial load //
|
||||
//////////////////
|
||||
if (!asyncLoadInited)
|
||||
useEffect(() =>
|
||||
{
|
||||
setAsyncLoadInited(true);
|
||||
(async () =>
|
||||
if (!asyncLoadInited)
|
||||
{
|
||||
const tableMetaData = await qController.loadTableMetaData(tableName);
|
||||
setTableMetaData(tableMetaData);
|
||||
recordAnalytics({location: window.location, title: (props.isCopy ? "Copy" : props.id ? "Edit" : "New") + ": " + tableMetaData.label});
|
||||
|
||||
setupFieldRules(tableMetaData);
|
||||
|
||||
const metaData = await qController.loadMetaData();
|
||||
setMetaData(metaData);
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
// define the sections, e.g., for the left-bar //
|
||||
/////////////////////////////////////////////////
|
||||
const tableSections = TableUtils.getSectionsForRecordSidebar(tableMetaData, [...tableMetaData.fields.keys()], (section: QTableSection) =>
|
||||
setAsyncLoadInited(true);
|
||||
(async () =>
|
||||
{
|
||||
const widget = metaData.widgets.get(section.widgetName);
|
||||
if(widget)
|
||||
const tableMetaData = await qController.loadTableMetaData(tableName);
|
||||
setTableMetaData(tableMetaData);
|
||||
recordAnalytics({location: window.location, title: (props.isCopy ? "Copy" : props.id ? "Edit" : "New") + ": " + tableMetaData.label});
|
||||
|
||||
setupFieldRules(tableMetaData);
|
||||
|
||||
const metaData = await qController.loadMetaData();
|
||||
setMetaData(metaData);
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
// define the sections, e.g., for the left-bar //
|
||||
/////////////////////////////////////////////////
|
||||
const tableSections = TableUtils.getSectionsForRecordSidebar(tableMetaData, [...tableMetaData.fields.keys()], (section: QTableSection) =>
|
||||
{
|
||||
if(widget.type == "childRecordList" && widget.defaultValues?.has("manageAssociationName"))
|
||||
const widget = metaData?.widgets.get(section.widgetName);
|
||||
if (widget)
|
||||
{
|
||||
return (true);
|
||||
if (widget.type == "childRecordList" && widget.defaultValues?.has("manageAssociationName"))
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
|
||||
if (widget.type == "filterAndColumnsSetup" || widget.type == "pivotTableSetup" || widget.type == "dynamicForm")
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
}
|
||||
|
||||
if(widget.type == "reportSetup" || widget.type == "pivotTableSetup" || widget.type == "dynamicForm")
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
}
|
||||
return (false);
|
||||
});
|
||||
setTableSections(tableSections);
|
||||
|
||||
return (false);
|
||||
});
|
||||
setTableSections(tableSections);
|
||||
|
||||
const fieldArray = [] as QFieldMetaData[];
|
||||
const sortedKeys = [...tableMetaData.fields.keys()].sort();
|
||||
sortedKeys.forEach((key) =>
|
||||
{
|
||||
const fieldMetaData = tableMetaData.fields.get(key);
|
||||
fieldArray.push(fieldMetaData);
|
||||
});
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if doing an edit or copy, fetch the record and pre-populate the form values from it //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
let record: QRecord = null;
|
||||
let defaultDisplayValues = new Map<string, string>();
|
||||
if (props.id !== null)
|
||||
{
|
||||
record = await qController.get(tableName, props.id);
|
||||
setRecord(record);
|
||||
recordAnalytics({category: "tableEvents", action: props.isCopy ? "copy" : "edit", label: tableMetaData?.label + " / " + record?.recordLabel});
|
||||
|
||||
const titleVerb = props.isCopy ? "Copy" : "Edit";
|
||||
setFormTitle(`${titleVerb} ${tableMetaData?.label}: ${record?.recordLabel}`);
|
||||
|
||||
if (!props.isModal)
|
||||
const fieldArray = [] as QFieldMetaData[];
|
||||
const sortedKeys = [...tableMetaData.fields.keys()].sort();
|
||||
sortedKeys.forEach((key) =>
|
||||
{
|
||||
setPageHeader(`${titleVerb} ${tableMetaData?.label}: ${record?.recordLabel}`);
|
||||
}
|
||||
|
||||
tableMetaData.fields.forEach((fieldMetaData, key) =>
|
||||
{
|
||||
if (props.isCopy && fieldMetaData.name == tableMetaData.primaryKeyField)
|
||||
{
|
||||
return;
|
||||
}
|
||||
initialValues[key] = record.values.get(key);
|
||||
const fieldMetaData = tableMetaData.fields.get(key);
|
||||
fieldArray.push(fieldMetaData);
|
||||
});
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// these checks are only for updating records, if copying, it is actually an insert, which is checked after this block //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if (!props.isCopy)
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if doing an edit or copy, fetch the record and pre-populate the form values from it //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
let record: QRecord = null;
|
||||
let defaultDisplayValues = new Map<string, string>();
|
||||
if (props.id !== null)
|
||||
{
|
||||
record = await qController.get(tableName, props.id);
|
||||
setRecord(record);
|
||||
recordAnalytics({category: "tableEvents", action: props.isCopy ? "copy" : "edit", label: tableMetaData?.label + " / " + record?.recordLabel});
|
||||
|
||||
const titleVerb = props.isCopy ? "Copy" : "Edit";
|
||||
setFormTitle(`${titleVerb} ${tableMetaData?.label}: ${record?.recordLabel}`);
|
||||
|
||||
if (!props.isModal)
|
||||
{
|
||||
setPageHeader(`${titleVerb} ${tableMetaData?.label}: ${record?.recordLabel}`);
|
||||
}
|
||||
|
||||
tableMetaData.fields.forEach((fieldMetaData, key) =>
|
||||
{
|
||||
if (props.isCopy && fieldMetaData.name == tableMetaData.primaryKeyField)
|
||||
{
|
||||
return;
|
||||
}
|
||||
initialValues[key] = record.values.get(key);
|
||||
});
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// these checks are only for updating records, if copying, it is actually an insert, which is checked after this block //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if (!props.isCopy)
|
||||
{
|
||||
if (!tableMetaData.capabilities.has(Capability.TABLE_UPDATE))
|
||||
{
|
||||
setNotAllowedError("Records may not be edited in this table");
|
||||
}
|
||||
else if (!tableMetaData.editPermission)
|
||||
{
|
||||
setNotAllowedError(`You do not have permission to edit ${tableMetaData.label} records`);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
///////////////////////////////////////////
|
||||
// else handle preparing to do an insert //
|
||||
///////////////////////////////////////////
|
||||
setFormTitle(`Creating New ${tableMetaData?.label}`);
|
||||
recordAnalytics({category: "tableEvents", action: "new", label: tableMetaData?.label});
|
||||
|
||||
if (!props.isModal)
|
||||
{
|
||||
setPageHeader(`Creating New ${tableMetaData?.label}`);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if default values were supplied for a new record, then populate initialValues, for formik. //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
for (let i = 0; i < fieldArray.length; i++)
|
||||
{
|
||||
const fieldMetaData = fieldArray[i];
|
||||
const fieldName = fieldMetaData.name;
|
||||
const defaultValue = (defaultValues && defaultValues[fieldName]) ? defaultValues[fieldName] : fieldMetaData.defaultValue;
|
||||
if (defaultValue)
|
||||
{
|
||||
initialValues[fieldName] = defaultValue;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// we need to set the initialDisplayValue for possible value fields with a default value //
|
||||
// so, look them up here now if needed //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
if (fieldMetaData.possibleValueSourceName)
|
||||
{
|
||||
const results: QPossibleValue[] = await qController.possibleValues(tableName, null, fieldName, null, [initialValues[fieldName]]);
|
||||
if (results && results.length > 0)
|
||||
{
|
||||
defaultDisplayValues.set(fieldName, results[0].label);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////
|
||||
// if an override heading was passed in, use it. //
|
||||
///////////////////////////////////////////////////
|
||||
if (props.overrideHeading)
|
||||
{
|
||||
setFormTitle(props.overrideHeading);
|
||||
if (!props.isModal)
|
||||
{
|
||||
setPageHeader(props.overrideHeading);
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////
|
||||
// check capabilities & permissions //
|
||||
//////////////////////////////////////
|
||||
if (props.isCopy || !props.id)
|
||||
{
|
||||
if (!tableMetaData.capabilities.has(Capability.TABLE_INSERT))
|
||||
{
|
||||
setNotAllowedError("Records may not be created in this table");
|
||||
}
|
||||
else if (!tableMetaData.insertPermission)
|
||||
{
|
||||
setNotAllowedError(`You do not have permission to create ${tableMetaData.label} records`);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!tableMetaData.capabilities.has(Capability.TABLE_UPDATE))
|
||||
{
|
||||
@ -556,201 +648,123 @@ function EntityForm(props: Props): JSX.Element
|
||||
setNotAllowedError(`You do not have permission to edit ${tableMetaData.label} records`);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
///////////////////////////////////////////
|
||||
// else handle preparing to do an insert //
|
||||
///////////////////////////////////////////
|
||||
setFormTitle(`Creating New ${tableMetaData?.label}`);
|
||||
recordAnalytics({category: "tableEvents", action: "new", label: tableMetaData?.label});
|
||||
|
||||
if (!props.isModal)
|
||||
{
|
||||
setPageHeader(`Creating New ${tableMetaData?.label}`);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if default values were supplied for a new record, then populate initialValues, for formik. //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// make sure all initialValues are properly formatted for the form //
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
for (let i = 0; i < fieldArray.length; i++)
|
||||
{
|
||||
const fieldMetaData = fieldArray[i];
|
||||
const fieldName = fieldMetaData.name;
|
||||
const defaultValue = (defaultValues && defaultValues[fieldName]) ? defaultValues[fieldName] : fieldMetaData.defaultValue;
|
||||
if (defaultValue)
|
||||
if (fieldMetaData.type == QFieldType.DATE_TIME && initialValues[fieldMetaData.name])
|
||||
{
|
||||
initialValues[fieldName] = defaultValue;
|
||||
initialValues[fieldMetaData.name] = ValueUtils.formatDateTimeValueForForm(initialValues[fieldMetaData.name]);
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// we need to set the initialDisplayValue for possible value fields with a default value //
|
||||
// so, look them up here now if needed //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
if (fieldMetaData.possibleValueSourceName)
|
||||
setInitialValues(initialValues);
|
||||
|
||||
/////////////////////////////////////////////////////////
|
||||
// get formField and formValidation objects for Formik //
|
||||
/////////////////////////////////////////////////////////
|
||||
const {
|
||||
dynamicFormFields,
|
||||
formValidations,
|
||||
} = DynamicFormUtils.getFormData(fieldArray, disabledFields);
|
||||
DynamicFormUtils.addPossibleValueProps(dynamicFormFields, fieldArray, tableName, null, record ? record.displayValues : defaultDisplayValues);
|
||||
|
||||
/////////////////////////////////////
|
||||
// group the formFields by section //
|
||||
/////////////////////////////////////
|
||||
const dynamicFormFieldsBySection = new Map<string, any>();
|
||||
let t1sectionName;
|
||||
let t1section;
|
||||
const nonT1Sections: QTableSection[] = [];
|
||||
const newRenderedWidgetSections: { [name: string]: JSX.Element } = {};
|
||||
const newChildListWidgetData: { [name: string]: ChildRecordListData } = {};
|
||||
|
||||
for (let i = 0; i < tableSections.length; i++)
|
||||
{
|
||||
const section = tableSections[i];
|
||||
const sectionDynamicFormFields: any[] = [];
|
||||
|
||||
if (section.isHidden)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const hasFields = section.fieldNames && section.fieldNames.length > 0;
|
||||
if (hasFields)
|
||||
{
|
||||
for (let j = 0; j < section.fieldNames.length; j++)
|
||||
{
|
||||
const results: QPossibleValue[] = await qController.possibleValues(tableName, null, fieldName, null, [initialValues[fieldName]]);
|
||||
if (results && results.length > 0)
|
||||
const fieldName = section.fieldNames[j];
|
||||
const field = tableMetaData.fields.get(fieldName);
|
||||
|
||||
if (!field)
|
||||
{
|
||||
defaultDisplayValues.set(fieldName, results[0].label);
|
||||
console.log(`Omitting un-found field ${fieldName} from form`);
|
||||
continue;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if id !== null (and we're not copying) - means we're on the edit screen -- show all fields on the edit screen. //
|
||||
// || (or) we're on the insert screen in which case, only show editable fields. //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if ((props.id !== null && !props.isCopy) || field.isEditable)
|
||||
{
|
||||
sectionDynamicFormFields.push(dynamicFormFields[fieldName]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////
|
||||
// if an override heading was passed in, use it. //
|
||||
///////////////////////////////////////////////////
|
||||
if (props.overrideHeading)
|
||||
{
|
||||
setFormTitle(props.overrideHeading);
|
||||
if (!props.isModal)
|
||||
{
|
||||
setPageHeader(props.overrideHeading);
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////
|
||||
// check capabilities & permissions //
|
||||
//////////////////////////////////////
|
||||
if (props.isCopy || !props.id)
|
||||
{
|
||||
if (!tableMetaData.capabilities.has(Capability.TABLE_INSERT))
|
||||
{
|
||||
setNotAllowedError("Records may not be created in this table");
|
||||
}
|
||||
else if (!tableMetaData.insertPermission)
|
||||
{
|
||||
setNotAllowedError(`You do not have permission to create ${tableMetaData.label} records`);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!tableMetaData.capabilities.has(Capability.TABLE_UPDATE))
|
||||
{
|
||||
setNotAllowedError("Records may not be edited in this table");
|
||||
}
|
||||
else if (!tableMetaData.editPermission)
|
||||
{
|
||||
setNotAllowedError(`You do not have permission to edit ${tableMetaData.label} records`);
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// make sure all initialValues are properly formatted for the form //
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
for (let i = 0; i < fieldArray.length; i++)
|
||||
{
|
||||
const fieldMetaData = fieldArray[i];
|
||||
if (fieldMetaData.type == QFieldType.DATE_TIME && initialValues[fieldMetaData.name])
|
||||
{
|
||||
initialValues[fieldMetaData.name] = ValueUtils.formatDateTimeValueForForm(initialValues[fieldMetaData.name]);
|
||||
}
|
||||
}
|
||||
|
||||
setInitialValues(initialValues);
|
||||
|
||||
/////////////////////////////////////////////////////////
|
||||
// get formField and formValidation objects for Formik //
|
||||
/////////////////////////////////////////////////////////
|
||||
const {
|
||||
dynamicFormFields,
|
||||
formValidations,
|
||||
} = DynamicFormUtils.getFormData(fieldArray, disabledFields);
|
||||
DynamicFormUtils.addPossibleValueProps(dynamicFormFields, fieldArray, tableName, null, record ? record.displayValues : defaultDisplayValues);
|
||||
|
||||
/////////////////////////////////////
|
||||
// group the formFields by section //
|
||||
/////////////////////////////////////
|
||||
const dynamicFormFieldsBySection = new Map<string, any>();
|
||||
let t1sectionName;
|
||||
let t1section;
|
||||
const nonT1Sections: QTableSection[] = [];
|
||||
const newRenderedWidgetSections: { [name: string]: JSX.Element } = {};
|
||||
const newChildListWidgetData: { [name: string]: ChildRecordListData } = {};
|
||||
|
||||
for (let i = 0; i < tableSections.length; i++)
|
||||
{
|
||||
const section = tableSections[i];
|
||||
const sectionDynamicFormFields: any[] = [];
|
||||
|
||||
if (section.isHidden)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const hasFields = section.fieldNames && section.fieldNames.length > 0;
|
||||
if(hasFields)
|
||||
{
|
||||
for (let j = 0; j < section.fieldNames.length; j++)
|
||||
{
|
||||
const fieldName = section.fieldNames[j];
|
||||
const field = tableMetaData.fields.get(fieldName);
|
||||
|
||||
if (!field)
|
||||
if (sectionDynamicFormFields.length === 0)
|
||||
{
|
||||
console.log(`Omitting un-found field ${fieldName} from form`);
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// in case there are no active fields in this section, remove it from the tableSections array //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
tableSections.splice(i, 1);
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if id !== null (and we're not copying) - means we're on the edit screen -- show all fields on the edit screen. //
|
||||
// || (or) we're on the insert screen in which case, only show editable fields. //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if ((props.id !== null && !props.isCopy) || field.isEditable)
|
||||
else
|
||||
{
|
||||
sectionDynamicFormFields.push(dynamicFormFields[fieldName]);
|
||||
dynamicFormFieldsBySection.set(section.name, sectionDynamicFormFields);
|
||||
}
|
||||
}
|
||||
|
||||
if (sectionDynamicFormFields.length === 0)
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// in case there are no active fields in this section, remove it from the tableSections array //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
tableSections.splice(i, 1);
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
dynamicFormFieldsBySection.set(section.name, sectionDynamicFormFields);
|
||||
const widgetMetaData = metaData?.widgets.get(section.widgetName);
|
||||
const widgetData = await qController.widget(widgetMetaData.name, makeQueryStringWithIdAndObject(tableMetaData, defaultValues));
|
||||
|
||||
newRenderedWidgetSections[section.widgetName] = getWidgetSection(widgetMetaData, widgetData);
|
||||
newChildListWidgetData[section.widgetName] = widgetData;
|
||||
}
|
||||
|
||||
//////////////////////////////////////
|
||||
// capture the tier1 section's name //
|
||||
//////////////////////////////////////
|
||||
if (section.tier === "T1")
|
||||
{
|
||||
t1sectionName = section.name;
|
||||
t1section = section;
|
||||
}
|
||||
else
|
||||
{
|
||||
nonT1Sections.push(section);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const widgetMetaData = metaData.widgets.get(section.widgetName);
|
||||
const widgetData = await qController.widget(widgetMetaData.name, makeQueryStringWithIdAndObject(tableMetaData, defaultValues));
|
||||
|
||||
newRenderedWidgetSections[section.widgetName] = getWidgetSection(widgetMetaData, widgetData);
|
||||
newChildListWidgetData[section.widgetName] = widgetData;
|
||||
}
|
||||
setT1SectionName(t1sectionName);
|
||||
setT1Section(t1section);
|
||||
setNonT1Sections(nonT1Sections);
|
||||
setFormFields(dynamicFormFieldsBySection);
|
||||
setValidations(Yup.object().shape(formValidations));
|
||||
setRenderedWidgetSections(newRenderedWidgetSections);
|
||||
setChildListWidgetData(newChildListWidgetData);
|
||||
|
||||
//////////////////////////////////////
|
||||
// capture the tier1 section's name //
|
||||
//////////////////////////////////////
|
||||
if (section.tier === "T1")
|
||||
{
|
||||
t1sectionName = section.name;
|
||||
t1section = section;
|
||||
}
|
||||
else
|
||||
{
|
||||
nonT1Sections.push(section);
|
||||
}
|
||||
}
|
||||
|
||||
setT1SectionName(t1sectionName);
|
||||
setT1Section(t1section);
|
||||
setNonT1Sections(nonT1Sections);
|
||||
setFormFields(dynamicFormFieldsBySection);
|
||||
setValidations(Yup.object().shape(formValidations));
|
||||
setRenderedWidgetSections(newRenderedWidgetSections);
|
||||
setChildListWidgetData(newChildListWidgetData);
|
||||
|
||||
forceUpdate();
|
||||
})();
|
||||
}
|
||||
forceUpdate();
|
||||
})();
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
@ -870,16 +884,28 @@ function EntityForm(props: Props): JSX.Element
|
||||
let haveAssociationsToPost = false;
|
||||
for (let name of Object.keys(childListWidgetData))
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if cannot find association name, continue loop, since cannot tell backend which association this is for //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
const manageAssociationName = metaData.widgets.get(name)?.defaultValues?.get("manageAssociationName");
|
||||
if (!manageAssociationName)
|
||||
{
|
||||
console.log(`Cannot send association data to backend - missing a manageAssociationName defaultValue in widget meta data for widget name ${name}`);
|
||||
continue;
|
||||
}
|
||||
associationsToPost[manageAssociationName] = [];
|
||||
haveAssociationsToPost = true;
|
||||
for (let i = 0; i < childListWidgetData[name].queryOutput?.records?.length; i++)
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if the records array exists, add to associations to post - note: even if empty list, the backend will expect this //
|
||||
// association name to be present if it is to act on it (for the case when all associations have been deleted) //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if (childListWidgetData[name].queryOutput.records)
|
||||
{
|
||||
associationsToPost[manageAssociationName].push(childListWidgetData[name].queryOutput.records[i].values);
|
||||
associationsToPost[manageAssociationName] = [];
|
||||
haveAssociationsToPost = true;
|
||||
for (let i = 0; i < childListWidgetData[name].queryOutput?.records?.length; i++)
|
||||
{
|
||||
associationsToPost[manageAssociationName].push(childListWidgetData[name].queryOutput.records[i].values);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (haveAssociationsToPost)
|
||||
@ -1000,19 +1026,19 @@ function EntityForm(props: Props): JSX.Element
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
function makeQueryStringWithIdAndObject(tableMetaData: QTableMetaData, object: {[key: string]: any})
|
||||
function makeQueryStringWithIdAndObject(tableMetaData: QTableMetaData, object: { [key: string]: any })
|
||||
{
|
||||
const queryParamsArray: string[] = [];
|
||||
if(props.id)
|
||||
if (props.id)
|
||||
{
|
||||
queryParamsArray.push(`${tableMetaData.primaryKeyField}=${encodeURIComponent(props.id)}`)
|
||||
queryParamsArray.push(`${tableMetaData.primaryKeyField}=${encodeURIComponent(props.id)}`);
|
||||
}
|
||||
|
||||
if(object)
|
||||
if (object)
|
||||
{
|
||||
for (let key in object)
|
||||
{
|
||||
queryParamsArray.push(`${key}=${encodeURIComponent(object[key])}`)
|
||||
queryParamsArray.push(`${key}=${encodeURIComponent(object[key])}`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1023,7 +1049,7 @@ function EntityForm(props: Props): JSX.Element
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
async function reloadWidget(widgetName: string, additionalQueryParamsForWidget: {[key: string]: any })
|
||||
async function reloadWidget(widgetName: string, additionalQueryParamsForWidget: { [key: string]: any })
|
||||
{
|
||||
const widgetData = await qController.widget(widgetName, makeQueryStringWithIdAndObject(tableMetaData, additionalQueryParamsForWidget));
|
||||
const widgetMetaData = metaData.widgets.get(widgetName);
|
||||
@ -1045,11 +1071,11 @@ function EntityForm(props: Props): JSX.Element
|
||||
/*******************************************************************************
|
||||
** process a form-field having a changed value (e.g., apply field rules).
|
||||
*******************************************************************************/
|
||||
function handleChangedFieldValue(fieldName: string, oldValue: any, newValue: any, valueChangesToMake: {[fieldName: string]: any})
|
||||
function handleChangedFieldValue(fieldName: string, oldValue: any, newValue: any, valueChangesToMake: { [fieldName: string]: any })
|
||||
{
|
||||
for (let fieldRule of fieldRules)
|
||||
{
|
||||
if(fieldRule.trigger == FieldRuleTrigger.ON_CHANGE && fieldRule.sourceField == fieldName)
|
||||
if (fieldRule.trigger == FieldRuleTrigger.ON_CHANGE && fieldRule.sourceField == fieldName)
|
||||
{
|
||||
switch (fieldRule.action)
|
||||
{
|
||||
@ -1058,7 +1084,7 @@ function EntityForm(props: Props): JSX.Element
|
||||
valueChangesToMake[fieldRule.targetField] = null;
|
||||
break;
|
||||
case FieldRuleAction.RELOAD_WIDGET:
|
||||
const additionalQueryParamsForWidget: {[key: string]: any} = {};
|
||||
const additionalQueryParamsForWidget: { [key: string]: any } = {};
|
||||
additionalQueryParamsForWidget[fieldRule.sourceField] = newValue;
|
||||
reloadWidget(fieldRule.targetWidget, additionalQueryParamsForWidget);
|
||||
}
|
||||
@ -1148,21 +1174,21 @@ function EntityForm(props: Props): JSX.Element
|
||||
/////////////////////////////////////////////////
|
||||
// if we have values from formik, look at them //
|
||||
/////////////////////////////////////////////////
|
||||
if(values)
|
||||
if (values)
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// use stringified values as cheap/easy way to see if any are changed //
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
const newFormValuesJSON = JSON.stringify(values);
|
||||
if(formValuesJSON != newFormValuesJSON)
|
||||
if (formValuesJSON != newFormValuesJSON)
|
||||
{
|
||||
const valueChangesToMake: {[fieldName: string]: any} = {};
|
||||
const valueChangesToMake: { [fieldName: string]: any } = {};
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// if the form is dirty (e.g., we're not doing the initial load), //
|
||||
// then process rules for any changed fields //
|
||||
////////////////////////////////////////////////////////////////////
|
||||
if(dirty)
|
||||
if (dirty)
|
||||
{
|
||||
for (let fieldName in values)
|
||||
{
|
||||
@ -1194,7 +1220,7 @@ function EntityForm(props: Props): JSX.Element
|
||||
setFieldValue(fieldName, valueChangesToMake[fieldName], false);
|
||||
}
|
||||
|
||||
setFormValues(formValues)
|
||||
setFormValues(formValues);
|
||||
setFormValuesJSON(JSON.stringify(values));
|
||||
}
|
||||
}
|
||||
|
@ -358,7 +358,7 @@ export function GotoRecordButton(props: GotoRecordButtonProps): JSX.Element
|
||||
return (
|
||||
<React.Fragment>
|
||||
{
|
||||
props.buttonVisible && hasGotoFieldNames(props.tableMetaData) && <Button onClick={openGoto}>Go To...</Button>
|
||||
props.buttonVisible && hasGotoFieldNames(props.tableMetaData) && <Button onClick={openGoto} sx={{whiteSpace: "nowrap"}}>Go To...</Button>
|
||||
}
|
||||
<GotoRecordDialog metaData={props.metaData} tableMetaData={props.tableMetaData} tableVariant={props.tableVariant} isOpen={gotoIsOpen} closeHandler={closeGoto} mayClose={props.mayClose} subHeader={props.subHeader} />
|
||||
</React.Fragment>
|
||||
|
@ -22,7 +22,7 @@
|
||||
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||
import {QTableSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableSection";
|
||||
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
|
||||
import Box from "@mui/material/Box";
|
||||
import {Box} from "@mui/material";
|
||||
import Card from "@mui/material/Card";
|
||||
import Icon from "@mui/material/Icon";
|
||||
import {Theme} from "@mui/material/styles";
|
||||
@ -76,12 +76,12 @@ function QRecordSidebar({tableSections, widgetMetaDataList, light, stickyTop}: P
|
||||
|
||||
|
||||
return (
|
||||
<Card sx={{borderRadius: "0.75rem", position: "sticky", top: stickyTop, overflow: "auto", maxHeight: "calc(100vh - 2rem)"}}>
|
||||
<Box component="ul" display="flex" flexDirection="column" p={2} m={0} sx={{listStyle: "none"}}>
|
||||
<Card sx={{borderRadius: "0.75rem", position: "sticky", top: stickyTop, overflow: "hidden", maxHeight: "calc(100vh - 2rem)"}}>
|
||||
<Box component="ul" display="flex" flexDirection="column" p={2} m={0} sx={{listStyle: "none", overflow: "auto", height: "100%"}}>
|
||||
{
|
||||
sidebarEntries ? sidebarEntries.map((entry: SidebarEntry, key: number) => (
|
||||
|
||||
<HashLink key={`section-link-${entry.name}`} to={`#${entry.name}`}>
|
||||
<Box key={`section-link-${entry.name}`} onClick={() => document.getElementById(entry.name).scrollIntoView()} sx={{cursor: "pointer"}}>
|
||||
<Box key={`section-${entry.name}`} component="li" pt={key === 0 ? 0 : 1}>
|
||||
<MDTypography
|
||||
variant="button"
|
||||
@ -112,7 +112,7 @@ function QRecordSidebar({tableSections, widgetMetaDataList, light, stickyTop}: P
|
||||
|
||||
</MDTypography>
|
||||
</Box>
|
||||
</HashLink>
|
||||
</Box>
|
||||
)) : null
|
||||
}
|
||||
</Box>
|
||||
|
@ -25,7 +25,8 @@ import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QT
|
||||
import {QJobComplete} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobComplete";
|
||||
import {QJobError} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobError";
|
||||
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
|
||||
import {Alert, Box, Button} from "@mui/material";
|
||||
import {Alert, Button} from "@mui/material";
|
||||
import Box from "@mui/material/Box";
|
||||
import Dialog from "@mui/material/Dialog";
|
||||
import DialogActions from "@mui/material/DialogActions";
|
||||
import DialogContent from "@mui/material/DialogContent";
|
||||
@ -94,12 +95,12 @@ function SavedViews({qController, metaData, tableMetaData, currentSavedView, tab
|
||||
|
||||
const {accentColor, accentColorLight, userId: currentUserId} = useContext(QContext);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// this component is used by <RecordQuery> - but that component has different usages - //
|
||||
// e.g., the full-fledged query screen, but also, within other screens (e.g., a modal //
|
||||
// under the ReportSetupWidget). So, there are some behaviors we only want when we're //
|
||||
// on the full-fledged query screen, such as changing the URL with saved view ids. //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// this component is used by <RecordQuery> - but that component has different usages - //
|
||||
// e.g., the full-fledged query screen, but also, within other screens (e.g., a modal //
|
||||
// under the FilterAndColumnsSetupWidget). So, there are some behaviors we only want when //
|
||||
// we're on the full-fledged query screen, such as changing the URL with saved view ids. //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
const isQueryScreen = queryScreenUsage == "queryScreen";
|
||||
|
||||
const openSavedViewsMenu = (event: any) => setSavedViewsMenu(event.currentTarget);
|
||||
|
@ -22,16 +22,16 @@
|
||||
|
||||
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
|
||||
import {Box, Skeleton} from "@mui/material";
|
||||
import React from "react";
|
||||
import {BlockData} from "qqq/components/widgets/blocks/BlockModels";
|
||||
import WidgetBlock from "qqq/components/widgets/WidgetBlock";
|
||||
import React from "react";
|
||||
|
||||
|
||||
interface CompositeData
|
||||
{
|
||||
blocks: BlockData[];
|
||||
styleOverrides?: any;
|
||||
layout?: string
|
||||
layout?: string;
|
||||
}
|
||||
|
||||
|
||||
@ -57,7 +57,14 @@ export default function CompositeWidget({widgetMetaData, data}: CompositeWidgetP
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
let layout = data?.layout;
|
||||
let boxStyle: any = {};
|
||||
if (layout == "FLEX_ROW_WRAPPED")
|
||||
if (layout == "FLEX_COLUMN")
|
||||
{
|
||||
boxStyle.display = "flex";
|
||||
boxStyle.flexDirection = "column";
|
||||
boxStyle.flexWrap = "wrap";
|
||||
boxStyle.gap = "0.5rem";
|
||||
}
|
||||
else if (layout == "FLEX_ROW_WRAPPED")
|
||||
{
|
||||
boxStyle.display = "flex";
|
||||
boxStyle.flexDirection = "row";
|
||||
@ -68,7 +75,7 @@ export default function CompositeWidget({widgetMetaData, data}: CompositeWidgetP
|
||||
{
|
||||
boxStyle.display = "flex";
|
||||
boxStyle.flexDirection = "row";
|
||||
boxStyle.justifyContent = "space-between"
|
||||
boxStyle.justifyContent = "space-between";
|
||||
boxStyle.gap = "0.25rem";
|
||||
}
|
||||
else if (layout == "TABLE_SUB_ROW_DETAILS")
|
||||
|
@ -40,17 +40,17 @@ import DataBagViewer from "qqq/components/widgets/misc/DataBagViewer";
|
||||
import DividerWidget from "qqq/components/widgets/misc/Divider";
|
||||
import DynamicFormWidget from "qqq/components/widgets/misc/DynamicFormWidget";
|
||||
import FieldValueListWidget from "qqq/components/widgets/misc/FieldValueListWidget";
|
||||
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 ReportSetupWidget from "qqq/components/widgets/misc/ReportSetupWidget";
|
||||
import ScriptViewer from "qqq/components/widgets/misc/ScriptViewer";
|
||||
import StepperCard from "qqq/components/widgets/misc/StepperCard";
|
||||
import USMapWidget from "qqq/components/widgets/misc/USMapWidget";
|
||||
import ParentWidget from "qqq/components/widgets/ParentWidget";
|
||||
import MultiStatisticsCard from "qqq/components/widgets/statistics/MultiStatisticsCard";
|
||||
import StatisticsCard from "qqq/components/widgets/statistics/StatisticsCard";
|
||||
import Widget, {HeaderIcon, LabelComponent, WIDGET_DROPDOWN_SELECTION_LOCAL_STORAGE_KEY_ROOT} from "qqq/components/widgets/Widget";
|
||||
import Widget, {HeaderIcon, LabelComponent, WIDGET_DROPDOWN_SELECTION_LOCAL_STORAGE_KEY_ROOT, WidgetData} from "qqq/components/widgets/Widget";
|
||||
import WidgetBlock from "qqq/components/widgets/WidgetBlock";
|
||||
import ProcessRun from "qqq/pages/processes/ProcessRun";
|
||||
import Client from "qqq/utils/qqq/Client";
|
||||
@ -258,11 +258,11 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco
|
||||
** helper function, to convert values from a QRecord values map to a regular old
|
||||
** js object
|
||||
*******************************************************************************/
|
||||
function convertQRecordValuesFromMapToObject(record: QRecord): {[name: string]: any}
|
||||
function convertQRecordValuesFromMapToObject(record: QRecord): { [name: string]: any }
|
||||
{
|
||||
const rs: {[name: string]: any} = {};
|
||||
const rs: { [name: string]: any } = {};
|
||||
|
||||
if(record && record.values)
|
||||
if (record && record.values)
|
||||
{
|
||||
record.values.forEach((value, key) => rs[key] = value);
|
||||
}
|
||||
@ -293,7 +293,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco
|
||||
}
|
||||
|
||||
return (
|
||||
<Box key={`${widgetMetaData.name}-${i}`} sx={{alignItems: "stretch", flexGrow: 1, display: "flex", marginTop: "0px", paddingTop: "0px", width: "100%", height: "100%"}}>
|
||||
<Box key={`${widgetMetaData.name}-${i}`} sx={{alignItems: "stretch", flexGrow: 1, display: "flex", marginTop: "0px", paddingTop: "0px", width: "100%", height: "100%", flexDirection: widgetMetaData.type == "multiTable" ? "column" : "row"}}>
|
||||
{
|
||||
haveLoadedParams && widgetMetaData.type === "parentWidget" && (
|
||||
<ParentWidget
|
||||
@ -343,6 +343,20 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
widgetMetaData.type === "multiTable" && (
|
||||
widgetData[i]?.tableDataList?.map((tableData: WidgetData, index: number) =>
|
||||
<Box pb={3} key={`${widgetMetaData.type}-${index}`}>
|
||||
<TableWidget
|
||||
widgetMetaData={widgetMetaData}
|
||||
widgetData={tableData}
|
||||
reloadWidgetCallback={(data) => reloadWidget(i, data)}
|
||||
isChild={areChildren}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
)
|
||||
}
|
||||
{
|
||||
widgetMetaData.type === "stackedBarChart" && (
|
||||
<Widget
|
||||
@ -584,17 +598,19 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco
|
||||
)
|
||||
}
|
||||
{
|
||||
widgetMetaData.type === "reportSetup" && (
|
||||
widgetMetaData.type === "filterAndColumnsSetup" && (
|
||||
widgetData && widgetData[i] && widgetData[i].queryParams &&
|
||||
<ReportSetupWidget isEditable={false} widgetMetaData={widgetMetaData} recordValues={convertQRecordValuesFromMapToObject(record)} onSaveCallback={() =>
|
||||
{}} />
|
||||
<FilterAndColumnsSetupWidget isEditable={false} widgetMetaData={widgetMetaData} recordValues={convertQRecordValuesFromMapToObject(record)} onSaveCallback={() =>
|
||||
{
|
||||
}} />
|
||||
)
|
||||
}
|
||||
{
|
||||
widgetMetaData.type === "pivotTableSetup" && (
|
||||
widgetData && widgetData[i] && widgetData[i].queryParams &&
|
||||
<PivotTableSetupWidget isEditable={false} widgetMetaData={widgetMetaData} recordValues={convertQRecordValuesFromMapToObject(record)} onSaveCallback={() =>
|
||||
{}} />
|
||||
{
|
||||
}} />
|
||||
)
|
||||
}
|
||||
{
|
||||
|
@ -21,14 +21,16 @@
|
||||
|
||||
|
||||
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
|
||||
import Box from "@mui/material/Box";
|
||||
import Button from "@mui/material/Button";
|
||||
import Icon from "@mui/material/Icon";
|
||||
import Tooltip from "@mui/material/Tooltip/Tooltip";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import React from "react";
|
||||
import colors from "qqq/assets/theme/base/colors";
|
||||
import {WidgetData} from "qqq/components/widgets/Widget";
|
||||
import ValueUtils from "qqq/utils/qqq/ValueUtils";
|
||||
import React from "react";
|
||||
import {Link} from "react-router-dom";
|
||||
|
||||
/*******************************************************************************
|
||||
** Utility class used by Widgets
|
||||
@ -51,6 +53,17 @@ export class WidgetUtils
|
||||
};
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static generateLabelLink = (linkText: string, linkURL: string): JSX.Element =>
|
||||
{
|
||||
return (<Box key={1} fontSize="1rem" pl={1} display="inline" position="relative">
|
||||
(<Link to={linkURL}>{linkText}</Link>)
|
||||
</Box>);
|
||||
};
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -97,4 +110,4 @@ export class WidgetUtils
|
||||
return (fileName);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ export default function NumberIconBadgeBlock({widgetMetaData, data}: StandardBlo
|
||||
{
|
||||
data.values.iconName &&
|
||||
<BlockElementWrapper metaData={widgetMetaData} data={data} slot="icon">
|
||||
<Icon style={{color: data.styles.color, fontSize: "1rem", position: "relative", top: "3px"}}>{data.values.iconName}</Icon>
|
||||
<Icon style={{color: data.styles.color, fontSize: "1rem", marginLeft: "2px", position: "relative", top: "4px"}}>{data.values.iconName}</Icon>
|
||||
</BlockElementWrapper>
|
||||
}
|
||||
</div>);
|
||||
|
@ -22,6 +22,8 @@
|
||||
|
||||
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
|
||||
import {QCriteriaOperator} from "@kingsrook/qqq-frontend-core/lib/model/query/QCriteriaOperator";
|
||||
import {QFilterCriteria} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterCriteria";
|
||||
import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter";
|
||||
import {Alert, Collapse} from "@mui/material";
|
||||
import Box from "@mui/material/Box";
|
||||
@ -42,7 +44,7 @@ import Client from "qqq/utils/qqq/Client";
|
||||
import FilterUtils from "qqq/utils/qqq/FilterUtils";
|
||||
import React, {useContext, useEffect, useRef, useState} from "react";
|
||||
|
||||
interface ReportSetupWidgetProps
|
||||
interface FilterAndColumnsSetupWidgetProps
|
||||
{
|
||||
isEditable: boolean;
|
||||
widgetMetaData: QWidgetMetaData;
|
||||
@ -50,7 +52,7 @@ interface ReportSetupWidgetProps
|
||||
onSaveCallback?: (values: { [name: string]: any }) => void;
|
||||
}
|
||||
|
||||
ReportSetupWidget.defaultProps = {
|
||||
FilterAndColumnsSetupWidget.defaultProps = {
|
||||
onSaveCallback: null
|
||||
};
|
||||
|
||||
@ -80,9 +82,10 @@ const qController = Client.getInstance();
|
||||
/*******************************************************************************
|
||||
** Component for editing the main setup of a report - that is: filter & columns
|
||||
*******************************************************************************/
|
||||
export default function ReportSetupWidget({isEditable, widgetMetaData, recordValues, onSaveCallback}: ReportSetupWidgetProps): JSX.Element
|
||||
export default function FilterAndColumnsSetupWidget({isEditable, widgetMetaData, recordValues, onSaveCallback}: FilterAndColumnsSetupWidgetProps): JSX.Element
|
||||
{
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [hideColumns, setHideColumns] = useState(widgetMetaData?.defaultValues?.has("hideColumns") && widgetMetaData?.defaultValues?.get("hideColumns"));
|
||||
const [tableMetaData, setTableMetaData] = useState(null as QTableMetaData);
|
||||
|
||||
const [alertContent, setAlertContent] = useState(null as string);
|
||||
@ -101,15 +104,42 @@ export default function ReportSetupWidget({isEditable, widgetMetaData, recordVal
|
||||
/////////////////////////////
|
||||
// load values from record //
|
||||
/////////////////////////////
|
||||
let queryFilter = recordValues["queryFilterJson"] && JSON.parse(recordValues["queryFilterJson"]) as QQueryFilter;
|
||||
let columns: QQueryColumns = null;
|
||||
let usingDefaultEmptyFilter = false;
|
||||
let queryFilter = recordValues["queryFilterJson"] && JSON.parse(recordValues["queryFilterJson"]) as QQueryFilter;
|
||||
const defaultFilterFields = getDefaultFilterFieldNames(widgetMetaData);
|
||||
if (!queryFilter)
|
||||
{
|
||||
queryFilter = new QQueryFilter();
|
||||
usingDefaultEmptyFilter = true;
|
||||
if (defaultFilterFields?.length == 0)
|
||||
{
|
||||
usingDefaultEmptyFilter = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
queryFilter = Object.assign(new QQueryFilter(), queryFilter);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if there are default fields from which a query should be seeded, add/update the filter with them //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if (defaultFilterFields?.length > 0)
|
||||
{
|
||||
defaultFilterFields.forEach((fieldName: string) =>
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if a value for the default field exists, remove the criteria for it in our query first //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
queryFilter.criteria = queryFilter.criteria?.filter(c => c.fieldName != fieldName);
|
||||
|
||||
if (recordValues[fieldName])
|
||||
{
|
||||
queryFilter.addCriteria(new QFilterCriteria(fieldName, QCriteriaOperator.EQUALS, [recordValues[fieldName]]));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let columns: QQueryColumns = null;
|
||||
if (recordValues["columnsJson"])
|
||||
{
|
||||
columns = QQueryColumns.buildFromJSON(recordValues["columnsJson"]);
|
||||
@ -120,11 +150,20 @@ export default function ReportSetupWidget({isEditable, widgetMetaData, recordVal
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
useEffect(() =>
|
||||
{
|
||||
if (recordValues["tableName"] && (tableMetaData == null || tableMetaData.name != recordValues["tableName"]))
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if a default table name specified, use it, otherwise use it from the record values //
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
let tableName = widgetMetaData?.defaultValues?.get("tableName");
|
||||
if (!tableName && recordValues["tableName"] && (tableMetaData == null || tableMetaData.name != recordValues["tableName"]))
|
||||
{
|
||||
tableName = recordValues["tableName"];
|
||||
}
|
||||
|
||||
if (tableName)
|
||||
{
|
||||
(async () =>
|
||||
{
|
||||
const tableMetaData = await qController.loadTableMetaData(recordValues["tableName"]);
|
||||
const tableMetaData = await qController.loadTableMetaData(tableName);
|
||||
setTableMetaData(tableMetaData);
|
||||
|
||||
const queryFilterForFrontend = Object.assign({}, queryFilter);
|
||||
@ -132,7 +171,21 @@ export default function ReportSetupWidget({isEditable, widgetMetaData, recordVal
|
||||
setFrontendQueryFilter(queryFilterForFrontend);
|
||||
})();
|
||||
}
|
||||
}, [recordValues]);
|
||||
}, [JSON.stringify(recordValues)]);
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
function getDefaultFilterFieldNames(widgetMetaData: QWidgetMetaData)
|
||||
{
|
||||
if (widgetMetaData?.defaultValues?.has("filterDefaultFieldNames"))
|
||||
{
|
||||
return (widgetMetaData.defaultValues.get("filterDefaultFieldNames").split(","));
|
||||
}
|
||||
|
||||
return ([]);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -140,8 +193,27 @@ export default function ReportSetupWidget({isEditable, widgetMetaData, recordVal
|
||||
*******************************************************************************/
|
||||
function openEditor()
|
||||
{
|
||||
let missingRequiredFields = [] as string[];
|
||||
getDefaultFilterFieldNames(widgetMetaData)?.forEach((fieldName: string) =>
|
||||
{
|
||||
if (!recordValues[fieldName])
|
||||
{
|
||||
missingRequiredFields.push(tableMetaData.fields.get(fieldName).label);
|
||||
}
|
||||
});
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// display an alert and return if any required fields are missing //
|
||||
////////////////////////////////////////////////////////////////////
|
||||
if (missingRequiredFields.length > 0)
|
||||
{
|
||||
setAlertContent("The following fields must first be selected to edit the filter: '" + missingRequiredFields.join(", ") + "'");
|
||||
return;
|
||||
}
|
||||
|
||||
if (recordValues["tableName"])
|
||||
{
|
||||
setAlertContent(null);
|
||||
setModalOpen(true);
|
||||
}
|
||||
}
|
||||
@ -272,7 +344,14 @@ export default function ReportSetupWidget({isEditable, widgetMetaData, recordVal
|
||||
const labelAdditionalElementsRight: JSX.Element[] = [];
|
||||
if (isEditable)
|
||||
{
|
||||
labelAdditionalElementsRight.push(<HeaderLinkButtonComponent key="filterAndColumnsHeader" label="Edit Filters and Columns" onClickCallback={openEditor} disabled={tableMetaData == null} disabledTooltip={selectTableFirstTooltipTitle} />);
|
||||
if (!hideColumns)
|
||||
{
|
||||
labelAdditionalElementsRight.push(<HeaderLinkButtonComponent key="filterAndColumnsHeader" label="Edit Filters and Columns" onClickCallback={openEditor} disabled={tableMetaData == null} disabledTooltip={selectTableFirstTooltipTitle} />);
|
||||
}
|
||||
else
|
||||
{
|
||||
labelAdditionalElementsRight.push(<HeaderLinkButtonComponent key="filterAndColumnsHeader" label="Edit Filters" onClickCallback={openEditor} disabled={tableMetaData == null} disabledTooltip={selectTableFirstTooltipTitle} />);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -306,34 +385,36 @@ export default function ReportSetupWidget({isEditable, widgetMetaData, recordVal
|
||||
</Tooltip>
|
||||
}
|
||||
{
|
||||
!isEditable && <Box color={colors.gray.main}>Your report has no filters.</Box>
|
||||
!isEditable && <Box color={colors.gray.main}>No filters are configured.</Box>
|
||||
}
|
||||
</Box>
|
||||
}
|
||||
</Box>
|
||||
<Box pt="1rem">
|
||||
<h5>Columns</h5>
|
||||
<Box display="flex" flexWrap="wrap" fontSize="1rem">
|
||||
{
|
||||
mayShowColumnsPreview() &&
|
||||
columns.columns.map((column, i) => <React.Fragment key={`column-${i}`}>{renderColumn(column)}</React.Fragment>)
|
||||
}
|
||||
{
|
||||
!mayShowColumnsPreview() &&
|
||||
<Box width="100%" sx={{fontSize: "1rem", background: "#FFFFFF"}} minHeight={"2.375rem"} p={"0.5rem"} pb={"0.125rem"}>
|
||||
{
|
||||
isEditable &&
|
||||
<Tooltip title={selectTableFirstTooltipTitle}>
|
||||
<span><Button disabled={!recordValues["tableName"]} sx={unborderedButtonSX} onClick={openEditor}>+ Add Columns</Button></span>
|
||||
</Tooltip>
|
||||
}
|
||||
{
|
||||
!isEditable && <Box color={colors.gray.main}>Your report has no columns.</Box>
|
||||
}
|
||||
</Box>
|
||||
}
|
||||
{!hideColumns && (
|
||||
<Box pt="1rem">
|
||||
<h5>Columns</h5>
|
||||
<Box display="flex" flexWrap="wrap" fontSize="1rem">
|
||||
{
|
||||
mayShowColumnsPreview() &&
|
||||
columns.columns.map((column, i) => <React.Fragment key={`column-${i}`}>{renderColumn(column)}</React.Fragment>)
|
||||
}
|
||||
{
|
||||
!mayShowColumnsPreview() &&
|
||||
<Box width="100%" sx={{fontSize: "1rem", background: "#FFFFFF"}} minHeight={"2.375rem"} p={"0.5rem"} pb={"0.125rem"}>
|
||||
{
|
||||
isEditable &&
|
||||
<Tooltip title={selectTableFirstTooltipTitle}>
|
||||
<span><Button disabled={!recordValues["tableName"]} sx={unborderedButtonSX} onClick={openEditor}>+ Add Columns</Button></span>
|
||||
</Tooltip>
|
||||
}
|
||||
{
|
||||
!isEditable && <Box color={colors.gray.main}>No columns are selected.</Box>
|
||||
}
|
||||
</Box>
|
||||
}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
{
|
||||
modalOpen &&
|
||||
<Modal open={modalOpen} onClose={(event, reason) => closeEditor(event, reason)}>
|
@ -39,9 +39,9 @@ import colors from "qqq/assets/theme/base/colors";
|
||||
import {QCancelButton, QSaveButton} from "qqq/components/buttons/DefaultButtons";
|
||||
import FieldAutoComplete from "qqq/components/misc/FieldAutoComplete";
|
||||
import HelpContent, {hasHelpContent} from "qqq/components/misc/HelpContent";
|
||||
import {buttonSX, unborderedButtonSX} from "qqq/components/widgets/misc/FilterAndColumnsSetupWidget";
|
||||
import {PivotTableGroupByElement} from "qqq/components/widgets/misc/PivotTableGroupByElement";
|
||||
import {PivotTableValueElement} from "qqq/components/widgets/misc/PivotTableValueElement";
|
||||
import {buttonSX, unborderedButtonSX} from "qqq/components/widgets/misc/ReportSetupWidget";
|
||||
import Widget, {HeaderToggleComponent} from "qqq/components/widgets/Widget";
|
||||
import {PivotObjectKey, PivotTableDefinition, PivotTableFunction, pivotTableFunctionLabels, PivotTableGroupBy, PivotTableValue} from "qqq/models/misc/PivotTableDefinitionModels";
|
||||
import QQueryColumns from "qqq/models/query/QQueryColumns";
|
||||
|
@ -40,14 +40,14 @@ import {Link, useNavigate} from "react-router-dom";
|
||||
export interface ChildRecordListData extends WidgetData
|
||||
{
|
||||
title: string;
|
||||
queryOutput: {records: {values: any}[]}
|
||||
queryOutput: { records: { values: any }[] };
|
||||
childTableMetaData: QTableMetaData;
|
||||
tablePath: string;
|
||||
viewAllLink: string;
|
||||
totalRows: number;
|
||||
canAddChildRecord: boolean;
|
||||
defaultValuesForNewChildRecords: {[fieldName: string]: any};
|
||||
disabledFieldsForNewChildRecords: {[fieldName: string]: any};
|
||||
defaultValuesForNewChildRecords: { [fieldName: string]: any };
|
||||
disabledFieldsForNewChildRecords: { [fieldName: string]: any };
|
||||
}
|
||||
|
||||
interface Props
|
||||
@ -75,9 +75,9 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
|
||||
{
|
||||
const instance = useRef({timer: null});
|
||||
const [rows, setRows] = useState([]);
|
||||
const [records, setRecords] = useState([] as QRecord[])
|
||||
const [records, setRecords] = useState([] as QRecord[]);
|
||||
const [columns, setColumns] = useState([]);
|
||||
const [allColumns, setAllColumns] = useState([])
|
||||
const [allColumns, setAllColumns] = useState([]);
|
||||
const [csv, setCsv] = useState(null as string);
|
||||
const [fileName, setFileName] = useState(null as string);
|
||||
const [gridMouseDownX, setGridMouseDownX] = useState(0);
|
||||
@ -110,20 +110,20 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// capture all-columns to use for the export (before we might splice some away from the on-screen display) //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
const allColumns = [... columns];
|
||||
const allColumns = [...columns];
|
||||
setAllColumns(JSON.parse(JSON.stringify(columns)));
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// do not not show the foreign-key column of the parent table //
|
||||
////////////////////////////////////////////////////////////////
|
||||
if(data.defaultValuesForNewChildRecords)
|
||||
if (data.defaultValuesForNewChildRecords)
|
||||
{
|
||||
for (let i = 0; i < columns.length; i++)
|
||||
{
|
||||
if(data.defaultValuesForNewChildRecords[columns[i].field])
|
||||
if (data.defaultValuesForNewChildRecords[columns[i].field])
|
||||
{
|
||||
columns.splice(i, 1);
|
||||
i--
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -131,7 +131,7 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
|
||||
////////////////////////////////////
|
||||
// add actions cell, if available //
|
||||
////////////////////////////////////
|
||||
if(allowRecordEdit || allowRecordDelete)
|
||||
if (allowRecordEdit || allowRecordDelete)
|
||||
{
|
||||
columns.unshift({
|
||||
field: "_actions",
|
||||
@ -145,19 +145,19 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
|
||||
return <Box>
|
||||
{allowRecordEdit && <IconButton onClick={() => editRecordCallback(params.row.__rowIndex)}><Icon>edit</Icon></IconButton>}
|
||||
{allowRecordDelete && <IconButton onClick={() => deleteRecordCallback(params.row.__rowIndex)}><Icon>delete</Icon></IconButton>}
|
||||
</Box>
|
||||
</Box>;
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
setRows(rows);
|
||||
setRecords(records)
|
||||
setRecords(records);
|
||||
setColumns(columns);
|
||||
|
||||
let csv = "";
|
||||
for (let i = 0; i < allColumns.length; i++)
|
||||
{
|
||||
csv += `${i > 0 ? "," : ""}"${ValueUtils.cleanForCsv(allColumns[i].headerName)}"`
|
||||
csv += `${i > 0 ? "," : ""}"${ValueUtils.cleanForCsv(allColumns[i].headerName)}"`;
|
||||
}
|
||||
csv += "\n";
|
||||
|
||||
@ -165,8 +165,8 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
|
||||
{
|
||||
for (let j = 0; j < allColumns.length; j++)
|
||||
{
|
||||
const value = records[i].displayValues.get(allColumns[j].field) ?? records[i].values.get(allColumns[j].field)
|
||||
csv += `${j > 0 ? "," : ""}"${ValueUtils.cleanForCsv(value)}"`
|
||||
const value = records[i].displayValues.get(allColumns[j].field) ?? records[i].values.get(allColumns[j].field);
|
||||
csv += `${j > 0 ? "," : ""}"${ValueUtils.cleanForCsv(value)}"`;
|
||||
}
|
||||
csv += "\n";
|
||||
}
|
||||
@ -182,13 +182,13 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
|
||||
// view all link //
|
||||
///////////////////
|
||||
const labelAdditionalElementsLeft: JSX.Element[] = [];
|
||||
if(data && data.viewAllLink)
|
||||
if (data && data.viewAllLink)
|
||||
{
|
||||
labelAdditionalElementsLeft.push(
|
||||
<Typography key={"viewAllLink"} variant="body2" p={2} display="inline" fontSize=".875rem" pt="0" position="relative">
|
||||
<Link to={data.viewAllLink}>View All</Link>
|
||||
</Typography>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
///////////////////
|
||||
@ -200,10 +200,10 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
|
||||
{
|
||||
isExportDisabled = false;
|
||||
|
||||
if(data.totalRows && data.queryOutput.records.length < data.totalRows)
|
||||
if (data.totalRows && data.queryOutput.records.length < data.totalRows)
|
||||
{
|
||||
tooltipTitle = "Export these " + data.queryOutput.records.length + " records."
|
||||
if(data.viewAllLink)
|
||||
tooltipTitle = "Export these " + data.queryOutput.records.length + " records.";
|
||||
if (data.viewAllLink)
|
||||
{
|
||||
tooltipTitle += "\nClick View All to export all records.";
|
||||
}
|
||||
@ -212,17 +212,17 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
|
||||
|
||||
const onExportClick = () =>
|
||||
{
|
||||
if(csv)
|
||||
if (csv)
|
||||
{
|
||||
HtmlUtils.download(fileName, csv);
|
||||
}
|
||||
else
|
||||
{
|
||||
alert("There is no data available to export.")
|
||||
alert("There is no data available to export.");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if(widgetMetaData?.showExportButton)
|
||||
if (widgetMetaData?.showExportButton)
|
||||
{
|
||||
labelAdditionalElementsLeft.push(
|
||||
<Typography key={"exportButton"} variant="body2" px={0} display="inline" position="relative">
|
||||
@ -234,15 +234,15 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
|
||||
////////////////////
|
||||
// add new button //
|
||||
////////////////////
|
||||
const labelAdditionalComponentsRight: LabelComponent[] = []
|
||||
if(data && data.canAddChildRecord)
|
||||
const labelAdditionalComponentsRight: LabelComponent[] = [];
|
||||
if (data && data.canAddChildRecord)
|
||||
{
|
||||
let disabledFields = data.disabledFieldsForNewChildRecords;
|
||||
if(!disabledFields)
|
||||
if (!disabledFields)
|
||||
{
|
||||
disabledFields = data.defaultValuesForNewChildRecords;
|
||||
}
|
||||
labelAdditionalComponentsRight.push(new AddNewRecordButton(data.childTableMetaData, data.defaultValuesForNewChildRecords, "Add new", disabledFields, addNewRecordCallback))
|
||||
labelAdditionalComponentsRight.push(new AddNewRecordButton(data.childTableMetaData, data.defaultValuesForNewChildRecords, "Add new", disabledFields, addNewRecordCallback));
|
||||
}
|
||||
|
||||
|
||||
@ -251,16 +251,16 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
|
||||
/////////////////////////////////////////////////////////////////
|
||||
const handleRowClick = (params: GridRowParams, event: MuiEvent<React.MouseEvent>, details: GridCallbackDetails) =>
|
||||
{
|
||||
if(disableRowClick)
|
||||
if (disableRowClick)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
(async () =>
|
||||
{
|
||||
const qInstance = await qController.loadMetaData()
|
||||
let tablePath = qInstance.getTablePathByName(data.childTableMetaData.name)
|
||||
if(tablePath)
|
||||
const qInstance = await qController.loadMetaData();
|
||||
let tablePath = qInstance.getTablePathByName(data.childTableMetaData.name);
|
||||
if (tablePath)
|
||||
{
|
||||
tablePath = `${tablePath}/${params.row[data.childTableMetaData.primaryKeyField]}`;
|
||||
DataGridUtils.handleRowClick(tablePath, event, gridMouseDownX, gridMouseDownY, navigate, instance);
|
||||
@ -276,7 +276,7 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
|
||||
*******************************************************************************/
|
||||
function CustomToolbar()
|
||||
{
|
||||
const handleMouseDown: GridEventListener<"cellMouseDown"> = ( params, event, details ) =>
|
||||
const handleMouseDown: GridEventListener<"cellMouseDown"> = (params, event, details) =>
|
||||
{
|
||||
setGridMouseDownX(event.clientX);
|
||||
setGridMouseDownY(event.clientY);
|
||||
@ -304,8 +304,8 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
|
||||
labelAdditionalComponentsRight={labelAdditionalComponentsRight}
|
||||
labelBoxAdditionalSx={{position: "relative", top: "-0.375rem"}}
|
||||
>
|
||||
<Box mx={-2} mb={-3}>
|
||||
<Box className="recordGridWidget">
|
||||
<Box mx={-3} mb={-3}>
|
||||
<Box>
|
||||
<DataGridPro
|
||||
autoHeight
|
||||
sx={{
|
||||
|
@ -30,8 +30,6 @@ import TableContainer from "@mui/material/TableContainer";
|
||||
import TableRow from "@mui/material/TableRow";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import parse from "html-react-parser";
|
||||
import React, {useEffect, useMemo, useState} from "react";
|
||||
import {useAsyncDebounce, useExpanded, useGlobalFilter, usePagination, useSortBy, useTable} from "react-table";
|
||||
import colors from "qqq/assets/theme/base/colors";
|
||||
import MDInput from "qqq/components/legacy/MDInput";
|
||||
import MDPagination from "qqq/components/legacy/MDPagination";
|
||||
@ -43,6 +41,8 @@ import DefaultCell from "qqq/components/widgets/tables/cells/DefaultCell";
|
||||
import ImageCell from "qqq/components/widgets/tables/cells/ImageCell";
|
||||
import {TableDataInput} from "qqq/components/widgets/tables/TableCard";
|
||||
import WidgetBlock from "qqq/components/widgets/WidgetBlock";
|
||||
import React, {useEffect, useMemo, useState} from "react";
|
||||
import {useAsyncDebounce, useExpanded, useGlobalFilter, usePagination, useSortBy, useTable} from "react-table";
|
||||
|
||||
interface Props
|
||||
{
|
||||
@ -106,17 +106,17 @@ function DataTable({
|
||||
entries = entriesPerPageOptions ? entriesPerPageOptions : ["10", "25", "50", "100"];
|
||||
|
||||
let widths = [];
|
||||
for(let i = 0; i<table.columns.length; i++)
|
||||
for (let i = 0; i < table.columns.length; i++)
|
||||
{
|
||||
const column = table.columns[i];
|
||||
if(column.type !== "hidden")
|
||||
if (column.type !== "hidden")
|
||||
{
|
||||
widths.push(table.columns[i].width ?? "1fr");
|
||||
}
|
||||
}
|
||||
|
||||
let showExpandColumn = false;
|
||||
if(table.rows)
|
||||
if (table.rows)
|
||||
{
|
||||
for (let i = 0; i < table.rows.length; i++)
|
||||
{
|
||||
@ -129,7 +129,7 @@ function DataTable({
|
||||
}
|
||||
|
||||
const columnsToMemo = [...table.columns];
|
||||
if(showExpandColumn)
|
||||
if (showExpandColumn)
|
||||
{
|
||||
widths.push("60px");
|
||||
columnsToMemo.push(
|
||||
@ -173,11 +173,11 @@ function DataTable({
|
||||
);
|
||||
}
|
||||
|
||||
if(table.columnHeaderTooltips)
|
||||
if (table.columnHeaderTooltips)
|
||||
{
|
||||
for (let column of columnsToMemo)
|
||||
{
|
||||
if(table.columnHeaderTooltips[column.accessor])
|
||||
if (table.columnHeaderTooltips[column.accessor])
|
||||
{
|
||||
column.tooltip = table.columnHeaderTooltips[column.accessor];
|
||||
}
|
||||
@ -297,7 +297,7 @@ function DataTable({
|
||||
}
|
||||
|
||||
let visibleFooterRows = 1;
|
||||
if(expanded && expanded[`${table.rows.length-1}`])
|
||||
if (expanded && expanded[`${table.rows.length - 1}`])
|
||||
{
|
||||
//////////////////////////////////////////////////
|
||||
// todo - should count how many are expanded... //
|
||||
@ -308,7 +308,7 @@ function DataTable({
|
||||
function getTable(includeHead: boolean, rows: any, isFooter: boolean)
|
||||
{
|
||||
let boxStyle = {};
|
||||
if(fixedStickyLastRow)
|
||||
if (fixedStickyLastRow)
|
||||
{
|
||||
boxStyle = isFooter
|
||||
? {borderTop: `0.0625rem solid ${colors.grayLines.main};`, backgroundColor: "#EEEEEE"}
|
||||
@ -316,7 +316,7 @@ function DataTable({
|
||||
}
|
||||
|
||||
let innerBoxStyle = {};
|
||||
if(fixedStickyLastRow && isFooter)
|
||||
if (fixedStickyLastRow && isFooter)
|
||||
{
|
||||
innerBoxStyle = {overflowY: "auto", scrollbarGutter: "stable"};
|
||||
}
|
||||
@ -327,7 +327,7 @@ function DataTable({
|
||||
includeHead && (
|
||||
<Box component="thead" sx={{position: "sticky", top: 0, background: "white", zIndex: 10}}>
|
||||
{headerGroups.map((headerGroup: any, i: number) => (
|
||||
<TableRow key={i} {...headerGroup.getHeaderGroupProps()} sx={{display: "grid", gridTemplateColumns: gridTemplateColumns}}>
|
||||
<TableRow key={i} {...headerGroup.getHeaderGroupProps()} sx={{display: "grid", alignItems: "flex-end", gridTemplateColumns: gridTemplateColumns}}>
|
||||
{headerGroup.headers.map((column: any) => (
|
||||
column.type !== "hidden" && (
|
||||
<DataTableHeadCell
|
||||
@ -356,10 +356,10 @@ function DataTable({
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
// don't do an end-border on nested rows - unless they're the last one in a set //
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
if(row.depth > 0)
|
||||
if (row.depth > 0)
|
||||
{
|
||||
overrideNoEndBorder = true;
|
||||
if(key + 1 < rows.length && rows[key + 1].depth == 0)
|
||||
if (key + 1 < rows.length && rows[key + 1].depth == 0)
|
||||
{
|
||||
overrideNoEndBorder = false;
|
||||
}
|
||||
@ -368,17 +368,17 @@ function DataTable({
|
||||
///////////////////////////////////////
|
||||
// don't do end-border on the footer //
|
||||
///////////////////////////////////////
|
||||
if(isFooter)
|
||||
if (isFooter)
|
||||
{
|
||||
overrideNoEndBorder = true;
|
||||
}
|
||||
|
||||
let background = "initial";
|
||||
if(isFooter)
|
||||
if (isFooter)
|
||||
{
|
||||
background = "#EEEEEE";
|
||||
}
|
||||
else if(row.depth > 0 || row.isExpanded)
|
||||
else if (row.depth > 0 || row.isExpanded)
|
||||
{
|
||||
background = "#FAFAFA";
|
||||
}
|
||||
@ -453,7 +453,7 @@ function DataTable({
|
||||
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Box></Box>
|
||||
</Box></Box>;
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -28,13 +28,13 @@ import TableBody from "@mui/material/TableBody";
|
||||
import TableContainer from "@mui/material/TableContainer";
|
||||
import TableRow from "@mui/material/TableRow";
|
||||
import parse from "html-react-parser";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import MDTypography from "qqq/components/legacy/MDTypography";
|
||||
import DataTableBodyCell from "qqq/components/widgets/tables/cells/DataTableBodyCell";
|
||||
import DataTableHeadCell from "qqq/components/widgets/tables/cells/DataTableHeadCell";
|
||||
import DefaultCell from "qqq/components/widgets/tables/cells/DefaultCell";
|
||||
import DataTable from "qqq/components/widgets/tables/DataTable";
|
||||
import Client from "qqq/utils/qqq/Client";
|
||||
import React, {useEffect, useState} from "react";
|
||||
|
||||
|
||||
//////////////////////////////////////
|
||||
@ -43,7 +43,7 @@ import Client from "qqq/utils/qqq/Client";
|
||||
export interface TableDataInput
|
||||
{
|
||||
columns: { [key: string]: any }[];
|
||||
columnHeaderTooltips?: { [columnName: string]: string | JSX.Element }
|
||||
columnHeaderTooltips?: { [columnName: string]: string | JSX.Element };
|
||||
rows: { [key: string]: any }[];
|
||||
}
|
||||
|
||||
@ -63,6 +63,7 @@ interface Props
|
||||
}
|
||||
|
||||
const qController = Client.getInstance();
|
||||
|
||||
function TableCard({noRowsFoundHTML, data, rowsPerPage, hidePaginationDropdown, fixedStickyLastRow, fixedHeight, widgetMetaData}: Props): JSX.Element
|
||||
{
|
||||
const [qInstance, setQInstance] = useState(null as QInstance);
|
||||
@ -108,7 +109,7 @@ function TableCard({noRowsFoundHTML, data, rowsPerPage, hidePaginationDropdown,
|
||||
<TableContainer sx={{boxShadow: "none"}}>
|
||||
<Table>
|
||||
<Box component="thead">
|
||||
<TableRow key="header">
|
||||
<TableRow sx={{alignItems: "flex-end"}} key="header">
|
||||
{Array(8).fill(0).map((_, i) =>
|
||||
<DataTableHeadCell key={`head-${i}`} sorted={false} width="auto" align="center">
|
||||
<Skeleton width="100%" />
|
||||
|
@ -23,7 +23,6 @@
|
||||
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
|
||||
// @ts-ignore
|
||||
import {htmlToText} from "html-to-text";
|
||||
import React, {useContext, useEffect, useState} from "react";
|
||||
import QContext from "QContext";
|
||||
import HelpContent, {hasHelpContent} from "qqq/components/misc/HelpContent";
|
||||
import TableCard from "qqq/components/widgets/tables/TableCard";
|
||||
@ -31,6 +30,7 @@ import Widget, {WidgetData} from "qqq/components/widgets/Widget";
|
||||
import {WidgetUtils} from "qqq/components/widgets/WidgetUtils";
|
||||
import HtmlUtils from "qqq/utils/HtmlUtils";
|
||||
import ValueUtils from "qqq/utils/qqq/ValueUtils";
|
||||
import React, {useContext, useEffect, useState} from "react";
|
||||
|
||||
interface Props
|
||||
{
|
||||
@ -40,8 +40,7 @@ interface Props
|
||||
isChild?: boolean;
|
||||
}
|
||||
|
||||
TableWidget.defaultProps = {
|
||||
};
|
||||
TableWidget.defaultProps = {};
|
||||
|
||||
function TableWidget(props: Props): JSX.Element
|
||||
{
|
||||
@ -86,7 +85,7 @@ function TableWidget(props: Props): JSX.Element
|
||||
|
||||
const cell = rows[i][columns[j].accessor];
|
||||
let text = cell;
|
||||
if(columns[j].type != "default")
|
||||
if (columns[j].type != "default")
|
||||
{
|
||||
text = htmlToText(cell,
|
||||
{
|
||||
@ -105,7 +104,7 @@ function TableWidget(props: Props): JSX.Element
|
||||
setCsv(csv);
|
||||
|
||||
const fileName = WidgetUtils.makeExportFileName(props.widgetData, props.widgetMetaData);
|
||||
setFileName(fileName)
|
||||
setFileName(fileName);
|
||||
|
||||
console.log(`useEffect, setting fileName ${fileName}`);
|
||||
}
|
||||
@ -114,24 +113,28 @@ function TableWidget(props: Props): JSX.Element
|
||||
|
||||
const onExportClick = () =>
|
||||
{
|
||||
if(props.widgetData?.csvData)
|
||||
if (props.widgetData?.csvData)
|
||||
{
|
||||
const csv = WidgetUtils.widgetCsvDataToString(props.widgetData);
|
||||
const fileName = WidgetUtils.makeExportFileName(props.widgetData, props.widgetMetaData);
|
||||
HtmlUtils.download(fileName, csv);
|
||||
}
|
||||
else if(csv)
|
||||
else if (csv)
|
||||
{
|
||||
HtmlUtils.download(fileName, csv);
|
||||
}
|
||||
else
|
||||
{
|
||||
alert("There is no data available to export.")
|
||||
alert("There is no data available to export.");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const labelAdditionalElementsLeft: JSX.Element[] = [];
|
||||
if(props.widgetMetaData?.showExportButton)
|
||||
if (props.widgetData?.linkText && props.widgetData?.linkURL)
|
||||
{
|
||||
labelAdditionalElementsLeft.push(WidgetUtils.generateLabelLink(props.widgetData?.linkText, props.widgetData?.linkURL));
|
||||
}
|
||||
if (props.widgetMetaData?.showExportButton)
|
||||
{
|
||||
labelAdditionalElementsLeft.push(WidgetUtils.generateExportButton(onExportClick));
|
||||
}
|
||||
@ -139,14 +142,14 @@ function TableWidget(props: Props): JSX.Element
|
||||
//////////////////////////////////////////////////////
|
||||
// look for column-header tooltips from helpContent //
|
||||
//////////////////////////////////////////////////////
|
||||
const columnHeaderTooltips: {[columnName: string]: JSX.Element} = {}
|
||||
const columnHeaderTooltips: { [columnName: string]: JSX.Element } = {};
|
||||
for (let column of props.widgetData?.columns ?? [])
|
||||
{
|
||||
const helpRoles = ["ALL_SCREENS"]
|
||||
const helpRoles = ["ALL_SCREENS"];
|
||||
const slotName = `columnHeader=${column.accessor}`;
|
||||
const showHelp = helpHelpActive || hasHelpContent(props.widgetMetaData?.helpContent?.get(slotName), helpRoles);
|
||||
|
||||
if(showHelp)
|
||||
if (showHelp)
|
||||
{
|
||||
const formattedHelpContent = <HelpContent helpContents={props.widgetMetaData?.helpContent?.get(slotName)} roles={helpRoles} helpContentKey={`widget:${props.widgetMetaData?.name};slot:${slotName}`} />;
|
||||
columnHeaderTooltips[column.accessor] = formattedHelpContent;
|
||||
|
@ -54,7 +54,7 @@ import DynamicFormUtils from "qqq/components/forms/DynamicFormUtils";
|
||||
import MDButton from "qqq/components/legacy/MDButton";
|
||||
import MDProgress from "qqq/components/legacy/MDProgress";
|
||||
import MDTypography from "qqq/components/legacy/MDTypography";
|
||||
import HelpContent from "qqq/components/misc/HelpContent";
|
||||
import HelpContent, {hasHelpContent} from "qqq/components/misc/HelpContent";
|
||||
import QRecordSidebar from "qqq/components/misc/RecordSidebar";
|
||||
import {GoogleDriveFolderPickerWrapper} from "qqq/components/processes/GoogleDriveFolderPickerWrapper";
|
||||
import ProcessSummaryResults from "qqq/components/processes/ProcessSummaryResults";
|
||||
@ -94,7 +94,7 @@ const BACKOFF_AMOUNT = 1.5;
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
let formikSetFieldValueFunction = (field: string, value: any, shouldValidate?: boolean): void =>
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, isReport, recordIds, closeModalHandler, forceReInit, overrideLabel}: Props): JSX.Element
|
||||
{
|
||||
@ -134,9 +134,9 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
||||
const [showErrorDetail, setShowErrorDetail] = useState(false);
|
||||
const [showFullHelpText, setShowFullHelpText] = useState(false);
|
||||
|
||||
const [renderedWidgets, setRenderedWidgets] = useState({} as {[step: string]: {[widgetName: string]: any}});
|
||||
const [renderedWidgets, setRenderedWidgets] = useState({} as { [step: string]: { [widgetName: string]: any } });
|
||||
|
||||
const {pageHeader, recordAnalytics, setPageHeader} = useContext(QContext);
|
||||
const {pageHeader, recordAnalytics, setPageHeader, helpHelpActive} = useContext(QContext);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// for setting the processError state - call this function, which will also set the isUserFacingError state //
|
||||
@ -238,15 +238,15 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
||||
setShowFullHelpText(!showFullHelpText);
|
||||
};
|
||||
|
||||
const download = (processValues: {[key: string]: string}) =>
|
||||
const download = (processValues: { [key: string]: string }) =>
|
||||
{
|
||||
let url;
|
||||
let fileName = processValues.downloadFileName;
|
||||
if(processValues.serverFilePath)
|
||||
if (processValues.serverFilePath)
|
||||
{
|
||||
url = `/download/${encodeURIComponent(processValues.downloadFileName)}?filePath=${encodeURIComponent(processValues.serverFilePath)}`;
|
||||
}
|
||||
else if(processValues.storageTableName && processValues.storageReference)
|
||||
else if (processValues.storageTableName && processValues.storageReference)
|
||||
{
|
||||
url = `/download/${encodeURIComponent(processValues.downloadFileName)}?storageTableName=${encodeURIComponent(processValues.storageTableName)}&storageReference=${encodeURIComponent(processValues.storageReference)}`;
|
||||
}
|
||||
@ -291,19 +291,19 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
||||
*******************************************************************************/
|
||||
function renderWidget(widgetName: string)
|
||||
{
|
||||
if(!renderedWidgets[activeStep.name])
|
||||
if (!renderedWidgets[activeStep.name])
|
||||
{
|
||||
renderedWidgets[activeStep.name] = {};
|
||||
setRenderedWidgets(renderedWidgets);
|
||||
}
|
||||
|
||||
if(renderedWidgets[activeStep.name][widgetName])
|
||||
if (renderedWidgets[activeStep.name][widgetName])
|
||||
{
|
||||
return renderedWidgets[activeStep.name][widgetName];
|
||||
}
|
||||
|
||||
const widgetMetaData = qInstance.widgets.get(widgetName);
|
||||
if(!widgetMetaData)
|
||||
if (!widgetMetaData)
|
||||
{
|
||||
return (<Alert color="error">Unrecognized widget name: {widgetName}</Alert>);
|
||||
}
|
||||
@ -311,12 +311,12 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
||||
const queryStringParts: string[] = [];
|
||||
for (let name in processValues)
|
||||
{
|
||||
queryStringParts.push(`${name}=${encodeURIComponent(processValues[name])}`)
|
||||
queryStringParts.push(`${name}=${encodeURIComponent(processValues[name])}`);
|
||||
}
|
||||
|
||||
const renderedWidget = (<Box m={-2}>
|
||||
<DashboardWidgets widgetMetaDataList={[widgetMetaData]} omitWrappingGridContainer={true} childUrlParams={queryStringParts.join("&")} />
|
||||
</Box>)
|
||||
</Box>);
|
||||
renderedWidgets[activeStep.name][widgetName] = renderedWidget;
|
||||
return renderedWidget;
|
||||
}
|
||||
@ -367,8 +367,8 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
||||
</MDTypography>
|
||||
<Box component="div" py={3}>
|
||||
<Grid container justifyContent="flex-end" spacing={3}>
|
||||
{isModal ? <QCancelButton onClickHandler={handleCancelClicked} disabled={false} label="Close" />
|
||||
: !isWidget && <QCancelButton onClickHandler={handleCancelClicked} disabled={false} />
|
||||
{isModal ? <QCancelButton onClickHandler={() => handleCancelClicked(true)} disabled={false} label="Close" />
|
||||
: !isWidget && <QCancelButton onClickHandler={() => handleCancelClicked(true)} disabled={false} />
|
||||
}
|
||||
</Grid>
|
||||
</Box>
|
||||
@ -455,14 +455,12 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
||||
});
|
||||
}
|
||||
|
||||
// todo not commit - not ready - need process (or screen) meta-data to have helpContents...
|
||||
/*
|
||||
///////////////////////////////
|
||||
// screen-level help content //
|
||||
///////////////////////////////
|
||||
/////////////////////////////////////
|
||||
// screen(step)-level help content //
|
||||
/////////////////////////////////////
|
||||
let helpRoles = ["PROCESS_SCREEN", "ALL_SCREENS"];
|
||||
const formattedHelpContent = <HelpContent helpContents={process.helpContents} roles={helpRoles} helpContentKey={`table:${tableName};section:${section.name}`} />;
|
||||
*/
|
||||
const showHelp = helpHelpActive || hasHelpContent(step.helpContents, helpRoles);
|
||||
const formattedHelpContent = <HelpContent helpContents={step.helpContents} roles={helpRoles} helpContentKey={`process:${processName};step:${step?.name}`} />;
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -479,13 +477,10 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
||||
}
|
||||
|
||||
{
|
||||
/*
|
||||
// todo not commit - not ready - need process (or screen) meta-data to have helpContents...
|
||||
formattedHelpContent &&
|
||||
<Box px={"1.5rem"} fontSize={"0.875rem"} color={colors.blueGray.main}>
|
||||
showHelp &&
|
||||
<Box fontSize={"0.875rem"} color={colors.blueGray.main} pb={2}>
|
||||
{formattedHelpContent}
|
||||
</Box>
|
||||
*/
|
||||
}
|
||||
|
||||
{
|
||||
@ -505,7 +500,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
||||
// edit the formData object to just include those. //
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
let formDataToUse = formData;
|
||||
if(component.values && component.values.includeFieldNames)
|
||||
if (component.values && component.values.includeFieldNames)
|
||||
{
|
||||
formDataToUse = Object.assign({}, formData);
|
||||
|
||||
@ -613,21 +608,21 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
||||
}
|
||||
{
|
||||
component.type === QComponentType.EDIT_FORM &&
|
||||
<>
|
||||
{
|
||||
component.values?.sectionLabel ?
|
||||
<Box py={1.5}>
|
||||
<Card sx={{scrollMarginTop: "20px"}}>
|
||||
<MDTypography variant="h5" p={3} pl={2} pb={1}>
|
||||
{component.values?.sectionLabel}
|
||||
</MDTypography>
|
||||
<Box pt={0} p={2}>
|
||||
<QDynamicForm formData={formDataToUse} helpRoles={helpRoles} helpContentKeyPrefix={`process:${processName};`} />
|
||||
</Box>
|
||||
</Card>
|
||||
</Box> : <QDynamicForm formData={formDataToUse} helpRoles={helpRoles} helpContentKeyPrefix={`process:${processName};`} />
|
||||
}
|
||||
</>
|
||||
<>
|
||||
{
|
||||
component.values?.sectionLabel ?
|
||||
<Box py={1.5}>
|
||||
<Card sx={{scrollMarginTop: "20px"}}>
|
||||
<MDTypography variant="h5" p={3} pl={2} pb={1}>
|
||||
{component.values?.sectionLabel}
|
||||
</MDTypography>
|
||||
<Box pt={0} p={2}>
|
||||
<QDynamicForm formData={formDataToUse} helpRoles={helpRoles} helpContentKeyPrefix={`process:${processName};`} />
|
||||
</Box>
|
||||
</Card>
|
||||
</Box> : <QDynamicForm formData={formDataToUse} helpRoles={helpRoles} helpContentKeyPrefix={`process:${processName};`} />
|
||||
}
|
||||
</>
|
||||
}
|
||||
{
|
||||
component.type === QComponentType.VIEW_FORM && step.viewFields && (
|
||||
@ -1018,7 +1013,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
||||
}
|
||||
});
|
||||
|
||||
DynamicFormUtils.addPossibleValueProps(newDynamicFormFields, fullFieldList, tableMetaData.name, null, null);
|
||||
DynamicFormUtils.addPossibleValueProps(newDynamicFormFields, fullFieldList, tableMetaData?.name, null, null);
|
||||
|
||||
setFormFields(newDynamicFormFields);
|
||||
setValidationScheme(Yup.object().shape(newFormValidations));
|
||||
@ -1084,14 +1079,18 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
||||
setProcessValues(qJobComplete.values);
|
||||
setQJobRunning(null);
|
||||
|
||||
if(formikSetFieldValueFunction)
|
||||
if (formikSetFieldValueFunction)
|
||||
{
|
||||
//////////////////////////////////
|
||||
// reset field values in formik //
|
||||
//////////////////////////////////
|
||||
for (let key in qJobComplete.values)
|
||||
{
|
||||
formikSetFieldValueFunction(key, qJobComplete.values[key]);
|
||||
if (Object.hasOwn(formFields, key))
|
||||
{
|
||||
console.log(`(re)setting form field [${key}] to [${qJobComplete.values[key]}]`);
|
||||
formikSetFieldValueFunction(key, qJobComplete.values[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1099,7 +1098,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
||||
// if the process step sent a new frontend-step-list, then refresh what we have in state (constructing new full model objects) //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
const updatedFrontendStepList = qJobComplete.updatedFrontendStepList;
|
||||
if(updatedFrontendStepList)
|
||||
if (updatedFrontendStepList)
|
||||
{
|
||||
setSteps(updatedFrontendStepList);
|
||||
}
|
||||
@ -1402,8 +1401,20 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
||||
});
|
||||
};
|
||||
|
||||
const handleCancelClicked = () =>
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
const handleCancelClicked = (isClose: boolean) =>
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////
|
||||
// unless this is a 'close', then tell backend we're cancelling //
|
||||
//////////////////////////////////////////////////////////////////
|
||||
if (!isClose)
|
||||
{
|
||||
Client.getInstance().processCancel(processName, processUUID);
|
||||
}
|
||||
|
||||
if (isModal && closeModalHandler)
|
||||
{
|
||||
closeModalHandler(null, "cancelClicked");
|
||||
@ -1416,6 +1427,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
||||
navigate(path, {replace: true});
|
||||
};
|
||||
|
||||
|
||||
const mainCardStyles: any = {};
|
||||
const formStyles: any = {};
|
||||
mainCardStyles.minHeight = `calc(100vh - ${isModal ? 150 : 400}px)`;
|
||||
@ -1491,8 +1503,8 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
||||
<Box p={3}>
|
||||
<Box pb={isWidget ? 6 : "initial"}>
|
||||
{/***************************************************************************
|
||||
** step content - e.g., the appropriate form or other screen for the step **
|
||||
***************************************************************************/}
|
||||
** step content - e.g., the appropriate form or other screen for the step **
|
||||
***************************************************************************/}
|
||||
{getDynamicStepContent(
|
||||
activeStepIndex,
|
||||
activeStep,
|
||||
@ -1508,8 +1520,8 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
||||
setFieldValue,
|
||||
)}
|
||||
{/********************************
|
||||
** back &| next/submit buttons **
|
||||
********************************/}
|
||||
** back &| next/submit buttons **
|
||||
********************************/}
|
||||
<Box mt={6} width="100%" display="flex" justifyContent="space-between" position={isWidget ? "absolute" : "initial"} bottom={isWidget ? "3rem" : "initial"} right={isWidget ? "1.5rem" : "initial"}>
|
||||
{true || activeStepIndex === 0 ? (
|
||||
<Box />
|
||||
@ -1527,7 +1539,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
||||
)}
|
||||
{
|
||||
noMoreSteps && <QCancelButton
|
||||
onClickHandler={handleCancelClicked}
|
||||
onClickHandler={() => handleCancelClicked(true)}
|
||||
label={isModal ? "Close" : "Return"}
|
||||
iconName={isModal ? "cancel" : "arrow_back"}
|
||||
disabled={isSubmitting} />
|
||||
@ -1538,7 +1550,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
||||
<Grid container justifyContent="flex-end" spacing={3}>
|
||||
{
|
||||
!isWidget && (
|
||||
<QCancelButton onClickHandler={handleCancelClicked} disabled={isSubmitting} />
|
||||
<QCancelButton onClickHandler={() => handleCancelClicked(false)} disabled={isSubmitting} />
|
||||
)
|
||||
}
|
||||
<QSubmitButton label={nextButtonLabel} iconName={nextButtonIcon} disabled={isSubmitting} />
|
||||
@ -1553,7 +1565,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
||||
</Box>
|
||||
</Card>
|
||||
</Form>
|
||||
)
|
||||
);
|
||||
}}
|
||||
</Formik>
|
||||
);
|
||||
|
@ -535,7 +535,7 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
|
||||
|
||||
setPageHeader(record.recordLabel);
|
||||
|
||||
if (!launchingProcess)
|
||||
if (!launchingProcess && !activeModalProcess)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -421,6 +421,14 @@ input[type="search"]::-webkit-search-results-decoration
|
||||
font-size: 2rem !important;
|
||||
}
|
||||
|
||||
.dashboard-table-actions-icon
|
||||
{
|
||||
font-size: 1.5rem !important;
|
||||
position: relative;
|
||||
top: -5px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.dashboard-schedule-icon
|
||||
{
|
||||
font-size: 1.1rem !important;
|
||||
@ -653,6 +661,11 @@ input[type="search"]::-webkit-search-results-decoration
|
||||
min-height: unset !important;
|
||||
}
|
||||
|
||||
.MuiDataGrid-columnHeaders
|
||||
{
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
/* new style for toggle buttons */
|
||||
.MuiToggleButtonGroup-root
|
||||
{
|
||||
@ -698,13 +711,71 @@ input[type="search"]::-webkit-search-results-decoration
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.recordView .widget .recordGridWidget
|
||||
{
|
||||
margin: -8px;
|
||||
}
|
||||
|
||||
.MuiPickersDay-root.Mui-selected, .MuiPickersDay-root.MuiPickersDay-dayWithMargin:hover
|
||||
{
|
||||
color: white;
|
||||
background-color: #0062FF !important;
|
||||
}
|
||||
|
||||
.helpContentAlert
|
||||
{
|
||||
padding: 6px 16px;
|
||||
font-size: 1rem;
|
||||
font-weight: 300;
|
||||
line-height: 1.6;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.helpContentAlert .MuiAlert-icon
|
||||
{
|
||||
display: flex;
|
||||
margin-right: 12px;
|
||||
padding: 7px 0;
|
||||
font-size: 22px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.helpContentAlert .MuiAlert-icon .material-icons-round
|
||||
{
|
||||
display: inline-block;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
.helpContentAlert .MuiAlert-message
|
||||
{
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.helpContentAlert.success
|
||||
{
|
||||
background-color: rgb(240, 248, 241);
|
||||
color: rgb(44, 76, 46);
|
||||
}
|
||||
|
||||
.helpContentAlert.success .MuiAlert-icon .material-icons-round
|
||||
{
|
||||
color: #4CAF50;
|
||||
}
|
||||
|
||||
.helpContentAlert.warning
|
||||
{
|
||||
background-color: rgb(254, 245, 234);
|
||||
color: rgb(100, 65, 20);
|
||||
}
|
||||
|
||||
.helpContentAlert.warning .MuiAlert-icon .material-icons-round
|
||||
{
|
||||
color: #fb8c00;
|
||||
}
|
||||
|
||||
.helpContentAlert.error
|
||||
{
|
||||
background-color: rgb(254, 239, 238);
|
||||
color: rgb(98, 41, 37);
|
||||
}
|
||||
|
||||
.helpContentAlert.error .MuiAlert-icon .material-icons-round
|
||||
{
|
||||
color: #F44335;
|
||||
}
|
||||
|
@ -822,7 +822,7 @@
|
||||
"reportSetupWidget": {
|
||||
"name": "reportSetupWidget",
|
||||
"label": "Filters and Columns",
|
||||
"type": "reportSetup",
|
||||
"type": "filterAndColumnsSetup",
|
||||
"isCard": true,
|
||||
"storeDropdownSelections": false,
|
||||
"showReloadButton": true,
|
||||
|
Reference in New Issue
Block a user