diff --git a/inventory/src/lib/react-spreadsheet-import/src/steps/ImageUploadStep/ImageUploadStep.tsx b/inventory/src/lib/react-spreadsheet-import/src/steps/ImageUploadStep/ImageUploadStep.tsx index 6a4fd2e..505447b 100644 --- a/inventory/src/lib/react-spreadsheet-import/src/steps/ImageUploadStep/ImageUploadStep.tsx +++ b/inventory/src/lib/react-spreadsheet-import/src/steps/ImageUploadStep/ImageUploadStep.tsx @@ -88,9 +88,10 @@ export const ImageUploadStep = ({ // Set up sensors for drag and drop with enhanced configuration const sensors = useSensors( useSensor(PointerSensor, { - // Make it responsive with minimal constraints + // Make it responsive with less restrictive constraints activationConstraint: { - distance: 3, // Reduced distance for more responsive drag + distance: 1, // Reduced distance for more responsive drag + delay: 0, // No delay tolerance: 5 }, }), @@ -104,73 +105,15 @@ export const ImageUploadStep = ({ // Custom collision detection algorithm that prioritizes product containers const customCollisionDetection: CollisionDetection = (args) => { - const { droppableContainers, active, pointerCoordinates } = args; - - if (!pointerCoordinates) { - return []; - } - - // Get the active container (product index) - const activeContainer = findContainer(active.id.toString()); - - // Get all collisions + // Use the built-in pointerWithin algorithm first for better performance const pointerCollisions = pointerWithin(args); - const rectCollisions = rectIntersection(args); - // Combine collision methods for more reliable detection - const allCollisions = [...pointerCollisions, ...rectCollisions]; - - // Check for image collisions first - for reordering within the same container - const imageCollisions = allCollisions.filter(collision => { - const collisionId = collision.id.toString(); - // Only detect other images, not the active image - if (collisionId === active.id.toString()) return false; - - // Check if it's an image by looking for it in productImages - return productImages.some(img => img.id === collisionId); - }); - - // If we have image collisions within the same container, prioritize those for reordering - if (activeContainer && imageCollisions.length > 0) { - const sameContainerCollisions = imageCollisions.filter(collision => { - const image = productImages.find(img => img.id === collision.id); - return image && image.productIndex.toString() === activeContainer; - }); - - if (sameContainerCollisions.length > 0) { - return [sameContainerCollisions[0]]; // Return the first image collision in the same container - } + if (pointerCollisions.length > 0) { + return pointerCollisions; } - // If no image collisions in the same container, check for product container collisions - const productContainerCollisions = allCollisions.filter( - collision => typeof collision.id === 'string' && collision.id.toString().startsWith('product-') - ); - - // If the active container is different from container collisions, prioritize those - if (activeContainer && productContainerCollisions.length > 0) { - const differentContainerCollisions = productContainerCollisions.filter(collision => { - const containerIndex = collision.id.toString().split('-')[1]; - return containerIndex !== activeContainer; - }); - - if (differentContainerCollisions.length > 0) { - return [differentContainerCollisions[0]]; - } - } - - // If we have any product container collisions, use those - if (productContainerCollisions.length > 0) { - return [productContainerCollisions[0]]; - } - - // Finally, check images in different containers - if (imageCollisions.length > 0) { - return [imageCollisions[0]]; - } - - // Fall back to all collisions - return allCollisions.length > 0 ? [allCollisions[0]] : []; + // Fall back to rectIntersection if no pointer collisions + return rectIntersection(args); }; // Handle drag start to set active image and prevent default behavior @@ -255,21 +198,12 @@ export const ImageUploadStep = ({ // Check if the container has images const hasImages = getProductImages(index).length > 0; - // Add stronger attributes for empty containers to ensure they stand out as drop targets - if (!hasImages) { - container.setAttribute('data-empty', 'true'); - // Setting tabindex makes the element focusable which can help with accessibility - container.setAttribute('tabindex', '0'); - - // Add ARIA attributes to improve accessibility and recognition - container.setAttribute('aria-label', `Empty drop zone for product ${index + 1}`); - - // Ensure the empty container has sufficient size to be a drop target - if (container.offsetHeight < 100) { - container.style.minHeight = '100px'; - } - } else { - container.setAttribute('data-empty', 'false'); + // Set data-empty attribute for tracking purposes + container.setAttribute('data-empty', hasImages ? 'false' : 'true'); + + // Ensure the container has sufficient size to be a drop target + if (container.offsetHeight < 100) { + container.style.minHeight = '100px'; } } }); @@ -285,20 +219,16 @@ export const ImageUploadStep = ({ // Define handlers for native browser drag events const handleNativeDragOver = (e: DragEvent) => { e.preventDefault(); - if (getProductImages(index).length === 0) { - // Highlight empty containers during dragover - container.classList.add('border-primary', 'bg-primary/5'); - setActiveDroppableId(`product-${index}`); - } + // Highlight all containers during dragover + container.classList.add('border-primary', 'bg-primary/5'); + setActiveDroppableId(`product-${index}`); }; const handleNativeDragLeave = (e: DragEvent) => { - if (getProductImages(index).length === 0) { - // Remove highlight when drag leaves - container.classList.remove('border-primary', 'bg-primary/5'); - if (activeDroppableId === `product-${index}`) { - setActiveDroppableId(null); - } + // Remove highlight when drag leaves + container.classList.remove('border-primary', 'bg-primary/5'); + if (activeDroppableId === `product-${index}`) { + setActiveDroppableId(null); } }; @@ -306,11 +236,6 @@ export const ImageUploadStep = ({ container.addEventListener('dragover', handleNativeDragOver); container.addEventListener('dragleave', handleNativeDragLeave); - // Log this for debugging - console.log(`Added native drag handlers to product-${index}`, { - isEmpty: getProductImages(index).length === 0 - }); - // Return cleanup function return () => { container.removeEventListener('dragover', handleNativeDragOver); @@ -318,7 +243,7 @@ export const ImageUploadStep = ({ }; } }); - }, [data, productImages]); // Re-run when data or productImages change + }, [data, productImages, activeDroppableId]); // Re-run when data or productImages change // Function to remove an image URL from a product const removeImageFromProduct = (productIndex: number, imageUrl: string) => { @@ -1166,17 +1091,19 @@ export const ImageUploadStep = ({ }); // Create a new style object with fixed dimensions to prevent distortion - const style = { + const style: React.CSSProperties = { transform: CSS.Transform.toString(transform), transition, opacity: isDragging ? 0.5 : 1, - zIndex: isDragging ? 10 : 0, // Higher z-index when dragging - touchAction: 'none' as 'none', // Prevent touch scrolling during drag + zIndex: isDragging ? 999 : 1, // Higher z-index when dragging + touchAction: 'none', // Prevent touch scrolling during drag + userSelect: 'none', // Prevent text selection during drag + cursor: isDragging ? 'grabbing' : 'grab', width: '96px', height: '96px', flexShrink: 0, flexGrow: 0, - position: 'relative' as 'relative', + position: 'relative', }; // Create a ref for the buttons to exclude them from drag listeners @@ -1190,6 +1117,11 @@ export const ImageUploadStep = ({ className="relative border rounded-md overflow-hidden flex items-center justify-center shrink-0 cursor-grab active:cursor-grabbing select-none no-native-drag" {...attributes} {...listeners} + onDragStart={(e) => { + // This ensures the native drag doesn't interfere + e.preventDefault(); + e.stopPropagation(); + }} > {image.loading ? (
@@ -1200,7 +1132,7 @@ export const ImageUploadStep = ({ <> {`Product @@ -1262,12 +1194,12 @@ export const ImageUploadStep = ({
{`Product
- {`Product ${productIndex + 1} - Image ${imgIndex + 1}`} + {`${data[productIndex].name || `Product #${productIndex + 1}`} - Image ${imgIndex + 1}`}
@@ -1282,24 +1214,19 @@ export const ImageUploadStep = ({ const getProductContainerClasses = (index: number) => { const isValidDropTarget = activeId && findContainer(activeId) !== index.toString(); const isActiveDropTarget = activeDroppableId === `product-${index}`; - const hasImages = getProductImages(index).length > 0; return cn( "flex-1 min-h-[6rem] rounded-md p-2 transition-all", - // Always add a border for empty containers to make them visible as drop targets - !hasImages && "border-2 border-dashed border-secondary-foreground/30", - // Active drop target styling + // Only show borders during active drag operations isValidDropTarget && isActiveDropTarget ? "border-2 border-dashed border-primary bg-primary/10" - : isValidDropTarget && !hasImages - ? "border-2 border-dashed border-muted-foreground/40 bg-muted/20" - : isValidDropTarget - ? "border border-dashed border-muted-foreground/30" - : "" + : isValidDropTarget + ? "border border-dashed border-muted-foreground/30" + : "" ); }; - // Add a DroppableContainer component for empty product containers + // Add a DroppableContainer component for product containers const DroppableContainer = ({ id, children, isEmpty }: { id: string; children: React.ReactNode; isEmpty: boolean }) => { const { setNodeRef } = useDroppable({ id, @@ -1315,7 +1242,8 @@ export const ImageUploadStep = ({ id={id} data-droppable="true" data-empty={isEmpty ? "true" : "false"} - className={`w-full h-full ${!isEmpty ? "flex flex-row flex-wrap gap-2" : ""}`} + className="w-full h-full flex flex-row flex-wrap gap-2" + style={{ minHeight: '100px' }} // Ensure minimum height for empty containers > {children} @@ -1398,15 +1326,13 @@ export const ImageUploadStep = ({ style={{ pointerEvents: 'auto', touchAction: 'none', - minHeight: getProductImages(index).length === 0 ? '100px' : 'auto' + minHeight: '100px' }} onDragOver={(e) => { // This is a native event handler to ensure the browser recognizes the drop zone e.preventDefault(); e.stopPropagation(); - if (getProductImages(index).length === 0) { - setActiveDroppableId(`product-${index}`); - } + setActiveDroppableId(`product-${index}`); }} > ({ ))} ) : ( -
- Drop images here -
+
)}