main
vue 133 lines 4.07 KB
Raw
1 <template>
2 <div class="case-tasks-list flex flex-col gap-4">
3 <CaseTasksToolbar :case-id :customer-code :can-edit :tasks :linked-alerts @updated="fetchTasks" />
4
5 <n-spin :show="loading">
6 <div v-if="tasks.length" class="flex flex-col gap-4">
7 <!--
8 One group per originating alert plus a "Case-wide" bucket for
9 orphaned / never-attached tasks (alert_id IS NULL). Group order:
10 alerts in their linked order, case-wide last.
11 -->
12 <CardEntity
13 v-for="group in groups"
14 :key="group.key"
15 embedded
16 size="small"
17 header-box-class="flex flex-wrap items-center gap-2"
18 >
19 <template #header>
20 <div v-if="group.alert" class="flex flex-wrap items-center gap-2">
21 <span class="text-default text-base font-semibold">{{ group.alert.alert_name }}</span>
22 <n-tag size="small" :bordered="false">{{ group.alert.source }}</n-tag>
23 <n-tag size="small" :bordered="false">alert #{{ group.alert.id }}</n-tag>
24 <n-tag size="small" :bordered="false">{{ group.tasks.length }} task(s)</n-tag>
25 </div>
26 <div v-else class="flex flex-wrap items-center gap-2">
27 <span class="text-default text-base font-semibold">Case-wide / general</span>
28 <n-tag size="small" :bordered="false" type="info">not attached to any alert</n-tag>
29 <n-tag size="small" :bordered="false">{{ group.tasks.length }} task(s)</n-tag>
30 </div>
31 </template>
32 <template #mainExtra>
33 <div class="flex flex-col gap-3">
34 <CaseTaskItem
35 v-for="task in group.tasks"
36 :key="task.id"
37 :task
38 :case-id
39 :can-edit
40 @deleted="fetchTasks"
41 @updated="handleTaskUpdated"
42 />
43 </div>
44 </template>
45 </CardEntity>
46 </div>
47 <n-empty v-else-if="!loading" description="No tasks on this case" class="h-32 justify-center" />
48 </n-spin>
49 </div>
50 </template>
51
52 <script setup lang="ts">
53 import type { Alert } from "@/types/incidentManagement/alerts.d"
54 import type { CaseTask } from "@/types/incidentManagement/caseTemplates.d"
55 import { NEmpty, NSpin, NTag, useMessage } from "naive-ui"
56 import { computed, onBeforeMount, ref } from "vue"
57 import Api from "@/api"
58 import CardEntity from "@/components/common/cards/CardEntity.vue"
59 import CaseTaskItem from "./CaseTaskItem.vue"
60 import CaseTasksToolbar from "./CaseTasksToolbar.vue"
61
62 const props = defineProps<{
63 caseId: number
64 customerCode?: string | null
65 canEdit: boolean
66 linkedAlerts?: Alert[]
67 }>()
68
69 const message = useMessage()
70 const tasks = ref<CaseTask[]>([])
71 const loading = ref(false)
72
73 interface TaskGroup {
74 key: string
75 alert: Alert | null
76 tasks: CaseTask[]
77 }
78
79 const groups = computed<TaskGroup[]>(() => {
80 // Index tasks by alert_id so we can attach them to their alert group; tasks
81 // whose alert_id no longer matches a linked alert (orphans) fall through
82 // to the case-wide bucket alongside genuine alert_id=null tasks.
83 const linkedAlerts = props.linkedAlerts || []
84 const linkedIds = new Set(linkedAlerts.map(a => a.id))
85 const byAlert: Record<number, CaseTask[]> = {}
86 const caseWide: CaseTask[] = []
87
88 for (const t of tasks.value) {
89 if (t.alert_id != null && linkedIds.has(t.alert_id)) {
90 if (!byAlert[t.alert_id]) byAlert[t.alert_id] = []
91 byAlert[t.alert_id].push(t)
92 } else {
93 caseWide.push(t)
94 }
95 }
96
97 const out: TaskGroup[] = []
98 for (const a of linkedAlerts) {
99 const groupTasks = byAlert[a.id] || []
100 if (!groupTasks.length) continue
101 out.push({ key: `alert-${a.id}`, alert: a, tasks: groupTasks })
102 }
103 if (caseWide.length) {
104 out.push({ key: "case-wide", alert: null, tasks: caseWide })
105 }
106 return out
107 })
108
109 function fetchTasks() {
110 loading.value = true
111 Api.incidentManagement.caseTemplates
112 .listCaseTasks(props.caseId)
113 .then(res => {
114 if (res.data.success) {
115 tasks.value = res.data.tasks
116 } else {
117 message.warning(res.data.message)
118 }
119 })
120 .catch(err => {
121 message.error(err.response?.data?.message || "Failed to load case tasks")
122 })
123 .finally(() => {
124 loading.value = false
125 })
126 }
127
128 function handleTaskUpdated(task: CaseTask) {
129 tasks.value = tasks.value.map(t => (t.id === task.id ? task : t))
130 }
131
132 onBeforeMount(fetchTasks)
133 </script>