'use client' /** * AppLayout - 全域應用佈局 * ======================== * CPO-102: 整合側邊欄 + 頂部狀態列 * * Features: * - 響應式側邊欄折疊 * - 主內容區域自動適應 * - 持久化折疊狀態 (localStorage) * - **Phase 19 修復**: 全局 SSE 連接 (所有頁面共享連線狀態) * * @updated 2026-03-28 - 修復 Header「已斷線」狀態問題 */ import { useState, useEffect } from 'react' import { Sidebar } from './sidebar' import { Header } from './header' import { DotMatrixBg } from '@/components/ui/dot-matrix-bg' import { cn } from '@/lib/utils' import { useDashboardStore } from '@/stores/dashboard.store' // ============================================================================= // Types // ============================================================================= interface AppLayoutProps { locale: string children: React.ReactNode showBackground?: boolean /** 主頁用:移除 pt-16 + p-6,讓 children 自行控制佈局 */ fullBleed?: boolean } // ============================================================================= // Constants // ============================================================================= const SIDEBAR_STATE_KEY = 'awoooi-sidebar-collapsed' const MOBILE_SHELL_MEDIA = '(max-width: 768px)' // ============================================================================= // Component // ============================================================================= export function AppLayout({ locale, children, showBackground = true, fullBleed = false, }: AppLayoutProps) { const [collapsed, setCollapsed] = useState(false) const [mobileShell, setMobileShell] = useState(false) const [mounted, setMounted] = useState(false) // Phase 19 修復: 全局 SSE 連接 const { connect, disconnect } = useDashboardStore() // Load collapsed state from localStorage useEffect(() => { setMounted(true) const saved = localStorage.getItem(SIDEBAR_STATE_KEY) if (saved === 'true') { setCollapsed(true) } }, []) useEffect(() => { const media = window.matchMedia(MOBILE_SHELL_MEDIA) const updateMobileShell = () => setMobileShell(media.matches) updateMobileShell() media.addEventListener('change', updateMobileShell) return () => { media.removeEventListener('change', updateMobileShell) } }, []) // Phase 19 修復: 全局啟動 SSE 連接 (所有頁面共享) useEffect(() => { const apiBaseUrl = process.env.NEXT_PUBLIC_API_URL || '' if (apiBaseUrl) { console.log('[AppLayout] 全局 SSE 連接啟動...', apiBaseUrl) connect(apiBaseUrl) } return () => { console.log('[AppLayout] SSE 連接關閉') disconnect() } }, [connect, disconnect]) // Save collapsed state to localStorage const handleToggle = () => { const newState = !collapsed setCollapsed(newState) localStorage.setItem(SIDEBAR_STATE_KEY, String(newState)) } // Keep the navigation shell in the server-rendered HTML. If a rolling deploy // or stale browser cache delays hydration, the operator still has navigation. const effectiveCollapsed = mounted ? collapsed : false const shellCollapsed = mobileShell || effectiveCollapsed return (
{/* Background */} {showBackground && ( )} {/* Sidebar */} {/* Header */}
{/* Main Content */}
{fullBleed ? ( // fullBleed: 無 p-6,children 自行控制佈局(主頁用) children ) : (
{children}
)}
) }