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'
|
import type { ImageItem } from '@/types/images'
|
||||||
|
|
||||||
function ImagesPageInner() {
|
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)
|
const [lightboxItem, setLightboxItem] = useState<ImageItem | null>(null)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -42,6 +42,7 @@ function ImagesPageInner() {
|
|||||||
onUpload={uploadOne}
|
onUpload={uploadOne}
|
||||||
onRemove={removeItem}
|
onRemove={removeItem}
|
||||||
onOpenLightbox={setLightboxItem}
|
onOpenLightbox={setLightboxItem}
|
||||||
|
onAssignProduct={assignProduct}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ interface ImageCardProps {
|
|||||||
onUpload: (id: string) => void
|
onUpload: (id: string) => void
|
||||||
onRemove: (id: string) => void
|
onRemove: (id: string) => void
|
||||||
onOpenLightbox: (item: ImageItem) => void
|
onOpenLightbox: (item: ImageItem) => void
|
||||||
|
onAssignProduct: (id: string, shopifyProductId: string, shopifyProductTitle: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const STATUS_LABELS: Record<string, { label: string; color: string }> = {
|
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" />
|
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({
|
export function ImageCard({
|
||||||
item,
|
item,
|
||||||
onFeatherChange,
|
onFeatherChange,
|
||||||
@@ -43,6 +97,7 @@ export function ImageCard({
|
|||||||
onUpload,
|
onUpload,
|
||||||
onRemove,
|
onRemove,
|
||||||
onOpenLightbox,
|
onOpenLightbox,
|
||||||
|
onAssignProduct,
|
||||||
}: ImageCardProps) {
|
}: ImageCardProps) {
|
||||||
const statusInfo = STATUS_LABELS[item.status] ?? { label: item.status, color: 'text-slate-400' }
|
const statusInfo = STATUS_LABELS[item.status] ?? { label: item.status, color: 'text-slate-400' }
|
||||||
const isProcessing = ['pending', 'converting', 'processing', 'searching', 'uploading'].includes(item.status)
|
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>}
|
{item.error && <p className="text-xs text-red-400 mt-0.5 truncate" title={item.error}>{item.error}</p>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{item.status === 'not_found' && (
|
||||||
|
<ProductSearch itemId={item.id} onAssign={onAssignProduct} />
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Contrôles feather + rotation */}
|
{/* Contrôles feather + rotation */}
|
||||||
<div className={canAdjust ? '' : 'opacity-40 pointer-events-none'}>
|
<div className={canAdjust ? '' : 'opacity-40 pointer-events-none'}>
|
||||||
<label className="block text-xs text-slate-500 mb-1">
|
<label className="block text-xs text-slate-500 mb-1">
|
||||||
|
|||||||
@@ -257,5 +257,12 @@ export function useImages() {
|
|||||||
dispatch({ type: 'REMOVE', id })
|
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