Add copy down functionality to validate table
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
import { Field } from '../../../types'
|
||||
import { Loader2, AlertCircle } from 'lucide-react'
|
||||
import { Loader2, AlertCircle, CopyDown, ArrowDown } from 'lucide-react'
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
@@ -122,6 +122,7 @@ export interface ValidationCellProps {
|
||||
itemNumber?: string
|
||||
width: number
|
||||
rowIndex: number
|
||||
copyDown?: () => void
|
||||
}
|
||||
|
||||
const ItemNumberCell = React.memo(({
|
||||
@@ -131,7 +132,8 @@ const ItemNumberCell = React.memo(({
|
||||
width,
|
||||
errors = [],
|
||||
field,
|
||||
onChange
|
||||
onChange,
|
||||
copyDown
|
||||
}: {
|
||||
value: any,
|
||||
itemNumber?: string,
|
||||
@@ -139,7 +141,8 @@ const ItemNumberCell = React.memo(({
|
||||
width: number,
|
||||
errors?: ErrorObject[],
|
||||
field: Field<string>,
|
||||
onChange: (value: any) => void
|
||||
onChange: (value: any) => void,
|
||||
copyDown?: () => void
|
||||
}) => {
|
||||
// Helper function to check if a value is empty
|
||||
const isEmpty = (val: any): boolean =>
|
||||
@@ -171,7 +174,7 @@ const ItemNumberCell = React.memo(({
|
||||
: '';
|
||||
|
||||
return (
|
||||
<TableCell className="p-1" style={{ width: `${width}px`, minWidth: `${width}px` }}>
|
||||
<TableCell className="p-1 group relative" style={{ width: `${width}px`, minWidth: `${width}px` }}>
|
||||
<div className={`relative ${hasError || isRequiredButEmpty ? 'border-red-500' : ''}`}>
|
||||
{isValidating ? (
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
@@ -197,6 +200,25 @@ const ItemNumberCell = React.memo(({
|
||||
}} />
|
||||
</div>
|
||||
)}
|
||||
{copyDown && (
|
||||
<div className="absolute right-2 top-1/2 -translate-y-1/2 z-20 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
onClick={copyDown}
|
||||
className="ml-1 p-1 rounded-sm hover:bg-gray-100 text-gray-500 hover:text-gray-700"
|
||||
>
|
||||
<ArrowDown className="h-3 w-3" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Copy value to all cells below</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
);
|
||||
@@ -218,7 +240,9 @@ const ValidationCell = ({
|
||||
fieldKey,
|
||||
options = [],
|
||||
itemNumber,
|
||||
width}: ValidationCellProps) => {
|
||||
width,
|
||||
rowIndex,
|
||||
copyDown}: ValidationCellProps) => {
|
||||
// For item_number fields, use the specialized component
|
||||
if (fieldKey === 'item_number') {
|
||||
return (
|
||||
@@ -230,6 +254,7 @@ const ValidationCell = ({
|
||||
errors={errors}
|
||||
field={field}
|
||||
onChange={onChange}
|
||||
copyDown={copyDown}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -273,7 +298,7 @@ const ValidationCell = ({
|
||||
field.fieldType.price === true;
|
||||
|
||||
return (
|
||||
<TableCell className="p-1" style={{ width: `${width}px`, minWidth: `${width}px` }}>
|
||||
<TableCell className="p-1 group relative" style={{ width: `${width}px`, minWidth: `${width}px` }}>
|
||||
<div className={`relative ${hasError || isRequiredButEmpty ? 'border-red-500' : ''}`}>
|
||||
{isValidating ? (
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
@@ -299,6 +324,25 @@ const ValidationCell = ({
|
||||
}} />
|
||||
</div>
|
||||
)}
|
||||
{copyDown && (
|
||||
<div className="absolute right-2 top-1/2 -translate-y-1/2 z-20 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
onClick={copyDown}
|
||||
className="ml-1 p-1 rounded-sm hover:bg-gray-100 text-gray-500 hover:text-gray-700"
|
||||
>
|
||||
<ArrowDown className="h-3 w-3" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Copy value to all cells below</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
);
|
||||
|
||||
@@ -56,7 +56,8 @@ const ValidationContainer = <T extends string>({
|
||||
loadTemplates,
|
||||
setData,
|
||||
fields,
|
||||
isLoadingTemplates } = validationState
|
||||
isLoadingTemplates,
|
||||
copyDown } = validationState
|
||||
|
||||
// Add state for tracking product lines and sublines per row
|
||||
const [rowProductLines, setRowProductLines] = useState<Record<string, any[]>>({});
|
||||
@@ -832,22 +833,20 @@ const ValidationContainer = <T extends string>({
|
||||
validatingCells={validatingCells}
|
||||
itemNumbers={itemNumbersMap}
|
||||
isLoadingTemplates={isLoadingTemplates}
|
||||
copyDown={copyDown}
|
||||
/>
|
||||
);
|
||||
}, [validatingUpcRows, itemNumbers, isLoadingTemplates]);
|
||||
}, [validatingUpcRows, itemNumbers, isLoadingTemplates, copyDown]);
|
||||
|
||||
// Memoize the ValidationTable to prevent unnecessary re-renders
|
||||
const renderValidationTable = useMemo(() => {
|
||||
return (
|
||||
<EnhancedValidationTable
|
||||
data={filteredData}
|
||||
// @ts-ignore - The fields are compatible at runtime but TypeScript has issues with the exact type
|
||||
fields={validationState.fields}
|
||||
updateRow={(rowIndex: number, key: string, value: any) =>
|
||||
enhancedUpdateRow(rowIndex, key as T, value)
|
||||
}
|
||||
fields={fields}
|
||||
rowSelection={rowSelection}
|
||||
setRowSelection={setRowSelection}
|
||||
updateRow={updateRow}
|
||||
validationErrors={validationErrors}
|
||||
isValidatingUpc={isRowValidatingUpc}
|
||||
validatingUpcRows={Array.from(validatingUpcRows)}
|
||||
@@ -855,22 +854,19 @@ const ValidationContainer = <T extends string>({
|
||||
templates={templates}
|
||||
applyTemplate={applyTemplate}
|
||||
getTemplateDisplayText={getTemplateDisplayText}
|
||||
rowProductLines={rowProductLines}
|
||||
rowSublines={rowSublines}
|
||||
isLoadingLines={isLoadingLines}
|
||||
isLoadingSublines={isLoadingSublines}
|
||||
upcValidationResults={new Map(Object.entries(itemNumbers).map(([key, value]) => [parseInt(key), { itemNumber: value }]))}
|
||||
validatingCells={new Set()}
|
||||
itemNumbers={new Map()}
|
||||
isLoadingTemplates={isLoadingTemplates}
|
||||
copyDown={copyDown}
|
||||
/>
|
||||
);
|
||||
}, [
|
||||
EnhancedValidationTable,
|
||||
filteredData,
|
||||
validationState.fields,
|
||||
enhancedUpdateRow,
|
||||
fields,
|
||||
rowSelection,
|
||||
setRowSelection,
|
||||
updateRow,
|
||||
validationErrors,
|
||||
isRowValidatingUpc,
|
||||
validatingUpcRows,
|
||||
@@ -878,11 +874,8 @@ const ValidationContainer = <T extends string>({
|
||||
templates,
|
||||
applyTemplate,
|
||||
getTemplateDisplayText,
|
||||
rowProductLines,
|
||||
rowSublines,
|
||||
isLoadingLines,
|
||||
isLoadingSublines,
|
||||
itemNumbers
|
||||
isLoadingTemplates,
|
||||
copyDown
|
||||
]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -45,6 +45,7 @@ interface ValidationTableProps<T extends string> {
|
||||
validatingCells: Set<string>
|
||||
itemNumbers: Map<number, string>
|
||||
isLoadingTemplates?: boolean
|
||||
copyDown: (rowIndex: number, key: string) => void
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
@@ -62,6 +63,7 @@ interface MemoizedCellProps<T extends string = string> {
|
||||
validatingCells: Set<string>
|
||||
itemNumbers: Map<number, string>
|
||||
width: number
|
||||
copyDown: (rowIndex: number, key: string) => void
|
||||
}
|
||||
|
||||
// Memoized cell component that only updates when its specific data changes
|
||||
@@ -73,7 +75,8 @@ const MemoizedCell = React.memo(({
|
||||
validationErrors,
|
||||
validatingCells,
|
||||
itemNumbers,
|
||||
width
|
||||
width,
|
||||
copyDown
|
||||
}: MemoizedCellProps) => {
|
||||
const rowErrors = validationErrors.get(rowIndex) || {};
|
||||
const fieldErrors = rowErrors[String(field.key)] || [];
|
||||
@@ -92,6 +95,11 @@ const MemoizedCell = React.memo(({
|
||||
updateRow(rowIndex, field.key, newValue);
|
||||
}, [updateRow, rowIndex, field.key]);
|
||||
|
||||
// Memoize the copyDown handler
|
||||
const handleCopyDown = useCallback(() => {
|
||||
copyDown(rowIndex, field.key);
|
||||
}, [copyDown, rowIndex, field.key]);
|
||||
|
||||
return (
|
||||
<ValidationCell
|
||||
field={field}
|
||||
@@ -104,6 +112,7 @@ const MemoizedCell = React.memo(({
|
||||
itemNumber={itemNumbers.get(rowIndex)}
|
||||
width={width}
|
||||
rowIndex={rowIndex}
|
||||
copyDown={handleCopyDown}
|
||||
/>
|
||||
);
|
||||
}, (prev, next) => {
|
||||
@@ -177,6 +186,7 @@ interface MemoizedRowProps {
|
||||
options?: { [key: string]: any[] };
|
||||
rowIndex: number;
|
||||
isSelected: boolean;
|
||||
copyDown: (rowIndex: number, key: string) => void;
|
||||
}
|
||||
|
||||
const MemoizedRow = React.memo<MemoizedRowProps>(({
|
||||
@@ -188,7 +198,8 @@ const MemoizedRow = React.memo<MemoizedRowProps>(({
|
||||
itemNumbers,
|
||||
options = {},
|
||||
rowIndex,
|
||||
isSelected
|
||||
isSelected,
|
||||
copyDown
|
||||
}) => {
|
||||
return (
|
||||
<TableRow
|
||||
@@ -210,6 +221,11 @@ const MemoizedRow = React.memo<MemoizedRowProps>(({
|
||||
|
||||
const isValidating = validatingCells.has(`${rowIndex}-${field.key}`);
|
||||
|
||||
// Memoize the copyDown handler
|
||||
const handleCopyDown = () => {
|
||||
copyDown(rowIndex, field.key);
|
||||
};
|
||||
|
||||
return (
|
||||
<ValidationCell
|
||||
key={String(field.key)}
|
||||
@@ -223,6 +239,7 @@ const MemoizedRow = React.memo<MemoizedRowProps>(({
|
||||
width={fieldWidth}
|
||||
rowIndex={rowIndex}
|
||||
itemNumber={itemNumbers.get(rowIndex)}
|
||||
copyDown={handleCopyDown}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
@@ -272,7 +289,8 @@ const ValidationTable = <T extends string>({
|
||||
getTemplateDisplayText,
|
||||
validatingCells,
|
||||
itemNumbers,
|
||||
isLoadingTemplates = false
|
||||
isLoadingTemplates = false,
|
||||
copyDown
|
||||
}: ValidationTableProps<T>) => {
|
||||
const { translations } = useRsi<T>();
|
||||
|
||||
@@ -430,11 +448,12 @@ const ValidationTable = <T extends string>({
|
||||
validatingCells={validatingCells}
|
||||
itemNumbers={itemNumbers}
|
||||
width={fieldWidth}
|
||||
copyDown={(rowIndex, key) => copyDown(rowIndex, key as T)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
}).filter((col): col is ColumnDef<RowData<T>, any> => col !== null), [fields, validationErrors, validatingCells, itemNumbers, updateRow]);
|
||||
}).filter((col): col is ColumnDef<RowData<T>, any> => col !== null), [fields, validationErrors, validatingCells, itemNumbers, updateRow, copyDown]);
|
||||
|
||||
// Combine columns
|
||||
const columns = useMemo(() => [selectionColumn, templateColumn, ...fieldColumns], [selectionColumn, templateColumn, fieldColumns]);
|
||||
|
||||
@@ -901,9 +901,21 @@ useEffect(() => {
|
||||
newSet.delete(`${rowIndex}-${key}`);
|
||||
return newSet;
|
||||
});
|
||||
}, 300); // Increase debounce time to reduce validation frequency
|
||||
}, 300);
|
||||
}, [data, validateRow, validateUpc, setData, setRowValidationStatus, cleanPriceFields, fields]);
|
||||
|
||||
// Copy a cell value to all cells below it in the same column
|
||||
const copyDown = useCallback((rowIndex: number, key: T) => {
|
||||
// Get the source value to copy
|
||||
const sourceValue = data[rowIndex][key];
|
||||
|
||||
// Update all rows below with the same value using the existing updateRow function
|
||||
// This ensures all validation logic runs consistently
|
||||
for (let i = rowIndex + 1; i < data.length; i++) {
|
||||
updateRow(i, key, sourceValue);
|
||||
}
|
||||
}, [data, updateRow]);
|
||||
|
||||
// Add this at the top of the component, after other useRef declarations
|
||||
const validationTimeoutsRef = useRef<Record<number, NodeJS.Timeout>>({});
|
||||
|
||||
@@ -1616,6 +1628,7 @@ useEffect(() => {
|
||||
|
||||
// Row manipulation
|
||||
updateRow,
|
||||
copyDown,
|
||||
|
||||
// Templates
|
||||
templates,
|
||||
|
||||
Reference in New Issue
Block a user