feat: sélection manuelle de produit Shopify quand réf. introuvable

This commit is contained in:
2026-06-09 21:00:45 +02:00
parent 071f126244
commit d1e78e91e4
3 changed files with 69 additions and 2 deletions
+2 -1
View File
@@ -10,7 +10,7 @@ import { useImages } from '@/hooks/useImages'
import type { ImageItem } from '@/types/images'
function ImagesPageInner() {
const { items, addFiles, setFeather, rotate, uploadOne, uploadAll, removeItem } = useImages()
const { items, addFiles, setFeather, rotate, uploadOne, uploadAll, removeItem, assignProduct } = useImages()
const [lightboxItem, setLightboxItem] = useState<ImageItem | null>(null)
return (
@@ -42,6 +42,7 @@ function ImagesPageInner() {
onUpload={uploadOne}
onRemove={removeItem}
onOpenLightbox={setLightboxItem}
onAssignProduct={assignProduct}
/>
))}
</div>
+59
View File
@@ -9,6 +9,7 @@ interface ImageCardProps {
onUpload: (id: string) => void
onRemove: (id: string) => void
onOpenLightbox: (item: ImageItem) => void
onAssignProduct: (id: string, shopifyProductId: string, shopifyProductTitle: string) => void
}
const STATUS_LABELS: Record<string, { label: string; color: string }> = {
@@ -36,6 +37,59 @@ function BlobThumbnail({ blob, alt }: { blob: Blob; alt: string }) {
return <img src={url} alt={alt} className="w-full h-full object-contain" />
}
function ProductSearch({ itemId, onAssign }: { itemId: string; onAssign: (id: string, shopifyProductId: string, title: string) => void }) {
const [query, setQuery] = useState('')
const [results, setResults] = useState<{ shopifyId: string; title: string }[]>([])
const [loading, setLoading] = useState(false)
async function search() {
if (!query.trim()) return
setLoading(true)
try {
const res = await fetch(`/api/products/search?ref=${encodeURIComponent(query.trim())}`)
const data = await res.json()
if (data.found) setResults([{ shopifyId: data.shopifyId, title: data.title }])
else setResults([])
} finally {
setLoading(false)
}
}
return (
<div className="flex flex-col gap-1.5 mt-1">
<div className="flex gap-1">
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && search()}
placeholder="Rechercher un produit…"
className="flex-1 px-2 py-1 text-xs bg-slate-800 border border-slate-600 rounded text-slate-200 placeholder-slate-500 focus:outline-none focus:border-indigo-500"
/>
<button
onClick={search}
disabled={loading}
className="px-2 py-1 text-xs bg-slate-700 hover:bg-slate-600 rounded text-slate-300 disabled:opacity-50"
>
{loading ? '…' : '🔍'}
</button>
</div>
{results.length === 0 && !loading && query && (
<p className="text-xs text-slate-500">Aucun résultat</p>
)}
{results.map((r) => (
<button
key={r.shopifyId}
onClick={() => onAssign(itemId, r.shopifyId, r.title)}
className="text-left px-2 py-1.5 text-xs bg-slate-800 hover:bg-indigo-900 border border-slate-600 hover:border-indigo-500 rounded text-slate-200 truncate"
>
{r.title}
</button>
))}
</div>
)
}
export function ImageCard({
item,
onFeatherChange,
@@ -43,6 +97,7 @@ export function ImageCard({
onUpload,
onRemove,
onOpenLightbox,
onAssignProduct,
}: ImageCardProps) {
const statusInfo = STATUS_LABELS[item.status] ?? { label: item.status, color: 'text-slate-400' }
const isProcessing = ['pending', 'converting', 'processing', 'searching', 'uploading'].includes(item.status)
@@ -88,6 +143,10 @@ export function ImageCard({
{item.error && <p className="text-xs text-red-400 mt-0.5 truncate" title={item.error}>{item.error}</p>}
</div>
{item.status === 'not_found' && (
<ProductSearch itemId={item.id} onAssign={onAssignProduct} />
)}
{/* Contrôles feather + rotation */}
<div className={canAdjust ? '' : 'opacity-40 pointer-events-none'}>
<label className="block text-xs text-slate-500 mb-1">
+8 -1
View File
@@ -257,5 +257,12 @@ export function useImages() {
dispatch({ type: 'REMOVE', id })
}, [])
return { items, addFiles, setFeather, rotate, uploadOne, uploadAll, removeItem }
const assignProduct = useCallback(
(id: string, shopifyProductId: string, shopifyProductTitle: string) => {
update(id, { shopifyProductId, shopifyProductTitle, status: 'ready' })
},
[update],
)
return { items, addFiles, setFeather, rotate, uploadOne, uploadAll, removeItem, assignProduct }
}