main
vue 261 lines 7.01 KB
Raw
1 <template>
2 <n-spin :show="loading" class="flex grow flex-col" content-class="flex grow flex-col">
3 <div class="flex grow flex-col justify-between gap-4">
4 <div class="content-box flex flex-col gap-4 py-3">
5 <div class="flex flex-col gap-4 px-7 sm:flex-row!">
6 <CardKV
7 :color="
8 alert.status === 'OPEN' ? 'danger' : alert.status === 'IN_PROGRESS' ? 'warning' : 'success'
9 "
10 size="lg"
11 class="w-full grow"
12 >
13 <template #key>
14 <div class="flex items-center gap-2">
15 <StatusIcon :status="alert.status" />
16 <span>Status</span>
17 </div>
18 </template>
19 <template #value>
20 <div class="flex">
21 <AlertStatusSwitch
22 v-slot="{ loading: loadingStatus }"
23 :alert
24 @updated="updateAlert($event)"
25 >
26 <div
27 class="flex items-center gap-3"
28 :class="{
29 'cursor-not-allowed': loadingStatus,
30 'cursor-pointer': !loadingStatus
31 }"
32 >
33 <span>{{ alert.status || "n/d" }}</span>
34 <n-spin
35 :size="14"
36 :show="loadingStatus"
37 content-class="flex flex-col justify-center"
38 >
39 <Icon :name="EditIcon" />
40 </n-spin>
41 </div>
42 </AlertStatusSwitch>
43 </div>
44 </template>
45 </CardKV>
46
47 <CardKV :color="alert.assigned_to ? 'success' : undefined" size="lg" class="w-full grow">
48 <template #key>
49 <div class="flex items-center gap-2">
50 <AssigneeIcon :assignee="alert.assigned_to" />
51 <span>Assigned to</span>
52 </div>
53 </template>
54 <template #value>
55 <div class="flex">
56 <AlertAssignUser
57 v-slot="{ loading: loadingAssignee }"
58 :alert
59 @updated="updateAlert($event)"
60 >
61 <div
62 class="flex items-center gap-3"
63 :class="{
64 'cursor-not-allowed': loadingAssignee,
65 'cursor-pointer': !loadingAssignee
66 }"
67 >
68 <span>{{ alert.assigned_to || "n/d" }}</span>
69 <n-spin
70 :size="14"
71 :show="loadingAssignee"
72 content-class="flex flex-col justify-center"
73 >
74 <Icon :name="EditIcon" />
75 </n-spin>
76 </div>
77 </AlertAssignUser>
78 </div>
79 </template>
80 </CardKV>
81 </div>
82
83 <div class="px-7">
84 <CardKV>
85 <template #key>description</template>
86 <template #value>
87 {{ alert.alert_description ?? "-" }}
88 </template>
89 </CardKV>
90 </div>
91
92 <div class="grid-auto-fit-250 grid gap-2 px-7">
93 <CardKV>
94 <template #key>id</template>
95 <template #value>#{{ alert.id }}</template>
96 </CardKV>
97
98 <CardKV>
99 <template #key>source</template>
100 <template #value>
101 {{ alert.source ?? "-" }}
102 </template>
103 </CardKV>
104
105 <CardKV>
106 <template #key>customer code</template>
107 <template #value>
108 <code
109 class="text-primary cursor-pointer"
110 @click="routeCustomer({ code: alert.customer_code }).navigate()"
111 >
112 {{ alert.customer_code }}
113 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
114 </code>
115 </template>
116 </CardKV>
117
118 <CardKV>
119 <template #key>assets</template>
120 <template #value>
121 {{ alert.assets.length }}
122 </template>
123 </CardKV>
124
125 <CardKV>
126 <template #key>comments</template>
127 <template #value>
128 {{ alert.comments.length }}
129 </template>
130 </CardKV>
131
132 <CardKV>
133 <template #key>tags</template>
134 <template #value>
135 <AlertTags :alert @updated="updateAlert" />
136 </template>
137 </CardKV>
138 </div>
139 </div>
140
141 <div class="footer-box bg-secondary flex items-center gap-2 px-7 py-4">
142 <AlertCreateCaseButton v-if="!linkedCases.length" :alert @updated="updateAlert" />
143
144 <AlertMergeCaseButton v-if="!linkedCases.length" :alerts="[alert]" @updated="updateAlert" />
145
146 <div v-if="linkedCases.length" class="flex flex-wrap items-center gap-3">
147 <span>Linked Cases:</span>
148 <AlertLinkedCases :alert @updated="updateAlert($event)" />
149 </div>
150
151 <div class="grow"></div>
152
153 <n-button type="primary" secondary :loading="investigating" @click="handleInvestigate()">
154 <template #icon>
155 <Icon name="carbon:machine-learning-model" />
156 </template>
157 Investigate with AI Analyst
158 </n-button>
159
160 <n-button type="error" secondary @click="handleDelete()">
161 <template #icon>
162 <Icon :name="TrashIcon" />
163 </template>
164 Delete
165 </n-button>
166 </div>
167 </div>
168 </n-spin>
169 </template>
170
171 <script setup lang="ts">
172 import type { Alert } from "@/types/incidentManagement/alerts.d"
173 import { NButton, NSpin, useDialog, useMessage } from "naive-ui"
174 import { computed, defineAsyncComponent, ref, toRefs } from "vue"
175 import Api from "@/api"
176 import CardKV from "@/components/common/cards/CardKV.vue"
177 import Icon from "@/components/common/Icon.vue"
178 import { useNavigation } from "@/composables/useNavigation"
179 import AssigneeIcon from "../common/AssigneeIcon.vue"
180 import StatusIcon from "../common/StatusIcon.vue"
181 import AlertAssignUser from "./AlertAssignUser.vue"
182 import AlertStatusSwitch from "./AlertStatusSwitch.vue"
183 import AlertTags from "./AlertTags.vue"
184 import { handleDeleteAlert } from "./utils"
185
186 const props = defineProps<{ alert: Alert }>()
187 const emit = defineEmits<{
188 (e: "deleted"): void
189 (e: "updated", value: Alert): void
190 }>()
191 const AlertCreateCaseButton = defineAsyncComponent(() => import("./AlertCreateCaseButton.vue"))
192 const AlertMergeCaseButton = defineAsyncComponent(() => import("./AlertMergeCaseButton.vue"))
193 const AlertLinkedCases = defineAsyncComponent(() => import("./AlertLinkedCases.vue"))
194
195 const { alert } = toRefs(props)
196
197 const TrashIcon = "carbon:trash-can"
198 const LinkIcon = "carbon:launch"
199 const EditIcon = "uil:edit-alt"
200
201 const { routeCustomer } = useNavigation()
202 const dialog = useDialog()
203 const message = useMessage()
204 const loading = ref(false)
205 const investigating = ref(false)
206 const linkedCases = computed(() => alert.value.linked_cases)
207
208 function updateAlert(updatedAlert: Alert) {
209 emit("updated", updatedAlert)
210 }
211
212 function handleDelete() {
213 if (alert.value) {
214 handleDeleteAlert({
215 alert: alert.value,
216 cbBefore: () => {
217 loading.value = true
218 },
219 cbSuccess: () => {
220 emit("deleted")
221 },
222 cbAfter: () => {
223 loading.value = false
224 },
225 message,
226 dialog
227 })
228 }
229 }
230
231 function handleInvestigate() {
232 if (!alert.value) return
233 investigating.value = true
234
235 Api.talon
236 .investigate({
237 alert_id: alert.value.id,
238 customer_code: alert.value.customer_code,
239 sender: "copilot"
240 })
241 .then(res => {
242 if (res.data.success) {
243 message.success(res.data.message || "Investigation triggered successfully")
244 } else {
245 message.warning(res.data.message || "Failed to trigger investigation")
246 }
247 })
248 .catch(err => {
249 message.error(err.response?.data?.message || "Failed to trigger investigation")
250 })
251 .finally(() => {
252 investigating.value = false
253 })
254 }
255 </script>
256
257 <style lang="scss" scoped>
258 .footer-box {
259 border-top: 1px solid var(--border-color);
260 }
261 </style>