main
vue 67 lines 2.32 KB
Raw
1 <template>
2 <div class="feedback-dashboard @container flex flex-col gap-5">
3 <FeedbackDashboardToolbar v-model:customer="customer" v-model:loading="loading" @refresh="loadStats()" />
4
5 <n-spin :show="loading" class="min-h-40">
6 <div v-if="!customer" class="pt-6 text-center">
7 <n-empty description="Pick a customer to see their review feedback" />
8 </div>
9
10 <div v-else-if="stats" class="flex flex-col gap-5">
11 <FeedbackDashboardMetricTiles :stats />
12 <FeedbackDashboardTemplateChoiceDistribution :stats />
13 <FeedbackDashboardTemplateTable :stats />
14 <FeedbackDashboardRecentReviews :stats />
15 </div>
16 </n-spin>
17 </div>
18 </template>
19
20 <script setup lang="ts">
21 import type { AiAnalystReviewStats } from "@/types/aiAnalyst.d"
22 import { NEmpty, NSpin, useMessage } from "naive-ui"
23 import { ref, watch } from "vue"
24 import Api from "@/api"
25 import { getApiErrorMessage } from "@/utils"
26 import FeedbackDashboardMetricTiles from "./FeedbackDashboardMetricTiles.vue"
27 import FeedbackDashboardRecentReviews from "./FeedbackDashboardRecentReviews.vue"
28 import FeedbackDashboardTemplateChoiceDistribution from "./FeedbackDashboardTemplateChoiceDistribution.vue"
29 import FeedbackDashboardTemplateTable from "./FeedbackDashboardTemplateTable.vue"
30 import FeedbackDashboardToolbar from "./FeedbackDashboardToolbar.vue"
31
32 const message = useMessage()
33 const customer = ref<string | null>(null)
34 const loading = ref(false)
35 const stats = ref<AiAnalystReviewStats | null>(null)
36
37 async function loadStats() {
38 if (!customer.value) {
39 stats.value = null
40 return
41 }
42 loading.value = true
43 try {
44 const res = await Api.aiAnalyst.getReviewStats(customer.value, 10)
45 if (res.data.success) {
46 stats.value = res.data
47 } else {
48 message.warning(res.data.message || "Failed to load stats")
49 stats.value = null
50 }
51 } catch (err: unknown) {
52 message.error(getApiErrorMessage(err as never) || "Failed to load stats")
53 stats.value = null
54 } finally {
55 loading.value = false
56 }
57 }
58
59 // Refetch stats on actual customer changes only. Wiring via @update:value on
60 // the select is fragile — naive-ui can fire update:value when the options
61 // prop identity churns, which would loop the stats endpoint. watch() only
62 // fires on real value changes, so programmatic and user-driven picks behave
63 // the same and options churn is ignored.
64 watch(customer, () => {
65 loadStats()
66 })
67 </script>