modify my task

This commit is contained in:
2026-03-01 11:30:40 +08:00
parent de59b57ee7
commit c8c4c99a6e
14 changed files with 696 additions and 91 deletions
+17
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 }) => {
+9
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
+124 -91
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>