main
vue 146 lines 3.64 KB
Raw
1 <template>
2 <div class="customers-list">
3 <div class="header mb-4 flex items-center justify-between gap-2">
4 <div class="flex items-center gap-4">
5 <div>
6 Total:
7 <strong class="font-mono">{{ totalCustomers }}</strong>
8 </div>
9 <div class="flex items-center gap-2">
10 <span class="text-sm">Sort by:</span>
11 <n-select v-model:value="sortOption" :options="sortOptions" style="width: 120px" size="small" />
12 </div>
13 </div>
14 <div class="flex items-center gap-3">
15 <slot></slot>
16 </div>
17 </div>
18 <n-spin :show="loadingCustomers">
19 <div class="min-h-52">
20 <template v-if="sortedCustomersList.length">
21 <CustomerItem
22 v-for="customer of sortedCustomersList"
23 :key="customer.customer_code"
24 :customer
25 :highlight="customer.customer_code === highlight"
26 :hide-card-actions="loadingCustomers"
27 class="item-appear item-appear-bottom item-appear-005 mb-2"
28 @delete="getCustomers()"
29 />
30 </template>
31 <template v-else>
32 <n-empty v-if="!loadingCustomers" description="No items found" class="h-48 justify-center" />
33 </template>
34 </div>
35 </n-spin>
36 </div>
37 </template>
38
39 <script setup lang="ts">
40 // TODO-FE: refactor
41 import type { Customer } from "@/types/customers.d"
42 import { NEmpty, NSelect, NSpin, useMessage } from "naive-ui"
43 import { computed, nextTick, onBeforeMount, ref, toRefs, watch } from "vue"
44 import Api from "@/api"
45 import CustomerItem from "./CustomerItem.vue"
46
47 const props = defineProps<{ highlight: string | null | undefined; reload?: boolean }>()
48 const emit = defineEmits<{
49 (e: "loaded", value: number): void
50 }>()
51
52 const { highlight, reload } = toRefs(props)
53
54 const message = useMessage()
55 const loadingCustomers = ref(false)
56 const customersList = ref<Customer[]>([])
57 const sortOption = ref<string>("ID")
58
59 const sortOptions = [
60 { label: "ID", value: "ID" },
61 { label: "A-Z", value: "A-Z" }
62 ]
63
64 const totalCustomers = computed<number>(() => {
65 return customersList.value.length || 0
66 })
67
68 const sortedCustomersList = computed<Customer[]>(() => {
69 const customers = [...customersList.value]
70
71 if (sortOption.value === "A-Z") {
72 return customers.sort((a, b) => {
73 const nameA = a.customer_name.toLowerCase()
74 const nameB = b.customer_name.toLowerCase()
75 return nameA.localeCompare(nameB)
76 })
77 }
78
79 // For "ID" option, return original order (by creation/ID)
80 return customers
81 })
82
83 function getCustomers() {
84 loadingCustomers.value = true
85
86 Api.customers
87 .getCustomers()
88 .then(res => {
89 if (res.data.success) {
90 customersList.value = res.data?.customers || []
91 emit("loaded", customersList.value.length || 0)
92 } else {
93 message.warning(res.data?.message || "An error occurred. Please try again later.")
94 }
95 })
96 .catch(err => {
97 message.error(err.response?.data?.message || "An error occurred. Please try again later.")
98 })
99 .finally(() => {
100 loadingCustomers.value = false
101 })
102 }
103
104 function scrollToItem(id: string) {
105 const element = document.getElementById(`customer-${id}`)
106 const scrollContent = document.querySelector("#main > .n-scrollbar > .n-scrollbar-container") as HTMLElement
107
108 if (element && scrollContent) {
109 const wrap: HTMLElement = scrollContent
110 const middle = element.offsetTop - wrap.offsetHeight / 2
111 scrollContent?.scrollTo({ top: middle, behavior: "smooth" })
112 }
113 }
114
115 watch(reload, val => {
116 if (val) {
117 getCustomers()
118 }
119 })
120
121 watch(loadingCustomers, val => {
122 if (!val) {
123 nextTick(() => {
124 setTimeout(() => {
125 if (highlight.value) {
126 scrollToItem(highlight.value)
127 }
128 }, 300)
129 })
130 }
131 })
132
133 watch(highlight, val => {
134 if (val) {
135 nextTick(() => {
136 setTimeout(() => {
137 scrollToItem(val)
138 })
139 })
140 }
141 })
142
143 onBeforeMount(() => {
144 getCustomers()
145 })
146 </script>