'use client'; import { BarChart3, Bookmark, Bot, Building2, CreditCard, Factory, FileText, Gem, Home, List, LogOut, MessageSquare, Moon, Plus, Search, Sun, Target, User, type LucideIcon, } from 'lucide-react'; import Image from 'next/image'; import { usePathname } from 'next/navigation'; import { useTranslations } from 'next-intl'; import { useEffect, useState } from 'react'; import { DashboardLayout } from '@/components/design-system/dashboard-layout'; import { CompactHeader } from '@/components/design-system/compact-header'; import { TickerStrip } from '@/components/design-system/ticker-strip'; import type { TickerItem } from '@/components/design-system/ticker-strip'; import { NotificationBell } from '@/components/notifications/notification-bell'; import { useTheme } from '@/components/providers/theme-provider'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { LanguageSwitcher } from '@/components/ui/language-switcher'; import { Link } from '@/i18n/navigation'; import { useRouter } from '@/i18n/navigation'; import { useAuthStore } from '@/lib/auth-store'; import { cn } from '@/lib/utils'; interface NavItem { href: string; label: string; icon: LucideIcon; } interface NavGroup { label: string; items: NavItem[]; } /** Icon-only sidebar button with tooltip. */ function SidebarNavItem({ item, active, onClick, }: { item: NavItem; active: boolean; onClick?: () => void; }) { return ( {/* Tooltip */} {item.label} ); } export default function AppDashboardLayout({ children }: { children: React.ReactNode }) { const pathname = usePathname(); const router = useRouter(); const { user, isAuthenticated, isInitialized, logout } = useAuthStore(); const { theme, toggleTheme } = useTheme(); const t = useTranslations(); const [now, setNow] = useState(null); // Auth guard useEffect(() => { if (isInitialized && !isAuthenticated) { const next = encodeURIComponent(pathname); router.replace(`/login?next=${next}`); } }, [isInitialized, isAuthenticated, pathname, router]); // Live clock for status bar useEffect(() => { setNow(new Date()); const id = setInterval(() => setNow(new Date()), 30_000); return () => clearInterval(id); }, []); if (!isInitialized || !isAuthenticated) { return ( {t('common.loading')} ); } const role = user?.role; const isDeveloper = role === 'DEVELOPER'; const isParkOperator = role === 'PARK_OPERATOR'; const showListings = !isDeveloper && !isParkOperator; const showProjects = !isParkOperator; const showParks = !isDeveloper; const navGroups: NavGroup[] = [ { label: t('dashboard.title'), items: [ { href: '/dashboard', label: t('dashboard.title'), icon: Home }, ...(showListings ? [ { href: '/listings', label: t('dashboard.listings'), icon: List }, { href: '/listings/new', label: t('dashboard.createListing'), icon: Plus }, ] : []), ], }, { label: t('dashboard.catalogs'), items: [ ...(showProjects ? [ { href: '/projects', label: isDeveloper ? 'Dự án của tôi' : t('dashboard.manageProjects'), icon: Building2, }, ] : []), ...(showParks ? [ { href: '/industrial-parks', label: isParkOperator ? 'KCN của tôi' : t('dashboard.manageIndustrialParks'), icon: Factory, }, ] : []), ], }, { label: 'CRM', items: [ { href: '/inquiries', label: t('dashboard.inquiries'), icon: MessageSquare }, ...(showListings ? [{ href: '/leads', label: t('dashboard.leads'), icon: Target }] : []), ], }, ...(showListings ? [ { label: t('dashboard.analytics'), items: [ { href: '/analytics', label: t('dashboard.analytics'), icon: BarChart3 }, { href: '/dashboard/reports', label: t('dashboard.reports'), icon: FileText }, { href: '/dashboard/saved-searches', label: t('dashboard.savedSearches'), icon: Bookmark, }, { href: '/dashboard/valuation', label: t('dashboard.aiValuation'), icon: Bot }, ], }, ] : []), { label: t('dashboard.profile'), items: [ { href: '/dashboard/profile', label: t('dashboard.profile'), icon: User }, ...(showListings ? [ { href: '/dashboard/subscription', label: t('dashboard.subscription'), icon: Gem, }, { href: '/dashboard/payments', label: t('dashboard.payments'), icon: CreditCard, }, ] : []), ], }, ].filter((g) => g.items.length > 0); const allNavItems = navGroups.flatMap((g) => g.items); const isActive = (href: string) => pathname === href || (href !== '/dashboard' && pathname.startsWith(href)); // ── Sidebar (icon-only 56px, mobile uses sheet drawer) ────────────────── const sidebar = ( {/* Logo mark */} GG {/* Nav items */} {allNavItems.map((item) => ( ))} {/* Bottom: logout */} logout()} aria-label={t('common.logout')} title={t('common.logout')} className="flex h-10 w-10 items-center justify-center rounded-md text-foreground-muted transition-colors hover:bg-background-surface hover:text-foreground" > ); // ── CompactHeader ──────────────────────────────────────────────────────── const header = ( {t('common.goodgo')} } breadcrumb={ / } search={ } actions={ <> {user && } {theme === 'light' ? ( ) : ( )} {user && ( {user.fullName} )} > } /> ); // ── Ticker strip (top 8 quận, placeholder → TODO: /analytics/districts) ─── // TODO: thay thế bằng dữ liệu thực từ /analytics/districts khi API sẵn sàng (TEC-3047) const tickerItems: TickerItem[] = [ { id: 'q1', label: 'Quận 1', changePercent: 2.4, direction: 'up' }, { id: 'q2', label: 'Quận 2', changePercent: -0.8, direction: 'down' }, { id: 'q3', label: 'Quận 3', changePercent: 1.1, direction: 'up' }, { id: 'q7', label: 'Quận 7', changePercent: 3.2, direction: 'up' }, { id: 'binhthanh', label: 'Bình Thạnh', changePercent: 0.0, direction: 'neutral' }, { id: 'thuduc', label: 'Thủ Đức', changePercent: 1.7, direction: 'up' }, { id: 'tanbinh', label: 'Tân Bình', changePercent: -1.3, direction: 'down' }, { id: 'phuninh', label: 'Phú Nhuận', changePercent: 0.5, direction: 'up' }, ]; const ticker = ; // ── Status bar ─────────────────────────────────────────────────────────── const statusBar = ( <> Đã kết nối {now && ( Cập nhật lúc {now.toLocaleTimeString('vi-VN', { hour: '2-digit', minute: '2-digit' })} )} > ); return ( {children} ); }