Merge separate itemNumberCell in to ValidationCell

This commit is contained in:
2025-03-17 14:18:49 -04:00
parent 676cd44d9d
commit f60f0b1b5c

View File

@@ -262,190 +262,6 @@ function compareErrorArrays(prevErrors: ErrorObject[], nextErrors: ErrorObject[]
}); });
} }
const ItemNumberCell = React.memo(({
value,
itemNumber,
isValidating,
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
const {
hasError,
isRequiredButEmpty,
shouldShowErrorIcon,
errorMessages
} = React.useMemo(() =>
processErrors(displayValue, errors),
[displayValue, errors]
);
// Add state for hover on copy down button
const [isCopyDownHovered, setIsCopyDownHovered] = React.useState(false);
// Add state for hover on target row
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
const isSourceCell = copyDownContext.isInCopyDownMode &&
copyDownContext.sourceRowIndex === rowIndex &&
copyDownContext.sourceFieldKey === 'item_number';
// Check if this cell is in a row that can be a target for copy down
const isInTargetRow = copyDownContext.isInCopyDownMode &&
copyDownContext.sourceFieldKey === 'item_number' &&
rowIndex > (copyDownContext.sourceRowIndex || 0);
// Check if this row is the currently selected target row
const isSelectedTarget = isInTargetRow && rowIndex <= (copyDownContext.targetRowIndex || 0);
// Handle click on a potential target cell
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',
type: ErrorType.Custom
}} />
</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 = React.memo(({ const ValidationCell = React.memo(({
field, field,
value, value,
@@ -454,25 +270,30 @@ const ValidationCell = React.memo(({
isValidating, isValidating,
fieldKey, fieldKey,
options = [], options = [],
itemNumber,
width, width,
copyDown, copyDown,
rowIndex, rowIndex,
totalRows = 0}: ValidationCellProps) => { totalRows = 0
}: ValidationCellProps) => {
// Use the CopyDown context // Use the CopyDown context
const copyDownContext = React.useContext(CopyDownContext); const copyDownContext = React.useContext(CopyDownContext);
// Only destructure what we need to avoid unused variables warning
const { isInCopyDownMode, sourceRowIndex, sourceFieldKey } = 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 // Use the optimized processErrors function to avoid redundant filtering
const { const {
hasError, hasError,
isRequiredButEmpty, isRequiredButEmpty,
shouldShowErrorIcon, shouldShowErrorIcon,
errorMessages errorMessages
} = React.useMemo(() => processErrors(value, errors), [value, errors]); } = React.useMemo(() => processErrors(displayValue, errors), [displayValue, errors]);
// Track whether this cell is the source of a copy-down operation // Track whether this cell is the source of a copy-down operation
const isSourceCell = isInCopyDownMode && rowIndex === sourceRowIndex && fieldKey === sourceFieldKey; 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);
@@ -507,10 +328,10 @@ const ValidationCell = React.memo(({
); );
} }
}; };
//normal selects, normal inputs, not item_number or multi-select
return ( return (
<TableCell <TableCell
className="p-1 group relative" className="p-1 group relative"
style={{ style={{
width: `${width}px`, width: `${width}px`,
minWidth: `${width}px`, minWidth: `${width}px`,
@@ -536,7 +357,7 @@ const ValidationCell = React.memo(({
}} /> }} />
</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>
@@ -587,11 +408,10 @@ const ValidationCell = React.memo(({
</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; })()} {(() => { 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}