mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-21 06:38:43 +00:00
Compare commits
13 Commits
snapshot-f
...
snapshot-f
Author | SHA1 | Date | |
---|---|---|---|
30991cb34e | |||
2fd6272ea3 | |||
b63d74f785 | |||
7e15f4601d | |||
cdb61695d1 | |||
5e3991d9ae | |||
ff946df461 | |||
f1826c81a9 | |||
230aaeef8c | |||
c08696b3a1 | |||
84e627467f | |||
6c524c4eca | |||
edab918763 |
@ -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.89",
|
||||
"@kingsrook/qqq-frontend-core": "1.0.90",
|
||||
"@mui/icons-material": "5.4.1",
|
||||
"@mui/material": "5.11.1",
|
||||
"@mui/styles": "5.11.1",
|
||||
|
@ -63,7 +63,7 @@ interface Props
|
||||
disabledFields: { [key: string]: boolean } | string[];
|
||||
isCopy?: boolean;
|
||||
onSubmitCallback?: (values: any) => void;
|
||||
overrideHeading?: string
|
||||
overrideHeading?: string;
|
||||
}
|
||||
|
||||
EntityForm.defaultProps = {
|
||||
@ -100,8 +100,8 @@ function EntityForm(props: Props): JSX.Element
|
||||
const [metaData, setMetaData] = useState(null as QInstance);
|
||||
const [record, setRecord] = useState(null as QRecord);
|
||||
const [tableSections, setTableSections] = useState(null as QTableSection[]);
|
||||
const [renderedWidgetSections, setRenderedWidgetSections] = useState({} as {[name: string]: JSX.Element});
|
||||
const [childListWidgetData, setChildListWidgetData] = useState({} as {[name: string]: ChildRecordListData});
|
||||
const [renderedWidgetSections, setRenderedWidgetSections] = useState({} as { [name: string]: JSX.Element });
|
||||
const [childListWidgetData, setChildListWidgetData] = useState({} as { [name: string]: ChildRecordListData });
|
||||
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
||||
|
||||
const [showEditChildForm, setShowEditChildForm] = useState(null as any);
|
||||
@ -128,23 +128,28 @@ 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")
|
||||
{
|
||||
disabledFields = JSON.parse(decodeURIComponent(parts[1])) as { [key: string]: any };
|
||||
if (name == "disabledFields")
|
||||
{
|
||||
disabledFields = JSON.parse(decodeURIComponent(value)) as { [key: string]: any };
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e)
|
||||
{}
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -153,7 +158,7 @@ function EntityForm(props: Props): JSX.Element
|
||||
let defaultValues = widgetData.defaultValuesForNewChildRecords;
|
||||
|
||||
let disabledFields = widgetData.disabledFieldsForNewChildRecords;
|
||||
if(!disabledFields)
|
||||
if (!disabledFields)
|
||||
{
|
||||
disabledFields = widgetData.defaultValuesForNewChildRecords;
|
||||
}
|
||||
@ -170,7 +175,7 @@ function EntityForm(props: Props): JSX.Element
|
||||
let defaultValues = widgetData.queryOutput.records[rowIndex].values;
|
||||
|
||||
let disabledFields = widgetData.disabledFieldsForNewChildRecords;
|
||||
if(!disabledFields)
|
||||
if (!disabledFields)
|
||||
{
|
||||
disabledFields = widgetData.defaultValuesForNewChildRecords;
|
||||
}
|
||||
@ -234,19 +239,19 @@ 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)
|
||||
if(!newChildListWidgetData[widgetName].queryOutput.records)
|
||||
const newChildListWidgetData: { [name: string]: ChildRecordListData } = Object.assign({}, childListWidgetData);
|
||||
if (!newChildListWidgetData[widgetName].queryOutput.records)
|
||||
{
|
||||
newChildListWidgetData[widgetName].queryOutput.records = [];
|
||||
}
|
||||
|
||||
switch(action)
|
||||
switch (action)
|
||||
{
|
||||
case "insert":
|
||||
newChildListWidgetData[widgetName].queryOutput.records.push(new QRecord({values: values}))
|
||||
newChildListWidgetData[widgetName].queryOutput.records.push({values: values});
|
||||
break;
|
||||
case "edit":
|
||||
newChildListWidgetData[widgetName].queryOutput.records[rowIndex] = new QRecord({values: values});
|
||||
newChildListWidgetData[widgetName].queryOutput.records[rowIndex] = {values: values};
|
||||
break;
|
||||
case "delete":
|
||||
newChildListWidgetData[widgetName].queryOutput.records.splice(rowIndex, 1);
|
||||
@ -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();
|
||||
@ -290,14 +295,14 @@ function EntityForm(props: Props): JSX.Element
|
||||
|
||||
if (!Object.keys(formFields).length)
|
||||
{
|
||||
return <div>Error: No form fields in section {section.name}</div>;
|
||||
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)
|
||||
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)}
|
||||
/>
|
||||
/>;
|
||||
}
|
||||
|
||||
|
||||
@ -341,13 +346,13 @@ function EntityForm(props: Props): JSX.Element
|
||||
*******************************************************************************/
|
||||
function renderSection(section: QTableSection, values: FormikValues | Value, touched: FormikTouched<FormikValues> | Value, formFields: Map<string, any>, errors: FormikErrors<FormikValues> | Value)
|
||||
{
|
||||
if(section.fieldNames && section.fieldNames.length > 0)
|
||||
if (section.fieldNames && section.fieldNames.length > 0)
|
||||
{
|
||||
return getFormSection(section, values, touched, formFields.get(section.name), errors);
|
||||
}
|
||||
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);
|
||||
|
||||
@ -410,7 +415,7 @@ function EntityForm(props: Props): JSX.Element
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// these checks are only for updating records, if copying, it is actually an insert, which is checked after this block //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(! props.isCopy)
|
||||
if (!props.isCopy)
|
||||
{
|
||||
if (!tableMetaData.capabilities.has(Capability.TABLE_UPDATE))
|
||||
{
|
||||
@ -465,7 +470,7 @@ function EntityForm(props: Props): JSX.Element
|
||||
///////////////////////////////////////////////////
|
||||
// if an override heading was passed in, use it. //
|
||||
///////////////////////////////////////////////////
|
||||
if(props.overrideHeading)
|
||||
if (props.overrideHeading)
|
||||
{
|
||||
setFormTitle(props.overrideHeading);
|
||||
if (!props.isModal)
|
||||
@ -530,8 +535,8 @@ function EntityForm(props: Props): JSX.Element
|
||||
let t1sectionName;
|
||||
let t1section;
|
||||
const nonT1Sections: QTableSection[] = [];
|
||||
const newRenderedWidgetSections: {[name: string]: JSX.Element} = {};
|
||||
const newChildListWidgetData: {[name: string]: ChildRecordListData} = {};
|
||||
const newRenderedWidgetSections: { [name: string]: JSX.Element } = {};
|
||||
const newChildListWidgetData: { [name: string]: ChildRecordListData } = {};
|
||||
|
||||
for (let i = 0; i < tableSections.length; i++)
|
||||
{
|
||||
@ -544,13 +549,13 @@ 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"
|
||||
if(!hasFields && !hasChildRecordListWidget)
|
||||
const hasChildRecordListWidget = section.widgetName && metaData.widgets.get(section.widgetName)?.type == "childRecordList";
|
||||
if (!hasFields && !hasChildRecordListWidget)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if(hasFields)
|
||||
if (hasFields)
|
||||
{
|
||||
for (let j = 0; j < section.fieldNames.length; j++)
|
||||
{
|
||||
@ -626,10 +631,10 @@ function EntityForm(props: Props): JSX.Element
|
||||
//////////////////////////////////////////////////////////////////
|
||||
useEffect(() =>
|
||||
{
|
||||
if(childListWidgetData)
|
||||
if (childListWidgetData)
|
||||
{
|
||||
const newRenderedWidgetSections: {[name: string]: JSX.Element} = {};
|
||||
for(let name in childListWidgetData)
|
||||
const newRenderedWidgetSections: { [name: string]: JSX.Element } = {};
|
||||
for (let name in childListWidgetData)
|
||||
{
|
||||
const widgetMetaData = metaData.widgets.get(name);
|
||||
newRenderedWidgetSections[name] = getWidgetSection(widgetMetaData, childListWidgetData[name]);
|
||||
@ -674,7 +679,7 @@ function EntityForm(props: Props): JSX.Element
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if there's a callback (e.g., for a modal nested on another create/edit screen), then just pass our data back there anre return. //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(props.onSubmitCallback)
|
||||
if (props.onSubmitCallback)
|
||||
{
|
||||
props.onSubmitCallback(values);
|
||||
return;
|
||||
@ -687,7 +692,7 @@ function EntityForm(props: Props): JSX.Element
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
const valuesToPost = JSON.parse(JSON.stringify(values));
|
||||
|
||||
for(let fieldName of tableMetaData.fields.keys())
|
||||
for (let fieldName of tableMetaData.fields.keys())
|
||||
{
|
||||
const fieldMetaData = tableMetaData.fields.get(fieldName);
|
||||
|
||||
@ -699,9 +704,9 @@ function EntityForm(props: Props): JSX.Element
|
||||
// changing from, say, 12:15:30 to just 12:15:00... this seems to get around that, for cases when the //
|
||||
// user didn't change the value in the field (but if the user did change the value, then we will submit it) //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(fieldMetaData.type === QFieldType.DATE_TIME && valuesToPost[fieldName])
|
||||
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");
|
||||
@ -720,12 +725,12 @@ function EntityForm(props: Props): JSX.Element
|
||||
// 3) they are a String, which is their URL path to download them... in that case, don't submit them to //
|
||||
// the backend at all, so they'll stay what they were. do that by deleting them from the values object here. //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(fieldMetaData.type === QFieldType.BLOB)
|
||||
if (fieldMetaData.type === QFieldType.BLOB)
|
||||
{
|
||||
if(typeof valuesToPost[fieldName] === "string")
|
||||
if (typeof valuesToPost[fieldName] === "string")
|
||||
{
|
||||
console.log(`${fieldName} value was a string, so, we're deleting it from the values array, to not submit it to the backend, to not change it.`);
|
||||
delete(valuesToPost[fieldName]);
|
||||
delete (valuesToPost[fieldName]);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -734,32 +739,32 @@ 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")
|
||||
if(!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}`);
|
||||
}
|
||||
associationsToPost[manageAssociationName] = [];
|
||||
haveAssociationsToPost = true;
|
||||
for(let i=0; i<childListWidgetData[name].queryOutput.records.length; i++)
|
||||
for (let i = 0; i < childListWidgetData[name].queryOutput?.records?.length; i++)
|
||||
{
|
||||
associationsToPost[manageAssociationName].push(childListWidgetData[name].queryOutput.records[i].values);
|
||||
}
|
||||
}
|
||||
if(haveAssociationsToPost)
|
||||
if (haveAssociationsToPost)
|
||||
{
|
||||
valuesToPost["associations"] = JSON.stringify(associationsToPost);
|
||||
}
|
||||
|
||||
if (props.id !== null && !props.isCopy)
|
||||
{
|
||||
// todo - audit that it's a dupe
|
||||
///////////////////////
|
||||
// perform an update //
|
||||
///////////////////////
|
||||
await qController
|
||||
.update(tableName, props.id, valuesToPost)
|
||||
.then((record) =>
|
||||
@ -770,8 +775,14 @@ function EntityForm(props: Props): JSX.Element
|
||||
}
|
||||
else
|
||||
{
|
||||
let warningMessage = null;
|
||||
if (record.warnings && record.warnings.length && record.warnings.length > 0)
|
||||
{
|
||||
warningMessage = record.warnings[0];
|
||||
}
|
||||
|
||||
const path = location.pathname.replace(/\/edit$/, "");
|
||||
navigate(path, {state: {updateSuccess: true}});
|
||||
navigate(path, {state: {updateSuccess: true, warning: warningMessage}});
|
||||
}
|
||||
})
|
||||
.catch((error) =>
|
||||
@ -779,7 +790,7 @@ function EntityForm(props: Props): JSX.Element
|
||||
console.log("Caught:");
|
||||
console.log(error);
|
||||
|
||||
if(error.message.toLowerCase().startsWith("warning"))
|
||||
if (error.message.toLowerCase().startsWith("warning"))
|
||||
{
|
||||
const path = location.pathname.replace(/\/edit$/, "");
|
||||
navigate(path, {state: {updateSuccess: true, warning: error.message}});
|
||||
@ -793,6 +804,10 @@ function EntityForm(props: Props): JSX.Element
|
||||
}
|
||||
else
|
||||
{
|
||||
/////////////////////////////////
|
||||
// perform an insert //
|
||||
// todo - audit if it's a dupe //
|
||||
/////////////////////////////////
|
||||
await qController
|
||||
.create(tableName, valuesToPost)
|
||||
.then((record) =>
|
||||
@ -803,15 +818,21 @@ function EntityForm(props: Props): JSX.Element
|
||||
}
|
||||
else
|
||||
{
|
||||
let warningMessage = null;
|
||||
if (record.warnings && record.warnings.length && record.warnings.length > 0)
|
||||
{
|
||||
warningMessage = record.warnings[0];
|
||||
}
|
||||
|
||||
const path = props.isCopy ?
|
||||
location.pathname.replace(new RegExp(`/${props.id}/copy$`), "/" + record.values.get(tableMetaData.primaryKeyField))
|
||||
: location.pathname.replace(/create$/, record.values.get(tableMetaData.primaryKeyField));
|
||||
navigate(path, {state: {createSuccess: true}});
|
||||
navigate(path, {state: {createSuccess: true, warning: warningMessage}});
|
||||
}
|
||||
})
|
||||
.catch((error) =>
|
||||
{
|
||||
if(error.message.toLowerCase().startsWith("warning"))
|
||||
if (error.message.toLowerCase().startsWith("warning"))
|
||||
{
|
||||
const path = props.isCopy ?
|
||||
location.pathname.replace(new RegExp(`/${props.id}/copy$`), "/" + record.values.get(tableMetaData.primaryKeyField))
|
||||
@ -834,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)
|
||||
{
|
||||
@ -991,7 +1012,7 @@ function EntityForm(props: Props): JSX.Element
|
||||
|
||||
function ScrollToFirstError(): JSX.Element
|
||||
{
|
||||
const {submitCount, isValid} = useFormikContext()
|
||||
const {submitCount, isValid} = useFormikContext();
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
@ -1021,8 +1042,8 @@ function ScrollToFirstError(): JSX.Element
|
||||
}
|
||||
firstErrorMessage.scrollIntoView({block: "center"});
|
||||
|
||||
}, 100)
|
||||
}, [submitCount, isValid])
|
||||
}, 100);
|
||||
}, [submitCount, isValid]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
@ -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>
|
||||
{
|
||||
|
@ -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,7 +821,9 @@ const BasicAndAdvancedQueryControls = forwardRef((props: BasicAndAdvancedQueryCo
|
||||
pb={"0.125rem"}
|
||||
boxShadow={"inset 0px 0px 4px 2px #EFEFED"}
|
||||
>
|
||||
{queryToAdvancedString(queryFilter)}
|
||||
<Box display="flex" flexWrap="wrap" fontSize="0.875rem">
|
||||
{queryToAdvancedString(queryFilter)}
|
||||
</Box>
|
||||
</Box>
|
||||
}
|
||||
</Box>
|
||||
|
@ -40,7 +40,7 @@ import {Link, useNavigate} from "react-router-dom";
|
||||
export interface ChildRecordListData extends WidgetData
|
||||
{
|
||||
title: string;
|
||||
queryOutput: {records: QRecord[]}
|
||||
queryOutput: {records: {values: any}[]}
|
||||
childTableMetaData: QTableMetaData;
|
||||
tablePath: string;
|
||||
viewAllLink: string;
|
||||
|
@ -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. //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -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}
|
||||
|
@ -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)
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
@ -46,8 +46,6 @@ import Menu from "@mui/material/Menu";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import Modal from "@mui/material/Modal";
|
||||
import Tooltip from "@mui/material/Tooltip/Tooltip";
|
||||
import React, {useContext, useEffect, useState} from "react";
|
||||
import {useLocation, useNavigate, useParams} from "react-router-dom";
|
||||
import QContext from "QContext";
|
||||
import colors from "qqq/assets/theme/base/colors";
|
||||
import AuditBody from "qqq/components/audits/AuditBody";
|
||||
@ -65,6 +63,8 @@ import Client from "qqq/utils/qqq/Client";
|
||||
import ProcessUtils from "qqq/utils/qqq/ProcessUtils";
|
||||
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";
|
||||
|
||||
const qController = Client.getInstance();
|
||||
|
||||
@ -148,9 +148,9 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
///////////////////////
|
||||
useEffect(() =>
|
||||
{
|
||||
if(tableMetaData == null)
|
||||
if (tableMetaData == null)
|
||||
{
|
||||
(async() =>
|
||||
(async () =>
|
||||
{
|
||||
const tableMetaData = await qController.loadTableMetaData(tableName);
|
||||
setTableMetaData(tableMetaData);
|
||||
@ -162,54 +162,54 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
const type = (e.target as any).type;
|
||||
const validType = (type !== "text" && type !== "textarea" && type !== "input" && type !== "search");
|
||||
|
||||
if(validType && !dotMenuOpen && !keyboardHelpOpen && !showAudit && !showEditChildForm)
|
||||
if (validType && !dotMenuOpen && !keyboardHelpOpen && !showAudit && !showEditChildForm)
|
||||
{
|
||||
if (! e.metaKey && e.key === "n" && table.capabilities.has(Capability.TABLE_INSERT) && table.insertPermission)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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"))
|
||||
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, //
|
||||
@ -267,7 +267,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
// if our table is in the -4 index, and there's `createChild` in the -2 index, try to open a createChild form //
|
||||
// e.g., person/42/createChild/address (to create an address under person 42) //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(pathParts[pathParts.length - 4] === tableName && pathParts[pathParts.length - 2] == "createChild")
|
||||
if (pathParts[pathParts.length - 4] === tableName && pathParts[pathParts.length - 2] == "createChild")
|
||||
{
|
||||
(async () =>
|
||||
{
|
||||
@ -298,7 +298,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
}
|
||||
}
|
||||
|
||||
if(hashParts[0] == "#audit")
|
||||
if (hashParts[0] == "#audit")
|
||||
{
|
||||
setShowAudit(true);
|
||||
return;
|
||||
@ -307,11 +307,11 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// look for anchor links - e.g., table section names. return w/ no-op if found. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
if(tableSections)
|
||||
if (tableSections)
|
||||
{
|
||||
for (let i = 0; i < tableSections.length; i++)
|
||||
{
|
||||
if("#" + tableSections[i].name === location.hash)
|
||||
if ("#" + tableSections[i].name === location.hash)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -345,11 +345,11 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
section.fieldNames.forEach((fieldName) =>
|
||||
{
|
||||
const [field, tableForField] = TableUtils.getFieldAndTable(tableMetaData, fieldName);
|
||||
if(tableForField && tableForField.name != tableMetaData.name)
|
||||
if (tableForField && tableForField.name != tableMetaData.name)
|
||||
{
|
||||
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);
|
||||
|
||||
@ -417,7 +417,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
|
||||
let queryJoins: QueryJoin[] = null;
|
||||
const visibleJoinTables = getVisibleJoinTables(tableMetaData);
|
||||
if(visibleJoinTables.size > 0)
|
||||
if (visibleJoinTables.size > 0)
|
||||
{
|
||||
queryJoins = TableUtils.getQueryJoins(tableMetaData, visibleJoinTables);
|
||||
}
|
||||
@ -439,7 +439,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
{
|
||||
HistoryUtils.ensurePathNotInHistory(location.pathname);
|
||||
}
|
||||
catch(e)
|
||||
catch (e)
|
||||
{
|
||||
console.error("Error pushing history: " + e);
|
||||
}
|
||||
@ -464,13 +464,13 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
|
||||
setPageHeader(record.recordLabel);
|
||||
|
||||
if(!launchingProcess)
|
||||
if (!launchingProcess)
|
||||
{
|
||||
try
|
||||
{
|
||||
HistoryUtils.push({label: `${tableMetaData?.label}: ${record.recordLabel}`, path: location.pathname, iconName: table.iconName});
|
||||
}
|
||||
catch(e)
|
||||
catch (e)
|
||||
{
|
||||
console.error("Error pushing history: " + e);
|
||||
}
|
||||
@ -522,27 +522,30 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
section.fieldNames.map((fieldName: string) =>
|
||||
{
|
||||
let [field, tableForField] = TableUtils.getFieldAndTable(tableMetaData, fieldName);
|
||||
let label = field.label;
|
||||
if (field != null)
|
||||
{
|
||||
let label = field.label;
|
||||
|
||||
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 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}>
|
||||
<>
|
||||
{
|
||||
showHelp && formattedHelpContent ? <Tooltip title={formattedHelpContent}>{labelElement}</Tooltip> : labelElement
|
||||
}
|
||||
<div style={{display: "inline-block", width: 0}}> </div>
|
||||
<Typography variant="button" textTransform="none" fontWeight="regular" color="rgb(123, 128, 154)">
|
||||
{ValueUtils.getDisplayValue(field, record, "view", fieldName)}
|
||||
</Typography>
|
||||
</>
|
||||
</Box>
|
||||
)
|
||||
return (
|
||||
<Box key={fieldName} flexDirection="row" pr={2}>
|
||||
<>
|
||||
{
|
||||
showHelp && formattedHelpContent ? <Tooltip title={formattedHelpContent}>{labelElement}</Tooltip> : labelElement
|
||||
}
|
||||
<div style={{display: "inline-block", width: 0}}> </div>
|
||||
<Typography variant="button" textTransform="none" fontWeight="regular" color="rgb(123, 128, 154)">
|
||||
{ValueUtils.getDisplayValue(field, record, "view", fieldName)}
|
||||
</Typography>
|
||||
</>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
</Box>
|
||||
@ -590,7 +593,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
setSectionFieldElements(sectionFieldElements);
|
||||
setNonT1TableSections(nonT1TableSections);
|
||||
|
||||
if(location.state)
|
||||
if (location.state)
|
||||
{
|
||||
let state: any = location.state;
|
||||
if (state["createSuccess"] || state["updateSuccess"])
|
||||
@ -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, "");
|
||||
}
|
||||
@ -640,7 +643,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
console.log("Caught:");
|
||||
console.log(error);
|
||||
|
||||
if(error.message.toLowerCase().startsWith("warning"))
|
||||
if (error.message.toLowerCase().startsWith("warning"))
|
||||
{
|
||||
const path = pathParts.slice(0, -1).join("/");
|
||||
navigate(path, {state: {deleteSuccess: true, warning: error.message}});
|
||||
@ -767,7 +770,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// when closing a modal process, navigate up to the record being viewed //
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
if(location.hash)
|
||||
if (location.hash)
|
||||
{
|
||||
navigate(location.pathname);
|
||||
}
|
||||
@ -801,7 +804,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
/////////////////////////////////////////////////
|
||||
// navigate back up to the record being viewed //
|
||||
/////////////////////////////////////////////////
|
||||
if(location.hash)
|
||||
if (location.hash)
|
||||
{
|
||||
navigate(location.pathname);
|
||||
}
|
||||
@ -828,7 +831,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
/////////////////////////////////////////////////
|
||||
// navigate back up to the record being viewed //
|
||||
/////////////////////////////////////////////////
|
||||
if(location.hash)
|
||||
if (location.hash)
|
||||
{
|
||||
navigate(location.pathname);
|
||||
}
|
||||
@ -864,7 +867,7 @@ function RecordView({table, launchProcess}: Props): JSX.Element
|
||||
}
|
||||
{
|
||||
warningMessage ?
|
||||
<Alert color="warning" sx={{mb: 3}} onClose={() =>
|
||||
<Alert color="warning" sx={{mb: 3}} icon={<Icon>warning</Icon>} onClose={() =>
|
||||
{
|
||||
setWarningMessage(null);
|
||||
}}>
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user