mirror of
https://github.com/Kingsrook/qqq-frontend-material-dashboard.git
synced 2025-07-21 06:38:43 +00:00
Compare commits
25 Commits
snapshot-f
...
snapshot-i
Author | SHA1 | Date | |
---|---|---|---|
ce9ffaab4d | |||
6076c4ddfd | |||
71dc3f3f65 | |||
ce22db2f89 | |||
9816403bec | |||
aacb239164 | |||
219458ec63 | |||
59fdc72455 | |||
5c3ddb7dec | |||
d65c1fb5d8 | |||
19a63d6956 | |||
40f5b55307 | |||
7320b19fbb | |||
3f8a3e7e4d | |||
3ef2d64327 | |||
d793c23861 | |||
d0201d96e1 | |||
7b66ece466 | |||
02c163899a | |||
8fafe16a95 | |||
722c8d3bcf | |||
85acb612c9 | |||
74c634414a | |||
f8368b030c | |||
dda4ea4f4b |
@ -6,7 +6,7 @@
|
||||
"@auth0/auth0-react": "1.10.2",
|
||||
"@emotion/react": "11.7.1",
|
||||
"@emotion/styled": "11.6.0",
|
||||
"@kingsrook/qqq-frontend-core": "1.0.113",
|
||||
"@kingsrook/qqq-frontend-core": "1.0.114",
|
||||
"@mui/icons-material": "5.4.1",
|
||||
"@mui/material": "5.11.1",
|
||||
"@mui/styles": "5.11.1",
|
||||
|
@ -57,7 +57,7 @@ function QDynamicForm({formData, formLabel, bulkEditMode, bulkEditSwitchChangeHa
|
||||
<MDTypography variant="h5">{formLabel}</MDTypography>
|
||||
</Box>
|
||||
<Box mt={1.625}>
|
||||
<Grid container spacing={3}>
|
||||
<Grid container lg={12} display="flex" spacing={3}>
|
||||
{formFields
|
||||
&& Object.keys(formFields).length > 0
|
||||
&& 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}`} />;
|
||||
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} />;
|
||||
|
||||
let itemLG = (field?.fieldMetaData?.gridColumns && field?.fieldMetaData?.gridColumns > 0) ? field.fieldMetaData.gridColumns : 6;
|
||||
let itemXS = 12;
|
||||
let itemSM = 6;
|
||||
|
||||
@ -92,13 +93,13 @@ function QDynamicForm({formData, formLabel, bulkEditMode, bulkEditSwitchChangeHa
|
||||
const fileUploadAdornment = field.fieldMetaData?.getAdornment(AdornmentType.FILE_UPLOAD);
|
||||
const width = fileUploadAdornment?.values?.get("width") ?? "half";
|
||||
|
||||
if(width == "full")
|
||||
if (width == "full")
|
||||
{
|
||||
itemSM = 12;
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid item xs={itemXS} sm={itemSM} key={fieldName}>
|
||||
<Grid item lg={itemLG} xs={itemXS} sm={itemSM} flexDirection="column" key={fieldName}>
|
||||
{labelElement}
|
||||
<FileInputField field={field} record={record} errorMessage={errors[fieldName]} />
|
||||
</Grid>
|
||||
@ -114,10 +115,10 @@ function QDynamicForm({formData, formLabel, bulkEditMode, bulkEditSwitchChangeHa
|
||||
Object.keys(values).forEach((key) =>
|
||||
{
|
||||
otherValuesMap.set(key, values[key]);
|
||||
})
|
||||
});
|
||||
|
||||
return (
|
||||
<Grid item xs={itemXS} sm={itemSM} key={fieldName}>
|
||||
<Grid item lg={itemLG} xs={itemXS} sm={itemSM} key={fieldName}>
|
||||
{labelElement}
|
||||
<DynamicSelect
|
||||
fieldPossibleValueProps={field.possibleValueProps}
|
||||
@ -138,7 +139,7 @@ function QDynamicForm({formData, formLabel, bulkEditMode, bulkEditSwitchChangeHa
|
||||
// everything else!! //
|
||||
///////////////////////
|
||||
return (
|
||||
<Grid item xs={itemXS} sm={itemSM} key={fieldName}>
|
||||
<Grid item lg={itemLG} xs={itemXS} sm={itemSM} key={fieldName}>
|
||||
{labelElement}
|
||||
<QDynamicFormField
|
||||
id={field.name}
|
||||
|
@ -141,7 +141,10 @@ function QDynamicFormField({
|
||||
newValue = newValue.toLowerCase();
|
||||
}
|
||||
setFieldValue(name, newValue);
|
||||
onChangeCallback(newValue);
|
||||
if(onChangeCallback)
|
||||
{
|
||||
onChangeCallback(newValue);
|
||||
}
|
||||
});
|
||||
|
||||
const input = document.getElementById(name) as HTMLInputElement;
|
||||
|
@ -66,7 +66,7 @@ interface Props
|
||||
defaultValues: { [key: string]: string };
|
||||
disabledFields: { [key: string]: boolean } | string[];
|
||||
isCopy?: boolean;
|
||||
onSubmitCallback?: (values: any) => void;
|
||||
onSubmitCallback?: (values: any, tableName: string) => void;
|
||||
overrideHeading?: string;
|
||||
}
|
||||
|
||||
@ -173,7 +173,7 @@ function EntityForm(props: Props): JSX.Element
|
||||
*******************************************************************************/
|
||||
function openAddChildRecord(name: string, widgetData: any)
|
||||
{
|
||||
let defaultValues = widgetData.defaultValuesForNewChildRecords;
|
||||
let defaultValues = widgetData.defaultValuesForNewChildRecords || {};
|
||||
|
||||
let disabledFields = widgetData.disabledFieldsForNewChildRecords;
|
||||
if (!disabledFields)
|
||||
@ -181,6 +181,18 @@ function EntityForm(props: Props): JSX.Element
|
||||
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);
|
||||
}
|
||||
|
||||
@ -208,7 +220,7 @@ function EntityForm(props: Props): JSX.Element
|
||||
function deleteChildRecord(name: string, widgetData: any, rowIndex: number)
|
||||
{
|
||||
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 widgetMetaData = metaData.widgets.get(widgetName);
|
||||
@ -263,13 +275,38 @@ function EntityForm(props: Props): JSX.Element
|
||||
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)
|
||||
{
|
||||
case "insert":
|
||||
newChildListWidgetData[widgetName].queryOutput.records.push({values: values});
|
||||
newChildListWidgetData[widgetName].queryOutput.records.push({values: values, displayValues: displayValues});
|
||||
break;
|
||||
case "edit":
|
||||
newChildListWidgetData[widgetName].queryOutput.records[rowIndex] = {values: values};
|
||||
newChildListWidgetData[widgetName].queryOutput.records[rowIndex] = {values: values, displayValues: displayValues};
|
||||
break;
|
||||
case "delete":
|
||||
newChildListWidgetData[widgetName].queryOutput.records.splice(rowIndex, 1);
|
||||
@ -407,6 +444,7 @@ function EntityForm(props: Props): JSX.Element
|
||||
widgetMetaData={widgetMetaData}
|
||||
widgetData={widgetData}
|
||||
recordValues={formValues}
|
||||
label={tableMetaData?.fields.get(widgetData?.filterFieldName ?? "queryFilterJson")?.label}
|
||||
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 //
|
||||
//////////////////
|
||||
@ -595,18 +653,24 @@ function EntityForm(props: Props): JSX.Element
|
||||
if (defaultValue)
|
||||
{
|
||||
initialValues[fieldName] = defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// we need to set the initialDisplayValue for possible value fields with a default value //
|
||||
// so, look them up here now if needed //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
if (fieldMetaData.possibleValueSourceName)
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// do a second loop, this time looking up display-values for any possible-value fields with a default value //
|
||||
// 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. //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
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");
|
||||
if (results && results.length > 0)
|
||||
{
|
||||
defaultDisplayValues.set(fieldName, results[0].label);
|
||||
}
|
||||
defaultDisplayValues.set(fieldName, results[0].label);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -823,7 +887,7 @@ function EntityForm(props: Props): JSX.Element
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if (props.onSubmitCallback)
|
||||
{
|
||||
props.onSubmitCallback(values);
|
||||
props.onSubmitCallback(values, tableName);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@
|
||||
|
||||
|
||||
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 Box from "@mui/material/Box";
|
||||
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 DynamicFormUtils from "qqq/components/forms/DynamicFormUtils";
|
||||
import {BulkLoadField, FileDescription} from "qqq/models/processes/BulkLoadModels";
|
||||
import Client from "qqq/utils/qqq/Client";
|
||||
import React, {useEffect, useState} from "react";
|
||||
|
||||
interface BulkLoadMappingFieldProps
|
||||
@ -45,6 +46,29 @@ interface BulkLoadMappingFieldProps
|
||||
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.
|
||||
***************************************************************************/
|
||||
@ -54,6 +78,11 @@ export default function BulkLoadFileMappingField({bulkLoadField, isRequired, rem
|
||||
|
||||
const [valueType, setValueType] = useState(bulkLoadField.valueType);
|
||||
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 dynamicField = DynamicFormUtils.getDynamicField(fieldMetaData);
|
||||
@ -61,10 +90,59 @@ export default function BulkLoadFileMappingField({bulkLoadField, isRequired, rem
|
||||
dynamicFieldInObject[fieldMetaData["name"]] = dynamicField;
|
||||
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 usedLabels: {[label: string]: boolean} = {};
|
||||
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)
|
||||
{
|
||||
setSelectedColumn({label: columnNames[bulkLoadField.columnIndex], value: bulkLoadField.columnIndex})
|
||||
setSelectedColumnInputValue(columnNames[bulkLoadField.columnIndex]);
|
||||
}
|
||||
|
||||
const mainFontSize = "0.875rem";
|
||||
@ -98,6 +177,8 @@ export default function BulkLoadFileMappingField({bulkLoadField, isRequired, rem
|
||||
function columnChanged(event: any, newValue: any, reason: string)
|
||||
{
|
||||
setSelectedColumn(newValue);
|
||||
setSelectedColumnInputValue(newValue == null ? "" : newValue.label);
|
||||
|
||||
bulkLoadField.columnIndex = newValue == null ? null : newValue.value;
|
||||
|
||||
if (fileDescription.hasHeaderRow)
|
||||
@ -106,6 +187,7 @@ export default function BulkLoadFileMappingField({bulkLoadField, isRequired, rem
|
||||
}
|
||||
|
||||
bulkLoadField.error = null;
|
||||
bulkLoadField.warning = null;
|
||||
forceParentUpdate && forceParentUpdate();
|
||||
}
|
||||
|
||||
@ -118,6 +200,7 @@ export default function BulkLoadFileMappingField({bulkLoadField, isRequired, rem
|
||||
setFieldValue(`${bulkLoadField.field.name}.defaultValue`, newValue);
|
||||
bulkLoadField.defaultValue = newValue;
|
||||
bulkLoadField.error = null;
|
||||
bulkLoadField.warning = null;
|
||||
forceParentUpdate && forceParentUpdate();
|
||||
}
|
||||
|
||||
@ -131,6 +214,7 @@ export default function BulkLoadFileMappingField({bulkLoadField, isRequired, rem
|
||||
bulkLoadField.valueType = newValueType;
|
||||
setValueType(newValueType);
|
||||
bulkLoadField.error = null;
|
||||
bulkLoadField.warning = null;
|
||||
forceParentUpdate && forceParentUpdate();
|
||||
}
|
||||
|
||||
@ -144,7 +228,15 @@ export default function BulkLoadFileMappingField({bulkLoadField, isRequired, rem
|
||||
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={
|
||||
{
|
||||
"& .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">
|
||||
{
|
||||
(!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">
|
||||
{bulkLoadField.getQualifiedLabel()}
|
||||
@ -167,13 +261,13 @@ export default function BulkLoadFileMappingField({bulkLoadField, isRequired, rem
|
||||
valueType == "column" && <Box width="100%">
|
||||
<Autocomplete
|
||||
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
|
||||
options={columnOptions}
|
||||
multiple={false}
|
||||
defaultValue={selectedColumn}
|
||||
value={selectedColumn}
|
||||
inputValue={selectedColumn?.label}
|
||||
inputValue={selectedColumnInputValue}
|
||||
onChange={columnChanged}
|
||||
getOptionLabel={(option) => typeof (option) == "string" ? option : (option?.label ?? "")}
|
||||
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"}}>
|
||||
<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
|
||||
name={`${bulkLoadField.field.name}.defaultValue`}
|
||||
displayFormat={""}
|
||||
@ -200,9 +297,15 @@ export default function BulkLoadFileMappingField({bulkLoadField, isRequired, rem
|
||||
}
|
||||
</Box>
|
||||
</Box>
|
||||
{
|
||||
bulkLoadField.warning &&
|
||||
<Box fontSize={smallerFontSize} color={colors.warning.main} ml="145px" className="bulkLoadFieldError">
|
||||
{bulkLoadField.warning}
|
||||
</Box>
|
||||
}
|
||||
{
|
||||
bulkLoadField.error &&
|
||||
<Box fontSize={smallerFontSize} color={colors.error.main} ml="145px">
|
||||
<Box fontSize={smallerFontSize} color={colors.error.main} ml="145px" className="bulkLoadFieldError">
|
||||
{bulkLoadField.error}
|
||||
</Box>
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ export default function BulkLoadFileMappingFields({bulkLoadMapping, fileDescript
|
||||
{
|
||||
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 //
|
||||
@ -98,8 +98,9 @@ export default function BulkLoadFileMappingFields({bulkLoadMapping, fileDescript
|
||||
|
||||
setAddFieldsDisableStates(newDisableStates);
|
||||
setTooltips(newTooltips);
|
||||
setForceHierarchyAutoCompleteRerender(forceHierarchyAutoCompleteRerender + 1);
|
||||
|
||||
}, [bulkLoadMapping]);
|
||||
}, [bulkLoadMapping, bulkLoadMapping.layout]);
|
||||
|
||||
|
||||
///////////////////////////////////////////////
|
||||
@ -140,9 +141,6 @@ export default function BulkLoadFileMappingFields({bulkLoadMapping, fileDescript
|
||||
***************************************************************************/
|
||||
function removeField(bulkLoadField: BulkLoadField)
|
||||
{
|
||||
// addFieldsToggleStates[bulkLoadField.getQualifiedName()] = false;
|
||||
// setAddFieldsToggleStates(Object.assign({}, addFieldsToggleStates));
|
||||
|
||||
addFieldsDisableStates[bulkLoadField.getQualifiedName()] = false;
|
||||
setAddFieldsDisableStates(Object.assign({}, addFieldsDisableStates));
|
||||
|
||||
@ -160,7 +158,7 @@ export default function BulkLoadFileMappingFields({bulkLoadMapping, fileDescript
|
||||
bulkLoadMapping.removeField(bulkLoadField);
|
||||
forceUpdate();
|
||||
forceParentUpdate();
|
||||
setForceRerender(forceRerender + 1);
|
||||
setForceHierarchyAutoCompleteRerender(forceHierarchyAutoCompleteRerender + 1);
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
@ -297,7 +295,7 @@ export default function BulkLoadFileMappingFields({bulkLoadMapping, fileDescript
|
||||
isModeSelectOne
|
||||
keepOpenAfterSelectOne
|
||||
handleSelectedOption={handleAddField}
|
||||
forceRerender={forceRerender}
|
||||
forceRerender={forceHierarchyAutoCompleteRerender}
|
||||
disabledStates={addFieldsDisableStates}
|
||||
tooltips={tooltips}
|
||||
/>
|
||||
|
@ -20,45 +20,56 @@
|
||||
*/
|
||||
|
||||
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 {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData";
|
||||
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
|
||||
import {Badge, Icon} from "@mui/material";
|
||||
import Autocomplete from "@mui/material/Autocomplete";
|
||||
import Box from "@mui/material/Box";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import Tooltip from "@mui/material/Tooltip/Tooltip";
|
||||
import {useFormikContext} from "formik";
|
||||
import colors from "qqq/assets/theme/base/colors";
|
||||
import {DynamicFormFieldLabel} from "qqq/components/forms/DynamicForm";
|
||||
import QDynamicFormField from "qqq/components/forms/DynamicFormField";
|
||||
import MDTypography from "qqq/components/legacy/MDTypography";
|
||||
import HelpContent from "qqq/components/misc/HelpContent";
|
||||
import SavedBulkLoadProfiles from "qqq/components/misc/SavedBulkLoadProfiles";
|
||||
import BulkLoadFileMappingFields from "qqq/components/processes/BulkLoadFileMappingFields";
|
||||
import {BulkLoadField, BulkLoadMapping, BulkLoadProfile, BulkLoadTableStructure, FileDescription, Wrapper} from "qqq/models/processes/BulkLoadModels";
|
||||
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";
|
||||
|
||||
|
||||
interface BulkLoadMappingFormProps
|
||||
{
|
||||
processValues: any;
|
||||
tableMetaData: QTableMetaData;
|
||||
metaData: QInstance;
|
||||
setActiveStepLabel: (label: string) => void;
|
||||
processValues: any,
|
||||
tableMetaData: QTableMetaData,
|
||||
metaData: QInstance,
|
||||
setActiveStepLabel: (label: string) => void,
|
||||
frontendStep: QFrontendStepMetaData,
|
||||
processMetaData: QProcessMetaData,
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** 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 [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 [fieldErrors, setFieldErrors] = useState({} as { [fieldName: string]: string });
|
||||
const [noMappedFieldsError, setNoMappedFieldsError] = useState(null as string);
|
||||
|
||||
const [suggestedBulkLoadProfile] = useState(processValues.suggestedBulkLoadProfile as BulkLoadProfile);
|
||||
const [tableStructure] = useState(processValues.tableStructure as BulkLoadTableStructure);
|
||||
@ -119,18 +130,31 @@ const BulkLoadFileMappingForm = forwardRef(({processValues, tableMetaData, metaD
|
||||
}
|
||||
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};
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
console.log("@dk has header row changed!");
|
||||
}, [bulkLoadMapping.hasHeaderRow]);
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@ -214,6 +238,8 @@ const BulkLoadFileMappingForm = forwardRef(({processValues, tableMetaData, metaD
|
||||
tableStructure={tableStructure}
|
||||
fileName={processValues.fileBaseName}
|
||||
fieldErrors={fieldErrors}
|
||||
frontendStep={frontendStep}
|
||||
processMetaData={processMetaData}
|
||||
forceParentUpdate={() => forceUpdate()}
|
||||
/>
|
||||
|
||||
@ -221,8 +247,15 @@ const BulkLoadFileMappingForm = forwardRef(({processValues, tableMetaData, metaD
|
||||
<BulkLoadFileMappingFields
|
||||
bulkLoadMapping={bulkLoadMapping}
|
||||
fileDescription={fileDescription}
|
||||
forceParentUpdate={() => forceUpdate()}
|
||||
forceParentUpdate={() =>
|
||||
{
|
||||
setRerenderHeader(rerenderHeader + 1);
|
||||
forceUpdate();
|
||||
}}
|
||||
/>
|
||||
{
|
||||
noMappedFieldsError && <Box color={colors.error.main} textAlign="right">{noMappedFieldsError}</Box>
|
||||
}
|
||||
</Box>
|
||||
|
||||
</Box>);
|
||||
@ -232,8 +265,6 @@ const BulkLoadFileMappingForm = forwardRef(({processValues, tableMetaData, metaD
|
||||
export default BulkLoadFileMappingForm;
|
||||
|
||||
|
||||
|
||||
|
||||
interface BulkLoadMappingHeaderProps
|
||||
{
|
||||
fileDescription: FileDescription,
|
||||
@ -241,13 +272,15 @@ interface BulkLoadMappingHeaderProps
|
||||
bulkLoadMapping?: BulkLoadMapping,
|
||||
fieldErrors: { [fieldName: string]: string },
|
||||
tableStructure: BulkLoadTableStructure,
|
||||
forceParentUpdate?: () => void
|
||||
forceParentUpdate?: () => void,
|
||||
frontendStep: QFrontendStepMetaData,
|
||||
processMetaData: QProcessMetaData,
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
** 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 = [
|
||||
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};
|
||||
|
||||
let helpRoles = ["PROCESS_SCREEN", "ALL_SCREENS"];
|
||||
|
||||
const layoutOptions = [
|
||||
{label: "Flat", id: "FLAT"},
|
||||
{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;
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
function hasHeaderRowChanged(newValue: any)
|
||||
{
|
||||
bulkLoadMapping.hasHeaderRow = newValue;
|
||||
fileDescription.hasHeaderRow = newValue;
|
||||
|
||||
bulkLoadMapping.handleChangeToHasHeaderRow(newValue, fileDescription);
|
||||
|
||||
fieldErrors.hasHeaderRow = null;
|
||||
forceParentUpdate();
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
function layoutChanged(event: any, newValue: any)
|
||||
{
|
||||
bulkLoadMapping.layout = newValue ? newValue.id : null;
|
||||
bulkLoadMapping.switchLayout(newValue ? newValue.id : null);
|
||||
fieldErrors.layout = null;
|
||||
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 (
|
||||
<Box>
|
||||
<h5>File Details</h5>
|
||||
<Box ml="1rem">
|
||||
<ProcessViewForm fields={viewFields} values={viewValues} columns={2} />
|
||||
<BulkLoadMappingFilePreview fileDescription={fileDescription} />
|
||||
<BulkLoadMappingFilePreview fileDescription={fileDescription} bulkLoadMapping={bulkLoadMapping} />
|
||||
<Grid container pt="1rem">
|
||||
<Grid item xs={12} md={6}>
|
||||
<DynamicFormFieldLabel name={hasHeaderRowFormField.name} label={`${hasHeaderRowFormField.label} *`} />
|
||||
@ -307,6 +366,7 @@ function BulkLoadMappingHeader({fileDescription, fileName, bulkLoadMapping, fiel
|
||||
{<div className="fieldErrorMessage">{fieldErrors.hasHeaderRow}</div>}
|
||||
</MDTypography>
|
||||
}
|
||||
{getFormattedHelpContent("hasHeaderRow")}
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<DynamicFormFieldLabel name={"layout"} label={"File Layout *"} />
|
||||
@ -320,6 +380,7 @@ function BulkLoadMappingHeader({fileDescription, fileName, bulkLoadMapping, fiel
|
||||
getOptionLabel={(option) => typeof (option) == "string" ? option : (option?.label ?? "")}
|
||||
isOptionEqualToValue={(option, value) => option == null && value == null || option.id == value.id}
|
||||
renderOption={(props, option, state) => (<li {...props}>{option?.label ?? ""}</li>)}
|
||||
disableClearable
|
||||
sx={{"& .MuiOutlinedInput-root": {padding: "0"}}}
|
||||
/>
|
||||
{
|
||||
@ -328,6 +389,7 @@ function BulkLoadMappingHeader({fileDescription, fileName, bulkLoadMapping, fiel
|
||||
{<div className="fieldErrorMessage">{fieldErrors.layout}</div>}
|
||||
</MDTypography>
|
||||
}
|
||||
{getFormattedHelpContent("layout")}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
@ -336,16 +398,16 @@ function BulkLoadMappingHeader({fileDescription, fileName, bulkLoadMapping, fiel
|
||||
}
|
||||
|
||||
|
||||
|
||||
interface BulkLoadMappingFilePreviewProps
|
||||
{
|
||||
fileDescription: FileDescription;
|
||||
fileDescription: FileDescription,
|
||||
bulkLoadMapping?: BulkLoadMapping
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
** 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[] = [];
|
||||
for (let i = 0; i < fileDescription.bodyValuesPreview[0].length; i++)
|
||||
@ -353,25 +415,145 @@ function BulkLoadMappingFilePreview({fileDescription}: BulkLoadMappingFilePrevie
|
||||
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 (
|
||||
<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"}}>
|
||||
<table cellSpacing="0" width="100%">
|
||||
<thead>
|
||||
<tr style={{backgroundColor: "#d3d3d3"}}>
|
||||
<tr style={{backgroundColor: "#d3d3d3", height: "1.75rem"}}>
|
||||
<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>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<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>
|
||||
{rows.map((i) => (
|
||||
<tr key={i}>
|
||||
<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>
|
||||
))}
|
||||
</tbody>
|
||||
|
@ -266,76 +266,6 @@ function ValidationReview({
|
||||
</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 && (
|
||||
<Box border="1px solid rgb(70%, 70%, 70%)" borderRadius="10px" p={2} mt={2}>
|
||||
@ -370,11 +300,11 @@ function ValidationReview({
|
||||
{
|
||||
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>
|
||||
@ -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;
|
||||
|
@ -118,7 +118,7 @@ const doesOperatorOptionEqualCriteria = (operatorOption: OperatorOption, criteri
|
||||
** autocomplete), given an array of options, the query's active criteria in this
|
||||
** 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)
|
||||
{
|
||||
@ -135,6 +135,23 @@ const getOperatorSelectedValue = (operatorOptions: OperatorOption[], criteria: Q
|
||||
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);
|
||||
};
|
||||
|
||||
@ -157,7 +174,7 @@ export default function QuickFilter({tableMetaData, fullFieldName, fieldMetaData
|
||||
const [criteria, setCriteria] = useState(criteriaParamIsCriteria(criteriaParam) ? Object.assign({}, criteriaParam) as QFilterCriteriaWithId : null);
|
||||
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 {criteriaIsValid, criteriaStatusTooltip} = validateCriteria(criteria, operatorSelectedValue);
|
||||
|
@ -49,7 +49,7 @@ function ScriptDocsForm({helpText, exampleCode, aceEditorHeight}: Props): JSX.El
|
||||
<Card sx={{width: "100%", height: "100%"}}>
|
||||
<Typography variant="h6" p={2} pb={1}>{heading}</Typography>
|
||||
<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
|
||||
mode={mode}
|
||||
theme="github"
|
||||
@ -62,7 +62,7 @@ function ScriptDocsForm({helpText, exampleCode, aceEditorHeight}: Props): JSX.El
|
||||
width="100%"
|
||||
showPrintMargin={false}
|
||||
height="100%"
|
||||
style={{borderBottomRightRadius: "1rem", borderBottomLeftRadius: "1rem"}}
|
||||
style={{borderBottomRightRadius: "0.75rem", borderBottomLeftRadius: "0.75rem"}}
|
||||
/>
|
||||
</Typography>
|
||||
</Box>
|
||||
|
@ -313,6 +313,7 @@ function DashboardWidgets({widgetMetaDataList, tableName, entityPrimaryKey, reco
|
||||
function deleteChildRecord(name: string, widgetIndex: number, rowIndex: number)
|
||||
{
|
||||
updateChildRecordList(name, "delete", rowIndex);
|
||||
forceUpdate();
|
||||
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);
|
||||
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}
|
||||
widgetMetaData={widgetMetaData}
|
||||
data={widgetData[i]}
|
||||
parentRecord={record}
|
||||
/>
|
||||
)
|
||||
|
||||
|
@ -46,11 +46,12 @@ import React, {useContext, useEffect, useRef, useState} from "react";
|
||||
|
||||
interface FilterAndColumnsSetupWidgetProps
|
||||
{
|
||||
isEditable: boolean;
|
||||
widgetMetaData: QWidgetMetaData;
|
||||
widgetData: any;
|
||||
recordValues: { [name: string]: any };
|
||||
onSaveCallback?: (values: { [name: string]: any }) => void;
|
||||
isEditable: boolean,
|
||||
widgetMetaData: QWidgetMetaData,
|
||||
widgetData: any,
|
||||
recordValues: { [name: string]: any },
|
||||
onSaveCallback?: (values: { [name: string]: any }) => void,
|
||||
label?: string
|
||||
}
|
||||
|
||||
FilterAndColumnsSetupWidget.defaultProps = {
|
||||
@ -83,13 +84,16 @@ const qController = Client.getInstance();
|
||||
/*******************************************************************************
|
||||
** 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 [hideColumns, setHideColumns] = useState(widgetData?.hideColumns);
|
||||
const [hidePreview, setHidePreview] = useState(widgetData?.hidePreview);
|
||||
const [hideColumns] = useState(widgetData?.hideColumns);
|
||||
const [hidePreview] = useState(widgetData?.hidePreview);
|
||||
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);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -108,7 +112,7 @@ export default function FilterAndColumnsSetupWidget({isEditable, widgetMetaData,
|
||||
/////////////////////////////
|
||||
let columns: QQueryColumns = null;
|
||||
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;
|
||||
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);
|
||||
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();
|
||||
}
|
||||
@ -356,7 +363,7 @@ export default function FilterAndColumnsSetupWidget({isEditable, widgetMetaData,
|
||||
</Collapse>
|
||||
<Box pt="0.5rem">
|
||||
<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>
|
||||
{
|
||||
|
@ -40,7 +40,7 @@ import {Link, useNavigate} from "react-router-dom";
|
||||
export interface ChildRecordListData extends WidgetData
|
||||
{
|
||||
title?: string;
|
||||
queryOutput?: { records: { values: any }[] };
|
||||
queryOutput?: { records: { values: any, displayValues?: any } [] };
|
||||
childTableMetaData?: QTableMetaData;
|
||||
tablePath?: string;
|
||||
viewAllLink?: string;
|
||||
@ -48,20 +48,22 @@ export interface ChildRecordListData extends WidgetData
|
||||
canAddChildRecord?: boolean;
|
||||
defaultValuesForNewChildRecords?: { [fieldName: string]: any };
|
||||
disabledFieldsForNewChildRecords?: { [fieldName: string]: any };
|
||||
defaultValuesForNewChildRecordsFromParentFields?: { [fieldName: string]: string };
|
||||
}
|
||||
|
||||
interface Props
|
||||
{
|
||||
widgetMetaData: QWidgetMetaData;
|
||||
data: ChildRecordListData;
|
||||
addNewRecordCallback?: () => void;
|
||||
disableRowClick: boolean;
|
||||
allowRecordEdit: boolean;
|
||||
editRecordCallback?: (rowIndex: number) => void;
|
||||
allowRecordDelete: boolean;
|
||||
deleteRecordCallback?: (rowIndex: number) => void;
|
||||
gridOnly?: boolean;
|
||||
gridDensity?: GridDensity;
|
||||
widgetMetaData: QWidgetMetaData,
|
||||
data: ChildRecordListData,
|
||||
addNewRecordCallback?: () => void,
|
||||
disableRowClick: boolean,
|
||||
allowRecordEdit: boolean,
|
||||
editRecordCallback?: (rowIndex: number) => void,
|
||||
allowRecordDelete: boolean,
|
||||
deleteRecordCallback?: (rowIndex: number) => void,
|
||||
gridOnly?: boolean,
|
||||
gridDensity?: GridDensity,
|
||||
parentRecord?: QRecord
|
||||
}
|
||||
|
||||
RecordGridWidget.defaultProps =
|
||||
@ -74,7 +76,7 @@ RecordGridWidget.defaultProps =
|
||||
|
||||
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 [rows, setRows] = useState([]);
|
||||
@ -97,7 +99,7 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
|
||||
{
|
||||
for (let i = 0; i < queryOutputRecords.length; i++)
|
||||
{
|
||||
if(queryOutputRecords[i] instanceof QRecord)
|
||||
if (queryOutputRecords[i] instanceof QRecord)
|
||||
{
|
||||
records.push(queryOutputRecords[i] as QRecord);
|
||||
}
|
||||
@ -252,7 +254,22 @@ function RecordGridWidget({widgetMetaData, data, addNewRecordCallback, disableRo
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
@ -393,7 +393,7 @@ export default function ScriptViewer({scriptId, associatedScriptTableName, assoc
|
||||
}
|
||||
|
||||
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}>
|
||||
<Box>
|
||||
{
|
||||
@ -530,7 +530,7 @@ export default function ScriptViewer({scriptId, associatedScriptTableName, assoc
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel index={2} value={selectedTab}>
|
||||
<Box sx={{height: "455px"}} px={2} pb={1}>
|
||||
<Box sx={{height: "455px"}} px={2} pt={1}>
|
||||
<ScriptTestForm scriptId={scriptId}
|
||||
scriptType={scriptTypeRecord}
|
||||
tableName={associatedScriptTableName}
|
||||
@ -543,7 +543,7 @@ export default function ScriptViewer({scriptId, associatedScriptTableName, assoc
|
||||
</TabPanel>
|
||||
|
||||
<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")} />
|
||||
</Box>
|
||||
</TabPanel>
|
||||
|
@ -35,7 +35,7 @@ export interface ModalEditFormData
|
||||
defaultValues?: { [key: string]: string };
|
||||
disabledFields?: { [key: string]: boolean } | string[];
|
||||
overrideHeading?: string;
|
||||
onSubmitCallback?: (values: any) => void;
|
||||
onSubmitCallback?: (values: any, tableName: String) => void;
|
||||
initialShowModalValue?: boolean;
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@
|
||||
|
||||
|
||||
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";
|
||||
|
||||
export type ValueType = "defaultValue" | "column";
|
||||
@ -42,6 +43,7 @@ export class BulkLoadField
|
||||
wideLayoutIndexPath: number[] = [];
|
||||
|
||||
error: string = null;
|
||||
warning: string = null;
|
||||
|
||||
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.tableStructure = tableStructure;
|
||||
@ -59,6 +61,8 @@ export class BulkLoadField
|
||||
this.defaultValue = defaultValue;
|
||||
this.doValueMapping = doValueMapping;
|
||||
this.wideLayoutIndexPath = wideLayoutIndexPath;
|
||||
this.error = error;
|
||||
this.warning = warning;
|
||||
this.key = new Date().getTime().toString();
|
||||
}
|
||||
|
||||
@ -68,7 +72,7 @@ export class 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
|
||||
{
|
||||
index = 0;
|
||||
///////////////////////////////////////////////////////////
|
||||
// count how many copies of this field there are already //
|
||||
///////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////
|
||||
// find the max index for this field already //
|
||||
///////////////////////////////////////////////
|
||||
let maxIndex = -1;
|
||||
for (let existingField of [...this.requiredFields, ...this.additionalFields])
|
||||
{
|
||||
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);
|
||||
@ -455,7 +464,7 @@ export class BulkLoadMapping
|
||||
const newAdditionalFields: BulkLoadField[] = [];
|
||||
for (let bulkLoadField of this.additionalFields)
|
||||
{
|
||||
if (bulkLoadField.getQualifiedName() != toRemove.getQualifiedName())
|
||||
if (bulkLoadField.getQualifiedNameWithWideSuffix() != toRemove.getQualifiedNameWithWideSuffix())
|
||||
{
|
||||
newAdditionalFields.push(bulkLoadField);
|
||||
}
|
||||
@ -463,6 +472,171 @@ export class BulkLoadMapping
|
||||
|
||||
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[];
|
||||
bodyValuesPreview: string[][];
|
||||
|
||||
duplicateHeaderIndexes: boolean[];
|
||||
|
||||
// todo - just get this from the profile always - it's not part of the file per-se
|
||||
hasHeaderRow: boolean = true;
|
||||
|
||||
@ -486,6 +662,18 @@ export class FileDescription
|
||||
this.headerValues = headerValues;
|
||||
this.headerLetters = headerLetters;
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1032,9 +1032,11 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
||||
<BulkLoadFileMappingForm
|
||||
processValues={processValues}
|
||||
tableMetaData={tableMetaData}
|
||||
processMetaData={processMetaData}
|
||||
metaData={qInstance}
|
||||
ref={bulkLoadFileMappingFormRef}
|
||||
setActiveStepLabel={setActiveStepLabel}
|
||||
frontendStep={activeStep}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@ -2220,7 +2222,7 @@ function ProcessRun({process, table, defaultProcessValues, isModal, isWidget, is
|
||||
if (isModal)
|
||||
{
|
||||
return (
|
||||
<Box sx={{position: "absolute", overflowY: "auto", maxHeight: "100%", width: "100%"}}>
|
||||
<Box sx={{position: "absolute", overflowY: "auto", maxHeight: "100%", width: "100%"}} id="modalProcessScrollContainer">
|
||||
{body}
|
||||
</Box>
|
||||
);
|
||||
|
@ -191,7 +191,7 @@ function RecordDeveloperView({table}: Props): JSX.Element
|
||||
<Card sx={{mb: 3}}>
|
||||
<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 ?
|
||||
<ScriptViewer
|
||||
scriptId={scriptId}
|
||||
|
@ -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) =>
|
||||
{
|
||||
@ -103,6 +103,7 @@ export function renderSectionOfFields(key: string, fieldNames: string[], tableMe
|
||||
if (field != null)
|
||||
{
|
||||
let label = field.label;
|
||||
let gridColumns = (field.gridColumns && field.gridColumns > 0) ? field.gridColumns : 12;
|
||||
|
||||
const helpRoles = ["VIEW_SCREEN", "READ_SCREENS", "ALL_SCREENS"];
|
||||
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>;
|
||||
|
||||
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
|
||||
@ -121,12 +122,12 @@ export function renderSectionOfFields(key: string, fieldNames: string[], tableMe
|
||||
{ValueUtils.getDisplayValue(field, record, "view", fieldName)}
|
||||
</Typography>
|
||||
</>
|
||||
</Box>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
</Box>;
|
||||
</Grid>;
|
||||
}
|
||||
|
||||
|
||||
|
@ -37,7 +37,7 @@ export class SavedBulkLoadProfileUtils
|
||||
|
||||
for (let bulkLoadField of orderedFieldArray)
|
||||
{
|
||||
const fieldName = bulkLoadField.field.name;
|
||||
const fieldName = bulkLoadField.getQualifiedName()
|
||||
const compareField = compareFieldsMap[fieldName];
|
||||
const baseField = baseFieldsMap[fieldName];
|
||||
if(!compareField)
|
||||
@ -55,12 +55,13 @@ export class SavedBulkLoadProfileUtils
|
||||
if (compareField.valueType == "column")
|
||||
{
|
||||
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")
|
||||
{
|
||||
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")
|
||||
@ -70,7 +71,8 @@ export class SavedBulkLoadProfileUtils
|
||||
//////////////////////////////////////////////////
|
||||
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")
|
||||
@ -78,25 +80,29 @@ export class SavedBulkLoadProfileUtils
|
||||
///////////////////////////////////////////
|
||||
// if we changed the column, report that //
|
||||
///////////////////////////////////////////
|
||||
let isDiff = false;
|
||||
if (fileDescription.hasHeaderRow)
|
||||
{
|
||||
if (baseField.headerName != compareField.headerName)
|
||||
{
|
||||
const baseColumn = fileDescription.getColumnNames()[baseField.columnIndex];
|
||||
const compareColumn = fileDescription.getColumnNames()[compareField.columnIndex];
|
||||
rs.push(`Changed ${compareField.getQualifiedLabel()} file column from (${baseColumn}) to (${compareColumn})`);
|
||||
isDiff = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (baseField.columnIndex != compareField.columnIndex)
|
||||
{
|
||||
const baseColumn = fileDescription.getColumnNames()[baseField.columnIndex];
|
||||
const compareColumn = fileDescription.getColumnNames()[compareField.columnIndex];
|
||||
rs.push(`Changed ${compareField.getQualifiedLabel()} file column from (${baseColumn}) to (${compareColumn})`);
|
||||
isDiff = true;
|
||||
}
|
||||
}
|
||||
|
||||
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) //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -120,7 +126,7 @@ export class SavedBulkLoadProfileUtils
|
||||
|
||||
for (let bulkLoadField of orderedFieldArray)
|
||||
{
|
||||
const fieldName = bulkLoadField.field.name;
|
||||
const fieldName = bulkLoadField.getQualifiedName()
|
||||
const compareField = compareFieldsMap[fieldName];
|
||||
if(!compareField)
|
||||
{
|
||||
@ -292,7 +298,7 @@ export class SavedBulkLoadProfileUtils
|
||||
{
|
||||
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] ?? {});
|
||||
if(valueMappingDiff)
|
||||
|
Reference in New Issue
Block a user