x
This commit is contained in:
1 parent
3631f59bba
commit
c3c2d97cc1
2 files changed
+94
-3
No files matched your search
@@ -124,7 +124,7 @@ export default function ProductEditPage() {
|
|||||||
const [name, setName] = useState<Record<string, string>>({})
|
const [name, setName] = useState<Record<string, string>>({})
|
||||||
const [description, setDescription] = useState<Record<string, string>>({})
|
const [description, setDescription] = useState<Record<string, string>>({})
|
||||||
const [details, setDetails] = 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 [variants, setVariants] = useState<Partial<Variant>[]>([])
|
||||||
const [images, setImages] = useState<EditableProductImage[]>([])
|
const [images, setImages] = useState<EditableProductImage[]>([])
|
||||||
const [titleGraphicUrl, setTitleGraphicUrl] = useState<string>('')
|
const [titleGraphicUrl, setTitleGraphicUrl] = useState<string>('')
|
||||||
@@ -254,7 +254,7 @@ export default function ProductEditPage() {
|
|||||||
setVariants(updated)
|
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 => {
|
setVariants(prevVariants => {
|
||||||
const validOptions = newOptions.filter(o => o.name && (o.name['th'] || o.name['en']) && o.values.length > 0)
|
const validOptions = newOptions.filter(o => o.name && (o.name['th'] || o.name['en']) && o.values.length > 0)
|
||||||
if (validOptions.length === 0) {
|
if (validOptions.length === 0) {
|
||||||
@@ -277,7 +277,15 @@ export default function ProductEditPage() {
|
|||||||
const option3 = combo[2] || ''
|
const option3 = combo[2] || ''
|
||||||
|
|
||||||
const existing = prevVariants.find(v => v.option1 === option1 && v.option2 === option2 && v.option3 === option3)
|
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"
|
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}`}
|
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
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const newOpts = [...options]
|
const newOpts = [...options]
|
||||||
|
|||||||
@@ -250,6 +250,19 @@ export default function AddToCartSection({ product, showBrandMeta = true, showMo
|
|||||||
const description = resolveLocalized(product.description, lang)
|
const description = resolveLocalized(product.description, lang)
|
||||||
|
|
||||||
const getOptionImage = (optIndex: number, val: string) => {
|
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;
|
if (!product.variants) return null;
|
||||||
const match = product.variants.find(v => {
|
const match = product.variants.find(v => {
|
||||||
const vVal = optIndex === 0 ? v.option1 : optIndex === 1 ? v.option2 : v.option3;
|
const vVal = optIndex === 0 ? v.option1 : optIndex === 1 ? v.option2 : v.option3;
|
||||||
|
|||||||
Reference in new issue
Block a user