diff --git a/apps/admin/src/App.tsx b/apps/admin/src/App.tsx index 32eccdd..4aa604b 100644 --- a/apps/admin/src/App.tsx +++ b/apps/admin/src/App.tsx @@ -76,6 +76,7 @@ import MarketplaceProductsPage from './pages/tools/MarketplaceProductsPage' import MarketplaceCrmPage from './pages/crm/MarketplaceCrmPage' import MarketplaceCustomerDetailPage from './pages/crm/MarketplaceCustomerDetailPage' import PosPage from './pages/PosPage' +import UserLlmPromptsPage from './pages/UserLlmPromptsPage' import { StoreSettingsProvider } from './contexts/StoreSettingsContext' function App() { @@ -179,6 +180,9 @@ function App() { } /> } /> } /> + {/* User & LLM Prompts */} + } /> + } /> diff --git a/apps/admin/src/components/Sidebar.tsx b/apps/admin/src/components/Sidebar.tsx index 1d86aa9..e327542 100644 --- a/apps/admin/src/components/Sidebar.tsx +++ b/apps/admin/src/components/Sidebar.tsx @@ -18,6 +18,7 @@ import { BarChart3, Layers, Globe2, + User, Users, UserCheck, Image as ImageIcon, @@ -66,6 +67,7 @@ export default function Sidebar({ isCollapsed, onToggle }: SidebarProps) { const isInCms = ['/page-builder', '/cms-mockup', '/media', '/articles'].some(p => location.pathname.startsWith(p)) const isInCustomer = ['/customers', '/subscribers', '/contacts', '/crm'].some(p => location.pathname.startsWith(p)) || isRepeatCustomers const isInMenuManager = location.pathname.startsWith('/menu-manager') + const isInUser = location.pathname.startsWith('/user') const isInBackup = location.pathname.startsWith('/backup') const isInSettings = location.pathname.startsWith('/settings') @@ -80,6 +82,7 @@ export default function Sidebar({ isCollapsed, onToggle }: SidebarProps) { const [cmsOpen, setCmsOpen] = useState(isInCms) const [customerOpen, setCustomerOpen] = useState(isInCustomer) const [menuManagerOpen, setMenuManagerOpen] = useState(isInMenuManager) + const [userOpen, setUserOpen] = useState(isInUser) const [backupOpen, setBackupOpen] = useState(isInBackup) const [settingsOpen, setSettingsOpen] = useState(isInSettings) @@ -164,6 +167,10 @@ export default function Sidebar({ isCollapsed, onToggle }: SidebarProps) { { icon: LayoutTemplate, label: t('nav.footerLayout', 'Footer Layout & Presentation'), to: '/menu-manager/footer-layout' }, ] + const USER_MENU = [ + { icon: Sparkles, label: t('nav.llmPrompts', 'LLM Prompts'), to: '/user/llm-prompts' }, + ] + const BACKUP_MENU = [ { icon: DatabaseBackup, label: t('nav.backupDatabase', 'Database Backup'), to: '/backup/database' }, { icon: Package, label: t('nav.backupCatalog', 'Catalog Backup'), to: '/backup/catalog' }, @@ -561,6 +568,36 @@ export default function Sidebar({ isCollapsed, onToggle }: SidebarProps) { )} + {/* User Group */} +
+ + + {!isCollapsed && userOpen && ( +
+ {USER_MENU.map((item) => ( + + `sidebar-link text-sm ${isActive ? 'sidebar-link-active' : 'sidebar-link-inactive'}` + } + > + + {item.label} + + ))} +
+ )} +
+ {/* Backup & Restore Group */}
+ +
- {/* Tiles List */} + {/* ── Section Header & Layout Controls ── */} +
+
+ + การจัดวางและโครงสร้าง (Layout & Styling) +
+ +
+
+ + updateLocalizedValue('title', e.target.value)} + className="admin-input w-full text-xs" + placeholder="e.g. Featured Campaigns" + /> +
+ +
+ + updateLocalizedValue('subtitle', e.target.value)} + className="admin-input w-full text-xs" + placeholder="e.g. Discover our curated collections" + /> +
+ +
+ + +
+
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+
+ +
+ updatePayloadValue('backgroundColor', e.target.value)} + className="w-10 h-9 p-1 rounded-lg border border-white/10 bg-slate-900 cursor-pointer" + /> + updatePayloadValue('backgroundColor', e.target.value)} + className="admin-input flex-1 text-xs" + placeholder="#F7F4EE หรือ transparent" + /> +
+
+ +
+ +
+ updatePayloadValue('textColor', e.target.value)} + className="w-10 h-9 p-1 rounded-lg border border-white/10 bg-slate-900 cursor-pointer" + /> + updatePayloadValue('textColor', e.target.value)} + className="admin-input flex-1 text-xs" + placeholder="#1E293B" + /> +
+
+
+
+ + {/* ── Cards List ── */}

Campaign Cards ({tiles.length})

@@ -129,13 +339,13 @@ export default function FeaturedTilesForm({
) : ( tiles.map((tile: any, idx: number) => ( -
+
Card #{idx + 1} + + + + + + + + +
+
+
+ + {/* Search & Category Filter Navigation */} +
+ {/* Search Bar */} +
+ + setSearchQuery(e.target.value)} + placeholder="ค้นหาตามชื่อ Prompt, ข้อความคำสั่ง, หมวดหมู่ หรือแท็ก..." + className="w-full bg-night-800/90 border border-white/10 rounded-xl pl-12 pr-10 py-3 text-sm text-white placeholder-slate-500 focus:outline-none focus:border-cyan-500/50 focus:ring-1 focus:ring-cyan-500/50 shadow-inner" + /> + {searchQuery && ( + + )} +
+ + {/* Categories Tab Navigation */} +
+ {CATEGORIES.map(cat => { + const Icon = cat.icon + const isActive = selectedCategory === cat.key + const count = cat.key === 'all' + ? prompts.length + : prompts.filter(p => p.category === cat.key).length + + return ( + + ) + })} +
+ + {/* Tag Filters (if any) */} + {allTags.length > 0 && ( +
+ + แท็ก: + + {selectedTag && ( + + )} + {allTags.map(tag => ( + + ))} +
+ )} +
+ + {/* Loading state */} + {loading ? ( +
+ +

กำลังโหลด Prompts จากฐานข้อมูล...

+
+ ) : filteredPrompts.length === 0 ? ( +
+
+ +
+

ไม่พบ Prompt ที่ตรงกับเงื่อนไข

+

+ ลองปรับเปลี่ยนคำค้นหา หรือเลือกหมวดหมู่อื่น หรือกดปุ่ม "สร้าง Prompt ใหม่" ด้านบน +

+ {(searchQuery || selectedCategory !== 'all' || selectedTag) && ( + + )} +
+ ) : ( +
+ {filteredPrompts.map((item) => { + const lines = item.content.split('\n').length + const chars = item.content.length + + return ( +
+ {/* Top Section */} +
+
+ + {item.category} + + {item.isSystem ? ( + + System Built-in + + ) : ( + + User Saved (DB) + + )} +
+ +
+

+ {item.title} +

+

+ {item.description || 'ไม่มีคำอธิบาย'} +

+
+ + {/* Tags */} + {Array.isArray(item.tags) && item.tags.length > 0 && ( +
+ {item.tags.map(tag => ( + setSelectedTag(tag)} + className="text-[10px] px-1.5 py-0.5 rounded bg-white/5 text-slate-400 hover:text-white cursor-pointer" + > + #{tag} + + ))} +
+ )} + + {/* Content Preview Box */} +
setViewingPrompt(item)} + className="p-3 bg-night-950/80 border border-white/5 rounded-xl cursor-pointer hover:border-cyan-500/20 transition-colors relative group/preview" + > +
+                      {item.content}
+                    
+
+ + คลิกเพื่อดูเต็มจอ + +
+
+ + {/* Stats */} +
+ {lines} บรรทัด • {chars.toLocaleString()} ตัวอักษร + {new Date(item.updatedAt).toLocaleDateString()} +
+
+ + {/* Bottom Actions */} +
+ {/* Copy Button (Primary) */} + + + {/* Secondary Actions */} +
+ + + {!item.isSystem && ( + + )} +
+
+
+ ) + })} +
+ )} + + {/* ── View Modal (Inspect Prompt) ─────────────────────────────────── */} + {viewingPrompt && ( +
+
+
+
+ + {viewingPrompt.category} + +

{viewingPrompt.title}

+
+
+ + +
+
+ +
+ {viewingPrompt.description && ( +

+ 💡 {viewingPrompt.description} +

+ )} +
+
+                  {viewingPrompt.content}
+                
+
+
+ +
+ {viewingPrompt.content.length.toLocaleString()} ตัวอักษร • {viewingPrompt.content.split('\n').length} บรรทัด + +
+
+
+ )} + + {/* ── Create / Edit Modal ────────────────────────────────────────── */} + {isModalOpen && ( +
+
+ {/* Modal Header */} +
+

+ + {editingPrompt ? 'แก้ไข Prompt ใน Database' : 'สร้าง Prompt ใหม่ลง Database'} +

+ +
+ + {/* Modal Form */} +
+
+ + setModalTitle(e.target.value)} + placeholder="เช่น Mega Menu Generator สำหรับเทศกาลปีใหม่" + className="w-full bg-night-950 border border-white/10 rounded-xl px-4 py-2.5 text-sm text-white placeholder-slate-500 focus:outline-none focus:border-cyan-500" + /> +
+ +
+
+ + +
+ +
+ + setModalTags(e.target.value)} + placeholder="เช่น Menu, JSON, Luxury, Spring" + className="w-full bg-night-950 border border-white/10 rounded-xl px-4 py-2.5 text-sm text-white placeholder-slate-500 focus:outline-none focus:border-cyan-500" + /> +
+
+ +
+ + setModalDescription(e.target.value)} + placeholder="อธิบายสั้นๆ เกี่ยวกับวัตถุประสงค์ของ Prompt นี้" + className="w-full bg-night-950 border border-white/10 rounded-xl px-4 py-2.5 text-sm text-white placeholder-slate-500 focus:outline-none focus:border-cyan-500" + /> +
+ +
+
+ + + {modalContent.length.toLocaleString()} chars • {modalContent.split('\n').length} lines + +
+