|
|
|
@@ -1,4 +1,4 @@
|
|
|
|
|
import { useState, useEffect } from 'react'
|
|
|
|
|
import { useState, useEffect, useRef } from 'react'
|
|
|
|
|
import type { Product, ProductCreate, Store } from '../../api/shopping'
|
|
|
|
|
import { searchProducts, createProduct, updateProduct, deleteProduct } from '../../api/shopping'
|
|
|
|
|
import Modal from '../Modal'
|
|
|
|
@@ -26,6 +26,8 @@ const emptyForm: ProductCreate = {
|
|
|
|
|
name: '', brand: '', category: '', description: '',
|
|
|
|
|
default_unit: '', barcode: '', price: '', quantity_per_unit: '',
|
|
|
|
|
default_store_id: undefined,
|
|
|
|
|
image_path: undefined,
|
|
|
|
|
thumbnail_path: undefined,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default function CatalogueModal({ stores, onClose }: CatalogueModalProps) {
|
|
|
|
@@ -35,7 +37,9 @@ export default function CatalogueModal({ stores, onClose }: CatalogueModalProps)
|
|
|
|
|
const [creating, setCreating] = useState(false)
|
|
|
|
|
const [form, setForm] = useState<ProductCreate>(emptyForm)
|
|
|
|
|
const [saving, setSaving] = useState(false)
|
|
|
|
|
const [uploading, setUploading] = useState(false)
|
|
|
|
|
const [error, setError] = useState<string | null>(null)
|
|
|
|
|
const photoRef = useRef<HTMLInputElement>(null)
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
void loadProducts()
|
|
|
|
@@ -67,6 +71,8 @@ export default function CatalogueModal({ stores, onClose }: CatalogueModalProps)
|
|
|
|
|
price: p.price ?? '',
|
|
|
|
|
quantity_per_unit: p.quantity_per_unit ?? '',
|
|
|
|
|
default_store_id: p.default_store_id ?? undefined,
|
|
|
|
|
image_path: p.image_path ?? undefined,
|
|
|
|
|
thumbnail_path: p.thumbnail_path ?? undefined,
|
|
|
|
|
})
|
|
|
|
|
setEditing(p)
|
|
|
|
|
setCreating(false)
|
|
|
|
@@ -90,6 +96,28 @@ export default function CatalogueModal({ stores, onClose }: CatalogueModalProps)
|
|
|
|
|
price: f.price?.trim() || undefined,
|
|
|
|
|
quantity_per_unit: f.quantity_per_unit?.trim() || undefined,
|
|
|
|
|
default_store_id: f.default_store_id || undefined,
|
|
|
|
|
image_path: f.image_path || undefined,
|
|
|
|
|
thumbnail_path: f.thumbnail_path || undefined,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function handlePhotoChange(e: React.ChangeEvent<HTMLInputElement>) {
|
|
|
|
|
const file = e.target.files?.[0]
|
|
|
|
|
if (!file) return
|
|
|
|
|
setUploading(true)
|
|
|
|
|
setError(null)
|
|
|
|
|
try {
|
|
|
|
|
const fd = new FormData()
|
|
|
|
|
fd.append('file', file)
|
|
|
|
|
const res = await fetch('/api/media/upload?context=product', { method: 'POST', body: fd })
|
|
|
|
|
if (!res.ok) throw new Error('Upload échoué')
|
|
|
|
|
const data = await res.json() as { file_path: string; thumbnail_path: string | null }
|
|
|
|
|
setForm(f => ({ ...f, image_path: data.file_path, thumbnail_path: data.thumbnail_path ?? undefined }))
|
|
|
|
|
} catch {
|
|
|
|
|
setError('Erreur lors de l\'upload photo')
|
|
|
|
|
} finally {
|
|
|
|
|
setUploading(false)
|
|
|
|
|
if (photoRef.current) photoRef.current.value = ''
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -124,6 +152,7 @@ export default function CatalogueModal({ stores, onClose }: CatalogueModalProps)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const isFormOpen = creating || editing !== null
|
|
|
|
|
const previewSrc = form.thumbnail_path ?? form.image_path
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Modal title="Catalogue d'articles" onClose={onClose} width={580}>
|
|
|
|
@@ -134,6 +163,38 @@ export default function CatalogueModal({ stores, onClose }: CatalogueModalProps)
|
|
|
|
|
{isFormOpen ? (
|
|
|
|
|
/* ── Formulaire création / édition ── */
|
|
|
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
|
|
|
|
|
{/* Zone photo */}
|
|
|
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
|
|
|
|
{previewSrc ? (
|
|
|
|
|
<img
|
|
|
|
|
src={`/media/${previewSrc}`}
|
|
|
|
|
alt="photo"
|
|
|
|
|
style={{ width: 56, height: 56, objectFit: 'cover', borderRadius: 8, flexShrink: 0 }}
|
|
|
|
|
/>
|
|
|
|
|
) : (
|
|
|
|
|
<div style={{
|
|
|
|
|
width: 56, height: 56, borderRadius: 8, flexShrink: 0,
|
|
|
|
|
background: 'var(--bg-4)', border: '1px dashed var(--bg-5)',
|
|
|
|
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
|
|
|
color: 'var(--ink-4)', fontSize: 22,
|
|
|
|
|
}}>📷</div>
|
|
|
|
|
)}
|
|
|
|
|
<input ref={photoRef} type="file" accept="image/*" capture="environment" style={{ display: 'none' }} onChange={handlePhotoChange} />
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={() => photoRef.current?.click()}
|
|
|
|
|
disabled={uploading}
|
|
|
|
|
style={{ padding: '6px 14px', borderRadius: 8, border: '1px solid var(--bg-5)', background: 'var(--bg-4)', color: 'var(--ink-2)', cursor: 'pointer', fontFamily: 'var(--font-ui)', fontSize: 13 }}
|
|
|
|
|
>{uploading ? '…' : previewSrc ? 'Changer' : 'Ajouter photo'}</button>
|
|
|
|
|
{previewSrc && (
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={() => setForm(f => ({ ...f, image_path: undefined, thumbnail_path: undefined }))}
|
|
|
|
|
style={{ padding: '6px 10px', borderRadius: 8, border: 'none', background: 'transparent', color: 'var(--err)', cursor: 'pointer', fontSize: 14 }}
|
|
|
|
|
>✕</button>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<input
|
|
|
|
|
style={inputStyle} placeholder="Nom *" value={form.name}
|
|
|
|
|
onChange={e => setForm(f => ({ ...f, name: e.target.value }))}
|
|
|
|
@@ -236,6 +297,15 @@ export default function CatalogueModal({ stores, onClose }: CatalogueModalProps)
|
|
|
|
|
userSelect: 'none',
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{p.thumbnail_path || p.image_path ? (
|
|
|
|
|
<img
|
|
|
|
|
src={`/media/${p.thumbnail_path ?? p.image_path}`}
|
|
|
|
|
alt=""
|
|
|
|
|
style={{ width: 36, height: 36, objectFit: 'cover', borderRadius: 6, flexShrink: 0 }}
|
|
|
|
|
/>
|
|
|
|
|
) : (
|
|
|
|
|
<div style={{ width: 36, height: 36, borderRadius: 6, background: 'var(--bg-4)', flexShrink: 0 }} />
|
|
|
|
|
)}
|
|
|
|
|
<div style={{ flex: 1, minWidth: 0 }}>
|
|
|
|
|
<div style={{ color: 'var(--ink-1)', fontFamily: 'var(--font-ui)', fontSize: 14, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
|
|
|
|
{p.name}
|
|
|
|
|