Compare commits

...

11 Commits

Author SHA1 Message Date
30991cb34e CE-881 - Fix parsing hash (e.g., for defaultValues) in case it has a # in it 2024-04-02 15:46:23 -05:00
2fd6272ea3 CE-881 - Update download component to understand storageTableName & storageReference, as alternative to serverFilePath 2024-04-01 16:05:16 -05:00
b63d74f785 Merged main into feature/CE-881-create-basic-saved-reports 2024-03-29 18:36:51 -05:00
7e15f4601d Merged feature/quartz-scheduler into main 2024-03-29 18:35:41 -05:00
cdb61695d1 Merge pull request #50 from Kingsrook/feature/CE-1120-bug-order-statuses-not
CE-1120: updated to handle errors on join tables (specifically was ha…
2024-03-28 15:20:10 -05:00
5e3991d9ae CE-1120: updated to handle errors on join tables (specifically was happening with deposco customer orders) 2024-03-28 15:09:56 -05:00
ff946df461 CE-881 - Add views menu option to punch into create-saved-view screen w/ pre-populated form 2024-03-27 20:16:06 -05:00
f1826c81a9 Merge pull request #48 from Kingsrook/bugfix/null-field-names-in-criteria
Strip away null field names in criteria (e.g., from incomplete advanc…
2024-03-27 20:14:01 -05:00
230aaeef8c Strip away null field names in criteria (e.g., from incomplete advanced filters) when storing in local storage, in saved views, and any time we load a view. 2024-03-21 16:41:09 -05:00
c08696b3a1 Remove todo no longer needed 2024-03-20 10:34:37 -05:00
edab918763 CE-969: fixed flex wrapping on advanced queries, recursive calls to 'clean values' and 'prep subquery for backend' 2024-03-18 19:39:19 -05:00
9 changed files with 254 additions and 141 deletions

View File

@ -63,7 +63,7 @@ interface Props
disabledFields: { [key: string]: boolean } | string[];
isCopy?: boolean;
onSubmitCallback?: (values: any) => void;
overrideHeading?: string
overrideHeading?: string;
}
EntityForm.defaultProps = {
@ -128,21 +128,26 @@ function EntityForm(props: Props): JSX.Element
{
try
{
const parts = hashParts[i].split("=")
if (parts.length > 1 && parts[0] == "defaultValues")
const parts = hashParts[i].split("=");
if (parts.length > 1)
{
defaultValues = JSON.parse(decodeURIComponent(parts[1])) as { [key: string]: any };
const name = parts[0].replace(/^#/, "");
const value = parts[1];
if (name == "defaultValues")
{
defaultValues = JSON.parse(decodeURIComponent(value)) as { [key: string]: any };
}
if (parts.length > 1 && parts[0] == "disabledFields")
if (name == "disabledFields")
{
disabledFields = JSON.parse(decodeURIComponent(parts[1])) as { [key: string]: any };
disabledFields = JSON.parse(decodeURIComponent(value)) as { [key: string]: any };
}
}
}
catch (e)
{}
{
}
}
/*******************************************************************************
@ -234,7 +239,7 @@ function EntityForm(props: Props): JSX.Element
const metaData = await qController.loadMetaData();
const widgetMetaData = metaData.widgets.get(widgetName);
const newChildListWidgetData: {[name: string]: ChildRecordListData} = Object.assign({}, childListWidgetData)
const newChildListWidgetData: { [name: string]: ChildRecordListData } = Object.assign({}, childListWidgetData);
if (!newChildListWidgetData[widgetName].queryOutput.records)
{
newChildListWidgetData[widgetName].queryOutput.records = [];
@ -243,7 +248,7 @@ function EntityForm(props: Props): JSX.Element
switch (action)
{
case "insert":
newChildListWidgetData[widgetName].queryOutput.records.push({values: values})
newChildListWidgetData[widgetName].queryOutput.records.push({values: values});
break;
case "edit":
newChildListWidgetData[widgetName].queryOutput.records[rowIndex] = {values: values};
@ -255,7 +260,7 @@ function EntityForm(props: Props): JSX.Element
newChildListWidgetData[widgetName].totalRows = newChildListWidgetData[widgetName].queryOutput.records.length;
setChildListWidgetData(newChildListWidgetData);
const newRenderedWidgetSections = Object.assign({}, renderedWidgetSections)
const newRenderedWidgetSections = Object.assign({}, renderedWidgetSections);
newRenderedWidgetSections[widgetName] = getWidgetSection(widgetMetaData, newChildListWidgetData[widgetName]);
setRenderedWidgetSections(newRenderedWidgetSections);
forceUpdate();
@ -293,11 +298,11 @@ function EntityForm(props: Props): JSX.Element
return <div>Error: No form fields in section {section.name}</div>;
}
const helpRoles = [props.id ? "EDIT_SCREEN" : "INSERT_SCREEN", "WRITE_SCREENS", "ALL_SCREENS"]
const helpRoles = [props.id ? "EDIT_SCREEN" : "INSERT_SCREEN", "WRITE_SCREENS", "ALL_SCREENS"];
if (omitWrapper)
{
return <QDynamicForm formData={formData} record={record} helpRoles={helpRoles} helpContentKeyPrefix={`table:${tableName};`} />
return <QDynamicForm formData={formData} record={record} helpRoles={helpRoles} helpContentKeyPrefix={`table:${tableName};`} />;
}
return <Card id={section.name} sx={{overflow: "visible", scrollMarginTop: "100px"}} elevation={cardElevation}>
@ -310,7 +315,7 @@ function EntityForm(props: Props): JSX.Element
<QDynamicForm formData={formData} record={record} helpRoles={helpRoles} helpContentKeyPrefix={`table:${tableName};`} />
</Box>
</Box>
</Card>
</Card>;
}
@ -332,7 +337,7 @@ function EntityForm(props: Props): JSX.Element
addNewRecordCallback={() => openAddChildRecord(widgetMetaData.name, widgetData)}
editRecordCallback={(rowIndex) => openEditChildRecord(widgetMetaData.name, widgetData, rowIndex)}
deleteRecordCallback={(rowIndex) => deleteChildRecord(widgetMetaData.name, widgetData, rowIndex)}
/>
/>;
}
@ -347,7 +352,7 @@ function EntityForm(props: Props): JSX.Element
}
else
{
return renderedWidgetSections[section.widgetName] ?? <Box>Loading {section.label}...</Box>
return renderedWidgetSections[section.widgetName] ?? <Box>Loading {section.label}...</Box>;
}
}
@ -368,7 +373,7 @@ function EntityForm(props: Props): JSX.Element
/////////////////////////////////////////////////
const tableSections = TableUtils.getSectionsForRecordSidebar(tableMetaData, [...tableMetaData.fields.keys()], (section: QTableSection) =>
{
return section.widgetName && metaData.widgets.get(section.widgetName)?.type == "childRecordList" && metaData.widgets.get(section.widgetName)?.defaultValues?.has("manageAssociationName")
return section.widgetName && metaData.widgets.get(section.widgetName)?.type == "childRecordList" && metaData.widgets.get(section.widgetName)?.defaultValues?.has("manageAssociationName");
});
setTableSections(tableSections);
@ -544,7 +549,7 @@ function EntityForm(props: Props): JSX.Element
}
const hasFields = section.fieldNames && section.fieldNames.length > 0;
const hasChildRecordListWidget = section.widgetName && metaData.widgets.get(section.widgetName)?.type == "childRecordList"
const hasChildRecordListWidget = section.widgetName && metaData.widgets.get(section.widgetName)?.type == "childRecordList";
if (!hasFields && !hasChildRecordListWidget)
{
continue;
@ -701,7 +706,7 @@ function EntityForm(props: Props): JSX.Element
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
if (fieldMetaData.type === QFieldType.DATE_TIME && valuesToPost[fieldName])
{
console.log(`DateTime ${fieldName}: Initial value: [${initialValues[fieldName]}] -> [${valuesToPost[fieldName]}]`)
console.log(`DateTime ${fieldName}: Initial value: [${initialValues[fieldName]}] -> [${valuesToPost[fieldName]}]`);
if (initialValues[fieldName] == valuesToPost[fieldName])
{
console.log(" - Is the same, so, deleting from the post");
@ -734,13 +739,11 @@ function EntityForm(props: Props): JSX.Element
}
}
// todo - associations + copy might be a "bad time"
const associationsToPost: any = {}
const associationsToPost: any = {};
let haveAssociationsToPost = false;
for (let name of Object.keys(childListWidgetData))
{
const manageAssociationName = metaData.widgets.get(name)?.defaultValues?.get("manageAssociationName")
const manageAssociationName = metaData.widgets.get(name)?.defaultValues?.get("manageAssociationName");
if (!manageAssociationName)
{
console.log(`Cannot send association data to backend - missing a manageAssociationName defaultValue in widget meta data for widget name ${name}`);
@ -852,15 +855,15 @@ function EntityForm(props: Props): JSX.Element
const getSectionHelp = (section: QTableSection) =>
{
const helpRoles = [props.id ? "EDIT_SCREEN" : "INSERT_SCREEN", "WRITE_SCREENS", "ALL_SCREENS"]
const helpRoles = [props.id ? "EDIT_SCREEN" : "INSERT_SCREEN", "WRITE_SCREENS", "ALL_SCREENS"];
const formattedHelpContent = <HelpContent helpContents={section.helpContents} roles={helpRoles} helpContentKey={`table:${tableMetaData.name};section:${section.name}`} />;
return formattedHelpContent && (
<Box px={"1.5rem"} fontSize={"0.875rem"}>
{formattedHelpContent}
</Box>
)
}
);
};
if (notAllowedError)
{
@ -1009,7 +1012,7 @@ function EntityForm(props: Props): JSX.Element
function ScrollToFirstError(): JSX.Element
{
const {submitCount, isValid} = useFormikContext()
const {submitCount, isValid} = useFormikContext();
useEffect(() =>
{
@ -1039,8 +1042,8 @@ function ScrollToFirstError(): JSX.Element
}
firstErrorMessage.scrollIntoView({block: "center"});
}, 100)
}, [submitCount, isValid])
}, 100);
}, [submitCount, isValid]);
return null;
}

View File

@ -25,7 +25,7 @@ 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, Button, Link} 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";
@ -40,14 +40,14 @@ import TextField from "@mui/material/TextField";
import Tooltip from "@mui/material/Tooltip";
import {TooltipProps} from "@mui/material/Tooltip/Tooltip";
import FormData from "form-data";
import React, {useContext, useEffect, useRef, useState} from "react";
import {useLocation, useNavigate} from "react-router-dom";
import QContext from "QContext";
import colors from "qqq/assets/theme/base/colors";
import {QCancelButton, QDeleteButton, QSaveButton} from "qqq/components/buttons/DefaultButtons";
import RecordQueryView from "qqq/models/query/RecordQueryView";
import FilterUtils from "qqq/utils/qqq/FilterUtils";
import {SavedViewUtils} from "qqq/utils/qqq/SavedViewUtils";
import React, {useContext, useEffect, useRef, useState} from "react";
import {useLocation, useNavigate} from "react-router-dom";
interface Props
{
@ -87,7 +87,7 @@ function SavedViews({qController, metaData, tableMetaData, currentSavedView, tab
const RENAME_OPTION = "Rename...";
const DELETE_OPTION = "Delete...";
const CLEAR_OPTION = "New View";
const dropdownOptions = [DUPLICATE_OPTION, RENAME_OPTION, DELETE_OPTION, CLEAR_OPTION];
const NEW_REPORT_OPTION = "Create Report from Current View";
const {accentColor, accentColorLight} = useContext(QContext);
@ -187,10 +187,26 @@ function SavedViews({qController, metaData, tableMetaData, currentSavedView, tab
case DELETE_OPTION:
setIsDeleteFilter(true)
break;
case NEW_REPORT_OPTION:
createNewReport();
break;
}
}
/*******************************************************************************
**
*******************************************************************************/
function createNewReport()
{
const defaultValues: {[key: string]: any} = {};
defaultValues.tableName = tableMetaData.name;
defaultValues.queryFilterJson = JSON.stringify(view.queryFilter, null, 3);
defaultValues.columnsJson = JSON.stringify(view.queryColumns, null, 3);
navigate(`${metaData.getTablePathByName("savedReport")}/create#defaultValues=${encodeURIComponent(JSON.stringify(defaultValues))}`);
}
/*******************************************************************************
** fired when save or delete button saved on confirmation dialogs
@ -227,6 +243,12 @@ function SavedViews({qController, metaData, tableMetaData, currentSavedView, tab
/////////////////////////////////////////////////////////////////////////////////////////////////
const viewObject = JSON.parse(JSON.stringify(view));
viewObject.queryFilter = JSON.parse(JSON.stringify(FilterUtils.convertFilterPossibleValuesToIds(viewObject.queryFilter)));
////////////////////////////////////////////////////////////////////////////
// strip away incomplete filters too, just for cleaner saved view filters //
////////////////////////////////////////////////////////////////////////////
FilterUtils.stripAwayIncompleteCriteria(viewObject.queryFilter)
formData.append("viewJson", JSON.stringify(viewObject));
if (isSaveFilterAs || isRenameFilter || currentSavedView == null)
@ -370,6 +392,7 @@ function SavedViews({qController, metaData, tableMetaData, currentSavedView, tab
const hasStorePermission = metaData?.processes.has("storeSavedView");
const hasDeletePermission = metaData?.processes.has("deleteSavedView");
const hasQueryPermission = metaData?.processes.has("querySavedView");
const hasSavedReportsPermission = metaData?.tables.has("savedReport");
const tooltipMaxWidth = (maxWidth: string) =>
{
@ -423,7 +446,7 @@ function SavedViews({qController, metaData, tableMetaData, currentSavedView, tab
</Tooltip>
}
{
hasStorePermission && currentSavedView != null &&
hasDeletePermission && currentSavedView != null &&
<Tooltip {...menuTooltipAttribs} title="Delete this saved view.">
<MenuItem disabled={currentSavedView === null} onClick={() => handleDropdownOptionClick(DELETE_OPTION)}>
<ListItemIcon><Icon>delete</Icon></ListItemIcon>
@ -439,6 +462,15 @@ function SavedViews({qController, metaData, tableMetaData, currentSavedView, tab
</MenuItem>
</Tooltip>
}
{
hasSavedReportsPermission &&
<Tooltip {...menuTooltipAttribs} title="Create a new Saved Report using your current view of this table as a starting point.">
<MenuItem onClick={() => handleDropdownOptionClick(NEW_REPORT_OPTION)}>
<ListItemIcon><Icon>article</Icon></ListItemIcon>
Create Report from Current View
</MenuItem>
</Tooltip>
}
<Divider/>
<MenuItem disabled style={{"opacity": "initial"}}><b>Your Saved Views</b></MenuItem>
{

View File

@ -410,7 +410,7 @@ const BasicAndAdvancedQueryControls = forwardRef((props: BasicAndAdvancedQueryCo
let counter = 0;
return (
<Box display="flex" flexWrap="wrap" fontSize="0.875rem">
<React.Fragment>
{thisQueryFilter.criteria?.map((criteria, i) =>
{
const {criteriaIsValid} = validateCriteria(criteria, null);
@ -446,7 +446,7 @@ const BasicAndAdvancedQueryControls = forwardRef((props: BasicAndAdvancedQueryCo
</React.Fragment>
);
}))}
</Box>
</React.Fragment>
);
};
@ -821,8 +821,10 @@ const BasicAndAdvancedQueryControls = forwardRef((props: BasicAndAdvancedQueryCo
pb={"0.125rem"}
boxShadow={"inset 0px 0px 4px 2px #EFEFED"}
>
<Box display="flex" flexWrap="wrap" fontSize="0.875rem">
{queryToAdvancedString(queryFilter)}
</Box>
</Box>
}
</Box>
</Box>

View File

@ -63,6 +63,8 @@ export default class RecordQueryView
view.queryFilter = json.queryFilter as QQueryFilter;
FilterUtils.stripAwayIncompleteCriteria(view.queryFilter)
//////////////////////////////////////////////////////////////////////////////////////////
// it's important that some criteria values exist as expression objects - so - do that. //
//////////////////////////////////////////////////////////////////////////////////////////

View File

@ -47,9 +47,6 @@ import {DataGridPro, GridColDef} from "@mui/x-data-grid-pro";
import FormData from "form-data";
import {Form, Formik} from "formik";
import parse from "html-react-parser";
import React, {useContext, useEffect, useState} from "react";
import {useLocation, useNavigate, useParams} from "react-router-dom";
import * as Yup from "yup";
import QContext from "QContext";
import {QCancelButton, QSubmitButton} from "qqq/components/buttons/DefaultButtons";
import QDynamicForm from "qqq/components/forms/DynamicForm";
@ -66,6 +63,9 @@ import {TABLE_VARIANT_LOCAL_STORAGE_KEY_ROOT} from "qqq/pages/records/query/Reco
import Client from "qqq/utils/qqq/Client";
import TableUtils from "qqq/utils/qqq/TableUtils";
import ValueUtils from "qqq/utils/qqq/ValueUtils";
import React, {useContext, useEffect, useState} from "react";
import {useLocation, useNavigate, useParams} from "react-router-dom";
import * as Yup from "yup";
interface Props
@ -226,8 +226,19 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
setShowFullHelpText(!showFullHelpText);
};
const download = (url: string, fileName: string) =>
const download = (processValues: {[key: string]: string}) =>
{
let url;
let fileName = processValues.downloadFileName;
if(processValues.serverFilePath)
{
url = `/download/${encodeURIComponent(processValues.downloadFileName)}?filePath=${encodeURIComponent(processValues.serverFilePath)}`;
}
else if(processValues.storageTableName && processValues.storageReference)
{
url = `/download/${encodeURIComponent(processValues.downloadFileName)}?storageTableName=${encodeURIComponent(processValues.storageTableName)}&storageReference=${encodeURIComponent(processValues.storageReference)}`;
}
/////////////////////////////////////////////////////////////////////////////////////////////
// todo - this could be simplified, i think? //
// it was originally built like this when we had to submit full access token to backend... //
@ -556,7 +567,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
Download
</Box>
<Box display="flex" py={1} pr={2}>
<MDTypography variant="button" fontWeight="bold" onClick={() => download(`/download/${processValues.downloadFileName}?filePath=${processValues.serverFilePath}`, processValues.downloadFileName)} sx={{cursor: "pointer"}}>
<MDTypography variant="button" fontWeight="bold" onClick={() => download(processValues)} sx={{cursor: "pointer"}}>
<Box display="flex" alignItems="center" gap={1} py={1} pr={2}>
<Icon fontSize="large">download_for_offline</Icon>
{processValues.downloadFileName}

View File

@ -447,9 +447,19 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
}
}
}
/////////////////////////////////////////
// recursively prep subfilters as well //
/////////////////////////////////////////
let subFilters = [] as QQueryFilter[];
for (let j = 0; j < sourceFilter?.subFilters?.length; j++)
{
subFilters.push(prepQueryFilterForBackend(sourceFilter.subFilters[j]));
}
filterForBackend.subFilters = subFilters;
filterForBackend.skip = pageNumber * rowsPerPage;
filterForBackend.limit = rowsPerPage;
return filterForBackend;
};
@ -701,8 +711,27 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element
const doSetView = (view: RecordQueryView): void =>
{
setView(view);
setViewAsJson(JSON.stringify(view));
localStorage.setItem(viewLocalStorageKey, JSON.stringify(view));
const viewAsJSON = JSON.stringify(view);
setViewAsJson(viewAsJSON);
try
{
////////////////////////////////////////////////////////////////////////////////////
// in case there's an incomplete criteria in the view (e.g., w/o a fieldName), //
// don't store that in local storage - we don't want that, it's messy, and it //
// has caused fails in the past. So, clone the view, and strip away such things. //
////////////////////////////////////////////////////////////////////////////////////
const viewForLocalStorage: RecordQueryView = JSON.parse(viewAsJSON);
if (viewForLocalStorage?.queryFilter?.criteria?.length > 0)
{
FilterUtils.stripAwayIncompleteCriteria(viewForLocalStorage.queryFilter)
}
localStorage.setItem(viewLocalStorageKey, JSON.stringify(viewForLocalStorage));
}
catch(e)
{
console.log("Error storing view in local storage: " + e)
}
};

View File

@ -166,50 +166,50 @@ function RecordView({table, launchProcess}: Props): JSX.Element
{
if (!e.metaKey && e.key === "n" && table.capabilities.has(Capability.TABLE_INSERT) && table.insertPermission)
{
e.preventDefault()
e.preventDefault();
gotoCreate();
}
else if (!e.metaKey && e.key === "e" && table.capabilities.has(Capability.TABLE_UPDATE) && table.editPermission)
{
e.preventDefault()
e.preventDefault();
navigate("edit");
}
else if (!e.metaKey && e.key === "c" && table.capabilities.has(Capability.TABLE_INSERT) && table.insertPermission)
{
e.preventDefault()
e.preventDefault();
navigate("copy");
}
else if (!e.metaKey && e.key === "d" && table.capabilities.has(Capability.TABLE_DELETE) && table.deletePermission)
{
e.preventDefault()
e.preventDefault();
handleClickDeleteButton();
}
else if (!e.metaKey && e.key === "a" && metaData && metaData.tables.has("audit"))
{
e.preventDefault()
e.preventDefault();
navigate("#audit");
}
}
}
};
document.addEventListener("keydown", down)
document.addEventListener("keydown", down);
return () =>
{
document.removeEventListener("keydown", down)
}
}, [dotMenuOpen, keyboardHelpOpen, showEditChildForm, showAudit, metaData, location])
document.removeEventListener("keydown", down);
};
}, [dotMenuOpen, keyboardHelpOpen, showEditChildForm, showAudit, metaData, location]);
const gotoCreate = () =>
{
const path = `${pathParts.slice(0, -1).join("/")}/create`;
navigate(path);
}
};
const gotoEdit = () =>
{
const path = `${pathParts.slice(0, -1).join("/")}/${record.values.get(table.primaryKeyField)}/edit`;
navigate(path);
}
};
////////////////////////////////////////////////////////////////////////////////////////////////////
// monitor location changes - if we've clicked a link from viewing one record to viewing another, //
@ -349,7 +349,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
{
visibleJoinTables.add(tableForField.name);
}
})
});
}
return (visibleJoinTables);
@ -361,15 +361,15 @@ function RecordView({table, launchProcess}: Props): JSX.Element
*******************************************************************************/
const getSectionHelp = (section: QTableSection) =>
{
const helpRoles = ["VIEW_SCREEN", "READ_SCREENS", "ALL_SCREENS"]
const helpRoles = ["VIEW_SCREEN", "READ_SCREENS", "ALL_SCREENS"];
const formattedHelpContent = <HelpContent helpContents={section.helpContents} roles={helpRoles} helpContentKey={`table:${tableName};section:${section.name}`} />;
return formattedHelpContent && (
<Box px={"1.5rem"} fontSize={"0.875rem"} color={colors.blueGray.main}>
{formattedHelpContent}
</Box>
)
}
);
};
if (!asyncLoadInited)
@ -401,11 +401,11 @@ function RecordView({table, launchProcess}: Props): JSX.Element
//////////////////////////////////////////////////////
// load processes that the routing needs to respect //
//////////////////////////////////////////////////////
const allTableProcesses = ProcessUtils.getProcessesForTable(metaData, tableName, true) // these include hidden ones (e.g., to find the bulks)
const allTableProcesses = ProcessUtils.getProcessesForTable(metaData, tableName, true); // these include hidden ones (e.g., to find the bulks)
const runRecordScriptProcess = metaData?.processes.get("runRecordScript");
if (runRecordScriptProcess)
{
allTableProcesses.unshift(runRecordScriptProcess)
allTableProcesses.unshift(runRecordScriptProcess);
}
setAllTableProcesses(allTableProcesses);
@ -522,13 +522,15 @@ function RecordView({table, launchProcess}: Props): JSX.Element
section.fieldNames.map((fieldName: string) =>
{
let [field, tableForField] = TableUtils.getFieldAndTable(tableMetaData, fieldName);
if (field != null)
{
let label = field.label;
const helpRoles = ["VIEW_SCREEN", "READ_SCREENS", "ALL_SCREENS"]
const helpRoles = ["VIEW_SCREEN", "READ_SCREENS", "ALL_SCREENS"];
const showHelp = helpHelpActive || hasHelpContent(field.helpContents, helpRoles);
const formattedHelpContent = <HelpContent helpContents={field.helpContents} roles={helpRoles} heading={label} helpContentKey={`table:${tableName};field:${fieldName}`} />;
const labelElement = <Typography variant="button" textTransform="none" fontWeight="bold" pr={1} color="rgb(52, 71, 103)" sx={{cursor: "default"}}>{label}:</Typography>
const labelElement = <Typography variant="button" textTransform="none" fontWeight="bold" pr={1} color="rgb(52, 71, 103)" sx={{cursor: "default"}}>{label}:</Typography>;
return (
<Box key={fieldName} flexDirection="row" pr={2}>
@ -542,7 +544,8 @@ function RecordView({table, launchProcess}: Props): JSX.Element
</Typography>
</>
</Box>
)
);
}
})
}
</Box>
@ -603,9 +606,9 @@ function RecordView({table, launchProcess}: Props): JSX.Element
setWarningMessage(state["warning"]);
}
delete state["createSuccess"]
delete state["updateSuccess"]
delete state["warning"]
delete state["createSuccess"];
delete state["updateSuccess"];
delete state["warning"];
window.history.replaceState(state, "");
}

View File

@ -159,6 +159,14 @@ class FilterUtils
criteria.values = values;
}
////////////////////////////////////////////////
// recursively clean values in any subfilters //
////////////////////////////////////////////////
for (let j = 0; j < queryFilter?.subFilters?.length; j++)
{
await FilterUtils.cleanupValuesInFilerFromQueryString(qController, tableMetaData, queryFilter.subFilters[j]);
}
}
@ -581,6 +589,29 @@ class FilterUtils
}
}
/*******************************************************************************
** after go-live of redesigin in march 2024, we had bugs where we could get a
** filter with a criteria w/ a null field name (e.g., by having an incomplete
** criteria in the Advanced filter builder - and that would sometimes break
** the screen! So, strip those away when storing or loading filters, via
** this function.
*******************************************************************************/
public static stripAwayIncompleteCriteria(filter: QQueryFilter)
{
if (filter?.criteria?.length > 0)
{
for (let i = 0; i < filter.criteria.length; i++)
{
if (!filter.criteria[i].fieldName)
{
filter.criteria.splice(i, 1);
i--;
}
}
}
}
}
export default FilterUtils;

View File

@ -150,7 +150,7 @@ class TableUtils
return ([tableMetaData.fields.get(fieldName), tableMetaData]);
}
return (null);
return [null, null];
}
@ -173,7 +173,7 @@ class TableUtils
catch (e)
{
console.log(`Error getting full field label for ${fieldName} in table ${tableMetaData?.name}: ${e}`);
return fieldName
return fieldName;
}
}