Validation step tweaks, remove remaining references to old version
This commit is contained in:
@@ -1394,7 +1394,7 @@ router.get('/check-upc-and-generate-sku', async (req, res) => {
|
||||
|
||||
if (upcCheck.length > 0) {
|
||||
return res.status(409).json({
|
||||
error: 'UPC already exists',
|
||||
error: 'A product with this UPC already exists',
|
||||
existingProductId: upcCheck[0].pid,
|
||||
existingItemNumber: upcCheck[0].itemnumber
|
||||
});
|
||||
|
||||
@@ -13,7 +13,7 @@ import { useRsi } from "../hooks/useRsi"
|
||||
import type { RawData, Data } from "../types"
|
||||
import { Progress } from "@/components/ui/progress"
|
||||
import { useToast } from "@/hooks/use-toast"
|
||||
import { addErrorsAndRunHooks } from "./ValidationStepNew/utils/dataMutations"
|
||||
import { addErrorsAndRunHooks } from "./ValidationStep/utils/dataMutations"
|
||||
|
||||
export enum StepType {
|
||||
upload = "upload",
|
||||
|
||||
@@ -324,20 +324,55 @@ const CellWrapper = memo(({
|
||||
|
||||
// Only check uniqueness if value is not empty
|
||||
if (stringValue !== '') {
|
||||
const isDuplicate = rows.some((row, idx) => {
|
||||
if (idx === rowIndex) return false;
|
||||
const otherValue = String(row[field.key] ?? '').toLowerCase().trim();
|
||||
return otherValue === stringValue;
|
||||
// Find ALL rows with the same value (including current row)
|
||||
const duplicateRowIndices: number[] = [];
|
||||
rows.forEach((row, idx) => {
|
||||
// For current row, use the new value being saved; for other rows, use stored value
|
||||
const cellValue = idx === rowIndex ? valueToSave : row[field.key];
|
||||
const otherValue = String(cellValue ?? '').toLowerCase().trim();
|
||||
if (otherValue === stringValue) {
|
||||
duplicateRowIndices.push(idx);
|
||||
}
|
||||
});
|
||||
|
||||
const isDuplicate = duplicateRowIndices.length > 1;
|
||||
|
||||
if (isDuplicate) {
|
||||
setError(rowIndex, field.key, {
|
||||
// Set error on ALL duplicate rows (bidirectional)
|
||||
const errorObj = {
|
||||
message: (uniqueValidation as { errorMessage?: string }).errorMessage || 'Must be unique',
|
||||
level: (uniqueValidation as { level?: 'error' | 'warning' | 'info' }).level || 'error',
|
||||
source: ErrorSource.Table,
|
||||
type: ErrorType.Unique,
|
||||
};
|
||||
duplicateRowIndices.forEach((idx) => {
|
||||
setError(idx, field.key, errorObj);
|
||||
});
|
||||
hasError = true;
|
||||
} else {
|
||||
// Value is now unique - clear any existing unique errors on other rows
|
||||
// that might have had this value before
|
||||
rows.forEach((row, idx) => {
|
||||
if (idx !== rowIndex) {
|
||||
const existingErrors = useValidationStore.getState().errors.get(idx);
|
||||
const fieldErrors = existingErrors?.[field.key];
|
||||
if (fieldErrors?.some(e => e.type === ErrorType.Unique)) {
|
||||
// Re-validate this row's uniqueness
|
||||
const otherValue = String(row[field.key] ?? '').toLowerCase().trim();
|
||||
if (otherValue !== '') {
|
||||
const stillHasDuplicate = rows.some((r, i) => {
|
||||
if (i === idx) return false;
|
||||
const cellValue = i === rowIndex ? valueToSave : r[field.key];
|
||||
const v = String(cellValue ?? '').toLowerCase().trim();
|
||||
return v === otherValue;
|
||||
});
|
||||
if (!stillHasDuplicate) {
|
||||
clearFieldError(idx, field.key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -348,6 +383,69 @@ const CellWrapper = memo(({
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger product lines fetch if company changed
|
||||
if (field.key === 'company' && valueToSave) {
|
||||
const companyId = String(valueToSave);
|
||||
const state = useValidationStore.getState();
|
||||
const cached = state.productLinesCache.get(companyId);
|
||||
|
||||
if (!cached) {
|
||||
// Start loading state and fetch product lines
|
||||
state.setLoadingProductLines(companyId, true);
|
||||
fetch(`/api/import/product-lines/${companyId}`)
|
||||
.then(res => res.json())
|
||||
.then(lines => {
|
||||
const opts = lines.map((line: { name?: string; label?: string; value?: string | number; id?: string | number }) => ({
|
||||
label: line.name || line.label || String(line.value || line.id),
|
||||
value: String(line.value || line.id),
|
||||
}));
|
||||
state.setProductLines(companyId, opts);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Error prefetching product lines:', err);
|
||||
state.setProductLines(companyId, []);
|
||||
})
|
||||
.finally(() => {
|
||||
state.setLoadingProductLines(companyId, false);
|
||||
});
|
||||
}
|
||||
|
||||
// Clear line and subline when company changes (they're no longer valid)
|
||||
updateCell(rowIndex, 'line', '');
|
||||
updateCell(rowIndex, 'subline', '');
|
||||
}
|
||||
|
||||
// Trigger sublines fetch if line changed
|
||||
if (field.key === 'line' && valueToSave) {
|
||||
const lineId = String(valueToSave);
|
||||
const state = useValidationStore.getState();
|
||||
const cached = state.sublinesCache.get(lineId);
|
||||
|
||||
if (!cached) {
|
||||
// Start loading state and fetch sublines
|
||||
state.setLoadingSublines(lineId, true);
|
||||
fetch(`/api/import/sublines/${lineId}`)
|
||||
.then(res => res.json())
|
||||
.then(sublines => {
|
||||
const opts = sublines.map((subline: { name?: string; label?: string; value?: string | number; id?: string | number }) => ({
|
||||
label: subline.name || subline.label || String(subline.value || subline.id),
|
||||
value: String(subline.value || subline.id),
|
||||
}));
|
||||
state.setSublines(lineId, opts);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Error prefetching sublines:', err);
|
||||
state.setSublines(lineId, []);
|
||||
})
|
||||
.finally(() => {
|
||||
state.setLoadingSublines(lineId, false);
|
||||
});
|
||||
}
|
||||
|
||||
// Clear subline when line changes (it's no longer valid)
|
||||
updateCell(rowIndex, 'subline', '');
|
||||
}
|
||||
|
||||
// Trigger UPC validation if supplier or UPC changed
|
||||
if (field.key === 'supplier' || field.key === 'upc') {
|
||||
const currentRow = useValidationStore.getState().rows[rowIndex];
|
||||
@@ -379,7 +477,7 @@ const CellWrapper = memo(({
|
||||
if (response.status === 409) {
|
||||
// UPC already exists
|
||||
setError(rowIndex, 'upc', {
|
||||
message: 'UPC already exists in database',
|
||||
message: 'A product with this UPC already exists',
|
||||
level: 'error',
|
||||
source: ErrorSource.Upc,
|
||||
type: ErrorType.Unique,
|
||||
@@ -707,7 +805,7 @@ const TemplateCell = memo(({ rowIndex, currentTemplateId, defaultBrand }: Templa
|
||||
if (response.status === 409) {
|
||||
// UPC already exists
|
||||
setError(rowIndex, 'upc', {
|
||||
message: 'UPC already exists in database',
|
||||
message: 'A product with this UPC already exists',
|
||||
level: 'error',
|
||||
source: ErrorSource.Upc,
|
||||
type: ErrorType.Unique,
|
||||
@@ -809,6 +907,25 @@ const VirtualRow = memo(({
|
||||
useCallback((state) => state.validatingCells.has(`${rowIndex}-item_number`), [rowIndex])
|
||||
);
|
||||
|
||||
// Subscribe to loading states for line/subline fields
|
||||
// These need reactive updates so loading spinners clear when API calls complete
|
||||
const company = rowData?.company;
|
||||
const line = rowData?.line;
|
||||
|
||||
const isLoadingProductLinesForCompany = useValidationStore(
|
||||
useCallback(
|
||||
(state) => (company ? state.loadingProductLines.has(String(company)) : false),
|
||||
[company]
|
||||
)
|
||||
);
|
||||
|
||||
const isLoadingSublineForLine = useValidationStore(
|
||||
useCallback(
|
||||
(state) => (line ? state.loadingSublines.has(String(line)) : false),
|
||||
[line]
|
||||
)
|
||||
);
|
||||
|
||||
// Subscribe to selection status
|
||||
const isSelected = useValidationStore(
|
||||
useCallback((state) => state.selectedRows.has(rowId), [rowId])
|
||||
@@ -821,8 +938,7 @@ const VirtualRow = memo(({
|
||||
|
||||
// DON'T subscribe to caches - read via getState() when needed
|
||||
// Subscribing to caches causes ALL rows with same company to re-render when cache updates!
|
||||
const company = rowData?.company;
|
||||
const line = rowData?.line;
|
||||
// Note: company and line are already declared above for loading state subscriptions
|
||||
const supplier = rowData?.supplier;
|
||||
|
||||
// Get action via getState() - no need to subscribe
|
||||
@@ -899,12 +1015,12 @@ const VirtualRow = memo(({
|
||||
const needsLine = field.key === 'subline';
|
||||
const needsSupplier = field.key === 'upc';
|
||||
|
||||
// Check loading state for dependent dropdowns via getState()
|
||||
// Check loading state for dependent dropdowns - uses subscribed values for reactivity
|
||||
let isLoadingOptions = false;
|
||||
if (needsCompany && company) {
|
||||
isLoadingOptions = useValidationStore.getState().loadingProductLines.has(String(company));
|
||||
} else if (needsLine && line) {
|
||||
isLoadingOptions = useValidationStore.getState().loadingSublines.has(String(line));
|
||||
if (needsCompany) {
|
||||
isLoadingOptions = isLoadingProductLinesForCompany;
|
||||
} else if (needsLine) {
|
||||
isLoadingOptions = isLoadingSublineForLine;
|
||||
}
|
||||
|
||||
// Calculate copy-down state for this cell
|
||||
|
||||
@@ -19,7 +19,6 @@ import { Badge } from '@/components/ui/badge';
|
||||
import { useValidationStore } from '../store/validationStore';
|
||||
import {
|
||||
useFilters,
|
||||
useSelectedRowCount,
|
||||
useFields,
|
||||
} from '../store/selectors';
|
||||
import { CreateProductCategoryDialog, type CreatedCategoryInfo } from '../../../CreateProductCategoryDialog';
|
||||
@@ -38,7 +37,6 @@ export const ValidationToolbar = ({
|
||||
rowsWithErrors,
|
||||
}: ValidationToolbarProps) => {
|
||||
const filters = useFilters();
|
||||
const selectedRowCount = useSelectedRowCount();
|
||||
const fields = useFields();
|
||||
|
||||
// State for the product search template dialog
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* Only visible to users with admin:debug permission.
|
||||
*/
|
||||
|
||||
import { useState, useMemo } from 'react';
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
|
||||
@@ -154,7 +154,7 @@ export const useUpcValidation = () => {
|
||||
// Set specific error for conflicts
|
||||
if (result.code === 'conflict') {
|
||||
setError(rowIndex, 'upc', {
|
||||
message: 'UPC already exists in database',
|
||||
message: 'A product with this UPC already exists',
|
||||
level: 'error',
|
||||
source: ErrorSource.Upc,
|
||||
type: ErrorType.Unique,
|
||||
@@ -262,7 +262,7 @@ export const useUpcValidation = () => {
|
||||
|
||||
if (result.code === 'conflict') {
|
||||
setError(index, 'upc', {
|
||||
message: 'UPC already exists in database',
|
||||
message: 'A product with this UPC already exists',
|
||||
level: 'error',
|
||||
source: ErrorSource.Upc,
|
||||
type: ErrorType.Unique,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { Data, Fields, Info, RowHook, TableHook } from "../../../types"
|
||||
import type { Meta, Errors } from "../../ValidationStepNew/types"
|
||||
import type { Data, Fields, Info, RowHook, TableHook, Meta, Errors } from "../../../types"
|
||||
import { v4 } from "uuid"
|
||||
import { ErrorSources, ErrorType } from "../../../types"
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import type { Meta } from "./steps/ValidationStepNew/types"
|
||||
import type { DeepReadonly } from "ts-essentials"
|
||||
import type { TranslationsRSIProps } from "./translationsRSIProps"
|
||||
import type { Columns } from "./steps/MatchColumnsStep/types"
|
||||
import type { StepState } from "./steps/UploadFlow"
|
||||
|
||||
// Meta type for row data with unique index
|
||||
export type Meta = { __index: string }
|
||||
|
||||
export type SubmitOptions = {
|
||||
targetEnvironment: "dev" | "prod"
|
||||
useTestDataSource: boolean
|
||||
@@ -200,6 +202,10 @@ export type InfoWithSource = Info & {
|
||||
type: ErrorType;
|
||||
}
|
||||
|
||||
// Legacy error types used by dataMutations utility
|
||||
export type FieldError = { [key: string]: InfoWithSource }
|
||||
export type Errors = { [id: string]: FieldError }
|
||||
|
||||
export type Result<T extends string> = {
|
||||
validData: Data<T>[]
|
||||
invalidData: Data<T>[]
|
||||
|
||||
@@ -431,7 +431,7 @@ export function Import() {
|
||||
// {
|
||||
// upc: "123456789012",
|
||||
// item_number: "ITEM-001",
|
||||
// error_msg: "UPC already exists in the system",
|
||||
// error_msg: "A product with this UPC already exists",
|
||||
// },
|
||||
// {
|
||||
// upc: "234567890123",
|
||||
|
||||
Reference in New Issue
Block a user