This commit is contained in:
Your Name committed 2026-10-08 07:12:58 +07:00
1 parent b7f337fb79
commit bf9599008e
29 files changed
+3828 -1541

No files matched your search

@@ -13,7 +13,11 @@ import {
CheckCircle2,
Tag,
Sliders,
Maximize2
Maximize2,
LayoutGrid,
PanelLeft,
PanelsTopBottom,
Palette,
} from 'lucide-react'
import { API_BASE, type CategoryFeaturedSeries, type CategoryUspBenefit, type CategoryFilterConfig, type AttributeDefinition } from '@homaroma/types'
import CategoryFilterManagerModal from './CategoryFilterManagerModal'
@@ -228,6 +232,52 @@ export default function CategoryHybridCmsEditor({
{featuredSeries.enabled && (
<div className="space-y-5 pt-2">
{/* Full Wide Banner Toggle */}
<div
onClick={() => onChangeFeaturedSeries({ ...featuredSeries, fullWidth: !featuredSeries.fullWidth })}
className={`flex items-center justify-between p-4 rounded-xl border transition-all cursor-pointer select-none ${
featuredSeries.fullWidth
? 'bg-cyan-glow/10 border-cyan-glow/40 shadow-sm'
: 'bg-white/[0.02] border-white/10 hover:border-white/20'
}`}
>
<div className="space-y-0.5">
<div className="text-sm font-bold text-white flex items-center gap-2">
<Maximize2 className="w-4 h-4 text-cyan-glow" />
<span>Full Wide Banner (ขยายกว้างเต็มจอ)</span>
<span className={`text-[10px] font-bold px-2 py-0.5 rounded-full ${
featuredSeries.fullWidth
? 'text-cyan-glow bg-cyan-glow/10 border border-cyan-glow/20'
: 'text-slate-400 bg-white/5 border border-white/10'
}`}>
{featuredSeries.fullWidth ? 'Full Wide (1920px+)' : 'Boxed (1280px)'}
</span>
</div>
<p className="text-xs text-slate-400">
ขยายขนาดแบนเนอร์ Featured Series ให้กว้างเต็มจอหน้าเว็บของผู้ใช้ (Fluid Full Width แทนที่ Boxed max-w-7xl)
</p>
</div>
<button
type="button"
role="switch"
aria-checked={featuredSeries.fullWidth ?? false}
onClick={(e) => {
e.stopPropagation();
onChangeFeaturedSeries({ ...featuredSeries, fullWidth: !featuredSeries.fullWidth });
}}
className={`relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none ${
featuredSeries.fullWidth ? 'bg-cyan-glow' : 'bg-night-950 border border-white/20'
}`}
>
<span
className={`pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out ${
featuredSeries.fullWidth ? 'translate-x-5' : 'translate-x-0'
}`}
/>
</button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-5">
{/* Badge Kicker */}
<div className="space-y-1.5">
@@ -502,6 +552,289 @@ export default function CategoryHybridCmsEditor({
{/* ── TAB 3: Filter System Configuration ──────────────────────────── */}
{activeTab === 'filters' && (
<div className="space-y-6 animate-fade-in">
{/* Container Width (Standard, Wide, Full) */}
<div className="p-4 rounded-xl bg-night-950 border border-white/10 space-y-3">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
<div>
<h5 className="text-xs font-bold text-white flex items-center gap-2">
<Maximize2 className="w-4 h-4 text-cyan-glow" />
<span>Container Width (ความกว้างพื้นที่แคตตาล็อกสินค้า)</span>
</h5>
<p className="text-[11px] text-slate-400 mt-0.5">
เลือกความกว้างของบล็อกสินค้าและตัวกรอง ให้พอดีกับดีไซน์หน้าร้าน
</p>
</div>
<span className="text-[10px] font-bold px-2.5 py-1 rounded-full text-cyan-glow bg-cyan-glow/10 border border-cyan-glow/20 shrink-0">
{filterConfig.containerWidth === 'full' || filterConfig.fullWidth
? 'Full Width (1920px+ Fluid)'
: filterConfig.containerWidth === 'wide'
? 'Wide (1600px)'
: 'Standard (1280px Boxed)'}
</span>
</div>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-2.5">
{[
{ val: 'standard', label: 'Standard Boxed', desc: 'กรอบ 1280px กึ่งกลาง' },
{ val: 'wide', label: 'Wide (1600px)', desc: 'ขยายกว้างพิเศษ 1600px' },
{ val: 'full', label: 'Full Width Fluid', desc: 'เต็มพื้นที่ 100% (1920px+)' },
].map((item) => {
const currentWidth = filterConfig.containerWidth || (filterConfig.fullWidth ? 'full' : 'standard')
const isSelected = currentWidth === item.val
return (
<button
key={item.val}
type="button"
onClick={() =>
onChangeFilterConfig({
...filterConfig,
containerWidth: item.val as any,
fullWidth: item.val === 'full',
})
}
className={`p-3 rounded-xl border text-left transition-all cursor-pointer ${
isSelected
? 'bg-cyan-glow/10 border-cyan-glow text-white shadow-sm'
: 'bg-white/[0.02] border-white/10 hover:border-white/20 text-slate-400'
}`}
>
<div className="text-xs font-bold">{item.label}</div>
<div className="text-[10px] text-slate-500 mt-0.5">{item.desc}</div>
</button>
)
})}
</div>
</div>
{/* Card Presentation & Styling (Parity with Web CMS Product Carousel) */}
<div className="p-4 rounded-xl bg-night-950 border border-white/10 space-y-5">
<div>
<h5 className="text-xs font-bold text-white flex items-center gap-2">
<Palette className="w-4 h-4 text-cyan-glow" />
<span>Product Card Presentation (รูปแบบและการแสดงผลการ์ดสินค้า)</span>
</h5>
<p className="text-[11px] text-slate-400 mt-0.5">
ปรับแต่งสไตล์การ์ด ขนาด และองค์ประกอบปุ่มการสั่งซื้อ (Parity กับ CMS Product Carousel)
</p>
</div>
{/* 1. Card Style Selector */}
<div className="space-y-2">
<label className="text-[10px] uppercase font-bold tracking-widest text-slate-400">
Card Layout Style (สไตล์การ์ด)
</label>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
{[
{
val: 'standard',
label: 'Standard Retail',
desc: 'กรอบ 1:1 Boxed พร้อมปุ่มเพิ่มลงตะกร้า',
badge: 'E-Commerce'
},
{
val: 'minimal',
label: 'Fashion Minimal',
desc: 'กรอบแนวตั้ง 3:4 Portrait โหมด Lookbook/Zara/Pomelo',
badge: 'Fashion / Minimal'
},
{
val: 'swatch',
label: 'Editorial Swatch',
desc: 'การ์ดพร้อมจุด Swatch สีของ Variant สินค้า',
badge: 'Interactive'
},
].map((item) => {
const isSelected = (filterConfig.cardStyle || 'standard') === item.val
return (
<button
key={item.val}
type="button"
onClick={() => onChangeFilterConfig({ ...filterConfig, cardStyle: item.val as any })}
className={`p-3.5 rounded-xl border text-left transition-all cursor-pointer ${
isSelected
? 'bg-cyan-glow/10 border-cyan-glow text-white shadow-sm'
: 'bg-white/[0.02] border-white/10 hover:border-white/20 text-slate-400'
}`}
>
<div className="flex items-center justify-between">
<span className="text-xs font-bold">{item.label}</span>
<span className="text-[9px] font-bold px-1.5 py-0.5 rounded-md bg-white/5 border border-white/10 text-slate-400">
{item.badge}
</span>
</div>
<p className="text-[10px] text-slate-400 mt-1 leading-relaxed">{item.desc}</p>
</button>
)
})}
</div>
</div>
{/* 2. Card Size / Density */}
<div className="space-y-2">
<label className="text-[10px] uppercase font-bold tracking-widest text-slate-400">
Card Size & Density (ขนาดและความหนาแน่นการ์ด)
</label>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
{[
{ val: 'compact', label: 'Compact (กะทัดรัด)', desc: '~5-6 ใบต่อหน้าจอ เหมาะกับแคตตาล็อกสินค้าจำนวนมาก' },
{ val: 'medium', label: 'Medium (มาตรฐาน)', desc: '~4 ใบต่อหน้าจอ สัดส่วนสวยงามสมดุล (แนะนำ)' },
{ val: 'large', label: 'Large (ใหญ่พิเศษ)', desc: '~2-3 ใบต่อหน้าจอ การ์ดกว้างเต็มตา โชว์ภาพคมชัด' },
].map((item) => {
const isSelected = (filterConfig.cardSize || 'medium') === item.val
return (
<button
key={item.val}
type="button"
onClick={() => onChangeFilterConfig({ ...filterConfig, cardSize: item.val as any })}
className={`p-3 rounded-xl border text-left transition-all cursor-pointer ${
isSelected
? 'bg-cyan-glow/10 border-cyan-glow text-white shadow-sm'
: 'bg-white/[0.02] border-white/10 hover:border-white/20 text-slate-400'
}`}
>
<div className="text-xs font-bold">{item.label}</div>
<p className="text-[10px] text-slate-400 mt-0.5">{item.desc}</p>
</button>
)
})}
</div>
</div>
{/* 3. Card Elements Toggles */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 pt-1 border-t border-white/5">
<label className="flex items-center gap-3 p-3 border border-white/5 bg-white/[0.02] rounded-xl cursor-pointer select-none">
<input
type="checkbox"
checked={filterConfig.showPrice ?? true}
onChange={(e) => onChangeFilterConfig({ ...filterConfig, showPrice: e.target.checked })}
className="accent-cyan-glow"
/>
<div>
<span className="text-xs font-bold text-slate-200 block">Show Price (แสดงราคา)</span>
<span className="text-[10px] text-slate-400">แสดงป้ายราคาสินค้าบนการ์ด</span>
</div>
</label>
<label className="flex items-center gap-3 p-3 border border-white/5 bg-white/[0.02] rounded-xl cursor-pointer select-none">
<input
type="checkbox"
checked={filterConfig.showAddToCart ?? true}
onChange={(e) => onChangeFilterConfig({ ...filterConfig, showAddToCart: e.target.checked })}
className="accent-cyan-glow"
/>
<div>
<span className="text-xs font-bold text-slate-200 block">Show Add to Cart (ปุ่มสั่งซื้อ)</span>
<span className="text-[10px] text-slate-400">แสดงปุ่มหยิบลงตะกร้าบนการ์ดสินค้า</span>
</div>
</label>
</div>
</div>
{/* Card Columns & Grid Layout */}
<div className="p-4 rounded-xl bg-night-950 border border-white/10 space-y-4">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
<div>
<h5 className="text-xs font-bold text-white flex items-center gap-2">
<LayoutGrid className="w-4 h-4 text-cyan-glow" />
<span>Product Card Columns (จำนวนคอลัมน์การ์ดสินค้า)</span>
</h5>
<p className="text-[11px] text-slate-400 mt-0.5">
กำหนดขนาดและความกว้างของการ์ดสินค้าบน Desktop / Ultra-Wide
</p>
</div>
<span className="text-[10px] font-bold px-2.5 py-1 rounded-full text-cyan-glow bg-cyan-glow/10 border border-cyan-glow/20 shrink-0">
{filterConfig.gridColumns ? `${filterConfig.gridColumns} Columns` : 'Adaptive Auto'}
</span>
</div>
<div className="grid grid-cols-2 sm:grid-cols-5 gap-2.5">
{[
{ val: undefined, label: 'Auto (ตามธีม)', desc: 'ปรับอัตโนมัติ' },
{ val: 2, label: '2 คอลัมน์', desc: 'การ์ดใหญ่พิเศษ Wide' },
{ val: 3, label: '3 คอลัมน์', desc: 'ขนาดมาตรฐาน' },
{ val: 4, label: '4 คอลัมน์', desc: 'หน้าจอกว้าง Wide' },
{ val: 5, label: '5 คอลัมน์', desc: 'ความหนาแน่นสูง Dense' },
].map((item) => {
const isSelected = filterConfig.gridColumns === item.val
return (
<button
key={item.label}
type="button"
onClick={() => onChangeFilterConfig({ ...filterConfig, gridColumns: item.val as any })}
className={`p-3 rounded-xl border text-left transition-all cursor-pointer ${
isSelected
? 'bg-cyan-glow/10 border-cyan-glow text-white shadow-sm'
: 'bg-white/[0.02] border-white/10 hover:border-white/20 text-slate-400'
}`}
>
<div className="text-xs font-bold">{item.label}</div>
<div className="text-[10px] text-slate-500 mt-0.5">{item.desc}</div>
</button>
)
})}
</div>
</div>
{/* Filters UI Layout Position */}
<div className="p-4 rounded-xl bg-night-950 border border-white/10 space-y-4">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
<div>
<h5 className="text-xs font-bold text-white flex items-center gap-2">
<Sliders className="w-4 h-4 text-cyan-glow" />
<span>Filters UI Position (ตำแหน่งและการจัดวางตัวกรอง)</span>
</h5>
<p className="text-[11px] text-slate-400 mt-0.5">
เลือกตำแหน่งแถบตัวกรอง เพื่อขยายตารางสินค้าให้กว้างเต็ม 100% หรือจัดเป็น Sidebar ด้านข้าง
</p>
</div>
<span className="text-[10px] font-bold px-2.5 py-1 rounded-full text-cyan-glow bg-cyan-glow/10 border border-cyan-glow/20 shrink-0">
{filterConfig.filterPosition === 'top_bar' ? 'Top Bar (Wide Ribbon)' : 'Sidebar (ซ้าย)'}
</span>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
<button
type="button"
onClick={() => onChangeFilterConfig({ ...filterConfig, filterPosition: 'sidebar' })}
className={`p-3.5 rounded-xl border text-left transition-all cursor-pointer flex items-start gap-3 ${
filterConfig.filterPosition !== 'top_bar'
? 'bg-cyan-glow/10 border-cyan-glow text-white shadow-sm'
: 'bg-white/[0.02] border-white/10 hover:border-white/20 text-slate-400'
}`}
>
<div className="p-2 rounded-lg bg-white/5 border border-white/10 shrink-0 text-cyan-glow">
<PanelLeft className="w-4 h-4" />
</div>
<div>
<div className="text-xs font-bold">Sidebar (ด้านซ้ายแนวตั้ง)</div>
<div className="text-[10px] text-slate-400 mt-1 leading-relaxed">
แถบตัวกรองอยู่ทางซ้าย เหมาะสำหรับหมวดหมู่ที่มีตัวกรองหลายหัวข้อย่อย
</div>
</div>
</button>
<button
type="button"
onClick={() => onChangeFilterConfig({ ...filterConfig, filterPosition: 'top_bar' })}
className={`p-3.5 rounded-xl border text-left transition-all cursor-pointer flex items-start gap-3 ${
filterConfig.filterPosition === 'top_bar'
? 'bg-cyan-glow/10 border-cyan-glow text-white shadow-sm'
: 'bg-white/[0.02] border-white/10 hover:border-white/20 text-slate-400'
}`}
>
<div className="p-2 rounded-lg bg-white/5 border border-white/10 shrink-0 text-cyan-glow">
<PanelsTopBottom className="w-4 h-4" />
</div>
<div>
<div className="text-xs font-bold">Top Bar (แนวนอนด้านบน Wide Ribbon)</div>
<div className="text-[10px] text-slate-400 mt-1 leading-relaxed">
ตัวกรองวางแนวนอนเต็มความกว้างด้านบน ปลดล็อกให้ตารางสินค้าด้านล่างได้พื้นที่กว้าง 100% เต็มจอ
</div>
</div>
</button>
</div>
</div>
{/* Section 1: Standard Global Facet Presets */}
<div>
<div className="mb-3">
File diff suppressed because it is too large. Load diff
+63 -210
View File
@@ -2,7 +2,6 @@ import { useState, useEffect } from 'react'
import { useParams, useNavigate, Link } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import { API_BASE, type Category, type CategoryMedia, type CategoryMediaRole, type CategoryFeaturedSeries, type CategoryUspBenefit, type CategoryFilterConfig } from '@homaroma/types'
import CategoryHybridCmsEditor from '../components/category/CategoryHybridCmsEditor'
import {
ArrowLeft,
Save,
@@ -23,9 +22,6 @@ import {
Download,
Sparkles,
LayoutTemplate,
Columns,
Grid3X3,
EyeOff,
Upload,
} from 'lucide-react'
import MediaLibraryModal from '../components/MediaLibraryModal'
@@ -118,8 +114,6 @@ export default function CategoryEditPage() {
showStock: true,
})
const [mediaTarget, setMediaTarget] = useState<'role' | 'featured_image' | 'featured_action'>('role')
// Media Library Modal
const [isMediaModalOpen, setIsMediaModalOpen] = useState(false)
const [activeMediaRole, setActiveMediaRole] = useState<CategoryMediaRole>('thumbnail')
@@ -470,11 +464,11 @@ export default function CategoryEditPage() {
{!isNew && (
<Link
to={`/categories/${id}/cms`}
to={`/categories/${id}/cms?tab=presentation`}
className="px-4 py-2.5 rounded-xl bg-cyan-glow/10 hover:bg-cyan-glow/20 text-cyan-glow border border-cyan-glow/30 text-sm font-semibold transition-all cursor-pointer flex items-center gap-2"
>
<LayoutTemplate className="w-4 h-4" />
<span>CMS Blocks</span>
<span>CMS & Presentation</span>
</Link>
)}
@@ -803,212 +797,78 @@ export default function CategoryEditPage() {
)}
</div>
{/* Card: Category Layout Archetype */}
<div className="admin-card p-6 md:p-8 space-y-6">
<div className="flex items-center justify-between border-b border-white/5 pb-4">
<div className="flex items-center gap-3">
<div className="p-2 rounded-xl bg-amber-500/10 text-amber-400">
<LayoutTemplate className="w-5 h-5" />
</div>
<div>
<h2 className="text-lg font-bold text-white">Category Layout & Presentation</h2>
<p className="text-xs text-slate-400">Choose how this category displays on Storefront & Website</p>
</div>
</div>
<span className={`px-2.5 py-1 rounded-lg text-xs font-mono font-semibold border ${
layoutType === 'none'
? 'bg-rose-500/10 text-rose-300 border-rose-500/30'
: 'bg-white/5 text-slate-300 border-white/10'
}`}>
{layoutType === 'none' ? 'none (ปิดส่วนหัว)' : layoutType}
</span>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-4">
{/* Option 1: Split Showcase */}
<div
onClick={() => setLayoutType('split_showcase')}
className={`relative p-5 rounded-2xl border-2 transition-all cursor-pointer flex flex-col justify-between group ${
layoutType === 'split_showcase'
? 'bg-cyan-glow/10 border-cyan-glow shadow-lg shadow-cyan-glow/10'
: 'bg-white/[0.02] border-white/10 hover:border-white/20 hover:bg-white/[0.04]'
}`}
>
<div className="space-y-3">
{/* Visual Layout Mockup */}
<div className="w-full h-24 rounded-xl bg-night-950/80 border border-white/10 p-2.5 flex flex-col justify-between overflow-hidden">
<div className="flex items-center gap-2">
<div className="flex-1 space-y-1">
<div className="h-2 w-3/4 bg-white/30 rounded" />
<div className="h-1.5 w-1/2 bg-white/15 rounded" />
</div>
<div className="w-8 h-8 rounded-lg bg-cyan-glow/30 border border-cyan-glow/50 shrink-0" />
</div>
<div className="grid grid-cols-3 gap-1 pt-1">
<div className="h-6 rounded bg-white/10" />
<div className="h-6 rounded bg-white/10" />
<div className="h-6 rounded bg-white/10" />
</div>
{/* Card: Category Presentation & CMS Blocks Overview */}
{!isNew ? (
<div className="admin-card p-6 md:p-8 space-y-6 border border-cyan-glow/20 bg-gradient-to-br from-night-900/90 via-night-900/60 to-night-950/80">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 border-b border-white/5 pb-4">
<div className="flex items-center gap-3">
<div className="p-2.5 rounded-xl bg-cyan-glow/10 text-cyan-glow border border-cyan-glow/20">
<LayoutTemplate className="w-5 h-5" />
</div>
<div>
<div className="flex items-center justify-between">
<h4 className="text-sm font-bold text-white flex items-center gap-1.5">
<Columns className="w-4 h-4 text-cyan-glow" />
Split Showcase
</h4>
{layoutType === 'split_showcase' && (
<CheckCircle2 className="w-4 h-4 text-cyan-glow" />
)}
</div>
<p className="text-[11px] text-cyan-400 font-medium mt-0.5">ค่าเริ่มต้น (Balanced)</p>
<p className="text-xs text-slate-400 mt-2 leading-relaxed">
หัวข้อแบ่ง 2 ฝั่ง (ข้อความซ้าย + รูปเด่นขวา) พร้อมการจัดแสดงสินค้า 3-4 คอลัมน์มาตรฐาน
<h2 className="text-base font-bold text-white flex items-center gap-2">
Category Presentation & CMS Blocks
<span className="text-[10px] uppercase font-mono font-bold tracking-wider px-2 py-0.5 rounded-full bg-cyan-glow/20 text-cyan-glow border border-cyan-glow/30">
CMS Page
</span>
</h2>
<p className="text-xs text-slate-400">
ปรับแต่ง Layout Archetype, แบนเนอร์ Featured Series, USP Benefits และ Dynamic Blocks ในหน้า CMS แบบเต็มจอ (Wide Full)
</p>
</div>
</div>
<Link
to={`/categories/${id}/cms?tab=presentation`}
className="inline-flex items-center gap-2 px-4 py-2.5 rounded-xl bg-cyan-glow hover:bg-cyan-400 text-night-950 text-xs font-bold transition-all shadow-md shadow-cyan-glow/20 cursor-pointer self-start sm:self-auto shrink-0"
>
<LayoutTemplate className="w-4 h-4" />
<span>จัดการ Presentation & CMS Blocks</span>
<ExternalLink className="w-3.5 h-3.5" />
</Link>
</div>
{/* Option 2: Editorial Hero */}
<div
onClick={() => setLayoutType('editorial_hero')}
className={`relative p-5 rounded-2xl border-2 transition-all cursor-pointer flex flex-col justify-between group ${
layoutType === 'editorial_hero'
? 'bg-amber-500/10 border-amber-400 shadow-lg shadow-amber-500/10'
: 'bg-white/[0.02] border-white/10 hover:border-white/20 hover:bg-white/[0.04]'
}`}
>
<div className="space-y-3">
{/* Visual Layout Mockup */}
<div className="w-full h-24 rounded-xl bg-night-950/80 border border-white/10 p-2 flex flex-col justify-between overflow-hidden">
<div className="w-full h-10 rounded-lg bg-amber-500/20 border border-amber-500/40 flex flex-col items-center justify-center gap-1">
<div className="h-2 w-16 bg-amber-200/80 rounded" />
<div className="h-1 w-10 bg-amber-200/40 rounded" />
</div>
<div className="grid grid-cols-2 gap-1.5 pt-1">
<div className="h-8 rounded-lg bg-white/10 border border-white/5" />
<div className="h-8 rounded-lg bg-white/10 border border-white/5" />
</div>
</div>
<div>
<div className="flex items-center justify-between">
<h4 className="text-sm font-bold text-white flex items-center gap-1.5">
<Sparkles className="w-4 h-4 text-amber-400" />
Editorial Hero
</h4>
{layoutType === 'editorial_hero' && (
<CheckCircle2 className="w-4 h-4 text-amber-400" />
)}
</div>
<p className="text-[11px] text-amber-400 font-medium mt-0.5">Cinematic & Luxury</p>
<p className="text-xs text-slate-400 mt-2 leading-relaxed">
Hero Banner เต็มจอสไตล์นิตยสารหรูหรา พร้อมการ์ดสินค้าขนาดใหญ่ 2-3 คอลัมน์
</p>
</div>
{/* Status Chips */}
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
<div className="p-3.5 rounded-xl bg-white/[0.02] border border-white/5">
<p className="text-[10px] uppercase font-bold tracking-widest text-slate-500">Layout</p>
<p className="mt-1 text-xs font-bold text-slate-200 capitalize">
{categoryMeta?.layoutType || layoutType || 'split_showcase'}
</p>
</div>
</div>
{/* Option 3: Dense Catalog */}
<div
onClick={() => setLayoutType('dense_catalog')}
className={`relative p-5 rounded-2xl border-2 transition-all cursor-pointer flex flex-col justify-between group ${
layoutType === 'dense_catalog'
? 'bg-purple-500/10 border-purple-400 shadow-lg shadow-purple-500/10'
: 'bg-white/[0.02] border-white/10 hover:border-white/20 hover:bg-white/[0.04]'
}`}
>
<div className="space-y-3">
{/* Visual Layout Mockup */}
<div className="w-full h-24 rounded-xl bg-night-950/80 border border-white/10 p-2 flex flex-col justify-between overflow-hidden">
<div className="flex items-center justify-between border-b border-white/10 pb-1">
<div className="h-2 w-12 bg-white/40 rounded" />
<div className="h-2 w-6 bg-purple-400/60 rounded" />
</div>
<div className="grid grid-cols-4 gap-1 pt-1">
<div className="h-6 rounded bg-white/10" />
<div className="h-6 rounded bg-white/10" />
<div className="h-6 rounded bg-white/10" />
<div className="h-6 rounded bg-white/10" />
</div>
</div>
<div>
<div className="flex items-center justify-between">
<h4 className="text-sm font-bold text-white flex items-center gap-1.5">
<Grid3X3 className="w-4 h-4 text-purple-400" />
Dense Catalog
</h4>
{layoutType === 'dense_catalog' && (
<CheckCircle2 className="w-4 h-4 text-purple-400" />
)}
</div>
<p className="text-[11px] text-purple-400 font-medium mt-0.5">High Density Shopper</p>
<p className="text-xs text-slate-400 mt-2 leading-relaxed">
Header ย่อกะทัดรัด พร้อมตารางสินค้าความหนาแน่นสูง 4-5 คอลัมน์ เหมาะสำหรับสินค้าจำนวนมาก
</p>
</div>
<div className="p-3.5 rounded-xl bg-white/[0.02] border border-white/5">
<p className="text-[10px] uppercase font-bold tracking-widest text-slate-500">Featured Series</p>
<p className={`mt-1 text-xs font-bold ${categoryMeta?.featuredSeries?.enabled ? 'text-emerald-400' : 'text-slate-500'}`}>
{categoryMeta?.featuredSeries?.enabled ? 'เปิดใช้งาน' : 'ปิดอยู่'}
</p>
</div>
</div>
{/* Option 4: None / ปิดส่วนหัว */}
<div
onClick={() => setLayoutType('none')}
className={`relative p-5 rounded-2xl border-2 transition-all cursor-pointer flex flex-col justify-between group ${
layoutType === 'none'
? 'bg-rose-500/10 border-rose-400 shadow-lg shadow-rose-500/10'
: 'bg-white/[0.02] border-white/10 hover:border-white/20 hover:bg-white/[0.04]'
}`}
>
<div className="space-y-3">
{/* Visual Layout Mockup */}
<div className="w-full h-24 rounded-xl bg-night-950/80 border border-white/10 p-2.5 flex flex-col justify-between overflow-hidden">
<div className="w-full py-1 px-2 border-b border-dashed border-rose-500/30 flex items-center justify-between">
<div className="h-1.5 w-12 bg-white/20 rounded" />
<span className="text-[9px] font-mono uppercase text-rose-400 font-bold">No Header</span>
</div>
<div className="grid grid-cols-3 gap-1 pt-1 flex-1 items-center">
<div className="h-8 rounded bg-white/10 border border-white/5" />
<div className="h-8 rounded bg-white/10 border border-white/5" />
<div className="h-8 rounded bg-white/10 border border-white/5" />
</div>
</div>
<div>
<div className="flex items-center justify-between">
<h4 className="text-sm font-bold text-white flex items-center gap-1.5">
<EyeOff className="w-4 h-4 text-rose-400" />
ปิด (No Header)
</h4>
{layoutType === 'none' && (
<CheckCircle2 className="w-4 h-4 text-rose-400" />
)}
</div>
<p className="text-[11px] text-rose-400 font-medium mt-0.5">Clean & Streamlined</p>
<p className="text-xs text-slate-400 mt-2 leading-relaxed">
ไม่แสดง Archetype Header ด้านบน เพื่อเริ่มแสดง Featured Series หรือสินค้าทันที ลดความซ้ำซ้อน
</p>
</div>
<div className="p-3.5 rounded-xl bg-white/[0.02] border border-white/5">
<p className="text-[10px] uppercase font-bold tracking-widest text-slate-500">USP Benefits</p>
<p className="mt-1 text-xs font-bold text-slate-200">
{(categoryMeta?.uspBenefits as any[])?.length || (uspBenefits as any[])?.length || 0} รายการ
</p>
</div>
<div className="p-3.5 rounded-xl bg-white/[0.02] border border-white/5">
<p className="text-[10px] uppercase font-bold tracking-widest text-slate-500">CMS Builder</p>
<Link
to={`/categories/${id}/cms?tab=blocks`}
className="mt-1 text-xs font-bold text-cyan-glow hover:underline flex items-center gap-1"
>
<span>Page Blocks</span>
<ExternalLink className="w-3 h-3" />
</Link>
</div>
</div>
</div>
</div>
{/* Hybrid CMS Presentation Editor */}
<CategoryHybridCmsEditor
featuredSeries={featuredSeries}
onChangeFeaturedSeries={setFeaturedSeries}
uspBenefits={uspBenefits}
onChangeUspBenefits={setUspBenefits}
filterConfig={filterConfig}
onChangeFilterConfig={setFilterConfig}
onOpenMediaModal={(target) => {
setMediaTarget(target)
setIsMediaModalOpen(true)
}}
categoryId={!isNew && id ? parseInt(id) : undefined}
categoryName={name.th || name.en || 'Category'}
/>
) : (
<div className="admin-card p-6 border border-dashed border-white/10 text-center space-y-2">
<LayoutTemplate className="w-8 h-8 text-slate-600 mx-auto" />
<p className="text-sm font-semibold text-slate-300">Category Presentation & CMS Blocks</p>
<p className="text-xs text-slate-500 max-w-md mx-auto">
บันทึกสร้างหมวดหมู่นี้ก่อน จากนั้นจะสามารถไปปรับแต่ง Presentation & Dynamic CMS Blocks ได้ที่หน้า CMS
</p>
</div>
)}
{/* Card 3: Associated Products & Quick Link */}
{!isNew && categoryMeta && (
@@ -1046,7 +906,6 @@ export default function CategoryEditPage() {
uploadingRole={uploadingRole}
onChange={setMedia}
onBrowse={(role) => {
setMediaTarget('role')
setActiveMediaRole(role)
setIsMediaModalOpen(true)
}}
@@ -1085,13 +944,7 @@ export default function CategoryEditPage() {
isOpen={isMediaModalOpen}
onClose={() => setIsMediaModalOpen(false)}
onSelect={(url) => {
if (mediaTarget === 'featured_image') {
setFeaturedSeries(prev => ({ ...prev, imageUrl: url }))
} else if (mediaTarget === 'featured_action') {
setFeaturedSeries(prev => ({ ...prev, actionImageUrl: url }))
} else {
setMediaUrl(activeMediaRole, url)
}
setMediaUrl(activeMediaRole, url)
setIsMediaModalOpen(false)
}}
/>