main
vue 141 lines 4.4 KB
Raw
1 <template>
2 <n-spin :show="loading" class="flex grow flex-col" content-class="flex grow flex-col">
3 <n-tabs
4 v-if="caseEntity"
5 type="line"
6 animated
7 :tabs-padding="24"
8 class="grow"
9 pane-wrapper-class="flex grow flex-col"
10 >
11 <n-tab-pane name="Overview" tab="Overview" display-directive="show:lazy" class="flex grow flex-col">
12 <div class="pt-1">
13 <CaseOverview :case-data="caseEntity" @updated="updateCase($event)" @deleted="emit('deleted')" />
14 </div>
15 </n-tab-pane>
16 <n-tab-pane name="Alerts" tab="Alerts" display-directive="show:lazy">
17 <div class="flex flex-col gap-2 p-6 pt-3">
18 <template v-if="caseEntity.alerts.length">
19 <AlertItem
20 v-for="alert of caseEntity.alerts"
21 :key="alert.id"
22 :alert-data="alert"
23 embedded
24 @deleted="getCase(caseEntity.id)"
25 @updated="getCase(caseEntity.id)"
26 />
27 </template>
28 <template v-else>
29 <n-empty v-if="!loading" description="No items found" class="h-48 justify-center" />
30 </template>
31 </div>
32 </n-tab-pane>
33 <n-tab-pane name="Tasks" tab="Tasks" display-directive="show:lazy">
34 <div class="p-6 pt-3">
35 <CaseTasksList
36 :case-id="caseEntity.id"
37 :customer-code="caseEntity.customer_code"
38 :can-edit="canEditTasks"
39 :linked-alerts="caseEntity.alerts || []"
40 />
41 </div>
42 </n-tab-pane>
43 <n-tab-pane name="Timeline" tab="Timeline" display-directive="show:lazy">
44 <div class="p-6 pt-3">
45 <CaseTimelineFeed :case-id="caseEntity.id" />
46 </div>
47 </n-tab-pane>
48 <n-tab-pane name="Comments" tab="Comments" display-directive="show:lazy">
49 <div class="p-6 pt-3">
50 <CaseCommentsList
51 :comments="caseEntity.comments || []"
52 :case-id="caseEntity.id"
53 @updated="updateCaseComments($event)"
54 />
55 </div>
56 </n-tab-pane>
57 <n-tab-pane name="Data Store" tab="Data Store" display-directive="show:lazy">
58 <div class="p-6 pt-3">
59 <CaseDataStore :case-id="caseEntity.id" />
60 </div>
61 </n-tab-pane>
62 </n-tabs>
63 </n-spin>
64 </template>
65
66 <script setup lang="ts">
67 import type { Case, CaseComment } from "@/types/incidentManagement/cases.d"
68 import _clone from "lodash/cloneDeep"
69 import { NEmpty, NSpin, NTabPane, NTabs, useMessage } from "naive-ui"
70 import { computed, defineAsyncComponent, onBeforeMount, ref, toRefs } from "vue"
71 import Api from "@/api"
72 import { useAuthStore } from "@/stores/auth"
73 import { AuthUserRole } from "@/types/auth.d"
74
75 const props = defineProps<{
76 caseData?: Case
77 caseId?: number
78 }>()
79 const emit = defineEmits<{
80 (e: "deleted"): void
81 (e: "updated", value: Case): void
82 }>()
83 const CaseOverview = defineAsyncComponent(() => import("./CaseOverview.vue"))
84 const CaseDataStore = defineAsyncComponent(() => import("./CaseDataStore.vue"))
85 const CaseCommentsList = defineAsyncComponent(() => import("./CaseCommentsList.vue"))
86 const CaseTasksList = defineAsyncComponent(() => import("./CaseTasks/CaseTasksList.vue"))
87 const CaseTimelineFeed = defineAsyncComponent(() => import("./CaseTimelineFeed.vue"))
88 const AlertItem = defineAsyncComponent(() => import("../alerts/AlertItem.vue"))
89
90 // Customer-portal users see Tasks + Timeline read-only. The /frontend app is
91 // primarily analyst-facing, but role-conditional rendering keeps it correct
92 // if a customer_user happens to land on this view.
93 const authStore = useAuthStore()
94 const canEditTasks = computed(() => authStore.userRole !== AuthUserRole.CustomerUser)
95
96 const { caseData, caseId } = toRefs(props)
97
98 const message = useMessage()
99 const loading = ref(false)
100 const caseEntity = ref<Case | null>(null)
101
102 function updateCase(updatedCase: Case) {
103 caseEntity.value = updatedCase
104 emit("updated", updatedCase)
105 }
106
107 function updateCaseComments(updatedComments: CaseComment[]) {
108 if (caseEntity.value) {
109 caseEntity.value.comments = updatedComments
110 emit("updated", caseEntity.value)
111 }
112 }
113
114 function getCase(caseId: number) {
115 loading.value = true
116
117 Api.incidentManagement.cases
118 .getCase(caseId)
119 .then(res => {
120 if (res.data.success) {
121 caseEntity.value = res.data?.cases?.[0] || null
122 } else {
123 message.warning(res.data?.message || "An error occurred. Please try again later.")
124 }
125 })
126 .catch(err => {
127 message.error(err.response?.data?.message || "An error occurred. Please try again later.")
128 })
129 .finally(() => {
130 loading.value = false
131 })
132 }
133
134 onBeforeMount(() => {
135 if (caseId.value) {
136 getCase(caseId.value)
137 } else if (caseData.value) {
138 caseEntity.value = _clone(caseData.value)
139 }
140 })
141 </script>