From 54a87ca3dc79fe090731919bd8ba4f1707d2a7be Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 24 Feb 2025 22:31:57 -0500 Subject: [PATCH] Add required fields display to match columns step --- .../MatchColumnsStep/MatchColumnsStep.tsx | 449 +++++++++++------- .../utils/findUnmatchedRequiredFields.ts | 15 +- .../SelectHeaderStep/SelectHeaderStep.tsx | 27 +- 3 files changed, 294 insertions(+), 197 deletions(-) diff --git a/inventory/src/lib/react-spreadsheet-import/src/steps/MatchColumnsStep/MatchColumnsStep.tsx b/inventory/src/lib/react-spreadsheet-import/src/steps/MatchColumnsStep/MatchColumnsStep.tsx index 7fd86bb..7108c93 100644 --- a/inventory/src/lib/react-spreadsheet-import/src/steps/MatchColumnsStep/MatchColumnsStep.tsx +++ b/inventory/src/lib/react-spreadsheet-import/src/steps/MatchColumnsStep/MatchColumnsStep.tsx @@ -35,6 +35,13 @@ import { import { useQuery } from "@tanstack/react-query" import config from "@/config" import { Button } from "@/components/ui/button" +import { CheckCircle2, AlertCircle, HelpCircle } from "lucide-react" +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip" export type MatchColumnsProps = { data: RawData[] @@ -120,13 +127,12 @@ export const MatchColumnsStep = ({ initialGlobalSelections }: MatchColumnsProps) => { const dataExample = data.slice(0, 2) - const { fields, autoMapHeaders, autoMapSelectValues, autoMapDistance, translations, allowInvalidSubmit } = useRsi() + const { fields, autoMapHeaders, autoMapSelectValues, autoMapDistance, translations } = useRsi() const [isLoading, setIsLoading] = useState(false) const [columns, setColumns] = useState>( // Do not remove spread, it indexes empty array elements, otherwise map() skips over them ([...headerValues] as string[]).map((value, index) => ({ type: ColumnType.empty, index, header: value ?? "" })), ) - const [showUnmatchedFieldsAlert, setShowUnmatchedFieldsAlert] = useState(false) const [globalSelections, setGlobalSelections] = useState(initialGlobalSelections || {}) // Initialize with any provided global selections @@ -237,6 +243,46 @@ export const MatchColumnsStep = ({ }, [columns, setColumns], ) + + // Get all required fields + const requiredFields = useMemo(() => { + // Convert the fields to the expected type + const fieldsArray = Array.isArray(fields) ? fields : [fields]; + + // Log the fields for debugging + console.log("All fields:", fieldsArray); + + // Log validation rules for each field + fieldsArray.forEach(field => { + if (field.validations && Array.isArray(field.validations)) { + console.log(`Field ${field.key} validations:`, field.validations.map((v: any) => v.rule)); + } else { + console.log(`Field ${field.key} has no validations`); + } + }); + + // Check for required fields based on validations + const required = fieldsArray.filter(field => { + // Check if the field has validations + if (!field.validations || !Array.isArray(field.validations)) { + return false; + } + + // Check for any validation with rule: 'required' or required: true + const isRequired = field.validations.some( + (v: any) => v.rule === 'required' || v.required === true + ); + + console.log(`Field ${field.key} required:`, isRequired, field.validations); + + return isRequired; + }); + + console.log("Required fields:", required); + return required; + }, [fields]); + + // Get unmatched required fields const unmatchedRequiredFields = useMemo(() => { // Convert the fields to the expected type const fieldsArray = Array.isArray(fields) ? fields : [fields] @@ -245,25 +291,45 @@ export const MatchColumnsStep = ({ key: field.key as TsDeepReadonly })) as unknown as Fields - return findUnmatchedRequiredFields(typedFields, columns) + const unmatched = findUnmatchedRequiredFields(typedFields, columns); + console.log("Unmatched required fields:", unmatched); + return unmatched; }, [fields, columns]) - const handleOnContinue = useCallback(async () => { - if (unmatchedRequiredFields.length > 0) { - setShowUnmatchedFieldsAlert(true) - } else { - setIsLoading(true) - // Normalize the data with global selections before continuing - const normalizedData = normalizeTableData(columns, data, fields) - await onContinue(normalizedData, data, columns, globalSelections) - setIsLoading(false) - } - }, [unmatchedRequiredFields.length, onContinue, columns, data, fields, globalSelections]) + // Get matched required fields + const matchedRequiredFields = useMemo(() => { + const matched = requiredFields + .map(field => field.key) + .filter(key => { + // Type assertion to handle the DeepReadonly vs string type mismatch + return !unmatchedRequiredFields.includes(key as any); + }); + console.log("Matched required fields:", matched); + return matched; + }, [requiredFields, unmatchedRequiredFields]); - const handleAlertOnContinue = useCallback(async () => { - setShowUnmatchedFieldsAlert(false) + // Get field label by key + const getFieldLabel = useCallback((key: string) => { + const fieldsArray = Array.isArray(fields) ? fields : [fields]; + const field = fieldsArray.find(f => f.key === key); + return field?.label || key; + }, [fields]); + + // Check if a field is covered by global selections + const isFieldCoveredByGlobalSelections = useCallback((key: string) => { + const isCovered = (key === 'supplier' && globalSelections.supplier) || + (key === 'company' && globalSelections.company) || + (key === 'line' && globalSelections.line) || + (key === 'subline' && globalSelections.subline); + console.log(`Field ${key} covered by global selections:`, isCovered); + return isCovered; + }, [globalSelections]); + + const handleOnContinue = useCallback(async () => { setIsLoading(true) - await onContinue(normalizeTableData(columns, data, fields), data, columns, globalSelections) + // Normalize the data with global selections before continuing + const normalizedData = normalizeTableData(columns, data, fields) + await onContinue(normalizedData, data, columns, globalSelections) setIsLoading(false) }, [onContinue, columns, data, fields, globalSelections]) @@ -278,173 +344,198 @@ export const MatchColumnsStep = ({ ) return ( - <> - - - - - - - {translations.alerts.unmatchedRequiredFields.headerTitle} - -
- - {translations.alerts.unmatchedRequiredFields.bodyText} - -

- {translations.alerts.unmatchedRequiredFields.listTitle}{" "} - - {unmatchedRequiredFields.join(", ")} - -

-
-
- - - {translations.alerts.unmatchedRequiredFields.cancelButtonTitle} - - {allowInvalidSubmit && ( - - {translations.alerts.unmatchedRequiredFields.continueButtonTitle} - - )} - -
-
-
-
-
-
-
-
- {/* Global Selections Section */} - - - Global Selections - - -
-
- - -
- -
- - -
- -
- - -
- -
- - -
+
+
+
+
+
+ {/* Global Selections Section */} + + + Global Selections + + +
+
+ +
- - -
- ( - row[column.index])} - /> - )} - templateColumn={(column) => } - /> -
+
+ + +
+ +
+ + +
+ +
+ + +
+
+
+
+ +
+ ( + row[column.index])} + /> + )} + templateColumn={(column) => } + />
+ + {/* Required Fields Checklist */} + + + + Required Fields + + + + + + +

+ These fields are required for product import. You can either map them from your spreadsheet or set them globally above. + {unmatchedRequiredFields.length > 0 && " Missing fields will need to be filled in during validation."} +

+
+
+
+
+
+ +
+ {requiredFields.length > 0 ? ( + requiredFields.map(field => { + const isMatched = matchedRequiredFields.includes(field.key); + const isCoveredByGlobal = isFieldCoveredByGlobalSelections(field.key); + const isAccountedFor = isMatched || isCoveredByGlobal; + + return ( +
+ {isAccountedFor ? ( + + ) : ( + + )} + + {getFieldLabel(field.key)} + + {isCoveredByGlobal && ( + (set globally) + )} +
+ ); + }) + ) : ( +
+ No required fields found in the configuration. +
+ )} +
+
+
-
-
- {onBack && ( - +
+
+
+ {onBack && ( + + )} +
+ {unmatchedRequiredFields.length > 0 && ( + + {unmatchedRequiredFields.length} required field(s) missing + )}
- +
) } diff --git a/inventory/src/lib/react-spreadsheet-import/src/steps/MatchColumnsStep/utils/findUnmatchedRequiredFields.ts b/inventory/src/lib/react-spreadsheet-import/src/steps/MatchColumnsStep/utils/findUnmatchedRequiredFields.ts index f98fa45..4c74bf1 100644 --- a/inventory/src/lib/react-spreadsheet-import/src/steps/MatchColumnsStep/utils/findUnmatchedRequiredFields.ts +++ b/inventory/src/lib/react-spreadsheet-import/src/steps/MatchColumnsStep/utils/findUnmatchedRequiredFields.ts @@ -1,8 +1,15 @@ import type { Fields } from "../../../types" import type { Columns } from "../MatchColumnsStep" -export const findUnmatchedRequiredFields = (fields: Fields, columns: Columns) => - fields - .filter((field) => field.validations?.some((validation) => validation.rule === "required")) +export const findUnmatchedRequiredFields = (fields: Fields, columns: Columns) => { + // Get all required fields + const requiredFields = fields + .filter((field) => field.validations?.some((validation: any) => + validation.rule === "required" || validation.required === true + )) + + // Find which required fields are not matched in columns + return requiredFields .filter((field) => columns.findIndex((column) => "value" in column && column.value === field.key) === -1) - .map((field) => field.label) || [] + .map((field) => field.key) || [] +} diff --git a/inventory/src/lib/react-spreadsheet-import/src/steps/SelectHeaderStep/SelectHeaderStep.tsx b/inventory/src/lib/react-spreadsheet-import/src/steps/SelectHeaderStep/SelectHeaderStep.tsx index ef9a098..dd13f8a 100644 --- a/inventory/src/lib/react-spreadsheet-import/src/steps/SelectHeaderStep/SelectHeaderStep.tsx +++ b/inventory/src/lib/react-spreadsheet-import/src/steps/SelectHeaderStep/SelectHeaderStep.tsx @@ -108,10 +108,7 @@ export const SelectHeaderStep = ({ data, onContinue, onBack }: SelectHeaderProps removedRows.push({ index, reason: "Duplicate", - row, - normalizedRow, - rowStr, - headerStr: selectedHeaderStr + row }); return false; } @@ -151,27 +148,29 @@ export const SelectHeaderStep = ({ data, onContinue, onBack }: SelectHeaderProps }, [localData, selectedRows, toast]); return ( -
-
+
+

{translations.selectHeaderStep.title}

-
-
+
+
- +
+ +
{onBack && (