'use client'; import React, { useState, useEffect } from 'react'; import { Box, Typography, Button, IconButton, Toolbar, Tooltip, Fade, Chip, Card, Skeleton, CircularProgress, Backdrop, } from '@mui/material'; import { Add, ViewList, CalendarViewMonth, FilterList, Search, SelectAll, CloudUpload, } from '@mui/icons-material'; import { motion, AnimatePresence } from 'framer-motion'; import { useTheme } from '@/providers/ThemeProvider'; import DashboardLayout from '@/components/layout/DashboardLayout'; import TodoList from '@/components/todos/TodoList'; import CalendarView from '@/components/todos/CalendarView'; import TodoFilters from '@/components/todos/TodoFilters'; import BatchActions from '@/components/todos/BatchActions'; import SearchBar from '@/components/todos/SearchBar'; import TodoDialog from '@/components/todos/TodoDialog'; import ExcelImport from '@/components/todos/ExcelImport'; import { Todo } from '@/types'; import { todosApi } from '@/lib/api'; import { useSearchParams } from 'next/navigation'; type ViewMode = 'list' | 'calendar'; type FilterMode = 'all' | 'created' | 'responsible' | 'following'; const TodosPage = () => { const { actualTheme } = useTheme(); const searchParams = useSearchParams(); const [viewMode, setViewMode] = useState('list'); const [filterMode, setFilterMode] = useState('all'); const [showFilters, setShowFilters] = useState(false); const [appliedFilters, setAppliedFilters] = useState({ status: [] as string[], priority: [] as string[], assignee: '', dateFrom: null as any, dateTo: null as any, starred: false, overdue: false, dueSoon: false, }); const [showSearch, setShowSearch] = useState(false); const [selectedTodos, setSelectedTodos] = useState([]); const [searchQuery, setSearchQuery] = useState(''); const [showTodoDialog, setShowTodoDialog] = useState(false); const [editingTodo, setEditingTodo] = useState(null); const [showExcelImport, setShowExcelImport] = useState(false); const [todos, setTodos] = useState([]); const [loading, setLoading] = useState(true); const [currentUser, setCurrentUser] = useState(null); // 讀取 URL 參數並設定篩選條件 useEffect(() => { console.log('URL search params:', searchParams.toString()); // 當從 Sidebar 點擊時,應該清除所有其他篩選,只保留當前篩選 const viewParam = searchParams.get('view'); const statusParam = searchParams.get('status'); const starredParam = searchParams.get('starred'); // 重置所有篩選狀態 setFilterMode('all'); setAppliedFilters({ status: [], priority: [], assignee: '', dateFrom: null, dateTo: null, starred: false, overdue: false, dueSoon: false, }); // 根據 URL 參數設定對應的篩選 if (viewParam && ['created', 'responsible', 'following'].includes(viewParam)) { setFilterMode(viewParam as FilterMode); console.log('Setting filterMode to:', viewParam); } else if (statusParam) { // 狀態篩選:清除視圖篩選,只保留狀態篩選 setAppliedFilters(prev => ({ ...prev, status: [statusParam] })); console.log('Setting status filter to:', statusParam); } else if (starredParam === 'true') { // 星標篩選:清除其他篩選,只保留星標篩選 setAppliedFilters(prev => ({ ...prev, starred: true })); console.log('Setting starred filter to: true'); } }, [searchParams]); // 從 API 獲取資料 useEffect(() => { const fetchTodos = async () => { try { setLoading(true); // 檢查是否有有效的 token const token = localStorage.getItem('access_token'); console.log('Access token:', token ? 'Found' : 'Not found'); if (!token) { console.log('No access token found, redirecting to login'); setTodos([]); window.location.href = '/login'; return; } // 獲取當前用戶信息 try { const userResponse = await fetch('http://localhost:5000/api/auth/me', { headers: { 'Authorization': `Bearer ${token}`, }, }); if (userResponse.ok) { const userData = await userResponse.json(); setCurrentUser(userData); } } catch (userError) { console.warn('Failed to fetch user data:', userError); } // 獲取待辦事項 console.log('Fetching todos with filterMode:', filterMode); const response = await todosApi.getTodos({ view: filterMode === 'all' ? 'all' : filterMode }); console.log('Todos API response:', response); setTodos(response.todos || []); } catch (error: any) { console.error('Failed to fetch todos:', error); // 如果是認證錯誤,清除 token 並跳轉到登入頁 if (error?.response?.status === 401) { localStorage.removeItem('access_token'); localStorage.removeItem('refresh_token'); localStorage.removeItem('user'); window.location.href = '/login'; } setTodos([]); } finally { setLoading(false); } }; fetchTodos(); }, [filterMode]); const containerVariants = { hidden: { opacity: 0 }, visible: { opacity: 1, transition: { staggerChildren: 0.1, }, }, }; const itemVariants = { hidden: { opacity: 0, y: 20 }, visible: { opacity: 1, y: 0, transition: { duration: 0.5 }, }, }; const filteredTodos = todos.filter(todo => { // 搜尋過濾 if (searchQuery) { const query = searchQuery.toLowerCase(); if (!todo.title.toLowerCase().includes(query) && !todo.description?.toLowerCase().includes(query)) { return false; } } // 視圖過濾 - 修正:這裡應該是篩選而非直接返回 if (currentUser) { switch (filterMode) { case 'created': if (todo.creator_ad !== currentUser.ad_account) return false; break; case 'responsible': if (!todo.responsible_users?.includes(currentUser.ad_account)) return false; break; case 'following': if (!todo.followers?.includes(currentUser.ad_account)) return false; break; default: break; // 'all' 模式,繼續其他篩選 } } // 進階篩選 // 狀態篩選 if (appliedFilters.status.length > 0 && !appliedFilters.status.includes(todo.status)) { console.log(`Todo ${todo.title} filtered out by status: ${todo.status} not in`, appliedFilters.status); return false; } // 優先級篩選 if (appliedFilters.priority.length > 0 && !appliedFilters.priority.includes(todo.priority)) { console.log(`Todo ${todo.title} filtered out by priority: ${todo.priority} not in`, appliedFilters.priority); return false; } // 指派人篩選 if (appliedFilters.assignee && currentUser) { switch (appliedFilters.assignee) { case 'me': if (!todo.responsible_users?.includes(currentUser.ad_account)) { console.log(`Todo ${todo.title} filtered out: not assigned to me`); return false; } break; case 'created_by_me': if (todo.creator_ad !== currentUser.ad_account) { console.log(`Todo ${todo.title} filtered out: not created by me`); return false; } break; case 'followed_by_me': if (!todo.followers?.includes(currentUser.ad_account)) { console.log(`Todo ${todo.title} filtered out: not followed by me`); return false; } break; } } // 日期篩選 if (appliedFilters.dateFrom || appliedFilters.dateTo) { if (!todo.due_date) { console.log(`Todo ${todo.title} filtered out: no due date`); return false; } const dueDate = new Date(todo.due_date); if (appliedFilters.dateFrom && dueDate < new Date(appliedFilters.dateFrom)) { console.log(`Todo ${todo.title} filtered out: due date before ${appliedFilters.dateFrom}`); return false; } if (appliedFilters.dateTo && dueDate > new Date(appliedFilters.dateTo)) { console.log(`Todo ${todo.title} filtered out: due date after ${appliedFilters.dateTo}`); return false; } } // 星號篩選 if (appliedFilters.starred && !todo.starred) { console.log(`Todo ${todo.title} filtered out: not starred`); return false; } // 逾期篩選 if (appliedFilters.overdue) { if (!todo.due_date) { console.log(`Todo ${todo.title} filtered out for overdue: no due date`); return false; } const dueDate = new Date(todo.due_date); const today = new Date(); today.setHours(0, 0, 0, 0); if (dueDate >= today || todo.status === 'DONE') { console.log(`Todo ${todo.title} filtered out for overdue: not overdue or done`); return false; } } // 即將到期篩選 if (appliedFilters.dueSoon) { if (!todo.due_date || todo.status === 'DONE') { console.log(`Todo ${todo.title} filtered out for due soon: no due date or done`); return false; } const dueDate = new Date(todo.due_date); const today = new Date(); const threeDaysFromNow = new Date(); threeDaysFromNow.setDate(today.getDate() + 3); if (dueDate < today || dueDate > threeDaysFromNow) { console.log(`Todo ${todo.title} filtered out for due soon: not in 3-day window`); return false; } } return true; }); // 加入除錯資訊 useEffect(() => { console.log('Applied filters:', appliedFilters); console.log('Total todos:', todos.length); console.log('Filtered todos:', filteredTodos.length); }, [appliedFilters, todos.length, filteredTodos.length]); const getFilterModeLabel = (mode: FilterMode) => { switch (mode) { case 'created': return '我建立的'; case 'responsible': return '指派給我'; case 'following': return '我追蹤的'; default: return '所有待辦'; } }; const handleSelectAll = () => { if (selectedTodos.length === filteredTodos.length) { setSelectedTodos([]); } else { setSelectedTodos(filteredTodos.map(todo => todo.id)); } }; const handleCreateTodo = () => { setEditingTodo(null); setShowTodoDialog(true); }; const handleEditTodo = (todo: any) => { setEditingTodo(todo); setShowTodoDialog(true); }; const handleSaveTodo = (todoData: any) => { console.log('Saving todo:', todoData); // 這裡會調用 API 來儲存待辦事項 // 儲存成功後可以更新 todos 列表 }; const handleCloseTodoDialog = () => { setShowTodoDialog(false); setEditingTodo(null); }; const handleTodoCreated = async () => { // 刷新待辦事項列表 try { const response = await todosApi.getTodos({ view: filterMode === 'all' ? 'all' : filterMode }); setTodos(response.todos || []); } catch (error) { console.error('Failed to refresh todos:', error); } }; // 批次操作處理函數 const handleBulkStatusChange = async (status: 'NEW' | 'DOING' | 'BLOCKED') => { try { if (selectedTodos.length === 0) return; // 使用批次更新 API await todosApi.batchUpdateTodos(selectedTodos, { status }); // 更新本地狀態 setTodos(prevTodos => prevTodos.map(todo => selectedTodos.includes(todo.id) ? { ...todo, status } : todo ) ); // 清除選擇 setSelectedTodos([]); console.log(`批次更新 ${selectedTodos.length} 個待辦事項狀態為 ${status}`); } catch (error) { console.error('批次狀態更新失敗:', error); } }; const handleBulkComplete = async () => { try { if (selectedTodos.length === 0) return; // 使用批次更新 API 設為完成 await todosApi.batchUpdateTodos(selectedTodos, { status: 'DONE' }); // 更新本地狀態 setTodos(prevTodos => prevTodos.map(todo => selectedTodos.includes(todo.id) ? { ...todo, status: 'DONE' as const, completed_at: new Date().toISOString() } : todo ) ); // 清除選擇 setSelectedTodos([]); console.log(`批次完成 ${selectedTodos.length} 個待辦事項`); } catch (error) { console.error('批次完成失敗:', error); } }; const handleBulkDelete = async () => { try { if (selectedTodos.length === 0) return; if (!confirm(`確定要刪除 ${selectedTodos.length} 個待辦事項嗎?此操作無法復原。`)) { return; } // 逐一刪除待辦事項(如果沒有批次刪除 API) for (const todoId of selectedTodos) { await todosApi.deleteTodo(todoId); } // 從本地狀態中移除 setTodos(prevTodos => prevTodos.filter(todo => !selectedTodos.includes(todo.id)) ); // 清除選擇 setSelectedTodos([]); console.log(`批次刪除 ${selectedTodos.length} 個待辦事項`); } catch (error) { console.error('批次刪除失敗:', error); } }; // 單個待辦事項狀態變更處理函數 const handleStatusChange = async (todoId: string, status: string) => { try { // 確保 status 是有效的類型 const validStatus = status as 'NEW' | 'DOING' | 'BLOCKED' | 'DONE'; // 使用 API 更新單個待辦事項的狀態 await todosApi.updateTodo(todoId, { status: validStatus }); // 更新本地狀態 setTodos(prevTodos => prevTodos.map(todo => todo.id === todoId ? { ...todo, status: validStatus, completed_at: validStatus === 'DONE' ? new Date().toISOString() : undefined } : todo ) ); console.log(`待辦事項 ${todoId} 狀態已更新為 ${status}`); } catch (error) { console.error('狀態更新失敗:', error); } }; return ( {/* 標題區域 */} 待辦清單 {getFilterModeLabel(filterMode)} · {filteredTodos.length} 項目 {selectedTodos.length > 0 && ( )} {/* 工具列 */} {/* 左側工具 */} {/* 視圖切換 */} setViewMode('list')} sx={{ backgroundColor: viewMode === 'list' ? 'primary.main' : 'transparent', color: viewMode === 'list' ? 'white' : 'text.secondary', '&:hover': { backgroundColor: viewMode === 'list' ? 'primary.dark' : 'action.hover', }, }} > setViewMode('calendar')} sx={{ backgroundColor: viewMode === 'calendar' ? 'primary.main' : 'transparent', color: viewMode === 'calendar' ? 'white' : 'text.secondary', '&:hover': { backgroundColor: viewMode === 'calendar' ? 'primary.dark' : 'action.hover', }, }} > {/* 篩選器切換 */} {(['all', 'created', 'responsible', 'following'] as FilterMode[]).map((mode) => ( setFilterMode(mode)} sx={{ fontSize: '0.75rem', fontWeight: filterMode === mode ? 600 : 400, '&:hover': { transform: 'translateY(-1px)', }, }} /> ))} {/* 右側工具 */} setShowSearch(!showSearch)} sx={{ color: showSearch ? 'primary.main' : 'text.secondary', backgroundColor: showSearch ? (actualTheme === 'dark' ? 'rgba(59, 130, 246, 0.1)' : 'rgba(59, 130, 246, 0.1)') : 'transparent', }} > setShowFilters(!showFilters)} sx={{ color: showFilters ? 'primary.main' : 'text.secondary', backgroundColor: showFilters ? (actualTheme === 'dark' ? 'rgba(59, 130, 246, 0.1)' : 'rgba(59, 130, 246, 0.1)') : 'transparent', }} > 0 ? 'primary.main' : 'text.secondary', }} > {/* 搜尋列 */} {showSearch && ( setShowSearch(false)} /> )} {/* 進階篩選 */} {showFilters && ( setShowFilters(false)} onApply={setAppliedFilters} initialFilters={appliedFilters} /> )} {/* 批次操作工具列 */} {selectedTodos.length > 0 && ( setSelectedTodos([])} onBulkStatusChange={handleBulkStatusChange} onBulkComplete={handleBulkComplete} onBulkDelete={handleBulkDelete} /> )} {/* 主要內容區域 */} {loading ? ( {Array.from({ length: 3 }).map((_, index) => ( ))} ) : viewMode === 'list' ? ( ) : ( )} {/* 新增/編輯待辦對話框 */} {/* Excel 匯入對話框 */} setShowExcelImport(false)} onImportComplete={handleTodoCreated} /> ); }; export default TodosPage;