Merge department task types into the Task Type management page
Project Settings > Tasks previously only showed the flat custom task type lists, with department-owned task types manageable only from the separate Departments tab. The Asset/Shot Task Type sections now show a merged view (flat + department-owned, tagged with their department), and the Add/Edit/Delete dialog can create a task type directly under a custom department instead of just as a general-purpose flat type.
This commit is contained in:
@@ -2,9 +2,9 @@
|
|||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-lg font-semibold">Custom Task Types</h3>
|
<h3 class="text-lg font-semibold">Task Types</h3>
|
||||||
<p class="text-sm text-muted-foreground mt-1">
|
<p class="text-sm text-muted-foreground mt-1">
|
||||||
Add custom task types beyond the standard types to adapt the pipeline to your project needs
|
Add task types beyond the standard types, either general-purpose or owned by a specific department, to adapt the pipeline to your project needs
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -30,33 +30,30 @@
|
|||||||
|
|
||||||
<!-- Asset Task Types List -->
|
<!-- Asset Task Types List -->
|
||||||
<div class="border rounded-lg divide-y">
|
<div class="border rounded-lg divide-y">
|
||||||
<template v-if="assetTaskTypes.length > 0">
|
<template v-if="assetTaskTypeRows.length > 0">
|
||||||
<div
|
<div
|
||||||
v-for="taskType in assetTaskTypes"
|
v-for="row in assetTaskTypeRows"
|
||||||
:key="taskType"
|
:key="row.name"
|
||||||
class="flex items-center justify-between p-3 hover:bg-muted/50 transition-colors"
|
class="flex items-center justify-between p-3 hover:bg-muted/50 transition-colors"
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<span class="font-medium capitalize">{{ formatTaskTypeName(taskType) }}</span>
|
<span class="font-medium capitalize">{{ formatTaskTypeName(row.name) }}</span>
|
||||||
<Badge v-if="isStandardAssetType(taskType)" variant="secondary">
|
<Badge v-if="row.isStandard" variant="secondary">Standard</Badge>
|
||||||
Standard
|
<Badge v-else variant="outline">Custom</Badge>
|
||||||
</Badge>
|
<Badge v-if="row.department" variant="outline" class="capitalize">{{ formatTaskTypeName(row.department) }}</Badge>
|
||||||
<Badge v-else variant="outline">
|
|
||||||
Custom
|
|
||||||
</Badge>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!isStandardAssetType(taskType)" class="flex items-center gap-2">
|
<div v-if="!row.isStandard" class="flex items-center gap-2">
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@click="openEditDialog('asset', taskType)"
|
@click="openEditDialog('asset', row.name, row.department)"
|
||||||
>
|
>
|
||||||
<Pencil class="h-4 w-4" />
|
<Pencil class="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@click="handleDelete('asset', taskType)"
|
@click="handleDelete('asset', row.name, row.department)"
|
||||||
>
|
>
|
||||||
<Trash2 class="h-4 w-4 text-destructive" />
|
<Trash2 class="h-4 w-4 text-destructive" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -86,33 +83,30 @@
|
|||||||
|
|
||||||
<!-- Shot Task Types List -->
|
<!-- Shot Task Types List -->
|
||||||
<div class="border rounded-lg divide-y">
|
<div class="border rounded-lg divide-y">
|
||||||
<template v-if="shotTaskTypes.length > 0">
|
<template v-if="shotTaskTypeRows.length > 0">
|
||||||
<div
|
<div
|
||||||
v-for="taskType in shotTaskTypes"
|
v-for="row in shotTaskTypeRows"
|
||||||
:key="taskType"
|
:key="row.name"
|
||||||
class="flex items-center justify-between p-3 hover:bg-muted/50 transition-colors"
|
class="flex items-center justify-between p-3 hover:bg-muted/50 transition-colors"
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<span class="font-medium capitalize">{{ formatTaskTypeName(taskType) }}</span>
|
<span class="font-medium capitalize">{{ formatTaskTypeName(row.name) }}</span>
|
||||||
<Badge v-if="isStandardShotType(taskType)" variant="secondary">
|
<Badge v-if="row.isStandard" variant="secondary">Standard</Badge>
|
||||||
Standard
|
<Badge v-else variant="outline">Custom</Badge>
|
||||||
</Badge>
|
<Badge v-if="row.department" variant="outline" class="capitalize">{{ formatTaskTypeName(row.department) }}</Badge>
|
||||||
<Badge v-else variant="outline">
|
|
||||||
Custom
|
|
||||||
</Badge>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!isStandardShotType(taskType)" class="flex items-center gap-2">
|
<div v-if="!row.isStandard" class="flex items-center gap-2">
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@click="openEditDialog('shot', taskType)"
|
@click="openEditDialog('shot', row.name, row.department)"
|
||||||
>
|
>
|
||||||
<Pencil class="h-4 w-4" />
|
<Pencil class="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@click="handleDelete('shot', taskType)"
|
@click="handleDelete('shot', row.name, row.department)"
|
||||||
>
|
>
|
||||||
<Trash2 class="h-4 w-4 text-destructive" />
|
<Trash2 class="h-4 w-4 text-destructive" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -134,9 +128,9 @@
|
|||||||
{{ dialogMode === 'add' ? 'Add' : 'Edit' }} {{ dialogCategory === 'asset' ? 'Asset' : 'Shot' }} Task Type
|
{{ dialogMode === 'add' ? 'Add' : 'Edit' }} {{ dialogCategory === 'asset' ? 'Asset' : 'Shot' }} Task Type
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
{{ dialogMode === 'add'
|
{{ dialogMode === 'add'
|
||||||
? 'Enter a name for the new task type. Use lowercase letters, numbers, and underscores only.'
|
? 'Enter a name for the new task type. Use lowercase letters, numbers, and underscores only.'
|
||||||
: 'Update the task type name. This will update all existing tasks using this type.'
|
: 'Update the task type name. This will update all existing tasks using this type.'
|
||||||
}}
|
}}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
@@ -155,7 +149,25 @@
|
|||||||
{{ validationError }}
|
{{ validationError }}
|
||||||
</p>
|
</p>
|
||||||
<p v-else class="text-sm text-muted-foreground">
|
<p v-else class="text-sm text-muted-foreground">
|
||||||
3-50 characters, lowercase alphanumeric with underscores only
|
{{ dialogDepartment ? '2-50' : '3-50' }} characters, lowercase alphanumeric with underscores only
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="dialogMode === 'add'" class="space-y-2">
|
||||||
|
<Label>Department (optional)</Label>
|
||||||
|
<Select :model-value="dialogDepartment || 'none'" @update:model-value="(value) => { dialogDepartment = value === 'none' ? '' : (value as string); validateTaskTypeName() }">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="None (general purpose)" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="none">None (general purpose)</SelectItem>
|
||||||
|
<SelectItem v-for="department in dialogCustomDepartments" :key="department.name" :value="department.name">
|
||||||
|
{{ formatTaskTypeName(department.name) }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<p class="text-sm text-muted-foreground">
|
||||||
|
Assign this task type to a custom department so it's owned by that department, or leave it general-purpose.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -173,14 +185,15 @@
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<!-- Delete Confirmation Dialog -->
|
<!-- Delete Confirmation Dialog -->
|
||||||
<AlertDialog
|
<AlertDialog
|
||||||
:open="isDeleteDialogOpen"
|
:open="isDeleteDialogOpen"
|
||||||
@update:open="(open) => {
|
@update:open="(open) => {
|
||||||
isDeleteDialogOpen = open
|
isDeleteDialogOpen = open
|
||||||
if (!open && !isDeleting.value) {
|
if (!open && !isDeleting.value) {
|
||||||
// Only clear values when dialog closes and we're not in the middle of deleting
|
// Only clear values when dialog closes and we're not in the middle of deleting
|
||||||
taskTypeToDelete = ''
|
taskTypeToDelete = ''
|
||||||
categoryToDelete = ''
|
categoryToDelete = ''
|
||||||
|
departmentToDelete = ''
|
||||||
deleteError = ''
|
deleteError = ''
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
@@ -218,6 +231,7 @@ import { Badge } from '@/components/ui/badge'
|
|||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input'
|
||||||
import { Label } from '@/components/ui/label'
|
import { Label } from '@/components/ui/label'
|
||||||
import { Separator } from '@/components/ui/separator'
|
import { Separator } from '@/components/ui/separator'
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -237,6 +251,8 @@ import {
|
|||||||
AlertDialogTitle,
|
AlertDialogTitle,
|
||||||
} from '@/components/ui/alert-dialog'
|
} from '@/components/ui/alert-dialog'
|
||||||
import { customTaskTypeService, type AllTaskTypesResponse } from '@/services/customTaskType'
|
import { customTaskTypeService, type AllTaskTypesResponse } from '@/services/customTaskType'
|
||||||
|
import { departmentService, type DepartmentInfo } from '@/services/department'
|
||||||
|
import { useDepartmentsStore } from '@/stores/departments'
|
||||||
import { useToast } from '@/components/ui/toast/use-toast'
|
import { useToast } from '@/components/ui/toast/use-toast'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -250,6 +266,13 @@ const emit = defineEmits<{
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
const { toast } = useToast()
|
const { toast } = useToast()
|
||||||
|
const departmentsStore = useDepartmentsStore()
|
||||||
|
|
||||||
|
interface TaskTypeRow {
|
||||||
|
name: string
|
||||||
|
isStandard: boolean
|
||||||
|
department?: string
|
||||||
|
}
|
||||||
|
|
||||||
// State
|
// State
|
||||||
const isLoading = ref(true)
|
const isLoading = ref(true)
|
||||||
@@ -259,6 +282,8 @@ const taskTypes = ref<AllTaskTypesResponse | null>(null)
|
|||||||
const isDialogOpen = ref(false)
|
const isDialogOpen = ref(false)
|
||||||
const dialogMode = ref<'add' | 'edit'>('add')
|
const dialogMode = ref<'add' | 'edit'>('add')
|
||||||
const dialogCategory = ref<'asset' | 'shot'>('asset')
|
const dialogCategory = ref<'asset' | 'shot'>('asset')
|
||||||
|
const dialogDepartment = ref('')
|
||||||
|
const editingDepartment = ref('')
|
||||||
const taskTypeName = ref('')
|
const taskTypeName = ref('')
|
||||||
const originalTaskTypeName = ref('')
|
const originalTaskTypeName = ref('')
|
||||||
const validationError = ref('')
|
const validationError = ref('')
|
||||||
@@ -268,6 +293,7 @@ const isSaving = ref(false)
|
|||||||
const isDeleteDialogOpen = ref(false)
|
const isDeleteDialogOpen = ref(false)
|
||||||
const taskTypeToDelete = ref('')
|
const taskTypeToDelete = ref('')
|
||||||
const categoryToDelete = ref<'asset' | 'shot'>('asset')
|
const categoryToDelete = ref<'asset' | 'shot'>('asset')
|
||||||
|
const departmentToDelete = ref('')
|
||||||
const deleteError = ref('')
|
const deleteError = ref('')
|
||||||
const isDeleting = ref(false)
|
const isDeleting = ref(false)
|
||||||
|
|
||||||
@@ -277,15 +303,52 @@ const shotTaskTypes = computed(() => taskTypes.value?.shot_task_types || [])
|
|||||||
const standardAssetTypes = computed(() => taskTypes.value?.standard_asset_types || [])
|
const standardAssetTypes = computed(() => taskTypes.value?.standard_asset_types || [])
|
||||||
const standardShotTypes = computed(() => taskTypes.value?.standard_shot_types || [])
|
const standardShotTypes = computed(() => taskTypes.value?.standard_shot_types || [])
|
||||||
|
|
||||||
|
const allDepartments = computed(() => departmentsStore.getProjectDepartments(props.projectId)?.departments || [])
|
||||||
|
const standardDepartmentNames = computed(() => (departmentsStore.getProjectDepartments(props.projectId)?.standard_departments || []).map(d => d.name))
|
||||||
|
const assetDepartments = computed(() => allDepartments.value.filter(d => d.type === 'asset'))
|
||||||
|
const shotDepartments = computed(() => allDepartments.value.filter(d => d.type === 'shot'))
|
||||||
|
const dialogCustomDepartments = computed(() => {
|
||||||
|
const departments = dialogCategory.value === 'asset' ? assetDepartments.value : shotDepartments.value
|
||||||
|
return departments.filter(d => !standardDepartmentNames.value.includes(d.name))
|
||||||
|
})
|
||||||
|
|
||||||
|
function buildRows(flatAll: string[], flatStandard: string[], departments: DepartmentInfo[]): TaskTypeRow[] {
|
||||||
|
const rows = new Map<string, TaskTypeRow>()
|
||||||
|
for (const t of flatAll) {
|
||||||
|
rows.set(t, { name: t, isStandard: flatStandard.includes(t) })
|
||||||
|
}
|
||||||
|
for (const department of departments) {
|
||||||
|
const isDeptStandard = standardDepartmentNames.value.includes(department.name)
|
||||||
|
for (const t of department.task_types) {
|
||||||
|
const existing = rows.get(t)
|
||||||
|
if (existing) {
|
||||||
|
existing.department = department.name
|
||||||
|
existing.isStandard = existing.isStandard || isDeptStandard
|
||||||
|
} else {
|
||||||
|
rows.set(t, { name: t, isStandard: isDeptStandard, department: department.name })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Array.from(rows.values())
|
||||||
|
}
|
||||||
|
|
||||||
|
const assetTaskTypeRows = computed(() => buildRows(assetTaskTypes.value, standardAssetTypes.value, assetDepartments.value))
|
||||||
|
const shotTaskTypeRows = computed(() => buildRows(shotTaskTypes.value, standardShotTypes.value, shotDepartments.value))
|
||||||
|
|
||||||
const isTaskTypeNameValid = computed(() => {
|
const isTaskTypeNameValid = computed(() => {
|
||||||
return taskTypeName.value.length >= 3 && !validationError.value
|
const minLength = dialogDepartment.value ? 2 : 3
|
||||||
|
return taskTypeName.value.length >= minLength && !validationError.value
|
||||||
})
|
})
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
const loadTaskTypes = async () => {
|
const loadTaskTypes = async () => {
|
||||||
try {
|
try {
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
taskTypes.value = await customTaskTypeService.getAllTaskTypes(props.projectId)
|
const [types] = await Promise.all([
|
||||||
|
customTaskTypeService.getAllTaskTypes(props.projectId),
|
||||||
|
departmentsStore.fetchProjectDepartments(props.projectId, true)
|
||||||
|
])
|
||||||
|
taskTypes.value = types
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Failed to load task types:', error)
|
console.error('Failed to load task types:', error)
|
||||||
toast({
|
toast({
|
||||||
@@ -298,65 +361,60 @@ const loadTaskTypes = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const isStandardAssetType = (taskType: string): boolean => {
|
|
||||||
return standardAssetTypes.value.includes(taskType)
|
|
||||||
}
|
|
||||||
|
|
||||||
const isStandardShotType = (taskType: string): boolean => {
|
|
||||||
return standardShotTypes.value.includes(taskType)
|
|
||||||
}
|
|
||||||
|
|
||||||
const formatTaskTypeName = (taskType: string): string => {
|
const formatTaskTypeName = (taskType: string): string => {
|
||||||
return taskType.replace(/_/g, ' ')
|
return taskType.replace(/_/g, ' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
const validateTaskTypeName = () => {
|
const validateTaskTypeName = () => {
|
||||||
const name = taskTypeName.value.trim()
|
const name = taskTypeName.value.trim()
|
||||||
|
const minLength = dialogDepartment.value ? 2 : 3
|
||||||
|
|
||||||
if (name.length === 0) {
|
if (name.length === 0) {
|
||||||
validationError.value = ''
|
validationError.value = ''
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name.length < 3) {
|
if (name.length < minLength) {
|
||||||
validationError.value = 'Task type name must be at least 3 characters'
|
validationError.value = `Task type name must be at least ${minLength} characters`
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name.length > 50) {
|
if (name.length > 50) {
|
||||||
validationError.value = 'Task type name must be at most 50 characters'
|
validationError.value = 'Task type name must be at most 50 characters'
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!/^[a-z0-9_]+$/.test(name)) {
|
if (!/^[a-z0-9_]+$/.test(name)) {
|
||||||
validationError.value = 'Task type name must be lowercase alphanumeric with underscores only'
|
validationError.value = 'Task type name must be lowercase alphanumeric with underscores only'
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for duplicates (only if adding or changing name)
|
// Check for duplicates against the merged (flat + department) rows for this category
|
||||||
if (dialogMode.value === 'add' || name !== originalTaskTypeName.value) {
|
if (dialogMode.value === 'add' || name !== originalTaskTypeName.value) {
|
||||||
const existingTypes = dialogCategory.value === 'asset' ? assetTaskTypes.value : shotTaskTypes.value
|
const existingRows = dialogCategory.value === 'asset' ? assetTaskTypeRows.value : shotTaskTypeRows.value
|
||||||
if (existingTypes.includes(name)) {
|
if (existingRows.some(row => row.name === name)) {
|
||||||
validationError.value = 'A task type with this name already exists'
|
validationError.value = 'A task type with this name already exists'
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
validationError.value = ''
|
validationError.value = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
const openAddDialog = (category: 'asset' | 'shot') => {
|
const openAddDialog = (category: 'asset' | 'shot') => {
|
||||||
dialogMode.value = 'add'
|
dialogMode.value = 'add'
|
||||||
dialogCategory.value = category
|
dialogCategory.value = category
|
||||||
|
dialogDepartment.value = ''
|
||||||
taskTypeName.value = ''
|
taskTypeName.value = ''
|
||||||
originalTaskTypeName.value = ''
|
originalTaskTypeName.value = ''
|
||||||
validationError.value = ''
|
validationError.value = ''
|
||||||
isDialogOpen.value = true
|
isDialogOpen.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const openEditDialog = (category: 'asset' | 'shot', taskType: string) => {
|
const openEditDialog = (category: 'asset' | 'shot', taskType: string, department?: string) => {
|
||||||
dialogMode.value = 'edit'
|
dialogMode.value = 'edit'
|
||||||
dialogCategory.value = category
|
dialogCategory.value = category
|
||||||
|
editingDepartment.value = department || ''
|
||||||
taskTypeName.value = taskType
|
taskTypeName.value = taskType
|
||||||
originalTaskTypeName.value = taskType
|
originalTaskTypeName.value = taskType
|
||||||
validationError.value = ''
|
validationError.value = ''
|
||||||
@@ -365,6 +423,8 @@ const openEditDialog = (category: 'asset' | 'shot', taskType: string) => {
|
|||||||
|
|
||||||
const closeDialog = () => {
|
const closeDialog = () => {
|
||||||
isDialogOpen.value = false
|
isDialogOpen.value = false
|
||||||
|
dialogDepartment.value = ''
|
||||||
|
editingDepartment.value = ''
|
||||||
taskTypeName.value = ''
|
taskTypeName.value = ''
|
||||||
originalTaskTypeName.value = ''
|
originalTaskTypeName.value = ''
|
||||||
validationError.value = ''
|
validationError.value = ''
|
||||||
@@ -372,45 +432,58 @@ const closeDialog = () => {
|
|||||||
|
|
||||||
const handleDialogSave = async () => {
|
const handleDialogSave = async () => {
|
||||||
validateTaskTypeName()
|
validateTaskTypeName()
|
||||||
|
|
||||||
if (!isTaskTypeNameValid.value) {
|
if (!isTaskTypeNameValid.value) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
isSaving.value = true
|
isSaving.value = true
|
||||||
|
|
||||||
if (dialogMode.value === 'add') {
|
if (dialogMode.value === 'add') {
|
||||||
const response = await customTaskTypeService.addCustomTaskType(props.projectId, {
|
if (dialogDepartment.value) {
|
||||||
task_type: taskTypeName.value.trim(),
|
const response = await departmentService.addDepartmentTaskType(props.projectId, dialogDepartment.value, taskTypeName.value.trim())
|
||||||
category: dialogCategory.value
|
departmentsStore.updateProjectDepartments(props.projectId, response)
|
||||||
})
|
} else {
|
||||||
console.log('Add task type response:', response)
|
const response = await customTaskTypeService.addCustomTaskType(props.projectId, {
|
||||||
taskTypes.value = response
|
task_type: taskTypeName.value.trim(),
|
||||||
|
category: dialogCategory.value
|
||||||
|
})
|
||||||
|
taskTypes.value = response
|
||||||
|
}
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: 'Success',
|
title: 'Success',
|
||||||
description: `Task type "${taskTypeName.value}" added successfully`
|
description: `Task type "${taskTypeName.value}" added successfully`
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
const response = await customTaskTypeService.updateCustomTaskType(
|
if (editingDepartment.value) {
|
||||||
props.projectId,
|
const response = await departmentService.renameDepartmentTaskType(
|
||||||
originalTaskTypeName.value,
|
props.projectId,
|
||||||
{
|
editingDepartment.value,
|
||||||
old_name: originalTaskTypeName.value,
|
originalTaskTypeName.value,
|
||||||
new_name: taskTypeName.value.trim(),
|
taskTypeName.value.trim()
|
||||||
category: dialogCategory.value
|
)
|
||||||
}
|
departmentsStore.updateProjectDepartments(props.projectId, response)
|
||||||
)
|
} else {
|
||||||
console.log('Update task type response:', response)
|
const response = await customTaskTypeService.updateCustomTaskType(
|
||||||
taskTypes.value = response
|
props.projectId,
|
||||||
|
originalTaskTypeName.value,
|
||||||
|
{
|
||||||
|
old_name: originalTaskTypeName.value,
|
||||||
|
new_name: taskTypeName.value.trim(),
|
||||||
|
category: dialogCategory.value
|
||||||
|
}
|
||||||
|
)
|
||||||
|
taskTypes.value = response
|
||||||
|
}
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: 'Success',
|
title: 'Success',
|
||||||
description: `Task type updated successfully`
|
description: `Task type updated successfully`
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
emit('updated')
|
emit('updated')
|
||||||
closeDialog()
|
closeDialog()
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -427,9 +500,10 @@ const handleDialogSave = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDelete = (category: 'asset' | 'shot', taskType: string) => {
|
const handleDelete = (category: 'asset' | 'shot', taskType: string, department?: string) => {
|
||||||
taskTypeToDelete.value = taskType
|
taskTypeToDelete.value = taskType
|
||||||
categoryToDelete.value = category
|
categoryToDelete.value = category
|
||||||
|
departmentToDelete.value = department || ''
|
||||||
deleteError.value = ''
|
deleteError.value = ''
|
||||||
isDeleteDialogOpen.value = true
|
isDeleteDialogOpen.value = true
|
||||||
}
|
}
|
||||||
@@ -443,6 +517,7 @@ const confirmDelete = async () => {
|
|||||||
// Capture values immediately before any async operations
|
// Capture values immediately before any async operations
|
||||||
const taskTypeToDeleteLocal = taskTypeToDelete.value
|
const taskTypeToDeleteLocal = taskTypeToDelete.value
|
||||||
const categoryToDeleteLocal = categoryToDelete.value
|
const categoryToDeleteLocal = categoryToDelete.value
|
||||||
|
const departmentToDeleteLocal = departmentToDelete.value
|
||||||
|
|
||||||
try {
|
try {
|
||||||
isDeleting.value = true
|
isDeleting.value = true
|
||||||
@@ -454,35 +529,44 @@ const confirmDelete = async () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await customTaskTypeService.deleteCustomTaskType(
|
if (departmentToDeleteLocal) {
|
||||||
props.projectId,
|
const response = await departmentService.removeDepartmentTaskType(props.projectId, departmentToDeleteLocal, taskTypeToDeleteLocal)
|
||||||
taskTypeToDeleteLocal,
|
departmentsStore.updateProjectDepartments(props.projectId, response)
|
||||||
categoryToDeleteLocal
|
} else {
|
||||||
)
|
const response = await customTaskTypeService.deleteCustomTaskType(
|
||||||
taskTypes.value = response
|
props.projectId,
|
||||||
|
taskTypeToDeleteLocal,
|
||||||
|
categoryToDeleteLocal
|
||||||
|
)
|
||||||
|
taskTypes.value = response
|
||||||
|
}
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: 'Success',
|
title: 'Success',
|
||||||
description: `Task type "${taskTypeToDeleteLocal}" deleted successfully`
|
description: `Task type "${taskTypeToDeleteLocal}" deleted successfully`
|
||||||
})
|
})
|
||||||
|
|
||||||
emit('updated')
|
emit('updated')
|
||||||
|
|
||||||
// Close dialog and clear values
|
// Close dialog and clear values
|
||||||
isDeleteDialogOpen.value = false
|
isDeleteDialogOpen.value = false
|
||||||
taskTypeToDelete.value = ''
|
taskTypeToDelete.value = ''
|
||||||
categoryToDelete.value = ''
|
categoryToDelete.value = 'asset'
|
||||||
|
departmentToDelete.value = ''
|
||||||
deleteError.value = ''
|
deleteError.value = ''
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Failed to delete task type:', error)
|
console.error('Failed to delete task type:', error)
|
||||||
const errorData = error.response?.data
|
const errorData = error.response?.data
|
||||||
|
const detail = errorData?.detail
|
||||||
if (errorData?.task_count) {
|
|
||||||
deleteError.value = `Cannot delete: ${errorData.task_count} task(s) are using this type`
|
if (detail?.task_count) {
|
||||||
|
deleteError.value = `Cannot delete: ${detail.task_count} task(s) are using this type`
|
||||||
|
} else if (typeof detail === 'string') {
|
||||||
|
deleteError.value = detail
|
||||||
} else {
|
} else {
|
||||||
deleteError.value = errorData?.detail || 'Failed to delete task type'
|
deleteError.value = 'Failed to delete task type'
|
||||||
}
|
}
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: 'Error',
|
title: 'Error',
|
||||||
description: deleteError.value,
|
description: deleteError.value,
|
||||||
|
|||||||
Reference in New Issue
Block a user