This commit is contained in:
Your Name committed 2026-10-06 06:36:26 +07:00
1 parent 7fd3c40ae2
commit 230716933e
2 files changed
+620 -4

No files matched your search

@@ -0,0 +1,496 @@
import { useState } from 'react'
import {
Sparkles,
Copy,
Check,
X,
BookOpen,
Flower2,
Shirt,
Sparkle,
Compass,
Code2,
Layers,
Globe2,
Upload,
} from 'lucide-react'
interface MenuJsonGuideModalProps {
isOpen: boolean
onClose: () => void
onOpenPasteJson?: () => void
onOpenImportFile?: () => void
}
const GUIDE_OVERVIEW_MARKDOWN = `# คู่มือการใช้ AI/LLM สร้าง JSON สำหรับ Main Navigation & Mega Menu
ระบบเมนูหลัก (Main Navigation) รองรับการนำเข้าโครงสร้างผ่าน **JSON Import** โดยสามารถส่งข้อมูลเข้าสู่ระบบได้ทั้งแบบไฟล์ \`.json\` หรือนำ JSON ไปวางในระบบได้ทันที
---
## 🛠️ ข้อกำหนดทางเทคนิคสากล (Mega Menu Schema Rules)
โครงสร้างเมนูหลักของ DBK Platform / HomAroma ประกอบด้วยลำดับชั้นแบบ 3 ระดับ (3-Tier Hierarchy) ดังนี้:
### 1. Root Items (เมนูแถวบนสุด - Level 1)
- **label**: ออบเจกต์ข้อความรองรับ 8 ภาษา \`{ th, en, ja, zh, vi, ko, lo, it }\`
- **url**: ลิงก์ปลายทาง เช่น \`/category/reed-diffuser\` หรือ \`/collections/all\`
- **rootImageUrl**: URL รูปภาพหลักฝั่งซ้ายสุดของเมกะเมนู (อัตราส่วน 1:1 จัตุรัส ขนาดแนะนำ 800x800)
- **columns**: อาร์เรย์ของคอลัมน์เมนูย่อย (Level 2)
- **featured**: การ์ดสินค้า/ไฮไลต์โปรโมตพิเศษที่แสดงฝั่งขวาสุดของเมกะเมนู
### 2. Columns (คอลัมน์เมนูย่อย - Level 2)
- **title**: ออบเจกต์หัวข้อคอลัมน์ 8 ภาษา \`{ th, en, ... }\`
- **iconUrl**: (ไม่บังคับ) รูปไอคอนประจำคอลัมน์ (1:1)
- **icon**: (ไม่บังคับ) ชื่อไอคอนจาก Lucide Registry เช่น \`Sparkles\`, \`Flame\`, \`Flower\`, \`Heart\`
- **links**: อาร์เรย์ของลิงก์ภายในคอลัมน์ (Level 3)
### 3. Links (ลิงก์รายการสินค้าหรือหมวด - Level 3)
- **label**: ออบเจกต์ชื่อลิงก์ 8 ภาษา \`{ th, en, ... }\`
- **url**: URL ปลายทาง เช่น \`/products/amber-bloom\` หรือ \`/category/candles\`
- **imageUrl**: (ไม่บังคับ) รูปภาพพรีวิวที่จะแสดงฝั่งขวาเมื่อผู้ใช้นำเมาส์ไปชี้ (Hover Image, 1:1)
- **links**: (ไม่บังคับ) ลิงก์ย่อยระดับที่ 4 สำหรับโครงสร้างแบบซ้อนลึก
### 4. Featured Card (การ์ดไฮไลต์แนะนำ)
- **enabled**: \`true\` หรือ \`false\`
- **sectionTitle**: เช่น \`{ th: "สินค้าแนะนำ", en: "Featured" }\`
- **badge**: เช่น \`{ th: "ขายดี", en: "Bestseller" }\`
- **title**: ชื่อสินค้าหรือคอลเลกชันไฮไลต์
- **subtitle**: คำโปรยสั้นๆ อธิบายความโดดเด่น
- **ctaLabel**: ข้อความปุ่ม เช่น \`{ th: "ดูรายละเอียด", en: "Explore" }\`
- **imageUrl**: URL รูปภาพความละเอียดสูงสำหรับแบนเนอร์การ์ด (อัตราส่วน 4:3 หรือ 1:1)
- **url**: ลิงก์ไปยังหน้าสินค้านั้น
---
## 🌐 รหัสภาษามาตรฐานทั้ง 8 ภาษา (Standard Language Codes)
1. \`th\`: ภาษาไทย (ภาษาหลัก)
2. \`en\`: English (ภาษาอังกฤษสากล)
3. \`ja\`: 日本語 (ภาษาญี่ปุ่น)
4. \`zh\`: 简体中文 (ภาษาจีนตัวย่อ)
5. \`vi\`: Tiếng Việt (ภาษาเวียดนาม)
6. \`ko\`: 한국어 (ภาษาเกาหลี)
7. \`lo\`: ພາສາລາວ (ภาษาลาว)
8. \`it\`: Italiano (ภาษาอิตาลี)`
const FRAGRANCE_PROMPT = `คุณคือ Senior E-commerce Information Architect และ Luxury Brand Director เชี่ยวชาญการออกแบบสถาปัตยกรรม Navigation & Mega Menu สำหรับแบรนด์ Luxury Lifestyle, Fine Fragrance, และ Aromatherapy (เทียบเคียงกับ Panpuri, Jo Malone London, Diptyque, Aesop, Byredo)
[ภารกิจของคุณ]:
สร้างโครงสร้างเมนูหลัก (Main Navigation Mega Menu) สำหรับนำเข้าสู่ระบบ E-commerce ในรูปแบบ **Valid JSON Array** ตาม Schema ของระบบอย่างเคร่งครัด
[กฎและข้อกำหนดทางเทคนิค]:
1. ผลลัพธ์ต้องเป็น Valid JSON Array ล้วนๆ เริ่มต้นด้วย [ และจบด้วย ] เท่านั้น (ห้ามมีคำเกริ่นนำหรือคำลงท้าย)
2. โครงสร้างเมนูต้องประกอบด้วยเมนูหลัก 4-6 กลุ่ม (Root Items) เช่น:
- "Home Ambience" (ก้านไม้หอม, เทียนหอม, สเปรย์ปรับอากาศ)
- "Personal Scents" (น้ำหอม Perfume Oil, Solid Perfume, Hair & Body Mist)
- "Bath & Body Sanctuary" (ครีมอาบน้ำ, โลชั่นบำรุงผิว, สครับขัดผิว, น้ำมันนวด)
- "Gifts & Sets" (เซ็ตของขวัญพรีเมียม, Corporate Gifting, Travel Sets)
- "The Scent Stories" (เรื่องราวคอลเลกชันกลิ่นหอม, Aromachology Journal)
3. แต่ละ Root Item ต้องมี:
- "label": รองรับครบ 8 ภาษา { "th", "en", "ja", "zh", "vi", "ko", "lo", "it" }
- "url": ลิงก์หมวดหมู่ เช่น "/category/home-ambience"
- "rootImageUrl": รูปภาพ 1:1 ความละเอียดสูงจาก Unsplash เกี่ยวกับเทียนหอม ก้านไม้หอม หรือขวดน้ำหอมหรู
- "featured": บัตรแนะนำสินค้าขายดี พร้อม sectionTitle, badge, title, subtitle, ctaLabel, imageUrl, url
- "columns": มี 2-4 คอลัมน์ย่อย แต่ละคอลัมน์มี title (8 ภาษา) และ links 3-5 รายการ (พร้อม hover imageUrl)
4. ภาษาต้องแปลให้สละสลวย ถ่ายทอดอารมณ์ความหรูหรา และตรงกับบริบทสากล
[ตัวอย่างรูปแบบ JSON ที่ต้องการ]:
[
{
"label": {
"th": "เครื่องหอมสำหรับบ้าน",
"en": "Home Fragrance",
"ja": "ホームフレグランス",
"zh": "家居香氛",
"vi": "Hương thơm gia đình",
"ko": "홈 프래그런스",
"lo": "ເຄື່ອງຫອມສຳລັບບ້ານ",
"it": "Fragranze per la Casa"
},
"url": "/category/home-fragrance",
"rootImageUrl": "https://images.unsplash.com/photo-1608571423902-eed4a5ad8108?w=800",
"featured": {
"enabled": true,
"sectionTitle": { "th": "คอลเลกชันไฮไลต์", "en": "Featured Collection" },
"badge": { "th": "ยอดนิยม", "en": "Best Seller" },
"title": { "th": "Siamese Water Reed Diffuser", "en": "Siamese Water Reed Diffuser" },
"subtitle": { "th": "ก้านไม้หอมปรับอากาศกลิ่นมะลิหลวงและกระดังงา มอบความสงบผ่อนคลาย", "en": "Signature Jasmine & Ylang Ylang diffuser for pure tranquility" },
"ctaLabel": { "th": "เลือกซื้อตอนนี้", "en": "Shop Now" },
"imageUrl": "https://images.unsplash.com/photo-1617897903246-719242758050?w=800",
"url": "/products/siamese-water-diffuser"
},
"columns": [
{
"title": {
"th": "ก้านไม้หอมปรับอากาศ",
"en": "Reed Diffusers",
"ja": "リードディフューザー",
"zh": "无火香薰",
"vi": "Tinh dầu cắm que",
"ko": "리드 디퓨저",
"lo": "ກ້ານໄມ້ຫອມ",
"it": "Diffusori a Midollino"
},
"icon": "Sparkles",
"links": [
{
"label": { "th": "ก้านไม้หอมคลาสสิก 100ml", "en": "Classic Diffusers 100ml" },
"url": "/category/diffusers-classic",
"imageUrl": "https://images.unsplash.com/photo-1608571423902-eed4a5ad8108?w=800"
},
{
"label": { "th": "น้ำตรีฟิลก้านไม้หอม 200ml", "en": "Diffuser Refills 200ml" },
"url": "/category/diffuser-refills",
"imageUrl": "https://images.unsplash.com/photo-1605651202774-7d573fd3f12d?w=800"
}
]
},
{
"title": {
"th": "เทียนหอมอโรมา",
"en": "Scented Candles",
"ja": "アロマキャンドル",
"zh": "香薰蜡烛",
"vi": "Nến thơm",
"ko": "센티드 캔들",
"lo": "ທຽນຫອມ",
"it": "Candele Profumate"
},
"icon": "Flame",
"links": [
{
"label": { "th": "เทียนหอมไขถั่วเหลืองธรรมชาติ", "en": "Natural Soy Wax Candles" },
"url": "/category/soy-candles",
"imageUrl": "https://images.unsplash.com/photo-1592945403244-b3fbafd7f539?w=800"
},
{
"label": { "th": "เทียนหอมขนาดพกพา Travel Tin", "en": "Travel Candle Tins" },
"url": "/category/travel-candles",
"imageUrl": "https://images.unsplash.com/photo-1543418219-44e30b057fea?w=800"
}
]
}
]
}
]
[คำสั่ง]:
กรุณาสร้าง Mega Menu สำหรับร้านค้า Luxury Fragrance & Aromatherapy จำนวน 5 Root Items ตามโครงสร้างและสเปกด้านบนให้ครบทั้ง 8 ภาษา ส่งเฉพาะ Valid JSON Array เท่านั้น`
const BEAUTY_PROMPT = `คุณคือ Senior E-commerce Merchandiser และ UX Designer เชี่ยวชาญการออกแบบระบบ Navigation สำหรับเว็บไซต์บิวตี้และสกินแคร์ระดับแนวหน้า (เช่น Sephora, EVEANDBOY, Boots, Olive Young)
[ภารกิจของคุณ]:
สร้างโครงสร้างเมนูหลัก (Main Navigation Mega Menu) สำหรับนำเข้าสู่ระบบ E-commerce ในรูปแบบ **Valid JSON Array** ตาม Schema ของระบบอย่างเคร่งครัด
[กฎและข้อกำหนดทางเทคนิค]:
1. ผลลัพธ์ต้องเป็น Valid JSON Array ล้วนๆ เริ่มต้นด้วย [ และจบด้วย ] เท่านั้น
2. หมวดหมู่หลัก (Root Items) เช่น:
- "Skincare" (คลีนเซอร์, โทนเนอร์, เซรั่ม, มอยส์เจอไรเซอร์, ครีมกันแดด)
- "Makeup" (รองพื้น, แป้งพัฟ, ลิปสติก, อายแชโดว์, บลัชออน)
- "Hair & Body" (แชมพู, ทรีตเมนต์ผม, ครีมอาบน้ำ, บอดี้โลชั่น)
- "Brands" (แบรนด์ยอดนิยมระดับโลกและแบรนด์ดังในเอเชีย)
- "Special Offers" (โปรโมชั่นประจำเดือน, สินค้าซื้อ 1 แถม 1, สิทธิพิเศษสมาชิก)
3. แต่ละ Root Item ต้องมี:
- "label": รองรับครบ 8 ภาษา { "th", "en", "ja", "zh", "vi", "ko", "lo", "it" }
- "url": เช่น "/category/skincare"
- "rootImageUrl": รูปภาพความละเอียดสูงจาก Unsplash เกี่ยวกับสกินแคร์ เครื่องสำอาง หรือบิวตี้
- "featured": แบนเนอร์ไฮไลต์ผลิตภัณฑ์เด่นประจำหมวด
- "columns": 3-4 คอลัมน์แบ่งตามกลุ่มการใช้งานหรือปัญหาผิว (เช่น Cleanser, Treatment & Serum, Sun Protection)
[คำสั่ง]:
กรุณาสร้าง Mega Menu สำหรับร้านค้า Cosmetics & Skincare จำนวน 4-5 Root Items ตามโครงสร้างที่กำหนดให้ครบ 8 ภาษา ส่งเฉพาะ Valid JSON Array เท่านั้น`
const FASHION_PROMPT = `คุณคือ Senior Fashion Merchandiser และ Digital Platform Architect เชี่ยวชาญการออกแบบโครงสร้างเมนูสำหรับแบรนด์แฟชั่นระดับไฮเอนด์และมัลติแบรนด์สโตร์ (เช่น Zara, COS, Massimo Dutti, SSENSE, Farfetch)
[ภารกิจของคุณ]:
สร้างโครงสร้างเมนูหลัก (Main Navigation Mega Menu) สำหรับนำเข้าสู่ระบบ E-commerce ในรูปแบบ **Valid JSON Array** ตาม Schema ของระบบอย่างเคร่งครัด
[กฎและข้อกำหนดทางเทคนิค]:
1. ผลลัพธ์ต้องเป็น Valid JSON Array ล้วนๆ เริ่มต้นด้วย [ และจบด้วย ] เท่านั้น
2. หมวดหมู่หลัก (Root Items) เช่น:
- "Women" (เดรส, เสื้อท็อป, กางเกง, กระโปรง, แจ็กเก็ต)
- "Men" (เสื้อเชิ้ต, ทีเชิ้ต, สแล็ค, ยีนส์, เบลเซอร์)
- "Accessories & Bags" (กระเป๋าถือ, กระเป๋าสตางค์, เข็มขัด, หมวก, เครื่องประดับ)
- "Shoes" (ส้นสูง, สนีกเกอร์, โลฟเฟอร์, บูท)
- "New Arrivals & Editorial" (คอลเลกชันใหม่ล่าสุด, Lookbook, Sustainability Edit)
3. แต่ละ Root Item ต้องมี:
- "label": ครบ 8 ภาษา { "th", "en", "ja", "zh", "vi", "ko", "lo", "it" }
- "url": ลิงก์หมวด
- "rootImageUrl": ภาพ Lookbook หรือ Moodboard สไตล์โมเดิร์น
- "featured": ไฮไลต์ New Season Drop
- "columns": 3-4 คอลัมน์แบ่งตามประเภทย่อย พร้อม hover imageUrl
[คำสั่ง]:
กรุณาสร้าง Mega Menu สำหรับแบรนด์ Modern Fashion & Lifestyle จำนวน 5 Root Items ครบ 8 ภาษา ส่งเฉพาะ Valid JSON Array เท่านั้น`
const GENERIC_PROMPT = `คุณคือ Senior E-commerce Architect และ UI/UX Specialist เชี่ยวชาญการออกแบบ Navigation Tree สำหรับระบบ Multi-tenant E-commerce Platform
[ภารกิจของคุณ]:
สร้างโครงสร้างเมนูนำทาง (Main Navigation Mega Menu) สำหรับนำเข้าสู่ระบบ E-commerce ในรูปแบบ **Valid JSON Array**
[ข้อกำหนด Schema]:
- Root Array: [ { label: { th, en, ... }, url, rootImageUrl, featured: { ... }, columns: [ ... ] } ]
- Column: { title: { th, en, ... }, icon, links: [ { label: { th, en, ... }, url, imageUrl } ] }
- Featured: { enabled: true, sectionTitle, badge, title, subtitle, ctaLabel, imageUrl, url }
- Multilingual: รองรับ 8 ภาษา (th, en, ja, zh, vi, ko, lo, it)
[คำสั่ง]:
กรุณาสร้าง Mega Menu ที่สมบูรณ์และสวยงามตาม Schema ด้านบน โดยให้คุณออกแบบหมวดหมู่ที่เหมาะสม สละสลวย และส่งผลลัพธ์เป็น Valid JSON Array เท่านั้น`
const TABS = [
{
id: 'overview',
label: 'Overview & Schema',
icon: BookOpen,
badge: 'Universal Structure',
theme: 'สถาปัตยกรรม 3 ระดับ + 8 ภาษา',
content: GUIDE_OVERVIEW_MARKDOWN,
},
{
id: 'fragrance',
label: 'Luxury Fragrance (HomAroma)',
icon: Flower2,
badge: 'Luxury Wellness',
theme: 'เทียนหอม / ก้านไม้หอม / น้ำหอม',
content: FRAGRANCE_PROMPT,
},
{
id: 'beauty',
label: 'Cosmetics & Skincare',
icon: Sparkle,
badge: 'Beauty & Skincare',
theme: 'เครื่องสำอาง / สกินแคร์ / คลีนเซอร์',
content: BEAUTY_PROMPT,
},
{
id: 'fashion',
label: 'Fashion & Lifestyle',
icon: Shirt,
badge: 'Modern Fashion',
theme: 'เสื้อผ้า / กระเป๋า / รองเท้า / แอคเซสเซอรี่',
content: FASHION_PROMPT,
},
{
id: 'generic',
label: 'Generic E-commerce',
icon: Compass,
badge: 'Custom Industry',
theme: 'ปรับใช้ได้กับทุกประเภทธุรกิจ',
content: GENERIC_PROMPT,
},
]
export default function MenuJsonGuideModal({
isOpen,
onClose,
onOpenPasteJson,
onOpenImportFile,
}: MenuJsonGuideModalProps) {
const [activeTab, setActiveTab] = useState('fragrance')
const [copied, setCopied] = useState(false)
if (!isOpen) return null
const currentTabItem = TABS.find((t) => t.id === activeTab) || TABS[0]
const handleCopy = async () => {
try {
await navigator.clipboard.writeText(currentTabItem.content)
setCopied(true)
setTimeout(() => setCopied(false), 2500)
} catch (err) {
console.error('Failed to copy text:', err)
alert('ไม่สามารถ Copy ได้อัตโนมัติ กรุณาเลือกข้อความแล้วกด Ctrl+C')
}
}
return (
<div className="fixed inset-0 z-50 bg-night-950/85 backdrop-blur-md flex items-center justify-center p-3 sm:p-5 animate-fade-in">
<div className="w-full max-w-5xl bg-night-900 border border-white/10 rounded-3xl shadow-2xl flex flex-col max-h-[92vh] overflow-hidden animate-zoom-in">
{/* Header */}
<div className="p-5 sm:p-6 border-b border-white/10 bg-white/[0.02] flex items-center justify-between gap-4">
<div className="flex items-center gap-3.5">
<div className="w-11 h-11 rounded-2xl bg-gradient-to-br from-purple-500/20 via-pink-500/10 to-cyan-500/20 border border-purple-500/30 flex items-center justify-center text-purple-300 shadow-inner">
<Sparkles className="w-6 h-6 text-purple-300 animate-pulse" />
</div>
<div>
<div className="flex items-center gap-2">
<h3 className="text-lg sm:text-xl font-bold text-white tracking-wide">
AI Navigation JSON Generator Guide
</h3>
<span className="px-2 py-0.5 rounded-full text-[10px] font-bold bg-purple-500/20 text-purple-300 border border-purple-500/30 uppercase tracking-wider">
8-Languages
</span>
</div>
<p className="text-xs text-slate-400 mt-0.5">
คู่มือและชุด Prompt สำหรับส่งให้ LLM (ChatGPT / Claude / Gemini) เพื่อสร้างข้อมูล JSON เมนู Main Navigation
</p>
</div>
</div>
<div className="flex items-center gap-2">
<button
onClick={handleCopy}
className={`flex items-center gap-1.5 px-4 py-2 rounded-xl text-xs font-bold transition-all cursor-pointer shadow-lg ${
copied
? 'bg-emerald-500 text-night-950 shadow-emerald-500/20'
: 'bg-gradient-to-r from-purple-500 to-indigo-500 hover:from-purple-400 hover:to-indigo-400 text-white shadow-purple-500/20'
}`}
title="คัดลอกข้อความในแท็บปัจจุบัน"
>
{copied ? (
<>
<Check className="w-4 h-4" />
<span>คัดลอกสำเร็จ!</span>
</>
) : (
<>
<Copy className="w-4 h-4" />
<span>Copy {currentTabItem.id === 'overview' ? 'Guide' : 'Prompt'}</span>
</>
)}
</button>
<button
onClick={onClose}
className="p-2 rounded-xl text-slate-400 hover:text-white hover:bg-white/10 transition-colors cursor-pointer"
>
<X className="w-5 h-5" />
</button>
</div>
</div>
{/* Tab Navigation */}
<div className="px-5 sm:px-6 pt-3 pb-2 border-b border-white/5 bg-night-950/40 flex items-center gap-2 overflow-x-auto scrollbar-hide">
{TABS.map((tab) => {
const Icon = tab.icon
const isActive = activeTab === tab.id
return (
<button
key={tab.id}
onClick={() => {
setActiveTab(tab.id)
setCopied(false)
}}
className={`flex items-center gap-2 px-3.5 py-2 rounded-xl text-xs font-semibold whitespace-nowrap transition-all cursor-pointer ${
isActive
? 'bg-white/10 text-white border border-white/15 shadow-md shadow-black/20'
: 'text-slate-400 hover:text-slate-200 hover:bg-white/5 border border-transparent'
}`}
>
<Icon className={`w-3.5 h-3.5 ${isActive ? 'text-cyan-glow' : 'text-slate-500'}`} />
<span>{tab.label}</span>
</button>
)
})}
</div>
{/* Tab Content Header Info */}
<div className="px-5 sm:px-6 py-3 bg-white/[0.015] border-b border-white/5 flex flex-wrap items-center justify-between gap-3 text-xs">
<div className="flex items-center gap-2 text-slate-300">
<span className="font-semibold text-white">{currentTabItem.badge}:</span>
<span className="text-slate-400 truncate max-w-md">{currentTabItem.theme}</span>
</div>
<div className="flex items-center gap-2">
{onOpenPasteJson && (
<button
type="button"
onClick={() => {
onClose()
onOpenPasteJson()
}}
className="inline-flex items-center gap-1.5 px-3 py-1 rounded-lg bg-emerald-500/20 text-emerald-300 border border-emerald-500/30 text-xs font-bold hover:bg-emerald-500/30 transition-colors cursor-pointer"
>
<Code2 className="w-3.5 h-3.5" />
<span>วาง JSON ผลลัพธ์ (Paste JSON)</span>
</button>
)}
{onOpenImportFile && (
<button
type="button"
onClick={() => {
onClose()
onOpenImportFile()
}}
className="inline-flex items-center gap-1.5 px-3 py-1 rounded-lg bg-white/5 text-slate-300 border border-white/10 text-xs font-bold hover:bg-white/10 transition-colors cursor-pointer"
>
<Upload className="w-3.5 h-3.5" />
<span>เลือกไฟล์ .json</span>
</button>
)}
<span className="text-[11px] text-slate-500 font-mono ml-2">
{currentTabItem.content.length.toLocaleString()} characters
</span>
</div>
</div>
{/* Content Body */}
<div className="flex-1 overflow-y-auto p-5 sm:p-6 space-y-4 font-sans text-sm text-slate-200 leading-relaxed">
{/* Quick Tip Alert */}
{activeTab !== 'overview' ? (
<div className="p-3.5 sm:p-4 rounded-2xl bg-gradient-to-r from-purple-500/10 via-indigo-500/5 to-cyan-500/10 border border-purple-500/20 text-xs text-purple-200 flex items-start gap-3 shadow-inner">
<div className="p-1 rounded-lg bg-purple-500/20 text-purple-300 shrink-0 mt-0.5">
<Sparkles className="w-3.5 h-3.5" />
</div>
<div className="space-y-1">
<p className="font-semibold text-white">
คำแนะนำการนำไปใช้งาน:
</p>
<p className="text-slate-300 text-[11px] leading-relaxed">
กดปุ่ม <strong>"Copy Prompt"</strong> ด้านบน แล้วนำไปวางใน <strong>ChatGPT, Claude 3.5 Sonnet หรือ Google Gemini</strong> เมื่อได้ผลลัพธ์เป็น JSON Array กลับมา สามารถกดปุ่ม <strong>"วาง JSON ผลลัพธ์ (Paste JSON)"</strong> เพื่อนำเข้าเมนูได้ทันทีโดยไม่ต้องเซฟเป็นไฟล์
</p>
</div>
</div>
) : (
<div className="p-3.5 sm:p-4 rounded-2xl bg-cyan-glow/5 border border-cyan-glow/20 text-xs text-cyan-200 flex items-start gap-3 shadow-inner">
<div className="p-1 rounded-lg bg-cyan-glow/20 text-cyan-glow shrink-0 mt-0.5">
<Layers className="w-3.5 h-3.5" />
</div>
<div className="space-y-1">
<p className="font-semibold text-white">
สถาปัตยกรรม Mega Menu รองรับครบ 8 ภาษา:
</p>
<p className="text-slate-300 text-[11px] leading-relaxed">
โครงสร้างลำดับชั้น: Root Item (Level 1 พร้อม rootImageUrl + Featured Card) ➔ Columns (Level 2) ➔ Links (Level 3 พร้อม Hover Image)
</p>
</div>
</div>
)}
{/* Text Area / Code Display */}
<div className="relative rounded-2xl border border-white/10 bg-night-950 p-4 font-mono text-xs text-slate-300 overflow-x-auto selection:bg-purple-500/30">
<pre className="whitespace-pre-wrap leading-relaxed">{currentTabItem.content}</pre>
</div>
</div>
{/* Footer Actions */}
<div className="p-4 sm:p-5 border-t border-white/10 bg-night-950/60 flex flex-wrap items-center justify-between gap-3">
<div className="flex items-center gap-2 text-xs text-slate-400">
<Globe2 className="w-4 h-4 text-cyan-glow" />
<span>Format: Valid JSON Array <code className="text-cyan-glow">[ ... ]</code></span>
</div>
<div className="flex items-center gap-2.5">
<button
onClick={handleCopy}
className={`flex items-center gap-1.5 px-4 py-2 rounded-xl text-xs font-bold transition-all cursor-pointer shadow-md ${
copied
? 'bg-emerald-500 text-night-950'
: 'bg-white/10 hover:bg-white/15 text-white'
}`}
>
{copied ? <Check className="w-4 h-4" /> : <Copy className="w-4 h-4" />}
<span>{copied ? 'คัดลอกแล้ว!' : 'Copy Prompt'}</span>
</button>
<button
onClick={onClose}
className="px-4 py-2 rounded-xl text-xs font-semibold text-slate-300 hover:text-white hover:bg-white/5 transition-colors cursor-pointer"
>
ปิดหน้าต่าง
</button>
</div>
</div>
</div>
</div>
)
}
@@ -1,8 +1,9 @@
import { useState, useEffect, useRef } from 'react'
import { API_BASE, type Menu } from '@homaroma/types'
import { Save, Loader2, Plus, Trash2, ChevronUp, ChevronDown, Copy, Check, Download, Upload, Star, Pencil } from 'lucide-react'
import { Save, Loader2, Plus, Trash2, ChevronUp, ChevronDown, Copy, Check, Download, Upload, Star, Pencil, Sparkles, Code2, X } from 'lucide-react'
import { ICON_REGISTRY } from '../../utils/iconRegistry'
import FocalPointPicker from '../../components/page-builder/FocalPointPicker'
import MenuJsonGuideModal from '../../components/MenuJsonGuideModal'
const IconSelect = ({ value, onChange }: { value?: string, onChange: (val: string) => void }) => {
return (
@@ -400,6 +401,10 @@ export default function MenuEditorPage({ menuName, title, description }: MenuEdi
const [pages, setPages] = useState<any[]>([])
const [copiedSlug, setCopiedSlug] = useState<string | null>(null)
const [selectedFeaturedIndex, setSelectedFeaturedIndex] = useState(0)
const [showLlmModal, setShowLlmModal] = useState(false)
const [showPasteModal, setShowPasteModal] = useState(false)
const [pastedJsonText, setPastedJsonText] = useState('')
const [pasteError, setPasteError] = useState<string | null>(null)
const fileInputRef = useRef<HTMLInputElement>(null)
useEffect(() => {
@@ -524,6 +529,39 @@ export default function MenuEditorPage({ menuName, title, description }: MenuEdi
reader.readAsText(file)
}
const handleApplyPastedJson = () => {
if (!pastedJsonText.trim()) {
setPasteError('กรุณาวางโค้ด JSON ก่อนกดยืนยัน')
return
}
try {
const parsed = JSON.parse(pastedJsonText)
let newItems: any[] = []
let newPayload: any = []
if (Array.isArray(parsed)) {
newPayload = parsed
} else {
newItems = Array.isArray(parsed.items) ? parsed.items : []
newPayload = Array.isArray(parsed.payload) ? parsed.payload : (parsed.payload || [])
}
setMenus(menus.map(m => m.name === menuName ? { ...m, items: newItems, payload: newPayload } : m))
if (!menus.find(m => m.name === menuName)) {
setMenus([...menus, { id: Date.now(), name: menuName, items: newItems, payload: newPayload }])
}
setShowPasteModal(false)
setPastedJsonText('')
setPasteError(null)
alert('โหลดข้อมูลเมนูจาก JSON สำเร็จ! กรุณากดปุ่ม "Save Menu" ด้านล่างเพื่อบันทึกลงระบบ')
} catch (err: any) {
console.error(err)
setPasteError(`รูปแบบ JSON ไม่ถูกต้อง: ${err?.message || 'Syntax error'}`)
}
}
const handleSave = async (e: React.FormEvent) => {
e.preventDefault()
setSaving(true)
@@ -568,7 +606,27 @@ export default function MenuEditorPage({ menuName, title, description }: MenuEdi
</div>
<div className="flex justify-end gap-2 border-b border-white/5 pb-4">
<div className="flex gap-2">
<div className="flex flex-wrap items-center gap-2">
{menuName === 'main' && (
<button
type="button"
onClick={() => setShowLlmModal(true)}
className="flex items-center gap-1.5 px-3 py-2 bg-gradient-to-r from-purple-500/20 via-pink-500/20 to-indigo-500/20 hover:from-purple-500/30 hover:to-indigo-500/30 text-purple-200 border border-purple-500/30 rounded-lg text-xs font-bold transition-all shadow-sm cursor-pointer"
title="ดูคู่มือและ Prompt สำหรับให้ AI / LLM สร้าง JSON เมนู Main Navigation"
>
<Sparkles className="w-4 h-4 text-pink-400" />
<span>LLM Prompt</span>
</button>
)}
<button
type="button"
onClick={() => setShowPasteModal(true)}
className="flex items-center gap-1.5 px-3 py-2 bg-emerald-500/10 hover:bg-emerald-500/20 text-emerald-300 border border-emerald-500/30 rounded-lg text-xs font-bold transition-colors cursor-pointer"
title="วางโค้ด JSON จาก LLM เพื่อนำเข้าเมนูทันที"
>
<Code2 className="w-4 h-4 text-emerald-400" />
<span>Paste JSON</span>
</button>
<input
type="file"
accept=".json"
@@ -579,14 +637,16 @@ export default function MenuEditorPage({ menuName, title, description }: MenuEdi
<button
type="button"
onClick={() => fileInputRef.current?.click()}
className="flex items-center gap-2 px-3 py-2 bg-white/5 hover:bg-white/10 text-slate-300 rounded-lg text-xs font-bold transition-colors"
className="flex items-center gap-2 px-3 py-2 bg-white/5 hover:bg-white/10 text-slate-300 rounded-lg text-xs font-bold transition-colors cursor-pointer"
title="เลือกไฟล์ .json จากคอมพิวเตอร์"
>
<Upload className="w-4 h-4" /> Import
</button>
<button
type="button"
onClick={handleExport}
className="flex items-center gap-2 px-3 py-2 bg-white/5 hover:bg-white/10 text-slate-300 rounded-lg text-xs font-bold transition-colors"
className="flex items-center gap-2 px-3 py-2 bg-white/5 hover:bg-white/10 text-slate-300 rounded-lg text-xs font-bold transition-colors cursor-pointer"
title="ดาวน์โหลดโครงสร้างเมนูเป็นไฟล์ .json"
>
<Download className="w-4 h-4" /> Export
</button>
@@ -822,6 +882,66 @@ export default function MenuEditorPage({ menuName, title, description }: MenuEdi
Copied URL: /{copiedSlug}
</div>
)}
{/* Paste JSON Modal */}
{showPasteModal && (
<div className="fixed inset-0 z-50 bg-night-950/85 backdrop-blur-md flex items-center justify-center p-4 animate-fade-in">
<div className="w-full max-w-2xl bg-night-900 border border-white/10 rounded-2xl shadow-2xl p-6 space-y-4 animate-zoom-in">
<div className="flex items-center justify-between border-b border-white/10 pb-3">
<div className="flex items-center gap-2.5">
<Code2 className="w-5 h-5 text-emerald-400" />
<h3 className="text-lg font-bold text-white">วางโค้ด JSON นำเข้าเมนู ({menuName})</h3>
</div>
<button
type="button"
onClick={() => { setShowPasteModal(false); setPasteError(null); }}
className="text-slate-400 hover:text-white p-1 rounded-lg hover:bg-white/10 transition-colors cursor-pointer"
>
<X className="w-5 h-5" />
</button>
</div>
<p className="text-xs text-slate-400">
วางโค้ด JSON Array ที่ได้รับจาก LLM (ChatGPT / Claude / Gemini) ด้านล่าง จากนั้นกด "นำเข้าสู่เมนู"
</p>
<textarea
rows={12}
className="admin-input w-full font-mono text-xs p-3 resize-y bg-night-950 border border-white/10 rounded-xl text-slate-200 focus:border-emerald-500/50"
placeholder={`[\n {\n "label": { "th": "เครื่องหอมสำหรับบ้าน", "en": "Home Fragrance" },\n "url": "/category/home-fragrance",\n "rootImageUrl": "https://images.unsplash.com/...",\n "columns": [\n {\n "title": { "th": "ก้านไม้หอม", "en": "Reed Diffusers" },\n "links": [\n { "label": { "th": "คลาสสิก", "en": "Classic" }, "url": "/category/diffusers" }\n ]\n }\n ]\n }\n]`}
value={pastedJsonText}
onChange={(e) => { setPastedJsonText(e.target.value); setPasteError(null); }}
/>
{pasteError && (
<p className="text-xs text-rose-400 bg-rose-500/10 border border-rose-500/20 p-2.5 rounded-lg">
{pasteError}
</p>
)}
<div className="flex items-center justify-end gap-2 pt-2">
<button
type="button"
onClick={() => { setShowPasteModal(false); setPasteError(null); }}
className="px-4 py-2 rounded-xl text-xs font-semibold text-slate-300 hover:text-white hover:bg-white/5 transition-colors cursor-pointer"
>
ยกเลิก
</button>
<button
type="button"
onClick={handleApplyPastedJson}
className="px-5 py-2 rounded-xl text-xs font-bold bg-emerald-500 hover:bg-emerald-400 text-night-950 transition-colors shadow-lg shadow-emerald-500/20 cursor-pointer"
>
นำเข้าสู่เมนู (Apply Menu)
</button>
</div>
</div>
</div>
)}
{/* LLM Mega Menu Guide Modal */}
<MenuJsonGuideModal
isOpen={showLlmModal}
onClose={() => setShowLlmModal(false)}
onOpenPasteJson={() => setShowPasteModal(true)}
onOpenImportFile={() => fileInputRef.current?.click()}
/>
</div>
)
}