Image upload tweaks/fixes
This commit is contained in:
@@ -329,7 +329,13 @@ export const ImageUploadStep = ({
|
||||
handleAddImageFromUrl(index, urlInputs[index]);
|
||||
}
|
||||
}}
|
||||
onImageUpload={(files: FileList | File[]) => handleImageUpload(files, index)}
|
||||
onImageUpload={async (files: FileList | File[]) => {
|
||||
const { skipped } = await handleImageUpload(files, index);
|
||||
if (skipped > 0) {
|
||||
const label = product.name || `Product #${index + 1}`;
|
||||
toast.info(`Skipped ${skipped} duplicate ${skipped === 1 ? 'image' : 'images'} for ${label}`);
|
||||
}
|
||||
}}
|
||||
onDragOver={(e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { UnassignedImage, Product } from "../types";
|
||||
|
||||
type HandleImageUploadFn = (files: FileList | File[], productIndex: number) => Promise<void>;
|
||||
type HandleImageUploadFn = (files: FileList | File[], productIndex: number) => Promise<{ uploaded: number; skipped: number }>;
|
||||
|
||||
interface UseBulkImageUploadProps {
|
||||
data: Product[];
|
||||
@@ -88,27 +88,31 @@ export const useBulkImageUpload = ({ data, handleImageUpload }: UseBulkImageUplo
|
||||
// Function to handle bulk image upload
|
||||
const handleBulkUpload = async (files: File[]) => {
|
||||
if (!files.length) return;
|
||||
|
||||
|
||||
setProcessingBulk(true);
|
||||
const unassigned: UnassignedImage[] = [];
|
||||
|
||||
let totalUploaded = 0;
|
||||
let totalSkipped = 0;
|
||||
|
||||
for (const file of files) {
|
||||
// Extract identifiers from filename
|
||||
const identifiers = extractIdentifiers(file.name);
|
||||
let assigned = false;
|
||||
|
||||
|
||||
// Try to match each identifier
|
||||
for (const identifier of identifiers) {
|
||||
const productIndex = findProductByIdentifier(identifier);
|
||||
|
||||
|
||||
if (productIndex !== -1) {
|
||||
// Found a match, upload to this product
|
||||
await handleImageUpload([file], productIndex);
|
||||
const result = await handleImageUpload([file], productIndex);
|
||||
totalUploaded += result.uploaded;
|
||||
totalSkipped += result.skipped;
|
||||
assigned = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// If no match was found, add to unassigned
|
||||
if (!assigned) {
|
||||
unassigned.push({
|
||||
@@ -117,15 +121,17 @@ export const useBulkImageUpload = ({ data, handleImageUpload }: UseBulkImageUplo
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Update unassigned images
|
||||
setUnassignedImages(prev => [...prev, ...unassigned]);
|
||||
setProcessingBulk(false);
|
||||
|
||||
// Show summary toast
|
||||
const assignedCount = files.length - unassigned.length;
|
||||
if (assignedCount > 0) {
|
||||
toast.success(`Auto-assigned ${assignedCount} ${assignedCount === 1 ? 'image' : 'images'} to products`);
|
||||
|
||||
// Show summary toasts
|
||||
if (totalUploaded > 0) {
|
||||
toast.success(`Auto-assigned ${totalUploaded} ${totalUploaded === 1 ? 'image' : 'images'} to products`);
|
||||
}
|
||||
if (totalSkipped > 0) {
|
||||
toast.info(`Skipped ${totalSkipped} ${totalSkipped === 1 ? 'image' : 'images'} already uploaded for their products`);
|
||||
}
|
||||
if (unassigned.length > 0) {
|
||||
toast.warning(`Could not auto-assign ${unassigned.length} ${unassigned.length === 1 ? 'image' : 'images'}`);
|
||||
|
||||
@@ -144,20 +144,37 @@ export const useProductImageOperations = ({
|
||||
product.product_images = [product.product_images].filter(Boolean);
|
||||
}
|
||||
|
||||
// Only add if the URL doesn't already exist
|
||||
// Only add if the URL doesn't already exist (spread to avoid mutating frozen Immer arrays)
|
||||
if (!product.product_images.includes(imageUrl)) {
|
||||
product.product_images.push(imageUrl);
|
||||
product.product_images = [...product.product_images, imageUrl];
|
||||
}
|
||||
|
||||
return newData;
|
||||
};
|
||||
|
||||
// Function to handle image upload - update product data
|
||||
const handleImageUpload = async (files: FileList | File[], productIndex: number) => {
|
||||
if (!files || files.length === 0) return;
|
||||
|
||||
const handleImageUpload = async (files: FileList | File[], productIndex: number): Promise<{ uploaded: number; skipped: number }> => {
|
||||
if (!files || files.length === 0) return { uploaded: 0, skipped: 0 };
|
||||
|
||||
let uploaded = 0;
|
||||
let skipped = 0;
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
|
||||
// Check for exact duplicate (same filename, size, and type already uploaded for this product)
|
||||
const isDuplicate = productImages.some(
|
||||
img => img.productIndex === productIndex &&
|
||||
!img.loading &&
|
||||
img.imageUrl &&
|
||||
img.fileName === file.name &&
|
||||
img.metadata?.size === file.size
|
||||
);
|
||||
if (isDuplicate) {
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const imageId = `image-${productIndex}-${Date.now()}-${i}`;
|
||||
const productLabel = data[productIndex].name || `Product #${productIndex + 1}`;
|
||||
|
||||
@@ -305,17 +322,20 @@ export const useProductImageOperations = ({
|
||||
|
||||
// Update the product data with the new image URL
|
||||
addImageToProduct(productIndex, result.imageUrl);
|
||||
|
||||
|
||||
uploaded++;
|
||||
toast.success(`Image uploaded for ${productLabel}`);
|
||||
} catch (error) {
|
||||
console.error('Upload error:', error);
|
||||
|
||||
|
||||
// Remove the failed image from our state
|
||||
setProductImages(prev => prev.filter(img => img.id !== imageId));
|
||||
|
||||
|
||||
toast.error(`Failed to upload image: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||
}
|
||||
}
|
||||
|
||||
return { uploaded, skipped };
|
||||
};
|
||||
|
||||
// Function to remove an image - update to work with product_images
|
||||
|
||||
Reference in New Issue
Block a user