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

View File

@ -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,6 +119,7 @@ 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);
@ -282,6 +283,8 @@ function EntityForm(props: Props): JSX.Element
setRenderedWidgetSections(newRenderedWidgetSections);
forceUpdate();
setModalDataChangedCount(modalDataChangedCounter + 1);
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
** 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;
return <RecordGridWidget
key={new Date().getTime()} // added so that editing values actually re-renders...
key={`${formValues["tableName"]}-${modalDataChangedCounter}`}
widgetMetaData={widgetMetaData}
data={widgetData}
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...
isEditable={true}
widgetMetaData={widgetMetaData}
recordValues={formValues}
onSaveCallback={setFormFieldValuesFromWidget}
/>
/>;
}
if (widgetMetaData.type == "pivotTableSetup")
@ -407,7 +418,7 @@ function EntityForm(props: Props): JSX.Element
widgetMetaData={widgetMetaData}
recordValues={formValues}
onSaveCallback={setFormFieldValuesFromWidget}
/>
/>;
}
if (widgetMetaData.type == "dynamicForm")
@ -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>);
}
@ -488,7 +499,7 @@ function EntityForm(props: Props): JSX.Element
/////////////////////////////////////////////////
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.type == "childRecordList" && widget.defaultValues?.has("manageAssociationName"))
@ -496,7 +507,7 @@ function EntityForm(props: Props): JSX.Element
return (true);
}
if(widget.type == "reportSetup" || widget.type == "pivotTableSetup" || widget.type == "dynamicForm")
if (widget.type == "filterAndColumnsSetup" || widget.type == "pivotTableSetup" || widget.type == "dynamicForm")
{
return (true);
}
@ -719,7 +730,7 @@ function EntityForm(props: Props): JSX.Element
}
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));
newRenderedWidgetSections[section.widgetName] = getWidgetSection(widgetMetaData, widgetData);
@ -1005,14 +1016,14 @@ function EntityForm(props: Props): JSX.Element
const queryParamsArray: string[] = [];
if (props.id)
{
queryParamsArray.push(`${tableMetaData.primaryKeyField}=${encodeURIComponent(props.id)}`)
queryParamsArray.push(`${tableMetaData.primaryKeyField}=${encodeURIComponent(props.id)}`);
}
if (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);
}
setFormValues(formValues)
setFormValues(formValues);
setFormValuesJSON(JSON.stringify(values));
}
}

View File

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

View File

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

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 {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. //
/////////////////////////////////////////////////////////////////////////////////////////
// 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);

View File

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

View File

@ -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";
@ -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={() =>
{}} />
{
}} />
)
}
{

View File

@ -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>);
};
/*******************************************************************************
**
*******************************************************************************/

View File

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

View File

@ -22,6 +22,9 @@
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 {QFilterOrderBy} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterOrderBy";
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 +45,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 +53,7 @@ interface ReportSetupWidgetProps
onSaveCallback?: (values: { [name: string]: any }) => void;
}
ReportSetupWidget.defaultProps = {
FilterAndColumnsSetupWidget.defaultProps = {
onSaveCallback: null
};
@ -80,9 +83,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 +105,36 @@ 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;
if (!queryFilter)
{
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;
}
}
let columns: QQueryColumns = null;
if (recordValues["columnsJson"])
{
columns = QQueryColumns.buildFromJSON(recordValues["columnsJson"]);
@ -120,11 +145,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 +166,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 +188,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 add Additional Order Filters: '" + missingRequiredFields.join(", ") + "'");
return;
}
if (recordValues["tableName"])
{
setAlertContent(null);
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 labelAdditionalElementsRight: JSX.Element[] = [];
if (isEditable)
{
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} />);
}
}
return (<Widget widgetMetaData={widgetMetaData} labelAdditionalElementsRight={labelAdditionalElementsRight}>
@ -306,11 +380,12 @@ 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>
{!hideColumns && (
<Box pt="1rem">
<h5>Columns</h5>
<Box display="flex" flexWrap="wrap" fontSize="1rem">
@ -328,12 +403,13 @@ export default function ReportSetupWidget({isEditable, widgetMetaData, recordVal
</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>
)}
{
modalOpen &&
<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 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";

View File

@ -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
{
@ -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
@ -453,7 +453,7 @@ function DataTable({
</TableBody>
</Table>
</Box></Box>
</Box></Box>;
}
return (

View File

@ -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%" />

View File

@ -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
{
@ -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}`);
}
@ -126,11 +125,15 @@ function TableWidget(props: Props): JSX.Element
}
else
{
alert("There is no data available to export.")
}
alert("There is no data available to export.");
}
};
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)
{
labelAdditionalElementsLeft.push(WidgetUtils.generateExportButton(onExportClick));
@ -139,10 +142,10 @@ 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);

View File

@ -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
{
@ -136,7 +136,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
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 //
@ -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>
*/
}
{
@ -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));
@ -1091,9 +1086,13 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
//////////////////////////////////
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]);
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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)
{
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)`;
@ -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>
);

View File

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

View File

@ -421,6 +421,14 @@ input[type="search"]::-webkit-search-results-decoration
font-size: 2rem !important;
}
.dashboard-order-release-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
{
@ -708,3 +721,66 @@ input[type="search"]::-webkit-search-results-decoration
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;
}

View File

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