x
This commit is contained in:
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
@@ -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)
|
||||
}}
|
||||
/>
|
||||
|
||||
Reference in new issue
Block a user