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