Add per-project Department management

Departments are now a customizable per-project list (standard + custom),
matching the existing custom task type/status pattern, instead of a fixed
7-value enum. They're usable directly on tasks (new field, independent of
assignee) and continue to drive team member department roles.
This commit is contained in:
2026-07-22 04:03:33 +08:00
parent 11e1369f2f
commit c09710f4e5
20 changed files with 1220 additions and 75 deletions
@@ -123,7 +123,22 @@
<Badge variant="outline">{{ formatTaskType(task.task_type) }}</Badge>
</p>
</div>
<div></div>
<div>
<Label class="text-muted-foreground">Department</Label>
<div class="mt-1">
<Select :model-value="localDepartment || 'none'" @update:model-value="(value) => handleDepartmentChange(value === 'none' ? '' : (value as string))">
<SelectTrigger class="h-8">
<SelectValue placeholder="None" />
</SelectTrigger>
<SelectContent>
<SelectItem value="none">None</SelectItem>
<SelectItem v-for="department in departmentOptions" :key="department" :value="department">
{{ formatDepartment(department) }}
</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div>
<Label class="text-muted-foreground">Start Date</Label>
<div class="mt-1">
@@ -341,6 +356,7 @@ import TaskAttachments from './TaskAttachments.vue'
import TaskSubmissions from './TaskSubmissions.vue'
import { taskService, type Task, type ProductionNote, type TaskAttachment, type Submission } from '@/services/task'
import { projectService, type ProjectMember } from '@/services/project'
import { useDepartmentsStore } from '@/stores/departments'
import { useAuthStore } from '@/stores/auth'
import { useToast } from '@/components/ui/toast/use-toast'
@@ -358,6 +374,7 @@ const emit = defineEmits<{
const { toast } = useToast()
const authStore = useAuthStore()
const { isCoordinatorOrAdmin } = usePermission()
const departmentsStore = useDepartmentsStore()
const task = ref<Task | null>(null)
const loading = ref(false)
@@ -365,6 +382,7 @@ const error = ref<string | null>(null)
const localStatus = ref('')
const localStartDate = ref('')
const localDeadline = ref('')
const localDepartment = ref('')
const notes = ref<ProductionNote[]>([])
const attachments = ref<TaskAttachment[]>([])
const submissions = ref<Submission[]>([])
@@ -392,6 +410,15 @@ const canSubmitWork = computed(() => {
const canReassign = computed(() => isCoordinatorOrAdmin.value)
const departmentOptions = computed(() => {
if (!task.value) return []
return departmentsStore.getAllDepartmentOptions(task.value.project_id)
})
function formatDepartment(department: string): string {
return department.replace(/_/g, ' ').replace(/^\w/, c => c.toUpperCase())
}
async function loadTask() {
loading.value = true
error.value = null
@@ -400,6 +427,8 @@ async function loadTask() {
localStatus.value = task.value.status
localStartDate.value = task.value.start_date || ''
localDeadline.value = task.value.deadline || ''
localDepartment.value = task.value.department || ''
departmentsStore.fetchProjectDepartments(task.value.project_id)
} catch (err: any) {
console.error('Error loading task:', err)
error.value = err.response?.data?.detail || 'Failed to load task'
@@ -483,6 +512,30 @@ async function handleDateChange(field: 'start_date' | 'deadline', value: string)
}
}
async function handleDepartmentChange(value: string) {
if (!task.value) return
const previous = task.value.department
try {
const updated = await taskService.updateTask(props.taskId, { department: value || null } as any)
task.value.department = updated.department
localDepartment.value = updated.department || ''
emit('taskUpdated')
toast({
title: 'Success',
description: 'Task department updated successfully'
})
} catch (error: any) {
console.error('Error updating department:', error)
localDepartment.value = previous || ''
toast({
title: 'Error',
description: error.response?.data?.detail || 'Failed to update task department',
variant: 'destructive'
})
}
}
async function handleQuickAction(action: 'start' | 'submit') {
if (!task.value) return