This commit is contained in:
Your Name committed 2026-10-02 09:27:12 +07:00
1 parent 3631f59bba
commit c3c2d97cc1
2 files changed
+94 -3

No files matched your search

+81 -3
View File
@@ -124,7 +124,7 @@ export default function ProductEditPage() {
const [name, setName] = useState<Record<string, string>>({})
const [description, setDescription] = useState<Record<string, string>>({})
const [details, setDetails] = useState<Record<string, string>>({})
const [options, setOptions] = useState<{name: Record<string, string>, values: Record<string, string>[]}[]>([])
const [options, setOptions] = useState<{name: Record<string, string>, values: (Record<string, string> & { imageUrl?: string })[]}[]>([])
const [variants, setVariants] = useState<Partial<Variant>[]>([])
const [images, setImages] = useState<EditableProductImage[]>([])
const [titleGraphicUrl, setTitleGraphicUrl] = useState<string>('')
@@ -254,7 +254,7 @@ export default function ProductEditPage() {
setVariants(updated)
}
const regenerateVariants = (newOptions: {name: Record<string, string>, values: Record<string, string>[]}[]) => {
const regenerateVariants = (newOptions: {name: Record<string, string>, values: (Record<string, string> & { imageUrl?: string })[]}[]) => {
setVariants(prevVariants => {
const validOptions = newOptions.filter(o => o.name && (o.name['th'] || o.name['en']) && o.values.length > 0)
if (validOptions.length === 0) {
@@ -277,7 +277,15 @@ export default function ProductEditPage() {
const option3 = combo[2] || ''
const existing = prevVariants.find(v => v.option1 === option1 && v.option2 === option2 && v.option3 === option3)
return existing || { option1, option2, option3, price: 0, stock: 0, sku: '', barcode: '' }
const opt1Val = newOptions[0]?.values.find(v => (v['th'] || v['en'] || Object.values(v)[0]) === option1)
const swatchUrl = opt1Val?.imageUrl || null
if (existing) {
return {
...existing,
imageUrl: existing.imageUrl || swatchUrl || null,
}
}
return { option1, option2, option3, price: 0, stock: 0, sku: '', barcode: '', imageUrl: swatchUrl }
})
})
}
@@ -1058,6 +1066,76 @@ export default function ProductEditPage() {
className="w-full px-4 py-2 bg-night-950 border border-white/10 focus:border-cyan-glow/30 rounded-xl outline-none text-sm text-white transition-all shadow-inner"
placeholder={`Value ${vIdx + 1}`}
/>
{val.imageUrl ? (
<div className="relative group shrink-0 w-9 h-9 rounded-xl overflow-hidden border border-white/20 bg-black/60 flex items-center justify-center">
<img src={val.imageUrl} alt="Swatch" className="w-full h-full object-cover" />
<div className="absolute inset-0 bg-black/75 opacity-0 group-hover:opacity-100 flex items-center justify-center gap-1 transition-opacity">
<button
type="button"
onClick={() => {
openMediaModal((url) => {
const newOpts = [...options]
newOpts[idx].values[vIdx] = { ...newOpts[idx].values[vIdx], imageUrl: url }
setOptions(newOpts)
const valName = val[activeLang] || val['th'] || val['en'] || ''
if (valName) {
setVariants(prev => prev.map(variant => {
const optMatch = idx === 0 ? variant.option1 : idx === 1 ? variant.option2 : variant.option3
if (optMatch === valName) {
return { ...variant, imageUrl: url }
}
return variant
}))
}
})
}}
className="p-1 text-cyan-400 hover:text-white"
title="Change swatch image"
>
<ImageIcon className="w-3.5 h-3.5" />
</button>
<button
type="button"
onClick={() => {
const newOpts = [...options]
const { imageUrl: _, ...rest } = newOpts[idx].values[vIdx]
newOpts[idx].values[vIdx] = rest
setOptions(newOpts)
}}
className="p-1 text-rose-400 hover:text-white"
title="Remove swatch image"
>
<Trash2 className="w-3.5 h-3.5" />
</button>
</div>
</div>
) : (
<button
type="button"
onClick={() => {
openMediaModal((url) => {
const newOpts = [...options]
newOpts[idx].values[vIdx] = { ...newOpts[idx].values[vIdx], imageUrl: url }
setOptions(newOpts)
const valName = val[activeLang] || val['th'] || val['en'] || ''
if (valName) {
setVariants(prev => prev.map(variant => {
const optMatch = idx === 0 ? variant.option1 : idx === 1 ? variant.option2 : variant.option3
if (optMatch === valName) {
return { ...variant, imageUrl: url }
}
return variant
}))
}
})
}}
className="shrink-0 flex items-center gap-1.5 px-3 py-2 rounded-xl border border-dashed border-white/20 hover:border-cyan-glow/50 text-slate-400 hover:text-cyan-glow bg-white/[0.02] text-xs font-medium transition-all"
title="Attach 1:1 image for this option"
>
<ImagePlus className="w-4 h-4 text-cyan-400/80" />
<span className="text-[11px] whitespace-nowrap">รูป 1:1</span>
</button>
)}
<button
onClick={() => {
const newOpts = [...options]
@@ -250,6 +250,19 @@ export default function AddToCartSection({ product, showBrandMeta = true, showMo
const description = resolveLocalized(product.description, lang)
const getOptionImage = (optIndex: number, val: string) => {
// 1. Check if option itself has an imageUrl on value
const option = product.options?.[optIndex];
if (option && Array.isArray(option.values)) {
const matchVal = option.values.find((v: any) => {
const base = typeof v === 'string' ? v : (v?.th || v?.en || Object.values(v)[0]);
return base === val;
});
if (matchVal && typeof matchVal === 'object' && (matchVal as any).imageUrl) {
return (matchVal as any).imageUrl as string;
}
}
// 2. Fallback to matching variant imageUrl
if (!product.variants) return null;
const match = product.variants.find(v => {
const vVal = optIndex === 0 ? v.option1 : optIndex === 1 ? v.option2 : v.option3;