main
vue 123 lines 3.31 KB
Raw
1 <template>
2 <div class="alerts-reports-list @container">
3 <div class="flex items-center justify-between gap-2">
4 <div class="flex items-center gap-2">
5 <div class="flex items-center gap-1 text-sm">
6 <span>Total</span>
7 <code class="py-1">{{ alertsList.length }}</code>
8 </div>
9 </div>
10
11 <div class="flex items-center justify-end gap-2 whitespace-nowrap">
12 <n-select
13 v-model:value="customerFilter"
14 size="small"
15 clearable
16 placeholder="All customers"
17 :options="customerOptions"
18 :show-checkmark="false"
19 class="min-w-40"
20 :disabled="loading"
21 />
22 <n-select
23 v-model:value="sort"
24 size="small"
25 :options="sortOptions"
26 :show-checkmark="false"
27 class="max-w-20"
28 :disabled="loading"
29 />
30 </div>
31 </div>
32
33 <n-spin :show="loading">
34 <div class="my-3 flex min-h-52 flex-col gap-2">
35 <template v-if="sortedList.length">
36 <AlertReportItem
37 v-for="alert of sortedList"
38 :key="`${alert.alert_id}-${alert.report.id}`"
39 :alert-data="alert"
40 class="item-appear item-appear-bottom item-appear-005"
41 />
42 </template>
43 <template v-else>
44 <n-empty
45 v-if="!loading"
46 description="No alerts with AI reports found"
47 class="h-48 justify-center"
48 />
49 </template>
50 </div>
51 </n-spin>
52 </div>
53 </template>
54
55 <script setup lang="ts">
56 import type { AlertWithReport } from "@/types/aiAnalyst.d"
57 import { NEmpty, NSelect, NSpin, useMessage } from "naive-ui"
58 import { computed, onBeforeMount, ref, watch } from "vue"
59 import Api from "@/api"
60 import { getApiErrorMessage } from "@/utils"
61 import AlertReportItem from "./AlertReportItem.vue"
62
63 const message = useMessage()
64 const loading = ref(false)
65 const alertsList = ref<AlertWithReport[]>([])
66 const customerFilter = ref<string | null>(null)
67
68 const sort = ref<"desc" | "asc">("desc")
69 const sortOptions = [
70 { label: "Desc", value: "desc" },
71 { label: "Asc", value: "asc" }
72 ]
73
74 const sortedList = computed(() => {
75 const list = [...alertsList.value]
76 return list.sort((a, b) => {
77 const dateA = new Date(a.report.created_at).getTime()
78 const dateB = new Date(b.report.created_at).getTime()
79 return sort.value === "desc" ? dateB - dateA : dateA - dateB
80 })
81 })
82
83 const customerOptions = computed(() => {
84 const codes = new Set(alertsList.value.map(a => a.customer_code))
85 return Array.from(codes)
86 .sort()
87 .map(code => ({ label: code, value: code }))
88 })
89
90 function getData() {
91 loading.value = true
92
93 Api.aiAnalyst
94 .getAlertsWithReports(customerFilter.value || undefined)
95 .then(res => {
96 if (res.data.success) {
97 alertsList.value = res.data?.alerts || []
98 } else {
99 message.warning(res.data?.message || "An error occurred. Please try again later.")
100 }
101 })
102 .catch(err => {
103 alertsList.value = []
104 message.error(getApiErrorMessage(err) || "An error occurred. Please try again later.")
105 })
106 .finally(() => {
107 loading.value = false
108 })
109 }
110
111 // Refetch only on actual user-driven filter changes. Previously wired via
112 // @update:value on the select, but naive-ui can fire update:value when the
113 // options prop identity changes (our computed returns a new array each time
114 // alertsList updates) — that created a feedback loop: fetch → options ref
115 // changes → update:value fires → fetch again.
116 watch(customerFilter, () => {
117 getData()
118 })
119
120 onBeforeMount(() => {
121 getData()
122 })
123 </script>