main
vue 178 lines 4.67 KB
Raw
1 <template>
2 <n-popover trigger="click" :show-arrow="false" to="body" placement="left" @update:show="loadList">
3 <template #trigger>
4 <n-button :size :focusable="false" :loading="!!linkingCaseId || !!linkingAlertId" :secondary>
5 {{ label || "Link Alert" }}
6 </n-button>
7 </template>
8
9 <n-spin :show="loading" class="min-h-50 max-w-100 min-w-50">
10 <div class="mt-1 flex flex-col gap-2">
11 <div class="flex flex-col gap-2">
12 <CardEntity
13 v-for="item in cases"
14 :key="item.id"
15 size="small"
16 embedded
17 main-box-class="p-1!"
18 header-box-class="flex-nowrap!"
19 hoverable
20 >
21 <template #header-main>
22 <div class="px-1">#{{ item.id }} - {{ item.case_name }}</div>
23 </template>
24 <template #header-extra>
25 <n-button
26 type="primary"
27 size="tiny"
28 :loading="linkingCaseId === item.id"
29 :disabled="item.alerts.some(alert => alert.id === props.alertId)"
30 @click="linkAlert(item.id, props.alertId || 0)"
31 >
32 <template #icon>
33 <Icon name="carbon:link" />
34 </template>
35 Link
36 </n-button>
37 </template>
38 </CardEntity>
39
40 <CardEntity
41 v-for="item in alerts"
42 :key="item.id"
43 size="small"
44 embedded
45 main-box-class="p-1!"
46 header-box-class="flex-nowrap!"
47 hoverable
48 >
49 <template #header-main>
50 <div class="px-1">#{{ item.id }} - {{ item.alert_name }}</div>
51 </template>
52 <template #header-extra>
53 <n-button
54 type="primary"
55 size="tiny"
56 :loading="linkingAlertId === item.id"
57 :disabled="item.linked_cases.some(caseData => caseData.id === props.caseId)"
58 @click="linkAlert(props.caseId || 0, item.id)"
59 >
60 <template #icon>
61 <Icon name="carbon:link" />
62 </template>
63 Link
64 </n-button>
65 </template>
66 </CardEntity>
67 </div>
68 <div class="flex justify-end">
69 <n-pagination
70 v-if="pagination.total > pagination.pageSize"
71 v-model:page="pagination.page"
72 :page-size="pagination.pageSize"
73 :item-count="pagination.total"
74 :page-slot="6"
75 size="small"
76 />
77 </div>
78 </div>
79 </n-spin>
80 </n-popover>
81 </template>
82
83 <script setup lang="ts">
84 import type { ButtonSize } from "naive-ui"
85 import type { Alert } from "@/types/alerts"
86 import type { Case } from "@/types/cases"
87 import type { ApiError, Pagination } from "@/types/common"
88 import { useDebounceFn } from "@vueuse/core"
89 import { NButton, NPagination, NPopover, NSpin, useMessage } from "naive-ui"
90 import { ref, watch } from "vue"
91 import Api from "@/api"
92 import CardEntity from "@/components/common/cards/CardEntity.vue"
93 import Icon from "@/components/common/Icon.vue"
94 import { getApiErrorMessage } from "@/utils"
95
96 const props = defineProps<{
97 alertId?: number
98 caseId?: number
99 label?: string
100 secondary?: boolean
101 size?: ButtonSize
102 }>()
103
104 const emit = defineEmits<{
105 (e: "linked", value: number): void
106 }>()
107
108 const linkingCaseId = ref<number | null>(null)
109 const linkingAlertId = ref<number | null>(null)
110 const loading = ref(false)
111 const message = useMessage()
112 const pagination = ref({
113 page: 1,
114 pageSize: 5,
115 total: 0
116 })
117 const cases = ref<Case[]>([])
118 const alerts = ref<Alert[]>([])
119
120 function linkAlert(caseId: number, alertId: number) {
121 if (!caseId || !alertId) {
122 message.error("Case and alert IDs are required")
123 return
124 }
125
126 linkingCaseId.value = caseId
127 linkingAlertId.value = alertId
128
129 Api.cases
130 .linkCaseToAlert(caseId, alertId)
131 .then(res => {
132 if (res.data.success) {
133 emit("linked", alertId)
134 message.success(res.data?.message || "Alert linked to case successfully")
135 } else {
136 message.warning(res.data?.message || "An error occurred. Please try again later.")
137 }
138 })
139 .catch(err => {
140 message.error(getApiErrorMessage(err as ApiError) || "An error occurred. Please try again later.")
141 })
142 .finally(() => {
143 linkingCaseId.value = null
144 linkingAlertId.value = null
145 })
146 }
147
148 const loadList = useDebounceFn(async () => {
149 loading.value = true
150
151 try {
152 const paginationPayload: Pagination = {
153 page: pagination.value.page,
154 pageSize: pagination.value.pageSize,
155 order: "desc"
156 }
157
158 const method = props.alertId ? Api.cases.getCases(paginationPayload) : Api.alerts.getAlerts(paginationPayload)
159
160 const response = await method
161
162 if (props.alertId && "cases" in response.data) {
163 cases.value = response.data?.cases || []
164 }
165 if (props.caseId && "alerts" in response.data) {
166 alerts.value = response.data?.alerts || []
167 }
168
169 pagination.value.total = response.data?.total || 0
170 } catch (err) {
171 message.error(getApiErrorMessage(err as ApiError))
172 } finally {
173 loading.value = false
174 }
175 }, 300)
176
177 watch(() => pagination.value.page, loadList)
178 </script>