Image upload tweaks/fixes
This commit is contained in:
@@ -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();
|
||||||
|
|||||||
@@ -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[];
|
||||||
@@ -91,6 +91,8 @@ export const useBulkImageUpload = ({ data, handleImageUpload }: UseBulkImageUplo
|
|||||||
|
|
||||||
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
|
||||||
@@ -103,7 +105,9 @@ export const useBulkImageUpload = ({ data, handleImageUpload }: UseBulkImageUplo
|
|||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
@@ -122,10 +126,12 @@ export const useBulkImageUpload = ({ data, handleImageUpload }: UseBulkImageUplo
|
|||||||
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'}`);
|
||||||
|
|||||||
@@ -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}`;
|
||||||
|
|
||||||
@@ -306,6 +323,7 @@ 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);
|
||||||
@@ -316,6 +334,8 @@ export const useProductImageOperations = ({
|
|||||||
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
|
||||||
|
|||||||
Reference in New Issue
Block a user