diff --git a/src/qqq/components/processes/BulkLoadFileMappingField.tsx b/src/qqq/components/processes/BulkLoadFileMappingField.tsx index 1e6d82b..b5457b3 100644 --- a/src/qqq/components/processes/BulkLoadFileMappingField.tsx +++ b/src/qqq/components/processes/BulkLoadFileMappingField.tsx @@ -131,11 +131,18 @@ export default function BulkLoadFileMappingField({bulkLoadField, isRequired, rem ////////////////////////////////////////////////////// // 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; + } } ////////////////////////////////////////////////////////////////////// @@ -180,6 +187,7 @@ export default function BulkLoadFileMappingField({bulkLoadField, isRequired, rem } bulkLoadField.error = null; + bulkLoadField.warning = null; forceParentUpdate && forceParentUpdate(); } @@ -192,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(); } @@ -205,6 +214,7 @@ export default function BulkLoadFileMappingField({bulkLoadField, isRequired, rem bulkLoadField.valueType = newValueType; setValueType(newValueType); bulkLoadField.error = null; + bulkLoadField.warning = null; forceParentUpdate && forceParentUpdate(); } @@ -287,6 +297,12 @@ export default function BulkLoadFileMappingField({bulkLoadField, isRequired, rem } + { + bulkLoadField.warning && + + {bulkLoadField.warning} + + } { bulkLoadField.error && diff --git a/src/qqq/components/processes/BulkLoadFileMappingForm.tsx b/src/qqq/components/processes/BulkLoadFileMappingForm.tsx index bacf001..9ea436e 100644 --- a/src/qqq/components/processes/BulkLoadFileMappingForm.tsx +++ b/src/qqq/components/processes/BulkLoadFileMappingForm.tsx @@ -298,6 +298,9 @@ function BulkLoadMappingHeader({fileDescription, fileName, bulkLoadMapping, fiel { bulkLoadMapping.hasHeaderRow = newValue; fileDescription.hasHeaderRow = newValue; + + bulkLoadMapping.handleChangeToHasHeaderRow(newValue, fileDescription); + fieldErrors.hasHeaderRow = null; forceParentUpdate(); } @@ -470,19 +473,29 @@ function BulkLoadMappingFilePreview({fileDescription, bulkLoadMapping}: BulkLoad { const fields = bulkLoadMapping.getFieldsForColumnIndex(index); const count = fields.length; + + let dupeWarning = <> + if(fileDescription.hasHeaderRow && fileDescription.duplicateHeaderIndexes[index]) + { + dupeWarning = + warning + + } + return ( <> { count > 0 && + {dupeWarning} {letter} } { - count == 0 && {letter} + count == 0 && {dupeWarning}{letter} } ); diff --git a/src/qqq/models/processes/BulkLoadModels.ts b/src/qqq/models/processes/BulkLoadModels.ts index 9acbc13..955e355 100644 --- a/src/qqq/models/processes/BulkLoadModels.ts +++ b/src/qqq/models/processes/BulkLoadModels.ts @@ -43,6 +43,7 @@ export class BulkLoadField wideLayoutIndexPath: number[] = []; error: string = null; + warning: string = null; key: string; @@ -50,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; @@ -60,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(); } @@ -69,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)); } @@ -431,7 +434,7 @@ export class BulkLoadMapping { if (existingField.getQualifiedName() == bulkLoadField.getQualifiedName()) { - const thisIndex = existingField.wideLayoutIndexPath[0] + const thisIndex = existingField.wideLayoutIndexPath[0]; if (thisIndex != null && thisIndex != undefined && thisIndex > maxIndex) { maxIndex = thisIndex; @@ -506,7 +509,7 @@ export class BulkLoadMapping namesWhereOneWideLayoutIndexHasBeenFound[name] = true; const newField = BulkLoadField.clone(existingField); newField.wideLayoutIndexPath = []; - newAdditionalFields.push(newField) + newAdditionalFields.push(newField); anyChanges = true; } } @@ -515,7 +518,7 @@ export class BulkLoadMapping ////////////////////////////////////////////////////// // else, non-wide-path fields, just get added as-is // ////////////////////////////////////////////////////// - newAdditionalFields.push(existingField) + newAdditionalFields.push(existingField); } } } @@ -531,7 +534,7 @@ export class BulkLoadMapping //////////////////////////////////////////// // fields from main table come over as-is // //////////////////////////////////////////// - newAdditionalFields.push(existingField) + newAdditionalFields.push(existingField); } else { @@ -540,7 +543,7 @@ export class BulkLoadMapping ///////////////////////////////////////////////////////////////////////////////////////////// const newField = BulkLoadField.clone(existingField); newField.wideLayoutIndexPath = [0]; - newAdditionalFields.push(newField) + newAdditionalFields.push(newField); anyChanges = true; } } @@ -564,7 +567,7 @@ export class BulkLoadMapping for (let field of [...this.requiredFields, ...this.additionalFields]) { - if(field.valueType == "column" && field.columnIndex == i) + if (field.valueType == "column" && field.columnIndex == i) { rs.push(field); } @@ -572,6 +575,68 @@ export class BulkLoadMapping 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; + } + } } @@ -584,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; @@ -595,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; + } } @@ -635,7 +714,7 @@ export class FileDescription function getTypedValue(value: any): string { - if(value == null) + if (value == null) { return ""; } @@ -694,13 +773,13 @@ export class FileDescription if (!this.hasHeaderRow) { - const typedValue = getTypedValue(this.headerValues[columnIndex]) + const typedValue = getTypedValue(this.headerValues[columnIndex]); valueArray.push(typedValue == null ? "" : `${typedValue}`); } for (let value of this.bodyValuesPreview[columnIndex]) { - const typedValue = getTypedValue(value) + const typedValue = getTypedValue(value); valueArray.push(typedValue == null ? "" : `${typedValue}`); }