/** * Admin Layout Components * Core UI components for the admin dashboard layout */ "use client"; import React, { useState, useRef, useEffect } from "react"; import { useAdminLayout } from "./context"; import { AdminLayoutProps, SidebarProps, HeaderProps, ContentAreaProps, NavigationMenuProps, BreadcrumbNavigationProps, UserMenuProps, NotificationsPanelProps, SearchBarProps, NavigationItem, SearchResult, } from "./types"; // Icons mapping - using simple icon names that can be mapped to actual icons const iconMap: Record = { dashboard: "πŸ“Š", sites: "🌐", templates: "πŸ“„", users: "πŸ‘₯", settings: "βš™οΈ", analytics: "πŸ“ˆ", search: "πŸ”", notification: "πŸ””", profile: "πŸ‘€", logout: "πŸšͺ", menu: "☰", close: "βœ•", chevron: "❯", home: "🏠", }; // Icon component function Icon({ name, className = "" }: { name: string; className?: string }) { return ( {iconMap[name] || "●"} ); } // Main Admin Layout Component export function AdminLayout({ children, config, className = "", }: AdminLayoutProps) { const { config: layoutConfig, navigation, breadcrumbs, userMenu, notifications, search, updateConfig, } = useAdminLayout(); const finalConfig = { ...layoutConfig, ...config }; return (
{/* Sidebar */} updateConfig({ sidebar: { ...finalConfig.sidebar, isCollapsed: !finalConfig.sidebar.isCollapsed, }, }) } /> {/* Main Content Area */}
{/* Header */}
{/* Content */} {children}
); } // Sidebar Component export function Sidebar({ navigation, config, onToggle, className = "", }: SidebarProps) { const { setActiveNavigation } = useAdminLayout(); const sidebarWidth = config.isCollapsed ? config.collapsedWidth : config.width; return ( ); } // Header Component export function Header({ breadcrumbs, userMenu, notifications, search, config, className = "", }: HeaderProps) { return (
{/* Left Section */}
{config.showBreadcrumbs && ( )}
{/* Right Section */}
{/* Search Bar */} { // Mock search implementation return [ { id: "1", title: `Search result for "${query}"`, type: "site" as const, url: `/search?q=${query}`, }, ]; }} onResultClick={(result) => { window.location.href = result.url; }} /> {/* Notifications */} {config.showNotifications && ( {}} onClearAll={() => {}} /> )} {/* User Menu */} {config.showUserMenu && ( { if (item.onClick) { item.onClick(); } else if (item.href) { window.location.href = item.href; } }} /> )}
); } // Content Area Component export function ContentArea({ children, config, className = "", }: ContentAreaProps) { return (
{children}
); } // Navigation Menu Component export function NavigationMenu({ groups, activeItem, onItemClick, className = "", }: NavigationMenuProps) { const [expandedGroups, setExpandedGroups] = useState>(new Set()); const toggleGroup = (groupId: string) => { const newExpanded = new Set(expandedGroups); if (newExpanded.has(groupId)) { newExpanded.delete(groupId); } else { newExpanded.add(groupId); } setExpandedGroups(newExpanded); }; const renderNavigationItem = (item: NavigationItem, level = 0) => { const hasChildren = item.children && item.children.length > 0; const isActive = item.isActive || item.id === activeItem; const isExpanded = item.isExpanded || expandedGroups.has(item.id); return (
{hasChildren && isExpanded && (
{item.children!.map((child) => renderNavigationItem(child, level + 1) )}
)}
); }; return (
{groups.map((group) => (
{group.label && (

{group.label}

)}
{group.items.map((item) => renderNavigationItem(item))}
))}
); } // Breadcrumb Navigation Component export function BreadcrumbNavigation({ items, className = "", }: BreadcrumbNavigationProps) { if (!items.length) return null; return ( ); } // User Menu Component export function UserMenu({ config, onItemClick, className = "", }: UserMenuProps) { const [isOpen, setIsOpen] = useState(false); const menuRef = useRef(null); useEffect(() => { function handleClickOutside(event: MouseEvent) { if (menuRef.current && !menuRef.current.contains(event.target as Node)) { setIsOpen(false); } } document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }, []); return (
{isOpen && (
{config.items.map((item) => (
{item.isDivider ? (
) : ( )}
))}
)}
); } // Notifications Panel Component export function NotificationsPanel({ config, onMarkAsRead, onClearAll, className = "", }: NotificationsPanelProps) { const [isOpen, setIsOpen] = useState(false); const panelRef = useRef(null); useEffect(() => { function handleClickOutside(event: MouseEvent) { if ( panelRef.current && !panelRef.current.contains(event.target as Node) ) { setIsOpen(false); } } document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }, []); return (
{isOpen && (

μ•Œλ¦Ό

{config.items.length > 0 && ( )}
{config.items.length === 0 ? (
μƒˆλ‘œμš΄ μ•Œλ¦Όμ΄ μ—†μŠ΅λ‹ˆλ‹€.
) : ( config.items.map((notification) => (
{ if (!notification.isRead) { onMarkAsRead(notification.id); } if (notification.actionUrl) { window.location.href = notification.actionUrl; } setIsOpen(false); }} >

{notification.title}

{notification.message}

{notification.timestamp.toLocaleDateString("ko-KR")}

{!notification.isRead && (
)}
)) )}
)}
); } // Search Bar Component export function SearchBar({ config, onSearch, onResultClick, className = "", }: SearchBarProps) { const [query, setQuery] = useState(""); const [results, setResults] = useState([]); const [isOpen, setIsOpen] = useState(false); const [isLoading, setIsLoading] = useState(false); const searchRef = useRef(null); useEffect(() => { function handleClickOutside(event: MouseEvent) { if ( searchRef.current && !searchRef.current.contains(event.target as Node) ) { setIsOpen(false); } } document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }, []); useEffect(() => { if (query.length >= config.minQueryLength) { const timer = setTimeout(async () => { setIsLoading(true); try { const searchResults = await onSearch(query); setResults(searchResults.slice(0, config.maxResults)); } catch (error) { console.error("Search error:", error); setResults([]); } setIsLoading(false); }, config.debounceDelay); return () => clearTimeout(timer); } else { setResults([]); } }, [ query, config.minQueryLength, config.maxResults, config.debounceDelay, onSearch, ]); return (
{ setQuery(e.target.value); setIsOpen(true); }} onFocus={() => setIsOpen(true)} placeholder={config.placeholder} className="w-64 pl-10 pr-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" />
{isOpen && (query.length >= config.minQueryLength || isLoading) && (
{isLoading ? (
검색 쀑...
) : results.length === 0 ? (
검색 κ²°κ³Όκ°€ μ—†μŠ΅λ‹ˆλ‹€.
) : (
{results.map((result) => ( ))}
)}
)}
); }