10 Commits

64 changed files with 5093 additions and 2664 deletions

View File

@@ -0,0 +1,43 @@
# Details
Date : 2025-03-17 14:20:03
Directory /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew
Total : 28 files, 6565 codes, 1027 comments, 1053 blanks, all 8645 lines
[Summary](results.md) / Details / [Diff Summary](diff.md) / [Diff Details](diff-details.md)
## Files
| filename | language | code | comment | blank | total |
| :--- | :--- | ---: | ---: | ---: | ---: |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/README.md](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/README.md) | Markdown | 39 | 0 | 19 | 58 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/AiValidationDialogs.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/AiValidationDialogs.tsx) | TypeScript JSX | 230 | 10 | 8 | 248 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/BaseCellContent.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/BaseCellContent.tsx) | TypeScript JSX | 18 | 0 | 3 | 21 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/SaveTemplateDialog.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/SaveTemplateDialog.tsx) | TypeScript JSX | 83 | 0 | 4 | 87 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/SearchableTemplateSelect.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/SearchableTemplateSelect.tsx) | TypeScript JSX | 273 | 19 | 37 | 329 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/TemplateManager.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/TemplateManager.tsx) | TypeScript JSX | 193 | 4 | 15 | 212 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationCell.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationCell.tsx) | TypeScript JSX | 374 | 42 | 44 | 460 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationContainer.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationContainer.tsx) | TypeScript JSX | 1,101 | 234 | 213 | 1,548 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationTable.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationTable.tsx) | TypeScript JSX | 499 | 48 | 54 | 601 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/CheckboxCell.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/CheckboxCell.tsx) | TypeScript JSX | 112 | 12 | 21 | 145 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/InputCell.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/InputCell.tsx) | TypeScript JSX | 232 | 31 | 32 | 295 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultiSelectCell.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultiSelectCell.tsx) | TypeScript JSX | 407 | 56 | 52 | 515 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultilineInput.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultilineInput.tsx) | TypeScript JSX | 193 | 23 | 22 | 238 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/SelectCell.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/SelectCell.tsx) | TypeScript JSX | 289 | 36 | 31 | 356 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useAiValidation.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useAiValidation.tsx) | TypeScript JSX | 500 | 75 | 89 | 664 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useFilters.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useFilters.tsx) | TypeScript JSX | 89 | 12 | 16 | 117 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useTemplates.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useTemplates.tsx) | TypeScript JSX | 204 | 26 | 33 | 263 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useUpcValidation.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useUpcValidation.tsx) | TypeScript JSX | 93 | 13 | 18 | 124 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidation.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidation.tsx) | TypeScript JSX | 219 | 39 | 47 | 305 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidationState.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidationState.tsx) | TypeScript JSX | 1,060 | 228 | 229 | 1,517 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/index.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/index.tsx) | TypeScript JSX | 20 | 6 | 2 | 28 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/types.ts](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/types.ts) | TypeScript | 4 | 0 | 1 | 5 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/types/index.ts](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/types/index.ts) | TypeScript | 16 | 4 | 4 | 24 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/dataMutations.ts](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/dataMutations.ts) | TypeScript | 124 | 4 | 14 | 142 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/errorUtils.ts](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/errorUtils.ts) | TypeScript | 21 | 15 | 5 | 41 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/upcValidation.ts](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/upcValidation.ts) | TypeScript | 43 | 24 | 7 | 74 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/validation-helper.js](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/validation-helper.js) | JavaScript | 28 | 7 | 9 | 44 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/validationUtils.ts](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/validationUtils.ts) | TypeScript | 101 | 59 | 24 | 184 |
[Summary](results.md) / Details / [Diff Summary](diff.md) / [Diff Details](diff-details.md)

View File

@@ -0,0 +1,15 @@
# Diff Details
Date : 2025-03-17 14:20:03
Directory /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew
Total : 0 files, 0 codes, 0 comments, 0 blanks, all 0 lines
[Summary](results.md) / [Details](details.md) / [Diff Summary](diff.md) / Diff Details
## Files
| filename | language | code | comment | blank | total |
| :--- | :--- | ---: | ---: | ---: | ---: |
[Summary](results.md) / [Details](details.md) / [Diff Summary](diff.md) / Diff Details

View File

@@ -0,0 +1,19 @@
# Diff Summary
Date : 2025-03-17 14:20:03
Directory /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew
Total : 0 files, 0 codes, 0 comments, 0 blanks, all 0 lines
[Summary](results.md) / [Details](details.md) / Diff Summary / [Diff Details](diff-details.md)
## Languages
| language | files | code | comment | blank | total |
| :--- | ---: | ---: | ---: | ---: | ---: |
## Directories
| path | files | code | comment | blank | total |
| :--- | ---: | ---: | ---: | ---: | ---: |
[Summary](results.md) / [Details](details.md) / Diff Summary / [Diff Details](diff-details.md)

View File

@@ -0,0 +1,22 @@
Date : 2025-03-17 14:20:03
Directory : /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew
Total : 0 files, 0 codes, 0 comments, 0 blanks, all 0 lines
Languages
+----------+------------+------------+------------+------------+------------+
| language | files | code | comment | blank | total |
+----------+------------+------------+------------+------------+------------+
+----------+------------+------------+------------+------------+------------+
Directories
+------+------------+------------+------------+------------+------------+
| path | files | code | comment | blank | total |
+------+------------+------------+------------+------------+------------+
+------+------------+------------+------------+------------+------------+
Files
+----------+----------+------------+------------+------------+------------+
| filename | language | code | comment | blank | total |
+----------+----------+------------+------------+------------+------------+
| Total | | 0 | 0 | 0 | 0 |
+----------+----------+------------+------------+------------+------------+

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,31 @@
# Summary
Date : 2025-03-17 14:20:03
Directory /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew
Total : 28 files, 6565 codes, 1027 comments, 1053 blanks, all 8645 lines
Summary / [Details](details.md) / [Diff Summary](diff.md) / [Diff Details](diff-details.md)
## Languages
| language | files | code | comment | blank | total |
| :--- | ---: | ---: | ---: | ---: | ---: |
| TypeScript JSX | 20 | 6,189 | 914 | 970 | 8,073 |
| TypeScript | 6 | 309 | 106 | 55 | 470 |
| Markdown | 1 | 39 | 0 | 19 | 58 |
| JavaScript | 1 | 28 | 7 | 9 | 44 |
## Directories
| path | files | code | comment | blank | total |
| :--- | ---: | ---: | ---: | ---: | ---: |
| . | 28 | 6,565 | 1,027 | 1,053 | 8,645 |
| . (Files) | 3 | 63 | 6 | 22 | 91 |
| components | 13 | 4,004 | 515 | 536 | 5,055 |
| components (Files) | 8 | 2,771 | 357 | 378 | 3,506 |
| components/cells | 5 | 1,233 | 158 | 158 | 1,549 |
| hooks | 6 | 2,165 | 393 | 432 | 2,990 |
| types | 1 | 16 | 4 | 4 | 24 |
| utils | 5 | 317 | 109 | 59 | 485 |
Summary / [Details](details.md) / [Diff Summary](diff.md) / [Diff Details](diff-details.md)

View File

@@ -0,0 +1,62 @@
Date : 2025-03-17 14:20:03
Directory : /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew
Total : 28 files, 6565 codes, 1027 comments, 1053 blanks, all 8645 lines
Languages
+----------------+------------+------------+------------+------------+------------+
| language | files | code | comment | blank | total |
+----------------+------------+------------+------------+------------+------------+
| TypeScript JSX | 20 | 6,189 | 914 | 970 | 8,073 |
| TypeScript | 6 | 309 | 106 | 55 | 470 |
| Markdown | 1 | 39 | 0 | 19 | 58 |
| JavaScript | 1 | 28 | 7 | 9 | 44 |
+----------------+------------+------------+------------+------------+------------+
Directories
+------------------------------------------------------------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+
| path | files | code | comment | blank | total |
+------------------------------------------------------------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+
| . | 28 | 6,565 | 1,027 | 1,053 | 8,645 |
| . (Files) | 3 | 63 | 6 | 22 | 91 |
| components | 13 | 4,004 | 515 | 536 | 5,055 |
| components (Files) | 8 | 2,771 | 357 | 378 | 3,506 |
| components/cells | 5 | 1,233 | 158 | 158 | 1,549 |
| hooks | 6 | 2,165 | 393 | 432 | 2,990 |
| types | 1 | 16 | 4 | 4 | 24 |
| utils | 5 | 317 | 109 | 59 | 485 |
+------------------------------------------------------------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+
Files
+------------------------------------------------------------------------------------------------------------------------------------------+----------------+------------+------------+------------+------------+
| filename | language | code | comment | blank | total |
+------------------------------------------------------------------------------------------------------------------------------------------+----------------+------------+------------+------------+------------+
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/README.md | Markdown | 39 | 0 | 19 | 58 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/AiValidationDialogs.tsx | TypeScript JSX | 230 | 10 | 8 | 248 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/BaseCellContent.tsx | TypeScript JSX | 18 | 0 | 3 | 21 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/SaveTemplateDialog.tsx | TypeScript JSX | 83 | 0 | 4 | 87 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/SearchableTemplateSelect.tsx | TypeScript JSX | 273 | 19 | 37 | 329 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/TemplateManager.tsx | TypeScript JSX | 193 | 4 | 15 | 212 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationCell.tsx | TypeScript JSX | 374 | 42 | 44 | 460 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationContainer.tsx | TypeScript JSX | 1,101 | 234 | 213 | 1,548 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationTable.tsx | TypeScript JSX | 499 | 48 | 54 | 601 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/CheckboxCell.tsx | TypeScript JSX | 112 | 12 | 21 | 145 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/InputCell.tsx | TypeScript JSX | 232 | 31 | 32 | 295 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultiSelectCell.tsx | TypeScript JSX | 407 | 56 | 52 | 515 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultilineInput.tsx | TypeScript JSX | 193 | 23 | 22 | 238 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/SelectCell.tsx | TypeScript JSX | 289 | 36 | 31 | 356 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useAiValidation.tsx | TypeScript JSX | 500 | 75 | 89 | 664 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useFilters.tsx | TypeScript JSX | 89 | 12 | 16 | 117 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useTemplates.tsx | TypeScript JSX | 204 | 26 | 33 | 263 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useUpcValidation.tsx | TypeScript JSX | 93 | 13 | 18 | 124 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidation.tsx | TypeScript JSX | 219 | 39 | 47 | 305 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidationState.tsx | TypeScript JSX | 1,060 | 228 | 229 | 1,517 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/index.tsx | TypeScript JSX | 20 | 6 | 2 | 28 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/types.ts | TypeScript | 4 | 0 | 1 | 5 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/types/index.ts | TypeScript | 16 | 4 | 4 | 24 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/dataMutations.ts | TypeScript | 124 | 4 | 14 | 142 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/errorUtils.ts | TypeScript | 21 | 15 | 5 | 41 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/upcValidation.ts | TypeScript | 43 | 24 | 7 | 74 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/validation-helper.js | JavaScript | 28 | 7 | 9 | 44 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/validationUtils.ts | TypeScript | 101 | 59 | 24 | 184 |
| Total | | 6,565 | 1,027 | 1,053 | 8,645 |
+------------------------------------------------------------------------------------------------------------------------------------------+----------------+------------+------------+------------+------------+

View File

@@ -0,0 +1,43 @@
# Details
Date : 2025-03-17 16:02:14
Directory /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew
Total : 28 files, 6551 codes, 1023 comments, 1050 blanks, all 8624 lines
[Summary](results.md) / Details / [Diff Summary](diff.md) / [Diff Details](diff-details.md)
## Files
| filename | language | code | comment | blank | total |
| :--- | :--- | ---: | ---: | ---: | ---: |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/README.md](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/README.md) | Markdown | 39 | 0 | 19 | 58 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/AiValidationDialogs.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/AiValidationDialogs.tsx) | TypeScript JSX | 230 | 10 | 8 | 248 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/BaseCellContent.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/BaseCellContent.tsx) | TypeScript JSX | 18 | 0 | 3 | 21 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/SaveTemplateDialog.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/SaveTemplateDialog.tsx) | TypeScript JSX | 83 | 0 | 4 | 87 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/SearchableTemplateSelect.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/SearchableTemplateSelect.tsx) | TypeScript JSX | 273 | 19 | 37 | 329 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/TemplateManager.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/TemplateManager.tsx) | TypeScript JSX | 193 | 4 | 15 | 212 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationCell.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationCell.tsx) | TypeScript JSX | 374 | 42 | 44 | 460 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationContainer.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationContainer.tsx) | TypeScript JSX | 971 | 194 | 178 | 1,343 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationTable.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationTable.tsx) | TypeScript JSX | 499 | 48 | 54 | 601 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/CheckboxCell.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/CheckboxCell.tsx) | TypeScript JSX | 112 | 12 | 21 | 145 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/InputCell.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/InputCell.tsx) | TypeScript JSX | 232 | 31 | 32 | 295 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultiSelectCell.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultiSelectCell.tsx) | TypeScript JSX | 407 | 56 | 52 | 515 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultilineInput.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultilineInput.tsx) | TypeScript JSX | 193 | 23 | 22 | 238 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/SelectCell.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/SelectCell.tsx) | TypeScript JSX | 289 | 36 | 31 | 356 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useAiValidation.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useAiValidation.tsx) | TypeScript JSX | 500 | 75 | 89 | 664 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useFilters.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useFilters.tsx) | TypeScript JSX | 89 | 12 | 16 | 117 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useTemplates.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useTemplates.tsx) | TypeScript JSX | 204 | 26 | 33 | 263 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useUpcValidation.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useUpcValidation.tsx) | TypeScript JSX | 209 | 49 | 50 | 308 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidation.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidation.tsx) | TypeScript JSX | 219 | 39 | 47 | 305 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidationState.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidationState.tsx) | TypeScript JSX | 1,060 | 228 | 229 | 1,517 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/index.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/index.tsx) | TypeScript JSX | 20 | 6 | 2 | 28 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/types.ts](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/types.ts) | TypeScript | 4 | 0 | 1 | 5 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/types/index.ts](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/types/index.ts) | TypeScript | 16 | 4 | 4 | 24 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/dataMutations.ts](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/dataMutations.ts) | TypeScript | 124 | 4 | 14 | 142 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/errorUtils.ts](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/errorUtils.ts) | TypeScript | 21 | 15 | 5 | 41 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/upcValidation.ts](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/upcValidation.ts) | TypeScript | 43 | 24 | 7 | 74 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/validation-helper.js](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/validation-helper.js) | JavaScript | 28 | 7 | 9 | 44 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/validationUtils.ts](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/validationUtils.ts) | TypeScript | 101 | 59 | 24 | 184 |
[Summary](results.md) / Details / [Diff Summary](diff.md) / [Diff Details](diff-details.md)

View File

@@ -0,0 +1,17 @@
# Diff Details
Date : 2025-03-17 16:02:14
Directory /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew
Total : 2 files, -14 codes, -4 comments, -3 blanks, all -21 lines
[Summary](results.md) / [Details](details.md) / [Diff Summary](diff.md) / Diff Details
## Files
| filename | language | code | comment | blank | total |
| :--- | :--- | ---: | ---: | ---: | ---: |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationContainer.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationContainer.tsx) | TypeScript JSX | -130 | -40 | -35 | -205 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useUpcValidation.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useUpcValidation.tsx) | TypeScript JSX | 116 | 36 | 32 | 184 |
[Summary](results.md) / [Details](details.md) / [Diff Summary](diff.md) / Diff Details

View File

@@ -0,0 +1,23 @@
# Diff Summary
Date : 2025-03-17 16:02:14
Directory /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew
Total : 2 files, -14 codes, -4 comments, -3 blanks, all -21 lines
[Summary](results.md) / [Details](details.md) / Diff Summary / [Diff Details](diff-details.md)
## Languages
| language | files | code | comment | blank | total |
| :--- | ---: | ---: | ---: | ---: | ---: |
| TypeScript JSX | 2 | -14 | -4 | -3 | -21 |
## Directories
| path | files | code | comment | blank | total |
| :--- | ---: | ---: | ---: | ---: | ---: |
| . | 2 | -14 | -4 | -3 | -21 |
| components | 1 | -130 | -40 | -35 | -205 |
| hooks | 1 | 116 | 36 | 32 | 184 |
[Summary](results.md) / [Details](details.md) / Diff Summary / [Diff Details](diff-details.md)

View File

@@ -0,0 +1,28 @@
Date : 2025-03-17 16:02:14
Directory : /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew
Total : 2 files, -14 codes, -4 comments, -3 blanks, all -21 lines
Languages
+----------------+------------+------------+------------+------------+------------+
| language | files | code | comment | blank | total |
+----------------+------------+------------+------------+------------+------------+
| TypeScript JSX | 2 | -14 | -4 | -3 | -21 |
+----------------+------------+------------+------------+------------+------------+
Directories
+-------------------------------------------------------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+
| path | files | code | comment | blank | total |
+-------------------------------------------------------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+
| . | 2 | -14 | -4 | -3 | -21 |
| components | 1 | -130 | -40 | -35 | -205 |
| hooks | 1 | 116 | 36 | 32 | 184 |
+-------------------------------------------------------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+
Files
+-------------------------------------------------------------------------------------------------------------------------------------+----------------+------------+------------+------------+------------+
| filename | language | code | comment | blank | total |
+-------------------------------------------------------------------------------------------------------------------------------------+----------------+------------+------------+------------+------------+
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationContainer.tsx | TypeScript JSX | -130 | -40 | -35 | -205 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useUpcValidation.tsx | TypeScript JSX | 116 | 36 | 32 | 184 |
| Total | | -14 | -4 | -3 | -21 |
+-------------------------------------------------------------------------------------------------------------------------------------+----------------+------------+------------+------------+------------+

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,31 @@
# Summary
Date : 2025-03-17 16:02:14
Directory /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew
Total : 28 files, 6551 codes, 1023 comments, 1050 blanks, all 8624 lines
Summary / [Details](details.md) / [Diff Summary](diff.md) / [Diff Details](diff-details.md)
## Languages
| language | files | code | comment | blank | total |
| :--- | ---: | ---: | ---: | ---: | ---: |
| TypeScript JSX | 20 | 6,175 | 910 | 967 | 8,052 |
| TypeScript | 6 | 309 | 106 | 55 | 470 |
| Markdown | 1 | 39 | 0 | 19 | 58 |
| JavaScript | 1 | 28 | 7 | 9 | 44 |
## Directories
| path | files | code | comment | blank | total |
| :--- | ---: | ---: | ---: | ---: | ---: |
| . | 28 | 6,551 | 1,023 | 1,050 | 8,624 |
| . (Files) | 3 | 63 | 6 | 22 | 91 |
| components | 13 | 3,874 | 475 | 501 | 4,850 |
| components (Files) | 8 | 2,641 | 317 | 343 | 3,301 |
| components/cells | 5 | 1,233 | 158 | 158 | 1,549 |
| hooks | 6 | 2,281 | 429 | 464 | 3,174 |
| types | 1 | 16 | 4 | 4 | 24 |
| utils | 5 | 317 | 109 | 59 | 485 |
Summary / [Details](details.md) / [Diff Summary](diff.md) / [Diff Details](diff-details.md)

View File

@@ -0,0 +1,62 @@
Date : 2025-03-17 16:02:14
Directory : /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew
Total : 28 files, 6551 codes, 1023 comments, 1050 blanks, all 8624 lines
Languages
+----------------+------------+------------+------------+------------+------------+
| language | files | code | comment | blank | total |
+----------------+------------+------------+------------+------------+------------+
| TypeScript JSX | 20 | 6,175 | 910 | 967 | 8,052 |
| TypeScript | 6 | 309 | 106 | 55 | 470 |
| Markdown | 1 | 39 | 0 | 19 | 58 |
| JavaScript | 1 | 28 | 7 | 9 | 44 |
+----------------+------------+------------+------------+------------+------------+
Directories
+------------------------------------------------------------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+
| path | files | code | comment | blank | total |
+------------------------------------------------------------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+
| . | 28 | 6,551 | 1,023 | 1,050 | 8,624 |
| . (Files) | 3 | 63 | 6 | 22 | 91 |
| components | 13 | 3,874 | 475 | 501 | 4,850 |
| components (Files) | 8 | 2,641 | 317 | 343 | 3,301 |
| components/cells | 5 | 1,233 | 158 | 158 | 1,549 |
| hooks | 6 | 2,281 | 429 | 464 | 3,174 |
| types | 1 | 16 | 4 | 4 | 24 |
| utils | 5 | 317 | 109 | 59 | 485 |
+------------------------------------------------------------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+
Files
+------------------------------------------------------------------------------------------------------------------------------------------+----------------+------------+------------+------------+------------+
| filename | language | code | comment | blank | total |
+------------------------------------------------------------------------------------------------------------------------------------------+----------------+------------+------------+------------+------------+
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/README.md | Markdown | 39 | 0 | 19 | 58 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/AiValidationDialogs.tsx | TypeScript JSX | 230 | 10 | 8 | 248 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/BaseCellContent.tsx | TypeScript JSX | 18 | 0 | 3 | 21 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/SaveTemplateDialog.tsx | TypeScript JSX | 83 | 0 | 4 | 87 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/SearchableTemplateSelect.tsx | TypeScript JSX | 273 | 19 | 37 | 329 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/TemplateManager.tsx | TypeScript JSX | 193 | 4 | 15 | 212 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationCell.tsx | TypeScript JSX | 374 | 42 | 44 | 460 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationContainer.tsx | TypeScript JSX | 971 | 194 | 178 | 1,343 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationTable.tsx | TypeScript JSX | 499 | 48 | 54 | 601 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/CheckboxCell.tsx | TypeScript JSX | 112 | 12 | 21 | 145 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/InputCell.tsx | TypeScript JSX | 232 | 31 | 32 | 295 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultiSelectCell.tsx | TypeScript JSX | 407 | 56 | 52 | 515 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultilineInput.tsx | TypeScript JSX | 193 | 23 | 22 | 238 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/SelectCell.tsx | TypeScript JSX | 289 | 36 | 31 | 356 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useAiValidation.tsx | TypeScript JSX | 500 | 75 | 89 | 664 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useFilters.tsx | TypeScript JSX | 89 | 12 | 16 | 117 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useTemplates.tsx | TypeScript JSX | 204 | 26 | 33 | 263 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useUpcValidation.tsx | TypeScript JSX | 209 | 49 | 50 | 308 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidation.tsx | TypeScript JSX | 219 | 39 | 47 | 305 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidationState.tsx | TypeScript JSX | 1,060 | 228 | 229 | 1,517 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/index.tsx | TypeScript JSX | 20 | 6 | 2 | 28 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/types.ts | TypeScript | 4 | 0 | 1 | 5 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/types/index.ts | TypeScript | 16 | 4 | 4 | 24 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/dataMutations.ts | TypeScript | 124 | 4 | 14 | 142 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/errorUtils.ts | TypeScript | 21 | 15 | 5 | 41 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/upcValidation.ts | TypeScript | 43 | 24 | 7 | 74 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/validation-helper.js | JavaScript | 28 | 7 | 9 | 44 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/validationUtils.ts | TypeScript | 101 | 59 | 24 | 184 |
| Total | | 6,551 | 1,023 | 1,050 | 8,624 |
+------------------------------------------------------------------------------------------------------------------------------------------+----------------+------------+------------+------------+------------+

View File

@@ -0,0 +1,41 @@
# Details
Date : 2025-03-17 16:24:17
Directory /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew
Total : 26 files, 6193 codes, 1008 comments, 1017 blanks, all 8218 lines
[Summary](results.md) / Details / [Diff Summary](diff.md) / [Diff Details](diff-details.md)
## Files
| filename | language | code | comment | blank | total |
| :--- | :--- | ---: | ---: | ---: | ---: |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/README.md](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/README.md) | Markdown | 39 | 0 | 19 | 58 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/AiValidationDialogs.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/AiValidationDialogs.tsx) | TypeScript JSX | 230 | 10 | 8 | 248 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/BaseCellContent.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/BaseCellContent.tsx) | TypeScript JSX | 18 | 0 | 3 | 21 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/SearchableTemplateSelect.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/SearchableTemplateSelect.tsx) | TypeScript JSX | 273 | 19 | 37 | 329 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationCell.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationCell.tsx) | TypeScript JSX | 374 | 42 | 44 | 460 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationContainer.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationContainer.tsx) | TypeScript JSX | 730 | 126 | 106 | 962 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationTable.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationTable.tsx) | TypeScript JSX | 499 | 48 | 54 | 601 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/CheckboxCell.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/CheckboxCell.tsx) | TypeScript JSX | 112 | 12 | 21 | 145 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/InputCell.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/InputCell.tsx) | TypeScript JSX | 232 | 31 | 32 | 295 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultiSelectCell.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultiSelectCell.tsx) | TypeScript JSX | 407 | 56 | 52 | 515 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultilineInput.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultilineInput.tsx) | TypeScript JSX | 193 | 23 | 22 | 238 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/SelectCell.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/SelectCell.tsx) | TypeScript JSX | 289 | 36 | 31 | 356 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useAiValidation.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useAiValidation.tsx) | TypeScript JSX | 500 | 75 | 89 | 664 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useProductLinesFetching.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useProductLinesFetching.tsx) | TypeScript JSX | 248 | 69 | 74 | 391 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useTemplates.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useTemplates.tsx) | TypeScript JSX | 204 | 26 | 33 | 263 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useUpcValidation.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useUpcValidation.tsx) | TypeScript JSX | 209 | 49 | 50 | 308 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidation.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidation.tsx) | TypeScript JSX | 219 | 39 | 47 | 305 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidationState.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidationState.tsx) | TypeScript JSX | 1,060 | 228 | 229 | 1,517 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/index.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/index.tsx) | TypeScript JSX | 20 | 6 | 2 | 28 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/types.ts](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/types.ts) | TypeScript | 4 | 0 | 1 | 5 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/types/index.ts](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/types/index.ts) | TypeScript | 16 | 4 | 4 | 24 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/dataMutations.ts](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/dataMutations.ts) | TypeScript | 124 | 4 | 14 | 142 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/errorUtils.ts](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/errorUtils.ts) | TypeScript | 21 | 15 | 5 | 41 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/upcValidation.ts](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/upcValidation.ts) | TypeScript | 43 | 24 | 7 | 74 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/validation-helper.js](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/validation-helper.js) | JavaScript | 28 | 7 | 9 | 44 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/validationUtils.ts](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/validationUtils.ts) | TypeScript | 101 | 59 | 24 | 184 |
[Summary](results.md) / Details / [Diff Summary](diff.md) / [Diff Details](diff-details.md)

View File

@@ -0,0 +1,20 @@
# Diff Details
Date : 2025-03-17 16:24:17
Directory /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew
Total : 5 files, -358 codes, -15 comments, -33 blanks, all -406 lines
[Summary](results.md) / [Details](details.md) / [Diff Summary](diff.md) / Diff Details
## Files
| filename | language | code | comment | blank | total |
| :--- | :--- | ---: | ---: | ---: | ---: |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/SaveTemplateDialog.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/SaveTemplateDialog.tsx) | TypeScript JSX | -83 | 0 | -4 | -87 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/TemplateManager.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/TemplateManager.tsx) | TypeScript JSX | -193 | -4 | -15 | -212 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationContainer.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationContainer.tsx) | TypeScript JSX | -241 | -68 | -72 | -381 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useFilters.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useFilters.tsx) | TypeScript JSX | -89 | -12 | -16 | -117 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useProductLinesFetching.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useProductLinesFetching.tsx) | TypeScript JSX | 248 | 69 | 74 | 391 |
[Summary](results.md) / [Details](details.md) / [Diff Summary](diff.md) / Diff Details

View File

@@ -0,0 +1,23 @@
# Diff Summary
Date : 2025-03-17 16:24:17
Directory /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew
Total : 5 files, -358 codes, -15 comments, -33 blanks, all -406 lines
[Summary](results.md) / [Details](details.md) / Diff Summary / [Diff Details](diff-details.md)
## Languages
| language | files | code | comment | blank | total |
| :--- | ---: | ---: | ---: | ---: | ---: |
| TypeScript JSX | 5 | -358 | -15 | -33 | -406 |
## Directories
| path | files | code | comment | blank | total |
| :--- | ---: | ---: | ---: | ---: | ---: |
| . | 5 | -358 | -15 | -33 | -406 |
| components | 3 | -517 | -72 | -91 | -680 |
| hooks | 2 | 159 | 57 | 58 | 274 |
[Summary](results.md) / [Details](details.md) / Diff Summary / [Diff Details](diff-details.md)

View File

@@ -0,0 +1,31 @@
Date : 2025-03-17 16:24:17
Directory : /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew
Total : 5 files, -358 codes, -15 comments, -33 blanks, all -406 lines
Languages
+----------------+------------+------------+------------+------------+------------+
| language | files | code | comment | blank | total |
+----------------+------------+------------+------------+------------+------------+
| TypeScript JSX | 5 | -358 | -15 | -33 | -406 |
+----------------+------------+------------+------------+------------+------------+
Directories
+-------------------------------------------------------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+
| path | files | code | comment | blank | total |
+-------------------------------------------------------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+
| . | 5 | -358 | -15 | -33 | -406 |
| components | 3 | -517 | -72 | -91 | -680 |
| hooks | 2 | 159 | 57 | 58 | 274 |
+-------------------------------------------------------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+
Files
+-------------------------------------------------------------------------------------------------------------------------------------+----------------+------------+------------+------------+------------+
| filename | language | code | comment | blank | total |
+-------------------------------------------------------------------------------------------------------------------------------------+----------------+------------+------------+------------+------------+
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/SaveTemplateDialog.tsx | TypeScript JSX | -83 | 0 | -4 | -87 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/TemplateManager.tsx | TypeScript JSX | -193 | -4 | -15 | -212 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationContainer.tsx | TypeScript JSX | -241 | -68 | -72 | -381 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useFilters.tsx | TypeScript JSX | -89 | -12 | -16 | -117 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useProductLinesFetching.tsx | TypeScript JSX | 248 | 69 | 74 | 391 |
| Total | | -358 | -15 | -33 | -406 |
+-------------------------------------------------------------------------------------------------------------------------------------+----------------+------------+------------+------------+------------+

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,31 @@
# Summary
Date : 2025-03-17 16:24:17
Directory /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew
Total : 26 files, 6193 codes, 1008 comments, 1017 blanks, all 8218 lines
Summary / [Details](details.md) / [Diff Summary](diff.md) / [Diff Details](diff-details.md)
## Languages
| language | files | code | comment | blank | total |
| :--- | ---: | ---: | ---: | ---: | ---: |
| TypeScript JSX | 18 | 5,817 | 895 | 934 | 7,646 |
| TypeScript | 6 | 309 | 106 | 55 | 470 |
| Markdown | 1 | 39 | 0 | 19 | 58 |
| JavaScript | 1 | 28 | 7 | 9 | 44 |
## Directories
| path | files | code | comment | blank | total |
| :--- | ---: | ---: | ---: | ---: | ---: |
| . | 26 | 6,193 | 1,008 | 1,017 | 8,218 |
| . (Files) | 3 | 63 | 6 | 22 | 91 |
| components | 11 | 3,357 | 403 | 410 | 4,170 |
| components (Files) | 6 | 2,124 | 245 | 252 | 2,621 |
| components/cells | 5 | 1,233 | 158 | 158 | 1,549 |
| hooks | 6 | 2,440 | 486 | 522 | 3,448 |
| types | 1 | 16 | 4 | 4 | 24 |
| utils | 5 | 317 | 109 | 59 | 485 |
Summary / [Details](details.md) / [Diff Summary](diff.md) / [Diff Details](diff-details.md)

View File

@@ -0,0 +1,60 @@
Date : 2025-03-17 16:24:17
Directory : /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew
Total : 26 files, 6193 codes, 1008 comments, 1017 blanks, all 8218 lines
Languages
+----------------+------------+------------+------------+------------+------------+
| language | files | code | comment | blank | total |
+----------------+------------+------------+------------+------------+------------+
| TypeScript JSX | 18 | 5,817 | 895 | 934 | 7,646 |
| TypeScript | 6 | 309 | 106 | 55 | 470 |
| Markdown | 1 | 39 | 0 | 19 | 58 |
| JavaScript | 1 | 28 | 7 | 9 | 44 |
+----------------+------------+------------+------------+------------+------------+
Directories
+------------------------------------------------------------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+
| path | files | code | comment | blank | total |
+------------------------------------------------------------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+
| . | 26 | 6,193 | 1,008 | 1,017 | 8,218 |
| . (Files) | 3 | 63 | 6 | 22 | 91 |
| components | 11 | 3,357 | 403 | 410 | 4,170 |
| components (Files) | 6 | 2,124 | 245 | 252 | 2,621 |
| components/cells | 5 | 1,233 | 158 | 158 | 1,549 |
| hooks | 6 | 2,440 | 486 | 522 | 3,448 |
| types | 1 | 16 | 4 | 4 | 24 |
| utils | 5 | 317 | 109 | 59 | 485 |
+------------------------------------------------------------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+
Files
+------------------------------------------------------------------------------------------------------------------------------------------+----------------+------------+------------+------------+------------+
| filename | language | code | comment | blank | total |
+------------------------------------------------------------------------------------------------------------------------------------------+----------------+------------+------------+------------+------------+
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/README.md | Markdown | 39 | 0 | 19 | 58 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/AiValidationDialogs.tsx | TypeScript JSX | 230 | 10 | 8 | 248 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/BaseCellContent.tsx | TypeScript JSX | 18 | 0 | 3 | 21 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/SearchableTemplateSelect.tsx | TypeScript JSX | 273 | 19 | 37 | 329 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationCell.tsx | TypeScript JSX | 374 | 42 | 44 | 460 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationContainer.tsx | TypeScript JSX | 730 | 126 | 106 | 962 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationTable.tsx | TypeScript JSX | 499 | 48 | 54 | 601 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/CheckboxCell.tsx | TypeScript JSX | 112 | 12 | 21 | 145 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/InputCell.tsx | TypeScript JSX | 232 | 31 | 32 | 295 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultiSelectCell.tsx | TypeScript JSX | 407 | 56 | 52 | 515 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultilineInput.tsx | TypeScript JSX | 193 | 23 | 22 | 238 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/SelectCell.tsx | TypeScript JSX | 289 | 36 | 31 | 356 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useAiValidation.tsx | TypeScript JSX | 500 | 75 | 89 | 664 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useProductLinesFetching.tsx | TypeScript JSX | 248 | 69 | 74 | 391 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useTemplates.tsx | TypeScript JSX | 204 | 26 | 33 | 263 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useUpcValidation.tsx | TypeScript JSX | 209 | 49 | 50 | 308 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidation.tsx | TypeScript JSX | 219 | 39 | 47 | 305 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidationState.tsx | TypeScript JSX | 1,060 | 228 | 229 | 1,517 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/index.tsx | TypeScript JSX | 20 | 6 | 2 | 28 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/types.ts | TypeScript | 4 | 0 | 1 | 5 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/types/index.ts | TypeScript | 16 | 4 | 4 | 24 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/dataMutations.ts | TypeScript | 124 | 4 | 14 | 142 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/errorUtils.ts | TypeScript | 21 | 15 | 5 | 41 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/upcValidation.ts | TypeScript | 43 | 24 | 7 | 74 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/validation-helper.js | JavaScript | 28 | 7 | 9 | 44 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/validationUtils.ts | TypeScript | 101 | 59 | 24 | 184 |
| Total | | 6,193 | 1,008 | 1,017 | 8,218 |
+------------------------------------------------------------------------------------------------------------------------------------------+----------------+------------+------------+------------+------------+

View File

@@ -0,0 +1,42 @@
# Details
Date : 2025-03-18 12:39:04
Directory /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew
Total : 27 files, 6925 codes, 1247 comments, 1248 blanks, all 9420 lines
[Summary](results.md) / Details / [Diff Summary](diff.md) / [Diff Details](diff-details.md)
## Files
| filename | language | code | comment | blank | total |
| :--- | :--- | ---: | ---: | ---: | ---: |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/README.md](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/README.md) | Markdown | 39 | 0 | 19 | 58 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/AiValidationDialogs.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/AiValidationDialogs.tsx) | TypeScript JSX | 230 | 10 | 8 | 248 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/BaseCellContent.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/BaseCellContent.tsx) | TypeScript JSX | 18 | 0 | 3 | 21 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/SearchableTemplateSelect.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/SearchableTemplateSelect.tsx) | TypeScript JSX | 273 | 19 | 37 | 329 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/UpcValidationTableAdapter.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/UpcValidationTableAdapter.tsx) | TypeScript JSX | 113 | 17 | 10 | 140 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationCell.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationCell.tsx) | TypeScript JSX | 377 | 49 | 54 | 480 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationContainer.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationContainer.tsx) | TypeScript JSX | 969 | 182 | 158 | 1,309 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationTable.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationTable.tsx) | TypeScript JSX | 509 | 50 | 57 | 616 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/CheckboxCell.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/CheckboxCell.tsx) | TypeScript JSX | 112 | 12 | 21 | 145 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/InputCell.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/InputCell.tsx) | TypeScript JSX | 233 | 34 | 33 | 300 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultiSelectCell.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultiSelectCell.tsx) | TypeScript JSX | 420 | 66 | 59 | 545 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultilineInput.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultilineInput.tsx) | TypeScript JSX | 193 | 23 | 22 | 238 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/SelectCell.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/SelectCell.tsx) | TypeScript JSX | 227 | 36 | 32 | 295 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useAiValidation.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useAiValidation.tsx) | TypeScript JSX | 500 | 75 | 89 | 664 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useProductLinesFetching.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useProductLinesFetching.tsx) | TypeScript JSX | 264 | 75 | 81 | 420 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useTemplates.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useTemplates.tsx) | TypeScript JSX | 204 | 26 | 33 | 263 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useUpcValidation.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useUpcValidation.tsx) | TypeScript JSX | 337 | 88 | 92 | 517 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidation.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidation.tsx) | TypeScript JSX | 360 | 78 | 85 | 523 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidationState.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidationState.tsx) | TypeScript JSX | 1,190 | 288 | 289 | 1,767 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/index.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/index.tsx) | TypeScript JSX | 20 | 6 | 2 | 28 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/types.ts](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/types.ts) | TypeScript | 4 | 0 | 1 | 5 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/types/index.ts](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/types/index.ts) | TypeScript | 16 | 4 | 4 | 24 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/dataMutations.ts](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/dataMutations.ts) | TypeScript | 124 | 4 | 14 | 142 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/errorUtils.ts](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/errorUtils.ts) | TypeScript | 21 | 15 | 5 | 41 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/upcValidation.ts](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/upcValidation.ts) | TypeScript | 43 | 24 | 7 | 74 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/validation-helper.js](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/validation-helper.js) | JavaScript | 28 | 7 | 9 | 44 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/validationUtils.ts](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/validationUtils.ts) | TypeScript | 101 | 59 | 24 | 184 |
[Summary](results.md) / Details / [Diff Summary](diff.md) / [Diff Details](diff-details.md)

View File

@@ -0,0 +1,26 @@
# Diff Details
Date : 2025-03-18 12:39:04
Directory /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew
Total : 11 files, 732 codes, 239 comments, 231 blanks, all 1202 lines
[Summary](results.md) / [Details](details.md) / [Diff Summary](diff.md) / Diff Details
## Files
| filename | language | code | comment | blank | total |
| :--- | :--- | ---: | ---: | ---: | ---: |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/UpcValidationTableAdapter.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/UpcValidationTableAdapter.tsx) | TypeScript JSX | 113 | 17 | 10 | 140 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationCell.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationCell.tsx) | TypeScript JSX | 3 | 7 | 10 | 20 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationContainer.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationContainer.tsx) | TypeScript JSX | 239 | 56 | 52 | 347 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationTable.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationTable.tsx) | TypeScript JSX | 10 | 2 | 3 | 15 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/InputCell.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/InputCell.tsx) | TypeScript JSX | 1 | 3 | 1 | 5 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultiSelectCell.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultiSelectCell.tsx) | TypeScript JSX | 13 | 10 | 7 | 30 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/SelectCell.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/SelectCell.tsx) | TypeScript JSX | -62 | 0 | 1 | -61 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useProductLinesFetching.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useProductLinesFetching.tsx) | TypeScript JSX | 16 | 6 | 7 | 29 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useUpcValidation.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useUpcValidation.tsx) | TypeScript JSX | 128 | 39 | 42 | 209 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidation.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidation.tsx) | TypeScript JSX | 141 | 39 | 38 | 218 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidationState.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidationState.tsx) | TypeScript JSX | 130 | 60 | 60 | 250 |
[Summary](results.md) / [Details](details.md) / [Diff Summary](diff.md) / Diff Details

View File

@@ -0,0 +1,25 @@
# Diff Summary
Date : 2025-03-18 12:39:04
Directory /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew
Total : 11 files, 732 codes, 239 comments, 231 blanks, all 1202 lines
[Summary](results.md) / [Details](details.md) / Diff Summary / [Diff Details](diff-details.md)
## Languages
| language | files | code | comment | blank | total |
| :--- | ---: | ---: | ---: | ---: | ---: |
| TypeScript JSX | 11 | 732 | 239 | 231 | 1,202 |
## Directories
| path | files | code | comment | blank | total |
| :--- | ---: | ---: | ---: | ---: | ---: |
| . | 11 | 732 | 239 | 231 | 1,202 |
| components | 7 | 317 | 95 | 84 | 496 |
| components (Files) | 4 | 365 | 82 | 75 | 522 |
| components/cells | 3 | -48 | 13 | 9 | -26 |
| hooks | 4 | 415 | 144 | 147 | 706 |
[Summary](results.md) / [Details](details.md) / Diff Summary / [Diff Details](diff-details.md)

View File

@@ -0,0 +1,39 @@
Date : 2025-03-18 12:39:04
Directory : /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew
Total : 11 files, 732 codes, 239 comments, 231 blanks, all 1202 lines
Languages
+----------------+------------+------------+------------+------------+------------+
| language | files | code | comment | blank | total |
+----------------+------------+------------+------------+------------+------------+
| TypeScript JSX | 11 | 732 | 239 | 231 | 1,202 |
+----------------+------------+------------+------------+------------+------------+
Directories
+-------------------------------------------------------------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+
| path | files | code | comment | blank | total |
+-------------------------------------------------------------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+
| . | 11 | 732 | 239 | 231 | 1,202 |
| components | 7 | 317 | 95 | 84 | 496 |
| components (Files) | 4 | 365 | 82 | 75 | 522 |
| components/cells | 3 | -48 | 13 | 9 | -26 |
| hooks | 4 | 415 | 144 | 147 | 706 |
+-------------------------------------------------------------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+
Files
+-------------------------------------------------------------------------------------------------------------------------------------------+----------------+------------+------------+------------+------------+
| filename | language | code | comment | blank | total |
+-------------------------------------------------------------------------------------------------------------------------------------------+----------------+------------+------------+------------+------------+
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/UpcValidationTableAdapter.tsx | TypeScript JSX | 113 | 17 | 10 | 140 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationCell.tsx | TypeScript JSX | 3 | 7 | 10 | 20 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationContainer.tsx | TypeScript JSX | 239 | 56 | 52 | 347 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationTable.tsx | TypeScript JSX | 10 | 2 | 3 | 15 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/InputCell.tsx | TypeScript JSX | 1 | 3 | 1 | 5 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultiSelectCell.tsx | TypeScript JSX | 13 | 10 | 7 | 30 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/SelectCell.tsx | TypeScript JSX | -62 | 0 | 1 | -61 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useProductLinesFetching.tsx | TypeScript JSX | 16 | 6 | 7 | 29 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useUpcValidation.tsx | TypeScript JSX | 128 | 39 | 42 | 209 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidation.tsx | TypeScript JSX | 141 | 39 | 38 | 218 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidationState.tsx | TypeScript JSX | 130 | 60 | 60 | 250 |
| Total | | 732 | 239 | 231 | 1,202 |
+-------------------------------------------------------------------------------------------------------------------------------------------+----------------+------------+------------+------------+------------+

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,31 @@
# Summary
Date : 2025-03-18 12:39:04
Directory /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew
Total : 27 files, 6925 codes, 1247 comments, 1248 blanks, all 9420 lines
Summary / [Details](details.md) / [Diff Summary](diff.md) / [Diff Details](diff-details.md)
## Languages
| language | files | code | comment | blank | total |
| :--- | ---: | ---: | ---: | ---: | ---: |
| TypeScript JSX | 19 | 6,549 | 1,134 | 1,165 | 8,848 |
| TypeScript | 6 | 309 | 106 | 55 | 470 |
| Markdown | 1 | 39 | 0 | 19 | 58 |
| JavaScript | 1 | 28 | 7 | 9 | 44 |
## Directories
| path | files | code | comment | blank | total |
| :--- | ---: | ---: | ---: | ---: | ---: |
| . | 27 | 6,925 | 1,247 | 1,248 | 9,420 |
| . (Files) | 3 | 63 | 6 | 22 | 91 |
| components | 12 | 3,674 | 498 | 494 | 4,666 |
| components (Files) | 7 | 2,489 | 327 | 327 | 3,143 |
| components/cells | 5 | 1,185 | 171 | 167 | 1,523 |
| hooks | 6 | 2,855 | 630 | 669 | 4,154 |
| types | 1 | 16 | 4 | 4 | 24 |
| utils | 5 | 317 | 109 | 59 | 485 |
Summary / [Details](details.md) / [Diff Summary](diff.md) / [Diff Details](diff-details.md)

View File

@@ -0,0 +1,61 @@
Date : 2025-03-18 12:39:04
Directory : /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew
Total : 27 files, 6925 codes, 1247 comments, 1248 blanks, all 9420 lines
Languages
+----------------+------------+------------+------------+------------+------------+
| language | files | code | comment | blank | total |
+----------------+------------+------------+------------+------------+------------+
| TypeScript JSX | 19 | 6,549 | 1,134 | 1,165 | 8,848 |
| TypeScript | 6 | 309 | 106 | 55 | 470 |
| Markdown | 1 | 39 | 0 | 19 | 58 |
| JavaScript | 1 | 28 | 7 | 9 | 44 |
+----------------+------------+------------+------------+------------+------------+
Directories
+-------------------------------------------------------------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+
| path | files | code | comment | blank | total |
+-------------------------------------------------------------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+
| . | 27 | 6,925 | 1,247 | 1,248 | 9,420 |
| . (Files) | 3 | 63 | 6 | 22 | 91 |
| components | 12 | 3,674 | 498 | 494 | 4,666 |
| components (Files) | 7 | 2,489 | 327 | 327 | 3,143 |
| components/cells | 5 | 1,185 | 171 | 167 | 1,523 |
| hooks | 6 | 2,855 | 630 | 669 | 4,154 |
| types | 1 | 16 | 4 | 4 | 24 |
| utils | 5 | 317 | 109 | 59 | 485 |
+-------------------------------------------------------------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+
Files
+-------------------------------------------------------------------------------------------------------------------------------------------+----------------+------------+------------+------------+------------+
| filename | language | code | comment | blank | total |
+-------------------------------------------------------------------------------------------------------------------------------------------+----------------+------------+------------+------------+------------+
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/README.md | Markdown | 39 | 0 | 19 | 58 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/AiValidationDialogs.tsx | TypeScript JSX | 230 | 10 | 8 | 248 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/BaseCellContent.tsx | TypeScript JSX | 18 | 0 | 3 | 21 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/SearchableTemplateSelect.tsx | TypeScript JSX | 273 | 19 | 37 | 329 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/UpcValidationTableAdapter.tsx | TypeScript JSX | 113 | 17 | 10 | 140 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationCell.tsx | TypeScript JSX | 377 | 49 | 54 | 480 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationContainer.tsx | TypeScript JSX | 969 | 182 | 158 | 1,309 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationTable.tsx | TypeScript JSX | 509 | 50 | 57 | 616 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/CheckboxCell.tsx | TypeScript JSX | 112 | 12 | 21 | 145 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/InputCell.tsx | TypeScript JSX | 233 | 34 | 33 | 300 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultiSelectCell.tsx | TypeScript JSX | 420 | 66 | 59 | 545 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultilineInput.tsx | TypeScript JSX | 193 | 23 | 22 | 238 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/SelectCell.tsx | TypeScript JSX | 227 | 36 | 32 | 295 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useAiValidation.tsx | TypeScript JSX | 500 | 75 | 89 | 664 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useProductLinesFetching.tsx | TypeScript JSX | 264 | 75 | 81 | 420 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useTemplates.tsx | TypeScript JSX | 204 | 26 | 33 | 263 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useUpcValidation.tsx | TypeScript JSX | 337 | 88 | 92 | 517 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidation.tsx | TypeScript JSX | 360 | 78 | 85 | 523 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidationState.tsx | TypeScript JSX | 1,190 | 288 | 289 | 1,767 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/index.tsx | TypeScript JSX | 20 | 6 | 2 | 28 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/types.ts | TypeScript | 4 | 0 | 1 | 5 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/types/index.ts | TypeScript | 16 | 4 | 4 | 24 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/dataMutations.ts | TypeScript | 124 | 4 | 14 | 142 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/errorUtils.ts | TypeScript | 21 | 15 | 5 | 41 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/upcValidation.ts | TypeScript | 43 | 24 | 7 | 74 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/validation-helper.js | JavaScript | 28 | 7 | 9 | 44 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/validationUtils.ts | TypeScript | 101 | 59 | 24 | 184 |
| Total | | 6,925 | 1,247 | 1,248 | 9,420 |
+-------------------------------------------------------------------------------------------------------------------------------------------+----------------+------------+------------+------------+------------+

View File

@@ -0,0 +1,42 @@
# Details
Date : 2025-03-18 13:49:23
Directory /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew
Total : 27 files, 6961 codes, 1254 comments, 1252 blanks, all 9467 lines
[Summary](results.md) / Details / [Diff Summary](diff.md) / [Diff Details](diff-details.md)
## Files
| filename | language | code | comment | blank | total |
| :--- | :--- | ---: | ---: | ---: | ---: |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/README.md](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/README.md) | Markdown | 39 | 0 | 19 | 58 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/AiValidationDialogs.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/AiValidationDialogs.tsx) | TypeScript JSX | 230 | 10 | 8 | 248 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/BaseCellContent.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/BaseCellContent.tsx) | TypeScript JSX | 18 | 0 | 3 | 21 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/SearchableTemplateSelect.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/SearchableTemplateSelect.tsx) | TypeScript JSX | 273 | 19 | 37 | 329 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/UpcValidationTableAdapter.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/UpcValidationTableAdapter.tsx) | TypeScript JSX | 113 | 17 | 10 | 140 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationCell.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationCell.tsx) | TypeScript JSX | 395 | 51 | 55 | 501 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationContainer.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationContainer.tsx) | TypeScript JSX | 969 | 182 | 158 | 1,309 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationTable.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationTable.tsx) | TypeScript JSX | 527 | 55 | 60 | 642 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/CheckboxCell.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/CheckboxCell.tsx) | TypeScript JSX | 112 | 12 | 21 | 145 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/InputCell.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/InputCell.tsx) | TypeScript JSX | 233 | 34 | 33 | 300 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultiSelectCell.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultiSelectCell.tsx) | TypeScript JSX | 420 | 66 | 59 | 545 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultilineInput.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultilineInput.tsx) | TypeScript JSX | 193 | 23 | 22 | 238 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/SelectCell.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/SelectCell.tsx) | TypeScript JSX | 227 | 36 | 32 | 295 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useAiValidation.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useAiValidation.tsx) | TypeScript JSX | 500 | 75 | 89 | 664 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useProductLinesFetching.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useProductLinesFetching.tsx) | TypeScript JSX | 264 | 75 | 81 | 420 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useTemplates.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useTemplates.tsx) | TypeScript JSX | 204 | 26 | 33 | 263 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useUpcValidation.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useUpcValidation.tsx) | TypeScript JSX | 337 | 88 | 92 | 517 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidation.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidation.tsx) | TypeScript JSX | 360 | 78 | 85 | 523 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidationState.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidationState.tsx) | TypeScript JSX | 1,190 | 288 | 289 | 1,767 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/index.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/index.tsx) | TypeScript JSX | 20 | 6 | 2 | 28 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/types.ts](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/types.ts) | TypeScript | 4 | 0 | 1 | 5 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/types/index.ts](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/types/index.ts) | TypeScript | 16 | 4 | 4 | 24 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/dataMutations.ts](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/dataMutations.ts) | TypeScript | 124 | 4 | 14 | 142 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/errorUtils.ts](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/errorUtils.ts) | TypeScript | 21 | 15 | 5 | 41 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/upcValidation.ts](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/upcValidation.ts) | TypeScript | 43 | 24 | 7 | 74 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/validation-helper.js](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/validation-helper.js) | JavaScript | 28 | 7 | 9 | 44 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/validationUtils.ts](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/validationUtils.ts) | TypeScript | 101 | 59 | 24 | 184 |
[Summary](results.md) / Details / [Diff Summary](diff.md) / [Diff Details](diff-details.md)

View File

@@ -0,0 +1,17 @@
# Diff Details
Date : 2025-03-18 13:49:23
Directory /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew
Total : 2 files, 36 codes, 7 comments, 4 blanks, all 47 lines
[Summary](results.md) / [Details](details.md) / [Diff Summary](diff.md) / Diff Details
## Files
| filename | language | code | comment | blank | total |
| :--- | :--- | ---: | ---: | ---: | ---: |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationCell.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationCell.tsx) | TypeScript JSX | 18 | 2 | 1 | 21 |
| [inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationTable.tsx](/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationTable.tsx) | TypeScript JSX | 18 | 5 | 3 | 26 |
[Summary](results.md) / [Details](details.md) / [Diff Summary](diff.md) / Diff Details

View File

@@ -0,0 +1,22 @@
# Diff Summary
Date : 2025-03-18 13:49:23
Directory /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew
Total : 2 files, 36 codes, 7 comments, 4 blanks, all 47 lines
[Summary](results.md) / [Details](details.md) / Diff Summary / [Diff Details](diff-details.md)
## Languages
| language | files | code | comment | blank | total |
| :--- | ---: | ---: | ---: | ---: | ---: |
| TypeScript JSX | 2 | 36 | 7 | 4 | 47 |
## Directories
| path | files | code | comment | blank | total |
| :--- | ---: | ---: | ---: | ---: | ---: |
| . | 2 | 36 | 7 | 4 | 47 |
| components | 2 | 36 | 7 | 4 | 47 |
[Summary](results.md) / [Details](details.md) / Diff Summary / [Diff Details](diff-details.md)

View File

@@ -0,0 +1,27 @@
Date : 2025-03-18 13:49:23
Directory : /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew
Total : 2 files, 36 codes, 7 comments, 4 blanks, all 47 lines
Languages
+----------------+------------+------------+------------+------------+------------+
| language | files | code | comment | blank | total |
+----------------+------------+------------+------------+------------+------------+
| TypeScript JSX | 2 | 36 | 7 | 4 | 47 |
+----------------+------------+------------+------------+------------+------------+
Directories
+---------------------------------------------------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+
| path | files | code | comment | blank | total |
+---------------------------------------------------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+
| . | 2 | 36 | 7 | 4 | 47 |
| components | 2 | 36 | 7 | 4 | 47 |
+---------------------------------------------------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+
Files
+---------------------------------------------------------------------------------------------------------------------------------+----------------+------------+------------+------------+------------+
| filename | language | code | comment | blank | total |
+---------------------------------------------------------------------------------------------------------------------------------+----------------+------------+------------+------------+------------+
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationCell.tsx | TypeScript JSX | 18 | 2 | 1 | 21 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationTable.tsx | TypeScript JSX | 18 | 5 | 3 | 26 |
| Total | | 36 | 7 | 4 | 47 |
+---------------------------------------------------------------------------------------------------------------------------------+----------------+------------+------------+------------+------------+

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,31 @@
# Summary
Date : 2025-03-18 13:49:23
Directory /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew
Total : 27 files, 6961 codes, 1254 comments, 1252 blanks, all 9467 lines
Summary / [Details](details.md) / [Diff Summary](diff.md) / [Diff Details](diff-details.md)
## Languages
| language | files | code | comment | blank | total |
| :--- | ---: | ---: | ---: | ---: | ---: |
| TypeScript JSX | 19 | 6,585 | 1,141 | 1,169 | 8,895 |
| TypeScript | 6 | 309 | 106 | 55 | 470 |
| Markdown | 1 | 39 | 0 | 19 | 58 |
| JavaScript | 1 | 28 | 7 | 9 | 44 |
## Directories
| path | files | code | comment | blank | total |
| :--- | ---: | ---: | ---: | ---: | ---: |
| . | 27 | 6,961 | 1,254 | 1,252 | 9,467 |
| . (Files) | 3 | 63 | 6 | 22 | 91 |
| components | 12 | 3,710 | 505 | 498 | 4,713 |
| components (Files) | 7 | 2,525 | 334 | 331 | 3,190 |
| components/cells | 5 | 1,185 | 171 | 167 | 1,523 |
| hooks | 6 | 2,855 | 630 | 669 | 4,154 |
| types | 1 | 16 | 4 | 4 | 24 |
| utils | 5 | 317 | 109 | 59 | 485 |
Summary / [Details](details.md) / [Diff Summary](diff.md) / [Diff Details](diff-details.md)

View File

@@ -0,0 +1,61 @@
Date : 2025-03-18 13:49:23
Directory : /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew
Total : 27 files, 6961 codes, 1254 comments, 1252 blanks, all 9467 lines
Languages
+----------------+------------+------------+------------+------------+------------+
| language | files | code | comment | blank | total |
+----------------+------------+------------+------------+------------+------------+
| TypeScript JSX | 19 | 6,585 | 1,141 | 1,169 | 8,895 |
| TypeScript | 6 | 309 | 106 | 55 | 470 |
| Markdown | 1 | 39 | 0 | 19 | 58 |
| JavaScript | 1 | 28 | 7 | 9 | 44 |
+----------------+------------+------------+------------+------------+------------+
Directories
+-------------------------------------------------------------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+
| path | files | code | comment | blank | total |
+-------------------------------------------------------------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+
| . | 27 | 6,961 | 1,254 | 1,252 | 9,467 |
| . (Files) | 3 | 63 | 6 | 22 | 91 |
| components | 12 | 3,710 | 505 | 498 | 4,713 |
| components (Files) | 7 | 2,525 | 334 | 331 | 3,190 |
| components/cells | 5 | 1,185 | 171 | 167 | 1,523 |
| hooks | 6 | 2,855 | 630 | 669 | 4,154 |
| types | 1 | 16 | 4 | 4 | 24 |
| utils | 5 | 317 | 109 | 59 | 485 |
+-------------------------------------------------------------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+
Files
+-------------------------------------------------------------------------------------------------------------------------------------------+----------------+------------+------------+------------+------------+
| filename | language | code | comment | blank | total |
+-------------------------------------------------------------------------------------------------------------------------------------------+----------------+------------+------------+------------+------------+
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/README.md | Markdown | 39 | 0 | 19 | 58 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/AiValidationDialogs.tsx | TypeScript JSX | 230 | 10 | 8 | 248 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/BaseCellContent.tsx | TypeScript JSX | 18 | 0 | 3 | 21 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/SearchableTemplateSelect.tsx | TypeScript JSX | 273 | 19 | 37 | 329 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/UpcValidationTableAdapter.tsx | TypeScript JSX | 113 | 17 | 10 | 140 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationCell.tsx | TypeScript JSX | 395 | 51 | 55 | 501 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationContainer.tsx | TypeScript JSX | 969 | 182 | 158 | 1,309 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationTable.tsx | TypeScript JSX | 527 | 55 | 60 | 642 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/CheckboxCell.tsx | TypeScript JSX | 112 | 12 | 21 | 145 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/InputCell.tsx | TypeScript JSX | 233 | 34 | 33 | 300 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultiSelectCell.tsx | TypeScript JSX | 420 | 66 | 59 | 545 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultilineInput.tsx | TypeScript JSX | 193 | 23 | 22 | 238 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/SelectCell.tsx | TypeScript JSX | 227 | 36 | 32 | 295 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useAiValidation.tsx | TypeScript JSX | 500 | 75 | 89 | 664 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useProductLinesFetching.tsx | TypeScript JSX | 264 | 75 | 81 | 420 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useTemplates.tsx | TypeScript JSX | 204 | 26 | 33 | 263 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useUpcValidation.tsx | TypeScript JSX | 337 | 88 | 92 | 517 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidation.tsx | TypeScript JSX | 360 | 78 | 85 | 523 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidationState.tsx | TypeScript JSX | 1,190 | 288 | 289 | 1,767 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/index.tsx | TypeScript JSX | 20 | 6 | 2 | 28 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/types.ts | TypeScript | 4 | 0 | 1 | 5 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/types/index.ts | TypeScript | 16 | 4 | 4 | 24 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/dataMutations.ts | TypeScript | 124 | 4 | 14 | 142 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/errorUtils.ts | TypeScript | 21 | 15 | 5 | 41 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/upcValidation.ts | TypeScript | 43 | 24 | 7 | 74 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/validation-helper.js | JavaScript | 28 | 7 | 9 | 44 |
| /Users/matt/Dev/inventory/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/utils/validationUtils.ts | TypeScript | 101 | 59 | 24 | 184 |
| Total | | 6,961 | 1,254 | 1,252 | 9,467 |
+-------------------------------------------------------------------------------------------------------------------------------------------+----------------+------------+------------+------------+------------+

View File

@@ -0,0 +1,354 @@
## 1. ✅ Error Filtering Logic Inconsistency (RESOLVED)
> **Note: This issue has been resolved by implementing a type-based error system.**
The filtering logic in `ValidationCell.tsx` previously relied on string matching, which was fragile:
```typescript
// Old implementation (string-based matching)
const filteredErrors = React.useMemo(() => {
return !isEmpty(value)
? errors.filter(error => !error.message?.toLowerCase().includes('required'))
: errors;
}, [value, errors]);
// New implementation (type-based filtering)
const filteredErrors = React.useMemo(() => {
return !isEmpty(value)
? errors.filter(error => error.type !== ErrorType.Required)
: errors;
}, [value, errors]);
```
The solution implemented:
- Added an `ErrorType` enum in `types.ts` to standardize error categorization
- Updated all error creation to include the appropriate error type
- Modified error filtering to use the type property instead of string matching
- Ensured consistent error handling across the application
**Guidelines for future development:**
- Always use the `ErrorType` enum when creating errors
- Never rely on string matching for error filtering
- Ensure all error objects include the `type` property
- Use the appropriate error type for each validation rule:
- `ErrorType.Required` for required field validations
- `ErrorType.Regex` for regex validations
- `ErrorType.Unique` for uniqueness validations
- `ErrorType.Custom` for custom validations
- `ErrorType.Api` for API-based validations
## 2. ⚠️ Redundant Error Processing (PARTIALLY RESOLVED)
> **Note: This issue has been partially resolved by the re-rendering optimizations.**
The system still processes errors in multiple places:
- In `ValidationCell.tsx`, errors are filtered by the optimized `processErrors` function
- In `useValidation.tsx`, errors are generated at the field level
- In `ValidationContainer.tsx`, errors are manipulated at the container level
While the error processing has been optimized to be more efficient, there is still some redundancy in how errors are handled across components. However, the current implementation has mitigated the performance impact.
**Improvements made:**
- Created a central `processErrors` function in ValidationCell that efficiently handles error filtering
- Implemented a batched update system to reduce redundant error processing
- Added better memoization to avoid reprocessing errors when not needed
**Future improvement opportunities:**
- Further consolidate error processing logic into a single location
- Create a dedicated error handling service or hook
- Implement a more declarative approach to error handling
## 3. Race Conditions in Async Validation
async validations could create race conditions:
- If a user types quickly, multiple validation requests might be in flight
- Later responses could overwrite more recent ones if they complete out of order
- The debouncing helps but doesn't fully solve this issue
## 4. Memory Leaks in Timeout Management
The validation timeouts are stored in refs:
```typescript
const validationTimeoutsRef = useRef<Record<number, NodeJS.Timeout>>({});
```
While there is cleanup on unmount, if rows are added/removed dynamically, timeouts for deleted rows might not be properly cleared.
## 5. ✅ Inefficient Error Storage (RESOLVED)
**Status: RESOLVED**
### Problem
Previously, validation errors were stored in multiple locations:
- In the `validationErrors` Map in `useValidationState`
- In the row data itself as `__errors`
This redundancy caused several issues:
- Inconsistent error states between the two storage locations
- Increased memory usage by storing the same information twice
- Complex state management to keep both sources in sync
- Difficulty reasoning about where errors should be accessed from
### Solution
We've implemented a unified error storage approach by:
- Making the `validationErrors` Map in `useValidationState` the single source of truth for all validation errors
- Removed the `__errors` property from row data
- Updated all validation functions to interact with the central error store instead of modifying row data
- Modified UPC validation to use the central error store
- Updated all components to read errors from the `validationErrors` Map instead of row data
### Key Changes
1. Modified `dataMutations.ts` to stop storing errors in row data
2. Updated the `Meta` type to remove the `__errors` property
3. Modified the `RowData` type to remove the `__errors` property
4. Updated the `useValidation` hook to return errors separately from row data
5. Modified the `useAiValidation` hook to work with the central error store
6. Updated the `useFilters` hook to check for errors in the `validationErrors` Map
7. Modified the `ValidationTable` and `ValidationCell` components to read errors from the `validationErrors` Map
### Benefits
- **Single Source of Truth**: All validation errors are now stored in one place
- **Reduced Memory Usage**: No duplicate storage of error information
- **Simplified State Management**: Only one state to update when errors change
- **Cleaner Data Structure**: Row data no longer contains validation metadata
- **More Maintainable Code**: Clearer separation of concerns between data and validation
### Future Improvements
While this refactoring addresses the core issue of inefficient error storage, there are still opportunities for further optimization:
1.**Redundant Error Processing**: ~~The validation process still performs some redundant calculations that could be optimized.~~ This has been largely addressed by the re-rendering optimizations.
2. **Race Conditions**: Async validation can lead to race conditions when multiple validations are triggered in quick succession.
3. **Memory Leaks**: The timeout management for validation could be improved to prevent potential memory leaks.
4. **Tight Coupling**: Components are still tightly coupled to the validation state structure.
5. **Error Prioritization**: The system doesn't prioritize errors well, showing all errors at once rather than focusing on the most critical ones first.
### Validation Flow
The validation process now works as follows:
1. **Error Generation**:
- Field-level validations generate errors based on validation rules
- Row-level hooks add custom validation errors
- Table-level validations (like uniqueness checks) add errors across rows
2. **Error Storage**:
- All errors are stored in the `validationErrors` Map in `useValidationState`
- The Map uses row indices as keys and objects of field errors as values
3. **Error Display**:
- The `ValidationTable` component checks the `validationErrors` Map to highlight rows with errors
- The `ValidationCell` component receives errors for specific fields from the `validationErrors` Map
- Errors are filtered in the UI to avoid showing "required" errors for fields with values
This focused refactoring approach has successfully addressed a critical issue while keeping changes manageable and targeted.
## 6. ✅ Excessive Re-rendering (RESOLVED)
**Status: RESOLVED**
### Problem
The validation system was suffering from excessive re-renders due to several key issues:
- **Inefficient Error Filtering**: The ValidationCell component was filtering errors on every render
- **Redundant Error Processing**: The same validation work was repeated in multiple components
- **Poor Memoization**: Components were inadequately memoized, causing unnecessary re-renders
- **Inefficient Batch Updates**: The state update system wasn't optimally batching changes
These issues led to performance problems, especially with large datasets, and affected the user experience.
### Solution
We've implemented a comprehensive optimization approach:
- **Optimized Error Processing**: Created an efficient `processErrors` function in ValidationCell that calculates all derived state in one pass
- **Enhanced Memoization**: Improved memo comparison functions to avoid unnecessary rerenders
- **Improved Batch Updates**: Redesigned the batching system to aggregate multiple changes before state updates
- **Single Update Pattern**: Implemented a queue-based update mechanism that applies multiple state changes at once
### Key Changes
1. Added a more efficient error processing function in ValidationCell
2. Created an enhanced error comparison function to properly compare error arrays
3. Improved the memo comparison function in ValidationCell
4. Added a batch update system in useValidationState
5. Implemented a queue-based update mechanism for row modifications
### Benefits
- **Improved Performance**: Reduced render cycles = faster UI response
- **Better User Experience**: Less lag when editing large datasets
- **Reduced Memory Usage**: Fewer component instantiations and temporary objects
- **Increased Scalability**: The application can now handle larger datasets without slowdown
- **Maintainable Code**: More predictable update flow that's easier to debug and extend
### Guidelines for future development
- Use the `processErrors` function for error filtering and processing
- Ensure React.memo components have proper comparison functions
- Use the batched update system for state changes
- Maintain stable references to objects and functions
- Use appropriate React hooks (useMemo, useCallback) with correct dependencies
- Avoid unnecessary recreations of arrays, objects, and functions
## 7. Complex Error Merging Logic
When merging errors from different sources, the logic is complex and potentially error-prone:
```typescript
// Merge field errors and row hook errors
const mergedErrors: Record<string, InfoWithSource> = {}
// Convert field errors to InfoWithSource
Object.entries(fieldErrors).forEach(([key, errors]) => {
if (errors.length > 0) {
mergedErrors[key] = {
message: errors[0].message,
level: errors[0].level,
source: ErrorSources.Row,
type: errors[0].type || ErrorType.Custom
}
}
})
```
This only takes the first error for each field, potentially hiding important validation issues.
## 8. ✅ Inconsistent Error Handling for Empty Values (PARTIALLY RESOLVED)
> **Note: This issue has been partially resolved by standardizing the isEmpty function and error type system.**
The system previously had different approaches to handling empty values:
- Some validations skipped empty values unless they're required
- Others processed empty values differently
- The `isEmpty` function was defined multiple times with slight variations
The solution implemented:
- Standardized the `isEmpty` function implementation
- Ensured consistent error type usage for required field validations
- Made error filtering consistent across the application
**Guidelines for future development:**
- Always use the shared `isEmpty` function for checking empty values
- Ensure consistent handling of empty values across all validation rules
- Use the `ErrorType.Required` type for all required field validations
## 9. Tight Coupling Between Components
The validation system is tightly coupled across components:
- `ValidationCell` needs to understand the structure of errors
- `ValidationTable` needs to extract and pass the right errors
- `ValidationContainer` directly manipulates the error structure
This makes it harder to refactor or reuse components independently.
## 10. Limited Error Prioritization
There's no clear prioritization of errors:
- When multiple errors exist for a field, which one should be shown first?
- Are some errors more important than others?
- The current system mostly shows the first error it finds
A more robust approach would be to have a consistent error source identification system and a clear prioritization strategy for displaying errors.
------------
Let me explain how these hooks fit together to create the validation errors that eventually get filtered in the `ValidationCell` component:
## The Validation Flow
1. **useValidationState Hook**:
This is the main state management hook used by the `ValidationContainer` component. It:
- Manages the core data state (`data`)
- Tracks validation errors in a Map (`validationErrors`)
- Provides functions to update and validate rows
2. **useValidation Hook**:
This is a utility hook that provides the core validation logic:
- `validateField`: Validates a single field against its validation rules
- `validateRow`: Validates an entire row, field by field
- `validateTable`: Runs table-level validations
- `validateUnique`: Checks for uniqueness constraints
- `validateData`: Orchestrates the complete validation process
## How Errors Are Generated
Validation errors come from multiple sources:
1. **Field-Level Validations**:
In `useValidation.tsx`, the `validateField` function checks individual fields against rules like:
- `required`: Field must have a value
- `regex`: Value must match a pattern
- `min`/`max`: Numeric constraints
2. **Row-Level Validations**:
The `validateRow` function in `useValidation.tsx` runs:
- Field validations for each field in the row
- Special validations for required fields like supplier and company
- Custom row hooks provided by the application
3. **Table-Level Validations**:
- `validateUnique` checks for duplicate values in fields marked as unique
- `validateTable` runs custom table hooks for cross-row validations
4. **API-Based Validations**:
In `useValidationState.tsx` and `ValidationContainer.tsx`:
- UPC validation via API calls
- Item number uniqueness checks
## The Error Flow
1. Errors are collected in the `validationErrors` Map in `useValidationState`
2. This Map is passed to `ValidationTable` as a prop
3. `ValidationTable` extracts the relevant errors for each cell and passes them to `ValidationCell`
4. In `ValidationCell`, the errors are filtered based on whether the cell has a value:
```typescript
// Updated implementation using type-based filtering
const filteredErrors = React.useMemo(() => {
return !isEmpty(value)
? errors.filter(error => error.type !== ErrorType.Required)
: errors;
}, [value, errors]);
```
## Key Insights
1. **Error Structure**:
Errors now have a consistent structure with type information:
```typescript
type ErrorObject = {
message: string;
level: string; // 'error', 'warning', etc.
source?: ErrorSources; // Where the error came from
type: ErrorType; // The type of error (Required, Regex, Unique, etc.)
}
```
2. **Error Sources**:
Errors can come from:
- Field validations (required, regex, etc.)
- Row validations (custom business logic)
- Table validations (uniqueness checks)
- API validations (UPC checks)
3. **Error Types**:
Errors are now categorized by type:
- `ErrorType.Required`: Field is required but empty
- `ErrorType.Regex`: Value doesn't match the regex pattern
- `ErrorType.Unique`: Value must be unique across rows
- `ErrorType.Custom`: Custom validation errors
- `ErrorType.Api`: Errors from API calls
4. **Error Filtering**:
The filtering in `ValidationCell` is now more robust:
- When a field has a value, errors of type `ErrorType.Required` are filtered out
- When a field is empty, all errors are shown
5. **Performance Optimizations**:
- Batch processing of validations
- Debounced updates to avoid excessive re-renders
- Memoization of computed values

View File

@@ -1188,7 +1188,6 @@ export const MatchColumnsStep = React.memo(<T extends string>({
})) as unknown as Fields<T> })) as unknown as Fields<T>
const unmatched = findUnmatchedRequiredFields(typedFields, columns); const unmatched = findUnmatchedRequiredFields(typedFields, columns);
console.log("Unmatched required fields:", unmatched);
return unmatched; return unmatched;
}, [fields, columns]) }, [fields, columns])
@@ -1200,7 +1199,6 @@ export const MatchColumnsStep = React.memo(<T extends string>({
// Type assertion to handle the DeepReadonly<T> vs string type mismatch // Type assertion to handle the DeepReadonly<T> vs string type mismatch
return !unmatchedRequiredFields.includes(key as any); return !unmatchedRequiredFields.includes(key as any);
}); });
console.log("Matched required fields:", matched);
return matched; return matched;
}, [requiredFields, unmatchedRequiredFields]); }, [requiredFields, unmatchedRequiredFields]);

View File

@@ -185,12 +185,10 @@ export const UploadFlow = ({ state, onNext, onBack }: Props) => {
// Apply global selections to each row of data if they exist // Apply global selections to each row of data if they exist
const dataWithGlobalSelections = globalSelections const dataWithGlobalSelections = globalSelections
? dataWithMeta.map((row: Data<string> & { __errors?: any; __index?: string }) => { ? dataWithMeta.map((row: Data<string> & { __index?: string }) => {
const newRow = { ...row }; const newRow = { ...row };
if (globalSelections.supplier) newRow.supplier = globalSelections.supplier; if (globalSelections.supplier) newRow.supplier = globalSelections.supplier;
if (globalSelections.company) newRow.company = globalSelections.company; if (globalSelections.company) newRow.company = globalSelections.company;
if (globalSelections.line) newRow.line = globalSelections.line;
if (globalSelections.subline) newRow.subline = globalSelections.subline;
return newRow; return newRow;
}) })
: dataWithMeta; : dataWithMeta;

View File

@@ -1,87 +0,0 @@
import React, { useState } from 'react'
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
interface SaveTemplateDialogProps {
isOpen: boolean
onClose: () => void
onSave: (company: string, productType: string) => void
}
const SaveTemplateDialog: React.FC<SaveTemplateDialogProps> = ({
isOpen,
onClose,
onSave,
}) => {
const [company, setCompany] = useState("")
const [productType, setProductType] = useState("")
return (
<Dialog open={isOpen} onOpenChange={(open) => {
if (!open) {
onClose()
setCompany("")
setProductType("")
}
}}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>Save as Template</DialogTitle>
<DialogDescription>
Enter the company and product type for this template.
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="space-y-2">
<label htmlFor="company" className="text-sm font-medium">
Company
</label>
<Input
id="company"
value={company}
onChange={(e) => setCompany(e.target.value)}
placeholder="Enter company name"
/>
</div>
<div className="space-y-2">
<label htmlFor="productType" className="text-sm font-medium">
Product Type
</label>
<Input
id="productType"
value={productType}
onChange={(e) => setProductType(e.target.value)}
placeholder="Enter product type"
/>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={onClose}>
Cancel
</Button>
<Button
onClick={() => {
onSave(company, productType)
onClose()
setCompany("")
setProductType("")
}}
disabled={!company || !productType}
>
Save Template
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}
export default SaveTemplateDialog

View File

@@ -1,212 +0,0 @@
import React, { useState } from 'react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog'
import {
Popover,
PopoverContent,
PopoverTrigger
} from '@/components/ui/popover'
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList
} from '@/components/ui/command'
import { ChevronsUpDown, Check } from 'lucide-react'
import { cn } from '@/lib/utils'
import { Template } from '../hooks/useValidationState'
import { toast } from 'sonner'
interface TemplateManagerProps {
templates: Template[]
selectedTemplateId: string | null
onSelectTemplate: (templateId: string | null) => void
onSaveTemplate: (name: string, type: string) => void
onApplyTemplate: (templateId: string) => void
showDialog: boolean
onCloseDialog: () => void
selectedCount: number
}
const TemplateManager: React.FC<TemplateManagerProps> = ({
templates,
selectedTemplateId,
onSelectTemplate,
onSaveTemplate,
onApplyTemplate,
showDialog,
onCloseDialog,
selectedCount,
}) => {
const [templateName, setTemplateName] = useState('')
const [templateType, setTemplateType] = useState('')
const [open, setOpen] = useState(false)
const [searchQuery, setSearchQuery] = useState('')
// Filter templates based on search
const filteredTemplates = searchQuery
? templates.filter(template =>
template.product_type.toLowerCase().includes(searchQuery.toLowerCase()) ||
template.company.toLowerCase().includes(searchQuery.toLowerCase())
)
: templates
const handleSaveTemplate = () => {
if (!templateName.trim()) {
toast.error('Please enter a template name')
return
}
if (!templateType.trim()) {
toast.error('Please select a template type')
return
}
onSaveTemplate(templateName, templateType)
setTemplateName('')
setTemplateType('')
}
// Get display text for template
const getTemplateDisplayText = (template: Template) => {
return `${template.product_type} - ${template.company}`
}
// Find the currently selected template
const selectedTemplate = templates.find(t => t.id.toString() === selectedTemplateId)
return (
<>
<div className="space-y-4">
<div className="flex flex-col space-y-2">
<label className="text-sm font-medium">Template</label>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className="justify-between w-full"
>
{selectedTemplate
? getTemplateDisplayText(selectedTemplate)
: "Select a template"}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="p-0 w-[350px]">
<Command>
<CommandInput
placeholder="Search templates..."
value={searchQuery}
onValueChange={setSearchQuery}
/>
<CommandList>
<CommandEmpty>No templates found.</CommandEmpty>
<CommandGroup>
{filteredTemplates.map((template) => (
<CommandItem
key={template.id}
value={template.id.toString()}
onSelect={(value) => {
onSelectTemplate(value)
setOpen(false)
setSearchQuery('')
}}
>
<Check
className={cn(
"mr-2 h-4 w-4",
selectedTemplateId === template.id.toString()
? "opacity-100"
: "opacity-0"
)}
/>
{getTemplateDisplayText(template)}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
</div>
<div className="flex space-x-2">
<Button
variant="outline"
onClick={() => selectedTemplateId && onApplyTemplate(selectedTemplateId)}
disabled={!selectedTemplateId || selectedCount === 0}
className="flex-1"
>
Apply Template
</Button>
<Button
variant="outline"
onClick={onCloseDialog}
disabled={selectedCount === 0}
className="flex-1"
>
Save as Template
</Button>
</div>
</div>
{/* Template Save Dialog */}
<Dialog open={showDialog} onOpenChange={onCloseDialog}>
<DialogContent>
<DialogHeader>
<DialogTitle>Save Template</DialogTitle>
<DialogDescription>
Create a template from the selected row.
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="flex flex-col space-y-2">
<label className="text-sm font-medium">Company</label>
<Input
value={templateName}
onChange={(e) => setTemplateName(e.target.value)}
placeholder="Enter company name"
/>
</div>
<div className="flex flex-col space-y-2">
<label className="text-sm font-medium">Product Type</label>
<Input
value={templateType}
onChange={(e) => setTemplateType(e.target.value)}
placeholder="Enter product type"
/>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={onCloseDialog}>
Cancel
</Button>
<Button
onClick={handleSaveTemplate}
disabled={!templateName.trim() || !templateType.trim()}
>
Save Template
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</>
)
}
export default TemplateManager

View File

@@ -0,0 +1,140 @@
import React, { useMemo } from 'react'
import ValidationTable from './ValidationTable'
import { RowSelectionState } from '@tanstack/react-table'
import { Fields } from '../../../types'
interface UpcValidationTableAdapterProps<T extends string> {
data: any[]
fields: Fields<string>
validationErrors: Map<number, Record<string, any[]>>
rowSelection: RowSelectionState
setRowSelection: (value: RowSelectionState) => void
updateRow: (rowIndex: number, key: T, value: any) => void
filters: any
templates: any[]
applyTemplate: (templateId: string, rowIndexes: number[]) => void
getTemplateDisplayText: (templateId: string) => string
isValidatingUpc: (rowIndex: number) => boolean
validatingUpcRows: number[]
copyDown: (rowIndex: number, fieldKey: string, endRowIndex?: number) => void
validatingCells: Set<string>
isLoadingTemplates: boolean
rowProductLines: Record<string, any[]>
rowSublines: Record<string, any[]>
isLoadingLines: Record<string, boolean>
isLoadingSublines: Record<string, boolean>
upcValidation: {
validatingRows: Set<number>
getItemNumber: (rowIndex: number) => string | undefined
}
}
/**
* UpcValidationTableAdapter component - connects UPC validation data to ValidationTable
*
* This component adapts UPC validation data and functionality to work with the core ValidationTable,
* transforming item numbers and validation states into a format the table component can render.
*/
function UpcValidationTableAdapter<T extends string>({
data,
fields,
validationErrors,
rowSelection,
setRowSelection,
updateRow,
filters,
templates,
applyTemplate,
getTemplateDisplayText,
isValidatingUpc,
validatingUpcRows,
copyDown,
validatingCells: externalValidatingCells,
isLoadingTemplates,
rowProductLines,
rowSublines,
isLoadingLines,
isLoadingSublines,
upcValidation
}: UpcValidationTableAdapterProps<T>) {
// Prepare the validation table with UPC data
const AdaptedTable = useMemo(() => React.memo((props: React.ComponentProps<typeof ValidationTable>) => {
// Create validatingCells set from validating rows, but only for item_number fields
// This ensures only the item_number column shows loading state during UPC validation
const combinedValidatingCells = new Set<string>();
// Add UPC validation cells
upcValidation.validatingRows.forEach(rowIndex => {
// Only mark the item_number cells as validating, NOT the UPC or supplier
combinedValidatingCells.add(`${rowIndex}-item_number`);
});
// Add any other validating cells from state
externalValidatingCells.forEach(cellKey => {
combinedValidatingCells.add(cellKey);
});
// Convert the Map to the expected format for the ValidationTable
// Create a new Map from the item numbers to ensure proper typing
const itemNumbersMap = new Map<number, string>();
// Merge the item numbers with the data for display purposes only
const enhancedData = props.data.map((row: any, index: number) => {
const itemNumber = upcValidation.getItemNumber(index);
if (itemNumber) {
// Add to our map for proper prop passing
itemNumbersMap.set(index, itemNumber);
return {
...row,
item_number: itemNumber
};
}
return row;
});
return (
<ValidationTable
{...props}
data={enhancedData}
validatingCells={combinedValidatingCells}
itemNumbers={itemNumbersMap}
isLoadingTemplates={isLoadingTemplates}
copyDown={copyDown}
rowProductLines={rowProductLines}
rowSublines={rowSublines}
isLoadingLines={isLoadingLines}
isLoadingSublines={isLoadingSublines}
/>
);
}), [upcValidation.validatingRows, upcValidation.getItemNumber, isLoadingTemplates, copyDown, externalValidatingCells, rowProductLines, rowSublines, isLoadingLines, isLoadingSublines]);
// Render the validation table with the provided props and UPC data
return (
<AdaptedTable
data={data}
fields={fields}
rowSelection={rowSelection}
setRowSelection={setRowSelection}
updateRow={updateRow as unknown as (rowIndex: number, key: string, value: any) => void}
validationErrors={validationErrors}
isValidatingUpc={isValidatingUpc}
validatingUpcRows={validatingUpcRows}
filters={filters}
templates={templates}
applyTemplate={applyTemplate}
getTemplateDisplayText={getTemplateDisplayText}
validatingCells={new Set()}
itemNumbers={new Map()}
isLoadingTemplates={isLoadingTemplates}
copyDown={copyDown}
upcValidationResults={new Map()}
rowProductLines={rowProductLines}
rowSublines={rowSublines}
isLoadingLines={isLoadingLines}
isLoadingSublines={isLoadingSublines}
/>
)
}
export default UpcValidationTableAdapter

View File

@@ -1,6 +1,6 @@
import React from 'react' import React from 'react'
import { Field } from '../../../types' import { Field, ErrorType } from '../../../types'
import { Loader2, AlertCircle, ArrowDown, Check, X } from 'lucide-react' import { Loader2, AlertCircle, ArrowDown, X } from 'lucide-react'
import { import {
Tooltip, Tooltip,
TooltipContent, TooltipContent,
@@ -40,6 +40,7 @@ type ErrorObject = {
message: string; message: string;
level: string; level: string;
source?: string; source?: string;
type?: ErrorType;
} }
// Helper function to check if a value is empty - utility function shared by all components // Helper function to check if a value is empty - utility function shared by all components
@@ -75,7 +76,8 @@ const BaseCellContent = React.memo(({
onChange, onChange,
hasErrors, hasErrors,
options = [], options = [],
className = '' className = '',
fieldKey = ''
}: { }: {
field: Field<string>; field: Field<string>;
value: any; value: any;
@@ -83,11 +85,14 @@ const BaseCellContent = React.memo(({
hasErrors: boolean; hasErrors: boolean;
options?: readonly any[]; options?: readonly any[];
className?: string; className?: string;
fieldKey?: string;
}) => { }) => {
// Get field type information // Get field type information
const fieldType = typeof field.fieldType === 'string' const fieldType = fieldKey === 'line' || fieldKey === 'subline'
? field.fieldType ? 'select'
: field.fieldType?.type || 'input'; : typeof field.fieldType === 'string'
? field.fieldType
: field.fieldType?.type || 'input';
// Check for multiline input // Check for multiline input
const isMultiline = typeof field.fieldType === 'object' && const isMultiline = typeof field.fieldType === 'object' &&
@@ -99,7 +104,21 @@ const BaseCellContent = React.memo(({
(field.fieldType.type === 'input' || field.fieldType.type === 'multi-input') && (field.fieldType.type === 'input' || field.fieldType.type === 'multi-input') &&
field.fieldType.price === true; field.fieldType.price === true;
console.log(`BaseCellContent: field.key=${field.key}, fieldType=${fieldType}, disabled=${field.disabled}, options=`, options); // Special case for line and subline - check this first, before any other field type checks
if (fieldKey === 'line' || fieldKey === 'subline') {
// Force these fields to always use SelectCell regardless of fieldType
return (
<SelectCell
field={{...field, fieldType: { type: 'select', options }}}
value={value}
onChange={onChange}
options={options}
hasErrors={hasErrors}
className={className}
disabled={field.disabled}
/>
);
}
if (fieldType === 'select') { if (fieldType === 'select') {
return ( return (
@@ -174,20 +193,17 @@ export interface ValidationCellProps {
} }
// Add efficient error message extraction function // Add efficient error message extraction function
const getErrorMessage = (error: ErrorObject): string => error.message;
// Add a utility function to process errors with appropriate caching // Highly optimized error processing function with fast paths for common cases
function processErrors(value: any, errors: ErrorObject[]): { function processErrors(value: any, errors: ErrorObject[]): {
filteredErrors: ErrorObject[];
hasError: boolean; hasError: boolean;
isRequiredButEmpty: boolean; isRequiredButEmpty: boolean;
shouldShowErrorIcon: boolean; shouldShowErrorIcon: boolean;
errorMessages: string; errorMessages: string;
} { } {
// Fast path - if no errors, return immediately // Fast path - if no errors or empty error array, return immediately
if (!errors || errors.length === 0) { if (!errors || errors.length === 0) {
return { return {
filteredErrors: [],
hasError: false, hasError: false,
isRequiredButEmpty: false, isRequiredButEmpty: false,
shouldShowErrorIcon: false, shouldShowErrorIcon: false,
@@ -195,53 +211,37 @@ function processErrors(value: any, errors: ErrorObject[]): {
}; };
} }
// Check if value is empty - using local function for speed // Use the shared isEmpty function for value checking
const valueIsEmpty = value === undefined || const valueIsEmpty = isEmpty(value);
value === null ||
value === '' ||
(Array.isArray(value) && value.length === 0) ||
(typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length === 0);
// If not empty, filter out required errors // Fast path for the most common case - required field with empty value
// Create a new array only if we need to filter (avoid unnecessary allocations) if (valueIsEmpty && errors.length === 1 && errors[0].type === ErrorType.Required) {
let filteredErrors: ErrorObject[]; return {
let hasRequiredError = false; hasError: true,
isRequiredButEmpty: true,
if (valueIsEmpty) { shouldShowErrorIcon: false,
// For empty values, check if there are required errors errorMessages: ''
hasRequiredError = errors.some(error => };
error.message?.toLowerCase().includes('required')
);
filteredErrors = errors;
} else {
// For non-empty values, filter out required errors
filteredErrors = errors.filter(error =>
!error.message?.toLowerCase().includes('required')
);
} }
// Determine if any actual errors exist after filtering // For non-empty values with errors, we need to show error icons
const hasError = filteredErrors.some(error => const hasError = errors.some(error => error.level === 'error' || error.level === 'warning');
error.level === 'error' || error.level === 'warning'
);
// Check if field is required but empty // For empty values with required errors, show only a border
const isRequiredButEmpty = valueIsEmpty && hasRequiredError; const isRequiredButEmpty = valueIsEmpty && errors.some(error => error.type === ErrorType.Required);
// Only show error icons for non-empty fields with actual errors // Show error icons for non-empty fields with errors, or for empty fields with non-required errors
const shouldShowErrorIcon = hasError && !valueIsEmpty; const shouldShowErrorIcon = hasError && (!valueIsEmpty || !errors.every(error => error.type === ErrorType.Required));
// Get error messages for the tooltip - only if we need to show icon // Only compute error messages if we're going to show an icon
let errorMessages = ''; const errorMessages = shouldShowErrorIcon
if (shouldShowErrorIcon) { ? errors
errorMessages = filteredErrors .filter(e => e.level === 'error' || e.level === 'warning')
.filter(e => e.level === 'error' || e.level === 'warning') .map(e => e.message)
.map(getErrorMessage) .join('\n')
.join('\n'); : '';
}
return { return {
filteredErrors,
hasError, hasError,
isRequiredButEmpty, isRequiredButEmpty,
shouldShowErrorIcon, shouldShowErrorIcon,
@@ -249,190 +249,33 @@ function processErrors(value: any, errors: ErrorObject[]): {
}; };
} }
const ItemNumberCell = React.memo(({ // Helper function to compare error arrays efficiently with a hash-based approach
value, function compareErrorArrays(prevErrors: ErrorObject[], nextErrors: ErrorObject[]): boolean {
itemNumber, // Fast path for referential equality
isValidating, if (prevErrors === nextErrors) return true;
width,
errors = [],
field,
onChange,
copyDown,
rowIndex,
totalRows = 0
}: {
value: any,
itemNumber?: string,
isValidating?: boolean,
width: number,
errors?: ErrorObject[],
field: Field<string>,
onChange: (value: any) => void,
copyDown?: (endRowIndex?: number) => void,
rowIndex: number,
totalRows?: number
}) => {
// If we have a value or itemNumber, ignore "required" errors
const displayValue = itemNumber || value;
// Use the utility function to process errors once // Fast path for length check
const { if (!prevErrors || !nextErrors) return prevErrors === nextErrors;
hasError, if (prevErrors.length !== nextErrors.length) return false;
isRequiredButEmpty,
shouldShowErrorIcon,
errorMessages
} = React.useMemo(() =>
processErrors(displayValue, errors),
[displayValue, errors]
);
// Add state for hover on copy down button // Generate simple hash from error properties
const [isCopyDownHovered, setIsCopyDownHovered] = React.useState(false); const getErrorHash = (error: ErrorObject): string => {
// Add state for hover on target row return `${error.message}|${error.level}|${error.type || ''}`;
const [isTargetRowHovered, setIsTargetRowHovered] = React.useState(false);
// Get copy down context
const copyDownContext = React.useContext(CopyDownContext);
// Handle copy down button click
const handleCopyDownClick = () => {
if (copyDown && totalRows > rowIndex + 1) {
// Enter copy down mode
copyDownContext.setIsInCopyDownMode(true);
copyDownContext.setSourceRowIndex(rowIndex);
copyDownContext.setSourceFieldKey('item_number');
}
}; };
// Check if this cell is the source of the current copy down operation // Compare using hashes
const isSourceCell = copyDownContext.isInCopyDownMode && const prevHashes = prevErrors.map(getErrorHash);
copyDownContext.sourceRowIndex === rowIndex && const nextHashes = nextErrors.map(getErrorHash);
copyDownContext.sourceFieldKey === 'item_number';
// Check if this cell is in a row that can be a target for copy down // Sort hashes to ensure consistent order
const isInTargetRow = copyDownContext.isInCopyDownMode && prevHashes.sort();
copyDownContext.sourceFieldKey === 'item_number' && nextHashes.sort();
rowIndex > (copyDownContext.sourceRowIndex || 0);
// Check if this row is the currently selected target row // Compare sorted hash arrays
const isSelectedTarget = isInTargetRow && rowIndex <= (copyDownContext.targetRowIndex || 0); return prevHashes.join(',') === nextHashes.join(',');
}
// Handle click on a potential target cell const ValidationCell = React.memo(({
const handleTargetCellClick = () => {
if (isInTargetRow && copyDownContext.sourceRowIndex !== null) {
copyDownContext.handleCopyDownComplete(
copyDownContext.sourceRowIndex,
'item_number',
rowIndex
);
}
};
//item_number fields
return (
<TableCell
className="p-1 group relative"
style={{
width: `${width}px`,
minWidth: `${width}px`,
maxWidth: `${width}px`,
boxSizing: 'border-box',
cursor: isInTargetRow ? 'pointer' : undefined,
...(isSourceCell ? { backgroundColor: '#dbeafe', borderRadius: '0.375rem', padding: 0, boxShadow: '0 0 0 2px #3b82f6' } :
isSelectedTarget ? { backgroundColor: '#bfdbfe', borderRadius: '0.375rem', padding: 0 } :
isInTargetRow && isTargetRowHovered ? { backgroundColor: '#dbeafe', borderRadius: '0.375rem', padding: 0, cursor: 'pointer' } :
isInTargetRow ? { borderRadius: '0.375rem', padding: 0, cursor: 'pointer' } : {})
}}
onClick={isInTargetRow ? handleTargetCellClick : undefined}
onMouseEnter={isInTargetRow ? () => setIsTargetRowHovered(true) : undefined}
onMouseLeave={isInTargetRow ? () => setIsTargetRowHovered(false) : undefined}
>
<div className={`relative ${hasError || isRequiredButEmpty ? 'border-red-500' : ''}`}>
{shouldShowErrorIcon && !isInTargetRow && (
<div className="absolute right-1.5 top-1/2 -translate-y-1/2 z-20">
<ValidationIcon error={{
message: errorMessages,
level: 'error'
}} />
</div>
)}
{!shouldShowErrorIcon && copyDown && !isEmpty(displayValue) && !copyDownContext.isInCopyDownMode && (
<div className="absolute right-1.5 top-1/2 -translate-y-1/2 z-20 opacity-0 group-hover:opacity-100 transition-opacity">
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<button
onClick={handleCopyDownClick}
onMouseEnter={() => setIsCopyDownHovered(true)}
onMouseLeave={() => setIsCopyDownHovered(false)}
className="p-1 rounded-full hover:bg-blue-100 text-blue-500/70 hover:text-blue-600 transition-colors"
aria-label="Copy value to rows below"
>
<ArrowDown className="h-3.5 w-3.5" />
</button>
</TooltipTrigger>
<TooltipContent side="right">
<div className="flex flex-col">
<p className="font-medium">Copy value to rows below</p>
</div>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
)}
{isSourceCell && (
<div className="absolute right-1.5 top-1/2 -translate-y-1/2 z-20">
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<button
onClick={() => copyDownContext.setIsInCopyDownMode(false)}
className="p-1 rounded-full bg-blue-100 text-blue-600 hover:bg-blue-200 transition-colors"
aria-label="Cancel copy down"
>
<X className="h-3.5 w-3.5" />
</button>
</TooltipTrigger>
<TooltipContent side="right">
<p>Cancel copy down</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
)}
{isValidating ? (
<div className={`flex items-center justify-center gap-2 border ${hasError || isRequiredButEmpty ? 'border-red-500' : 'border-input'} rounded-sm px-2 py-1.5`}>
<Loader2 className="h-4 w-4 animate-spin text-blue-500" />
<span>Loading...</span>
</div>
) : (
<div className={`truncate overflow-hidden ${isCopyDownHovered && !copyDownContext.isInCopyDownMode ? 'bg-blue-50/50' : ''}`}>
<BaseCellContent
field={field}
value={displayValue}
onChange={onChange}
hasErrors={hasError || isRequiredButEmpty}
options={(field.fieldType && typeof field.fieldType === 'object' && (field.fieldType as any).options) || []}
className={isSourceCell || isSelectedTarget || isInTargetRow ? `${
isSourceCell ? '!bg-blue-100 !border-blue-500 !rounded-md' :
isSelectedTarget ? '!bg-blue-200 !border-blue-200 !rounded-md' :
isInTargetRow ? 'hover:!bg-blue-100 !border-blue-200 !rounded-md' : ''
}` : ''}
/>
</div>
)}
</div>
</TableCell>
);
}, (prev, next) => (
prev.value === next.value &&
prev.itemNumber === next.itemNumber &&
prev.isValidating === next.isValidating &&
compareErrorArrays(prev.errors || [], next.errors || [])
));
ItemNumberCell.displayName = 'ItemNumberCell';
const ValidationCell = ({
field, field,
value, value,
onChange, onChange,
@@ -444,74 +287,44 @@ const ValidationCell = ({
width, width,
copyDown, copyDown,
rowIndex, rowIndex,
totalRows = 0}: ValidationCellProps) => { totalRows = 0
}: ValidationCellProps) => {
// Use the CopyDown context
const copyDownContext = React.useContext(CopyDownContext);
// Display value prioritizes itemNumber if available (for item_number fields)
const displayValue = fieldKey === 'item_number' && itemNumber ? itemNumber : value;
// Use the optimized processErrors function to avoid redundant filtering
const {
hasError,
isRequiredButEmpty,
shouldShowErrorIcon,
errorMessages
} = React.useMemo(() => processErrors(displayValue, errors), [displayValue, errors]);
// Track whether this cell is the source of a copy-down operation
const isSourceCell = copyDownContext.isInCopyDownMode &&
rowIndex === copyDownContext.sourceRowIndex &&
fieldKey === copyDownContext.sourceFieldKey;
// Add state for hover on copy down button // Add state for hover on copy down button
const [isCopyDownHovered, setIsCopyDownHovered] = React.useState(false); const [isCopyDownHovered, setIsCopyDownHovered] = React.useState(false);
// Add state for hover on target row // Add state for hover on target row
const [isTargetRowHovered, setIsTargetRowHovered] = React.useState(false); const [isTargetRowHovered, setIsTargetRowHovered] = React.useState(false);
// Get copy down context // Force isValidating to be a boolean
const copyDownContext = React.useContext(CopyDownContext); const isLoading = isValidating === true;
// For item_number fields, use the specialized component
if (fieldKey === 'item_number') {
return (
<ItemNumberCell
value={value}
itemNumber={itemNumber}
isValidating={isValidating}
width={width}
errors={errors}
field={field}
onChange={onChange}
copyDown={copyDown}
rowIndex={rowIndex}
totalRows={totalRows}
/>
);
}
// Memoize filtered errors to avoid recalculation on every render
const filteredErrors = React.useMemo(() => {
return !isEmpty(value)
? errors.filter(error => !error.message?.toLowerCase().includes('required'))
: errors;
}, [value, errors]);
// Memoize error state derivations
const { hasError, isRequiredButEmpty, shouldShowErrorIcon, errorMessages } = React.useMemo(() => {
// Determine if the field has an error after filtering
const hasError = filteredErrors.some(error => error.level === 'error' || error.level === 'warning');
// Determine if the field is required but empty
const isRequiredButEmpty = isEmpty(value) &&
errors.some(error => error.message?.toLowerCase().includes('required'));
// Only show error icons for non-empty fields with actual errors (not just required errors)
const shouldShowErrorIcon = hasError && !isEmpty(value);
// Get error messages for the tooltip
const errorMessages = shouldShowErrorIcon
? filteredErrors.filter(e => e.level === 'error' || e.level === 'warning').map(e => e.message).join('\n')
: '';
return { hasError, isRequiredButEmpty, shouldShowErrorIcon, errorMessages };
}, [filteredErrors, value, errors]);
// Handle copy down button click // Handle copy down button click
const handleCopyDownClick = () => { const handleCopyDownClick = React.useCallback(() => {
if (copyDown && totalRows > rowIndex + 1) { if (copyDown && totalRows > rowIndex + 1) {
// Enter copy down mode // Enter copy down mode
copyDownContext.setIsInCopyDownMode(true); copyDownContext.setIsInCopyDownMode(true);
copyDownContext.setSourceRowIndex(rowIndex); copyDownContext.setSourceRowIndex(rowIndex);
copyDownContext.setSourceFieldKey(fieldKey); copyDownContext.setSourceFieldKey(fieldKey);
} }
}; }, [copyDown, copyDownContext, fieldKey, rowIndex, totalRows]);
// Check if this cell is the source of the current copy down operation
const isSourceCell = copyDownContext.isInCopyDownMode &&
copyDownContext.sourceRowIndex === rowIndex &&
copyDownContext.sourceFieldKey === fieldKey;
// Check if this cell is in a row that can be a target for copy down // Check if this cell is in a row that can be a target for copy down
const isInTargetRow = copyDownContext.isInCopyDownMode && const isInTargetRow = copyDownContext.isInCopyDownMode &&
@@ -522,7 +335,7 @@ const ValidationCell = ({
const isSelectedTarget = isInTargetRow && rowIndex <= (copyDownContext.targetRowIndex || 0); const isSelectedTarget = isInTargetRow && rowIndex <= (copyDownContext.targetRowIndex || 0);
// Handle click on a potential target cell // Handle click on a potential target cell
const handleTargetCellClick = () => { const handleTargetCellClick = React.useCallback(() => {
if (isInTargetRow && copyDownContext.sourceRowIndex !== null && copyDownContext.sourceFieldKey !== null) { if (isInTargetRow && copyDownContext.sourceRowIndex !== null && copyDownContext.sourceFieldKey !== null) {
copyDownContext.handleCopyDownComplete( copyDownContext.handleCopyDownComplete(
copyDownContext.sourceRowIndex, copyDownContext.sourceRowIndex,
@@ -530,22 +343,35 @@ const ValidationCell = ({
rowIndex rowIndex
); );
} }
}; }, [copyDownContext, isInTargetRow, rowIndex]);
//normal selects, normal inputs, not item_number or multi-select
// Memoize the cell style objects to avoid recreating them on every render
const cellStyle = React.useMemo(() => ({
width: `${width}px`,
minWidth: `${width}px`,
maxWidth: `${width}px`,
boxSizing: 'border-box' as const,
cursor: isInTargetRow ? 'pointer' : undefined,
...(isSourceCell ? { backgroundColor: '#dbeafe', borderRadius: '0.375rem', padding: 0, boxShadow: '0 0 0 2px #3b82f6' } :
isSelectedTarget ? { backgroundColor: '#bfdbfe', borderRadius: '0.375rem', padding: 0 } :
isInTargetRow && isTargetRowHovered ? { backgroundColor: '#dbeafe', borderRadius: '0.375rem', padding: 0, cursor: 'pointer' } :
isInTargetRow ? { borderRadius: '0.375rem', padding: 0, cursor: 'pointer' } : {})
}), [width, isInTargetRow, isSourceCell, isSelectedTarget, isTargetRowHovered]);
// Memoize the cell class name to prevent re-calculating on every render
const cellClassName = React.useMemo(() => {
if (isSourceCell || isSelectedTarget || isInTargetRow) {
return isSourceCell ? '!bg-blue-100 !border-blue-500 !rounded-md' :
isSelectedTarget ? '!bg-blue-200 !border-blue-200 !rounded-md' :
isInTargetRow ? 'hover:!bg-blue-100 !border-blue-200 !rounded-md' : '';
}
return '';
}, [isSourceCell, isSelectedTarget, isInTargetRow]);
return ( return (
<TableCell <TableCell
className="p-1 group relative" className="p-1 group relative"
style={{ style={cellStyle}
width: `${width}px`,
minWidth: `${width}px`,
maxWidth: `${width}px`,
boxSizing: 'border-box',
cursor: isInTargetRow ? 'pointer' : undefined,
...(isSourceCell ? { backgroundColor: '#dbeafe', borderRadius: '0.375rem', padding: 0, boxShadow: '0 0 0 2px #3b82f6' } :
isSelectedTarget ? { backgroundColor: '#bfdbfe', borderRadius: '0.375rem', padding: 0 } :
isInTargetRow && isTargetRowHovered ? { backgroundColor: '#dbeafe', borderRadius: '0.375rem', padding: 0, cursor: 'pointer' } :
isInTargetRow ? { borderRadius: '0.375rem', padding: 0, cursor: 'pointer' } : {})
}}
onClick={isInTargetRow ? handleTargetCellClick : undefined} onClick={isInTargetRow ? handleTargetCellClick : undefined}
onMouseEnter={isInTargetRow ? () => setIsTargetRowHovered(true) : undefined} onMouseEnter={isInTargetRow ? () => setIsTargetRowHovered(true) : undefined}
onMouseLeave={isInTargetRow ? () => setIsTargetRowHovered(false) : undefined} onMouseLeave={isInTargetRow ? () => setIsTargetRowHovered(false) : undefined}
@@ -555,11 +381,12 @@ const ValidationCell = ({
<div className="absolute right-1.5 top-1/2 -translate-y-1/2 z-20"> <div className="absolute right-1.5 top-1/2 -translate-y-1/2 z-20">
<ValidationIcon error={{ <ValidationIcon error={{
message: errorMessages, message: errorMessages,
level: 'error' level: 'error',
type: ErrorType.Custom
}} /> }} />
</div> </div>
)} )}
{!shouldShowErrorIcon && copyDown && !isEmpty(value) && !copyDownContext.isInCopyDownMode && ( {!shouldShowErrorIcon && copyDown && !isEmpty(displayValue) && !copyDownContext.isInCopyDownMode && (
<div className="absolute right-0.5 top-1/2 -translate-y-1/2 z-20 opacity-0 group-hover:opacity-100 transition-opacity"> <div className="absolute right-0.5 top-1/2 -translate-y-1/2 z-20 opacity-0 group-hover:opacity-100 transition-opacity">
<TooltipProvider> <TooltipProvider>
<Tooltip> <Tooltip>
@@ -603,90 +430,72 @@ const ValidationCell = ({
</TooltipProvider> </TooltipProvider>
</div> </div>
)} )}
{isValidating ? ( {isLoading ? (
<div className={`flex items-center justify-center gap-2 border ${hasError || isRequiredButEmpty ? 'border-red-500' : 'border-input'} rounded-sm px-2 py-1.5`}> <div className={`flex items-center justify-center gap-2 border ${hasError || isRequiredButEmpty ? 'border-red-500' : 'border-input'} rounded-sm px-2 py-1.5`}>
<Loader2 className="h-4 w-4 animate-spin text-blue-500" /> <Loader2 className="h-4 w-4 animate-spin text-blue-500" />
<span>Loading...</span> <span>Loading...</span>
</div> </div>
) : ( ) : (
<div className={`truncate overflow-hidden ${isCopyDownHovered && !copyDownContext.isInCopyDownMode ? 'bg-blue-50/50' : ''}`}> <div className={`truncate overflow-hidden ${isCopyDownHovered && !copyDownContext.isInCopyDownMode ? 'bg-blue-50/50' : ''}`}>
{/* Log options for debugging */}
{(() => { console.log(`ValidationCell: fieldKey=${fieldKey}, options=`, options); return null; })()}
<BaseCellContent <BaseCellContent
field={field} field={field}
value={value} value={displayValue}
onChange={onChange} onChange={onChange}
hasErrors={hasError || isRequiredButEmpty} hasErrors={hasError || isRequiredButEmpty}
options={options} options={options}
className={isSourceCell || isSelectedTarget || isInTargetRow ? `${ className={cellClassName}
isSourceCell ? '!bg-blue-100 !border-blue-500 !rounded-md' : fieldKey={fieldKey}
isSelectedTarget ? '!bg-blue-200 !border-blue-200 !rounded-md' :
isInTargetRow ? 'hover:!bg-blue-100 !border-blue-200 !rounded-md' : ''
}` : ''}
/> />
</div> </div>
)} )}
</div> </div>
</TableCell> </TableCell>
); );
}; }, (prevProps, nextProps) => {
// Fast path: if all props are the same object
if (prevProps === nextProps) return true;
export default React.memo(ValidationCell, (prev, next) => { // Optimize the memo comparison function, checking most impactful props first
// For validating cells, always re-render // Check isValidating first as it's most likely to change frequently
if (prev.isValidating !== next.isValidating) { if (prevProps.isValidating !== nextProps.isValidating) return false;
return false;
// Then check value changes
if (prevProps.value !== nextProps.value) return false;
// Item number is related to validation state
if (prevProps.itemNumber !== nextProps.itemNumber) return false;
// Check errors with our optimized comparison function
if (!compareErrorArrays(prevProps.errors, nextProps.errors)) return false;
// Check field identity
if (prevProps.field !== nextProps.field) return false;
// Shallow options comparison - only if field type is select or multi-select
if (prevProps.field.fieldType?.type === 'select' || prevProps.field.fieldType?.type === 'multi-select') {
const optionsEqual = prevProps.options === nextProps.options ||
(Array.isArray(prevProps.options) &&
Array.isArray(nextProps.options) &&
prevProps.options.length === nextProps.options.length &&
prevProps.options.every((opt, idx) => {
const nextOptions = nextProps.options || [];
return opt === nextOptions[idx];
}));
if (!optionsEqual) return false;
} }
// Quick reference equality checks first for better performance // Check copy down context changes
if (prev.value !== next.value || prev.width !== next.width) { const copyDownContextChanged =
return false; prevProps.rowIndex !== nextProps.rowIndex ||
} prevProps.fieldKey !== nextProps.fieldKey;
// Check for error arrays equality - avoid JSON.stringify if (copyDownContextChanged) return false;
const errorsEqual = compareErrorArrays(prev.errors || [], next.errors || []);
if (!errorsEqual) return false;
// Check options only when needed // All essential props are the same - we can skip re-rendering
if (prev.field.fieldType.type === 'select' || prev.field.fieldType.type === 'multi-select') {
if (prev.options !== next.options) {
// Use safe defaults for options to handle undefined
const prevOpts = prev.options || [];
const nextOpts = next.options || [];
// Only do shallow comparison if references are different
if (prevOpts.length !== nextOpts.length) return false;
// Quick length check before detailed comparison
for (let i = 0; i < prevOpts.length; i++) {
if (prevOpts[i] !== nextOpts[i]) return false;
}
}
}
// For item numbers, check itemNumber equality
if (prev.fieldKey === 'item_number' && prev.itemNumber !== next.itemNumber) {
return false;
}
// If we got this far, the props are equal
return true; return true;
}); });
// Helper function to compare error arrays efficiently ValidationCell.displayName = 'ValidationCell';
function compareErrorArrays(prevErrors: ErrorObject[], nextErrors: ErrorObject[]): boolean {
if (prevErrors === nextErrors) return true;
if (prevErrors.length !== nextErrors.length) return false;
for (let i = 0; i < prevErrors.length; i++) { export default ValidationCell;
const prevError = prevErrors[i];
const nextError = nextErrors[i];
if (prevError.message !== nextError.message ||
prevError.level !== nextError.level ||
prevError.source !== nextError.source) {
return false;
}
}
return true;
}

View File

@@ -11,7 +11,7 @@ import { RowData, Template } from '../hooks/useValidationState'
import ValidationCell, { CopyDownContext } from './ValidationCell' import ValidationCell, { CopyDownContext } from './ValidationCell'
import { useRsi } from '../../../hooks/useRsi' import { useRsi } from '../../../hooks/useRsi'
import SearchableTemplateSelect from './SearchableTemplateSelect' import SearchableTemplateSelect from './SearchableTemplateSelect'
import { Table, TableHeader, TableBody, TableHead, TableRow, TableCell } from '@/components/ui/table' import { Table, TableBody, TableRow, TableCell } from '@/components/ui/table'
import { Checkbox } from '@/components/ui/checkbox' import { Checkbox } from '@/components/ui/checkbox'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
@@ -139,15 +139,30 @@ const MemoizedCell = React.memo(({
/> />
); );
}, (prev, next) => { }, (prev, next) => {
// Optimize the memo comparison function for better performance
// Only re-render if these essential props change // Only re-render if these essential props change
return ( const valueEqual = prev.value === next.value;
prev.value === next.value && const isValidatingEqual = prev.isValidating === next.isValidating;
prev.isValidating === next.isValidating && const itemNumberEqual = prev.itemNumber === next.itemNumber;
prev.itemNumber === next.itemNumber &&
// Deep compare errors // Shallow equality check for errors array
prev.errors === next.errors && const errorsEqual = prev.errors === next.errors || (
prev.options === next.options Array.isArray(prev.errors) &&
Array.isArray(next.errors) &&
prev.errors.length === next.errors.length &&
prev.errors.every((err, idx) => err === next.errors[idx])
); );
// Shallow equality check for options array
const optionsEqual = prev.options === next.options || (
Array.isArray(prev.options) &&
Array.isArray(next.options) &&
prev.options.length === next.options.length &&
prev.options.every((opt, idx) => opt === next.options?.[idx])
);
// Skip checking for props that rarely change
return valueEqual && isValidatingEqual && errorsEqual && optionsEqual && itemNumberEqual;
}); });
MemoizedCell.displayName = 'MemoizedCell'; MemoizedCell.displayName = 'MemoizedCell';
@@ -174,10 +189,6 @@ const ValidationTable = <T extends string>({
}: ValidationTableProps<T>) => { }: ValidationTableProps<T>) => {
const { translations } = useRsi<T>(); const { translations } = useRsi<T>();
// Debug logs
console.log('ValidationTable rowProductLines:', rowProductLines);
console.log('ValidationTable rowSublines:', rowSublines);
// Add state for copy down selection mode // Add state for copy down selection mode
const [isInCopyDownMode, setIsInCopyDownMode] = useState(false); const [isInCopyDownMode, setIsInCopyDownMode] = useState(false);
const [sourceRowIndex, setSourceRowIndex] = useState<number | null>(null); const [sourceRowIndex, setSourceRowIndex] = useState<number | null>(null);
@@ -293,10 +304,13 @@ const ValidationTable = <T extends string>({
const cache = new Map<string, readonly any[]>(); const cache = new Map<string, readonly any[]>();
fields.forEach((field) => { fields.forEach((field) => {
// Don't skip disabled fields // Get the field key
const fieldKey = String(field.key);
if (field.fieldType.type === 'select' || field.fieldType.type === 'multi-select') { // Handle all select and multi-select fields the same way
const fieldKey = String(field.key); if (field.fieldType &&
(typeof field.fieldType === 'object') &&
(field.fieldType.type === 'select' || field.fieldType.type === 'multi-select')) {
cache.set(fieldKey, (field.fieldType as any).options || []); cache.set(fieldKey, (field.fieldType as any).options || []);
} }
}); });
@@ -342,30 +356,61 @@ const ValidationTable = <T extends string>({
if (fieldKey === 'line' && rowId && rowProductLines[rowId]) { if (fieldKey === 'line' && rowId && rowProductLines[rowId]) {
options = rowProductLines[rowId]; options = rowProductLines[rowId];
console.log(`Setting line options for row ${rowId}:`, options);
} else if (fieldKey === 'subline' && rowId && rowSublines[rowId]) { } else if (fieldKey === 'subline' && rowId && rowSublines[rowId]) {
options = rowSublines[rowId]; options = rowSublines[rowId];
console.log(`Setting subline options for row ${rowId}:`, options);
} }
// Determine if this cell is in loading state // Determine if this cell is in loading state - use a clear consistent approach
let isLoading = validatingCells.has(`${row.index}-${field.key}`); let isLoading = false;
// Check the validatingCells Set first (for item_number and other fields)
const cellLoadingKey = `${row.index}-${fieldKey}`;
if (validatingCells.has(cellLoadingKey)) {
isLoading = true;
}
// Add loading state for line/subline fields // Add loading state for line/subline fields
if (fieldKey === 'line' && rowId && isLoadingLines[rowId]) { else if (fieldKey === 'line' && rowId && isLoadingLines[rowId]) {
isLoading = true; isLoading = true;
console.log(`Line field for row ${rowId} is loading`); }
} else if (fieldKey === 'subline' && rowId && isLoadingSublines[rowId]) { else if (fieldKey === 'subline' && rowId && isLoadingSublines[rowId]) {
isLoading = true; isLoading = true;
console.log(`Subline field for row ${rowId} is loading`); }
// Get validation errors for this cell
const cellErrors = validationErrors.get(row.index)?.[fieldKey] || [];
// Create a copy of the field with guaranteed field type for line and subline fields
let fieldWithType = field;
// Ensure line and subline fields always have the correct fieldType
if (fieldKey === 'line' || fieldKey === 'subline') {
// Create a deep clone of the field to prevent any reference issues
fieldWithType = {
...JSON.parse(JSON.stringify(field)), // Ensure deep clone
fieldType: {
type: 'select',
options: options
},
// Explicitly mark as not disabled to ensure dropdown works
disabled: false
};
// Debug logging
console.log(`Field ${fieldKey} in ValidationTable (after deep clone):`, {
originalField: field,
modifiedField: fieldWithType,
options,
hasOptions: options && options.length > 0,
disabled: fieldWithType.disabled
});
} }
return ( return (
<MemoizedCell <MemoizedCell
field={field as Field<string>} field={fieldWithType as Field<string>}
value={row.original[field.key as keyof typeof row.original]} value={row.original[field.key as keyof typeof row.original]}
onChange={(value) => handleFieldUpdate(row.index, field.key as T, value)} onChange={(value) => handleFieldUpdate(row.index, field.key as T, value)}
errors={validationErrors.get(row.index)?.[fieldKey] || []} errors={cellErrors}
isValidating={isLoading} isValidating={isLoading}
fieldKey={fieldKey} fieldKey={fieldKey}
options={options} options={options}
@@ -387,15 +432,13 @@ const ValidationTable = <T extends string>({
const table = useReactTable({ const table = useReactTable({
data, data,
columns, columns,
getCoreRowModel: getCoreRowModel(), state: {
state: { rowSelection }, rowSelection,
},
enableRowSelection: true, enableRowSelection: true,
onRowSelectionChange: setRowSelection, onRowSelectionChange: setRowSelection,
getRowId: (row) => { getCoreRowModel: getCoreRowModel(),
if (row.__index) return row.__index; getRowId: useCallback((_row: RowData<T>, index: number) => String(index), []),
const index = data.indexOf(row);
return String(index);
}
}); });
// Calculate total table width for stable horizontal scrolling // Calculate total table width for stable horizontal scrolling
@@ -481,13 +524,16 @@ const ValidationTable = <T extends string>({
</div> </div>
)} )}
<div className="relative"> <div className="relative">
{/* Custom Table Header - Always Visible */} {/* Custom Table Header - Always Visible with GPU acceleration */}
<div <div
className={`sticky top-0 z-20 bg-muted border-b shadow-sm`} className="sticky top-0 z-20 bg-muted border-b shadow-sm will-change-transform"
style={{ width: `${totalWidth}px` }} style={{
width: `${totalWidth}px`,
transform: 'translateZ(0)', // Force GPU acceleration
}}
> >
<div className="flex"> <div className="flex">
{table.getFlatHeaders().map((header, index) => { {table.getFlatHeaders().map((header) => {
const width = header.getSize(); const width = header.getSize();
return ( return (
<div <div
@@ -508,49 +554,57 @@ const ValidationTable = <T extends string>({
</div> </div>
</div> </div>
{/* Table Body */} {/* Table Body - With optimized rendering */}
<Table style={{ width: `${totalWidth}px`, tableLayout: 'fixed', borderCollapse: 'separate', borderSpacing: 0, marginTop: '-1px' }}> <Table style={{
width: `${totalWidth}px`,
tableLayout: 'fixed',
borderCollapse: 'separate',
borderSpacing: 0,
marginTop: '-1px',
willChange: 'transform', // Help browser optimize
contain: 'content', // Contain paint operations
transform: 'translateZ(0)' // Force GPU acceleration
}}>
<TableBody> <TableBody>
{table.getRowModel().rows.map((row) => ( {table.getRowModel().rows.map((row) => {
<TableRow // Precompute validation error status for this row
key={row.id} const hasErrors = validationErrors.has(parseInt(row.id)) &&
className={cn( Object.keys(validationErrors.get(parseInt(row.id)) || {}).length > 0;
"hover:bg-muted/50",
row.getIsSelected() ? "bg-muted/50" : "", // Precompute copy down target status
validationErrors.get(data.indexOf(row.original)) && const isCopyDownTarget = isInCopyDownMode &&
Object.keys(validationErrors.get(data.indexOf(row.original)) || {}).length > 0 ? "bg-red-50/40" : "", sourceRowIndex !== null &&
// Add cursor-pointer class when in copy down mode for target rows parseInt(row.id) > sourceRowIndex;
isInCopyDownMode && sourceRowIndex !== null && row.index > sourceRowIndex ? "cursor-pointer copy-down-target-row" : ""
)} // Using CSS variables for better performance on hover/state changes
style={{ const rowStyle = {
// Force cursor pointer on all target rows cursor: isCopyDownTarget ? 'pointer' : undefined,
cursor: isInCopyDownMode && sourceRowIndex !== null && row.index > sourceRowIndex ? 'pointer' : undefined, position: 'relative' as const,
position: 'relative' // Ensure we can position the overlay willChange: isInCopyDownMode ? 'background-color' : 'auto',
}} contain: 'layout',
onMouseEnter={() => handleRowMouseEnter(row.index)} transition: 'background-color 100ms ease-in-out'
> };
{row.getVisibleCells().map((cell, cellIndex) => {
const width = cell.column.getSize(); return (
return ( <TableRow
<TableCell key={row.id}
key={cell.id} className={cn(
style={{ "hover:bg-muted/50",
width: `${width}px`, row.getIsSelected() ? "bg-muted/50" : "",
minWidth: `${width}px`, hasErrors ? "bg-red-50/40" : "",
maxWidth: `${width}px`, isCopyDownTarget ? "cursor-pointer copy-down-target-row" : ""
boxSizing: 'border-box', )}
padding: '0', style={rowStyle}
// Force cursor pointer on all cells in target rows onMouseEnter={() => handleRowMouseEnter(parseInt(row.id))}
cursor: isInCopyDownMode && sourceRowIndex !== null && row.index > sourceRowIndex ? 'pointer' : undefined >
}} {row.getVisibleCells().map((cell: any) => (
className={isInCopyDownMode && sourceRowIndex !== null && row.index > sourceRowIndex ? "target-row-cell" : ""} <React.Fragment key={cell.id}>
>
{flexRender(cell.column.columnDef.cell, cell.getContext())} {flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell> </React.Fragment>
); ))}
})} </TableRow>
</TableRow> );
))} })}
</TableBody> </TableBody>
</Table> </Table>
</div> </div>

View File

@@ -107,23 +107,28 @@ const InputCell = <T extends string>({
// Handle blur event - use transition for non-critical updates // Handle blur event - use transition for non-critical updates
const handleBlur = useCallback(() => { const handleBlur = useCallback(() => {
// First - lock in the current edit value to prevent it from being lost
const finalValue = editValue.trim();
// Then transition to non-editing state
startTransition(() => { startTransition(() => {
setIsEditing(false); setIsEditing(false);
// Format the value for storage (remove formatting like $ for price) // Format the value for storage (remove formatting like $ for price)
let processedValue = deferredEditValue.trim(); let processedValue = finalValue;
if (isPrice && processedValue) { if (isPrice && processedValue) {
needsProcessingRef.current = true; needsProcessingRef.current = true;
} }
// Update local display value immediately // Update local display value immediately to prevent UI flicker
setLocalDisplayValue(processedValue); setLocalDisplayValue(processedValue);
// Commit the change to parent component
onChange(processedValue); onChange(processedValue);
onEndEdit?.(); onEndEdit?.();
}); });
}, [deferredEditValue, onChange, onEndEdit, isPrice]); }, [editValue, onChange, onEndEdit, isPrice]);
// Handle direct input change - optimized to be synchronous for typing // Handle direct input change - optimized to be synchronous for typing
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => { const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {

View File

@@ -167,41 +167,59 @@ const MultiSelectCell = <T extends string>({
const commandListRef = useRef<HTMLDivElement>(null) const commandListRef = useRef<HTMLDivElement>(null)
// Add state for hover // Add state for hover
const [isHovered, setIsHovered] = useState(false) const [isHovered, setIsHovered] = useState(false)
// Add ref to track if we need to sync internal state with external value
const shouldSyncWithExternalValue = useRef(true)
// Create a memoized Set for fast lookups of selected values // Create a memoized Set for fast lookups of selected values
const selectedValueSet = useMemo(() => new Set(internalValue), [internalValue]); const selectedValueSet = useMemo(() => new Set(internalValue), [internalValue]);
// Sync internalValue with external value when component mounts or value changes externally // Sync internalValue with external value when component mounts or value changes externally
// Modified to prevent infinite loop by checking if values are different before updating
useEffect(() => { useEffect(() => {
if (!open) { // Only sync if we should (not during internal edits) and if not open
// Ensure value is always an array if (shouldSyncWithExternalValue.current && !open) {
setInternalValue(Array.isArray(value) ? value : []) const externalValue = Array.isArray(value) ? value : [];
// Only update if values are actually different to prevent infinite loops
if (internalValue.length !== externalValue.length ||
!internalValue.every(v => externalValue.includes(v)) ||
!externalValue.every(v => internalValue.includes(v))) {
setInternalValue(externalValue);
}
} }
}, [value, open]) }, [value, open, internalValue]);
// Handle open state changes with improved responsiveness // Handle open state changes with improved responsiveness
const handleOpenChange = useCallback((newOpen: boolean) => { const handleOpenChange = useCallback((newOpen: boolean) => {
if (open && !newOpen) { if (open && !newOpen) {
// Prevent syncing with external value during our internal update
shouldSyncWithExternalValue.current = false;
// Only update parent state when dropdown closes // Only update parent state when dropdown closes
// Avoid expensive deep comparison if lengths are different // Make a defensive copy to avoid mutations
if (internalValue.length !== value.length || const valuesToCommit = [...internalValue];
internalValue.some((v, i) => v !== value[i])) {
onChange(internalValue); // Immediate UI update
} setOpen(false);
// Update parent with the value immediately
onChange(valuesToCommit);
if (onEndEdit) onEndEdit(); if (onEndEdit) onEndEdit();
// Allow syncing with external value again after a short delay
setTimeout(() => {
shouldSyncWithExternalValue.current = true;
}, 0);
} else if (newOpen && !open) { } else if (newOpen && !open) {
// Sync internal state with external state when opening // When opening the dropdown, sync with external value
setInternalValue(Array.isArray(value) ? value : []); const externalValue = Array.isArray(value) ? value : [];
setInternalValue(externalValue);
setSearchQuery(""); // Reset search query on open setSearchQuery(""); // Reset search query on open
setOpen(true);
if (onStartEdit) onStartEdit(); if (onStartEdit) onStartEdit();
} else if (!newOpen) { } else if (!newOpen) {
// Handle case when dropdown is already closed but handleOpenChange is called // Handle case when dropdown is already closed but handleOpenChange is called
// This ensures values are saved when clicking the chevron to close setOpen(false);
if (internalValue.length !== value.length ||
internalValue.some((v, i) => v !== value[i])) {
onChange(internalValue);
}
if (onEndEdit) onEndEdit();
} }
}, [open, internalValue, value, onChange, onStartEdit, onEndEdit]); }, [open, internalValue, value, onChange, onStartEdit, onEndEdit]);
@@ -302,13 +320,25 @@ const MultiSelectCell = <T extends string>({
// Update the handleSelect to operate on internalValue instead of directly calling onChange // Update the handleSelect to operate on internalValue instead of directly calling onChange
const handleSelect = useCallback((selectedValue: string) => { const handleSelect = useCallback((selectedValue: string) => {
// Prevent syncing with external value during our internal update
shouldSyncWithExternalValue.current = false;
setInternalValue(prev => { setInternalValue(prev => {
let newValue;
if (prev.includes(selectedValue)) { if (prev.includes(selectedValue)) {
return prev.filter(v => v !== selectedValue); // Remove the value
newValue = prev.filter(v => v !== selectedValue);
} else { } else {
return [...prev, selectedValue]; // Add the value - make a new array to avoid mutations
newValue = [...prev, selectedValue];
} }
return newValue;
}); });
// Allow syncing with external value again after a short delay
setTimeout(() => {
shouldSyncWithExternalValue.current = true;
}, 0);
}, []); }, []);
// Handle wheel scroll in dropdown // Handle wheel scroll in dropdown

View File

@@ -51,8 +51,6 @@ const SelectCell = <T extends string>({
// Add state for hover // Add state for hover
const [isHovered, setIsHovered] = useState(false); const [isHovered, setIsHovered] = useState(false);
console.log(`SelectCell: field.key=${field.key}, disabled=${disabled}, options=`, options);
// Helper function to check if a class is present in the className string // Helper function to check if a class is present in the className string
const hasClass = (cls: string): boolean => { const hasClass = (cls: string): boolean => {
const classNames = className.split(' '); const classNames = className.split(' ');
@@ -68,7 +66,6 @@ const SelectCell = <T extends string>({
// Memoize options processing to avoid recalculation on every render // Memoize options processing to avoid recalculation on every render
const selectOptions = useMemo(() => { const selectOptions = useMemo(() => {
console.log(`Processing options for ${field.key}:`, options);
// Fast path check - if we have raw options, just use those // Fast path check - if we have raw options, just use those
if (options && options.length > 0) { if (options && options.length > 0) {
// Check if options already have the correct structure to avoid mapping // Check if options already have the correct structure to avoid mapping
@@ -126,8 +123,11 @@ const SelectCell = <T extends string>({
// Handle selection - UPDATE INTERNAL VALUE FIRST // Handle selection - UPDATE INTERNAL VALUE FIRST
const handleSelect = useCallback((selectedValue: string) => { const handleSelect = useCallback((selectedValue: string) => {
// Store the selected value to prevent it being lost in async operations
const valueToCommit = selectedValue;
// 1. Update internal value immediately to prevent UI flicker // 1. Update internal value immediately to prevent UI flicker
setInternalValue(selectedValue); setInternalValue(valueToCommit);
// 2. Close the dropdown immediately // 2. Close the dropdown immediately
setOpen(false); setOpen(false);
@@ -139,105 +139,44 @@ const SelectCell = <T extends string>({
// This prevents the parent component from re-rendering and causing dropdown to reopen // This prevents the parent component from re-rendering and causing dropdown to reopen
if (onEndEdit) onEndEdit(); if (onEndEdit) onEndEdit();
// 5. Call onChange in the next tick to avoid synchronous re-renders // 5. Call onChange synchronously to avoid race conditions with other cells
onChange(valueToCommit);
// 6. Clear processing state after a short delay
setTimeout(() => { setTimeout(() => {
onChange(selectedValue); setIsProcessing(false);
}, 0); }, 200);
}, [onChange, onEndEdit]); }, [onChange, onEndEdit]);
// If disabled, render a static view // If disabled, render a static view
if (disabled) { if (disabled && field.key !== 'line' && field.key !== 'subline') {
const displayText = displayValue; const displayText = displayValue;
// For debugging, let's render the Popover component even if disabled
// This will help us determine if the issue is with the disabled state
return ( return (
<Popover <div
open={open} className={cn(
onOpenChange={(isOpen) => { "w-full px-3 py-2 h-10 rounded-md text-sm flex items-center",
setOpen(isOpen); "border",
if (isOpen && onStartEdit) onStartEdit(); hasErrors ? "border-destructive" : "border-input",
}} className
> )}
<PopoverTrigger asChild> style={{
<Button backgroundColor: hasClass('!bg-blue-100') ? '#dbeafe' :
variant="outline" hasClass('!bg-blue-200') ? '#bfdbfe' :
role="combobox" hasClass('hover:!bg-blue-100') && isHovered ? '#dbeafe' :
aria-expanded={open}
className={cn(
"w-full justify-between font-normal",
"border",
!internalValue && "text-muted-foreground",
isProcessing && "text-muted-foreground",
hasErrors ? "border-destructive" : "",
className
)}
style={{
backgroundColor: hasClass('!bg-blue-100') ? '#dbeafe' :
hasClass('!bg-blue-200') ? '#bfdbfe' :
hasClass('hover:!bg-blue-100') && isHovered ? '#dbeafe' :
undefined,
borderColor: hasClass('!border-blue-500') ? '#3b82f6' :
hasClass('!border-blue-200') ? '#bfdbfe' :
hasClass('!border-blue-200') && isHovered ? '#bfdbfe' :
undefined, undefined,
borderRadius: hasClass('!rounded-md') ? '0.375rem' : undefined, borderColor: hasClass('!border-blue-500') ? '#3b82f6' :
borderWidth: hasClass('!border-blue-500') || hasClass('!border-blue-200') ? '0px' : undefined, hasClass('!border-blue-200') ? '#bfdbfe' :
cursor: hasClass('hover:!bg-blue-100') ? 'pointer' : undefined hasClass('!border-blue-200') && isHovered ? '#bfdbfe' :
}} undefined,
onClick={(e) => { borderRadius: hasClass('!rounded-md') ? '0.375rem' : undefined,
e.preventDefault(); cursor: hasClass('hover:!bg-blue-100') ? 'pointer' : undefined
e.stopPropagation(); }}
setOpen(!open); onMouseEnter={() => setIsHovered(true)}
if (!open && onStartEdit) onStartEdit(); onMouseLeave={() => setIsHovered(false)}
}} >
onMouseEnter={() => setIsHovered(true)} {displayText || ""}
onMouseLeave={() => setIsHovered(false)} </div>
>
<span className={isProcessing ? "opacity-70" : ""}>
{displayValue}
</span>
<ChevronsUpDown className="mr-1.5 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent
className="p-0 w-[var(--radix-popover-trigger-width)]"
align="start"
sideOffset={4}
>
<Command shouldFilter={true}>
<CommandInput
placeholder="Search..."
className="h-9"
/>
<CommandList
ref={commandListRef}
onWheel={handleWheel}
className="max-h-[200px]"
>
<CommandEmpty>No options found.</CommandEmpty>
<CommandGroup>
{selectOptions.map((option) => (
<CommandItem
key={option.value}
value={option.value}
onSelect={(value) => handleSelect(value)}
className="flex w-full"
>
<Check
className={cn(
"mr-2 h-4 w-4 flex-shrink-0",
internalValue === option.value ? "opacity-100" : "opacity-0"
)}
/>
<span className="truncate w-full">{option.label}</span>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
); );
} }

View File

@@ -1,7 +1,7 @@
import { useState, useCallback } from 'react'; import { useState, useCallback } from 'react';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { getApiUrl, RowData } from './useValidationState'; import { getApiUrl, RowData } from './useValidationState';
import { Fields, InfoWithSource, ErrorSources } from '../../../types'; import { Fields, InfoWithSource, ErrorSources, ErrorType } from '../../../types';
import { Meta } from '../types'; import { Meta } from '../types';
import { addErrorsAndRunHooks } from '../utils/dataMutations'; import { addErrorsAndRunHooks } from '../utils/dataMutations';
import * as Diff from 'diff'; import * as Diff from 'diff';
@@ -88,21 +88,10 @@ export const useAiValidation = <T extends string>(
// Call the original hook // Call the original hook
const result = await rowHook(row); const result = await rowHook(row);
// Extract Meta-specific properties // Extract Meta-specific properties
const { __index, __errors } = result; const { __index } = result;
// Return a Meta object with properly typed errors // Return a Meta object with only the __index property
return { return {
__index: __index || row.__index || '', __index: __index || row.__index || ''
__errors: __errors ?
Object.fromEntries(
Object.entries(__errors).map(([key, value]) => {
const errorArray = Array.isArray(value) ? value : [value];
return [key, {
message: errorArray[0].message,
level: errorArray[0].level,
source: ErrorSources.Row
} as InfoWithSource]
})
) : null
}; };
} : undefined; } : undefined;
@@ -112,18 +101,7 @@ export const useAiValidation = <T extends string>(
const results = await tableHook(rows); const results = await tableHook(rows);
// Extract Meta-specific properties from each result // Extract Meta-specific properties from each result
return results.map((result, index) => ({ return results.map((result, index) => ({
__index: result.__index || rows[index].__index || '', __index: result.__index || rows[index].__index || ''
__errors: result.__errors ?
Object.fromEntries(
Object.entries(result.__errors).map(([key, value]) => {
const errorArray = Array.isArray(value) ? value : [value];
return [key, {
message: errorArray[0].message,
level: errorArray[0].level,
source: ErrorSources.Table
} as InfoWithSource]
})
) : null
})); }));
} : undefined; } : undefined;
@@ -283,7 +261,7 @@ export const useAiValidation = <T extends string>(
// Clean the data to ensure we only send what's needed // Clean the data to ensure we only send what's needed
const cleanedData = data.map(item => { const cleanedData = data.map(item => {
const { __errors, __index, ...rest } = item; const { __index, ...rest } = item;
return rest; return rest;
}); });
@@ -399,8 +377,8 @@ export const useAiValidation = <T extends string>(
// Clean the data to ensure we only send what's needed // Clean the data to ensure we only send what's needed
const cleanedData = data.map(item => { const cleanedData = data.map(item => {
const { __errors, __index, ...cleanProduct } = item; const { __index, ...rest } = item;
return cleanProduct; return rest;
}); });
console.log('Cleaned data for validation:', cleanedData); console.log('Cleaned data for validation:', cleanedData);
@@ -601,7 +579,7 @@ export const useAiValidation = <T extends string>(
console.log('Data updated after AI validation:', { console.log('Data updated after AI validation:', {
dataLength: validatedData.length, dataLength: validatedData.length,
hasErrors: validatedData.some(row => row.__errors && Object.keys(row.__errors).length > 0) hasErrors: false // We no longer check row.__errors
}); });
// Show changes and warnings in dialog after data is updated // Show changes and warnings in dialog after data is updated

View File

@@ -1,115 +0,0 @@
import { useState, useCallback, useMemo } from 'react'
import type { Fields } from '../../../types'
import { RowData } from './useValidationState'
export interface FilterState {
searchText: string
showErrorsOnly: boolean
filterField: string | null
filterValue: string | null
}
export const useFilters = <T extends string>(
data: RowData<T>[],
fields: Fields<T>
) => {
// Filter state
const [filters, setFilters] = useState<FilterState>({
searchText: '',
showErrorsOnly: false,
filterField: null,
filterValue: null
})
// Get available filter fields
const filterFields = useMemo(() => {
return fields.map(field => ({
key: field.key,
label: field.label
}))
}, [fields])
// Get available filter values for the selected field
const filterValues = useMemo(() => {
if (!filters.filterField) return []
// Get unique values for the selected field
const uniqueValues = new Set<string>()
data.forEach(row => {
const value = row[filters.filterField as keyof typeof row]
if (value !== undefined && value !== null) {
uniqueValues.add(String(value))
}
})
return Array.from(uniqueValues).map(value => ({
value,
label: value
}))
}, [data, filters.filterField])
// Update filters
const updateFilters = useCallback((newFilters: Partial<FilterState>) => {
setFilters(prev => ({
...prev,
...newFilters
}))
}, [])
// Apply filters to data
const applyFilters = useCallback((dataToFilter: RowData<T>[]) => {
return dataToFilter.filter(row => {
// Filter by search text
if (filters.searchText) {
const lowerSearchText = filters.searchText.toLowerCase()
const matchesSearch = Object.entries(row).some(([key, value]) => {
// Skip metadata fields
if (key.startsWith('__')) return false
// Check if the value contains the search text
return value !== undefined &&
value !== null &&
String(value).toLowerCase().includes(lowerSearchText)
})
if (!matchesSearch) return false
}
// Filter by errors
if (filters.showErrorsOnly) {
const hasErrors = row.__errors && Object.keys(row.__errors).length > 0
if (!hasErrors) return false
}
// Filter by field value
if (filters.filterField && filters.filterValue) {
const fieldValue = row[filters.filterField as keyof typeof row]
return fieldValue !== undefined &&
fieldValue !== null &&
String(fieldValue) === filters.filterValue
}
return true
})
}, [filters])
// Reset all filters
const resetFilters = useCallback(() => {
setFilters({
searchText: '',
showErrorsOnly: false,
filterField: null,
filterValue: null
})
}, [])
return {
filters,
filterFields,
filterValues,
updateFilters,
applyFilters,
resetFilters
}
}

View File

@@ -0,0 +1,420 @@
import { useState, useCallback, useEffect } from 'react'
import axios from 'axios'
import { toast } from 'sonner'
/**
* Custom hook for managing product lines and sublines fetching with caching
*/
export const useProductLinesFetching = (data: Record<string, any>[]) => {
// State for tracking product lines and sublines per row
const [rowProductLines, setRowProductLines] = useState<Record<string, any[]>>({});
const [rowSublines, setRowSublines] = useState<Record<string, any[]>>({});
// State for tracking loading states
const [isLoadingLines, setIsLoadingLines] = useState<Record<string, boolean>>({});
const [isLoadingSublines, setIsLoadingSublines] = useState<Record<string, boolean>>({});
// Add caches for product lines and sublines by company/line ID
const [companyLinesCache, setCompanyLinesCache] = useState<Record<string, any[]>>({});
const [lineSublineCache, setLineSublineCache] = useState<Record<string, any[]>>({});
// Function to fetch product lines for a specific company - memoized
const fetchProductLines = useCallback(async (rowIndex: string | number, companyId: string) => {
try {
// Only fetch if we have a valid company ID
if (!companyId) return;
console.log(`Fetching product lines for row ${rowIndex}, company ${companyId}`);
// Check if we already have this company's lines in the cache
if (companyLinesCache[companyId]) {
console.log(`Using cached product lines for company ${companyId}`);
// Use cached data
setRowProductLines(prev => ({ ...prev, [rowIndex]: companyLinesCache[companyId] }));
return companyLinesCache[companyId];
}
// Set loading state for this row
setIsLoadingLines(prev => ({ ...prev, [rowIndex]: true }));
// Fetch product lines from API
const productLinesUrl = `/api/import/product-lines/${companyId}`;
const response = await axios.get(productLinesUrl);
const lines = response.data;
console.log(`Received ${lines.length} product lines for company ${companyId}`);
// Format the data properly for dropdown display
const formattedLines = lines.map((line: any) => ({
label: line.name || line.label || String(line.value || line.id),
value: String(line.value || line.id)
}));
// Store in company cache
setCompanyLinesCache(prev => ({ ...prev, [companyId]: formattedLines }));
// Store for this specific row
setRowProductLines(prev => ({ ...prev, [rowIndex]: formattedLines }));
return formattedLines;
} catch (error) {
console.error(`Error fetching product lines for company ${companyId}:`, error);
toast.error(`Failed to load product lines for company ${companyId}`);
// Set empty array for this company to prevent repeated failed requests
setCompanyLinesCache(prev => ({ ...prev, [companyId]: [] }));
// Store empty array for this specific row
setRowProductLines(prev => ({ ...prev, [rowIndex]: [] }));
return [];
} finally {
// Clear loading state
setIsLoadingLines(prev => ({ ...prev, [rowIndex]: false }));
}
}, [companyLinesCache]);
// Function to fetch sublines for a specific line - memoized
const fetchSublines = useCallback(async (rowIndex: string | number, lineId: string) => {
try {
// Only fetch if we have a valid line ID
if (!lineId) return;
console.log(`Fetching sublines for row ${rowIndex}, line ${lineId}`);
// Check if we already have this line's sublines in the cache
if (lineSublineCache[lineId]) {
console.log(`Using cached sublines for line ${lineId}`);
// Use cached data
setRowSublines(prev => ({ ...prev, [rowIndex]: lineSublineCache[lineId] }));
return lineSublineCache[lineId];
}
// Set loading state for this row
setIsLoadingSublines(prev => ({ ...prev, [rowIndex]: true }));
// Fetch sublines from API
const sublinesUrl = `/api/import/sublines/${lineId}`;
const response = await axios.get(sublinesUrl);
const sublines = response.data;
console.log(`Received ${sublines.length} sublines for line ${lineId}`);
// Format the data properly for dropdown display
const formattedSublines = sublines.map((subline: any) => ({
label: subline.name || subline.label || String(subline.value || subline.id),
value: String(subline.value || subline.id)
}));
// Store in line cache
setLineSublineCache(prev => ({ ...prev, [lineId]: formattedSublines }));
// Store for this specific row
setRowSublines(prev => ({ ...prev, [rowIndex]: formattedSublines }));
return formattedSublines;
} catch (error) {
console.error(`Error fetching sublines for line ${lineId}:`, error);
// Set empty array for this line to prevent repeated failed requests
setLineSublineCache(prev => ({ ...prev, [lineId]: [] }));
// Store empty array for this specific row
setRowSublines(prev => ({ ...prev, [rowIndex]: [] }));
return [];
} finally {
// Clear loading state
setIsLoadingSublines(prev => ({ ...prev, [rowIndex]: false }));
}
}, [lineSublineCache]);
// When data changes, fetch product lines and sublines for rows that have company/line values
useEffect(() => {
// Skip if there's no data
if (!data.length) return;
// First check if we need to do anything at all
let needsFetching = false;
// Quick check for any rows that would need fetching
for (const row of data) {
const rowId = row.__index;
if (!rowId) continue;
if ((row.company && !rowProductLines[rowId]) || (row.line && !rowSublines[rowId])) {
needsFetching = true;
break;
}
}
// If nothing needs fetching, exit early
if (!needsFetching) {
return;
}
console.log("Starting to fetch product lines and sublines");
// Group rows by company and line to minimize API calls
const companiesNeeded = new Map<string, string[]>(); // company ID -> row IDs
const linesNeeded = new Map<string, string[]>(); // line ID -> row IDs
data.forEach(row => {
const rowId = row.__index;
if (!rowId) return; // Skip rows without an index
// If row has company but no product lines fetched yet
if (row.company && !rowProductLines[rowId]) {
const companyId = row.company.toString();
if (!companiesNeeded.has(companyId)) {
companiesNeeded.set(companyId, []);
}
companiesNeeded.get(companyId)?.push(rowId);
}
// If row has line but no sublines fetched yet
if (row.line && !rowSublines[rowId]) {
const lineId = row.line.toString();
if (!linesNeeded.has(lineId)) {
linesNeeded.set(lineId, []);
}
linesNeeded.get(lineId)?.push(rowId);
}
});
console.log(`Need to fetch product lines for ${companiesNeeded.size} companies and sublines for ${linesNeeded.size} lines`);
// If nothing to fetch, exit early to prevent unnecessary processing
if (companiesNeeded.size === 0 && linesNeeded.size === 0) {
return;
}
// Create arrays to hold all fetch promises
const fetchPromises: Promise<void>[] = [];
// Set initial loading states for all affected rows
const lineLoadingUpdates: Record<string, boolean> = {};
const sublineLoadingUpdates: Record<string, boolean> = {};
// Process companies that need product lines
companiesNeeded.forEach((rowIds, companyId) => {
// Skip if already in cache
if (companyLinesCache[companyId]) {
console.log(`Using cached product lines for company ${companyId}`);
// Use cached data for all rows with this company
const lines = companyLinesCache[companyId];
const updates: Record<string, any[]> = {};
rowIds.forEach(rowId => {
updates[rowId] = lines;
});
setRowProductLines(prev => ({ ...prev, ...updates }));
return;
}
// Set loading state for all affected rows
rowIds.forEach(rowId => {
lineLoadingUpdates[rowId] = true;
});
// Create fetch promise
const fetchPromise = (async () => {
// Safety timeout to ensure loading state is cleared after 10 seconds
const timeoutId = setTimeout(() => {
console.log(`Safety timeout triggered for company ${companyId}`);
const clearLoadingUpdates: Record<string, boolean> = {};
rowIds.forEach(rowId => {
clearLoadingUpdates[rowId] = false;
});
setIsLoadingLines(prev => ({ ...prev, ...clearLoadingUpdates }));
// Set empty cache to prevent repeated requests
setCompanyLinesCache(prev => ({ ...prev, [companyId]: [] }));
// Update rows with empty array
const updates: Record<string, any[]> = {};
rowIds.forEach(rowId => {
updates[rowId] = [];
});
setRowProductLines(prev => ({ ...prev, ...updates }));
toast.error(`Timeout loading product lines for company ${companyId}`);
}, 10000);
try {
console.log(`Fetching product lines for company ${companyId} (affecting ${rowIds.length} rows)`);
// Fetch product lines from API
const productLinesUrl = `/api/import/product-lines/${companyId}`;
console.log(`Fetching from URL: ${productLinesUrl}`);
const response = await axios.get(productLinesUrl);
console.log(`Product lines API response status for company ${companyId}:`, response.status);
const productLines = response.data;
console.log(`Received ${productLines.length} product lines for company ${companyId}`);
// Store in company cache
setCompanyLinesCache(prev => ({ ...prev, [companyId]: productLines }));
// Update all rows with this company
const updates: Record<string, any[]> = {};
rowIds.forEach(rowId => {
updates[rowId] = productLines;
});
setRowProductLines(prev => ({ ...prev, ...updates }));
} catch (error) {
console.error(`Error fetching product lines for company ${companyId}:`, error);
// Set empty array for this company to prevent repeated failed requests
setCompanyLinesCache(prev => ({ ...prev, [companyId]: [] }));
// Update rows with empty array
const updates: Record<string, any[]> = {};
rowIds.forEach(rowId => {
updates[rowId] = [];
});
setRowProductLines(prev => ({ ...prev, ...updates }));
// Show error toast
toast.error(`Failed to load product lines for company ${companyId}`);
} finally {
// Clear the safety timeout
clearTimeout(timeoutId);
// Clear loading state for all affected rows
const clearLoadingUpdates: Record<string, boolean> = {};
rowIds.forEach(rowId => {
clearLoadingUpdates[rowId] = false;
});
setIsLoadingLines(prev => ({ ...prev, ...clearLoadingUpdates }));
}
})();
fetchPromises.push(fetchPromise);
});
// Process lines that need sublines
linesNeeded.forEach((rowIds, lineId) => {
// Skip if already in cache
if (lineSublineCache[lineId]) {
console.log(`Using cached sublines for line ${lineId}`);
// Use cached data for all rows with this line
const sublines = lineSublineCache[lineId];
const updates: Record<string, any[]> = {};
rowIds.forEach(rowId => {
updates[rowId] = sublines;
});
setRowSublines(prev => ({ ...prev, ...updates }));
return;
}
// Set loading state for all affected rows
rowIds.forEach(rowId => {
sublineLoadingUpdates[rowId] = true;
});
// Create fetch promise
const fetchPromise = (async () => {
// Safety timeout to ensure loading state is cleared after 10 seconds
const timeoutId = setTimeout(() => {
console.log(`Safety timeout triggered for line ${lineId}`);
const clearLoadingUpdates: Record<string, boolean> = {};
rowIds.forEach(rowId => {
clearLoadingUpdates[rowId] = false;
});
setIsLoadingSublines(prev => ({ ...prev, ...clearLoadingUpdates }));
// Set empty cache to prevent repeated requests
setLineSublineCache(prev => ({ ...prev, [lineId]: [] }));
// Update rows with empty array
const updates: Record<string, any[]> = {};
rowIds.forEach(rowId => {
updates[rowId] = [];
});
setRowSublines(prev => ({ ...prev, ...updates }));
toast.error(`Timeout loading sublines for line ${lineId}`);
}, 10000);
try {
console.log(`Fetching sublines for line ${lineId} (affecting ${rowIds.length} rows)`);
// Fetch sublines from API
const sublinesUrl = `/api/import/sublines/${lineId}`;
console.log(`Fetching from URL: ${sublinesUrl}`);
const response = await axios.get(sublinesUrl);
console.log(`Sublines API response status for line ${lineId}:`, response.status);
const sublines = response.data;
console.log(`Received ${sublines.length} sublines for line ${lineId}`);
// Store in line cache
setLineSublineCache(prev => ({ ...prev, [lineId]: sublines }));
// Update all rows with this line
const updates: Record<string, any[]> = {};
rowIds.forEach(rowId => {
updates[rowId] = sublines;
});
setRowSublines(prev => ({ ...prev, ...updates }));
} catch (error) {
console.error(`Error fetching sublines for line ${lineId}:`, error);
// Set empty array for this line to prevent repeated failed requests
setLineSublineCache(prev => ({ ...prev, [lineId]: [] }));
// Update rows with empty array
const updates: Record<string, any[]> = {};
rowIds.forEach(rowId => {
updates[rowId] = [];
});
setRowSublines(prev => ({ ...prev, ...updates }));
// Show error toast
toast.error(`Failed to load sublines for line ${lineId}`);
} finally {
// Clear the safety timeout
clearTimeout(timeoutId);
// Clear loading state for all affected rows
const clearLoadingUpdates: Record<string, boolean> = {};
rowIds.forEach(rowId => {
clearLoadingUpdates[rowId] = false;
});
setIsLoadingSublines(prev => ({ ...prev, ...clearLoadingUpdates }));
}
})();
fetchPromises.push(fetchPromise);
});
// Set initial loading states
if (Object.keys(lineLoadingUpdates).length > 0) {
console.log(`Setting loading state for ${Object.keys(lineLoadingUpdates).length} rows (product lines)`);
setIsLoadingLines(prev => ({ ...prev, ...lineLoadingUpdates }));
}
if (Object.keys(sublineLoadingUpdates).length > 0) {
console.log(`Setting loading state for ${Object.keys(sublineLoadingUpdates).length} rows (sublines)`);
setIsLoadingSublines(prev => ({ ...prev, ...sublineLoadingUpdates }));
}
// Run all fetch operations in parallel
Promise.all(fetchPromises).then(() => {
console.log("All product lines and sublines fetch operations completed");
}).catch(error => {
console.error('Error in fetch operations:', error);
});
}, [data, rowProductLines, rowSublines, companyLinesCache, lineSublineCache]);
return {
rowProductLines,
rowSublines,
isLoadingLines,
isLoadingSublines,
fetchProductLines,
fetchSublines
};
};

View File

@@ -82,13 +82,11 @@ export const useTemplates = <T extends string>(
} }
// Remove metadata fields // Remove metadata fields
delete (template as any).__errors
delete (template as any).__meta delete (template as any).__meta
delete (template as any).__template delete (template as any).__template
delete (template as any).__original delete (template as any).__original
delete (template as any).__corrected delete (template as any).__corrected
delete (template as any).__changes delete (template as any).__changes
delete (template as any).__index
// Send to API // Send to API
const response = await fetch(`${getApiUrl()}/templates`, { const response = await fetch(`${getApiUrl()}/templates`, {

View File

@@ -1,27 +1,41 @@
import { useState, useCallback, useRef } from 'react' import { useState, useCallback, useRef, useEffect } from 'react'
import config from '@/config' import config from '@/config'
interface UpcValidationResult {
error?: boolean
message?: string
data?: Record<string, any>
}
interface ValidationState { interface ValidationState {
validatingCells: Set<string>; // Using rowIndex-fieldKey as identifier validatingCells: Set<string>; // Using rowIndex-fieldKey as identifier
itemNumbers: Map<number, string>; // Using rowIndex as key itemNumbers: Map<number, string>; // Using rowIndex as key
validatingRows: Set<number>; // Rows currently being validated
activeValidations: Set<string>; // Active validations
} }
export const useUpcValidation = () => { export const useUpcValidation = (
data: any[],
setData: (updater: any[] | ((prevData: any[]) => any[])) => void
) => {
// Use a ref for validation state to avoid triggering re-renders // Use a ref for validation state to avoid triggering re-renders
const validationStateRef = useRef<ValidationState>({ const validationStateRef = useRef<ValidationState>({
validatingCells: new Set(), validatingCells: new Set(),
itemNumbers: new Map() itemNumbers: new Map(),
validatingRows: new Set(),
activeValidations: new Set()
}); });
// Use state only for forcing re-renders of specific cells // Use state only for forcing re-renders of specific cells
const [validatingCellKeys, setValidatingCellKeys] = useState<Set<string>>(new Set()); const [, setValidatingCellKeys] = useState<Set<string>>(new Set());
const [itemNumberUpdates, setItemNumberUpdates] = useState<Map<number, string>>(new Map()); const [, setItemNumberUpdates] = useState<Map<number, string>>(new Map());
const [validatingRows, setValidatingRows] = useState<Set<number>>(new Set());
const [, setIsValidatingUpc] = useState(false);
// Cache for UPC validation results
const processedUpcMapRef = useRef(new Map<string, string>());
const initialUpcValidationDoneRef = useRef(false);
// For batch validation
const validationQueueRef = useRef<Array<{rowIndex: number, supplierId: string, upcValue: string}>>([]);
const isProcessingBatchRef = useRef(false);
// For validation results
const [upcValidationResults] = useState<Map<number, { itemNumber: string }>>(new Map());
// Helper to create cell key // Helper to create cell key
const getCellKey = (rowIndex: number, fieldKey: string) => `${rowIndex}-${fieldKey}`; const getCellKey = (rowIndex: number, fieldKey: string) => `${rowIndex}-${fieldKey}`;
@@ -42,83 +56,462 @@ export const useUpcValidation = () => {
// Update item number // Update item number
const updateItemNumber = useCallback((rowIndex: number, itemNumber: string) => { const updateItemNumber = useCallback((rowIndex: number, itemNumber: string) => {
console.log(`Setting item number for row ${rowIndex} to ${itemNumber}`);
validationStateRef.current.itemNumbers.set(rowIndex, itemNumber); validationStateRef.current.itemNumbers.set(rowIndex, itemNumber);
setItemNumberUpdates(new Map(validationStateRef.current.itemNumbers)); setItemNumberUpdates(new Map(validationStateRef.current.itemNumbers));
}, []); }, []);
// Mark a row as being validated
const startValidatingRow = useCallback((rowIndex: number) => {
validationStateRef.current.validatingRows.add(rowIndex);
setValidatingRows(new Set(validationStateRef.current.validatingRows));
setIsValidatingUpc(true);
}, []);
// Mark a row as no longer being validated
const stopValidatingRow = useCallback((rowIndex: number) => {
validationStateRef.current.validatingRows.delete(rowIndex);
setValidatingRows(new Set(validationStateRef.current.validatingRows));
// If no more rows are being validated, set global validation state to false
if (validationStateRef.current.validatingRows.size === 0) {
setIsValidatingUpc(false);
}
}, []);
// Check if a specific cell is being validated // Check if a specific cell is being validated
const isValidatingCell = useCallback((rowIndex: number, fieldKey: string): boolean => { const isValidatingCell = useCallback((rowIndex: number, fieldKey: string): boolean => {
return validationStateRef.current.validatingCells.has(getCellKey(rowIndex, fieldKey)); return validationStateRef.current.validatingCells.has(getCellKey(rowIndex, fieldKey));
}, []); }, []);
// Check if a specific row is being validated
const isRowValidatingUpc = useCallback((rowIndex: number): boolean => {
return validationStateRef.current.validatingRows.has(rowIndex);
}, []);
// Get item number for a row // Get item number for a row
const getItemNumber = useCallback((rowIndex: number): string | undefined => { const getItemNumber = useCallback((rowIndex: number): string | undefined => {
return validationStateRef.current.itemNumbers.get(rowIndex); return validationStateRef.current.itemNumbers.get(rowIndex);
}, []); }, []);
// Validate a UPC value // Fetch product by UPC from API
const validateUpc = useCallback(async ( const fetchProductByUpc = useCallback(async (supplierId: string, upcValue: string) => {
upcValue: string,
rowIndex: number,
supplier: string
): Promise<UpcValidationResult> => {
// Start validating UPC and item number cells
startValidatingCell(rowIndex, 'upc');
startValidatingCell(rowIndex, 'item_number');
try { try {
// Call the UPC validation API console.log(`Fetching product for UPC ${upcValue} with supplier ${supplierId}`);
const response = await fetch(`${config.apiUrl}/import/check-upc-and-generate-sku?upc=${encodeURIComponent(upcValue)}&supplierId=${encodeURIComponent(supplier)}`); const response = await fetch(`${config.apiUrl}/import/check-upc-and-generate-sku?upc=${encodeURIComponent(upcValue)}&supplierId=${encodeURIComponent(supplierId)}`);
// Handle error responses
if (response.status === 409) { if (response.status === 409) {
const errorData = await response.json(); console.log(`UPC ${upcValue} already exists`);
return { return { error: true, message: 'UPC already exists' };
error: true,
message: `UPC already exists (${errorData.existingItemNumber})`,
data: errorData
};
} }
if (!response.ok) { if (!response.ok) {
throw new Error(`API error (${response.status})`); console.error(`API error: ${response.status}`);
return { error: true, message: `API error (${response.status})` };
} }
const result = await response.json(); // Process successful response
const data = await response.json();
if (result.success && result.itemNumber) { if (!data.success) {
// Update item number in our state return { error: true, message: data.message || 'Unknown error' };
updateItemNumber(rowIndex, result.itemNumber);
return {
error: false,
data: {
itemNumber: result.itemNumber,
...result
}
};
} }
return { return {
error: true, error: false,
message: 'Invalid response from server' data: {
itemNumber: data.itemNumber || '',
...data
}
}; };
} catch (error) {
console.error('Network error:', error);
return { error: true, message: 'Network error' };
}
}, []);
// Validate a UPC for a row - returns a promise that resolves when complete
const validateUpc = useCallback(async (rowIndex: number, supplierId: string, upcValue: string) => {
// Clear any previous validation keys for this row to avoid cancellations
const previousKeys = Array.from(validationStateRef.current.activeValidations).filter(key =>
key.startsWith(`${rowIndex}-`)
);
previousKeys.forEach(key => validationStateRef.current.activeValidations.delete(key));
// Start validation - track this with the ref to avoid race conditions
startValidatingRow(rowIndex);
startValidatingCell(rowIndex, 'item_number');
console.log(`Validating UPC: rowIndex=${rowIndex}, supplierId=${supplierId}, upc=${upcValue}`);
try {
// Create a unique key for this validation to track it
const validationKey = `${rowIndex}-${supplierId}-${upcValue}`;
validationStateRef.current.activeValidations.add(validationKey);
// IMPORTANT: First update the data with the new UPC value to prevent UI flicker
// This ensures the UPC field keeps showing the new value while validation runs
setData(prevData => {
const newData = [...prevData];
if (newData[rowIndex]) {
newData[rowIndex] = {
...newData[rowIndex],
upc: upcValue
};
}
return newData;
});
// Fetch the product by UPC
const product = await fetchProductByUpc(supplierId, upcValue);
// Check if this validation is still relevant (hasn't been superseded by another)
if (!validationStateRef.current.activeValidations.has(validationKey)) {
console.log(`Validation ${validationKey} was cancelled`);
return { success: false };
}
// Extract the item number from the API response - check for !error since API returns { error: boolean, data: any }
if (product && !product.error && product.data?.itemNumber) {
// Store this validation result
updateItemNumber(rowIndex, product.data.itemNumber);
return {
success: true,
itemNumber: product.data.itemNumber
};
} else {
// No item number found but validation was still attempted
console.log(`No item number found for UPC ${upcValue}`);
// Clear any existing item number to show validation was attempted and failed
if (validationStateRef.current.itemNumbers.has(rowIndex)) {
validationStateRef.current.itemNumbers.delete(rowIndex);
setItemNumberUpdates(new Map(validationStateRef.current.itemNumbers));
}
return { success: false };
}
} catch (error) { } catch (error) {
console.error('Error validating UPC:', error); console.error('Error validating UPC:', error);
return { return { success: false };
error: true,
message: 'Failed to validate UPC'
};
} finally { } finally {
// Stop validating both cells // End validation
stopValidatingCell(rowIndex, 'upc'); stopValidatingRow(rowIndex);
stopValidatingCell(rowIndex, 'item_number'); stopValidatingCell(rowIndex, 'item_number');
} }
}, [startValidatingCell, stopValidatingCell, updateItemNumber]); }, [fetchProductByUpc, updateItemNumber, startValidatingCell, stopValidatingCell, startValidatingRow, stopValidatingRow, setData]);
// Apply item numbers to data
const applyItemNumbersToData = useCallback((onApplied?: (updatedRowIds: number[]) => void) => {
// Create a copy of the current item numbers map to avoid race conditions
const currentItemNumbers = new Map(validationStateRef.current.itemNumbers);
// Only apply if we have any item numbers
if (currentItemNumbers.size === 0) return;
// Track updated row indices to pass to callback
const updatedRowIndices: number[] = [];
// Log for debugging
console.log(`Applying ${currentItemNumbers.size} item numbers to data`);
setData(prevData => {
// Create a new copy of the data
const newData = [...prevData];
// Update each row with its item number without affecting other fields
currentItemNumbers.forEach((itemNumber, rowIndex) => {
if (rowIndex < newData.length) {
console.log(`Setting item_number for row ${rowIndex} to ${itemNumber}`);
// Only update the item_number field, leaving other fields unchanged
newData[rowIndex] = {
...newData[rowIndex],
item_number: itemNumber
};
// Track which rows were updated
updatedRowIndices.push(rowIndex);
}
});
return newData;
});
// Call the callback if provided, after state updates are processed
if (onApplied && updatedRowIndices.length > 0) {
// Use setTimeout to ensure this happens after the state update
setTimeout(() => {
onApplied(updatedRowIndices);
}, 100); // Use 100ms to ensure the data update is fully processed
}
}, [setData]);
// Process validation queue in batches - faster processing with smaller batches
const processBatchValidation = useCallback(async () => {
if (isProcessingBatchRef.current) return;
if (validationQueueRef.current.length === 0) return;
console.log(`Processing validation batch with ${validationQueueRef.current.length} items`);
isProcessingBatchRef.current = true;
// Process in smaller batches for better UI responsiveness
const BATCH_SIZE = 5;
const queue = [...validationQueueRef.current];
validationQueueRef.current = [];
// Track if any updates were made
let updatesApplied = false;
// Track updated row indices
const updatedRows: number[] = [];
try {
// Process in small batches
for (let i = 0; i < queue.length; i += BATCH_SIZE) {
const batch = queue.slice(i, i + BATCH_SIZE);
// Process batch in parallel
const results = await Promise.all(batch.map(async ({ rowIndex, supplierId, upcValue }) => {
try {
// Skip if already validated
const cacheKey = `${supplierId}-${upcValue}`;
if (processedUpcMapRef.current.has(cacheKey)) {
const cachedItemNumber = processedUpcMapRef.current.get(cacheKey);
if (cachedItemNumber) {
console.log(`Using cached item number for row ${rowIndex}: ${cachedItemNumber}`);
updateItemNumber(rowIndex, cachedItemNumber);
updatesApplied = true;
updatedRows.push(rowIndex);
return true;
}
return false;
}
// Fetch from API
const result = await fetchProductByUpc(supplierId, upcValue);
if (!result.error && result.data?.itemNumber) {
const itemNumber = result.data.itemNumber;
// Store in cache
processedUpcMapRef.current.set(cacheKey, itemNumber);
// Update item number
updateItemNumber(rowIndex, itemNumber);
updatesApplied = true;
updatedRows.push(rowIndex);
console.log(`Set item number for row ${rowIndex} to ${itemNumber}`);
return true;
}
return false;
} catch (error) {
console.error(`Error processing row ${rowIndex}:`, error);
return false;
} finally {
// Clear validation state
stopValidatingRow(rowIndex);
}
}));
// If any updates were applied in this batch, update the data
if (results.some(Boolean) && updatesApplied) {
applyItemNumbersToData(updatedRowIds => {
console.log(`Processed batch UPC validation for rows: ${updatedRowIds.join(', ')}`);
});
updatesApplied = false;
updatedRows.length = 0; // Clear the array
}
// Small delay between batches to allow UI to update
if (i + BATCH_SIZE < queue.length) {
await new Promise(resolve => setTimeout(resolve, 10));
}
}
} catch (error) {
console.error('Error in batch processing:', error);
} finally {
isProcessingBatchRef.current = false;
// Process any new items
if (validationQueueRef.current.length > 0) {
setTimeout(processBatchValidation, 0);
}
}
}, [fetchProductByUpc, updateItemNumber, stopValidatingRow, applyItemNumbersToData]);
// For immediate processing
// Batch validate all UPCs in the data
const validateAllUPCs = useCallback(async () => {
// Skip if we've already done the initial validation
if (initialUpcValidationDoneRef.current) {
console.log('Initial UPC validation already done, skipping');
return;
}
// Mark that we've started the initial validation
initialUpcValidationDoneRef.current = true;
console.log('Starting initial UPC validation...');
// Set validation state
setIsValidatingUpc(true);
// Find all rows that have both supplier and UPC/barcode
const rowsToValidate = data
.map((row, index) => ({ row, index }))
.filter(({ row }) => {
const rowAny = row as Record<string, any>;
const hasSupplier = rowAny.supplier;
const hasUpc = rowAny.upc || rowAny.barcode;
return hasSupplier && hasUpc;
});
const totalRows = rowsToValidate.length;
console.log(`Found ${totalRows} rows with both supplier and UPC for initial validation`);
if (totalRows === 0) {
setIsValidatingUpc(false);
return;
}
// Mark all rows as being validated
const newValidatingRows = new Set(rowsToValidate.map(({ index }) => index));
validationStateRef.current.validatingRows = newValidatingRows;
setValidatingRows(newValidatingRows);
try {
// Process rows in batches for better UX
const BATCH_SIZE = 100;
const batches = [];
// Split rows into batches
for (let i = 0; i < rowsToValidate.length; i += BATCH_SIZE) {
batches.push(rowsToValidate.slice(i, i + BATCH_SIZE));
}
console.log(`Processing ${batches.length} batches for ${totalRows} rows`);
// Process each batch sequentially
for (let batchIndex = 0; batchIndex < batches.length; batchIndex++) {
const batch = batches[batchIndex];
console.log(`Processing batch ${batchIndex + 1}/${batches.length} with ${batch.length} rows`);
// Track updated rows in this batch
const batchUpdatedRows: number[] = [];
// Process all rows in current batch in parallel
await Promise.all(
batch.map(async ({ row, index }) => {
try {
const rowAny = row as Record<string, any>;
const supplierId = rowAny.supplier.toString();
const upcValue = (rowAny.upc || rowAny.barcode).toString();
console.log(`Validating UPC in initial batch: row=${index}, supplier=${supplierId}, upc=${upcValue}`);
// Mark the item_number cell as validating
startValidatingCell(index, 'item_number');
// Validate the UPC directly (don't use validateUpc to avoid duplicate UI updates)
const cacheKey = `${supplierId}-${upcValue}`;
// Check cache first
if (processedUpcMapRef.current.has(cacheKey)) {
const cachedItemNumber = processedUpcMapRef.current.get(cacheKey);
if (cachedItemNumber) {
console.log(`Using cached item number for row ${index}: ${cachedItemNumber}`);
updateItemNumber(index, cachedItemNumber);
batchUpdatedRows.push(index);
return;
}
}
// Make API call
const result = await fetchProductByUpc(supplierId, upcValue);
if (!result.error && result.data?.itemNumber) {
const itemNumber = result.data.itemNumber;
console.log(`Got item number from API for row ${index}: ${itemNumber}`);
// Cache the result
processedUpcMapRef.current.set(cacheKey, itemNumber);
// Update item number
updateItemNumber(index, itemNumber);
batchUpdatedRows.push(index);
} else {
console.warn(`No item number found for row ${index} UPC ${upcValue}`);
}
} catch (error) {
console.error(`Error validating row ${index}:`, error);
} finally {
// Clear validation state
stopValidatingCell(index, 'item_number');
stopValidatingRow(index);
}
})
);
// Apply updates for this batch
if (validationStateRef.current.itemNumbers.size > 0) {
console.log(`Applying item numbers after batch ${batchIndex + 1}`);
applyItemNumbersToData(updatedRowIds => {
console.log(`Processed initial UPC validation batch ${batchIndex + 1} for rows: ${updatedRowIds.join(', ')}`);
});
}
// Small delay between batches to update UI
if (batchIndex < batches.length - 1) {
await new Promise(resolve => setTimeout(resolve, 100));
}
}
} catch (error) {
console.error('Error in batch validation:', error);
} finally {
// Make sure all validation states are cleared
validationStateRef.current.validatingRows.clear();
setValidatingRows(new Set());
setIsValidatingUpc(false);
console.log('Completed initial UPC validation');
}
}, [data, fetchProductByUpc, updateItemNumber, startValidatingCell, stopValidatingCell, stopValidatingRow, applyItemNumbersToData]);
// Run initial UPC validation when data changes
useEffect(() => {
// Skip if there's no data or we've already done the validation
if (data.length === 0 || initialUpcValidationDoneRef.current) return;
// Run validation
validateAllUPCs();
}, [data, validateAllUPCs]);
// Return public API
return { return {
// Validation methods
validateUpc, validateUpc,
validateAllUPCs,
// Cell state
isValidatingCell, isValidatingCell,
isRowValidatingUpc,
// Row state
validatingRows: validatingRows, // Expose as a Set to components
// Item number management
getItemNumber, getItemNumber,
itemNumbers: itemNumberUpdates, applyItemNumbersToData,
validatingCells: validatingCellKeys
} // Results
} upcValidationResults,
// Initialization state
initialValidationDone: initialUpcValidationDoneRef.current
};
};

View File

@@ -1,18 +1,16 @@
import { useCallback } from 'react' import { useCallback } from 'react'
import type { Field, Fields, RowHook, TableHook } from '../../../types' import type { Field, Fields, RowHook, TableHook } from '../../../types'
import type { Meta } from '../types' import type { Meta } from '../types'
import { ErrorSources } from '../../../types' import { ErrorSources, ErrorType, ValidationError } from '../../../types'
import { RowData } from './useValidationState' import { RowData } from './useValidationState'
interface ValidationError { // Define InfoWithSource to match the expected structure
message: string // Make sure source is required (not optional)
level: 'info' | 'warning' | 'error' export interface InfoWithSource {
} message: string;
level: 'info' | 'warning' | 'error';
interface InfoWithSource { source: ErrorSources;
message: string type: ErrorType;
level: 'info' | 'warning' | 'error'
source: ErrorSources
} }
// Shared utility function for checking empty values - defined once to avoid duplication // Shared utility function for checking empty values - defined once to avoid duplication
@@ -23,6 +21,40 @@ const isEmpty = (value: any): boolean =>
(Array.isArray(value) && value.length === 0) || (Array.isArray(value) && value.length === 0) ||
(typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length === 0); (typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length === 0);
// Cache validation results to avoid running expensive validations repeatedly
const validationResultCache = new Map<string, ValidationError[]>();
// Add debounce to prevent rapid successive validations
let validateDataTimeoutId: ReturnType<typeof setTimeout> | null = null;
// Add a function to clear cache for a specific field value
export const clearValidationCacheForField = (fieldKey: string, value: any) => {
// Create a pattern to match cache keys for this field
const pattern = new RegExp(`^${fieldKey}-`);
// Find and clear matching cache entries
validationResultCache.forEach((_, key) => {
if (pattern.test(key)) {
validationResultCache.delete(key);
}
});
};
// Add a special function to clear all uniqueness validation caches
export const clearAllUniquenessCaches = () => {
// Clear cache for common unique fields
['item_number', 'upc', 'barcode', 'supplier_no', 'notions_no'].forEach(fieldKey => {
clearValidationCacheForField(fieldKey, null);
});
// Also clear any cache entries that might involve uniqueness validation
validationResultCache.forEach((_, key) => {
if (key.includes('unique')) {
validationResultCache.delete(key);
}
});
};
export const useValidation = <T extends string>( export const useValidation = <T extends string>(
fields: Fields<T>, fields: Fields<T>,
rowHook?: RowHook<T>, rowHook?: RowHook<T>,
@@ -37,6 +69,14 @@ export const useValidation = <T extends string>(
if (!field.validations) return errors if (!field.validations) return errors
// Create a cache key using field key, value, and validation rules
const cacheKey = `${field.key}-${String(value)}-${JSON.stringify(field.validations)}`;
// Check cache first to avoid redundant validation
if (validationResultCache.has(cacheKey)) {
return validationResultCache.get(cacheKey) || [];
}
field.validations.forEach(validation => { field.validations.forEach(validation => {
switch (validation.rule) { switch (validation.rule) {
case 'required': case 'required':
@@ -44,7 +84,8 @@ export const useValidation = <T extends string>(
if (isEmpty(value)) { if (isEmpty(value)) {
errors.push({ errors.push({
message: validation.errorMessage || 'This field is required', message: validation.errorMessage || 'This field is required',
level: validation.level || 'error' level: validation.level || 'error',
type: ErrorType.Required
}) })
} }
break break
@@ -60,7 +101,8 @@ export const useValidation = <T extends string>(
if (!regex.test(String(value))) { if (!regex.test(String(value))) {
errors.push({ errors.push({
message: validation.errorMessage, message: validation.errorMessage,
level: validation.level || 'error' level: validation.level || 'error',
type: ErrorType.Regex
}) })
} }
} catch (error) { } catch (error) {
@@ -71,6 +113,9 @@ export const useValidation = <T extends string>(
} }
}) })
// Store results in cache to speed up future validations
validationResultCache.set(cacheKey, errors);
return errors return errors
}, []) }, [])
@@ -98,56 +143,38 @@ export const useValidation = <T extends string>(
if (fields.some(field => String(field.key) === 'supplier') && isEmpty(row.supplier)) { if (fields.some(field => String(field.key) === 'supplier') && isEmpty(row.supplier)) {
fieldErrors['supplier'] = [{ fieldErrors['supplier'] = [{
message: 'Supplier is required', message: 'Supplier is required',
level: 'error' level: 'error',
type: ErrorType.Required
}] }]
} }
if (fields.some(field => String(field.key) === 'company') && isEmpty(row.company)) { if (fields.some(field => String(field.key) === 'company') && isEmpty(row.company)) {
fieldErrors['company'] = [{ fieldErrors['company'] = [{
message: 'Company is required', message: 'Company is required',
level: 'error' level: 'error',
type: ErrorType.Required
}] }]
} }
// Run row hook if provided // Run row hook if provided
let rowHookResult: Meta = { let rowHookResult: Meta = {
__index: row.__index || String(rowIndex), __index: row.__index || String(rowIndex)
__errors: {}
} }
if (rowHook) { if (rowHook) {
try { try {
rowHookResult = await rowHook(row, rowIndex, allRows) // Call the row hook and extract only the __index property
const result = await rowHook(row, rowIndex, allRows);
rowHookResult.__index = result.__index || rowHookResult.__index;
} catch (error) { } catch (error) {
console.error('Error in row hook:', error) console.error('Error in row hook:', error)
} }
} }
// Merge field errors and row hook errors // We no longer need to merge errors since we're not storing them in the row data
const mergedErrors: Record<string, InfoWithSource> = {} // The calling code should handle storing errors in the validationErrors Map
// Convert field errors to InfoWithSource
Object.entries(fieldErrors).forEach(([key, errors]) => {
if (errors.length > 0) {
mergedErrors[key] = {
message: errors[0].message,
level: errors[0].level,
source: ErrorSources.Row
}
}
})
// Merge row hook errors
if (rowHookResult.__errors) {
Object.entries(rowHookResult.__errors).forEach(([key, error]) => {
if (error) {
mergedErrors[key] = error
}
})
}
return { return {
__index: row.__index || String(rowIndex), __index: row.__index || String(rowIndex)
__errors: mergedErrors
} }
}, [fields, validateField, rowHook]) }, [fields, validateField, rowHook])
@@ -155,8 +182,7 @@ export const useValidation = <T extends string>(
const validateTable = useCallback(async (data: RowData<T>[]): Promise<Meta[]> => { const validateTable = useCallback(async (data: RowData<T>[]): Promise<Meta[]> => {
if (!tableHook) { if (!tableHook) {
return data.map((row, index) => ({ return data.map((row, index) => ({
__index: row.__index || String(index), __index: row.__index || String(index)
__errors: {}
})) }))
} }
@@ -165,159 +191,333 @@ export const useValidation = <T extends string>(
// Process table validation results // Process table validation results
return tableResults.map((result, index) => { return tableResults.map((result, index) => {
// Ensure errors are properly formatted
const formattedErrors: Record<string, InfoWithSource> = {}
if (result.__errors) {
Object.entries(result.__errors).forEach(([key, error]) => {
if (error) {
formattedErrors[key] = {
...error,
source: ErrorSources.Table
}
}
})
}
return { return {
__index: result.__index || data[index].__index || String(index), __index: result.__index || data[index].__index || String(index)
__errors: formattedErrors
} }
}) })
} catch (error) { } catch (error) {
console.error('Error in table hook:', error) console.error('Error in table hook:', error)
return data.map((row, index) => ({ return data.map((row, index) => ({
__index: row.__index || String(index), __index: row.__index || String(index)
__errors: {}
})) }))
} }
}, [tableHook]) }, [tableHook])
// Validate unique fields across the table // Validate unique fields across the table
const validateUnique = useCallback((data: RowData<T>[]) => { const validateUnique = useCallback((data: RowData<T>[]) => {
const uniqueErrors: Meta[] = data.map((row, index) => ({ // Create a map to store errors for each row
__index: row.__index || String(index), const uniqueErrors = new Map<number, Record<string, InfoWithSource>>();
__errors: {}
}))
// Find fields with unique validation // Find fields with unique validation
const uniqueFields = fields.filter(field => const uniqueFields = fields.filter(field =>
field.validations?.some(v => v.rule === 'unique') field.validations?.some(v => v.rule === 'unique')
) );
if (uniqueFields.length === 0) { if (uniqueFields.length === 0) {
return uniqueErrors return uniqueErrors;
} }
// Check each unique field // Check each unique field
uniqueFields.forEach(field => { uniqueFields.forEach(field => {
const { key } = field const { key } = field;
const validation = field.validations?.find(v => v.rule === 'unique') const validation = field.validations?.find(v => v.rule === 'unique');
const allowEmpty = validation?.allowEmpty ?? false const allowEmpty = validation?.allowEmpty ?? false;
const errorMessage = validation?.errorMessage || `${field.label} must be unique` const errorMessage = validation?.errorMessage || `${field.label} must be unique`;
const level = validation?.level || 'error' const level = validation?.level || 'error';
// Track values for uniqueness check // Track values for uniqueness check
const valueMap = new Map<string, number[]>() const valueMap = new Map<string, number[]>();
// Build value map // Build value map
data.forEach((row, rowIndex) => { data.forEach((row, rowIndex) => {
const value = String(row[String(key) as keyof typeof row] || '') const value = String(row[String(key) as keyof typeof row] || '');
// Skip empty values if allowed // Skip empty values if allowed
if (allowEmpty && isEmpty(value)) { if (allowEmpty && isEmpty(value)) {
return return;
} }
if (!valueMap.has(value)) { if (!valueMap.has(value)) {
valueMap.set(value, [rowIndex]) valueMap.set(value, [rowIndex]);
} else { } else {
valueMap.get(value)?.push(rowIndex) valueMap.get(value)?.push(rowIndex);
} }
}) });
// Add errors for duplicate values // Add errors for duplicate values
valueMap.forEach((rowIndexes) => { valueMap.forEach((rowIndexes) => {
if (rowIndexes.length > 1) { if (rowIndexes.length > 1) {
// Add error to all duplicate rows // Add error to all duplicate rows
rowIndexes.forEach(rowIndex => { rowIndexes.forEach(rowIndex => {
const rowErrors = uniqueErrors[rowIndex].__errors || {} // Get existing errors for this row or create a new object
const rowErrors = uniqueErrors.get(rowIndex) || {};
rowErrors[String(key)] = { rowErrors[String(key)] = {
message: errorMessage, message: errorMessage,
level, level,
source: ErrorSources.Table source: ErrorSources.Table,
} type: ErrorType.Unique
};
uniqueErrors[rowIndex].__errors = rowErrors uniqueErrors.set(rowIndex, rowErrors);
}) });
} }
}) });
}) });
return uniqueErrors return uniqueErrors;
}, [fields]) }, [fields]);
// Run complete validation // Additional function to explicitly validate uniqueness for specified fields
const validateData = useCallback(async (data: RowData<T>[]) => { const validateUniqueField = useCallback((data: RowData<T>[], fieldKey: string) => {
// Use the shared isEmpty function // Field keys that need special handling for uniqueness
const uniquenessFields = ['item_number', 'upc', 'barcode', 'supplier_no', 'notions_no'];
// Step 1: Run field and row validation // If the field doesn't need uniqueness validation, return empty errors
const rowValidations = await Promise.all( if (!uniquenessFields.includes(fieldKey)) {
data.map((row, index) => validateRow(row, index, data)) const field = fields.find(f => String(f.key) === fieldKey);
) if (!field || !field.validations?.some(v => v.rule === 'unique')) {
return new Map<number, Record<string, InfoWithSource>>();
}
}
// Step 2: Run unique validations // Create map to track errors
const uniqueValidations = validateUnique(data) const uniqueErrors = new Map<number, Record<string, InfoWithSource>>();
// Step 3: Run table hook // Find the field definition
const tableValidations = await validateTable(data) const field = fields.find(f => String(f.key) === fieldKey);
if (!field) return uniqueErrors;
// Merge all validation results // Get validation properties
return data.map((row, index) => { const validation = field.validations?.find(v => v.rule === 'unique');
const rowValidation = rowValidations[index] const allowEmpty = validation?.allowEmpty ?? false;
const uniqueValidation = uniqueValidations[index] const errorMessage = validation?.errorMessage || `${field.label} must be unique`;
const tableValidation = tableValidations[index] const level = validation?.level || 'error';
// Start with the original data // Track values for uniqueness check
const newRow = { ...row } const valueMap = new Map<string, number[]>();
// Combine all errors // Build value map
const combinedErrors = { data.forEach((row, rowIndex) => {
...(rowValidation.__errors || {}), const value = String(row[fieldKey as keyof typeof row] || '');
...(uniqueValidation.__errors || {}),
...(tableValidation.__errors || {}) // Skip empty values if allowed
if (allowEmpty && isEmpty(value)) {
return;
} }
// Filter out "required" errors for fields that have values if (!valueMap.has(value)) {
const filteredErrors: Record<string, InfoWithSource> = {} valueMap.set(value, [rowIndex]);
} else {
valueMap.get(value)?.push(rowIndex);
}
});
Object.entries(combinedErrors).forEach(([key, error]) => { // Add errors for duplicate values
const fieldValue = row[key as keyof typeof row] valueMap.forEach((rowIndexes, value) => {
if (rowIndexes.length > 1) {
// Skip empty values
if (!value || value.trim() === '') return;
// If the field has a value and the only error is "required", skip it // Add error to all duplicate rows
if (!isEmpty(fieldValue) && rowIndexes.forEach(rowIndex => {
error && // Create errors object if needed
typeof error === 'object' && if (!uniqueErrors.has(rowIndex)) {
'message' in error && uniqueErrors.set(rowIndex, {});
error.message?.toLowerCase().includes('required')) { }
return
// Add error for this field
uniqueErrors.get(rowIndex)![fieldKey] = {
message: errorMessage,
level: level as 'info' | 'warning' | 'error',
source: ErrorSources.Table,
type: ErrorType.Unique
};
});
}
});
return uniqueErrors;
}, [fields]);
// Run complete validation
const validateData = useCallback(async (data: RowData<T>[], fieldToUpdate?: { rowIndex: number, fieldKey: string }) => {
const validationErrors = new Map<number, Record<string, InfoWithSource>>();
// If we're updating a specific field, only validate that field for that row
if (fieldToUpdate) {
const { rowIndex, fieldKey } = fieldToUpdate;
// Special handling for fields that often update item_number
const triggersItemNumberValidation = fieldKey === 'upc' || fieldKey === 'barcode' || fieldKey === 'supplier';
// If updating a uniqueness field or field that affects item_number, clear ALL related validation caches
const isUniqueField = fieldKey === 'upc' || fieldKey === 'item_number' ||
fieldKey === 'supplier_no' || fieldKey === 'notions_no' ||
fieldKey === 'name' || triggersItemNumberValidation;
// Force cache clearing for uniqueness-validated fields to ensure fresh validation
if (isUniqueField) {
console.log(`Clearing validation cache for uniqueness field: ${fieldKey}`);
clearValidationCacheForField(fieldKey, null);
// If a field that might affect item_number, also clear item_number cache
if (triggersItemNumberValidation) {
console.log('Also clearing item_number validation cache');
clearValidationCacheForField('item_number', null);
} }
}
filteredErrors[key] = error as InfoWithSource if (rowIndex >= 0 && rowIndex < data.length) {
}) const row = data[rowIndex];
newRow.__errors = Object.keys(filteredErrors).length > 0 ? filteredErrors : undefined // Find the field definition
const field = fields.find(f => String(f.key) === fieldKey);
return newRow if (field) {
}) // Validate just this field for this row
}, [validateRow, validateUnique, validateTable]) const value = row[fieldKey as keyof typeof row];
const errors = validateField(value, field as Field<T>);
if (errors.length > 0) {
// Store the validation error
validationErrors.set(rowIndex, {
[fieldKey]: {
message: errors[0].message,
level: errors[0].level as 'info' | 'warning' | 'error',
source: ErrorSources.Row,
type: errors[0].type
}
});
}
// Check if the field requires uniqueness validation or if it's item_number after UPC/Supplier change
const needsUniquenessCheck = isUniqueField ||
field.validations?.some(v => v.rule === 'unique');
if (needsUniquenessCheck) {
console.log(`Running immediate uniqueness validation for field ${fieldKey}`);
// For item_number updated via UPC validation, or direct UPC update, check both fields
if (fieldKey === 'item_number' || fieldKey === 'upc' || fieldKey === 'barcode') {
// Validate both item_number and UPC/barcode fields for uniqueness
const itemNumberUniqueErrors = validateUniqueField(data, 'item_number');
const upcUniqueErrors = validateUniqueField(data, fieldKey === 'item_number' ? 'upc' : fieldKey);
// Combine the errors
itemNumberUniqueErrors.forEach((errors, rowIdx) => {
if (!validationErrors.has(rowIdx)) {
validationErrors.set(rowIdx, {});
}
Object.assign(validationErrors.get(rowIdx)!, errors);
});
upcUniqueErrors.forEach((errors, rowIdx) => {
if (!validationErrors.has(rowIdx)) {
validationErrors.set(rowIdx, {});
}
Object.assign(validationErrors.get(rowIdx)!, errors);
});
} else {
// Normal uniqueness validation for other fields
const uniqueErrors = validateUniqueField(data, fieldKey);
// Add unique errors to validation errors
uniqueErrors.forEach((errors, rowIdx) => {
if (!validationErrors.has(rowIdx)) {
validationErrors.set(rowIdx, {});
}
Object.assign(validationErrors.get(rowIdx)!, errors);
});
}
}
}
}
} else {
// Full validation - all fields for all rows
console.log('Running full validation for all fields and rows');
// Clear validation cache for full validation
validationResultCache.clear();
// Process each row for field-level validations
for (let rowIndex = 0; rowIndex < data.length; rowIndex++) {
const row = data[rowIndex];
let rowErrors: Record<string, InfoWithSource> = {};
// Validate all fields for this row
fields.forEach(field => {
const fieldKey = String(field.key);
const value = row[fieldKey as keyof typeof row];
const errors = validateField(value, field as Field<T>);
if (errors.length > 0) {
rowErrors[fieldKey] = {
message: errors[0].message,
level: errors[0].level as 'info' | 'warning' | 'error',
source: ErrorSources.Row,
type: errors[0].type
};
}
});
// Add row to validationErrors if it has any errors
if (Object.keys(rowErrors).length > 0) {
validationErrors.set(rowIndex, rowErrors);
}
}
// Get fields requiring uniqueness validation
const uniqueFields = fields.filter(field =>
field.validations?.some(v => v.rule === 'unique')
);
// Also add standard unique fields that might not be explicitly marked as unique
const standardUniqueFields = ['item_number', 'upc', 'barcode', 'supplier_no', 'notions_no'];
// Combine all fields that need uniqueness validation
const allUniqueFieldKeys = new Set([
...uniqueFields.map(field => String(field.key)),
...standardUniqueFields
]);
// Log uniqueness validation fields
console.log('Validating unique fields:', Array.from(allUniqueFieldKeys));
// Run uniqueness validation for each unique field
allUniqueFieldKeys.forEach(fieldKey => {
// Check if this field exists in the data
const hasField = data.some(row => fieldKey in row);
if (!hasField) return;
const uniqueErrors = validateUniqueField(data, fieldKey);
// Add unique errors to validation errors
uniqueErrors.forEach((errors, rowIdx) => {
if (!validationErrors.has(rowIdx)) {
validationErrors.set(rowIdx, {});
}
Object.assign(validationErrors.get(rowIdx)!, errors);
});
});
console.log('Uniqueness validation complete');
}
return {
data,
validationErrors
};
}, [fields, validateField, validateUniqueField]);
return { return {
validateData, validateData,
validateField, validateField,
validateRow, validateRow,
validateTable, validateTable,
validateUnique validateUnique,
validateUniqueField,
clearValidationCacheForField,
clearAllUniquenessCaches
} }
} }

View File

@@ -1,5 +1,5 @@
import { InfoWithSource } from "../../types" import { InfoWithSource } from "../../types"
export type Meta = { __index: string; __errors?: Error | null } export type Meta = { __index: string }
export type Error = { [key: string]: InfoWithSource } export type Error = { [key: string]: InfoWithSource }
export type Errors = { [id: string]: Error } export type Errors = { [id: string]: Error }

View File

@@ -1,16 +1,17 @@
import { InfoWithSource, ErrorLevel } from "../../../types" import { ErrorLevel, ErrorSources, ErrorType as ValidationErrorType } from "../../../types"
// Define our own Error type that's compatible with the original // Define our own Error type that's compatible with the original
export interface ErrorType { export interface ErrorType {
message: string; message: string;
level: ErrorLevel; level: ErrorLevel;
source?: string; source?: ErrorSources;
type: ValidationErrorType;
} }
// Export a namespace to make it accessible at runtime // Export a namespace to make it accessible at runtime
export const ErrorTypes = { export const ErrorTypes = {
createError: (message: string, level: ErrorLevel = 'error', source: string = 'row'): ErrorType => { createError: (message: string, level: ErrorLevel = 'error', source: ErrorSources = ErrorSources.Row, type: ValidationErrorType = ValidationErrorType.Custom): ErrorType => {
return { message, level, source }; return { message, level, source, type };
} }
}; };
@@ -20,5 +21,4 @@ export interface Errors { [id: string]: ErrorType[] }
// Make our Meta type match the original for compatibility // Make our Meta type match the original for compatibility
export interface Meta { export interface Meta {
__index?: string; __index?: string;
__errors?: Record<string, ErrorType[] | ErrorType | InfoWithSource | InfoWithSource[] | null>;
} }

View File

@@ -1,12 +1,11 @@
import type { Data, Fields, Info, RowHook, TableHook } from "../../../types" import type { Data, Fields, Info, RowHook, TableHook } from "../../../types"
import type { Meta, Error, Errors } from "../types" import type { Meta, Errors } from "../types"
import { v4 } from "uuid" import { v4 } from "uuid"
import { ErrorSources } from "../../../types" import { ErrorSources, ErrorType } from "../../../types"
type DataWithMeta<T extends string> = Data<T> & Meta & { type DataWithMeta<T extends string> = Data<T> & Meta & {
__index?: string; __index?: string;
__errors?: Error | null;
} }
export const addErrorsAndRunHooks = async <T extends string>( export const addErrorsAndRunHooks = async <T extends string>(
@@ -18,10 +17,10 @@ export const addErrorsAndRunHooks = async <T extends string>(
): Promise<DataWithMeta<T>[]> => { ): Promise<DataWithMeta<T>[]> => {
const errors: Errors = {} const errors: Errors = {}
const addError = (source: ErrorSources, rowIndex: number, fieldKey: string, error: Info) => { const addError = (source: ErrorSources, rowIndex: number, fieldKey: string, error: Info, type: ErrorType = ErrorType.Custom) => {
errors[rowIndex] = { errors[rowIndex] = {
...errors[rowIndex], ...errors[rowIndex],
[fieldKey]: { ...error, source }, [fieldKey]: { ...error, source, type },
} }
} }
@@ -89,7 +88,7 @@ export const addErrorsAndRunHooks = async <T extends string>(
addError(ErrorSources.Table, index, fieldKey, { addError(ErrorSources.Table, index, fieldKey, {
level: validation.level || "error", level: validation.level || "error",
message: validation.errorMessage || "Field must be unique", message: validation.errorMessage || "Field must be unique",
}) }, ErrorType.Unique)
} }
}) })
break break
@@ -103,7 +102,7 @@ export const addErrorsAndRunHooks = async <T extends string>(
addError(ErrorSources.Row, realIndex, fieldKey, { addError(ErrorSources.Row, realIndex, fieldKey, {
level: validation.level || "error", level: validation.level || "error",
message: validation.errorMessage || "Field is required", message: validation.errorMessage || "Field is required",
}) }, ErrorType.Required)
} }
}) })
break break
@@ -120,7 +119,7 @@ export const addErrorsAndRunHooks = async <T extends string>(
level: validation.level || "error", level: validation.level || "error",
message: message:
validation.errorMessage || `Field did not match the regex /${validation.value}/${validation.flags} `, validation.errorMessage || `Field did not match the regex /${validation.value}/${validation.flags} `,
}) }, ErrorType.Regex)
} }
}) })
break break
@@ -129,48 +128,15 @@ export const addErrorsAndRunHooks = async <T extends string>(
}) })
}) })
return processedData.map((value, index) => { return processedData.map((value) => {
// This is required only for table. Mutates to prevent needless rerenders // This is required only for table. Mutates to prevent needless rerenders
const result: DataWithMeta<T> = { ...value } const result: DataWithMeta<T> = { ...value }
if (!result.__index) { if (!result.__index) {
result.__index = v4() result.__index = v4()
} }
// If we are validating all indexes, or we did full validation on this row - apply all errors // We no longer store errors in the row data
if (!changedRowIndexes || changedRowIndexes.includes(index)) { // The errors are now only stored in the validationErrors Map
if (errors[index]) {
return { ...result, __errors: errors[index] }
}
if (!errors[index] && result.__errors) {
return { ...result, __errors: null }
}
}
// if we have not validated this row, keep it's row errors but apply global error changes
else {
// at this point errors[index] contains only table source errors, previous row and table errors are in value.__errors
const hasRowErrors =
result.__errors && Object.values(result.__errors).some((error) => error.source === ErrorSources.Row)
if (!hasRowErrors) {
if (errors[index]) {
return { ...result, __errors: errors[index] }
}
return result
}
const errorsWithoutTableError = Object.entries(result.__errors!).reduce((acc, [key, value]) => {
if (value.source === ErrorSources.Row) {
acc[key] = value
}
return acc
}, {} as Error)
const newErrors = { ...errorsWithoutTableError, ...errors[index] }
return { ...result, __errors: newErrors }
}
return result return result
}) })
} }

View File

@@ -1,5 +1,5 @@
import { Field, Data, ErrorSources } from '../../../types' import { Field, Data, ErrorSources, ErrorType as ValidationErrorType } from '../../../types'
import { ErrorType } from '../types/index' import { ErrorType } from '../types'
/** /**
* Formats a price value to a consistent format * Formats a price value to a consistent format
@@ -80,17 +80,20 @@ export const validateRegex = (value: any, regex: string, flags?: string): boolea
* @param message Error message * @param message Error message
* @param level Error level * @param level Error level
* @param source Error source * @param source Error source
* @param type Error type
* @returns Error object * @returns Error object
*/ */
export const createError = ( export const createError = (
message: string, message: string,
level: 'info' | 'warning' | 'error' = 'error', level: 'info' | 'warning' | 'error' = 'error',
source: ErrorSources = ErrorSources.Row source: ErrorSources = ErrorSources.Row,
type: ValidationErrorType = ValidationErrorType.Custom
): ErrorType => { ): ErrorType => {
return { return {
message, message,
level, level,
source source,
type
} as ErrorType } as ErrorType
} }
@@ -136,7 +139,8 @@ export const validateSpecialFields = <T extends string>(row: Data<T>): Record<st
errors['supplier'] = [{ errors['supplier'] = [{
message: 'Supplier is required', message: 'Supplier is required',
level: 'error', level: 'error',
source: ErrorSources.Row source: ErrorSources.Row,
type: ValidationErrorType.Required
}] }]
} }
@@ -145,7 +149,8 @@ export const validateSpecialFields = <T extends string>(row: Data<T>): Record<st
errors['company'] = [{ errors['company'] = [{
message: 'Company is required', message: 'Company is required',
level: 'error', level: 'error',
source: ErrorSources.Row source: ErrorSources.Row,
type: ValidationErrorType.Required
}] }]
} }

View File

@@ -163,18 +163,38 @@ export type Info = {
} }
export enum ErrorSources { export enum ErrorSources {
Table = "table", Row = 'row',
Row = "row", Table = 'table',
Api = 'api',
Upc = 'upc'
}
// Add a standardized error type enum
export enum ErrorType {
Required = 'required',
Regex = 'regex',
Unique = 'unique',
Custom = 'custom',
Api = 'api'
}
// Add a standardized error interface
export interface ValidationError {
message: string;
level: 'info' | 'warning' | 'error';
source?: ErrorSources;
type: ErrorType;
} }
/* /*
Source determines whether the error is from the full table or row validation Source determines whether the error is from the full table or row validation
Table validation is tableHook and "unique" validation Table validation is tableHook and "unique" validation
Row validation is rowHook and all other validations Row validation is rowHook and all other validations
it is used to determine if row.__errors should be updated or not depending on different validations It is used to determine how errors should be stored in the validationErrors Map
*/ */
export type InfoWithSource = Info & { export type InfoWithSource = Info & {
source: ErrorSources source: ErrorSources;
type: ErrorType;
} }
export type Result<T extends string> = { export type Result<T extends string> = {

View File

@@ -40,7 +40,7 @@ const BASE_IMPORT_FIELDS = [
description: "Product line", description: "Product line",
alternateMatches: ["collection"], alternateMatches: ["collection"],
fieldType: { fieldType: {
type: "select", type: "select" as const,
options: [], // Will be populated dynamically based on company selection options: [], // Will be populated dynamically based on company selection
}, },
width: 220, width: 220,
@@ -51,7 +51,7 @@ const BASE_IMPORT_FIELDS = [
key: "subline", key: "subline",
description: "Product sub-line", description: "Product sub-line",
fieldType: { fieldType: {
type: "select", type: "select" as const,
options: [], // Will be populated dynamically based on line selection options: [], // Will be populated dynamically based on line selection
}, },
width: 220, width: 220,

View File

@@ -8,7 +8,6 @@ import config from "../config";
import { Loader2, Box } from "lucide-react"; import { Loader2, Box } from "lucide-react";
import { motion } from "motion/react"; import { motion } from "motion/react";
const isDev = process.env.NODE_ENV === "development";
export function Login() { export function Login() {
const [username, setUsername] = useState(""); const [username, setUsername] = useState("");

339
package-lock.json generated
View File

@@ -8,6 +8,7 @@
"@dnd-kit/core": "^6.3.1", "@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0", "@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2", "@dnd-kit/utilities": "^3.2.2",
"@modelcontextprotocol/server-sequential-thinking": "^0.6.2",
"diff": "^7.0.0", "diff": "^7.0.0",
"shadcn": "^1.0.0" "shadcn": "^1.0.0"
}, },
@@ -69,6 +70,31 @@
"react": ">=16.8.0" "react": ">=16.8.0"
} }
}, },
"node_modules/@modelcontextprotocol/sdk": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-0.5.0.tgz",
"integrity": "sha512-RXgulUX6ewvxjAG0kOpLMEdXXWkzWgaoCGaA2CwNW7cQCIphjpJhjpHSiaPdVCnisjRF/0Cm9KWHUuIoeiAblQ==",
"license": "MIT",
"dependencies": {
"content-type": "^1.0.5",
"raw-body": "^3.0.0",
"zod": "^3.23.8"
}
},
"node_modules/@modelcontextprotocol/server-sequential-thinking": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/server-sequential-thinking/-/server-sequential-thinking-0.6.2.tgz",
"integrity": "sha512-CgYRG6PPPldPk60Oi/jmPNKQ8hUg1V2rqlBRWsRvB5/QIgb+kyd6dySh0WfEoWC5kT+7avM2qDD8SKEBBHRkOQ==",
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "0.5.0",
"chalk": "^5.3.0",
"yargs": "^17.7.2"
},
"bin": {
"mcp-server-sequential-thinking": "dist/index.js"
}
},
"node_modules/@types/diff": { "node_modules/@types/diff": {
"version": "7.0.1", "version": "7.0.1",
"resolved": "https://registry.npmjs.org/@types/diff/-/diff-7.0.1.tgz", "resolved": "https://registry.npmjs.org/@types/diff/-/diff-7.0.1.tgz",
@@ -76,6 +102,101 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/chalk": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
"integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==",
"license": "MIT",
"engines": {
"node": "^12.17.0 || ^14.13 || >=16.0.0"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"license": "ISC",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.1",
"wrap-ansi": "^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT"
},
"node_modules/content-type": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/diff": { "node_modules/diff": {
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz",
@@ -85,6 +206,88 @@
"node": ">=0.3.1" "node": ">=0.3.1"
} }
}, },
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT"
},
"node_modules/escalade": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"license": "ISC",
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/http-errors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
"license": "MIT",
"dependencies": {
"depd": "2.0.0",
"inherits": "2.0.4",
"setprototypeof": "1.2.0",
"statuses": "2.0.1",
"toidentifier": "1.0.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/raw-body": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
"integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
"license": "MIT",
"dependencies": {
"bytes": "3.1.2",
"http-errors": "2.0.0",
"iconv-lite": "0.6.3",
"unpipe": "1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/react": { "node_modules/react": {
"version": "19.0.0", "version": "19.0.0",
"resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz",
@@ -108,6 +311,21 @@
"react": "^19.0.0" "react": "^19.0.0"
} }
}, },
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
"node_modules/scheduler": { "node_modules/scheduler": {
"version": "0.25.0", "version": "0.25.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz",
@@ -115,12 +333,62 @@
"license": "MIT", "license": "MIT",
"peer": true "peer": true
}, },
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"license": "ISC"
},
"node_modules/shadcn": { "node_modules/shadcn": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/shadcn/-/shadcn-1.0.0.tgz", "resolved": "https://registry.npmjs.org/shadcn/-/shadcn-1.0.0.tgz",
"integrity": "sha512-kCxBIBiPS83WxrWkOQHamWpr9XlLtOtOlJM6QX90h9A5xZCBMhxu4ibcNT2ZnzZLdexkYbQrnijfPKdOsZxOpA==", "integrity": "sha512-kCxBIBiPS83WxrWkOQHamWpr9XlLtOtOlJM6QX90h9A5xZCBMhxu4ibcNT2ZnzZLdexkYbQrnijfPKdOsZxOpA==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
"license": "MIT",
"engines": {
"node": ">=0.6"
}
},
"node_modules/ts-essentials": { "node_modules/ts-essentials": {
"version": "10.0.4", "version": "10.0.4",
"resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-10.0.4.tgz", "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-10.0.4.tgz",
@@ -141,6 +409,77 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD" "license": "0BSD"
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
"license": "ISC",
"engines": {
"node": ">=10"
}
},
"node_modules/yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
"license": "MIT",
"dependencies": {
"cliui": "^8.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
"string-width": "^4.2.3",
"y18n": "^5.0.5",
"yargs-parser": "^21.1.1"
},
"engines": {
"node": ">=12"
}
},
"node_modules/yargs-parser": {
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/zod": {
"version": "3.24.2",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz",
"integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
} }
} }
} }

View File

@@ -3,6 +3,7 @@
"@dnd-kit/core": "^6.3.1", "@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0", "@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2", "@dnd-kit/utilities": "^3.2.2",
"@modelcontextprotocol/server-sequential-thinking": "^0.6.2",
"diff": "^7.0.0", "diff": "^7.0.0",
"shadcn": "^1.0.0" "shadcn": "^1.0.0"
}, },

View File

@@ -0,0 +1,91 @@
import React, { useMemo } from 'react';
import { ColumnDef } from '@tanstack/react-table';
import { RowData } from '@tanstack/react-table';
import { Field } from '@/types';
import { MemoizedCell } from '@/components/MemoizedCell';
// Memoize field columns with stable handlers
const fieldColumns = useMemo(() => fields.map((field): ColumnDef<RowData<T>, any> | null => {
// Don't filter out disabled fields, just pass the disabled state to the cell component
const fieldWidth = field.width || (
field.fieldType.type === "checkbox" ? 80 :
field.fieldType.type === "select" ? 150 :
field.fieldType.type === "multi-select" ? 200 :
(field.fieldType.type === "input" || field.fieldType.type === "multi-input") &&
(field.fieldType as any).multiline ? 300 :
150
);
const fieldKey = String(field.key);
// Get cached options for this field
const fieldOptions = optionsCache.get(fieldKey) || [];
return {
accessorKey: fieldKey,
header: field.label || fieldKey,
size: fieldWidth,
cell: ({ row }) => {
// Get row-specific options for line and subline fields
let options = fieldOptions;
const rowId = row.original.__index;
if (fieldKey === 'line' && rowId && rowProductLines[rowId]) {
options = rowProductLines[rowId];
} else if (fieldKey === 'subline' && rowId && rowSublines[rowId]) {
options = rowSublines[rowId];
}
// Determine if this cell is in loading state - use a clear consistent approach
let isLoading = false;
// Check the validatingCells Set first (for item_number and other fields)
const cellLoadingKey = `${row.index}-${fieldKey}`;
if (validatingCells.has(cellLoadingKey)) {
isLoading = true;
}
// Add loading state for line/subline fields
else if (fieldKey === 'line' && rowId && isLoadingLines[rowId]) {
isLoading = true;
}
else if (fieldKey === 'subline' && rowId && isLoadingSublines[rowId]) {
isLoading = true;
}
// Get validation errors for this cell
const cellErrors = validationErrors.get(row.index)?.[fieldKey] || [];
// Create a copy of the field with guaranteed field type for select fields
let fieldWithType = field;
// Ensure line and subline fields always have the correct fieldType
if (fieldKey === 'line' || fieldKey === 'subline') {
fieldWithType = {
...field,
fieldType: {
type: 'select',
options: options
}
};
}
return (
<MemoizedCell
field={fieldWithType as Field<string>}
value={row.original[field.key as keyof typeof row.original]}
onChange={(value) => handleFieldUpdate(row.index, field.key as T, value)}
errors={cellErrors}
isValidating={isLoading}
fieldKey={fieldKey}
options={options}
itemNumber={itemNumbers.get(row.index)}
width={fieldWidth}
rowIndex={row.index}
copyDown={(endRowIndex?: number) => handleCopyDown(row.index, field.key as string, endRowIndex)}
totalRows={data.length}
/>
);
}
};
}).filter((col): col is ColumnDef<RowData<T>, any> => col !== null),
[fields, validationErrors, validatingCells, itemNumbers, handleFieldUpdate, handleCopyDown, optionsCache, data.length, rowProductLines, rowSublines, isLoadingLines, isLoadingSublines]);