3RD
This commit is contained in:
@@ -20,10 +20,8 @@ import {
|
||||
ViewList,
|
||||
CalendarViewMonth,
|
||||
FilterList,
|
||||
Sort,
|
||||
Search,
|
||||
SelectAll,
|
||||
MoreVert,
|
||||
} from '@mui/icons-material';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { useTheme } from '@/providers/ThemeProvider';
|
||||
@@ -36,12 +34,14 @@ import SearchBar from '@/components/todos/SearchBar';
|
||||
import TodoDialog from '@/components/todos/TodoDialog';
|
||||
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<ViewMode>('list');
|
||||
const [filterMode, setFilterMode] = useState<FilterMode>('all');
|
||||
const [showFilters, setShowFilters] = useState(false);
|
||||
@@ -64,6 +64,49 @@ const TodosPage = () => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [currentUser, setCurrentUser] = useState<any>(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 () => {
|
||||
@@ -72,9 +115,11 @@ const TodosPage = () => {
|
||||
|
||||
// 檢查是否有有效的 token
|
||||
const token = localStorage.getItem('access_token');
|
||||
console.log('Access token:', token ? 'Found' : 'Not found');
|
||||
if (!token) {
|
||||
console.log('No access token found, skipping API call');
|
||||
console.log('No access token found, redirecting to login');
|
||||
setTodos([]);
|
||||
window.location.href = '/login';
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -94,14 +139,16 @@ const TodosPage = () => {
|
||||
}
|
||||
|
||||
// 獲取待辦事項
|
||||
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) {
|
||||
} catch (error: any) {
|
||||
console.error('Failed to fetch todos:', error);
|
||||
// 如果是認證錯誤,清除 token 並跳轉到登入頁
|
||||
if (error.response?.status === 401) {
|
||||
if (error?.response?.status === 401) {
|
||||
localStorage.removeItem('access_token');
|
||||
localStorage.removeItem('refresh_token');
|
||||
localStorage.removeItem('user');
|
||||
@@ -145,28 +192,33 @@ const TodosPage = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 視圖過濾
|
||||
// 視圖過濾 - 修正:這裡應該是篩選而非直接返回
|
||||
if (currentUser) {
|
||||
switch (filterMode) {
|
||||
case 'created':
|
||||
return todo.creator_ad === currentUser.ad_account;
|
||||
if (todo.creator_ad !== currentUser.ad_account) return false;
|
||||
break;
|
||||
case 'responsible':
|
||||
return todo.responsible_users?.includes(currentUser.ad_account) || false;
|
||||
if (!todo.responsible_users?.includes(currentUser.ad_account)) return false;
|
||||
break;
|
||||
case 'following':
|
||||
return todo.followers?.includes(currentUser.ad_account) || false;
|
||||
if (!todo.followers?.includes(currentUser.ad_account)) return false;
|
||||
break;
|
||||
default:
|
||||
break; // 繼續其他篩選
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -174,52 +226,90 @@ const TodosPage = () => {
|
||||
if (appliedFilters.assignee && currentUser) {
|
||||
switch (appliedFilters.assignee) {
|
||||
case 'me':
|
||||
if (!todo.responsible_users?.includes(currentUser.ad_account)) return false;
|
||||
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) return false;
|
||||
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)) return false;
|
||||
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) return false;
|
||||
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)) return false;
|
||||
if (appliedFilters.dateTo && dueDate > new Date(appliedFilters.dateTo)) return false;
|
||||
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) return false;
|
||||
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') return false;
|
||||
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') return false;
|
||||
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) return false;
|
||||
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 '我建立的';
|
||||
@@ -307,7 +397,7 @@ const TodosPage = () => {
|
||||
setTodos(prevTodos =>
|
||||
prevTodos.map(todo =>
|
||||
selectedTodos.includes(todo.id)
|
||||
? { ...todo, status: 'DONE', completed_at: new Date().toISOString() }
|
||||
? { ...todo, status: 'DONE' as const, completed_at: new Date().toISOString() }
|
||||
: todo
|
||||
)
|
||||
);
|
||||
@@ -351,8 +441,11 @@ const TodosPage = () => {
|
||||
// 單個待辦事項狀態變更處理函數
|
||||
const handleStatusChange = async (todoId: string, status: string) => {
|
||||
try {
|
||||
// 確保 status 是有效的類型
|
||||
const validStatus = status as 'NEW' | 'DOING' | 'BLOCKED' | 'DONE';
|
||||
|
||||
// 使用 API 更新單個待辦事項的狀態
|
||||
await todosApi.updateTodo(todoId, { status });
|
||||
await todosApi.updateTodo(todoId, { status: validStatus });
|
||||
|
||||
// 更新本地狀態
|
||||
setTodos(prevTodos =>
|
||||
@@ -360,8 +453,8 @@ const TodosPage = () => {
|
||||
todo.id === todoId
|
||||
? {
|
||||
...todo,
|
||||
status,
|
||||
completed_at: status === 'DONE' ? new Date().toISOString() : null
|
||||
status: validStatus,
|
||||
completed_at: validStatus === 'DONE' ? new Date().toISOString() : undefined
|
||||
}
|
||||
: todo
|
||||
)
|
||||
@@ -559,12 +652,6 @@ const TodosPage = () => {
|
||||
<FilterList fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title="排序">
|
||||
<IconButton size="small" sx={{ color: 'text.secondary' }}>
|
||||
<Sort fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title="全選">
|
||||
<IconButton
|
||||
@@ -577,12 +664,6 @@ const TodosPage = () => {
|
||||
<SelectAll fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title="更多選項">
|
||||
<IconButton size="small" sx={{ color: 'text.secondary' }}>
|
||||
<MoreVert fontSize="small" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Toolbar>
|
||||
</Card>
|
||||
|
Reference in New Issue
Block a user