Image upload tweaks/fixes

This commit is contained in:
2026-04-02 14:04:56 -04:00
parent 54f8cc2706
commit e43abdafd0
3 changed files with 54 additions and 22 deletions

View File

@@ -329,7 +329,13 @@ export const ImageUploadStep = ({
handleAddImageFromUrl(index, urlInputs[index]); 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) => { onDragOver={(e: React.DragEvent) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();

View File

@@ -2,7 +2,7 @@ import { useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { UnassignedImage, Product } from "../types"; 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 { interface UseBulkImageUploadProps {
data: Product[]; data: Product[];
@@ -88,27 +88,31 @@ export const useBulkImageUpload = ({ data, handleImageUpload }: UseBulkImageUplo
// Function to handle bulk image upload // Function to handle bulk image upload
const handleBulkUpload = async (files: File[]) => { const handleBulkUpload = async (files: File[]) => {
if (!files.length) return; if (!files.length) return;
setProcessingBulk(true); setProcessingBulk(true);
const unassigned: UnassignedImage[] = []; const unassigned: UnassignedImage[] = [];
let totalUploaded = 0;
let totalSkipped = 0;
for (const file of files) { for (const file of files) {
// Extract identifiers from filename // Extract identifiers from filename
const identifiers = extractIdentifiers(file.name); const identifiers = extractIdentifiers(file.name);
let assigned = false; let assigned = false;
// Try to match each identifier // Try to match each identifier
for (const identifier of identifiers) { for (const identifier of identifiers) {
const productIndex = findProductByIdentifier(identifier); const productIndex = findProductByIdentifier(identifier);
if (productIndex !== -1) { if (productIndex !== -1) {
// Found a match, upload to this product // 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; assigned = true;
break; break;
} }
} }
// If no match was found, add to unassigned // If no match was found, add to unassigned
if (!assigned) { if (!assigned) {
unassigned.push({ unassigned.push({
@@ -117,15 +121,17 @@ export const useBulkImageUpload = ({ data, handleImageUpload }: UseBulkImageUplo
}); });
} }
} }
// Update unassigned images // Update unassigned images
setUnassignedImages(prev => [...prev, ...unassigned]); setUnassignedImages(prev => [...prev, ...unassigned]);
setProcessingBulk(false); setProcessingBulk(false);
// Show summary toast // Show summary toasts
const assignedCount = files.length - unassigned.length; if (totalUploaded > 0) {
if (assignedCount > 0) { toast.success(`Auto-assigned ${totalUploaded} ${totalUploaded === 1 ? 'image' : 'images'} to products`);
toast.success(`Auto-assigned ${assignedCount} ${assignedCount === 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) { if (unassigned.length > 0) {
toast.warning(`Could not auto-assign ${unassigned.length} ${unassigned.length === 1 ? 'image' : 'images'}`); toast.warning(`Could not auto-assign ${unassigned.length} ${unassigned.length === 1 ? 'image' : 'images'}`);

View File

@@ -144,20 +144,37 @@ export const useProductImageOperations = ({
product.product_images = [product.product_images].filter(Boolean); 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)) { if (!product.product_images.includes(imageUrl)) {
product.product_images.push(imageUrl); product.product_images = [...product.product_images, imageUrl];
} }
return newData; return newData;
}; };
// Function to handle image upload - update product data // Function to handle image upload - update product data
const handleImageUpload = async (files: FileList | File[], productIndex: number) => { const handleImageUpload = async (files: FileList | File[], productIndex: number): Promise<{ uploaded: number; skipped: number }> => {
if (!files || files.length === 0) return; if (!files || files.length === 0) return { uploaded: 0, skipped: 0 };
let uploaded = 0;
let skipped = 0;
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
const file = files[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 imageId = `image-${productIndex}-${Date.now()}-${i}`;
const productLabel = data[productIndex].name || `Product #${productIndex + 1}`; const productLabel = data[productIndex].name || `Product #${productIndex + 1}`;
@@ -305,17 +322,20 @@ export const useProductImageOperations = ({
// Update the product data with the new image URL // Update the product data with the new image URL
addImageToProduct(productIndex, result.imageUrl); addImageToProduct(productIndex, result.imageUrl);
uploaded++;
toast.success(`Image uploaded for ${productLabel}`); toast.success(`Image uploaded for ${productLabel}`);
} catch (error) { } catch (error) {
console.error('Upload error:', error); console.error('Upload error:', error);
// Remove the failed image from our state // Remove the failed image from our state
setProductImages(prev => prev.filter(img => img.id !== imageId)); setProductImages(prev => prev.filter(img => img.id !== imageId));
toast.error(`Failed to upload image: ${error instanceof Error ? error.message : 'Unknown error'}`); 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 // Function to remove an image - update to work with product_images