feat: sélection manuelle de produit Shopify quand réf. introuvable
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user