Compare commits

..

19 Commits

Author SHA1 Message Date
71a1bfaa6b CE-938: fixed filter display text, dashboard widget style change 2024-05-29 12:58:06 -05:00
d9e9a0be08 CE-938 Add calls to processCancel 2024-05-28 16:27:51 -05:00
aefb282a0e CE-938 update qfc to 1.0.102 - adding processCancel 2024-05-28 16:27:03 -05:00
fb57718c1c Add some ?.'s around metaData.widgets (in case an instance has no widgets) 2024-05-28 16:26:24 -05:00
ba213b038b Removing cypress 2024-05-28 16:25:56 -05:00
69daf47021 CE-938: renamed reportSetup widget to filterAndColumns 2024-05-22 15:30:13 -05:00
1d24b9b40c CE-938: updated 'flashing' occurring in child widget whenever any form fields are changed, instead of only the data in the widget 2024-05-22 12:38:16 -05:00
f44ba8d6d3 CE-938: improvements to the report setup widget 2024-05-21 18:26:35 -05:00
dc131d5189 qqq-frontend-core 1.0.101 2024-05-15 19:44:12 -05:00
2b5cc1610f For CE-1280 - add helpContent to process steps, along with css for help content to do standard app style alerts 2024-05-15 19:42:25 -05:00
a36bdb1474 Merge pull request #60 from Kingsrook/feature/CE-1240-out-of-stock-summary-page
Feature/ce 1240 out of stock summary page
2024-05-15 19:20:59 -05:00
c2926d26e8 Merge pull request #59 from Kingsrook/feature/CE-1180-order-address-validation
Feature/ce 1180 order address validation
2024-05-15 19:17:49 -05:00
eb42a86655 CE-1180 Update to only set formik values for fields that are in the form (to avoid setting, e.g., 'backend only' type fields, like the extract code-reference 2024-05-15 09:01:38 -05:00
b7f715f832 Change to javascript-scroll into view, rather than use anchors (they don't work upon reload anyway, due to async loading, and they broke record-view-by-key); also move overflow down an element in the stack, to make border-radius look better 2024-05-14 22:29:22 -05:00
16a08cfd42 Fix, don't push record-view/process urls into history 2024-05-14 22:28:25 -05:00
f5919c66ab Add whitespace nowrap to goto button 2024-05-14 20:29:21 -05:00
d750ef0930 CE-1240: made link font slightly larger 2024-05-06 15:30:27 -05:00
267ead925b CE-1240: added support for table link 2024-05-06 11:58:57 -05:00
f925ad9116 CE-1240: updated composite widget to have flex column ability, support for 'multi table' widget, 2024-05-03 20:26:36 -05:00
20 changed files with 1491 additions and 19264 deletions

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@
"@auth0/auth0-react": "1.10.2", "@auth0/auth0-react": "1.10.2",
"@emotion/react": "11.7.1", "@emotion/react": "11.7.1",
"@emotion/styled": "11.6.0", "@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/icons-material": "5.4.1",
"@mui/material": "5.11.1", "@mui/material": "5.11.1",
"@mui/styles": "5.11.1", "@mui/styles": "5.11.1",

View File

@ -44,9 +44,9 @@ import MDTypography from "qqq/components/legacy/MDTypography";
import HelpContent from "qqq/components/misc/HelpContent"; import HelpContent from "qqq/components/misc/HelpContent";
import QRecordSidebar from "qqq/components/misc/RecordSidebar"; import QRecordSidebar from "qqq/components/misc/RecordSidebar";
import DynamicFormWidget from "qqq/components/widgets/misc/DynamicFormWidget"; 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 PivotTableSetupWidget from "qqq/components/widgets/misc/PivotTableSetupWidget";
import RecordGridWidget, {ChildRecordListData} from "qqq/components/widgets/misc/RecordGridWidget"; 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 {FieldRule, FieldRuleAction, FieldRuleTrigger} from "qqq/models/fields/FieldRules";
import HtmlUtils from "qqq/utils/HtmlUtils"; import HtmlUtils from "qqq/utils/HtmlUtils";
import Client from "qqq/utils/qqq/Client"; import Client from "qqq/utils/qqq/Client";
@ -88,7 +88,7 @@ EntityForm.defaultProps = {
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
let formikSetFieldValueFunction = (field: string, value: any, shouldValidate?: boolean): void => let formikSetFieldValueFunction = (field: string, value: any, shouldValidate?: boolean): void =>
{ {
} };
function EntityForm(props: Props): JSX.Element function EntityForm(props: Props): JSX.Element
{ {
@ -119,6 +119,7 @@ function EntityForm(props: Props): JSX.Element
const [, forceUpdate] = useReducer((x) => x + 1, 0); const [, forceUpdate] = useReducer((x) => x + 1, 0);
const [showEditChildForm, setShowEditChildForm] = useState(null as any); const [showEditChildForm, setShowEditChildForm] = useState(null as any);
const [modalDataChangedCounter, setModalDataChangedCount] = useState(0);
const [notAllowedError, setNotAllowedError] = useState(null as string); const [notAllowedError, setNotAllowedError] = useState(null as string);
@ -282,6 +283,8 @@ function EntityForm(props: Props): JSX.Element
setRenderedWidgetSections(newRenderedWidgetSections); setRenderedWidgetSections(newRenderedWidgetSections);
forceUpdate(); forceUpdate();
setModalDataChangedCount(modalDataChangedCounter + 1);
setShowEditChildForm(null); setShowEditChildForm(null);
} }
@ -351,7 +354,6 @@ function EntityForm(props: Props): JSX.Element
} }
/******************************************************************************* /*******************************************************************************
** if we have a widget that wants to set form-field values, they can take this ** 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 in as a callback, and then call it with their values.
@ -376,7 +378,7 @@ function EntityForm(props: Props): JSX.Element
widgetMetaData.showExportButton = false; widgetMetaData.showExportButton = false;
return <RecordGridWidget return <RecordGridWidget
key={new Date().getTime()} // added so that editing values actually re-renders... key={`${formValues["tableName"]}-${modalDataChangedCounter}`}
widgetMetaData={widgetMetaData} widgetMetaData={widgetMetaData}
data={widgetData} data={widgetData}
disableRowClick disableRowClick
@ -388,15 +390,24 @@ function EntityForm(props: Props): JSX.Element
/>; />;
} }
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... key={formValues["tableName"]} // todo, is this good? it was added so that editing values actually re-renders...
isEditable={true} isEditable={true}
widgetMetaData={widgetMetaData} widgetMetaData={widgetMetaData}
recordValues={formValues} recordValues={formValues}
onSaveCallback={setFormFieldValuesFromWidget} onSaveCallback={setFormFieldValuesFromWidget}
/> />;
} }
if (widgetMetaData.type == "pivotTableSetup") if (widgetMetaData.type == "pivotTableSetup")
@ -407,7 +418,7 @@ function EntityForm(props: Props): JSX.Element
widgetMetaData={widgetMetaData} widgetMetaData={widgetMetaData}
recordValues={formValues} recordValues={formValues}
onSaveCallback={setFormFieldValuesFromWidget} onSaveCallback={setFormFieldValuesFromWidget}
/> />;
} }
if (widgetMetaData.type == "dynamicForm") if (widgetMetaData.type == "dynamicForm")
@ -420,10 +431,10 @@ function EntityForm(props: Props): JSX.Element
recordValues={formValues} recordValues={formValues}
record={record} record={record}
onSaveCallback={setFormFieldValuesFromWidget} onSaveCallback={setFormFieldValuesFromWidget}
/> />;
} }
return (<Box>Unsupported widget type: {widgetMetaData.type}</Box>) return (<Box>Unsupported widget type: {widgetMetaData.type}</Box>);
} }
@ -488,7 +499,7 @@ function EntityForm(props: Props): JSX.Element
///////////////////////////////////////////////// /////////////////////////////////////////////////
const tableSections = TableUtils.getSectionsForRecordSidebar(tableMetaData, [...tableMetaData.fields.keys()], (section: QTableSection) => const tableSections = TableUtils.getSectionsForRecordSidebar(tableMetaData, [...tableMetaData.fields.keys()], (section: QTableSection) =>
{ {
const widget = metaData.widgets.get(section.widgetName); const widget = metaData?.widgets.get(section.widgetName);
if (widget) if (widget)
{ {
if (widget.type == "childRecordList" && widget.defaultValues?.has("manageAssociationName")) if (widget.type == "childRecordList" && widget.defaultValues?.has("manageAssociationName"))
@ -496,7 +507,7 @@ function EntityForm(props: Props): JSX.Element
return (true); return (true);
} }
if(widget.type == "reportSetup" || widget.type == "pivotTableSetup" || widget.type == "dynamicForm") if (widget.type == "filterAndColumnsSetup" || widget.type == "pivotTableSetup" || widget.type == "dynamicForm")
{ {
return (true); return (true);
} }
@ -719,7 +730,7 @@ function EntityForm(props: Props): JSX.Element
} }
else else
{ {
const widgetMetaData = metaData.widgets.get(section.widgetName); const widgetMetaData = metaData?.widgets.get(section.widgetName);
const widgetData = await qController.widget(widgetMetaData.name, makeQueryStringWithIdAndObject(tableMetaData, defaultValues)); const widgetData = await qController.widget(widgetMetaData.name, makeQueryStringWithIdAndObject(tableMetaData, defaultValues));
newRenderedWidgetSections[section.widgetName] = getWidgetSection(widgetMetaData, widgetData); newRenderedWidgetSections[section.widgetName] = getWidgetSection(widgetMetaData, widgetData);
@ -1005,14 +1016,14 @@ function EntityForm(props: Props): JSX.Element
const queryParamsArray: string[] = []; 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) for (let key in object)
{ {
queryParamsArray.push(`${key}=${encodeURIComponent(object[key])}`) queryParamsArray.push(`${key}=${encodeURIComponent(object[key])}`);
} }
} }
@ -1194,7 +1205,7 @@ function EntityForm(props: Props): JSX.Element
setFieldValue(fieldName, valueChangesToMake[fieldName], false); setFieldValue(fieldName, valueChangesToMake[fieldName], false);
} }
setFormValues(formValues) setFormValues(formValues);
setFormValuesJSON(JSON.stringify(values)); setFormValuesJSON(JSON.stringify(values));
} }
} }

View File

@ -358,7 +358,7 @@ export function GotoRecordButton(props: GotoRecordButtonProps): JSX.Element
return ( return (
<React.Fragment> <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} /> <GotoRecordDialog metaData={props.metaData} tableMetaData={props.tableMetaData} tableVariant={props.tableVariant} isOpen={gotoIsOpen} closeHandler={closeGoto} mayClose={props.mayClose} subHeader={props.subHeader} />
</React.Fragment> </React.Fragment>

View File

@ -22,7 +22,7 @@
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
import {QTableSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableSection"; import {QTableSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableSection";
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData"; 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 Card from "@mui/material/Card";
import Icon from "@mui/material/Icon"; import Icon from "@mui/material/Icon";
import {Theme} from "@mui/material/styles"; import {Theme} from "@mui/material/styles";
@ -76,12 +76,12 @@ function QRecordSidebar({tableSections, widgetMetaDataList, light, stickyTop}: P
return ( return (
<Card sx={{borderRadius: "0.75rem", position: "sticky", top: stickyTop, overflow: "auto", maxHeight: "calc(100vh - 2rem)"}}> <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"}}> <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) => ( 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}> <Box key={`section-${entry.name}`} component="li" pt={key === 0 ? 0 : 1}>
<MDTypography <MDTypography
variant="button" variant="button"
@ -112,7 +112,7 @@ function QRecordSidebar({tableSections, widgetMetaDataList, light, stickyTop}: P
</MDTypography> </MDTypography>
</Box> </Box>
</HashLink> </Box>
)) : null )) : null
} }
</Box> </Box>

View File

@ -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 {QJobComplete} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobComplete";
import {QJobError} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobError"; import {QJobError} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobError";
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord"; 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 Dialog from "@mui/material/Dialog";
import DialogActions from "@mui/material/DialogActions"; import DialogActions from "@mui/material/DialogActions";
import DialogContent from "@mui/material/DialogContent"; import DialogContent from "@mui/material/DialogContent";
@ -94,12 +95,12 @@ function SavedViews({qController, metaData, tableMetaData, currentSavedView, tab
const {accentColor, accentColorLight, userId: currentUserId} = useContext(QContext); const {accentColor, accentColorLight, userId: currentUserId} = useContext(QContext);
///////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////
// this component is used by <RecordQuery> - but that component has different usages - // // 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 // // 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 // // under the FilterAndColumnsSetupWidget). So, there are some behaviors we only want when //
// on the full-fledged query screen, such as changing the URL with saved view ids. // // we're on the full-fledged query screen, such as changing the URL with saved view ids. //
///////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////
const isQueryScreen = queryScreenUsage == "queryScreen"; const isQueryScreen = queryScreenUsage == "queryScreen";
const openSavedViewsMenu = (event: any) => setSavedViewsMenu(event.currentTarget); const openSavedViewsMenu = (event: any) => setSavedViewsMenu(event.currentTarget);

View File

@ -22,16 +22,16 @@
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData"; import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
import {Box, Skeleton} from "@mui/material"; import {Box, Skeleton} from "@mui/material";
import React from "react";
import {BlockData} from "qqq/components/widgets/blocks/BlockModels"; import {BlockData} from "qqq/components/widgets/blocks/BlockModels";
import WidgetBlock from "qqq/components/widgets/WidgetBlock"; import WidgetBlock from "qqq/components/widgets/WidgetBlock";
import React from "react";
interface CompositeData interface CompositeData
{ {
blocks: BlockData[]; blocks: BlockData[];
styleOverrides?: any; styleOverrides?: any;
layout?: string layout?: string;
} }
@ -57,7 +57,14 @@ export default function CompositeWidget({widgetMetaData, data}: CompositeWidgetP
//////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////
let layout = data?.layout; let layout = data?.layout;
let boxStyle: any = {}; 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.display = "flex";
boxStyle.flexDirection = "row"; boxStyle.flexDirection = "row";
@ -68,7 +75,7 @@ export default function CompositeWidget({widgetMetaData, data}: CompositeWidgetP
{ {
boxStyle.display = "flex"; boxStyle.display = "flex";
boxStyle.flexDirection = "row"; boxStyle.flexDirection = "row";
boxStyle.justifyContent = "space-between" boxStyle.justifyContent = "space-between";
boxStyle.gap = "0.25rem"; boxStyle.gap = "0.25rem";
} }
else if (layout == "TABLE_SUB_ROW_DETAILS") else if (layout == "TABLE_SUB_ROW_DETAILS")

View File

@ -40,17 +40,17 @@ import DataBagViewer from "qqq/components/widgets/misc/DataBagViewer";
import DividerWidget from "qqq/components/widgets/misc/Divider"; import DividerWidget from "qqq/components/widgets/misc/Divider";
import DynamicFormWidget from "qqq/components/widgets/misc/DynamicFormWidget"; import DynamicFormWidget from "qqq/components/widgets/misc/DynamicFormWidget";
import FieldValueListWidget from "qqq/components/widgets/misc/FieldValueListWidget"; 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 PivotTableSetupWidget from "qqq/components/widgets/misc/PivotTableSetupWidget";
import QuickSightChart from "qqq/components/widgets/misc/QuickSightChart"; import QuickSightChart from "qqq/components/widgets/misc/QuickSightChart";
import RecordGridWidget from "qqq/components/widgets/misc/RecordGridWidget"; 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 ScriptViewer from "qqq/components/widgets/misc/ScriptViewer";
import StepperCard from "qqq/components/widgets/misc/StepperCard"; import StepperCard from "qqq/components/widgets/misc/StepperCard";
import USMapWidget from "qqq/components/widgets/misc/USMapWidget"; import USMapWidget from "qqq/components/widgets/misc/USMapWidget";
import ParentWidget from "qqq/components/widgets/ParentWidget"; import ParentWidget from "qqq/components/widgets/ParentWidget";
import MultiStatisticsCard from "qqq/components/widgets/statistics/MultiStatisticsCard"; import MultiStatisticsCard from "qqq/components/widgets/statistics/MultiStatisticsCard";
import StatisticsCard from "qqq/components/widgets/statistics/StatisticsCard"; 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 WidgetBlock from "qqq/components/widgets/WidgetBlock";
import ProcessRun from "qqq/pages/processes/ProcessRun"; import ProcessRun from "qqq/pages/processes/ProcessRun";
import Client from "qqq/utils/qqq/Client"; import Client from "qqq/utils/qqq/Client";
@ -293,7 +293,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco
} }
return ( 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" && ( haveLoadedParams && widgetMetaData.type === "parentWidget" && (
<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" && ( widgetMetaData.type === "stackedBarChart" && (
<Widget <Widget
@ -584,17 +598,19 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco
) )
} }
{ {
widgetMetaData.type === "reportSetup" && ( widgetMetaData.type === "filterAndColumnsSetup" && (
widgetData && widgetData[i] && widgetData[i].queryParams && 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" && ( widgetMetaData.type === "pivotTableSetup" && (
widgetData && widgetData[i] && widgetData[i].queryParams && widgetData && widgetData[i] && widgetData[i].queryParams &&
<PivotTableSetupWidget isEditable={false} widgetMetaData={widgetMetaData} recordValues={convertQRecordValuesFromMapToObject(record)} onSaveCallback={() => <PivotTableSetupWidget isEditable={false} widgetMetaData={widgetMetaData} recordValues={convertQRecordValuesFromMapToObject(record)} onSaveCallback={() =>
{}} /> {
}} />
) )
} }
{ {

View File

@ -21,14 +21,16 @@
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData"; import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import Icon from "@mui/material/Icon"; import Icon from "@mui/material/Icon";
import Tooltip from "@mui/material/Tooltip/Tooltip"; import Tooltip from "@mui/material/Tooltip/Tooltip";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import React from "react";
import colors from "qqq/assets/theme/base/colors"; import colors from "qqq/assets/theme/base/colors";
import {WidgetData} from "qqq/components/widgets/Widget"; import {WidgetData} from "qqq/components/widgets/Widget";
import ValueUtils from "qqq/utils/qqq/ValueUtils"; import ValueUtils from "qqq/utils/qqq/ValueUtils";
import React from "react";
import {Link} from "react-router-dom";
/******************************************************************************* /*******************************************************************************
** Utility class used by Widgets ** 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>);
};
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/

View File

@ -41,7 +41,7 @@ export default function NumberIconBadgeBlock({widgetMetaData, data}: StandardBlo
{ {
data.values.iconName && data.values.iconName &&
<BlockElementWrapper metaData={widgetMetaData} data={data} slot="icon"> <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> </BlockElementWrapper>
} }
</div>); </div>);

View File

@ -22,6 +22,9 @@
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData"; 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 {QFilterOrderBy} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterOrderBy";
import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter"; import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter";
import {Alert, Collapse} from "@mui/material"; import {Alert, Collapse} from "@mui/material";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
@ -42,7 +45,7 @@ import Client from "qqq/utils/qqq/Client";
import FilterUtils from "qqq/utils/qqq/FilterUtils"; import FilterUtils from "qqq/utils/qqq/FilterUtils";
import React, {useContext, useEffect, useRef, useState} from "react"; import React, {useContext, useEffect, useRef, useState} from "react";
interface ReportSetupWidgetProps interface FilterAndColumnsSetupWidgetProps
{ {
isEditable: boolean; isEditable: boolean;
widgetMetaData: QWidgetMetaData; widgetMetaData: QWidgetMetaData;
@ -50,7 +53,7 @@ interface ReportSetupWidgetProps
onSaveCallback?: (values: { [name: string]: any }) => void; onSaveCallback?: (values: { [name: string]: any }) => void;
} }
ReportSetupWidget.defaultProps = { FilterAndColumnsSetupWidget.defaultProps = {
onSaveCallback: null onSaveCallback: null
}; };
@ -80,9 +83,10 @@ const qController = Client.getInstance();
/******************************************************************************* /*******************************************************************************
** Component for editing the main setup of a report - that is: filter & columns ** 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 [modalOpen, setModalOpen] = useState(false);
const [hideColumns, setHideColumns] = useState(widgetMetaData?.defaultValues?.has("hideColumns") && widgetMetaData?.defaultValues?.get("hideColumns"));
const [tableMetaData, setTableMetaData] = useState(null as QTableMetaData); const [tableMetaData, setTableMetaData] = useState(null as QTableMetaData);
const [alertContent, setAlertContent] = useState(null as string); const [alertContent, setAlertContent] = useState(null as string);
@ -101,15 +105,36 @@ export default function ReportSetupWidget({isEditable, widgetMetaData, recordVal
///////////////////////////// /////////////////////////////
// load values from record // // load values from record //
///////////////////////////// /////////////////////////////
let queryFilter = recordValues["queryFilterJson"] && JSON.parse(recordValues["queryFilterJson"]) as QQueryFilter; let columns: QQueryColumns = null;
let usingDefaultEmptyFilter = false; let usingDefaultEmptyFilter = false;
let queryFilter = recordValues["queryFilterJson"] && JSON.parse(recordValues["queryFilterJson"]) as QQueryFilter;
if (!queryFilter) if (!queryFilter)
{ {
queryFilter = new QQueryFilter(); queryFilter = new QQueryFilter();
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// if there is no queryFilter provided, see if there are default fields from which a query should be seeded //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
const defaultFilterFields = getDefaultFilterFieldNames(widgetMetaData);
if (defaultFilterFields?.length > 0)
{
defaultFilterFields.forEach((fieldName: string) =>
{
if (recordValues[fieldName])
{
queryFilter.addCriteria(new QFilterCriteria(fieldName, QCriteriaOperator.EQUALS, [recordValues[fieldName]]));
}
});
queryFilter.addOrderBy(new QFilterOrderBy("id", false));
queryFilter = Object.assign({}, queryFilter);
}
else
{
usingDefaultEmptyFilter = true; usingDefaultEmptyFilter = true;
} }
}
let columns: QQueryColumns = null;
if (recordValues["columnsJson"]) if (recordValues["columnsJson"])
{ {
columns = QQueryColumns.buildFromJSON(recordValues["columnsJson"]); columns = QQueryColumns.buildFromJSON(recordValues["columnsJson"]);
@ -120,11 +145,20 @@ export default function ReportSetupWidget({isEditable, widgetMetaData, recordVal
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
useEffect(() => 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 () => (async () =>
{ {
const tableMetaData = await qController.loadTableMetaData(recordValues["tableName"]); const tableMetaData = await qController.loadTableMetaData(tableName);
setTableMetaData(tableMetaData); setTableMetaData(tableMetaData);
const queryFilterForFrontend = Object.assign({}, queryFilter); const queryFilterForFrontend = Object.assign({}, queryFilter);
@ -132,7 +166,21 @@ export default function ReportSetupWidget({isEditable, widgetMetaData, recordVal
setFrontendQueryFilter(queryFilterForFrontend); setFrontendQueryFilter(queryFilterForFrontend);
})(); })();
} }
}, [recordValues]); }, [JSON.stringify(recordValues)]);
/*******************************************************************************
**
*******************************************************************************/
function getDefaultFilterFieldNames(widgetMetaData: QWidgetMetaData)
{
if (widgetMetaData?.defaultValues?.has("filterDefaultFieldNames"))
{
return (widgetMetaData.defaultValues.get("filterDefaultFieldNames").split(","));
}
return ([]);
}
/******************************************************************************* /*******************************************************************************
@ -140,8 +188,27 @@ export default function ReportSetupWidget({isEditable, widgetMetaData, recordVal
*******************************************************************************/ *******************************************************************************/
function openEditor() 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 add Additional Order Filters: '" + missingRequiredFields.join(", ") + "'");
return;
}
if (recordValues["tableName"]) if (recordValues["tableName"])
{ {
setAlertContent(null);
setModalOpen(true); setModalOpen(true);
} }
} }
@ -271,9 +338,16 @@ export default function ReportSetupWidget({isEditable, widgetMetaData, recordVal
const selectTableFirstTooltipTitle = tableMetaData ? null : "You must select a table before you can set up your report filters and columns"; const selectTableFirstTooltipTitle = tableMetaData ? null : "You must select a table before you can set up your report filters and columns";
const labelAdditionalElementsRight: JSX.Element[] = []; const labelAdditionalElementsRight: JSX.Element[] = [];
if (isEditable) if (isEditable)
{
if (!hideColumns)
{ {
labelAdditionalElementsRight.push(<HeaderLinkButtonComponent key="filterAndColumnsHeader" label="Edit Filters and Columns" onClickCallback={openEditor} disabled={tableMetaData == null} disabledTooltip={selectTableFirstTooltipTitle} />); 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} />);
}
}
return (<Widget widgetMetaData={widgetMetaData} labelAdditionalElementsRight={labelAdditionalElementsRight}> return (<Widget widgetMetaData={widgetMetaData} labelAdditionalElementsRight={labelAdditionalElementsRight}>
@ -306,11 +380,12 @@ export default function ReportSetupWidget({isEditable, widgetMetaData, recordVal
</Tooltip> </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> </Box>
{!hideColumns && (
<Box pt="1rem"> <Box pt="1rem">
<h5>Columns</h5> <h5>Columns</h5>
<Box display="flex" flexWrap="wrap" fontSize="1rem"> <Box display="flex" flexWrap="wrap" fontSize="1rem">
@ -328,12 +403,13 @@ export default function ReportSetupWidget({isEditable, widgetMetaData, recordVal
</Tooltip> </Tooltip>
} }
{ {
!isEditable && <Box color={colors.gray.main}>Your report has no columns.</Box> !isEditable && <Box color={colors.gray.main}>No columns are selected.</Box>
} }
</Box> </Box>
} }
</Box> </Box>
</Box> </Box>
)}
{ {
modalOpen && modalOpen &&
<Modal open={modalOpen} onClose={(event, reason) => closeEditor(event, reason)}> <Modal open={modalOpen} onClose={(event, reason) => closeEditor(event, reason)}>

View File

@ -39,9 +39,9 @@ import colors from "qqq/assets/theme/base/colors";
import {QCancelButton, QSaveButton} from "qqq/components/buttons/DefaultButtons"; import {QCancelButton, QSaveButton} from "qqq/components/buttons/DefaultButtons";
import FieldAutoComplete from "qqq/components/misc/FieldAutoComplete"; import FieldAutoComplete from "qqq/components/misc/FieldAutoComplete";
import HelpContent, {hasHelpContent} from "qqq/components/misc/HelpContent"; 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 {PivotTableGroupByElement} from "qqq/components/widgets/misc/PivotTableGroupByElement";
import {PivotTableValueElement} from "qqq/components/widgets/misc/PivotTableValueElement"; 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 Widget, {HeaderToggleComponent} from "qqq/components/widgets/Widget";
import {PivotObjectKey, PivotTableDefinition, PivotTableFunction, pivotTableFunctionLabels, PivotTableGroupBy, PivotTableValue} from "qqq/models/misc/PivotTableDefinitionModels"; import {PivotObjectKey, PivotTableDefinition, PivotTableFunction, pivotTableFunctionLabels, PivotTableGroupBy, PivotTableValue} from "qqq/models/misc/PivotTableDefinitionModels";
import QQueryColumns from "qqq/models/query/QQueryColumns"; import QQueryColumns from "qqq/models/query/QQueryColumns";

View File

@ -30,8 +30,6 @@ import TableContainer from "@mui/material/TableContainer";
import TableRow from "@mui/material/TableRow"; import TableRow from "@mui/material/TableRow";
import Tooltip from "@mui/material/Tooltip"; import Tooltip from "@mui/material/Tooltip";
import parse from "html-react-parser"; 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 colors from "qqq/assets/theme/base/colors";
import MDInput from "qqq/components/legacy/MDInput"; import MDInput from "qqq/components/legacy/MDInput";
import MDPagination from "qqq/components/legacy/MDPagination"; 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 ImageCell from "qqq/components/widgets/tables/cells/ImageCell";
import {TableDataInput} from "qqq/components/widgets/tables/TableCard"; import {TableDataInput} from "qqq/components/widgets/tables/TableCard";
import WidgetBlock from "qqq/components/widgets/WidgetBlock"; 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 interface Props
{ {
@ -327,7 +327,7 @@ function DataTable({
includeHead && ( includeHead && (
<Box component="thead" sx={{position: "sticky", top: 0, background: "white", zIndex: 10}}> <Box component="thead" sx={{position: "sticky", top: 0, background: "white", zIndex: 10}}>
{headerGroups.map((headerGroup: any, i: number) => ( {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) => ( {headerGroup.headers.map((column: any) => (
column.type !== "hidden" && ( column.type !== "hidden" && (
<DataTableHeadCell <DataTableHeadCell
@ -453,7 +453,7 @@ function DataTable({
</TableBody> </TableBody>
</Table> </Table>
</Box></Box> </Box></Box>;
} }
return ( return (

View File

@ -28,13 +28,13 @@ import TableBody from "@mui/material/TableBody";
import TableContainer from "@mui/material/TableContainer"; import TableContainer from "@mui/material/TableContainer";
import TableRow from "@mui/material/TableRow"; import TableRow from "@mui/material/TableRow";
import parse from "html-react-parser"; import parse from "html-react-parser";
import React, {useEffect, useState} from "react";
import MDTypography from "qqq/components/legacy/MDTypography"; import MDTypography from "qqq/components/legacy/MDTypography";
import DataTableBodyCell from "qqq/components/widgets/tables/cells/DataTableBodyCell"; import DataTableBodyCell from "qqq/components/widgets/tables/cells/DataTableBodyCell";
import DataTableHeadCell from "qqq/components/widgets/tables/cells/DataTableHeadCell"; import DataTableHeadCell from "qqq/components/widgets/tables/cells/DataTableHeadCell";
import DefaultCell from "qqq/components/widgets/tables/cells/DefaultCell"; import DefaultCell from "qqq/components/widgets/tables/cells/DefaultCell";
import DataTable from "qqq/components/widgets/tables/DataTable"; import DataTable from "qqq/components/widgets/tables/DataTable";
import Client from "qqq/utils/qqq/Client"; 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 export interface TableDataInput
{ {
columns: { [key: string]: any }[]; columns: { [key: string]: any }[];
columnHeaderTooltips?: { [columnName: string]: string | JSX.Element } columnHeaderTooltips?: { [columnName: string]: string | JSX.Element };
rows: { [key: string]: any }[]; rows: { [key: string]: any }[];
} }
@ -63,6 +63,7 @@ interface Props
} }
const qController = Client.getInstance(); const qController = Client.getInstance();
function TableCard({noRowsFoundHTML, data, rowsPerPage, hidePaginationDropdown, fixedStickyLastRow, fixedHeight, widgetMetaData}: Props): JSX.Element function TableCard({noRowsFoundHTML, data, rowsPerPage, hidePaginationDropdown, fixedStickyLastRow, fixedHeight, widgetMetaData}: Props): JSX.Element
{ {
const [qInstance, setQInstance] = useState(null as QInstance); const [qInstance, setQInstance] = useState(null as QInstance);
@ -108,7 +109,7 @@ function TableCard({noRowsFoundHTML, data, rowsPerPage, hidePaginationDropdown,
<TableContainer sx={{boxShadow: "none"}}> <TableContainer sx={{boxShadow: "none"}}>
<Table> <Table>
<Box component="thead"> <Box component="thead">
<TableRow key="header"> <TableRow sx={{alignItems: "flex-end"}} key="header">
{Array(8).fill(0).map((_, i) => {Array(8).fill(0).map((_, i) =>
<DataTableHeadCell key={`head-${i}`} sorted={false} width="auto" align="center"> <DataTableHeadCell key={`head-${i}`} sorted={false} width="auto" align="center">
<Skeleton width="100%" /> <Skeleton width="100%" />

View File

@ -23,7 +23,6 @@
import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData"; import {QWidgetMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QWidgetMetaData";
// @ts-ignore // @ts-ignore
import {htmlToText} from "html-to-text"; import {htmlToText} from "html-to-text";
import React, {useContext, useEffect, useState} from "react";
import QContext from "QContext"; import QContext from "QContext";
import HelpContent, {hasHelpContent} from "qqq/components/misc/HelpContent"; import HelpContent, {hasHelpContent} from "qqq/components/misc/HelpContent";
import TableCard from "qqq/components/widgets/tables/TableCard"; 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 {WidgetUtils} from "qqq/components/widgets/WidgetUtils";
import HtmlUtils from "qqq/utils/HtmlUtils"; import HtmlUtils from "qqq/utils/HtmlUtils";
import ValueUtils from "qqq/utils/qqq/ValueUtils"; import ValueUtils from "qqq/utils/qqq/ValueUtils";
import React, {useContext, useEffect, useState} from "react";
interface Props interface Props
{ {
@ -40,8 +40,7 @@ interface Props
isChild?: boolean; isChild?: boolean;
} }
TableWidget.defaultProps = { TableWidget.defaultProps = {};
};
function TableWidget(props: Props): JSX.Element function TableWidget(props: Props): JSX.Element
{ {
@ -105,7 +104,7 @@ function TableWidget(props: Props): JSX.Element
setCsv(csv); setCsv(csv);
const fileName = WidgetUtils.makeExportFileName(props.widgetData, props.widgetMetaData); const fileName = WidgetUtils.makeExportFileName(props.widgetData, props.widgetMetaData);
setFileName(fileName) setFileName(fileName);
console.log(`useEffect, setting fileName ${fileName}`); console.log(`useEffect, setting fileName ${fileName}`);
} }
@ -126,11 +125,15 @@ function TableWidget(props: Props): JSX.Element
} }
else else
{ {
alert("There is no data available to export.") alert("There is no data available to export.");
}
} }
};
const labelAdditionalElementsLeft: JSX.Element[] = []; const labelAdditionalElementsLeft: JSX.Element[] = [];
if (props.widgetData?.linkText && props.widgetData?.linkURL)
{
labelAdditionalElementsLeft.push(WidgetUtils.generateLabelLink(props.widgetData?.linkText, props.widgetData?.linkURL));
}
if (props.widgetMetaData?.showExportButton) if (props.widgetMetaData?.showExportButton)
{ {
labelAdditionalElementsLeft.push(WidgetUtils.generateExportButton(onExportClick)); labelAdditionalElementsLeft.push(WidgetUtils.generateExportButton(onExportClick));
@ -139,10 +142,10 @@ function TableWidget(props: Props): JSX.Element
////////////////////////////////////////////////////// //////////////////////////////////////////////////////
// look for column-header tooltips from helpContent // // 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 ?? []) for (let column of props.widgetData?.columns ?? [])
{ {
const helpRoles = ["ALL_SCREENS"] const helpRoles = ["ALL_SCREENS"];
const slotName = `columnHeader=${column.accessor}`; const slotName = `columnHeader=${column.accessor}`;
const showHelp = helpHelpActive || hasHelpContent(props.widgetMetaData?.helpContent?.get(slotName), helpRoles); const showHelp = helpHelpActive || hasHelpContent(props.widgetMetaData?.helpContent?.get(slotName), helpRoles);

View File

@ -54,7 +54,7 @@ import DynamicFormUtils from "qqq/components/forms/DynamicFormUtils";
import MDButton from "qqq/components/legacy/MDButton"; import MDButton from "qqq/components/legacy/MDButton";
import MDProgress from "qqq/components/legacy/MDProgress"; import MDProgress from "qqq/components/legacy/MDProgress";
import MDTypography from "qqq/components/legacy/MDTypography"; 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 QRecordSidebar from "qqq/components/misc/RecordSidebar";
import {GoogleDriveFolderPickerWrapper} from "qqq/components/processes/GoogleDriveFolderPickerWrapper"; import {GoogleDriveFolderPickerWrapper} from "qqq/components/processes/GoogleDriveFolderPickerWrapper";
import ProcessSummaryResults from "qqq/components/processes/ProcessSummaryResults"; 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 => let formikSetFieldValueFunction = (field: string, value: any, shouldValidate?: boolean): void =>
{ {
} };
function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, isReport, recordIds, closeModalHandler, forceReInit, overrideLabel}: Props): JSX.Element function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, isReport, recordIds, closeModalHandler, forceReInit, overrideLabel}: Props): JSX.Element
{ {
@ -136,7 +136,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
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 // // for setting the processError state - call this function, which will also set the isUserFacingError state //
@ -311,12 +311,12 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
const queryStringParts: string[] = []; const queryStringParts: string[] = [];
for (let name in processValues) for (let name in processValues)
{ {
queryStringParts.push(`${name}=${encodeURIComponent(processValues[name])}`) queryStringParts.push(`${name}=${encodeURIComponent(processValues[name])}`);
} }
const renderedWidget = (<Box m={-2}> const renderedWidget = (<Box m={-2}>
<DashboardWidgets widgetMetaDataList={[widgetMetaData]} omitWrappingGridContainer={true} childUrlParams={queryStringParts.join("&")} /> <DashboardWidgets widgetMetaDataList={[widgetMetaData]} omitWrappingGridContainer={true} childUrlParams={queryStringParts.join("&")} />
</Box>) </Box>);
renderedWidgets[activeStep.name][widgetName] = renderedWidget; renderedWidgets[activeStep.name][widgetName] = renderedWidget;
return renderedWidget; return renderedWidget;
} }
@ -367,8 +367,8 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
</MDTypography> </MDTypography>
<Box component="div" py={3}> <Box component="div" py={3}>
<Grid container justifyContent="flex-end" spacing={3}> <Grid container justifyContent="flex-end" spacing={3}>
{isModal ? <QCancelButton onClickHandler={handleCancelClicked} disabled={false} label="Close" /> {isModal ? <QCancelButton onClickHandler={() => handleCancelClicked(true)} disabled={false} label="Close" />
: !isWidget && <QCancelButton onClickHandler={handleCancelClicked} disabled={false} /> : !isWidget && <QCancelButton onClickHandler={() => handleCancelClicked(true)} disabled={false} />
} }
</Grid> </Grid>
</Box> </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(step)-level help content //
/////////////////////////////// /////////////////////////////////////
// screen-level help content //
///////////////////////////////
let helpRoles = ["PROCESS_SCREEN", "ALL_SCREENS"]; 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 ( return (
<> <>
@ -479,13 +477,10 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
} }
{ {
/* showHelp &&
// todo not commit - not ready - need process (or screen) meta-data to have helpContents... <Box fontSize={"0.875rem"} color={colors.blueGray.main} pb={2}>
formattedHelpContent &&
<Box px={"1.5rem"} fontSize={"0.875rem"} color={colors.blueGray.main}>
{formattedHelpContent} {formattedHelpContent}
</Box> </Box>
*/
} }
{ {
@ -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); setFormFields(newDynamicFormFields);
setValidationScheme(Yup.object().shape(newFormValidations)); setValidationScheme(Yup.object().shape(newFormValidations));
@ -1091,9 +1086,13 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
////////////////////////////////// //////////////////////////////////
for (let key in qJobComplete.values) for (let key in qJobComplete.values)
{ {
if (Object.hasOwn(formFields, key))
{
console.log(`(re)setting form field [${key}] to [${qJobComplete.values[key]}]`);
formikSetFieldValueFunction(key, qJobComplete.values[key]); formikSetFieldValueFunction(key, qJobComplete.values[key]);
} }
} }
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// if the process step sent a new frontend-step-list, then refresh what we have in state (constructing new full model objects) // // if the process step sent a new frontend-step-list, then refresh what we have in state (constructing new full model objects) //
@ -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) if (isModal && closeModalHandler)
{ {
closeModalHandler(null, "cancelClicked"); closeModalHandler(null, "cancelClicked");
@ -1416,6 +1427,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
navigate(path, {replace: true}); navigate(path, {replace: true});
}; };
const mainCardStyles: any = {}; const mainCardStyles: any = {};
const formStyles: any = {}; const formStyles: any = {};
mainCardStyles.minHeight = `calc(100vh - ${isModal ? 150 : 400}px)`; mainCardStyles.minHeight = `calc(100vh - ${isModal ? 150 : 400}px)`;
@ -1527,7 +1539,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
)} )}
{ {
noMoreSteps && <QCancelButton noMoreSteps && <QCancelButton
onClickHandler={handleCancelClicked} onClickHandler={() => handleCancelClicked(true)}
label={isModal ? "Close" : "Return"} label={isModal ? "Close" : "Return"}
iconName={isModal ? "cancel" : "arrow_back"} iconName={isModal ? "cancel" : "arrow_back"}
disabled={isSubmitting} /> disabled={isSubmitting} />
@ -1538,7 +1550,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
<Grid container justifyContent="flex-end" spacing={3}> <Grid container justifyContent="flex-end" spacing={3}>
{ {
!isWidget && ( !isWidget && (
<QCancelButton onClickHandler={handleCancelClicked} disabled={isSubmitting} /> <QCancelButton onClickHandler={() => handleCancelClicked(false)} disabled={isSubmitting} />
) )
} }
<QSubmitButton label={nextButtonLabel} iconName={nextButtonIcon} disabled={isSubmitting} /> <QSubmitButton label={nextButtonLabel} iconName={nextButtonIcon} disabled={isSubmitting} />
@ -1553,7 +1565,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
</Box> </Box>
</Card> </Card>
</Form> </Form>
) );
}} }}
</Formik> </Formik>
); );

View File

@ -535,7 +535,7 @@ function RecordView({table, record: overrideRecord, launchProcess}: Props): JSX.
setPageHeader(record.recordLabel); setPageHeader(record.recordLabel);
if (!launchingProcess) if (!launchingProcess && !activeModalProcess)
{ {
try try
{ {

View File

@ -421,6 +421,14 @@ input[type="search"]::-webkit-search-results-decoration
font-size: 2rem !important; font-size: 2rem !important;
} }
.dashboard-order-release-icon
{
font-size: 1.5rem !important;
position: relative;
top: -5px;
margin-right: 8px;
}
.dashboard-schedule-icon .dashboard-schedule-icon
{ {
font-size: 1.1rem !important; font-size: 1.1rem !important;
@ -653,6 +661,11 @@ input[type="search"]::-webkit-search-results-decoration
min-height: unset !important; min-height: unset !important;
} }
.MuiDataGrid-columnHeaders
{
scrollbar-gutter: stable;
}
/* new style for toggle buttons */ /* new style for toggle buttons */
.MuiToggleButtonGroup-root .MuiToggleButtonGroup-root
{ {
@ -708,3 +721,66 @@ input[type="search"]::-webkit-search-results-decoration
color: white; color: white;
background-color: #0062FF !important; 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;
}

View File

@ -822,7 +822,7 @@
"reportSetupWidget": { "reportSetupWidget": {
"name": "reportSetupWidget", "name": "reportSetupWidget",
"label": "Filters and Columns", "label": "Filters and Columns",
"type": "reportSetup", "type": "filterAndColumnsSetup",
"isCard": true, "isCard": true,
"storeDropdownSelections": false, "storeDropdownSelections": false,
"showReloadButton": true, "showReloadButton": true,