feat: make AI background removal optional with resize-only upload and batch detour selection

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-25 08:21:46 +02:00
parent c95093a81f
commit eec9bd5dce
5 changed files with 184 additions and 57 deletions
+58 -2
View File
@@ -1,5 +1,5 @@
'use client'
import { useState } from 'react'
import { useState, useCallback } from 'react'
import { Header } from '@/components/layout/Header'
import { DropZone } from '@/components/images/DropZone'
import { ImageCard } from '@/components/images/ImageCard'
@@ -10,8 +10,36 @@ import { useImages } from '@/hooks/useImages'
import type { ImageItem } from '@/types/images'
function ImagesPageInner() {
const { items, addFiles, setFeather, setAlphaThreshold, rotate, uploadOne, uploadAll, removeItem, assignProduct, reprocess } = useImages()
const { items, addFiles, setFeather, setAlphaThreshold, rotate, uploadOne, uploadAll, removeItem, assignProduct, reprocess, detour } = useImages()
const [lightboxItem, setLightboxItem] = useState<ImageItem | null>(null)
const [selected, setSelected] = useState<Set<string>>(new Set())
const toggleSelect = useCallback((id: string, value: boolean) => {
setSelected(prev => {
const next = new Set(prev)
if (value) next.add(id)
else next.delete(id)
return next
})
}, [])
const selectAll = useCallback(() => {
const detourable = items.filter(i =>
i.status === 'converted' || i.status === 'not_found' || i.status === 'error'
)
setSelected(new Set(detourable.map(i => i.id) as string[]))
}, [items])
const clearSelection = useCallback(() => setSelected(new Set()), [])
const detourSelected = useCallback(() => {
Array.from(selected).forEach(id => detour(id))
setSelected(new Set())
}, [selected, detour])
const detourable = items.filter(i =>
i.status === 'converted' || i.status === 'not_found' || i.status === 'error'
)
return (
<>
@@ -31,12 +59,39 @@ function ImagesPageInner() {
<GlobalActions items={items} onUploadAll={uploadAll} />
)}
{/* Barre de sélection multiple pour détourage par lot */}
{detourable.length > 0 && (
<div className="flex items-center gap-3 px-3 py-2 bg-slate-800 rounded-lg border border-slate-700">
<span className="text-xs text-slate-400">{detourable.length} image{detourable.length > 1 ? 's' : ''} sans détourage</span>
<button onClick={selectAll} className="text-xs text-indigo-400 hover:text-indigo-300">
Tout sélectionner
</button>
{selected.size > 0 && (
<>
<span className="text-xs text-slate-500">·</span>
<span className="text-xs text-white font-medium">{selected.size} sélectionnée{selected.size > 1 ? 's' : ''}</span>
<button
onClick={detourSelected}
className="ml-auto px-3 py-1 text-xs bg-purple-700 hover:bg-purple-600 text-white font-semibold rounded"
>
Détourer la sélection
</button>
<button onClick={clearSelection} className="text-xs text-slate-500 hover:text-slate-300">
</button>
</>
)}
</div>
)}
{items.length > 0 && (
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4">
{items.map((item) => (
<ImageCard
key={item.id}
item={item}
selected={selected.has(item.id)}
onSelect={toggleSelect}
onFeatherChange={setFeather}
onAlphaThresholdChange={setAlphaThreshold}
onRotate={rotate}
@@ -45,6 +100,7 @@ function ImagesPageInner() {
onOpenLightbox={setLightboxItem}
onAssignProduct={assignProduct}
onReprocess={reprocess}
onDetour={detour}
/>
))}
</div>