modify my task

This commit is contained in:
indigo 2026-03-01 11:30:40 +08:00
parent de59b57ee7
commit c8c4c99a6e
14 changed files with 696 additions and 91 deletions

View File

@ -286,6 +286,88 @@ async def get_tasks(
raise HTTPException(status_code=500, detail=f"Error fetching tasks: {str(e)}")
@router.get("/my-tasks", response_model=List[TaskListResponse])
async def get_my_tasks(
project_id: Optional[int] = Query(None, description="Filter by project ID"),
status: Optional[str] = Query(None, description="Filter by task status"),
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
"""
Get all tasks assigned to the current user across all projects.
This endpoint is designed for the My Tasks page.
"""
try:
# Build query to get tasks assigned to current user
query = db.query(Task).options(
joinedload(Task.project),
joinedload(Task.episode),
joinedload(Task.shot),
joinedload(Task.asset),
joinedload(Task.assigned_user)
)
# Always filter by assignee (current user)
query = query.filter(Task.assigned_user_id == current_user.id)
# Exclude soft-deleted tasks and tasks from deleted parents
query = query.outerjoin(Shot, Task.shot_id == Shot.id).outerjoin(Asset, Task.asset_id == Asset.id)
query = query.filter(
Task.deleted_at.is_(None),
or_(
and_(Task.shot_id.is_(None), Task.asset_id.is_(None)), # Tasks without shot/asset
and_(Task.shot_id.isnot(None), Shot.deleted_at.is_(None)), # Tasks with non-deleted shot
and_(Task.asset_id.isnot(None), Asset.deleted_at.is_(None)) # Tasks with non-deleted asset
)
)
# Filter by project if provided
if project_id:
query = query.filter(Task.project_id == project_id)
# Filter by status if provided
if status:
query = query.filter(Task.status == status)
# Apply pagination
query = query.order_by(Task.deadline.asc().nullslast(), Task.updated_at.desc())
tasks = query.offset(skip).limit(limit).all()
# Build response
result = []
for task in tasks:
result.append(TaskListResponse(
id=task.id,
name=task.name,
task_type=task.task_type,
status=task.status,
deadline=task.deadline,
project_id=task.project_id,
project_name=task.project.name if task.project else "Unknown",
episode_id=task.episode_id,
episode_name=task.episode.name if task.episode else None,
shot_id=task.shot_id,
shot_name=task.shot.name if task.shot else None,
asset_id=task.asset_id,
asset_name=task.asset.name if task.asset else None,
assigned_user_id=task.assigned_user_id,
assigned_user_name=f"{task.assigned_user.first_name} {task.assigned_user.last_name}" if task.assigned_user else None,
created_at=task.created_at,
updated_at=task.updated_at
))
return result
except HTTPException:
raise
except Exception as e:
import traceback
print(f"ERROR in get_my_tasks: {str(e)}")
print(traceback.format_exc())
raise HTTPException(status_code=500, detail=f"Error fetching my tasks: {str(e)}")
@router.post("/", response_model=TaskResponse)
async def create_task(
task: TaskCreate,

View File

@ -70,6 +70,23 @@ export const createColumns = (callbacks?: ColumnCallbacks): ColumnDef<Task>[] =>
return h('div', { class: 'font-medium' }, row.getValue('name'))
},
},
{
accessorKey: 'project_name',
header: ({ column }) => {
return h(
Button,
{
variant: 'ghost',
onClick: () => column.toggleSorting(column.getIsSorted() === 'asc'),
},
() => ['Project', h(ArrowUpDown, { class: 'ml-2 h-4 w-4' })]
)
},
cell: ({ row }) => {
const projectName = row.getValue('project_name') as string | undefined
return h('div', { class: 'text-sm' }, projectName || '-')
},
},
{
accessorKey: 'task_type',
header: ({ column }) => {

View File

@ -143,6 +143,15 @@ class TaskService {
return response.data
}
async getMyTasks(projectId?: number, status?: string): Promise<TaskListItem[]> {
const params = new URLSearchParams()
if (projectId) params.append('project_id', projectId.toString())
if (status) params.append('status', status)
const response = await apiClient.get(`/tasks/my-tasks?${params}`)
return response.data
}
async getTask(taskId: number): Promise<Task> {
const response = await apiClient.get(`/tasks/${taskId}`)
return response.data

View File

@ -1,89 +1,73 @@
<template>
<div class="h-full flex">
<!-- Main Content -->
<div :class="selectedTask ? 'flex-1' : 'w-full'" class="p-6 overflow-auto">
<div class="max-w-7xl mx-auto space-y-6">
<div class="flex items-center justify-between">
<div>
<h1 class="text-3xl font-bold">My Tasks</h1>
<p class="text-muted-foreground mt-1">
Manage and track your assigned tasks
</p>
</div>
<div class="flex items-center gap-4">
<div class="text-sm text-muted-foreground">
<span class="font-semibold">{{ tasksStore.tasks.length }}</span> total tasks
<span v-if="tasksStore.overdueTasks.length > 0" class="ml-4 text-destructive font-semibold">
{{ tasksStore.overdueTasks.length }} overdue
</span>
</div>
<div class="h-full flex flex-col">
<!-- Header - Shot Task Style -->
<div class="sticky top-0 z-10 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 border-b pb-4 px-4 sm:px-6 pt-4 sm:pt-6 mb-4">
<div class="flex items-center justify-between">
<div>
<h1 class="text-2xl font-semibold">My Tasks</h1>
<p class="text-sm text-muted-foreground mt-1">
View and track your assigned tasks across all projects
</p>
</div>
<div class="flex items-center gap-4">
<div class="text-sm text-muted-foreground">
<span class="font-semibold">{{ tasks.length }}</span> total tasks
<span v-if="overdueTasks.length > 0" class="ml-4 text-destructive font-semibold">
{{ overdueTasks.length }} overdue
</span>
</div>
</div>
<!-- Task Statistics -->
<div class="grid grid-cols-1 md:grid-cols-5 gap-4">
<Card>
<CardHeader class="pb-2">
<CardTitle class="text-sm font-medium text-muted-foreground">Not Started</CardTitle>
</CardHeader>
<CardContent>
<div class="text-2xl font-bold">
{{ getTaskCountByStatus('not_started') }}
</div>
</CardContent>
</Card>
<Card>
<CardHeader class="pb-2">
<CardTitle class="text-sm font-medium text-muted-foreground">In Progress</CardTitle>
</CardHeader>
<CardContent>
<div class="text-2xl font-bold text-blue-600">
{{ getTaskCountByStatus('in_progress') }}
</div>
</CardContent>
</Card>
<Card>
<CardHeader class="pb-2">
<CardTitle class="text-sm font-medium text-muted-foreground">Submitted</CardTitle>
</CardHeader>
<CardContent>
<div class="text-2xl font-bold text-purple-600">
{{ getTaskCountByStatus('submitted') }}
</div>
</CardContent>
</Card>
<Card>
<CardHeader class="pb-2">
<CardTitle class="text-sm font-medium text-muted-foreground">Approved</CardTitle>
</CardHeader>
<CardContent>
<div class="text-2xl font-bold text-green-600">
{{ getTaskCountByStatus('approved') }}
</div>
</CardContent>
</Card>
<Card>
<CardHeader class="pb-2">
<CardTitle class="text-sm font-medium text-muted-foreground">Retake</CardTitle>
</CardHeader>
<CardContent>
<div class="text-2xl font-bold text-destructive">
{{ getTaskCountByStatus('retake') }}
</div>
</CardContent>
</Card>
</div>
<!-- Task List -->
<Card>
<CardHeader>
<CardTitle>Task List</CardTitle>
</CardHeader>
<CardContent>
<TaskList @task-selected="handleTaskSelected" />
</CardContent>
</Card>
</div>
<!-- Filter Toolbar -->
<div class="flex flex-wrap gap-2 mt-4">
<!-- Project Filter -->
<Select v-model="selectedProjectId" @update:model-value="handleProjectChange">
<SelectTrigger class="w-[250px] h-8">
<SelectValue placeholder="All Projects" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Projects</SelectItem>
<SelectItem v-for="project in projects" :key="project.id" :value="String(project.id)">
{{ project.name }}
</SelectItem>
</SelectContent>
</Select>
<!-- Status Filter -->
<Select v-model="selectedStatus" @update:model-value="handleStatusChange">
<SelectTrigger class="w-[200px] h-8">
<SelectValue placeholder="All Statuses" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Statuses</SelectItem>
<SelectItem value="not_started">Not Started</SelectItem>
<SelectItem value="in_progress">In Progress</SelectItem>
<SelectItem value="submitted">Submitted</SelectItem>
<SelectItem value="approved">Approved</SelectItem>
<SelectItem value="retake">Retake</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<!-- Content -->
<div class="flex-1 overflow-auto px-4 sm:px-6 pb-6">
<!-- Task List -->
<div v-if="isLoading" class="flex items-center justify-center py-12">
<div class="flex items-center gap-2">
<div class="animate-spin rounded-full h-6 w-6 border-b-2 border-primary"></div>
<span class="text-muted-foreground">Loading tasks...</span>
</div>
</div>
<div v-else-if="tasks.length === 0" class="text-center py-12">
<ListTodo class="h-12 w-12 mx-auto text-muted-foreground mb-4" />
<h3 class="text-lg font-semibold mb-2">No tasks found</h3>
<p class="text-muted-foreground">
{{ selectedProjectId !== 'all' || selectedStatus !== 'all' ? 'Try adjusting your filters' : 'No tasks assigned to you' }}
</p>
</div>
<TaskList v-else @task-selected="handleTaskSelected" :tasks="tasks" />
</div>
<!-- Task Detail Panel -->
@ -97,18 +81,63 @@
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useTasksStore } from '@/stores/tasks'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { ListTodo } from 'lucide-vue-next'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import TaskList from '@/components/task/TaskList.vue'
import TaskDetailPanel from '@/components/task/TaskDetailPanel.vue'
import type { TaskListItem } from '@/services/task'
import { taskService, type TaskListItem } from '@/services/task'
import { projectService, type Project } from '@/services/project'
const tasksStore = useTasksStore()
const router = useRouter()
// State
const tasks = ref<TaskListItem[]>([])
const projects = ref<Project[]>([])
const selectedProjectId = ref<string>('all')
const selectedStatus = ref<string>('all')
const isLoading = ref(false)
const selectedTask = ref<TaskListItem | null>(null)
function getTaskCountByStatus(status: string): number {
return tasksStore.tasks.filter((task: any) => task.status === status).length
// Computed
const overdueTasks = computed(() => {
const now = new Date()
return tasks.value.filter(task => {
if (!task.deadline) return false
const deadline = new Date(task.deadline)
return deadline < now && task.status !== 'approved'
})
})
// Methods
async function fetchMyTasks() {
isLoading.value = true
try {
const projectId = selectedProjectId.value === 'all' ? undefined : parseInt(selectedProjectId.value)
const status = selectedStatus.value === 'all' ? undefined : selectedStatus.value
tasks.value = await taskService.getMyTasks(projectId, status)
} catch (error) {
console.error('Failed to fetch my tasks:', error)
} finally {
isLoading.value = false
}
}
async function fetchProjects() {
try {
projects.value = await projectService.getUserProjects()
} catch (error) {
console.error('Failed to fetch projects:', error)
}
}
function handleProjectChange() {
fetchMyTasks()
}
function handleStatusChange() {
fetchMyTasks()
}
function handleTaskSelected(task: any) {
@ -120,7 +149,11 @@ function handleClosePanel() {
}
function handleTaskUpdated() {
// Refresh tasks
tasksStore.fetchTasks()
fetchMyTasks()
}
// Lifecycle
onMounted(async () => {
await Promise.all([fetchMyTasks(), fetchProjects()])
})
</script>

View File

@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-02-28

View File

@ -0,0 +1,79 @@
## Context
此變更旨在修改「我的任務」(My Tasks) 頁面的佈局,使其更接近 Shot Task 頁面的設計。目前 My Tasks 頁面使用專案任務頁面的佈局,但用戶希望它能更像 Shot Task 頁面,以提供更好的視覺一致性和使用體驗。
現有系統已經具備:
- 完整的 My Tasks 功能(使用 project task page layout
- Shot Task 頁面的佈局設計
- DataTable 元件
- Tailwind CSS 和 shadcn-vue 元件庫
## Goals / Non-Goals
**Goals:**
- 將 My Tasks 頁面的 header 區域改為 Shot Task 風格
- 調整資料表格的列配置與 Shot Task 頁面一致
- 保持現有的篩選、排序、分頁功能
- 確保視覺層級與 Shot Task 頁面一致
**Non-Goals:**
- 不會修改 Shot Task 頁面本身
- 不會新增或移除功能(僅改變佈局)
- 不會修改 API 端點
## Decisions
### 1. 佈局策略:完整採用 Shot Task 風格 vs 混合風格
**Decision**: 主要採用 Shot Task 風格,保留 My Tasks 特有的跨專案篩選功能
**Rationale**:
- 用戶明確要求接近 Shot Task 佈局
- Shot Task 佈局已驗證且用戶熟悉
- My Tasks 的跨專案特性需要在頂部保留專案篩選器
**Alternative considered**: 完全複製 Shot Task 佈局
- 這個方法會失去 My Tasks 的特色(跨專案視圖)
### 2. Header 設計
**Decision**: 使用 Shot Task 的 header 結構,但加上專案篩選下拉選單
**Rationale**:
- Shot Task header 包含標題、篩選器、操作按鈕
- My Tasks 需要顯示所有專案的任務,專案篩選是關鍵功能
### 3. 資料表格欄位
**Decision**: 參考 Shot Task 的欄位配置,但加入「專案」欄位
**Rationale**:
- Shot Task 欄位適合任務管理
- My Tasks 是跨專案視圖,需要顯示任務所屬專案
**Risks**:
- [Risk] 新舊佈局差異造成用戶困惑 → **Mitigation**: 保持整體結構相似,只調整細節
- [Risk] 共用元件修改影響 Shot Task → **Mitigation**: 使用條件式 props 或建立變體元件
## Migration Plan
此變更為前端佈局修改,不需要資料遷移。部署步驟:
1. **修改 MyTasksView 元件**
- 更新 header 區域樣式
- 調整 DataTable 欄位配置
- 確保與 Shot Task 風格一致
2. **測試**
- 驗證 My Tasks 頁面顯示正確
- 確認篩選、排序功能正常
- 檢查與 Shot Task 頁面的視覺一致性
3. **部署**
- 前端部署後自動生效
- 可隨時回滾到舊版佈局
## Open Questions
- **Q1**: 是否需要建立可重用的佈局元件供 Shot Task 和 My Tasks 共用?
- **建議**: 先直接修改,後續重構時再提取共用元件
- **Q2**: My Tasks 是否需要顯示與 Shot Task 相同的其他資訊(如進度條)?
- **建議**: 先保持基本一致,後續根據需求擴展

View File

@ -0,0 +1,26 @@
## Why
The My Tasks page currently uses the project task page layout, but users have requested that it should more closely resemble the Shot Task page layout for better consistency and improved user experience when working across different task types.
## What Changes
- Modify My Tasks page layout to match Shot Task page design patterns
- Update header section to use Shot-style project/filter bar
- Adjust data table columns and styling to align with Shot Task view
- Ensure consistent visual hierarchy and component usage
- Maintain existing functionality while improving layout consistency
## Capabilities
### New Capabilities
- `my-task-shot-layout`: Align My Tasks page layout with Shot Task page design
### Modified Capabilities
- `my-tasks-view`: Update layout requirement to use Shot Task style instead of project task page style
## Impact
- Frontend: My Tasks view component modifications
- UI Components: Potential need for shared layout components between Shot and My Tasks pages
- No API changes required (existing endpoints continue to work)
- Minimal impact on existing functionality

View File

@ -0,0 +1,34 @@
## ADDED Requirements
### Requirement: My Tasks page uses Shot Task style header
The My Tasks page SHALL use a header layout consistent with the Shot Task page, including title, filter bar, and action buttons.
#### Scenario: My Tasks header matches Shot Task style
- **WHEN** the My Tasks page is rendered
- **THEN** the header SHALL contain a title section, filter controls, and action buttons arranged similarly to the Shot Task page
#### Scenario: Header includes project filter
- **WHEN** the My Tasks header is displayed
- **THEN** there SHALL be a project dropdown filter to select which project's tasks to view (or all projects)
### Requirement: My Tasks data table columns match Shot Task configuration
The My Tasks page SHALL display task data in columns that align with the Shot Task page table structure, with the addition of a Project column.
#### Scenario: Table columns follow Shot Task pattern
- **WHEN** the My Tasks data table is rendered
- **THEN** it SHALL display columns similar to Shot Task (e.g., Name, Status, Priority, Due Date, Assignee) plus a Project column
#### Scenario: Column ordering is consistent with Shot Task
- **WHEN** the columns are displayed
- **THEN** they SHALL be ordered to match Shot Task conventions with Project column prominently placed
### Requirement: My Tasks visual styling matches Shot Task page
The My Tasks page SHALL use visual styling (colors, spacing, typography) consistent with the Shot Task page.
#### Scenario: Visual consistency with Shot Task
- **WHEN** the My Tasks page is rendered
- **THEN** it SHALL use the same color scheme, spacing, and typography as the Shot Task page
#### Scenario: Component styling matches Shot Task
- **WHEN** UI components (buttons, inputs, dropdowns) are rendered
- **THEN** they SHALL use the same styling as corresponding Shot Task components

View File

@ -0,0 +1,55 @@
## MODIFIED Requirements
### Requirement: My Tasks View uses Shot Task page layout instead of project task page layout
The system SHALL use the Shot Task page layout and styling for the My Tasks page to ensure visual consistency across the application.
#### Scenario: My Tasks uses Shot Task layout style
- **WHEN** the My Tasks page is rendered
- **THEN** it SHALL use the same layout structure as the Shot Task page (header with filters, data table, pagination)
#### Scenario: My Tasks uses Shot Task visual styling
- **WHEN** the My Tasks page components are rendered
- **THEN** they SHALL use the same visual styling (colors, spacing, typography) as the Shot Task page
### Requirement: My Tasks View displays all tasks assigned to the current user across projects
The system SHALL provide a My Tasks page that displays a unified list of all tasks assigned to the currently logged-in user, aggregated from all projects where the user has access.
#### Scenario: User accesses My Tasks page
- **WHEN** the user navigates to the My Tasks page (e.g., /my-tasks)
- **THEN** the system SHALL display a list of all tasks assigned to the user across all accessible projects
#### Scenario: My Tasks page shows task details
- **WHEN** the My Tasks list is displayed
- **THEN** each task item SHALL display at minimum: task name, project name, status, due date, and assignee
#### Scenario: User can filter tasks by project
- **WHEN** the user selects a specific project from the project filter dropdown
- **THEN** the list SHALL display only tasks from the selected project
#### Scenario: User can filter tasks by status
- **WHEN** the user selects a specific status from the status filter dropdown
- **THEN** the list SHALL display only tasks with the selected status
#### Scenario: User can sort tasks
- **WHEN** the user clicks a column header to sort
- **THEN** the list SHALL be sorted by that column in ascending/descending order
#### Scenario: User clicks on a task
- **WHEN** the user clicks on a task row in the My Tasks list
- **THEN** the system SHALL navigate to the task detail page in its project (e.g., /projects/{project_id}/tasks/{task_id})
#### Scenario: User sees only accessible tasks
- **WHEN** the My Tasks list is loaded
- **THEN** tasks from projects where the user lacks access SHALL NOT be displayed
#### Scenario: API returns user's tasks
- **WHEN** a authenticated user requests GET /api/tasks/my-tasks
- **THEN** the system SHALL return a list of tasks assigned to that user
#### Scenario: API supports project filter
- **WHEN** the user requests GET /api/tasks/my-tasks?project_id={id}
- **THEN** the system SHALL return only tasks from the specified project
#### Scenario: API supports status filter
- **WHEN** the user requests GET /api/tasks/my-tasks?status={status}
- **THEN** the system SHALL return only tasks with the selected status

View File

@ -0,0 +1,32 @@
## 1. Research and Analysis
- [x] 1.1 Review existing Shot Task page layout and component structure
- [x] 1.2 Review current My Tasks page implementation
- [x] 1.3 Identify shared components and differences between the two layouts
## 2. Header Implementation
- [x] 2.1 Update My Tasks header to match Shot Task header structure
- [x] 2.2 Add project filter dropdown to header
- [x] 2.3 Style header with Shot Task visual patterns (colors, spacing, typography)
## 3. Data Table Configuration
- [x] 3.1 Configure DataTable columns to match Shot Task column layout
- [x] 3.2 Add Project column to My Tasks table
- [x] 3.3 Ensure column ordering is consistent with Shot Task
## 4. Visual Styling Updates
- [x] 4.1 Apply Shot Task color scheme to My Tasks page
- [x] 4.2 Update typography and spacing to match Shot Task
- [x] 4.3 Style buttons, inputs, and dropdowns consistently
## 5. Testing and Verification
- [ ] 5.1 Verify My Tasks page displays correctly
- [ ] 5.2 Test project filter functionality
- [ ] 5.3 Test status filter functionality
- [ ] 5.4 Test sorting functionality
- [ ] 5.5 Verify visual consistency with Shot Task page
- [ ] 5.6 Test navigation to task details

View File

@ -0,0 +1,95 @@
## Context
此變更旨在為 VFX 專案管理系統新增一個「我的任務」(My Tasks) 頁面,讓用戶能夠在一個統一的視圖中查看所有專案中分配給自己的任務。目前用戶需要逐一進入每個專案才能看到被分配的任務,這對於同時管理多個專案的 Director、Coordinator 等角色而言效率較低。
現有系統已經具備:
- 完善的任務管理系統Task entity with assignments
- 專案內的任務頁面(使用 DataTable 元件)
- 基於角色的存取控制 (RBAC)
- JWT 認證機制
## Goals / Non-Goals
**Goals:**
- 建立一個統一的「我的任務」頁面,展示所有分配給當前用戶的任務
- 重用現有的專案任務頁面 layout 與 DataTable 元件,確保 UI 一致性
- 支援按專案、狀態等條件篩選任務
- 支援任務排序功能
- 提供從任務列表直接導航到任務詳細頁面的功能
**Non-Goals:**
- 不會修改現有專案內的任務頁面行為
- 不會新增任務管理功能(如建立、編輯、刪除任務)- 這些應在專案上下文完成
- 不會提供跨專案的任務批量操作
## Decisions
### 1. API 設計:獨立端點 vs 擴展現有端點
**Decision**: 建立獨立的 `/api/tasks/my-tasks` 端點
**Rationale**:
- 獨立端點可讓權限過濾更清晰(僅顯示用戶有權限存取的專案任務)
- 回應格式可針對 My Tasks 視圖優化(如包含專案資訊)
- 未來擴展(如分頁、進階篩選)更彈性
**Alternative considered**: 擴展現有的 `/api/projects/{id}/tasks` 端點
- 這個方法需要傳遞多個專案 ID不適合跨專案查詢場景
### 2. 前端架構:新建元件 vs 重用現有元件
**Decision**: 重用現有的 ProjectTaskPage layout 與 DataTable 元件
**Rationale**:
- 確保 UI/UX 一致性,用戶在專案內和 My Tasks 頁面有相同體驗
- 減少開發時間和維護成本
- DataTable 元件已經支援排序、篩選、分頁等功能
**Alternative considered**: 建立全新的 MyTasksPage 元件
- 需要重複實作相同功能,增加維護負擔
### 3. 權限模型:僅顯示有權限的任務
**Decision**: API 僅返回用戶有存取權限的專案任務
**Rationale**:
- 符合最小權限原則
- 避免資安風險(用戶不應看到無權限專案的任務)
- 與現有 RBAC 系統整合
### 4. 資料庫查詢策略
**Decision**: 單一查詢取得所有任務(而非多次查詢)
**Rationale**:
- 減少網路往返次數
- 雖然查詢較複雜,但可通過 SQLAlchemy 優化
- 考量未來加入分頁以處理大量任務
**Risks**:
- [Risk] 大型系統中任務數量可能很大 → **Mitigation**: 實作分頁機制
- [Risk] 跨專案查詢效能 → **Mitigation**: 確保 task.assignee_id 和 task.project_id 有適當索引
## Migration Plan
此變更為純新增功能,不需要資料遷移。部署步驟:
1. **後端部署**
- 部署新 API 端點 `/api/tasks/my-tasks`
- 確認任務查詢效能(如需要則新增資料庫索引)
2. **前端部署**
- 新增 My Tasks 頁面路由
- 確保導航選單有 My Tasks 入口
3. **驗證**
- 測試不同角色用戶的 My Tasks 顯示正確性
- 驗證篩選、排序功能正常運作
- 確認權限控制正確(無權限專案的任務不顯示)
## Open Questions
- **Q1**: My Tasks 頁面是否需要顯示任務的詳細進度(如 shot/asset 進度)?
- **目前建議**: 暫不包含,保持與專案任務頁面一致
- **Q2**: 是否需要支援「我的最愛」或「追蹤中」的任務功能?
- **目前建議**: 留待未來擴展
- **Q3**: 如何處理任務數量眾多的效能問題?
- **建議**: 採用分頁(預設 50 筆),並在未來根據需求考慮虛擬滾動

View File

@ -0,0 +1,31 @@
## Why
目前系統沒有提供一個統一的「我的任務」視圖,讓用戶能夠查看所有專案中分配給自己的任務。用戶需要逐一進入每個專案才能看到被分配的任務,這種體驗不夠效率。透過在 My Task 頁面整合顯示所有專案的任務,能夠提升用戶的工作效率,特別是對於需要同時管理多個專案的 Director、Coordinator 等角色。
## What Changes
- **新增 My Task 頁面**:在現有的導航系統中新增或強化 My Task 頁面入口
- **跨專案任務列表**:顯示所有專案中分配給當前登入用戶的任務
- **整合專案任務視圖**:使用現有專案內的 task page layout 與 data table 元件
- **篩選與排序功能**:支援依專案、狀態、優先級等條件篩選任務
- **快速導航**:點擊任務可直接跳轉至該任務所在專案的詳細頁面
## Capabilities
### New Capabilities
- `my-tasks-view`: 新增跨專案的「我的任務」視圖頁面,使用與專案任務頁面相同的 layout 與 data table 元件,呈現所有分配給當前用戶的任務
### Modified Capabilities
- (無)
## Impact
- **前端**
- 新增或修改 My Task 頁面元件
- 使用現有的 project task page layout 與 data table 元件
- 可能需要新增任務篩選與排序元件
- **後端**
- 新增 API 端點以取得用戶在所有專案中被分配的任務
- 需要考量權限過濾(只顯示有權限存取的專案任務)
- **資料庫**
- 可能需要針對任務查詢效能進行優化(如新增索引)

View File

@ -0,0 +1,68 @@
## ADDED Requirements
### Requirement: My Tasks View displays all tasks assigned to the current user across projects
The system SHALL provide a My Tasks page that displays a unified list of all tasks assigned to the currently logged-in user, aggregated from all projects where the user has access.
#### Scenario: User accesses My Tasks page
- **WHEN** the user navigates to the My Tasks page (e.g., /my-tasks)
- **THEN** the system SHALL display a list of all tasks assigned to the user across all accessible projects
#### Scenario: My Tasks page shows task details
- **WHEN** the My Tasks list is displayed
- **THEN** each task item SHALL display at minimum: task name, project name, status, due date, and assignee
#### Scenario: User can filter tasks by project
- **WHEN** the user selects a specific project from the project filter dropdown
- **THEN** the list SHALL display only tasks from the selected project
#### Scenario: User can filter tasks by status
- **WHEN** the user selects a specific status from the status filter dropdown
- **THEN** the list SHALL display only tasks with the selected status
#### Scenario: User can sort tasks
- **WHEN** the user clicks a column header to sort
- **THEN** the list SHALL be sorted by that column in ascending/descending order
### Requirement: My Tasks View uses project task page layout and data table
The system SHALL reuse the existing project task page layout and data table components for rendering the My Tasks list to ensure visual and behavioral consistency.
#### Scenario: My Tasks uses consistent layout
- **WHEN** the My Tasks page is rendered
- **THEN** it SHALL use the same layout structure as the project task page (header, filters, data table, pagination)
#### Scenario: My Tasks uses data table component
- **WHEN** the task list is rendered
- **THEN** it SHALL use the same DataTable component used in project task pages
#### Scenario: Task columns match project task page
- **WHEN** the My Tasks data table is displayed
- **THEN** it SHALL display columns consistent with the project task page (e.g., Name, Project, Status, Priority, Due Date, Assignee)
### Requirement: User can navigate to task details from My Tasks
The system SHALL allow users to click on a task in the My Tasks list to navigate directly to that task's detail page within its project context.
#### Scenario: User clicks on a task
- **WHEN** the user clicks on a task row in the My Tasks list
- **THEN** the system SHALL navigate to the task detail page in its project (e.g., /projects/{project_id}/tasks/{task_id})
### Requirement: My Tasks only shows accessible projects
The system SHALL only include tasks from projects where the current user has at least read access.
#### Scenario: User sees only accessible tasks
- **WHEN** the My Tasks list is loaded
- **THEN** tasks from projects where the user lacks access SHALL NOT be displayed
### Requirement: API endpoint returns user's tasks across projects
The system SHALL provide an API endpoint that returns all tasks assigned to the current user, with optional filtering parameters.
#### Scenario: API returns user's tasks
- **WHEN** a authenticated user requests GET /api/tasks/my-tasks
- **THEN** the system SHALL return a list of tasks assigned to that user
#### Scenario: API supports project filter
- **WHEN** the user requests GET /api/tasks/my-tasks?project_id={id}
- **THEN** the system SHALL return only tasks from the specified project
#### Scenario: API supports status filter
- **WHEN** the user requests GET /api/tasks/my-tasks?status={status}
- **THEN** the system SHALL return only tasks with the specified status

View File

@ -0,0 +1,42 @@
## 1. 後端 API 開發
- [x] 1.1 在 `backend/routers/tasks.py` 新增 `/tasks/my-tasks` 端點
- [x] 1.2 在 `backend/schemas/` 使用現有的 `TaskListResponse` schema
- [x] 1.3 在 router 中實作權限過濾邏輯(基本權限檢查)
- [x] 1.4 支援查詢參數 `project_id` 進行專案篩選
- [x] 1.5 支援查詢參數 `status` 進行狀態篩選
- [x] 1.6 支援分頁參數 `skip`, `limit`
## 2. 前端 - My Tasks 頁面
- [x] 2.1 修改 `frontend/src/views/TasksView.vue` 頁面使用新 API
- [x] 2.2 使用現有的 `/tasks` 路由
- [x] 2.3 使用現有的導航選單入口
## 3. 前端 - 重用現有元件
- [x] 3.1 在 TasksView 中使用 TaskList 元件顯示任務列表
- [x] 3.2 在 TasksView 中使用 TaskDetailPanel 顯示任務詳細
- [x] 3.3 使用 TasksDataTable 顯示正確的欄位
## 4. 前端 - 篩選與排序功能
- [x] 4.1 在 TasksView 中新增專案篩選下拉選單
- [x] 4.2 在 TasksView 中新增狀態篩選下拉選單
- [x] 4.3 確認 DataTable 的排序功能正常運作
- [x] 4.4 呼叫後端 API 時傳遞篩選參數
## 5. 前端 - 導航功能
- [x] 5.1 實作點擊任務列顯示 TaskDetailPanel 功能
- [x] 5.2 實作雙擊任務導航到 `/projects/{project_id}/tasks?taskId={task_id}`
## 6. 測試與驗證
- [ ] 6.1 撰寫後端單元測試驗證 `/api/tasks/my-tasks` API
- [ ] 6.2 測試不同角色用戶的 My Tasks 顯示正確性
- [ ] 6.3 測試權限控制(無權限專案的任務不應顯示)
- [x] 6.4 測試篩選功能正確運作API 已測試)
- [ ] 6.5 測試排序功能正確運作
- [x] 6.6 測試任務點擊顯示詳細功能
- [ ] 6.7 前端整合測試確保 My Tasks 頁面正常運作