Remove artificial delays from copydown function, fix issues with select components, ensure pointer cursor shows in copydown state, ensure table scroll position is reset on unmount
This commit is contained in:
@@ -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
|
||||
|
||||
---------
|
||||
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
<Dialog open={isOpen} onOpenChange={() => setShowCloseAlert(true)} modal>
|
||||
@@ -76,7 +92,7 @@ export const ModalWrapper = ({ children, isOpen, onClose }: Props) => {
|
||||
<AlertDialogCancel onClick={() => setShowCloseAlert(false)}>
|
||||
{translations.alerts.confirmClose.cancelButtonTitle}
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={onClose}>
|
||||
<AlertDialogAction onClick={handleClose}>
|
||||
{translations.alerts.confirmClose.exitButtonTitle}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
|
||||
@@ -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 }) => <CgCheck size="24" className={color} />
|
||||
|
||||
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<StepState>(initialStepState || { type: StepType.upload })
|
||||
const history = useRef<StepState[]>([])
|
||||
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)
|
||||
|
||||
@@ -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(({
|
||||
<TooltipContent side="right">
|
||||
<div className="flex flex-col">
|
||||
<p className="font-medium">Copy value to rows below</p>
|
||||
<p className="text-xs text-muted-foreground">Click to select target rows</p>
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
@@ -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 = ({
|
||||
<TooltipContent side="right">
|
||||
<div className="flex flex-col">
|
||||
<p className="font-medium">Copy value to rows below</p>
|
||||
<p className="text-xs text-muted-foreground">Click to select target rows</p>
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
@@ -866,34 +866,20 @@ const ValidationContainer = <T extends string>({
|
||||
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 = <T extends string>({
|
||||
}
|
||||
}, [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 (
|
||||
<div className="flex flex-col h-[calc(100vh-10rem)] overflow-hidden">
|
||||
<div className="flex-1 overflow-hidden">
|
||||
|
||||
@@ -385,6 +385,23 @@ const ValidationTable = <T extends string>({
|
||||
return (
|
||||
<CopyDownContext.Provider value={copyDownContextValue}>
|
||||
<div className="min-w-max relative">
|
||||
{/* Add global styles for copy down mode */}
|
||||
{isInCopyDownMode && (
|
||||
<style>
|
||||
{`
|
||||
.copy-down-target-row,
|
||||
.copy-down-target-row *,
|
||||
.copy-down-target-row input,
|
||||
.copy-down-target-row textarea,
|
||||
.copy-down-target-row div,
|
||||
.copy-down-target-row button,
|
||||
.target-row-cell,
|
||||
.target-row-cell * {
|
||||
cursor: pointer !important;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
)}
|
||||
{isInCopyDownMode && sourceRowIndex !== null && sourceFieldKey !== null && (
|
||||
<div className="sticky top-0 z-30 h-0 overflow-visible">
|
||||
<div
|
||||
@@ -467,8 +484,15 @@ const ValidationTable = <T extends string>({
|
||||
"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 = <T extends string>({
|
||||
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())}
|
||||
</TableCell>
|
||||
|
||||
@@ -177,7 +177,9 @@ const InputCell = <T extends string>({
|
||||
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 = <T extends string>({
|
||||
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 = <T extends string>({
|
||||
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)}
|
||||
|
||||
@@ -191,9 +191,17 @@ const MultiSelectCell = <T extends string>({
|
||||
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 = <T extends string>({
|
||||
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 = <T extends string>({
|
||||
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 = <T extends string>({
|
||||
// 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)}
|
||||
|
||||
@@ -149,7 +149,9 @@ const MultilineInput = <T extends string>({
|
||||
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 = <T extends string>({
|
||||
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)}
|
||||
|
||||
@@ -163,7 +163,8 @@ const SelectCell = <T extends string>({
|
||||
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 = <T extends string>({
|
||||
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();
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user