This commit is contained in:
Your Name committed 2026-10-02 09:05:58 +07:00
1 parent f258e3e176
commit 3631f59bba
7 files changed
+524 -57

No files matched your search

+92 -1
View File
@@ -127,6 +127,7 @@ export default function ProductEditPage() {
const [options, setOptions] = useState<{name: Record<string, string>, values: Record<string, string>[]}[]>([])
const [variants, setVariants] = useState<Partial<Variant>[]>([])
const [images, setImages] = useState<EditableProductImage[]>([])
const [titleGraphicUrl, setTitleGraphicUrl] = useState<string>('')
const [availableAttributes, setAvailableAttributes] = useState<AttributeDefinition[]>([])
const [attributes, setAttributes] = useState<EditableAttribute[]>([])
const [selectedDefinitionId, setSelectedDefinitionId] = useState<number>(0)
@@ -211,6 +212,7 @@ export default function ProductEditPage() {
setOptions(parseOptions(prodData.options))
setVariants(prodData.variants || [])
setImages(prodData.images || [])
setTitleGraphicUrl(prodData.titleGraphicUrl || '')
setAttributes((prodData.attributes || []).map(parseEditableAttribute))
await fetchAttributeDefinitions(prodData.categoryId)
} else {
@@ -219,6 +221,7 @@ export default function ProductEditPage() {
setName({ th: '', en: '' })
setDescription({ th: '', en: '' })
setDetails({ th: '', en: '' })
setTitleGraphicUrl('')
setOptions([])
setVariants([])
setImages([])
@@ -384,6 +387,7 @@ export default function ProductEditPage() {
body: JSON.stringify({
categoryId,
name,
titleGraphicUrl: titleGraphicUrl.trim() || null,
description,
details,
options,
@@ -395,7 +399,8 @@ export default function ProductEditPage() {
price: Number(v.price) || 0,
stock: Number(v.stock) || 0,
sku: v.sku || '',
barcode: v.barcode || ''
barcode: v.barcode || '',
imageUrl: v.imageUrl || null,
})),
images: images.filter(img => img.url).map((img, idx) => ({
...(img.id ? { id: img.id } : {}),
@@ -550,6 +555,57 @@ export default function ProductEditPage() {
</select>
</div>
<div className="md:col-span-2 space-y-2 p-4 rounded-xl border border-white/10 bg-white/[0.02]">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<ImageIcon className="w-4 h-4 text-cyan-glow" />
<label className="text-[10px] uppercase font-bold tracking-widest text-slate-400">
Title Graphic / Series Logo (Wide PNG Transparent / SVG ~5:1)
</label>
</div>
{titleGraphicUrl && (
<button
type="button"
onClick={() => setTitleGraphicUrl('')}
className="text-xs text-rose-400 hover:text-rose-300 transition-colors"
>
Remove Graphic
</button>
)}
</div>
{titleGraphicUrl ? (
<div className="flex items-center justify-between gap-4 p-3 bg-night-950/80 border border-white/10 rounded-xl">
<div className="relative h-12 w-64 max-w-full bg-black/60 rounded-lg p-2 border border-white/5 flex items-center">
<img
src={titleGraphicUrl}
alt="Title graphic preview"
className="h-full w-auto max-w-full object-contain"
/>
</div>
<button
type="button"
onClick={() => openMediaModal((url) => setTitleGraphicUrl(url))}
className="px-4 py-2 bg-white/5 hover:bg-white/10 border border-white/10 rounded-lg text-xs font-semibold text-white transition-all shrink-0"
>
Change Graphic
</button>
</div>
) : (
<button
type="button"
onClick={() => openMediaModal((url) => setTitleGraphicUrl(url))}
className="w-full py-4 border-2 border-dashed border-white/10 hover:border-cyan-glow/40 bg-night-950/40 hover:bg-night-950/80 rounded-xl text-xs font-semibold text-slate-400 hover:text-cyan-glow transition-all flex items-center justify-center gap-2"
>
<ImagePlus className="w-4 h-4" />
Upload Title Graphic (e.g. &quot;NORIFUMI Oil Cup GP Series&quot; Logo PNG)
</button>
)}
<p className="text-[11px] text-slate-500">
Optional. Displays as a branded logo banner above the product title on the website.
</p>
</div>
<div className="md:col-span-2 space-y-2">
<label className="text-[10px] uppercase font-bold tracking-widest text-slate-500 ml-1">Short Description ({activeLang.toUpperCase()})</label>
<textarea
@@ -1068,6 +1124,7 @@ export default function ProductEditPage() {
)) : (
<th className="py-3 px-4 font-bold">Variant</th>
)}
<th className="py-3 px-4 font-bold">Swatch (1:1)</th>
<th className="py-3 px-4 font-bold">Price (THB)</th>
<th className="py-3 px-4 font-bold">Stock</th>
<th className="py-3 px-4 font-bold">SKU</th>
@@ -1086,6 +1143,40 @@ export default function ProductEditPage() {
}) : (
<td className="py-3 px-4 text-sm font-medium text-white">{v.option1 || 'Default'}</td>
)}
<td className="py-3 px-4">
{v.imageUrl ? (
<div className="flex items-center gap-2">
<div className="relative w-10 h-10 rounded-lg overflow-hidden border border-white/15 bg-black/60 shrink-0">
<img src={v.imageUrl} alt="Variant swatch" className="w-full h-full object-cover" />
</div>
<div className="flex flex-col gap-0.5">
<button
type="button"
onClick={() => openMediaModal((url) => handleUpdateVariant(idx, 'imageUrl', url))}
className="text-[10px] text-cyan-glow hover:underline text-left"
>
Change
</button>
<button
type="button"
onClick={() => handleUpdateVariant(idx, 'imageUrl', '')}
className="text-[10px] text-rose-400 hover:underline text-left"
>
Remove
</button>
</div>
</div>
) : (
<button
type="button"
onClick={() => openMediaModal((url) => handleUpdateVariant(idx, 'imageUrl', url))}
className="px-2.5 py-1.5 rounded-lg border border-dashed border-white/15 hover:border-cyan-glow/40 bg-white/5 hover:bg-white/10 text-[11px] text-slate-400 hover:text-cyan-glow flex items-center gap-1.5 transition-all"
>
<ImagePlus className="w-3.5 h-3.5" />
<span>1:1 Image</span>
</button>
)}
</td>
<td className="py-3 px-4">
<input
type="number"