diff --git a/docs/validate-table-changes.md b/docs/validate-table-changes.md index 8ac046c..d0fcfc3 100644 --- a/docs/validate-table-changes.md +++ b/docs/validate-table-changes.md @@ -4,10 +4,6 @@ * Red outline + alert circle icon with tooltip if cell is NOT empty and isn't valid 8. When you enter a value in 2+ cells before validation finishes, contents from all edited cells get erased when validation finishes 9. Import dialog state not fully reset when closing? (validate data step appears scrolled to the middle of the table where I left it) -11. Copy down needs to show a loading state on the cells that it will copy to -15. Enhance copy down feature by allowing user to choose the last cell to copy to, instead of going all the way to the bottom - - ## Do NOT change or edit * Anything related to AI validation @@ -23,9 +19,11 @@ ✅FIXED 6. Need to ensure all cell's contents don't overflow the input (truncate). COO does this currently, probably more ✅FIXED 7. The template select cell is expanding, needs to be fixed size and truncate ✅FIXED 10. UPC column doesn't need to show loading state when Item Number is being processed, only show on item number column +✅FIXED 11. Copy down needs to show a loading state on the cells that it will copy to ✅FIXED 12. Shipping restrictions/tax category should default to ID 0 if we didn't get it elsewhere ✅FIXED 13. Header row should be sticky (both up/down and left/right) ✅FIXED 14. Need a way to scroll around table if user doesn't have mouse wheel for left/right +✅FIXED 15. Enhance copy down feature by allowing user to choose the last cell to copy to, instead of going all the way to the bottom --------- diff --git a/inventory/src/lib/react-spreadsheet-import/src/components/ModalWrapper.tsx b/inventory/src/lib/react-spreadsheet-import/src/components/ModalWrapper.tsx index 4201c16..fbb4363 100644 --- a/inventory/src/lib/react-spreadsheet-import/src/components/ModalWrapper.tsx +++ b/inventory/src/lib/react-spreadsheet-import/src/components/ModalWrapper.tsx @@ -20,7 +20,7 @@ import { AlertDialogOverlay, } from "@/components/ui/alert-dialog" import { useRsi } from "../hooks/useRsi" -import { useState } from "react" +import { useState, useCallback } from "react" type Props = { children: React.ReactNode @@ -32,6 +32,22 @@ export const ModalWrapper = ({ children, isOpen, onClose }: Props) => { const { rtl, translations } = useRsi() const [showCloseAlert, setShowCloseAlert] = useState(false) + // Create a handler that resets scroll positions before closing + const handleClose = useCallback(() => { + // Reset all scroll positions in the dialog + const scrollContainers = document.querySelectorAll('.overflow-auto, .overflow-scroll'); + scrollContainers.forEach(container => { + if (container instanceof HTMLElement) { + // Reset scroll position to top-left + container.scrollTop = 0; + container.scrollLeft = 0; + } + }); + + // Call the original onClose handler + onClose(); + }, [onClose]); + return ( <> setShowCloseAlert(true)} modal> @@ -76,7 +92,7 @@ export const ModalWrapper = ({ children, isOpen, onClose }: Props) => { setShowCloseAlert(false)}> {translations.alerts.confirmClose.cancelButtonTitle} - + {translations.alerts.confirmClose.exitButtonTitle} diff --git a/inventory/src/lib/react-spreadsheet-import/src/steps/Steps.tsx b/inventory/src/lib/react-spreadsheet-import/src/steps/Steps.tsx index abe0050..d007f32 100644 --- a/inventory/src/lib/react-spreadsheet-import/src/steps/Steps.tsx +++ b/inventory/src/lib/react-spreadsheet-import/src/steps/Steps.tsx @@ -1,17 +1,32 @@ import { StepState, StepType, UploadFlow } from "./UploadFlow" import { useRsi } from "../hooks/useRsi" -import { useRef, useState } from "react" +import { useRef, useState, useEffect } from "react" import { steps, stepTypeToStepIndex, stepIndexToStepType } from "../utils/steps" import { CgCheck } from "react-icons/cg" const CheckIcon = ({ color }: { color: string }) => export const Steps = () => { - const { initialStepState, translations, isNavigationEnabled } = useRsi() + const { initialStepState, translations, isNavigationEnabled, isOpen } = useRsi() const initialStep = stepTypeToStepIndex(initialStepState?.type) const [activeStep, setActiveStep] = useState(initialStep) const [state, setState] = useState(initialStepState || { type: StepType.upload }) const history = useRef([]) + const prevIsOpen = useRef(isOpen) + + // Reset state when dialog is reopened + useEffect(() => { + // Check if dialog was closed and is now open again + if (isOpen && !prevIsOpen.current) { + // Reset to initial state + setActiveStep(initialStep) + setState(initialStepState || { type: StepType.upload }) + history.current = [] + } + + // Update previous isOpen value + prevIsOpen.current = isOpen + }, [isOpen, initialStep, initialStepState]) const onClickStep = (stepIndex: number) => { const type = stepIndexToStepType(stepIndex) diff --git a/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationCell.tsx b/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationCell.tsx index 104e4d2..62d4b50 100644 --- a/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationCell.tsx +++ b/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationCell.tsx @@ -331,6 +331,7 @@ const ItemNumberCell = React.memo(({ 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' } : @@ -367,7 +368,6 @@ const ItemNumberCell = React.memo(({

Copy value to rows below

-

Click to select target rows

@@ -535,6 +535,7 @@ const ValidationCell = ({ 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' } : @@ -571,7 +572,6 @@ const ValidationCell = ({

Copy value to rows below

-

Click to select target rows

diff --git a/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationContainer.tsx b/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationContainer.tsx index 1a3eb8d..957b5cb 100644 --- a/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationContainer.tsx +++ b/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationContainer.tsx @@ -866,34 +866,20 @@ const ValidationContainer = ({ return newSet; }); - // Use setTimeout to allow the UI to update with loading state before processing - setTimeout(() => { - // Update each row sequentially with a small delay for visual feedback - const updateSequentially = async () => { - for (let i = 0; i < rowsToUpdate.length; i++) { - const targetRowIndex = rowIndex + 1 + i; - - // Update the row with the copied value - enhancedUpdateRow(targetRowIndex, fieldKey as T, valueCopy); - - // Remove loading state after a short delay - setTimeout(() => { - setValidatingCells(prev => { - const newSet = new Set(prev); - newSet.delete(`${targetRowIndex}-${fieldKey}`); - return newSet; - }); - }, 100); // Short delay before removing loading state - - // Add a small delay between updates for visual effect - if (i < rowsToUpdate.length - 1) { - await new Promise(resolve => setTimeout(resolve, 50)); - } - } - }; + // Update all rows immediately + rowsToUpdate.forEach((_, i) => { + const targetRowIndex = rowIndex + 1 + i; - updateSequentially(); - }, 50); + // Update the row with the copied value + enhancedUpdateRow(targetRowIndex, fieldKey as T, valueCopy); + + // Remove loading state + setValidatingCells(prev => { + const newSet = new Set(prev); + newSet.delete(`${targetRowIndex}-${fieldKey}`); + return newSet; + }); + }); }, [data, enhancedUpdateRow, setValidatingCells]); // Memoize the enhanced validation table component @@ -1032,6 +1018,20 @@ const ValidationContainer = ({ } }, [filteredData]); + // Add cleanup effect to reset scroll position when component unmounts + useEffect(() => { + return () => { + // Reset the last scroll position reference + lastScrollPosition.current = { left: 0, top: 0 }; + + // Reset the scroll container if it exists + if (scrollContainerRef.current) { + scrollContainerRef.current.scrollTop = 0; + scrollContainerRef.current.scrollLeft = 0; + } + }; + }, []); + return (
diff --git a/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationTable.tsx b/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationTable.tsx index 42a161f..3c1a4dd 100644 --- a/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationTable.tsx +++ b/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationTable.tsx @@ -385,6 +385,23 @@ const ValidationTable = ({ return (
+ {/* Add global styles for copy down mode */} + {isInCopyDownMode && ( + + )} {isInCopyDownMode && sourceRowIndex !== null && sourceFieldKey !== null && (
({ "hover:bg-muted/50", row.getIsSelected() ? "bg-muted/50" : "", validationErrors.get(data.indexOf(row.original)) && - Object.keys(validationErrors.get(data.indexOf(row.original)) || {}).length > 0 ? "bg-red-50/40" : "" + Object.keys(validationErrors.get(data.indexOf(row.original)) || {}).length > 0 ? "bg-red-50/40" : "", + // Add cursor-pointer class when in copy down mode for target rows + isInCopyDownMode && sourceRowIndex !== null && row.index > sourceRowIndex ? "cursor-pointer copy-down-target-row" : "" )} + style={{ + // Force cursor pointer on all target rows + cursor: isInCopyDownMode && sourceRowIndex !== null && row.index > sourceRowIndex ? 'pointer' : undefined, + position: 'relative' // Ensure we can position the overlay + }} onMouseEnter={() => handleRowMouseEnter(row.index)} > {row.getVisibleCells().map((cell, cellIndex) => { @@ -481,8 +505,11 @@ const ValidationTable = ({ minWidth: `${width}px`, maxWidth: `${width}px`, boxSizing: 'border-box', - padding: '0' + padding: '0', + // Force cursor pointer on all cells in target rows + cursor: isInCopyDownMode && sourceRowIndex !== null && row.index > sourceRowIndex ? 'pointer' : undefined }} + className={isInCopyDownMode && sourceRowIndex !== null && row.index > sourceRowIndex ? "target-row-cell" : ""} > {flexRender(cell.column.columnDef.cell, cell.getContext())} diff --git a/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/InputCell.tsx b/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/InputCell.tsx index d62f9b4..3a689e4 100644 --- a/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/InputCell.tsx +++ b/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/InputCell.tsx @@ -177,7 +177,9 @@ const InputCell = ({ hasClass('!border-blue-200') ? '#bfdbfe' : hasClass('!border-blue-200') && isHovered ? '#bfdbfe' : undefined, - borderRadius: hasClass('!rounded-md') ? '0.375rem' : undefined + borderRadius: hasClass('!rounded-md') ? '0.375rem' : undefined, + borderWidth: hasClass('!border-blue-500') || hasClass('!border-blue-200') ? '0px' : undefined, + cursor: hasClass('hover:!bg-blue-100') ? 'pointer' : undefined }} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} @@ -225,7 +227,9 @@ const InputCell = ({ borderColor: hasClass('!border-blue-500') ? '#3b82f6' : hasClass('!border-blue-200') ? '#bfdbfe' : undefined, - borderRadius: hasClass('!rounded-md') ? '0.375rem' : undefined + borderRadius: hasClass('!rounded-md') ? '0.375rem' : undefined, + borderWidth: hasClass('!border-blue-500') || hasClass('!border-blue-200') ? '0px' : undefined, + cursor: hasClass('hover:!bg-blue-100') ? 'pointer' : undefined }} /> ) : ( @@ -245,7 +249,9 @@ const InputCell = ({ hasClass('!border-blue-200') ? '#bfdbfe' : hasClass('!border-blue-200') && isHovered ? '#bfdbfe' : undefined, - borderRadius: hasClass('!rounded-md') ? '0.375rem' : undefined + borderRadius: hasClass('!rounded-md') ? '0.375rem' : undefined, + borderWidth: hasClass('!border-blue-500') || hasClass('!border-blue-200') ? '0px' : undefined, + cursor: hasClass('hover:!bg-blue-100') ? 'pointer' : 'text' }} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} diff --git a/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultiSelectCell.tsx b/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultiSelectCell.tsx index 41058db..0e9268e 100644 --- a/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultiSelectCell.tsx +++ b/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultiSelectCell.tsx @@ -191,9 +191,17 @@ const MultiSelectCell = ({ if (onEndEdit) onEndEdit(); } else if (newOpen && !open) { // Sync internal state with external state when opening - setInternalValue(value); + setInternalValue(Array.isArray(value) ? value : []); setSearchQuery(""); // Reset search query on open if (onStartEdit) onStartEdit(); + } else if (!newOpen) { + // Handle case when dropdown is already closed but handleOpenChange is called + // This ensures values are saved when clicking the chevron to close + if (internalValue.length !== value.length || + internalValue.some((v, i) => v !== value[i])) { + onChange(internalValue); + } + if (onEndEdit) onEndEdit(); } }, [open, internalValue, value, onChange, onStartEdit, onEndEdit]); @@ -343,7 +351,8 @@ const MultiSelectCell = ({ hasClass('!border-blue-200') ? '#bfdbfe' : hasClass('!border-blue-200') && isHovered ? '#bfdbfe' : undefined, - borderRadius: hasClass('!rounded-md') ? '0.375rem' : undefined + borderRadius: hasClass('!rounded-md') ? '0.375rem' : undefined, + cursor: hasClass('hover:!bg-blue-100') ? 'pointer' : undefined }} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} @@ -383,7 +392,8 @@ const MultiSelectCell = ({ hasClass('!border-blue-200') && isHovered ? '#bfdbfe' : undefined, borderRadius: hasClass('!rounded-md') ? '0.375rem' : undefined, - borderWidth: hasClass('!border-blue-500') || hasClass('!border-blue-200') ? '0px' : undefined + borderWidth: hasClass('!border-blue-500') || hasClass('!border-blue-200') ? '0px' : undefined, + cursor: hasClass('hover:!bg-blue-100') ? 'pointer' : undefined }} onClick={(e) => { // Don't open the dropdown if we're in copy down mode @@ -395,7 +405,11 @@ const MultiSelectCell = ({ // Only prevent default and stop propagation if not in copy down mode e.preventDefault(); e.stopPropagation(); - setOpen(!open); + + // Toggle the open state and call handleOpenChange to ensure values are saved + const newOpenState = !open; + setOpen(newOpenState); + handleOpenChange(newOpenState); }} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} diff --git a/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultilineInput.tsx b/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultilineInput.tsx index 3521872..65b3d65 100644 --- a/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultilineInput.tsx +++ b/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/MultilineInput.tsx @@ -149,7 +149,9 @@ const MultilineInput = ({ hasClass('!border-blue-200') ? '#bfdbfe' : hasClass('!border-blue-200') && isHovered ? '#bfdbfe' : undefined, - borderRadius: hasClass('!rounded-md') ? '0.375rem' : undefined + borderRadius: hasClass('!rounded-md') ? '0.375rem' : undefined, + borderWidth: hasClass('!border-blue-500') || hasClass('!border-blue-200') ? '0px' : undefined, + cursor: hasClass('hover:!bg-blue-100') ? 'pointer' : undefined }} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} @@ -181,7 +183,9 @@ const MultilineInput = ({ hasClass('!border-blue-200') ? '#bfdbfe' : hasClass('!border-blue-200') && isHovered ? '#bfdbfe' : undefined, - borderRadius: hasClass('!rounded-md') ? '0.375rem' : undefined + borderRadius: hasClass('!rounded-md') ? '0.375rem' : undefined, + borderWidth: hasClass('!border-blue-500') || hasClass('!border-blue-200') ? '0px' : undefined, + cursor: hasClass('hover:!bg-blue-100') ? 'pointer' : 'pointer' }} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} diff --git a/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/SelectCell.tsx b/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/SelectCell.tsx index 75b4f7f..41b2095 100644 --- a/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/SelectCell.tsx +++ b/inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/cells/SelectCell.tsx @@ -163,7 +163,8 @@ const SelectCell = ({ hasClass('!border-blue-200') ? '#bfdbfe' : hasClass('!border-blue-200') && isHovered ? '#bfdbfe' : undefined, - borderRadius: hasClass('!rounded-md') ? '0.375rem' : undefined + borderRadius: hasClass('!rounded-md') ? '0.375rem' : undefined, + cursor: hasClass('hover:!bg-blue-100') ? 'pointer' : undefined }} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} @@ -207,18 +208,19 @@ const SelectCell = ({ hasClass('!border-blue-200') && isHovered ? '#bfdbfe' : undefined, borderRadius: hasClass('!rounded-md') ? '0.375rem' : undefined, - borderWidth: hasClass('!border-blue-500') || hasClass('!border-blue-200') ? '0px' : undefined + borderWidth: hasClass('!border-blue-500') || hasClass('!border-blue-200') ? '0px' : undefined, + cursor: hasClass('hover:!bg-blue-100') ? 'pointer' : undefined }} onClick={(e) => { - e.preventDefault(); - e.stopPropagation(); - // Don't open the dropdown if we're in copy down mode if (hasClass('!bg-blue-100') || hasClass('!bg-blue-200') || hasClass('hover:!bg-blue-100')) { - // Let the parent cell handle the click + // Let the parent cell handle the click by NOT preventing default or stopping propagation return; } + // Only prevent default and stop propagation if not in copy down mode + e.preventDefault(); + e.stopPropagation(); setOpen(!open); if (!open && onStartEdit) onStartEdit(); }}