Compare commits

...

25 Commits

Author SHA1 Message Date
ce9ffaab4d Merge branch 'feature/CE-2261-packing-slip-template-config' into integration/sprint-59 2025-02-19 17:05:31 -06:00
6076c4ddfd CE-2261: updated to respect field column widths on view and edit forms 2025-02-19 17:05:10 -06:00
71dc3f3f65 Merge pull request #79 from Kingsrook/feature/support-CE-2257-ice-logic
Add support for defaultValuesForNewChildRecordsFromParentFields for C…
2025-02-14 20:33:47 -06:00
ce22db2f89 Merge pull request #78 from Kingsrook/feature/CE-2258-manual-add-carton [skip ci]
CE-2258: updated dashboard widgets with a forcereload when child reco…
2025-02-14 20:32:15 -06:00
9816403bec Merged feature/support-CE-2257-ice-logic into integration/sprint-59 2025-02-03 09:27:32 -06:00
aacb239164 Add support for defaultValuesForNewChildRecordsFromParentFields for ChildRecordList; Load display values for possible-value fields when adding them to childRecord list and when opening child-edit form (adding passing of all other-values to the possible-value lookup, for filtered scenarios that need them); 2025-02-03 09:10:39 -06:00
219458ec63 CE-2258: updated dashboard widgets with a forcereload when child record is removed 2025-01-28 15:30:42 -06:00
59fdc72455 Merged feature/filter-json-field-improvements into dev 2025-01-22 20:01:10 -06:00
5c3ddb7dec Take label (e.g., of the field) as parameter 2025-01-21 12:18:21 -06:00
d65c1fb5d8 Padding & margin adjustments for script viewer 2025-01-21 12:12:03 -06:00
19a63d6956 Read filterFieldName and columnsFieldName from widgetData 2025-01-14 10:56:07 -06:00
40f5b55307 CE-1955 add error if no fields mapped 2025-01-07 11:47:52 -06:00
7320b19fbb CE-1955 Add warning about duplicate column headers, and un-selection of dupes if switching from no-header-row mode to header-row mode 2025-01-07 10:12:45 -06:00
3f8a3e7e4d CE-1955 Fix (new) switchLayout method to ... actually save the new layout 2025-01-06 16:52:19 -06:00
3ef2d64327 CE-1955 Bulk load bugs & usability improvements 2024-12-27 14:58:40 -06:00
d793c23861 CE-1955 Add guard around a call to onChangeCallback 2024-12-26 19:14:21 -06:00
d0201d96e1 CE-1955 Fix select box handling of 'x' and typing... 2024-12-26 19:13:48 -06:00
7b66ece466 Try to avoid an error a user is getting where no operatorSeletedValue is being selected when page is loading 2024-12-10 09:14:32 -06:00
02c163899a CE-1955 Handle associated fields; better messaging w/ undefined values 2024-12-04 16:11:49 -06:00
8fafe16a95 CE-1955 handle currentSavedBulkLoadProfile being set, when going back to this screen 2024-12-04 16:11:08 -06:00
722c8d3bcf CE-1955 Update to fetch label for possible-values being used as a default value 2024-12-04 16:10:33 -06:00
85acb612c9 CE-1955 Add add ? to record.associatedRecords?.get to avoid crash if no associations 2024-12-03 20:47:50 -06:00
74c634414a CE-1955 Add helpContent to hasHeaderRow and layout fields 2024-12-03 20:47:27 -06:00
f8368b030c CE-1955 make PreviewRecordUsingTableLayout a private component - try to make it re-render the associated child grids when switching records 2024-12-03 15:55:18 -06:00
dda4ea4f4b CE-1955 Delete an unused effect 2024-12-03 15:53:23 -06:00
20 changed files with 888 additions and 215 deletions

View File

@ -6,7 +6,7 @@
"@auth0/auth0-react": "1.10.2", "@auth0/auth0-react": "1.10.2",
"@emotion/react": "11.7.1", "@emotion/react": "11.7.1",
"@emotion/styled": "11.6.0", "@emotion/styled": "11.6.0",
"@kingsrook/qqq-frontend-core": "1.0.113", "@kingsrook/qqq-frontend-core": "1.0.114",
"@mui/icons-material": "5.4.1", "@mui/icons-material": "5.4.1",
"@mui/material": "5.11.1", "@mui/material": "5.11.1",
"@mui/styles": "5.11.1", "@mui/styles": "5.11.1",

View File

@ -57,7 +57,7 @@ function QDynamicForm({formData, formLabel, bulkEditMode, bulkEditSwitchChangeHa
<MDTypography variant="h5">{formLabel}</MDTypography> <MDTypography variant="h5">{formLabel}</MDTypography>
</Box> </Box>
<Box mt={1.625}> <Box mt={1.625}>
<Grid container spacing={3}> <Grid container lg={12} display="flex" spacing={3}>
{formFields {formFields
&& Object.keys(formFields).length > 0 && Object.keys(formFields).length > 0
&& Object.keys(formFields).map((fieldName: any) => && Object.keys(formFields).map((fieldName: any) =>
@ -74,13 +74,14 @@ function QDynamicForm({formData, formLabel, bulkEditMode, bulkEditSwitchChangeHa
} }
let formattedHelpContent = <HelpContent helpContents={field?.fieldMetaData?.helpContents} roles={helpRoles} helpContentKey={`${helpContentKeyPrefix ?? ""}field:${fieldName}`} />; let formattedHelpContent = <HelpContent helpContents={field?.fieldMetaData?.helpContents} roles={helpRoles} helpContentKey={`${helpContentKeyPrefix ?? ""}field:${fieldName}`} />;
if(formattedHelpContent) if (formattedHelpContent)
{ {
formattedHelpContent = <Box color="#757575" fontSize="0.875rem" mt="-0.25rem">{formattedHelpContent}</Box> formattedHelpContent = <Box color="#757575" fontSize="0.875rem" mt="-0.25rem">{formattedHelpContent}</Box>;
} }
const labelElement = <DynamicFormFieldLabel name={field.name} label={field.label} />; const labelElement = <DynamicFormFieldLabel name={field.name} label={field.label} />;
let itemLG = (field?.fieldMetaData?.gridColumns && field?.fieldMetaData?.gridColumns > 0) ? field.fieldMetaData.gridColumns : 6;
let itemXS = 12; let itemXS = 12;
let itemSM = 6; let itemSM = 6;
@ -92,13 +93,13 @@ function QDynamicForm({formData, formLabel, bulkEditMode, bulkEditSwitchChangeHa
const fileUploadAdornment = field.fieldMetaData?.getAdornment(AdornmentType.FILE_UPLOAD); const fileUploadAdornment = field.fieldMetaData?.getAdornment(AdornmentType.FILE_UPLOAD);
const width = fileUploadAdornment?.values?.get("width") ?? "half"; const width = fileUploadAdornment?.values?.get("width") ?? "half";
if(width == "full") if (width == "full")
{ {
itemSM = 12; itemSM = 12;
} }
return ( return (
<Grid item xs={itemXS} sm={itemSM} key={fieldName}> <Grid item lg={itemLG} xs={itemXS} sm={itemSM} flexDirection="column" key={fieldName}>
{labelElement} {labelElement}
<FileInputField field={field} record={record} errorMessage={errors[fieldName]} /> <FileInputField field={field} record={record} errorMessage={errors[fieldName]} />
</Grid> </Grid>
@ -114,10 +115,10 @@ function QDynamicForm({formData, formLabel, bulkEditMode, bulkEditSwitchChangeHa
Object.keys(values).forEach((key) => Object.keys(values).forEach((key) =>
{ {
otherValuesMap.set(key, values[key]); otherValuesMap.set(key, values[key]);
}) });
return ( return (
<Grid item xs={itemXS} sm={itemSM} key={fieldName}> <Grid item lg={itemLG} xs={itemXS} sm={itemSM} key={fieldName}>
{labelElement} {labelElement}
<DynamicSelect <DynamicSelect
fieldPossibleValueProps={field.possibleValueProps} fieldPossibleValueProps={field.possibleValueProps}
@ -138,7 +139,7 @@ function QDynamicForm({formData, formLabel, bulkEditMode, bulkEditSwitchChangeHa
// everything else!! // // everything else!! //
/////////////////////// ///////////////////////
return ( return (
<Grid item xs={itemXS} sm={itemSM} key={fieldName}> <Grid item lg={itemLG} xs={itemXS} sm={itemSM} key={fieldName}>
{labelElement} {labelElement}
<QDynamicFormField <QDynamicFormField
id={field.name} id={field.name}

View File

@ -141,7 +141,10 @@ function QDynamicFormField({
newValue = newValue.toLowerCase(); newValue = newValue.toLowerCase();
} }
setFieldValue(name, newValue); setFieldValue(name, newValue);
onChangeCallback(newValue); if(onChangeCallback)
{
onChangeCallback(newValue);
}
}); });
const input = document.getElementById(name) as HTMLInputElement; const input = document.getElementById(name) as HTMLInputElement;

View File

@ -66,7 +66,7 @@ interface Props
defaultValues: { [key: string]: string }; defaultValues: { [key: string]: string };
disabledFields: { [key: string]: boolean } | string[]; disabledFields: { [key: string]: boolean } | string[];
isCopy?: boolean; isCopy?: boolean;
onSubmitCallback?: (values: any) => void; onSubmitCallback?: (values: any, tableName: string) => void;
overrideHeading?: string; overrideHeading?: string;
} }
@ -173,7 +173,7 @@ function EntityForm(props: Props): JSX.Element
*******************************************************************************/ *******************************************************************************/
function openAddChildRecord(name: string, widgetData: any) function openAddChildRecord(name: string, widgetData: any)
{ {
let defaultValues = widgetData.defaultValuesForNewChildRecords; let defaultValues = widgetData.defaultValuesForNewChildRecords || {};
let disabledFields = widgetData.disabledFieldsForNewChildRecords; let disabledFields = widgetData.disabledFieldsForNewChildRecords;
if (!disabledFields) if (!disabledFields)
@ -181,6 +181,18 @@ function EntityForm(props: Props): JSX.Element
disabledFields = widgetData.defaultValuesForNewChildRecords; disabledFields = widgetData.defaultValuesForNewChildRecords;
} }
///////////////////////////////////////////////////////////////////////////////////////
// copy values from specified fields in the parent record down into the child record //
///////////////////////////////////////////////////////////////////////////////////////
if(widgetData.defaultValuesForNewChildRecordsFromParentFields)
{
for(let childField in widgetData.defaultValuesForNewChildRecordsFromParentFields)
{
const parentField = widgetData.defaultValuesForNewChildRecordsFromParentFields[childField];
defaultValues[childField] = formValues[parentField];
}
}
doOpenEditChildForm(name, widgetData.childTableMetaData, null, defaultValues, disabledFields); doOpenEditChildForm(name, widgetData.childTableMetaData, null, defaultValues, disabledFields);
} }
@ -208,7 +220,7 @@ function EntityForm(props: Props): JSX.Element
function deleteChildRecord(name: string, widgetData: any, rowIndex: number) function deleteChildRecord(name: string, widgetData: any, rowIndex: number)
{ {
updateChildRecordList(name, "delete", rowIndex); updateChildRecordList(name, "delete", rowIndex);
}; }
/******************************************************************************* /*******************************************************************************
@ -243,16 +255,16 @@ function EntityForm(props: Props): JSX.Element
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
function submitEditChildForm(values: any) function submitEditChildForm(values: any, tableName: string)
{ {
updateChildRecordList(showEditChildForm.widgetName, showEditChildForm.rowIndex == null ? "insert" : "edit", showEditChildForm.rowIndex, values); updateChildRecordList(showEditChildForm.widgetName, showEditChildForm.rowIndex == null ? "insert" : "edit", showEditChildForm.rowIndex, values, tableName);
} }
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
async function updateChildRecordList(widgetName: string, action: "insert" | "edit" | "delete", rowIndex?: number, values?: any) async function updateChildRecordList(widgetName: string, action: "insert" | "edit" | "delete", rowIndex?: number, values?: any, childTableName?: string)
{ {
const metaData = await qController.loadMetaData(); const metaData = await qController.loadMetaData();
const widgetMetaData = metaData.widgets.get(widgetName); const widgetMetaData = metaData.widgets.get(widgetName);
@ -263,13 +275,38 @@ function EntityForm(props: Props): JSX.Element
newChildListWidgetData[widgetName].queryOutput.records = []; newChildListWidgetData[widgetName].queryOutput.records = [];
} }
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// build a map of display values for the new record, specifically, for any possible-values that need translated. //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const displayValues: {[fieldName: string]: string} = {};
if(childTableName && values)
{
///////////////////////////////////////////////////////////////////////////////////////////////////////
// this function internally memoizes, so, we could potentially avoid an await here, but, seems ok... //
///////////////////////////////////////////////////////////////////////////////////////////////////////
const childTableMetaData = await qController.loadTableMetaData(childTableName)
for (let key in values)
{
const value = values[key];
const field = childTableMetaData.fields.get(key);
if(field.possibleValueSourceName)
{
const possibleValues = await qController.possibleValues(childTableName, null, field.name, null, [value], objectToMap(values), "form")
if(possibleValues && possibleValues.length > 0)
{
displayValues[key] = possibleValues[0].label;
}
}
}
}
switch (action) switch (action)
{ {
case "insert": case "insert":
newChildListWidgetData[widgetName].queryOutput.records.push({values: values}); newChildListWidgetData[widgetName].queryOutput.records.push({values: values, displayValues: displayValues});
break; break;
case "edit": case "edit":
newChildListWidgetData[widgetName].queryOutput.records[rowIndex] = {values: values}; newChildListWidgetData[widgetName].queryOutput.records[rowIndex] = {values: values, displayValues: displayValues};
break; break;
case "delete": case "delete":
newChildListWidgetData[widgetName].queryOutput.records.splice(rowIndex, 1); newChildListWidgetData[widgetName].queryOutput.records.splice(rowIndex, 1);
@ -407,6 +444,7 @@ function EntityForm(props: Props): JSX.Element
widgetMetaData={widgetMetaData} widgetMetaData={widgetMetaData}
widgetData={widgetData} widgetData={widgetData}
recordValues={formValues} recordValues={formValues}
label={tableMetaData?.fields.get(widgetData?.filterFieldName ?? "queryFilterJson")?.label}
onSaveCallback={setFormFieldValuesFromWidget} onSaveCallback={setFormFieldValuesFromWidget}
/>; />;
} }
@ -478,6 +516,26 @@ function EntityForm(props: Props): JSX.Element
} }
/***************************************************************************
**
***************************************************************************/
function objectToMap(object: { [key: string]: any }): Map<string, any>
{
if(object == null)
{
return (null);
}
const rs = new Map<string, any>();
for (let key in object)
{
rs.set(key, object[key]);
}
return rs
}
////////////////// //////////////////
// initial load // // initial load //
////////////////// //////////////////
@ -595,18 +653,24 @@ function EntityForm(props: Props): JSX.Element
if (defaultValue) if (defaultValue)
{ {
initialValues[fieldName] = defaultValue; initialValues[fieldName] = defaultValue;
}
}
/////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// we need to set the initialDisplayValue for possible value fields with a default value // // do a second loop, this time looking up display-values for any possible-value fields with a default value //
// so, look them up here now if needed // // do it in a second loop, to pass in all the other values (from initialValues), in case there's a PVS filter that needs them. //
/////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if (fieldMetaData.possibleValueSourceName) for (let i = 0; i < fieldArray.length; i++)
{
const fieldMetaData = fieldArray[i];
const fieldName = fieldMetaData.name;
const defaultValue = (defaultValues && defaultValues[fieldName]) ? defaultValues[fieldName] : fieldMetaData.defaultValue;
if (defaultValue && fieldMetaData.possibleValueSourceName)
{
const results: QPossibleValue[] = await qController.possibleValues(tableName, null, fieldName, null, [initialValues[fieldName]], objectToMap(initialValues), "form");
if (results && results.length > 0)
{ {
const results: QPossibleValue[] = await qController.possibleValues(tableName, null, fieldName, null, [initialValues[fieldName]], undefined, "form"); defaultDisplayValues.set(fieldName, results[0].label);
if (results && results.length > 0)
{
defaultDisplayValues.set(fieldName, results[0].label);
}
} }
} }
} }
@ -823,7 +887,7 @@ function EntityForm(props: Props): JSX.Element
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if (props.onSubmitCallback) if (props.onSubmitCallback)
{ {
props.onSubmitCallback(values); props.onSubmitCallback(values, tableName);
return; return;
} }

View File

@ -21,7 +21,7 @@
import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData"; import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
import {Checkbox, FormControlLabel, Radio} from "@mui/material"; import {Checkbox, FormControlLabel, Radio, Tooltip} from "@mui/material";
import Autocomplete from "@mui/material/Autocomplete"; import Autocomplete from "@mui/material/Autocomplete";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
@ -34,6 +34,7 @@ import colors from "qqq/assets/theme/base/colors";
import QDynamicFormField from "qqq/components/forms/DynamicFormField"; import QDynamicFormField from "qqq/components/forms/DynamicFormField";
import DynamicFormUtils from "qqq/components/forms/DynamicFormUtils"; import DynamicFormUtils from "qqq/components/forms/DynamicFormUtils";
import {BulkLoadField, FileDescription} from "qqq/models/processes/BulkLoadModels"; import {BulkLoadField, FileDescription} from "qqq/models/processes/BulkLoadModels";
import Client from "qqq/utils/qqq/Client";
import React, {useEffect, useState} from "react"; import React, {useEffect, useState} from "react";
interface BulkLoadMappingFieldProps interface BulkLoadMappingFieldProps
@ -45,6 +46,29 @@ interface BulkLoadMappingFieldProps
forceParentUpdate?: () => void, forceParentUpdate?: () => void,
} }
const xIconButtonSX =
{
border: `1px solid ${colors.grayLines.main} !important`,
borderRadius: "0.5rem",
textTransform: "none",
fontSize: "1rem",
fontWeight: "400",
width: "30px",
minWidth: "30px",
height: "2rem",
minHeight: "2rem",
paddingLeft: 0,
paddingRight: 0,
marginRight: "0.5rem",
marginTop: "0.5rem",
color: colors.error.main,
"&:hover": {color: colors.error.main},
"&:focus": {color: colors.error.main},
"&:focus:not(:hover)": {color: colors.error.main},
};
const qController = Client.getInstance();
/*************************************************************************** /***************************************************************************
** row for a single field on the bulk load mapping screen. ** row for a single field on the bulk load mapping screen.
***************************************************************************/ ***************************************************************************/
@ -54,6 +78,11 @@ export default function BulkLoadFileMappingField({bulkLoadField, isRequired, rem
const [valueType, setValueType] = useState(bulkLoadField.valueType); const [valueType, setValueType] = useState(bulkLoadField.valueType);
const [selectedColumn, setSelectedColumn] = useState({label: columnNames[bulkLoadField.columnIndex], value: bulkLoadField.columnIndex}); const [selectedColumn, setSelectedColumn] = useState({label: columnNames[bulkLoadField.columnIndex], value: bulkLoadField.columnIndex});
const [selectedColumnInputValue, setSelectedColumnInputValue] = useState(columnNames[bulkLoadField.columnIndex]);
const [doingInitialLoadOfPossibleValue, setDoingInitialLoadOfPossibleValue] = useState(false);
const [everDidInitialLoadOfPossibleValue, setEverDidInitialLoadOfPossibleValue] = useState(false);
const [possibleValueInitialDisplayValue, setPossibleValueInitialDisplayValue] = useState(null as string);
const fieldMetaData = new QFieldMetaData(bulkLoadField.field); const fieldMetaData = new QFieldMetaData(bulkLoadField.field);
const dynamicField = DynamicFormUtils.getDynamicField(fieldMetaData); const dynamicField = DynamicFormUtils.getDynamicField(fieldMetaData);
@ -61,10 +90,59 @@ export default function BulkLoadFileMappingField({bulkLoadField, isRequired, rem
dynamicFieldInObject[fieldMetaData["name"]] = dynamicField; dynamicFieldInObject[fieldMetaData["name"]] = dynamicField;
DynamicFormUtils.addPossibleValueProps(dynamicFieldInObject, [fieldMetaData], bulkLoadField.tableStructure.tableName, null, null); DynamicFormUtils.addPossibleValueProps(dynamicFieldInObject, [fieldMetaData], bulkLoadField.tableStructure.tableName, null, null);
/////////////////////////////////////////////////////////////////////////////////////
// deal with dynamically loading the initial default value for a possible value... //
/////////////////////////////////////////////////////////////////////////////////////
let actuallyDoingInitialLoadOfPossibleValue = doingInitialLoadOfPossibleValue;
if(dynamicField.possibleValueProps && bulkLoadField.defaultValue && !doingInitialLoadOfPossibleValue && !everDidInitialLoadOfPossibleValue)
{
actuallyDoingInitialLoadOfPossibleValue = true;
setDoingInitialLoadOfPossibleValue(true);
setEverDidInitialLoadOfPossibleValue(true);
(async () =>
{
try
{
const possibleValues = await qController.possibleValues(bulkLoadField.tableStructure.tableName, null, fieldMetaData.name, null, [bulkLoadField.defaultValue], undefined, "filter");
if (possibleValues && possibleValues.length > 0)
{
setPossibleValueInitialDisplayValue(possibleValues[0].label);
}
else
{
setPossibleValueInitialDisplayValue(null);
}
}
catch(e)
{
console.log(`Error loading possible value: ${e}`)
}
actuallyDoingInitialLoadOfPossibleValue = false;
setDoingInitialLoadOfPossibleValue(false);
})();
}
if(dynamicField.possibleValueProps && possibleValueInitialDisplayValue)
{
dynamicField.possibleValueProps.initialDisplayValue = possibleValueInitialDisplayValue;
}
//////////////////////////////////////////////////////
// build array of options for the columns drop down //
// don't allow duplicates //
//////////////////////////////////////////////////////
const columnOptions: { value: number, label: string }[] = []; const columnOptions: { value: number, label: string }[] = [];
const usedLabels: {[label: string]: boolean} = {};
for (let i = 0; i < columnNames.length; i++) for (let i = 0; i < columnNames.length; i++)
{ {
columnOptions.push({label: columnNames[i], value: i}); const label = columnNames[i];
if(!usedLabels[label])
{
columnOptions.push({label: label, value: i});
usedLabels[label] = true;
}
} }
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
@ -73,6 +151,7 @@ export default function BulkLoadFileMappingField({bulkLoadField, isRequired, rem
if(bulkLoadField.columnIndex != null && bulkLoadField.columnIndex != undefined && selectedColumn.label && columnNames[bulkLoadField.columnIndex] != selectedColumn.label) if(bulkLoadField.columnIndex != null && bulkLoadField.columnIndex != undefined && selectedColumn.label && columnNames[bulkLoadField.columnIndex] != selectedColumn.label)
{ {
setSelectedColumn({label: columnNames[bulkLoadField.columnIndex], value: bulkLoadField.columnIndex}) setSelectedColumn({label: columnNames[bulkLoadField.columnIndex], value: bulkLoadField.columnIndex})
setSelectedColumnInputValue(columnNames[bulkLoadField.columnIndex]);
} }
const mainFontSize = "0.875rem"; const mainFontSize = "0.875rem";
@ -98,6 +177,8 @@ export default function BulkLoadFileMappingField({bulkLoadField, isRequired, rem
function columnChanged(event: any, newValue: any, reason: string) function columnChanged(event: any, newValue: any, reason: string)
{ {
setSelectedColumn(newValue); setSelectedColumn(newValue);
setSelectedColumnInputValue(newValue == null ? "" : newValue.label);
bulkLoadField.columnIndex = newValue == null ? null : newValue.value; bulkLoadField.columnIndex = newValue == null ? null : newValue.value;
if (fileDescription.hasHeaderRow) if (fileDescription.hasHeaderRow)
@ -106,6 +187,7 @@ export default function BulkLoadFileMappingField({bulkLoadField, isRequired, rem
} }
bulkLoadField.error = null; bulkLoadField.error = null;
bulkLoadField.warning = null;
forceParentUpdate && forceParentUpdate(); forceParentUpdate && forceParentUpdate();
} }
@ -118,6 +200,7 @@ export default function BulkLoadFileMappingField({bulkLoadField, isRequired, rem
setFieldValue(`${bulkLoadField.field.name}.defaultValue`, newValue); setFieldValue(`${bulkLoadField.field.name}.defaultValue`, newValue);
bulkLoadField.defaultValue = newValue; bulkLoadField.defaultValue = newValue;
bulkLoadField.error = null; bulkLoadField.error = null;
bulkLoadField.warning = null;
forceParentUpdate && forceParentUpdate(); forceParentUpdate && forceParentUpdate();
} }
@ -131,6 +214,7 @@ export default function BulkLoadFileMappingField({bulkLoadField, isRequired, rem
bulkLoadField.valueType = newValueType; bulkLoadField.valueType = newValueType;
setValueType(newValueType); setValueType(newValueType);
bulkLoadField.error = null; bulkLoadField.error = null;
bulkLoadField.warning = null;
forceParentUpdate && forceParentUpdate(); forceParentUpdate && forceParentUpdate();
} }
@ -144,7 +228,15 @@ export default function BulkLoadFileMappingField({bulkLoadField, isRequired, rem
forceParentUpdate && forceParentUpdate(); forceParentUpdate && forceParentUpdate();
} }
return (<Box py="0.5rem" sx={{borderBottom: "1px solid lightgray", width: "100%", overflow: "auto"}}> /***************************************************************************
**
***************************************************************************/
function changeSelectedColumnInputValue(e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>)
{
setSelectedColumnInputValue(e.target.value);
}
return (<Box py="0.5rem" sx={{borderBottom: "1px solid lightgray", width: "100%", overflow: "auto"}} id={`blfmf-${bulkLoadField.field.name}`}>
<Box display="grid" gridTemplateColumns="200px 400px auto" fontSize="1rem" gap="0.5rem" sx={ <Box display="grid" gridTemplateColumns="200px 400px auto" fontSize="1rem" gap="0.5rem" sx={
{ {
"& .MuiFormControlLabel-label": {ml: "0 !important", fontWeight: "normal !important", fontSize: mainFontSize} "& .MuiFormControlLabel-label": {ml: "0 !important", fontWeight: "normal !important", fontSize: mainFontSize}
@ -152,7 +244,9 @@ export default function BulkLoadFileMappingField({bulkLoadField, isRequired, rem
<Box display="flex" alignItems="flex-start"> <Box display="flex" alignItems="flex-start">
{ {
(!isRequired) && <IconButton onClick={() => removeFieldCallback()} sx={{pt: "0.75rem"}}><Icon fontSize="small">remove_circle</Icon></IconButton> (!isRequired) && <Tooltip placement="bottom" title="Remove this field from your mapping.">
<Button sx={xIconButtonSX} onClick={() => removeFieldCallback()}><Icon>clear</Icon></Button>
</Tooltip>
} }
<Box pt="0.625rem"> <Box pt="0.625rem">
{bulkLoadField.getQualifiedLabel()} {bulkLoadField.getQualifiedLabel()}
@ -167,13 +261,13 @@ export default function BulkLoadFileMappingField({bulkLoadField, isRequired, rem
valueType == "column" && <Box width="100%"> valueType == "column" && <Box width="100%">
<Autocomplete <Autocomplete
id={bulkLoadField.field.name} id={bulkLoadField.field.name}
renderInput={(params) => (<TextField {...params} label={""} value={selectedColumn?.label} fullWidth variant="outlined" autoComplete="off" type="search" InputProps={{...params.InputProps}} sx={{"& .MuiOutlinedInput-root": {borderRadius: "0.75rem"}}} />)} renderInput={(params) => (<TextField {...params} label={""} value={selectedColumnInputValue} onChange={e => changeSelectedColumnInputValue(e)} fullWidth variant="outlined" autoComplete="off" type="search" InputProps={{...params.InputProps}} sx={{"& .MuiOutlinedInput-root": {borderRadius: "0.75rem"}}} />)}
fullWidth fullWidth
options={columnOptions} options={columnOptions}
multiple={false} multiple={false}
defaultValue={selectedColumn} defaultValue={selectedColumn}
value={selectedColumn} value={selectedColumn}
inputValue={selectedColumn?.label} inputValue={selectedColumnInputValue}
onChange={columnChanged} onChange={columnChanged}
getOptionLabel={(option) => typeof (option) == "string" ? option : (option?.label ?? "")} getOptionLabel={(option) => typeof (option) == "string" ? option : (option?.label ?? "")}
isOptionEqualToValue={(option, value) => option == null && value == null || option.value == value.value} isOptionEqualToValue={(option, value) => option == null && value == null || option.value == value.value}
@ -186,7 +280,10 @@ export default function BulkLoadFileMappingField({bulkLoadField, isRequired, rem
<Box display="flex" alignItems="center" sx={{height: "45px"}}> <Box display="flex" alignItems="center" sx={{height: "45px"}}>
<FormControlLabel value="defaultValue" control={<Radio size="small" onChange={(event, checked) => valueTypeChanged(!checked)} />} label={"Default value"} sx={{minWidth: "140px", whiteSpace: "nowrap"}} /> <FormControlLabel value="defaultValue" control={<Radio size="small" onChange={(event, checked) => valueTypeChanged(!checked)} />} label={"Default value"} sx={{minWidth: "140px", whiteSpace: "nowrap"}} />
{ {
valueType == "defaultValue" && <Box width="100%"> valueType == "defaultValue" && actuallyDoingInitialLoadOfPossibleValue && <Box width="100%">Loading...</Box>
}
{
valueType == "defaultValue" && !actuallyDoingInitialLoadOfPossibleValue && <Box width="100%">
<QDynamicFormField <QDynamicFormField
name={`${bulkLoadField.field.name}.defaultValue`} name={`${bulkLoadField.field.name}.defaultValue`}
displayFormat={""} displayFormat={""}
@ -200,9 +297,15 @@ export default function BulkLoadFileMappingField({bulkLoadField, isRequired, rem
} }
</Box> </Box>
</Box> </Box>
{
bulkLoadField.warning &&
<Box fontSize={smallerFontSize} color={colors.warning.main} ml="145px" className="bulkLoadFieldError">
{bulkLoadField.warning}
</Box>
}
{ {
bulkLoadField.error && bulkLoadField.error &&
<Box fontSize={smallerFontSize} color={colors.error.main} ml="145px"> <Box fontSize={smallerFontSize} color={colors.error.main} ml="145px" className="bulkLoadFieldError">
{bulkLoadField.error} {bulkLoadField.error}
</Box> </Box>
} }

View File

@ -47,7 +47,7 @@ export default function BulkLoadFileMappingFields({bulkLoadMapping, fileDescript
{ {
const [, forceUpdate] = useReducer((x) => x + 1, 0); const [, forceUpdate] = useReducer((x) => x + 1, 0);
const [forceRerender, setForceRerender] = useState(0); const [forceHierarchyAutoCompleteRerender, setForceHierarchyAutoCompleteRerender] = useState(0);
//////////////////////////////////////////// ////////////////////////////////////////////
// build list of fields that can be added // // build list of fields that can be added //
@ -98,8 +98,9 @@ export default function BulkLoadFileMappingFields({bulkLoadMapping, fileDescript
setAddFieldsDisableStates(newDisableStates); setAddFieldsDisableStates(newDisableStates);
setTooltips(newTooltips); setTooltips(newTooltips);
setForceHierarchyAutoCompleteRerender(forceHierarchyAutoCompleteRerender + 1);
}, [bulkLoadMapping]); }, [bulkLoadMapping, bulkLoadMapping.layout]);
/////////////////////////////////////////////// ///////////////////////////////////////////////
@ -140,9 +141,6 @@ export default function BulkLoadFileMappingFields({bulkLoadMapping, fileDescript
***************************************************************************/ ***************************************************************************/
function removeField(bulkLoadField: BulkLoadField) function removeField(bulkLoadField: BulkLoadField)
{ {
// addFieldsToggleStates[bulkLoadField.getQualifiedName()] = false;
// setAddFieldsToggleStates(Object.assign({}, addFieldsToggleStates));
addFieldsDisableStates[bulkLoadField.getQualifiedName()] = false; addFieldsDisableStates[bulkLoadField.getQualifiedName()] = false;
setAddFieldsDisableStates(Object.assign({}, addFieldsDisableStates)); setAddFieldsDisableStates(Object.assign({}, addFieldsDisableStates));
@ -160,7 +158,7 @@ export default function BulkLoadFileMappingFields({bulkLoadMapping, fileDescript
bulkLoadMapping.removeField(bulkLoadField); bulkLoadMapping.removeField(bulkLoadField);
forceUpdate(); forceUpdate();
forceParentUpdate(); forceParentUpdate();
setForceRerender(forceRerender + 1); setForceHierarchyAutoCompleteRerender(forceHierarchyAutoCompleteRerender + 1);
} }
/*************************************************************************** /***************************************************************************
@ -297,7 +295,7 @@ export default function BulkLoadFileMappingFields({bulkLoadMapping, fileDescript
isModeSelectOne isModeSelectOne
keepOpenAfterSelectOne keepOpenAfterSelectOne
handleSelectedOption={handleAddField} handleSelectedOption={handleAddField}
forceRerender={forceRerender} forceRerender={forceHierarchyAutoCompleteRerender}
disabledStates={addFieldsDisableStates} disabledStates={addFieldsDisableStates}
tooltips={tooltips} tooltips={tooltips}
/> />

View File

@ -20,45 +20,56 @@
*/ */
import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData"; import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType";
import {QFrontendStepMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFrontendStepMetaData";
import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance"; import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance";
import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData";
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord"; import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
import {Badge, Icon} from "@mui/material";
import Autocomplete from "@mui/material/Autocomplete"; import Autocomplete from "@mui/material/Autocomplete";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import Grid from "@mui/material/Grid"; import Grid from "@mui/material/Grid";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import Tooltip from "@mui/material/Tooltip/Tooltip";
import {useFormikContext} from "formik"; import {useFormikContext} from "formik";
import colors from "qqq/assets/theme/base/colors";
import {DynamicFormFieldLabel} from "qqq/components/forms/DynamicForm"; import {DynamicFormFieldLabel} from "qqq/components/forms/DynamicForm";
import QDynamicFormField from "qqq/components/forms/DynamicFormField"; import QDynamicFormField from "qqq/components/forms/DynamicFormField";
import MDTypography from "qqq/components/legacy/MDTypography"; import MDTypography from "qqq/components/legacy/MDTypography";
import HelpContent from "qqq/components/misc/HelpContent";
import SavedBulkLoadProfiles from "qqq/components/misc/SavedBulkLoadProfiles"; import SavedBulkLoadProfiles from "qqq/components/misc/SavedBulkLoadProfiles";
import BulkLoadFileMappingFields from "qqq/components/processes/BulkLoadFileMappingFields"; import BulkLoadFileMappingFields from "qqq/components/processes/BulkLoadFileMappingFields";
import {BulkLoadField, BulkLoadMapping, BulkLoadProfile, BulkLoadTableStructure, FileDescription, Wrapper} from "qqq/models/processes/BulkLoadModels"; import {BulkLoadField, BulkLoadMapping, BulkLoadProfile, BulkLoadTableStructure, FileDescription, Wrapper} from "qqq/models/processes/BulkLoadModels";
import {SubFormPreSubmitCallbackResultType} from "qqq/pages/processes/ProcessRun"; import {SubFormPreSubmitCallbackResultType} from "qqq/pages/processes/ProcessRun";
import React, {forwardRef, useEffect, useImperativeHandle, useReducer, useState} from "react"; import React, {forwardRef, useImperativeHandle, useReducer, useState} from "react";
import ProcessViewForm from "./ProcessViewForm"; import ProcessViewForm from "./ProcessViewForm";
interface BulkLoadMappingFormProps interface BulkLoadMappingFormProps
{ {
processValues: any; processValues: any,
tableMetaData: QTableMetaData; tableMetaData: QTableMetaData,
metaData: QInstance; metaData: QInstance,
setActiveStepLabel: (label: string) => void; setActiveStepLabel: (label: string) => void,
frontendStep: QFrontendStepMetaData,
processMetaData: QProcessMetaData,
} }
/*************************************************************************** /***************************************************************************
** process component - screen where user does a bulk-load file mapping. ** process component - screen where user does a bulk-load file mapping.
***************************************************************************/ ***************************************************************************/
const BulkLoadFileMappingForm = forwardRef(({processValues, tableMetaData, metaData, setActiveStepLabel}: BulkLoadMappingFormProps, ref) => const BulkLoadFileMappingForm = forwardRef(({processValues, tableMetaData, metaData, setActiveStepLabel, frontendStep, processMetaData}: BulkLoadMappingFormProps, ref) =>
{ {
const {setFieldValue} = useFormikContext(); const {setFieldValue} = useFormikContext();
const [currentSavedBulkLoadProfile, setCurrentSavedBulkLoadProfile] = useState(null as QRecord); const savedBulkLoadProfileRecordProcessValue = processValues.savedBulkLoadProfileRecord;
const [currentSavedBulkLoadProfile, setCurrentSavedBulkLoadProfile] = useState(savedBulkLoadProfileRecordProcessValue == null ? null : new QRecord(savedBulkLoadProfileRecordProcessValue));
const [wrappedCurrentSavedBulkLoadProfile] = useState(new Wrapper<QRecord>(currentSavedBulkLoadProfile)); const [wrappedCurrentSavedBulkLoadProfile] = useState(new Wrapper<QRecord>(currentSavedBulkLoadProfile));
const [fieldErrors, setFieldErrors] = useState({} as { [fieldName: string]: string }); const [fieldErrors, setFieldErrors] = useState({} as { [fieldName: string]: string });
const [noMappedFieldsError, setNoMappedFieldsError] = useState(null as string);
const [suggestedBulkLoadProfile] = useState(processValues.suggestedBulkLoadProfile as BulkLoadProfile); const [suggestedBulkLoadProfile] = useState(processValues.suggestedBulkLoadProfile as BulkLoadProfile);
const [tableStructure] = useState(processValues.tableStructure as BulkLoadTableStructure); const [tableStructure] = useState(processValues.tableStructure as BulkLoadTableStructure);
@ -119,18 +130,31 @@ const BulkLoadFileMappingForm = forwardRef(({processValues, tableMetaData, metaD
} }
setFieldErrors(fieldErrors); setFieldErrors(fieldErrors);
if(wrappedBulkLoadMapping.get().requiredFields.length == 0 && wrappedBulkLoadMapping.get().additionalFields.length == 0)
{
setNoMappedFieldsError("You must have at least 1 field.");
haveLocalErrors = true;
setTimeout(() => setNoMappedFieldsError(null), 2500);
}
else
{
setNoMappedFieldsError(null);
}
if(haveProfileErrors)
{
setTimeout(() =>
{
document.querySelector(".bulkLoadFieldError")?.scrollIntoView({behavior: "smooth", block: "center", inline: "center"});
}, 250);
}
return {maySubmit: !haveProfileErrors && !haveLocalErrors, values}; return {maySubmit: !haveProfileErrors && !haveLocalErrors, values};
} }
}; };
}); });
useEffect(() =>
{
console.log("@dk has header row changed!");
}, [bulkLoadMapping.hasHeaderRow]);
/*************************************************************************** /***************************************************************************
** **
***************************************************************************/ ***************************************************************************/
@ -214,6 +238,8 @@ const BulkLoadFileMappingForm = forwardRef(({processValues, tableMetaData, metaD
tableStructure={tableStructure} tableStructure={tableStructure}
fileName={processValues.fileBaseName} fileName={processValues.fileBaseName}
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
frontendStep={frontendStep}
processMetaData={processMetaData}
forceParentUpdate={() => forceUpdate()} forceParentUpdate={() => forceUpdate()}
/> />
@ -221,8 +247,15 @@ const BulkLoadFileMappingForm = forwardRef(({processValues, tableMetaData, metaD
<BulkLoadFileMappingFields <BulkLoadFileMappingFields
bulkLoadMapping={bulkLoadMapping} bulkLoadMapping={bulkLoadMapping}
fileDescription={fileDescription} fileDescription={fileDescription}
forceParentUpdate={() => forceUpdate()} forceParentUpdate={() =>
{
setRerenderHeader(rerenderHeader + 1);
forceUpdate();
}}
/> />
{
noMappedFieldsError && <Box color={colors.error.main} textAlign="right">{noMappedFieldsError}</Box>
}
</Box> </Box>
</Box>); </Box>);
@ -232,8 +265,6 @@ const BulkLoadFileMappingForm = forwardRef(({processValues, tableMetaData, metaD
export default BulkLoadFileMappingForm; export default BulkLoadFileMappingForm;
interface BulkLoadMappingHeaderProps interface BulkLoadMappingHeaderProps
{ {
fileDescription: FileDescription, fileDescription: FileDescription,
@ -241,13 +272,15 @@ interface BulkLoadMappingHeaderProps
bulkLoadMapping?: BulkLoadMapping, bulkLoadMapping?: BulkLoadMapping,
fieldErrors: { [fieldName: string]: string }, fieldErrors: { [fieldName: string]: string },
tableStructure: BulkLoadTableStructure, tableStructure: BulkLoadTableStructure,
forceParentUpdate?: () => void forceParentUpdate?: () => void,
frontendStep: QFrontendStepMetaData,
processMetaData: QProcessMetaData,
} }
/*************************************************************************** /***************************************************************************
** private subcomponent - the header section of the bulk load file mapping screen. ** private subcomponent - the header section of the bulk load file mapping screen.
***************************************************************************/ ***************************************************************************/
function BulkLoadMappingHeader({fileDescription, fileName, bulkLoadMapping, fieldErrors, tableStructure, forceParentUpdate}: BulkLoadMappingHeaderProps): JSX.Element function BulkLoadMappingHeader({fileDescription, fileName, bulkLoadMapping, fieldErrors, tableStructure, forceParentUpdate, frontendStep, processMetaData}: BulkLoadMappingHeaderProps): JSX.Element
{ {
const viewFields = [ const viewFields = [
new QFieldMetaData({name: "fileName", label: "File Name", type: "STRING"}), new QFieldMetaData({name: "fileName", label: "File Name", type: "STRING"}),
@ -261,8 +294,6 @@ function BulkLoadMappingHeader({fileDescription, fileName, bulkLoadMapping, fiel
const hasHeaderRowFormField = {name: "hasHeaderRow", label: "Does the file have a header row?", type: "checkbox", isRequired: true, isEditable: true}; const hasHeaderRowFormField = {name: "hasHeaderRow", label: "Does the file have a header row?", type: "checkbox", isRequired: true, isEditable: true};
let helpRoles = ["PROCESS_SCREEN", "ALL_SCREENS"];
const layoutOptions = [ const layoutOptions = [
{label: "Flat", id: "FLAT"}, {label: "Flat", id: "FLAT"},
{label: "Tall", id: "TALL"}, {label: "Tall", id: "TALL"},
@ -276,27 +307,55 @@ function BulkLoadMappingHeader({fileDescription, fileName, bulkLoadMapping, fiel
const selectedLayout = layoutOptions.filter(o => o.id == bulkLoadMapping.layout)[0] ?? null; const selectedLayout = layoutOptions.filter(o => o.id == bulkLoadMapping.layout)[0] ?? null;
/***************************************************************************
**
***************************************************************************/
function hasHeaderRowChanged(newValue: any) function hasHeaderRowChanged(newValue: any)
{ {
bulkLoadMapping.hasHeaderRow = newValue; bulkLoadMapping.hasHeaderRow = newValue;
fileDescription.hasHeaderRow = newValue; fileDescription.hasHeaderRow = newValue;
bulkLoadMapping.handleChangeToHasHeaderRow(newValue, fileDescription);
fieldErrors.hasHeaderRow = null; fieldErrors.hasHeaderRow = null;
forceParentUpdate(); forceParentUpdate();
} }
/***************************************************************************
**
***************************************************************************/
function layoutChanged(event: any, newValue: any) function layoutChanged(event: any, newValue: any)
{ {
bulkLoadMapping.layout = newValue ? newValue.id : null; bulkLoadMapping.switchLayout(newValue ? newValue.id : null);
fieldErrors.layout = null; fieldErrors.layout = null;
forceParentUpdate(); forceParentUpdate();
} }
/***************************************************************************
**
***************************************************************************/
function getFormattedHelpContent(fieldName: string): JSX.Element
{
const field = frontendStep?.formFields?.find(f => f.name == fieldName);
let helpRoles = ["PROCESS_SCREEN", "ALL_SCREENS"];
let formattedHelpContent = <HelpContent helpContents={field?.helpContents} roles={helpRoles} helpContentKey={`process:${processMetaData?.name};field:${fieldName}`} />;
if (formattedHelpContent)
{
const mt = field && field.type == QFieldType.BOOLEAN ? "-0.5rem" : "0.5rem";
return <Box color="#757575" fontSize="0.875rem" mt={mt}>{formattedHelpContent}</Box>;
}
return null;
}
return ( return (
<Box> <Box>
<h5>File Details</h5> <h5>File Details</h5>
<Box ml="1rem"> <Box ml="1rem">
<ProcessViewForm fields={viewFields} values={viewValues} columns={2} /> <ProcessViewForm fields={viewFields} values={viewValues} columns={2} />
<BulkLoadMappingFilePreview fileDescription={fileDescription} /> <BulkLoadMappingFilePreview fileDescription={fileDescription} bulkLoadMapping={bulkLoadMapping} />
<Grid container pt="1rem"> <Grid container pt="1rem">
<Grid item xs={12} md={6}> <Grid item xs={12} md={6}>
<DynamicFormFieldLabel name={hasHeaderRowFormField.name} label={`${hasHeaderRowFormField.label} *`} /> <DynamicFormFieldLabel name={hasHeaderRowFormField.name} label={`${hasHeaderRowFormField.label} *`} />
@ -307,6 +366,7 @@ function BulkLoadMappingHeader({fileDescription, fileName, bulkLoadMapping, fiel
{<div className="fieldErrorMessage">{fieldErrors.hasHeaderRow}</div>} {<div className="fieldErrorMessage">{fieldErrors.hasHeaderRow}</div>}
</MDTypography> </MDTypography>
} }
{getFormattedHelpContent("hasHeaderRow")}
</Grid> </Grid>
<Grid item xs={12} md={6}> <Grid item xs={12} md={6}>
<DynamicFormFieldLabel name={"layout"} label={"File Layout *"} /> <DynamicFormFieldLabel name={"layout"} label={"File Layout *"} />
@ -320,6 +380,7 @@ function BulkLoadMappingHeader({fileDescription, fileName, bulkLoadMapping, fiel
getOptionLabel={(option) => typeof (option) == "string" ? option : (option?.label ?? "")} getOptionLabel={(option) => typeof (option) == "string" ? option : (option?.label ?? "")}
isOptionEqualToValue={(option, value) => option == null && value == null || option.id == value.id} isOptionEqualToValue={(option, value) => option == null && value == null || option.id == value.id}
renderOption={(props, option, state) => (<li {...props}>{option?.label ?? ""}</li>)} renderOption={(props, option, state) => (<li {...props}>{option?.label ?? ""}</li>)}
disableClearable
sx={{"& .MuiOutlinedInput-root": {padding: "0"}}} sx={{"& .MuiOutlinedInput-root": {padding: "0"}}}
/> />
{ {
@ -328,6 +389,7 @@ function BulkLoadMappingHeader({fileDescription, fileName, bulkLoadMapping, fiel
{<div className="fieldErrorMessage">{fieldErrors.layout}</div>} {<div className="fieldErrorMessage">{fieldErrors.layout}</div>}
</MDTypography> </MDTypography>
} }
{getFormattedHelpContent("layout")}
</Grid> </Grid>
</Grid> </Grid>
</Box> </Box>
@ -336,16 +398,16 @@ function BulkLoadMappingHeader({fileDescription, fileName, bulkLoadMapping, fiel
} }
interface BulkLoadMappingFilePreviewProps interface BulkLoadMappingFilePreviewProps
{ {
fileDescription: FileDescription; fileDescription: FileDescription,
bulkLoadMapping?: BulkLoadMapping
} }
/*************************************************************************** /***************************************************************************
** private subcomponent - the file-preview section of the bulk load file mapping screen. ** private subcomponent - the file-preview section of the bulk load file mapping screen.
***************************************************************************/ ***************************************************************************/
function BulkLoadMappingFilePreview({fileDescription}: BulkLoadMappingFilePreviewProps): JSX.Element function BulkLoadMappingFilePreview({fileDescription, bulkLoadMapping}: BulkLoadMappingFilePreviewProps): JSX.Element
{ {
const rows: number[] = []; const rows: number[] = [];
for (let i = 0; i < fileDescription.bodyValuesPreview[0].length; i++) for (let i = 0; i < fileDescription.bodyValuesPreview[0].length; i++)
@ -353,25 +415,145 @@ function BulkLoadMappingFilePreview({fileDescription}: BulkLoadMappingFilePrevie
rows.push(i); rows.push(i);
} }
/***************************************************************************
**
***************************************************************************/
function getValue(i: number, j: number)
{
const value = fileDescription.bodyValuesPreview[j][i];
if (value == null)
{
return "";
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// this was useful at one point in time when we had an object coming back for xlsx files with many different data types //
// we'd see a .string attribute, which would have the value we'd want to show. not using it now, but keep in case //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// @ts-ignore
if (value && value.string)
{
// @ts-ignore
return (value.string);
}
return `${value}`;
}
/***************************************************************************
**
***************************************************************************/
function getHeaderColor(count: number): string
{
if (count > 0)
{
return "blue";
}
return "black";
}
/***************************************************************************
**
***************************************************************************/
function getCursor(count: number): string
{
if (count > 0)
{
return "pointer";
}
return "default";
}
/***************************************************************************
**
***************************************************************************/
function getColumnTooltip(fields: BulkLoadField[])
{
return (<Box>
This column is mapped to the field{fields.length == 1 ? "" : "s"}:
<ul style={{marginLeft: "1rem"}}>
{fields.map((field, i) => <li key={i}>{field.getQualifiedLabel()}</li>)}
</ul>
</Box>);
}
return ( return (
<Box sx={{"& table, & td": {border: "1px solid black", borderCollapse: "collapse", padding: "0 0.25rem", fontSize: "0.875rem", whiteSpace: "nowrap"}}}> <Box sx={{"& table, & td": {border: "1px solid black", borderCollapse: "collapse", padding: "0 0.25rem", fontSize: "0.875rem", whiteSpace: "nowrap"}}}>
<Box sx={{width: "100%", overflow: "auto"}}> <Box sx={{width: "100%", overflow: "auto"}}>
<table cellSpacing="0" width="100%"> <table cellSpacing="0" width="100%">
<thead> <thead>
<tr style={{backgroundColor: "#d3d3d3"}}> <tr style={{backgroundColor: "#d3d3d3", height: "1.75rem"}}>
<td></td> <td></td>
{fileDescription.headerLetters.map((letter) => <td key={letter} style={{textAlign: "center"}}>{letter}</td>)} {fileDescription.headerLetters.map((letter, index) =>
{
const fields = bulkLoadMapping.getFieldsForColumnIndex(index);
const count = fields.length;
let dupeWarning = <></>
if(fileDescription.hasHeaderRow && fileDescription.duplicateHeaderIndexes[index])
{
dupeWarning = <Tooltip title="This column header is a duplicate. Only the first occurrance of it will be used." placement="top" enterDelay={500}>
<Icon color="warning" sx={{p: "0.125rem", mr: "0.25rem"}}>warning</Icon>
</Tooltip>
}
return (<td key={letter} style={{textAlign: "center", color: getHeaderColor(count), cursor: getCursor(count)}}>
<>
{
count > 0 &&
<Tooltip title={getColumnTooltip(fields)} placement="top" enterDelay={500}>
<Box>
{dupeWarning}
{letter}
<Badge badgeContent={count} variant={"standard"} color="secondary" sx={{marginTop: ".75rem"}}><Icon></Icon></Badge>
</Box>
</Tooltip>
}
{
count == 0 && <Box>{dupeWarning}{letter}</Box>
}
</>
</td>);
})}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td style={{backgroundColor: "#d3d3d3", textAlign: "center"}}>1</td> <td style={{backgroundColor: "#d3d3d3", textAlign: "center"}}>1</td>
{fileDescription.headerValues.map((value) => <td key={value} style={{backgroundColor: fileDescription.hasHeaderRow ? "#ebebeb" : ""}}>{value}</td>)}
{fileDescription.headerValues.map((value, index) =>
{
const fields = bulkLoadMapping.getFieldsForColumnIndex(index);
const count = fields.length;
const tdStyle = {color: getHeaderColor(count), cursor: getCursor(count), backgroundColor: ""};
if(fileDescription.hasHeaderRow)
{
tdStyle.backgroundColor = "#ebebeb";
if(count > 0)
{
return <td key={value} style={tdStyle}>
<Tooltip title={getColumnTooltip(fields)} placement="top" enterDelay={500}><Box>{value}</Box></Tooltip>
</td>
}
else
{
return <td key={value} style={tdStyle}>{value}</td>
}
}
else
{
return <td key={value} style={tdStyle}>{value}</td>
}
}
)}
</tr> </tr>
{rows.map((i) => ( {rows.map((i) => (
<tr key={i}> <tr key={i}>
<td style={{backgroundColor: "#d3d3d3", textAlign: "center"}}>{i + 2}</td> <td style={{backgroundColor: "#d3d3d3", textAlign: "center"}}>{i + 2}</td>
{fileDescription.headerLetters.map((letter, j) => <td key={j}>{fileDescription.bodyValuesPreview[j][i]}</td>)} {fileDescription.headerLetters.map((letter, j) => <td key={j}>{getValue(i, j)}</td>)}
</tr> </tr>
))} ))}
</tbody> </tbody>

View File

@ -266,76 +266,6 @@ function ValidationReview({
</List> </List>
); );
/***************************************************************************
**
***************************************************************************/
function previewRecordUsingTableLayout(record: QRecord)
{
if (!previewTableMetaData)
{
return (<Box>Loading...</Box>);
}
const renderedSections: JSX.Element[] = [];
const tableSections = TableUtils.getSectionsForRecordSidebar(previewTableMetaData);
const previewRecord = previewRecords[previewRecordIndex];
for (let i = 0; i < tableSections.length; i++)
{
const section = tableSections[i];
if (section.isHidden)
{
continue;
}
if (section.fieldNames)
{
renderedSections.push(<Box mb="1rem">
<Box><h4>{section.label}</h4></Box>
<Box ml="1rem">
{renderSectionOfFields(section.name, section.fieldNames, previewTableMetaData, false, previewRecord, undefined, {label: {fontWeight: "500"}})}
</Box>
</Box>);
}
else if (section.widgetName)
{
const widget = qInstance.widgets.get(section.widgetName);
if (widget)
{
let data: ChildRecordListData = null;
if (associationPreviewsByWidgetName[section.widgetName])
{
const associationPreview = associationPreviewsByWidgetName[section.widgetName];
const associationRecords = previewRecord.associatedRecords.get(associationPreview.associationName) ?? [];
data = {
canAddChildRecord: false,
childTableMetaData: childTableMetaData[associationPreview.tableName],
defaultValuesForNewChildRecords: {},
disabledFieldsForNewChildRecords: {},
queryOutput: {records: associationRecords},
totalRows: associationRecords.length,
tablePath: "",
title: "",
viewAllLink: "",
};
renderedSections.push(<Box mb="1rem">
{
data && <Box>
<Box mb="0.5rem"><h4>{section.label}</h4></Box>
<Box pl="1rem">
<RecordGridWidget data={data} widgetMetaData={widget} disableRowClick gridOnly={true} gridDensity={"compact"} />
</Box>
</Box>
}
</Box>);
}
}
}
}
return renderedSections;
}
const recordPreviewWidget = step.recordListFields && ( const recordPreviewWidget = step.recordListFields && (
<Box border="1px solid rgb(70%, 70%, 70%)" borderRadius="10px" p={2} mt={2}> <Box border="1px solid rgb(70%, 70%, 70%)" borderRadius="10px" p={2} mt={2}>
@ -370,11 +300,11 @@ function ValidationReview({
{ {
processValues.validationSummary ? ( processValues.validationSummary ? (
<> <>
It appears as though this process does not contain any valid records. It appears as though this process does not contain any valid records.
</> </>
) : ( ) : (
<> <>
If you choose to Perform Validation, and there are any valid records, then you will see a preview here. If you choose to Perform Validation, and there are any valid records, then you will see a preview here.
</> </>
) )
} }
@ -405,7 +335,15 @@ function ValidationReview({
)) ))
} }
{ {
previewRecords && processValues.formatPreviewRecordUsingTableLayout && previewRecords[previewRecordIndex] && previewRecordUsingTableLayout(previewRecords[previewRecordIndex]) previewRecords && processValues.formatPreviewRecordUsingTableLayout && previewRecords[previewRecordIndex] &&
<PreviewRecordUsingTableLayout
index={previewRecordIndex}
record={previewRecords[previewRecordIndex]}
tableMetaData={previewTableMetaData}
qInstance={qInstance}
associationPreviewsByWidgetName={associationPreviewsByWidgetName}
childTableMetaData={childTableMetaData}
/>
} }
</Box> </Box>
</Box> </Box>
@ -441,4 +379,84 @@ function ValidationReview({
); );
} }
interface PreviewRecordUsingTableLayoutProps
{
index: number
record: QRecord,
tableMetaData: QTableMetaData,
qInstance: QInstance,
associationPreviewsByWidgetName: { [widgetName: string]: AssociationPreview },
childTableMetaData: { [name: string]: QTableMetaData },
}
function PreviewRecordUsingTableLayout({record, tableMetaData, qInstance, associationPreviewsByWidgetName, childTableMetaData, index}: PreviewRecordUsingTableLayoutProps): JSX.Element
{
if (!tableMetaData)
{
return (<i>Loading...</i>);
}
const renderedSections: JSX.Element[] = [];
const tableSections = TableUtils.getSectionsForRecordSidebar(tableMetaData);
for (let i = 0; i < tableSections.length; i++)
{
const section = tableSections[i];
if (section.isHidden)
{
continue;
}
if (section.fieldNames)
{
renderedSections.push(<Box mb="1rem">
<Box><h4>{section.label}</h4></Box>
<Box ml="1rem">
{renderSectionOfFields(section.name, section.fieldNames, tableMetaData, false, record, undefined, {label: {fontWeight: "500"}})}
</Box>
</Box>);
}
else if (section.widgetName)
{
const widget = qInstance.widgets.get(section.widgetName);
if (widget)
{
let data: ChildRecordListData = null;
if (associationPreviewsByWidgetName[section.widgetName])
{
const associationPreview = associationPreviewsByWidgetName[section.widgetName];
const associationRecords = record.associatedRecords?.get(associationPreview.associationName) ?? [];
data = {
canAddChildRecord: false,
childTableMetaData: childTableMetaData[associationPreview.tableName],
defaultValuesForNewChildRecords: {},
disabledFieldsForNewChildRecords: {},
queryOutput: {records: associationRecords},
totalRows: associationRecords.length,
tablePath: "",
title: "",
viewAllLink: "",
};
renderedSections.push(<Box mb="1rem">
{
data && <Box>
<Box mb="0.5rem"><h4>{section.label}</h4></Box>
<Box pl="1rem">
<RecordGridWidget key={index} data={data} widgetMetaData={widget} disableRowClick gridOnly={true} gridDensity={"compact"} />
</Box>
</Box>
}
</Box>);
}
}
}
}
return <>{renderedSections}</>;
}
export default ValidationReview; export default ValidationReview;

View File

@ -118,7 +118,7 @@ const doesOperatorOptionEqualCriteria = (operatorOption: OperatorOption, criteri
** autocomplete), given an array of options, the query's active criteria in this ** autocomplete), given an array of options, the query's active criteria in this
** field, and the default operator to use for this field ** field, and the default operator to use for this field
*******************************************************************************/ *******************************************************************************/
const getOperatorSelectedValue = (operatorOptions: OperatorOption[], criteria: QFilterCriteriaWithId, defaultOperator: QCriteriaOperator): OperatorOption => const getOperatorSelectedValue = (operatorOptions: OperatorOption[], criteria: QFilterCriteriaWithId, defaultOperator: QCriteriaOperator, return0thOptionInsteadOfNull: boolean = false): OperatorOption =>
{ {
if (criteria) if (criteria)
{ {
@ -135,6 +135,23 @@ const getOperatorSelectedValue = (operatorOptions: OperatorOption[], criteria: Q
return (filteredOptions[0]); return (filteredOptions[0]);
} }
if(return0thOptionInsteadOfNull)
{
console.log("Returning 0th operator instead of null - this isn't expected, but has been seen to happen - so here's some additional debugging:");
try
{
console.log("Operator options: " + JSON.stringify(operatorOptions));
console.log("Criteria: " + JSON.stringify(criteria));
console.log("Default Operator: " + JSON.stringify(defaultOperator));
}
catch(e)
{
console.log(`Error in debug output: ${e}`);
}
return operatorOptions[0];
}
return (null); return (null);
}; };
@ -157,7 +174,7 @@ export default function QuickFilter({tableMetaData, fullFieldName, fieldMetaData
const [criteria, setCriteria] = useState(criteriaParamIsCriteria(criteriaParam) ? Object.assign({}, criteriaParam) as QFilterCriteriaWithId : null); const [criteria, setCriteria] = useState(criteriaParamIsCriteria(criteriaParam) ? Object.assign({}, criteriaParam) as QFilterCriteriaWithId : null);
const [id, setId] = useState(criteriaParamIsCriteria(criteriaParam) ? (criteriaParam as QFilterCriteriaWithId).id : ++seedId); const [id, setId] = useState(criteriaParamIsCriteria(criteriaParam) ? (criteriaParam as QFilterCriteriaWithId).id : ++seedId);
const [operatorSelectedValue, setOperatorSelectedValue] = useState(getOperatorSelectedValue(operatorOptions, criteria, defaultOperator)); const [operatorSelectedValue, setOperatorSelectedValue] = useState(getOperatorSelectedValue(operatorOptions, criteria, defaultOperator, true));
const [operatorInputValue, setOperatorInputValue] = useState(operatorSelectedValue?.label); const [operatorInputValue, setOperatorInputValue] = useState(operatorSelectedValue?.label);
const {criteriaIsValid, criteriaStatusTooltip} = validateCriteria(criteria, operatorSelectedValue); const {criteriaIsValid, criteriaStatusTooltip} = validateCriteria(criteria, operatorSelectedValue);

View File

@ -49,7 +49,7 @@ function ScriptDocsForm({helpText, exampleCode, aceEditorHeight}: Props): JSX.El
<Card sx={{width: "100%", height: "100%"}}> <Card sx={{width: "100%", height: "100%"}}>
<Typography variant="h6" p={2} pb={1}>{heading}</Typography> <Typography variant="h6" p={2} pb={1}>{heading}</Typography>
<Box className="devDocumentation" height="100%"> <Box className="devDocumentation" height="100%">
<Typography variant="body2" sx={{maxWidth: "1200px", margin: "auto", height: "100%"}}> <Typography variant="body2" sx={{maxWidth: "1200px", margin: "auto", height: "calc(100% - 0.5rem)"}}>
<AceEditor <AceEditor
mode={mode} mode={mode}
theme="github" theme="github"
@ -62,7 +62,7 @@ function ScriptDocsForm({helpText, exampleCode, aceEditorHeight}: Props): JSX.El
width="100%" width="100%"
showPrintMargin={false} showPrintMargin={false}
height="100%" height="100%"
style={{borderBottomRightRadius: "1rem", borderBottomLeftRadius: "1rem"}} style={{borderBottomRightRadius: "0.75rem", borderBottomLeftRadius: "0.75rem"}}
/> />
</Typography> </Typography>
</Box> </Box>

View File

@ -313,6 +313,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco
function deleteChildRecord(name: string, widgetIndex: number, rowIndex: number) function deleteChildRecord(name: string, widgetIndex: number, rowIndex: number)
{ {
updateChildRecordList(name, "delete", rowIndex); updateChildRecordList(name, "delete", rowIndex);
forceUpdate();
actionCallback(widgetData[widgetIndex]); actionCallback(widgetData[widgetIndex]);
}; };
@ -368,7 +369,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
function submitEditChildForm(values: any) function submitEditChildForm(values: any, tableName: string)
{ {
updateChildRecordList(showEditChildForm.widgetName, showEditChildForm.rowIndex == null ? "insert" : "edit", showEditChildForm.rowIndex, values); updateChildRecordList(showEditChildForm.widgetName, showEditChildForm.rowIndex == null ? "insert" : "edit", showEditChildForm.rowIndex, values);
let widgetIndex = determineChildRecordListIndex(showEditChildForm.widgetName); let widgetIndex = determineChildRecordListIndex(showEditChildForm.widgetName);
@ -718,6 +719,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco
addNewRecordCallback={widgetData[i]?.isInProcess ? () => openAddChildRecord(widgetMetaData.name, widgetData[i]) : null} addNewRecordCallback={widgetData[i]?.isInProcess ? () => openAddChildRecord(widgetMetaData.name, widgetData[i]) : null}
widgetMetaData={widgetMetaData} widgetMetaData={widgetMetaData}
data={widgetData[i]} data={widgetData[i]}
parentRecord={record}
/> />
) )

View File

@ -46,11 +46,12 @@ import React, {useContext, useEffect, useRef, useState} from "react";
interface FilterAndColumnsSetupWidgetProps interface FilterAndColumnsSetupWidgetProps
{ {
isEditable: boolean; isEditable: boolean,
widgetMetaData: QWidgetMetaData; widgetMetaData: QWidgetMetaData,
widgetData: any; widgetData: any,
recordValues: { [name: string]: any }; recordValues: { [name: string]: any },
onSaveCallback?: (values: { [name: string]: any }) => void; onSaveCallback?: (values: { [name: string]: any }) => void,
label?: string
} }
FilterAndColumnsSetupWidget.defaultProps = { FilterAndColumnsSetupWidget.defaultProps = {
@ -83,13 +84,16 @@ const qController = Client.getInstance();
/******************************************************************************* /*******************************************************************************
** Component for editing the main setup of a report - that is: filter & columns ** Component for editing the main setup of a report - that is: filter & columns
*******************************************************************************/ *******************************************************************************/
export default function FilterAndColumnsSetupWidget({isEditable, widgetMetaData, widgetData, recordValues, onSaveCallback}: FilterAndColumnsSetupWidgetProps): JSX.Element export default function FilterAndColumnsSetupWidget({isEditable, widgetMetaData, widgetData, recordValues, onSaveCallback, label}: FilterAndColumnsSetupWidgetProps): JSX.Element
{ {
const [modalOpen, setModalOpen] = useState(false); const [modalOpen, setModalOpen] = useState(false);
const [hideColumns, setHideColumns] = useState(widgetData?.hideColumns); const [hideColumns] = useState(widgetData?.hideColumns);
const [hidePreview, setHidePreview] = useState(widgetData?.hidePreview); const [hidePreview] = useState(widgetData?.hidePreview);
const [tableMetaData, setTableMetaData] = useState(null as QTableMetaData); const [tableMetaData, setTableMetaData] = useState(null as QTableMetaData);
const [filterFieldName] = useState(widgetData?.filterFieldName ?? "queryFilterJson");
const [columnsFieldName] = useState(widgetData?.columnsFieldName ?? "columnsJson");
const [alertContent, setAlertContent] = useState(null as string); const [alertContent, setAlertContent] = useState(null as string);
////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////
@ -108,7 +112,7 @@ export default function FilterAndColumnsSetupWidget({isEditable, widgetMetaData,
///////////////////////////// /////////////////////////////
let columns: QQueryColumns = null; let columns: QQueryColumns = null;
let usingDefaultEmptyFilter = false; let usingDefaultEmptyFilter = false;
let queryFilter = recordValues["queryFilterJson"] && JSON.parse(recordValues["queryFilterJson"]) as QQueryFilter; let queryFilter = recordValues[filterFieldName] && JSON.parse(recordValues[filterFieldName]) as QQueryFilter;
const defaultFilterFields = widgetData?.filterDefaultFieldNames; const defaultFilterFields = widgetData?.filterDefaultFieldNames;
if (!queryFilter) if (!queryFilter)
{ {
@ -142,9 +146,9 @@ export default function FilterAndColumnsSetupWidget({isEditable, widgetMetaData,
}); });
} }
if (recordValues["columnsJson"]) if (recordValues[columnsFieldName])
{ {
columns = QQueryColumns.buildFromJSON(recordValues["columnsJson"]); columns = QQueryColumns.buildFromJSON(recordValues[columnsFieldName]);
} }
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
@ -230,7 +234,10 @@ export default function FilterAndColumnsSetupWidget({isEditable, widgetMetaData,
setFrontendQueryFilter(view.queryFilter); setFrontendQueryFilter(view.queryFilter);
const filter = FilterUtils.prepQueryFilterForBackend(tableMetaData, view.queryFilter); const filter = FilterUtils.prepQueryFilterForBackend(tableMetaData, view.queryFilter);
onSaveCallback({queryFilterJson: JSON.stringify(filter), columnsJson: JSON.stringify(view.queryColumns)}); const rs: { [key: string]: any } = {};
rs[filterFieldName] = JSON.stringify(filter);
rs[columnsFieldName] = JSON.stringify(view.queryColumns);
onSaveCallback(rs);
closeEditor(); closeEditor();
} }
@ -356,7 +363,7 @@ export default function FilterAndColumnsSetupWidget({isEditable, widgetMetaData,
</Collapse> </Collapse>
<Box pt="0.5rem"> <Box pt="0.5rem">
<Box display="flex" justifyContent="space-between" alignItems="center"> <Box display="flex" justifyContent="space-between" alignItems="center">
<h5>Query Filter</h5> <h5>{label ?? "Query Filter"}</h5>
<Box fontSize="0.75rem" fontWeight="700">{mayShowQuery() && getCurrentSortIndicator(frontendQueryFilter, tableMetaData, null)}</Box> <Box fontSize="0.75rem" fontWeight="700">{mayShowQuery() && getCurrentSortIndicator(frontendQueryFilter, tableMetaData, null)}</Box>
</Box> </Box>
{ {

View File

@ -40,7 +40,7 @@ import {Link, useNavigate} from "react-router-dom";
export interface ChildRecordListData extends WidgetData export interface ChildRecordListData extends WidgetData
{ {
title?: string; title?: string;
queryOutput?: { records: { values: any }[] }; queryOutput?: { records: { values: any, displayValues?: any } [] };
childTableMetaData?: QTableMetaData; childTableMetaData?: QTableMetaData;
tablePath?: string; tablePath?: string;
viewAllLink?: string; viewAllLink?: string;
@ -48,20 +48,22 @@ export interface ChildRecordListData extends WidgetData
canAddChildRecord?: boolean; canAddChildRecord?: boolean;
defaultValuesForNewChildRecords?: { [fieldName: string]: any }; defaultValuesForNewChildRecords?: { [fieldName: string]: any };
disabledFieldsForNewChildRecords?: { [fieldName: string]: any }; disabledFieldsForNewChildRecords?: { [fieldName: string]: any };
defaultValuesForNewChildRecordsFromParentFields?: { [fieldName: string]: string };
} }
interface Props interface Props
{ {
widgetMetaData: QWidgetMetaData; widgetMetaData: QWidgetMetaData,
data: ChildRecordListData; data: ChildRecordListData,
addNewRecordCallback?: () => void; addNewRecordCallback?: () => void,
disableRowClick: boolean; disableRowClick: boolean,
allowRecordEdit: boolean; allowRecordEdit: boolean,
editRecordCallback?: (rowIndex: number) => void; editRecordCallback?: (rowIndex: number) => void,
allowRecordDelete: boolean; allowRecordDelete: boolean,
deleteRecordCallback?: (rowIndex: number) => void; deleteRecordCallback?: (rowIndex: number) => void,
gridOnly?: boolean; gridOnly?: boolean,
gridDensity?: GridDensity; gridDensity?: GridDensity,
parentRecord?: QRecord
} }
RecordGridWidget.defaultProps = RecordGridWidget.defaultProps =
@ -74,7 +76,7 @@ RecordGridWidget.defaultProps =
const qController = Client.getInstance(); const qController = Client.getInstance();
function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRowClick, allowRecordEdit, editRecordCallback, allowRecordDelete, deleteRecordCallback, gridOnly, gridDensity}: Props): JSX.Element function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRowClick, allowRecordEdit, editRecordCallback, allowRecordDelete, deleteRecordCallback, gridOnly, gridDensity, parentRecord}: Props): JSX.Element
{ {
const instance = useRef({timer: null}); const instance = useRef({timer: null});
const [rows, setRows] = useState([]); const [rows, setRows] = useState([]);
@ -97,7 +99,7 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
{ {
for (let i = 0; i < queryOutputRecords.length; i++) for (let i = 0; i < queryOutputRecords.length; i++)
{ {
if(queryOutputRecords[i] instanceof QRecord) if (queryOutputRecords[i] instanceof QRecord)
{ {
records.push(queryOutputRecords[i] as QRecord); records.push(queryOutputRecords[i] as QRecord);
} }
@ -252,7 +254,22 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
{ {
disabledFields = data.defaultValuesForNewChildRecords; disabledFields = data.defaultValuesForNewChildRecords;
} }
labelAdditionalComponentsRight.push(new AddNewRecordButton(data.childTableMetaData, data.defaultValuesForNewChildRecords, "Add new", disabledFields, addNewRecordCallback));
const defaultValuesForNewChildRecords = data.defaultValuesForNewChildRecords || {}
///////////////////////////////////////////////////////////////////////////////////////
// copy values from specified fields in the parent record down into the child record //
///////////////////////////////////////////////////////////////////////////////////////
if(data.defaultValuesForNewChildRecordsFromParentFields)
{
for(let childField in data.defaultValuesForNewChildRecordsFromParentFields)
{
const parentField = data.defaultValuesForNewChildRecordsFromParentFields[childField];
defaultValuesForNewChildRecords[childField] = parentRecord?.values?.get(parentField);
}
}
labelAdditionalComponentsRight.push(new AddNewRecordButton(data.childTableMetaData, defaultValuesForNewChildRecords, "Add new", disabledFields, addNewRecordCallback));
} }
@ -357,7 +374,7 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
/> />
); );
if(gridOnly) if (gridOnly)
{ {
return (grid); return (grid);
} }

View File

@ -393,7 +393,7 @@ export default function ScriptViewer({scriptId, associatedScriptTableName, assoc
} }
return ( return (
<Grid container className="scriptViewer" m={-2} pt={4} width={"calc(100% + 2rem)"}> <Grid container className="scriptViewer" my={-3} mx={-3} pt={4} width={"calc(100% + 3rem)"}>
<Grid item xs={12}> <Grid item xs={12}>
<Box> <Box>
{ {
@ -530,7 +530,7 @@ export default function ScriptViewer({scriptId, associatedScriptTableName, assoc
</TabPanel> </TabPanel>
<TabPanel index={2} value={selectedTab}> <TabPanel index={2} value={selectedTab}>
<Box sx={{height: "455px"}} px={2} pb={1}> <Box sx={{height: "455px"}} px={2} pt={1}>
<ScriptTestForm scriptId={scriptId} <ScriptTestForm scriptId={scriptId}
scriptType={scriptTypeRecord} scriptType={scriptTypeRecord}
tableName={associatedScriptTableName} tableName={associatedScriptTableName}
@ -543,7 +543,7 @@ export default function ScriptViewer({scriptId, associatedScriptTableName, assoc
</TabPanel> </TabPanel>
<TabPanel index={3} value={selectedTab}> <TabPanel index={3} value={selectedTab}>
<Box sx={{height: "455px"}} px={2} pb={1}> <Box sx={{height: "455px"}} px={2} pt={1}>
<ScriptDocsForm helpText={scriptTypeRecord?.values.get("helpText")} exampleCode={scriptTypeRecord?.values.get("sampleCode")} /> <ScriptDocsForm helpText={scriptTypeRecord?.values.get("helpText")} exampleCode={scriptTypeRecord?.values.get("sampleCode")} />
</Box> </Box>
</TabPanel> </TabPanel>

View File

@ -35,7 +35,7 @@ export interface ModalEditFormData
defaultValues?: { [key: string]: string }; defaultValues?: { [key: string]: string };
disabledFields?: { [key: string]: boolean } | string[]; disabledFields?: { [key: string]: boolean } | string[];
overrideHeading?: string; overrideHeading?: string;
onSubmitCallback?: (values: any) => void; onSubmitCallback?: (values: any, tableName: String) => void;
initialShowModalValue?: boolean; initialShowModalValue?: boolean;
} }

View File

@ -21,6 +21,7 @@
import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData"; import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType";
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord"; import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
export type ValueType = "defaultValue" | "column"; export type ValueType = "defaultValue" | "column";
@ -42,6 +43,7 @@ export class BulkLoadField
wideLayoutIndexPath: number[] = []; wideLayoutIndexPath: number[] = [];
error: string = null; error: string = null;
warning: string = null;
key: string; key: string;
@ -49,7 +51,7 @@ export class BulkLoadField
/*************************************************************************** /***************************************************************************
** **
***************************************************************************/ ***************************************************************************/
constructor(field: QFieldMetaData, tableStructure: BulkLoadTableStructure, valueType: ValueType = "column", columnIndex?: number, headerName?: string, defaultValue?: any, doValueMapping?: boolean, wideLayoutIndexPath: number[] = []) constructor(field: QFieldMetaData, tableStructure: BulkLoadTableStructure, valueType: ValueType = "column", columnIndex?: number, headerName?: string, defaultValue?: any, doValueMapping?: boolean, wideLayoutIndexPath: number[] = [], error: string = null, warning: string = null)
{ {
this.field = field; this.field = field;
this.tableStructure = tableStructure; this.tableStructure = tableStructure;
@ -59,6 +61,8 @@ export class BulkLoadField
this.defaultValue = defaultValue; this.defaultValue = defaultValue;
this.doValueMapping = doValueMapping; this.doValueMapping = doValueMapping;
this.wideLayoutIndexPath = wideLayoutIndexPath; this.wideLayoutIndexPath = wideLayoutIndexPath;
this.error = error;
this.warning = warning;
this.key = new Date().getTime().toString(); this.key = new Date().getTime().toString();
} }
@ -68,7 +72,7 @@ export class BulkLoadField
***************************************************************************/ ***************************************************************************/
public static clone(source: BulkLoadField): BulkLoadField public static clone(source: BulkLoadField): BulkLoadField
{ {
return (new BulkLoadField(source.field, source.tableStructure, source.valueType, source.columnIndex, source.headerName, source.defaultValue, source.doValueMapping, source.wideLayoutIndexPath)); return (new BulkLoadField(source.field, source.tableStructure, source.valueType, source.columnIndex, source.headerName, source.defaultValue, source.doValueMapping, source.wideLayoutIndexPath, source.error, source.warning));
} }
@ -422,17 +426,22 @@ export class BulkLoadMapping
} }
else else
{ {
index = 0; ///////////////////////////////////////////////
/////////////////////////////////////////////////////////// // find the max index for this field already //
// count how many copies of this field there are already // ///////////////////////////////////////////////
/////////////////////////////////////////////////////////// let maxIndex = -1;
for (let existingField of [...this.requiredFields, ...this.additionalFields]) for (let existingField of [...this.requiredFields, ...this.additionalFields])
{ {
if (existingField.getQualifiedName() == bulkLoadField.getQualifiedName()) if (existingField.getQualifiedName() == bulkLoadField.getQualifiedName())
{ {
index++; const thisIndex = existingField.wideLayoutIndexPath[0];
if (thisIndex != null && thisIndex != undefined && thisIndex > maxIndex)
{
maxIndex = thisIndex;
}
} }
} }
index = maxIndex + 1;
} }
const cloneField = BulkLoadField.clone(bulkLoadField); const cloneField = BulkLoadField.clone(bulkLoadField);
@ -455,7 +464,7 @@ export class BulkLoadMapping
const newAdditionalFields: BulkLoadField[] = []; const newAdditionalFields: BulkLoadField[] = [];
for (let bulkLoadField of this.additionalFields) for (let bulkLoadField of this.additionalFields)
{ {
if (bulkLoadField.getQualifiedName() != toRemove.getQualifiedName()) if (bulkLoadField.getQualifiedNameWithWideSuffix() != toRemove.getQualifiedNameWithWideSuffix())
{ {
newAdditionalFields.push(bulkLoadField); newAdditionalFields.push(bulkLoadField);
} }
@ -463,6 +472,171 @@ export class BulkLoadMapping
this.additionalFields = newAdditionalFields; this.additionalFields = newAdditionalFields;
} }
/***************************************************************************
**
***************************************************************************/
public switchLayout(newLayout: string): void
{
const newAdditionalFields: BulkLoadField[] = [];
let anyChanges = false;
if ("WIDE" != newLayout)
{
////////////////////////////////////////////////////////////////////////////////////////////////////////
// if going to a layout other than WIDE, make sure there aren't any fields with a wideLayoutIndexPath //
////////////////////////////////////////////////////////////////////////////////////////////////////////
const namesWhereOneWideLayoutIndexHasBeenFound: { [name: string]: boolean } = {};
for (let existingField of this.additionalFields)
{
if (existingField.wideLayoutIndexPath.length > 0)
{
const name = existingField.getQualifiedName();
if (namesWhereOneWideLayoutIndexHasBeenFound[name])
{
/////////////////////////////////////////////////////////////////////////////////////////////////////////
// in this case, we're on like the 2nd or 3rd instance of, say, Line Item: SKU - so - just discard it. //
/////////////////////////////////////////////////////////////////////////////////////////////////////////
anyChanges = true;
}
else
{
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// else, this is the 1st instance of, say, Line Item: SKU - so mark that we've found it - and keep this field //
// (that is, put it in the new array), but with no index path //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namesWhereOneWideLayoutIndexHasBeenFound[name] = true;
const newField = BulkLoadField.clone(existingField);
newField.wideLayoutIndexPath = [];
newAdditionalFields.push(newField);
anyChanges = true;
}
}
else
{
//////////////////////////////////////////////////////
// else, non-wide-path fields, just get added as-is //
//////////////////////////////////////////////////////
newAdditionalFields.push(existingField);
}
}
}
else
{
///////////////////////////////////////////////////////////////////////////////////////////////
// if going to WIDE layout, then any field from a child table needs a wide-layout-index-path //
///////////////////////////////////////////////////////////////////////////////////////////////
for (let existingField of this.additionalFields)
{
if (existingField.tableStructure.isMain)
{
////////////////////////////////////////////
// fields from main table come over as-is //
////////////////////////////////////////////
newAdditionalFields.push(existingField);
}
else
{
/////////////////////////////////////////////////////////////////////////////////////////////
// fields from child tables get a wideLayoutIndexPath (and we're assuming just 1 for each) //
/////////////////////////////////////////////////////////////////////////////////////////////
const newField = BulkLoadField.clone(existingField);
newField.wideLayoutIndexPath = [0];
newAdditionalFields.push(newField);
anyChanges = true;
}
}
}
if (anyChanges)
{
this.additionalFields = newAdditionalFields;
}
this.layout = newLayout;
}
/***************************************************************************
**
***************************************************************************/
public getFieldsForColumnIndex(i: number): BulkLoadField[]
{
const rs: BulkLoadField[] = [];
for (let field of [...this.requiredFields, ...this.additionalFields])
{
if (field.valueType == "column" && field.columnIndex == i)
{
rs.push(field);
}
}
return (rs);
}
/***************************************************************************
**
***************************************************************************/
public handleChangeToHasHeaderRow(newValue: any, fileDescription: FileDescription)
{
const newRequiredFields: BulkLoadField[] = [];
let anyChangesToRequiredFields = false;
const newAdditionalFields: BulkLoadField[] = [];
let anyChangesToAdditionalFields = false;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// if we're switching to have header-rows enabled, then make sure that no columns w/ duplicated headers are selected //
// strategy to do this: build new lists of both required & additional fields - and track if we had to change any //
// column indexes (set to null) - add a warning to them, and only replace the arrays if there were changes. //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if (newValue)
{
for (let field of this.requiredFields)
{
if (field.valueType == "column" && fileDescription.duplicateHeaderIndexes[field.columnIndex])
{
const newField = BulkLoadField.clone(field);
newField.columnIndex = null;
newField.warning = "This field was assigned to a column with a duplicated header"
newRequiredFields.push(newField);
anyChangesToRequiredFields = true;
}
else
{
newRequiredFields.push(field);
}
}
for (let field of this.additionalFields)
{
if (field.valueType == "column" && fileDescription.duplicateHeaderIndexes[field.columnIndex])
{
const newField = BulkLoadField.clone(field);
newField.columnIndex = null;
newField.warning = "This field was assigned to a column with a duplicated header"
newAdditionalFields.push(newField);
anyChangesToAdditionalFields = true;
}
else
{
newAdditionalFields.push(field);
}
}
}
if (anyChangesToRequiredFields)
{
this.requiredFields = newRequiredFields;
}
if (anyChangesToAdditionalFields)
{
this.additionalFields = newAdditionalFields;
}
}
} }
@ -475,6 +649,8 @@ export class FileDescription
headerLetters: string[]; headerLetters: string[];
bodyValuesPreview: string[][]; bodyValuesPreview: string[][];
duplicateHeaderIndexes: boolean[];
// todo - just get this from the profile always - it's not part of the file per-se // todo - just get this from the profile always - it's not part of the file per-se
hasHeaderRow: boolean = true; hasHeaderRow: boolean = true;
@ -486,6 +662,18 @@ export class FileDescription
this.headerValues = headerValues; this.headerValues = headerValues;
this.headerLetters = headerLetters; this.headerLetters = headerLetters;
this.bodyValuesPreview = bodyValuesPreview; this.bodyValuesPreview = bodyValuesPreview;
this.duplicateHeaderIndexes = [];
const usedLabels: { [label: string]: boolean } = {};
for (let i = 0; i < headerValues.length; i++)
{
const label = headerValues[i];
if (usedLabels[label])
{
this.duplicateHeaderIndexes[i] = true;
}
usedLabels[label] = true;
}
} }
@ -517,21 +705,85 @@ export class FileDescription
/*************************************************************************** /***************************************************************************
** **
***************************************************************************/ ***************************************************************************/
public getPreviewValues(columnIndex: number): string[] public getPreviewValues(columnIndex: number, fieldType?: QFieldType): string[]
{ {
if (columnIndex == undefined) if (columnIndex == undefined)
{ {
return []; return [];
} }
if (this.hasHeaderRow) function getTypedValue(value: any): string
{ {
return (this.bodyValuesPreview[columnIndex]); if (value == null)
{
return "";
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// this was useful at one point in time when we had an object coming back for xlsx files with many different data types //
// we'd see a .string attribute, which would have the value we'd want to show. not using it now, but keep in case //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if (value && value.string)
{
switch (fieldType)
{
case QFieldType.BOOLEAN:
{
return value.bool;
}
case QFieldType.STRING:
case QFieldType.TEXT:
case QFieldType.HTML:
case QFieldType.PASSWORD:
{
return value.string;
}
case QFieldType.INTEGER:
case QFieldType.LONG:
{
return value.integer;
}
case QFieldType.DECIMAL:
{
return value.decimal;
}
case QFieldType.DATE:
{
return value.date;
}
case QFieldType.TIME:
{
return value.time;
}
case QFieldType.DATE_TIME:
{
return value.dateTime;
}
case QFieldType.BLOB:
return ""; // !!
}
}
return (`${value}`);
} }
else
const valueArray: string[] = [];
if (!this.hasHeaderRow)
{ {
return ([this.headerValues[columnIndex], ...this.bodyValuesPreview[columnIndex]]); const typedValue = getTypedValue(this.headerValues[columnIndex]);
valueArray.push(typedValue == null ? "" : `${typedValue}`);
} }
for (let value of this.bodyValuesPreview[columnIndex])
{
const typedValue = getTypedValue(value);
valueArray.push(typedValue == null ? "" : `${typedValue}`);
}
return (valueArray);
} }
} }

View File

@ -1032,9 +1032,11 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
<BulkLoadFileMappingForm <BulkLoadFileMappingForm
processValues={processValues} processValues={processValues}
tableMetaData={tableMetaData} tableMetaData={tableMetaData}
processMetaData={processMetaData}
metaData={qInstance} metaData={qInstance}
ref={bulkLoadFileMappingFormRef} ref={bulkLoadFileMappingFormRef}
setActiveStepLabel={setActiveStepLabel} setActiveStepLabel={setActiveStepLabel}
frontendStep={activeStep}
/> />
) )
} }
@ -2220,7 +2222,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
if (isModal) if (isModal)
{ {
return ( return (
<Box sx={{position: "absolute", overflowY: "auto", maxHeight: "100%", width: "100%"}}> <Box sx={{position: "absolute", overflowY: "auto", maxHeight: "100%", width: "100%"}} id="modalProcessScrollContainer">
{body} {body}
</Box> </Box>
); );

View File

@ -191,7 +191,7 @@ function RecordDeveloperView({table}: Props): JSX.Element
<Card sx={{mb: 3}}> <Card sx={{mb: 3}}>
<Typography variant="h6" p={2} pl={3} pb={3}>{field?.label}</Typography> <Typography variant="h6" p={2} pl={3} pb={3}>{field?.label}</Typography>
<Box display="flex" alignItems="center" justifyContent="space-between" gap={2}> <Box display="flex" alignItems="center" justifyContent="space-between" gap={2} mx={3} mb={3} mt={0}>
{scriptId ? {scriptId ?
<ScriptViewer <ScriptViewer
scriptId={scriptId} scriptId={scriptId}

View File

@ -92,9 +92,9 @@ const TABLE_VARIANT_LOCAL_STORAGE_KEY_ROOT = "qqq.tableVariant";
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
export function renderSectionOfFields(key: string, fieldNames: string[], tableMetaData: QTableMetaData, helpHelpActive: boolean, record: QRecord, fieldMap?: { [name: string]: QFieldMetaData }, styleOverrides?: {label?: SxProps, value?: SxProps}) export function renderSectionOfFields(key: string, fieldNames: string[], tableMetaData: QTableMetaData, helpHelpActive: boolean, record: QRecord, fieldMap?: { [name: string]: QFieldMetaData }, styleOverrides?: { label?: SxProps, value?: SxProps })
{ {
return <Box key={key} display="flex" flexDirection="column" py={1} pr={2}> return <Grid container lg={12} key={key} display="flex" py={1} pr={2}>
{ {
fieldNames.map((fieldName: string) => fieldNames.map((fieldName: string) =>
{ {
@ -103,6 +103,7 @@ export function renderSectionOfFields(key: string, fieldNames: string[], tableMe
if (field != null) if (field != null)
{ {
let label = field.label; let label = field.label;
let gridColumns = (field.gridColumns && field.gridColumns > 0) ? field.gridColumns : 12;
const helpRoles = ["VIEW_SCREEN", "READ_SCREENS", "ALL_SCREENS"]; const helpRoles = ["VIEW_SCREEN", "READ_SCREENS", "ALL_SCREENS"];
const showHelp = helpHelpActive || hasHelpContent(field.helpContents, helpRoles); const showHelp = helpHelpActive || hasHelpContent(field.helpContents, helpRoles);
@ -111,7 +112,7 @@ export function renderSectionOfFields(key: string, fieldNames: string[], tableMe
const labelElement = <Typography variant="button" textTransform="none" fontWeight="bold" pr={1} color="rgb(52, 71, 103)" sx={{cursor: "default", ...(styleOverrides?.label ?? {})}}>{label}:</Typography>; const labelElement = <Typography variant="button" textTransform="none" fontWeight="bold" pr={1} color="rgb(52, 71, 103)" sx={{cursor: "default", ...(styleOverrides?.label ?? {})}}>{label}:</Typography>;
return ( return (
<Box key={fieldName} flexDirection="row" pr={2}> <Grid item key={fieldName} lg={gridColumns} flexDirection="column" pr={2}>
<> <>
{ {
showHelp && formattedHelpContent ? <Tooltip title={formattedHelpContent}>{labelElement}</Tooltip> : labelElement showHelp && formattedHelpContent ? <Tooltip title={formattedHelpContent}>{labelElement}</Tooltip> : labelElement
@ -121,12 +122,12 @@ export function renderSectionOfFields(key: string, fieldNames: string[], tableMe
{ValueUtils.getDisplayValue(field, record, "view", fieldName)} {ValueUtils.getDisplayValue(field, record, "view", fieldName)}
</Typography> </Typography>
</> </>
</Box> </Grid>
); );
} }
}) })
} }
</Box>; </Grid>;
} }

View File

@ -37,7 +37,7 @@ export class SavedBulkLoadProfileUtils
for (let bulkLoadField of orderedFieldArray) for (let bulkLoadField of orderedFieldArray)
{ {
const fieldName = bulkLoadField.field.name; const fieldName = bulkLoadField.getQualifiedName()
const compareField = compareFieldsMap[fieldName]; const compareField = compareFieldsMap[fieldName];
const baseField = baseFieldsMap[fieldName]; const baseField = baseFieldsMap[fieldName];
if(!compareField) if(!compareField)
@ -55,12 +55,13 @@ export class SavedBulkLoadProfileUtils
if (compareField.valueType == "column") if (compareField.valueType == "column")
{ {
const column = fileDescription.getColumnNames()[compareField.columnIndex]; const column = fileDescription.getColumnNames()[compareField.columnIndex];
rs.push(`Changed ${compareField.getQualifiedLabel()} from using a default value (${baseField.defaultValue}) to using a file column (${column})`); rs.push(`Changed ${compareField.getQualifiedLabel()} from using a default value (${baseField.defaultValue}) to using a file column ${column ? `(${column})` : ""}`);
} }
else if (compareField.valueType == "defaultValue") else if (compareField.valueType == "defaultValue")
{ {
const column = fileDescription.getColumnNames()[baseField.columnIndex]; const column = fileDescription.getColumnNames()[baseField.columnIndex];
rs.push(`Changed ${compareField.getQualifiedLabel()} from using a file column (${column}) to using a default value (${compareField.defaultValue})`); const value = compareField.defaultValue;
rs.push(`Changed ${compareField.getQualifiedLabel()} from using a file column (${column}) to using a default value ${value === undefined ? "" : `(${value})`}`);
} }
} }
else if (baseField.valueType == compareField.valueType && baseField.valueType == "defaultValue") else if (baseField.valueType == compareField.valueType && baseField.valueType == "defaultValue")
@ -70,7 +71,8 @@ export class SavedBulkLoadProfileUtils
////////////////////////////////////////////////// //////////////////////////////////////////////////
if (baseField.defaultValue != compareField.defaultValue) if (baseField.defaultValue != compareField.defaultValue)
{ {
rs.push(`Changed ${compareField.getQualifiedLabel()} default value from (${baseField.defaultValue}) to (${compareField.defaultValue})`); const value = compareField.defaultValue;
rs.push(`Changed ${compareField.getQualifiedLabel()} default value from (${baseField.defaultValue}) to ${value === undefined ? "" : `(${value})`}`);
} }
} }
else if (baseField.valueType == compareField.valueType && baseField.valueType == "column") else if (baseField.valueType == compareField.valueType && baseField.valueType == "column")
@ -78,25 +80,29 @@ export class SavedBulkLoadProfileUtils
/////////////////////////////////////////// ///////////////////////////////////////////
// if we changed the column, report that // // if we changed the column, report that //
/////////////////////////////////////////// ///////////////////////////////////////////
let isDiff = false;
if (fileDescription.hasHeaderRow) if (fileDescription.hasHeaderRow)
{ {
if (baseField.headerName != compareField.headerName) if (baseField.headerName != compareField.headerName)
{ {
const baseColumn = fileDescription.getColumnNames()[baseField.columnIndex]; isDiff = true;
const compareColumn = fileDescription.getColumnNames()[compareField.columnIndex];
rs.push(`Changed ${compareField.getQualifiedLabel()} file column from (${baseColumn}) to (${compareColumn})`);
} }
} }
else else
{ {
if (baseField.columnIndex != compareField.columnIndex) if (baseField.columnIndex != compareField.columnIndex)
{ {
const baseColumn = fileDescription.getColumnNames()[baseField.columnIndex]; isDiff = true;
const compareColumn = fileDescription.getColumnNames()[compareField.columnIndex];
rs.push(`Changed ${compareField.getQualifiedLabel()} file column from (${baseColumn}) to (${compareColumn})`);
} }
} }
if(isDiff)
{
const baseColumn = fileDescription.getColumnNames()[baseField.columnIndex];
const compareColumn = fileDescription.getColumnNames()[compareField.columnIndex];
rs.push(`Changed ${compareField.getQualifiedLabel()} file column from ${baseColumn ? `(${baseColumn})` : "--"} to ${compareColumn ? `(${compareColumn})` : "--"}`);
}
///////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////
// if the do-value-mapping field changed, report that (note, only if was and still is column-type) // // if the do-value-mapping field changed, report that (note, only if was and still is column-type) //
///////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////
@ -120,7 +126,7 @@ export class SavedBulkLoadProfileUtils
for (let bulkLoadField of orderedFieldArray) for (let bulkLoadField of orderedFieldArray)
{ {
const fieldName = bulkLoadField.field.name; const fieldName = bulkLoadField.getQualifiedName()
const compareField = compareFieldsMap[fieldName]; const compareField = compareFieldsMap[fieldName];
if(!compareField) if(!compareField)
{ {
@ -292,7 +298,7 @@ export class SavedBulkLoadProfileUtils
{ {
try try
{ {
const fieldName = bulkLoadField.field.name; const fieldName = bulkLoadField.getQualifiedName() // todo - does this (and the others calls to this) need suffix?
const valueMappingDiff = this.diffFieldValueMappings(bulkLoadField, baseMapping.valueMappings[fieldName] ?? {}, activeMapping.valueMappings[fieldName] ?? {}); const valueMappingDiff = this.diffFieldValueMappings(bulkLoadField, baseMapping.valueMappings[fieldName] ?? {}, activeMapping.valueMappings[fieldName] ?? {});
if(valueMappingDiff) if(valueMappingDiff)