208 lines
6.9 KiB
Vue
208 lines
6.9 KiB
Vue
<template>
|
|
<div class="flex flex-col md:flex-row gap-10">
|
|
<div class="w-full flex flex-col items-center gap-4">
|
|
<UTable :data="usersList" :columns="columns" class="flex-1 w-full"
|
|
:ui="{ root: 'max-w-100wv overflow-auto!' }" />
|
|
<UPagination v-model:page="page" :total="total" :items-per-page="perPage" />
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, computed, watch, resolveComponent, h } from 'vue'
|
|
import { useFetchJson } from '@/composable/useFetchJson'
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
import CategoryMenu from '../inner/CategoryMenu.vue'
|
|
import type { TableColumn } from '@nuxt/ui'
|
|
import type { Customer } from '@/types/user'
|
|
|
|
const router = useRouter()
|
|
const route = useRoute()
|
|
|
|
const perPage = ref(15)
|
|
const page = computed({
|
|
get: () => Number(route.query.p) || 1,
|
|
set: (val: number) => {
|
|
router.push({ query: { ...route.query, p: val } })
|
|
}
|
|
})
|
|
|
|
const sortField = computed({
|
|
get: () => [
|
|
route.query.sort as string | undefined,
|
|
route.query.direction as 'asc' | 'desc' | undefined
|
|
],
|
|
set: ([sort]: [string, 'asc' | 'desc']) => {
|
|
const currentSort = route.query.sort as string | undefined
|
|
const currentDirection = route.query.direction as 'asc' | 'desc' | undefined
|
|
const query = { ...route.query }
|
|
|
|
if (currentSort === sort) {
|
|
if (currentDirection === 'asc') query.direction = 'desc'
|
|
else if (currentDirection === 'desc') {
|
|
delete query.sort
|
|
delete query.direction
|
|
} else {
|
|
query.direction = 'asc'
|
|
query.sort = sort
|
|
}
|
|
} else {
|
|
query.sort = sort
|
|
query.direction = 'asc'
|
|
}
|
|
router.push({ query })
|
|
}
|
|
})
|
|
|
|
const filters = computed<Record<string, string>>({
|
|
get: () => {
|
|
const q = { ...route.query }
|
|
delete q.page
|
|
delete q.sort
|
|
delete q.direction
|
|
return q as Record<string, string>
|
|
},
|
|
set: (val) => {
|
|
const baseQuery = { ...route.query }
|
|
Object.keys(baseQuery).forEach(k => {
|
|
if (!['page', 'sort', 'direction'].includes(k)) delete baseQuery[k]
|
|
})
|
|
router.push({ query: { ...baseQuery, ...val, page: 1 } })
|
|
}
|
|
})
|
|
|
|
function debounce(fn: Function, delay = 400) {
|
|
let t: any
|
|
return (...args: any[]) => {
|
|
clearTimeout(t)
|
|
t = setTimeout(() => fn(...args), delay)
|
|
}
|
|
}
|
|
|
|
const updateFilter = debounce((columnId: string, val: string) => {
|
|
const newFilters = { ...filters.value }
|
|
if (val) newFilters[columnId] = val
|
|
else delete newFilters[columnId]
|
|
filters.value = newFilters
|
|
}, 400)
|
|
|
|
const usersList = ref<Customer[]>([])
|
|
const total = ref(0)
|
|
const loading = ref(true)
|
|
const error = ref<string | null>(null)
|
|
|
|
async function fetchUsersList() {
|
|
loading.value = true
|
|
error.value = null
|
|
|
|
const params = new URLSearchParams(route.query as any).toString()
|
|
const url = `/api/v1/restricted/customer/list?elems=${perPage.value}&${params}`
|
|
|
|
try {
|
|
const res = await useFetchJson<{ items: { items: Customer[] }; count: number }>(url)
|
|
usersList.value = res.items?.items || []
|
|
total.value = res.count || 0
|
|
} catch (e: unknown) {
|
|
error.value = e instanceof Error ? e.message : 'Failed to load users'
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
function getIcon(name: string) {
|
|
if (sortField.value[0] === name) {
|
|
if (sortField.value[1] === 'asc') return 'i-lucide-arrow-up-narrow-wide'
|
|
if (sortField.value[1] === 'desc') return 'i-lucide-arrow-down-wide-narrow'
|
|
}
|
|
return 'i-lucide-arrow-up-down'
|
|
}
|
|
|
|
const UInput = resolveComponent('UInput')
|
|
const UIcon = resolveComponent('UIcon')
|
|
const UButton = resolveComponent('UButton')
|
|
|
|
const columns: TableColumn<Customer>[] = [
|
|
{
|
|
accessorKey: 'user_id',
|
|
header: ({ column }) => h('div', { class: 'flex flex-col gap-1' }, [
|
|
h('div', {
|
|
class: 'flex items-center gap-2 cursor-pointer',
|
|
onClick: () => (sortField.value = ['user_id', 'asc'])
|
|
}, [h('span', 'Client ID'), h(UIcon, { name: getIcon('user_id') })]),
|
|
h(UInput, {
|
|
placeholder: 'Search...',
|
|
modelValue: filters.value[column.id] ?? '',
|
|
'onUpdate:modelValue': (val: string) => updateFilter(column.id, val),
|
|
size: 'xs'
|
|
})
|
|
]),
|
|
cell: ({ row }) => `#${row.getValue('user_id')}`
|
|
},
|
|
{
|
|
accessorKey: 'name',
|
|
header: ({ column }) => h('div', { class: 'flex flex-col gap-1' }, [
|
|
h('div', {
|
|
class: 'flex items-center gap-2 cursor-pointer',
|
|
onClick: () => (sortField.value = ['last_name, first_name', 'asc'])
|
|
}, [h('span', 'Name/Surname'), h(UIcon, { name: getIcon('name') })]),
|
|
h(UInput, {
|
|
placeholder: 'Search...',
|
|
modelValue: filters.value[column.id] ?? '',
|
|
'onUpdate:modelValue': (val: string) => updateFilter(column.id, val),
|
|
size: 'xs'
|
|
})
|
|
]),
|
|
cell: ({ row }) => `${row.original.first_name} ${row.original.last_name}`
|
|
},
|
|
{
|
|
accessorKey: 'email',
|
|
header: ({ column }) => h('div', { class: 'flex flex-col gap-1' }, [
|
|
h('div', {
|
|
class: 'flex items-center gap-2 cursor-pointer',
|
|
onClick: () => (sortField.value = ['email', 'asc'])
|
|
}, [h('span', 'Email'), h(UIcon, { name: getIcon('email') })]),
|
|
h(UInput, {
|
|
placeholder: 'Search...',
|
|
modelValue: filters.value[column.id] ?? '',
|
|
'onUpdate:modelValue': (val: string) => updateFilter(column.id, val),
|
|
size: 'xs'
|
|
})
|
|
]),
|
|
cell: ({ row }) => row.getValue('email')
|
|
},
|
|
{
|
|
accessorKey: 'count',
|
|
header: '',
|
|
cell: ({ row }) => {
|
|
return h(UButton, {
|
|
// onClick: () => {
|
|
// goToProduct(row.original.product_id, row.original.link_rewrite)
|
|
// },
|
|
class: 'cursor-pointer',
|
|
color: 'info',
|
|
variant: 'soft'
|
|
}, () => 'Manage')
|
|
},
|
|
},
|
|
{
|
|
accessorKey: 'profile',
|
|
header: '',
|
|
cell: ({ row }) => {
|
|
const userId = row.original.user_id
|
|
return h(UButton, {
|
|
color: 'info',
|
|
size: 'sm',
|
|
variant: 'soft',
|
|
onClick: () => {
|
|
router.push({
|
|
name: 'customer-management-profile',
|
|
params: { user_id: userId }
|
|
})
|
|
}
|
|
}, () => 'Go to profile')
|
|
}
|
|
}
|
|
]
|
|
|
|
watch(() => route.query, fetchUsersList, { immediate: true })
|
|
</script> |