'use client'; import React, { useState, useEffect } from 'react'; import { useRouter, usePathname } from 'next/navigation'; import { Box, List, ListItem, ListItemButton, ListItemIcon, ListItemText, Typography, IconButton, Tooltip, Divider, Chip, } from '@mui/material'; import { Dashboard, Assignment, CalendarToday, People, Star, CheckCircle, Schedule, Block, FiberNew, ExpandLess, ExpandMore, Language as Public, ChevronLeft, } from '@mui/icons-material'; import { motion, AnimatePresence } from 'framer-motion'; import { useTheme } from '@/providers/ThemeProvider'; import { todosApi } from '@/lib/api'; import { Todo } from '@/types'; interface SidebarProps { collapsed: boolean; onToggleCollapse: () => void; onClose?: () => void; } interface NavItem { id: string; label: string; icon: React.ReactNode; path: string; badge?: number; color?: string; } interface NavGroup { id: string; label: string; items: NavItem[]; expanded?: boolean; } const Sidebar: React.FC = ({ collapsed, onToggleCollapse, onClose }) => { const router = useRouter(); const pathname = usePathname(); const { actualTheme } = useTheme(); const [expandedGroups, setExpandedGroups] = React.useState>({ views: true, status: true, }); const [todos, setTodos] = useState([]); const [loading, setLoading] = useState(true); // 獲取待辦事項數據 useEffect(() => { const fetchTodos = async () => { try { setLoading(true); const token = localStorage.getItem('access_token'); if (!token) { setTodos([]); return; } const response = await todosApi.getTodos({ view: 'dashboard' }); setTodos(response.todos || []); } catch (error) { console.error('Failed to fetch todos for sidebar:', error); setTodos([]); } finally { setLoading(false); } }; fetchTodos(); }, []); // 獲取當前用戶信息 const getCurrentUser = () => { try { const userStr = localStorage.getItem('user'); if (userStr) { const user = JSON.parse(userStr); return { ad_account: user.ad_account, email: user.email }; } } catch (error) { console.error('Failed to parse user from localStorage:', error); } return null; }; // 計算各種統計數字 const getStatistics = () => { if (loading || !todos.length) { return { total: 0, created: 0, assigned: 0, following: 0, new: 0, doing: 0, blocked: 0, done: 0, starred: 0 }; } const currentUser = getCurrentUser(); if (!currentUser) { return { total: todos.length, created: 0, assigned: 0, following: 0, new: todos.filter(todo => todo.status === 'NEW').length, doing: todos.filter(todo => todo.status === 'DOING').length, blocked: todos.filter(todo => todo.status === 'BLOCKED').length, done: todos.filter(todo => todo.status === 'DONE').length, starred: todos.filter(todo => todo.starred).length }; } return { total: todos.length, created: todos.filter(todo => todo.creator_ad === currentUser.ad_account || todo.creator_email === currentUser.email ).length, assigned: todos.filter(todo => todo.responsible_users?.includes(currentUser.ad_account) || todo.responsible_users?.includes(currentUser.email) ).length, following: todos.filter(todo => todo.followers?.includes(currentUser.ad_account) || todo.followers?.includes(currentUser.email) ).length, new: todos.filter(todo => todo.status === 'NEW').length, doing: todos.filter(todo => todo.status === 'DOING').length, blocked: todos.filter(todo => todo.status === 'BLOCKED').length, done: todos.filter(todo => todo.status === 'DONE').length, starred: todos.filter(todo => todo.starred).length }; }; const stats = getStatistics(); const navGroups: NavGroup[] = [ { id: 'main', label: '主要功能', items: [ { id: 'dashboard', label: '儀表板', icon: , path: '/dashboard', }, { id: 'todos', label: '待辦清單', icon: , path: '/todos', badge: stats.total || undefined, }, { id: 'public', label: '公開任務', icon: , path: '/public', }, { id: 'calendar', label: '日曆視圖', icon: , path: '/calendar', }, ], }, { id: 'views', label: '視圖篩選', items: [ { id: 'starred', label: '已加星', icon: , path: '/todos?starred=true', badge: stats.starred || undefined, color: '#fbbf24', }, { id: 'my-todos', label: '我建立的', icon: , path: '/todos?view=created', badge: stats.created || undefined, }, { id: 'assigned', label: '指派給我', icon: , path: '/todos?view=responsible', badge: stats.assigned || undefined, }, { id: 'following', label: '我追蹤的', icon: , path: '/todos?view=following', badge: stats.following || undefined, }, ], }, { id: 'status', label: '狀態分類', items: [ { id: 'new', label: '新建立', icon: , path: '/todos?status=NEW', badge: stats.new || undefined, color: '#6b7280', }, { id: 'doing', label: '進行中', icon: , path: '/todos?status=DOING', badge: stats.doing || undefined, color: '#3b82f6', }, { id: 'blocked', label: '已阻塞', icon: , path: '/todos?status=BLOCKED', badge: stats.blocked || undefined, color: '#ef4444', }, { id: 'done', label: '已完成', icon: , path: '/todos?status=DONE', badge: stats.done || undefined, color: '#10b981', }, ], }, ]; const handleNavClick = (path: string) => { router.push(path); if (onClose) onClose(); // 手機版關閉側邊欄 }; const toggleGroup = (groupId: string) => { if (collapsed) return; // 收合狀態下不允許展開群組 setExpandedGroups(prev => ({ ...prev, [groupId]: !prev[groupId], })); }; const isActive = (path: string) => { if (path === '/dashboard') return pathname === path; // 檢查是否含有查詢參數 if (path.includes('?')) { const [basePath, queryString] = path.split('?'); if (pathname !== basePath) return false; // 檢查查詢參數是否匹配 const urlParams = new URLSearchParams(window.location.search); const pathParams = new URLSearchParams(queryString); // 檢查每個路徑參數是否在當前 URL 中存在且相同 const pathParamsArray = Array.from(pathParams.entries()); for (const [key, value] of pathParamsArray) { if (urlParams.get(key) !== value) { return false; } } return true; } // 沒有查詢參數的情況 return pathname.includes(path); }; const renderNavItem = (item: NavItem) => { const active = isActive(item.path); return ( handleNavClick(item.path)} sx={{ borderRadius: 2, mx: 1, minHeight: 44, backgroundColor: active ? (actualTheme === 'dark' ? 'rgba(59, 130, 246, 0.15)' : 'rgba(59, 130, 246, 0.1)') : 'transparent', border: active ? `1px solid ${actualTheme === 'dark' ? 'rgba(59, 130, 246, 0.3)' : 'rgba(59, 130, 246, 0.2)'}` : '1px solid transparent', '&:hover': { backgroundColor: active ? (actualTheme === 'dark' ? 'rgba(59, 130, 246, 0.2)' : 'rgba(59, 130, 246, 0.15)') : (actualTheme === 'dark' ? 'rgba(255, 255, 255, 0.05)' : 'rgba(0, 0, 0, 0.04)'), }, transition: 'all 0.2s ease', }} > {item.icon} {!collapsed && ( {item.badge && ( {item.badge} )} )} ); }; return ( {/* Logo 區域 */} {!collapsed && ( )} {/* 導航列表 */} {navGroups.map((group) => ( {/* 群組標題 */} {!collapsed && group.label && ( toggleGroup(group.id)} sx={{ borderRadius: 2, mx: 1, minHeight: 36, '&:hover': { backgroundColor: actualTheme === 'dark' ? 'rgba(255, 255, 255, 0.05)' : 'rgba(0, 0, 0, 0.04)', }, }} disabled={group.id === 'main'} > {group.id !== 'main' && ( expandedGroups[group.id] ? : )} )} {/* 群組項目 */} {(collapsed || expandedGroups[group.id] || group.id === 'main') && ( {group.items.map(renderNavItem)} )} {/* 分隔線 */} {group.id === 'main' && !collapsed && ( )} ))} {/* 底部快速狀態 */} {!collapsed && ( {stats.doing > 0 && ( )} {(() => { const overdue = todos.filter(todo => { if (!todo.due_date) return false; const dueDate = new Date(todo.due_date); const today = new Date(); today.setHours(0, 0, 0, 0); return dueDate < today && todo.status !== 'DONE'; }).length; return overdue > 0 ? ( ) : null; })()} {stats.blocked > 0 && ( )} {stats.total === 0 && !loading && ( )} )} ); }; export default Sidebar;