From 41f7f33746fdfdf97315a14b040f00bbf532843a Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 26 Feb 2025 16:31:56 -0500 Subject: [PATCH] Make images rearrange-able with drag and drop --- .../steps/ImageUploadStep/ImageUploadStep.tsx | 195 ++++++++++++++---- package-lock.json | 92 +++++++++ package.json | 3 + 3 files changed, 255 insertions(+), 35 deletions(-) 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 b38b060..b369f37 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 @@ -1,7 +1,7 @@ import { useCallback, useState, useRef, useEffect } from "react"; import { useRsi } from "../../hooks/useRsi"; import { Button } from "@/components/ui/button"; -import { Loader2, Upload, Trash2, AlertCircle } from "lucide-react"; +import { Loader2, Upload, Trash2, AlertCircle, GripVertical } from "lucide-react"; import { Card, CardContent } from "@/components/ui/card"; import { Separator } from "@/components/ui/separator"; import { ScrollArea } from "@/components/ui/scroll-area"; @@ -11,6 +11,23 @@ import config from "@/config"; import { useDropzone } from "react-dropzone"; import { cn } from "@/lib/utils"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { + DndContext, + closestCenter, + KeyboardSensor, + PointerSensor, + useSensor, + useSensors, + DragEndEvent +} from '@dnd-kit/core'; +import { + arrayMove, + SortableContext, + sortableKeyboardCoordinates, + useSortable, + rectSortingStrategy +} from '@dnd-kit/sortable'; +import { CSS } from '@dnd-kit/utilities'; type Props = { data: any[]; @@ -31,6 +48,11 @@ type UnassignedImage = { previewUrl: string; } +// Add a product ID type to handle the sortable state +type ProductImageSortable = ProductImage & { + id: string; +}; + export const ImageUploadStep = ({ data, file, @@ -39,12 +61,70 @@ export const ImageUploadStep = ({ }: Props) => { const { translations } = useRsi(); const [isSubmitting, setIsSubmitting] = useState(false); - const [productImages, setProductImages] = useState([]); + const [productImages, setProductImages] = useState([]); const fileInputRefs = useRef<{ [key: number]: HTMLInputElement | null }>({}); const [unassignedImages, setUnassignedImages] = useState([]); const [processingBulk, setProcessingBulk] = useState(false); const [showUnassigned, setShowUnassigned] = useState(false); + // Set up sensors for drag and drop + const sensors = useSensors( + useSensor(PointerSensor), + useSensor(KeyboardSensor, { + coordinateGetter: sortableKeyboardCoordinates, + }) + ); + + // Handle drag end event to reorder images + const handleDragEnd = (event: DragEndEvent, productIndex: number) => { + const { active, over } = event; + + if (over && active.id !== over.id) { + setProductImages(items => { + // Filter to get only the images for this product + const productFilteredItems = items.filter(item => item.productIndex === productIndex); + + // Find the indices within this filtered list + const oldIndex = productFilteredItems.findIndex(item => item.id === active.id); + const newIndex = productFilteredItems.findIndex(item => item.id === over.id); + + if (oldIndex === -1 || newIndex === -1) return items; + + // Reorder the filtered items + const newFilteredItems = arrayMove(productFilteredItems, oldIndex, newIndex); + + // Create a new full list replacing the items for this product with the reordered ones + const newItems = items.filter(item => item.productIndex !== productIndex); + newItems.push(...newFilteredItems); + + // Update the product data with the new image order + updateProductImageOrder(productIndex, newFilteredItems); + + return newItems; + }); + } + }; + + // Function to update product data with the new image order + const updateProductImageOrder = (productIndex: number, orderedImages: ProductImageSortable[]) => { + // Create a copy of the data + const newData = [...data]; + + // Get the current product + const product = newData[productIndex]; + + // Get the ordered URLs + const orderedUrls = orderedImages.map(img => img.imageUrl); + + // Update the product with the ordered URLs + product.image_url = orderedUrls.join(','); + + // Update the data + newData[productIndex] = product; + + return newData; + }; + // Function to handle image upload const handleImageUpload = async (files: FileList | File[], productIndex: number) => { if (!files || files.length === 0) return; @@ -53,7 +133,8 @@ export const ImageUploadStep = ({ const file = files[i]; // Add placeholder for this image - const newImage: ProductImage = { + const newImage: ProductImageSortable = { + id: `image-${productIndex}-${Date.now()}-${i}`, // Generate a unique ID productIndex, imageUrl: '', loading: true, @@ -509,7 +590,7 @@ export const ImageUploadStep = ({

{image.file.name}