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:
2025-03-14 19:23:47 -04:00
parent d0a83c04ca
commit a88dbb8486
10 changed files with 136 additions and 54 deletions

View File

@@ -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>

View File

@@ -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)

View File

@@ -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>

View File

@@ -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">

View File

@@ -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>

View File

@@ -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)}

View File

@@ -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)}

View File

@@ -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)}

View File

@@ -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();
}}