412 lines
11 KiB
Vue
412 lines
11 KiB
Vue
<script setup lang="ts">
|
|
import { endOfMonth, format, startOfDay, startOfMonth, subDays, subMonths } from 'date-fns'
|
|
import { VDataTableServer } from 'vuetify/labs/VDataTable'
|
|
import { paginationMeta } from '@api-utils/paginationMeta'
|
|
|
|
const { t } = useI18n()
|
|
|
|
// Data table Headers
|
|
const headers = [
|
|
{ title: 'Id', key: 'meta.id' },
|
|
{ title: t('Order'), key: 'common.orderId' },
|
|
{ title: t('Date'), key: 'common.transaction.transactionDate' },
|
|
{ title: t('Status'), key: 'common.statusData.code' },
|
|
{ title: t('Type'), key: 'common.transaction.transactionTypeDescription' },
|
|
]
|
|
|
|
const selectedStatus = ref()
|
|
|
|
const status = [
|
|
'canceled',
|
|
'complete',
|
|
'fulfilled',
|
|
'intransit',
|
|
'intransit polled',
|
|
'new_order',
|
|
'open',
|
|
'polled',
|
|
'received',
|
|
'unfulfillable',
|
|
]
|
|
|
|
const selectedType = ref()
|
|
|
|
const type = [
|
|
{ id: 'PICKUP', title: 'Pickup Order' },
|
|
{ id: 'DELIVERY', title: 'Delivery Order' },
|
|
{ id: 'SHIPTOSTORE', title: 'Ship-to-Store Order' },
|
|
{ id: 'RETAILPICKUP', title: 'Retail Pickup Order' },
|
|
{ id: 'SHIPFORPICKUP', title: 'Ship-for-Pickup Order' },
|
|
{ id: 'UNKNOWN', title: 'unknown transaction type' },
|
|
]
|
|
|
|
const searchQuery = ref('')
|
|
|
|
// Data table options
|
|
const itemsPerPage = ref(10)
|
|
const page = ref(1)
|
|
const sortBy = ref('request_id')
|
|
const orderBy = ref('desc')
|
|
|
|
const beginDate = ref<Date | null>(null)
|
|
const endDate = ref<Date | null>(null)
|
|
|
|
const sortByMapping = (sortby: string) => {
|
|
const headerMapping: { [key: string]: string } = {
|
|
'meta.id': 'request_id',
|
|
'common.orderId': 'order_id',
|
|
'common.transaction.transactionDate': 'transaction_date',
|
|
'common.statusData.code': 'status',
|
|
'common.transaction.transactionTypeDescription': 'transaction_type_description',
|
|
}
|
|
|
|
return headerMapping[sortby] || sortby
|
|
}
|
|
|
|
const updateOptions = (options: any) => {
|
|
page.value = options.page
|
|
|
|
// updateOptions was called systematically by VDataTableServer with an empty object
|
|
if (options.sortBy[0]?.key) {
|
|
sortBy.value = sortByMapping(options.sortBy[0].key)
|
|
orderBy.value = options.sortBy[0].order
|
|
}
|
|
}
|
|
|
|
const { data: ordersData } = await useApi<any>(createUrl('/obi/order',
|
|
{
|
|
query: {
|
|
q: searchQuery,
|
|
page,
|
|
itemsPerPage,
|
|
sortBy,
|
|
orderBy,
|
|
status: selectedStatus,
|
|
type: selectedType,
|
|
minDate: beginDate,
|
|
maxDate: endDate,
|
|
},
|
|
},
|
|
))
|
|
|
|
const widgetData = computed(() => ordersData.value.statistics)
|
|
const orders = computed(() => ordersData.value.orders)
|
|
const totalOrder = computed(() => ordersData.value.total)
|
|
|
|
watch(beginDate, newBeginDate => {
|
|
if (endDate.value && newBeginDate !== null && newBeginDate > endDate.value)
|
|
endDate.value = newBeginDate
|
|
})
|
|
|
|
watch(endDate, newEndDate => {
|
|
if (beginDate.value && newEndDate !== null && newEndDate < beginDate.value)
|
|
beginDate.value = newEndDate
|
|
})
|
|
|
|
const selectLastMonth = () => {
|
|
beginDate.value = startOfMonth(subMonths(new Date(), 1))
|
|
endDate.value = endOfMonth(subMonths(new Date(), 1))
|
|
}
|
|
|
|
const selectThisMonth = () => {
|
|
const today = new Date()
|
|
const firstDayOfMonth = startOfMonth(today)
|
|
const lastDayOfMonth = endOfMonth(today)
|
|
|
|
beginDate.value = firstDayOfMonth
|
|
endDate.value = today < lastDayOfMonth ? today : lastDayOfMonth
|
|
}
|
|
|
|
const selectLast7Days = () => {
|
|
beginDate.value = startOfDay(subDays(new Date(), 6))
|
|
endDate.value = startOfDay(new Date())
|
|
}
|
|
|
|
const selectToday = () => {
|
|
beginDate.value = startOfDay(new Date())
|
|
endDate.value = startOfDay(new Date())
|
|
}
|
|
|
|
const clearDates = () => {
|
|
beginDate.value = null
|
|
endDate.value = null
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<VBreadcrumbs
|
|
class="px-0 py-2"
|
|
:items="[
|
|
{ title: t('Home'), to: { name: 'root' } },
|
|
{ title: t('Order list') },
|
|
]"
|
|
/>
|
|
<div>
|
|
<VCard class="mb-6">
|
|
<!-- 👉 Widgets -->
|
|
<VCardText>
|
|
<VRow>
|
|
<template
|
|
v-for="(data, id) in widgetData"
|
|
:key="id"
|
|
>
|
|
<VCol
|
|
cols="12"
|
|
sm="6"
|
|
md="2"
|
|
class="px-6"
|
|
>
|
|
<div
|
|
class="d-flex justify-space-between"
|
|
:class="$vuetify.display.xs
|
|
? 'product-widget'
|
|
: $vuetify.display.sm
|
|
? id < 2 ? 'product-widget' : ''
|
|
: ''"
|
|
>
|
|
<div class="d-flex flex-column gap-y-1">
|
|
<h4 class="text-h4">
|
|
{{ data.value }}
|
|
</h4>
|
|
|
|
<VChip
|
|
v-bind="resolveOrderStatus(data.title)"
|
|
variant="tonal"
|
|
label
|
|
size="default"
|
|
/>
|
|
</div>
|
|
|
|
<VAvatar
|
|
variant="tonal"
|
|
rounded
|
|
size="38"
|
|
>
|
|
<VIcon
|
|
:icon="data.icon"
|
|
size="28"
|
|
/>
|
|
</VAvatar>
|
|
</div>
|
|
</VCol>
|
|
<VDivider
|
|
v-if="$vuetify.display.mdAndUp ? id !== widgetData.length - 1
|
|
: $vuetify.display.smAndUp ? id % 2 === 0
|
|
: false"
|
|
vertical
|
|
inset
|
|
length="55"
|
|
/>
|
|
</template>
|
|
</VRow>
|
|
</VCardText>
|
|
</VCard>
|
|
|
|
<VCard
|
|
:title="$t('Filters')"
|
|
class="mb-6"
|
|
>
|
|
<VCardText>
|
|
<VRow>
|
|
<!-- 👉 Select Status -->
|
|
<VCol
|
|
cols="12"
|
|
sm="4"
|
|
>
|
|
<AppSelect
|
|
v-model="selectedStatus"
|
|
:placeholder="$t('Status')"
|
|
:items="status"
|
|
clearable
|
|
clear-icon="tabler-x"
|
|
/>
|
|
</VCol>
|
|
|
|
<!-- 👉 Select Type -->
|
|
<VCol
|
|
cols="12"
|
|
sm="4"
|
|
>
|
|
<AppSelect
|
|
v-model="selectedType"
|
|
:placeholder="$t('Type')"
|
|
:items="type"
|
|
clearable
|
|
clear-icon="tabler-x"
|
|
item-text="title"
|
|
item-value="id"
|
|
/>
|
|
</VCol>
|
|
</VRow>
|
|
|
|
<VRow>
|
|
<VCol
|
|
cols="12"
|
|
sm="4"
|
|
>
|
|
<!-- 👉 Begin Date -->
|
|
<AppDateTimePicker
|
|
v-model="beginDate"
|
|
:placeholder="$t('Date begin')"
|
|
:config="{ altFormat: 'J M Y', altInput: true, dateFormat: 'Ymd' }"
|
|
/>
|
|
</VCol>
|
|
<VCol
|
|
cols="12"
|
|
sm="4"
|
|
>
|
|
<!-- 👉 End Date -->
|
|
<AppDateTimePicker
|
|
v-model="endDate"
|
|
:placeholder="$t('Date end')"
|
|
:config="{ altFormat: 'J M Y', altInput: true, dateFormat: 'Ymd' }"
|
|
/>
|
|
</VCol>
|
|
<VCol
|
|
cols="12"
|
|
sm="4"
|
|
>
|
|
<VBtn
|
|
variant="text"
|
|
@click="selectLastMonth"
|
|
>
|
|
{{ $t('Last month') }}
|
|
</VBtn>
|
|
<VBtn
|
|
variant="text"
|
|
@click="selectThisMonth"
|
|
>
|
|
{{ $t('This month') }}
|
|
</VBtn>
|
|
<VBtn
|
|
variant="text"
|
|
@click="selectLast7Days"
|
|
>
|
|
{{ $t('Last 7 days') }}
|
|
</VBtn>
|
|
<VBtn
|
|
variant="text"
|
|
@click="selectToday"
|
|
>
|
|
{{ $t('Today') }}
|
|
</VBtn>
|
|
<VBtn
|
|
variant="text"
|
|
@click="clearDates"
|
|
>
|
|
{{ $t('All') }}
|
|
</VBtn>
|
|
</VCol>
|
|
</VRow>
|
|
</VCardText>
|
|
</VCard>
|
|
<!-- 👉 Order Table -->
|
|
<VCard>
|
|
<!-- 👉 Filters -->
|
|
<VCardText>
|
|
<div class="d-flex justify-sm-space-between justify-start flex-wrap gap-4">
|
|
<VTextField
|
|
v-model="searchQuery"
|
|
density="compact"
|
|
:placeholder="$t('Search')"
|
|
style=" max-inline-size: 200px; min-inline-size: 200px;"
|
|
/>
|
|
|
|
<div class="d-flex gap-x-4 align-center">
|
|
<AppSelect
|
|
v-model="itemsPerPage"
|
|
style="min-inline-size: 6.25rem;"
|
|
:items="[5, 10, 20, 50, 100]"
|
|
/>
|
|
<VBtn
|
|
variant="tonal"
|
|
color="secondary"
|
|
prepend-icon="tabler-screen-share"
|
|
text="Export"
|
|
append-icon="tabler-chevron-down"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</VCardText>
|
|
|
|
<VDivider />
|
|
|
|
<!-- 👉 Order Table -->
|
|
<VDataTableServer
|
|
v-model:items-per-page="itemsPerPage"
|
|
v-model:page="page"
|
|
v-model:sort-desc="orderBy"
|
|
v-model:sort-by:="sortBy"
|
|
:headers="headers"
|
|
:items="orders"
|
|
:items-length="totalOrder"
|
|
class="text-no-wrap"
|
|
density="compact"
|
|
@update:options="updateOptions"
|
|
>
|
|
<!-- Order ID -->
|
|
<template #item.common.orderId="{ item }">
|
|
<RouterLink
|
|
:to="{ name: 'obi-order-details-id', params: { id: item.meta.id } }"
|
|
class="font-weight-medium"
|
|
>
|
|
#{{ item.common.orderId }}
|
|
</RouterLink>
|
|
</template>
|
|
|
|
<!-- Date -->
|
|
<template #item.common.transaction.transactionDate="{ item }">
|
|
{{ format(item.common.transaction.transactionDate, "EEEE d MMMM yyyy HH:mm:ss") }}
|
|
</template>
|
|
|
|
<!-- Status -->
|
|
<template #item.common.statusData.code="{ item }">
|
|
<VChip
|
|
v-bind="resolveOrderStatus(item.common.statusData.code)"
|
|
label
|
|
/>
|
|
</template>
|
|
|
|
<!-- pagination -->
|
|
<template #bottom>
|
|
<VDivider />
|
|
|
|
<div class="d-flex align-center justify-sm-space-between justify-center flex-wrap gap-3 pa-5 pt-3">
|
|
<p class="text-sm text-disabled mb-0">
|
|
{{ paginationMeta({ page, itemsPerPage }, totalOrder) }}
|
|
</p>
|
|
|
|
<VPagination
|
|
v-model="page"
|
|
:length="Math.ceil(totalOrder / itemsPerPage)"
|
|
:total-visible="$vuetify.display.xs ? 1 : Math.min(Math.ceil(totalOrder / itemsPerPage), 5)"
|
|
>
|
|
<template #prev="slotProps">
|
|
<VBtn
|
|
variant="tonal"
|
|
color="default"
|
|
v-bind="slotProps"
|
|
:icon="false"
|
|
>
|
|
{{ $t('$vuetify.pagination.ariaLabel.previous') }}
|
|
</VBtn>
|
|
</template>
|
|
|
|
<template #next="slotProps">
|
|
<VBtn
|
|
variant="tonal"
|
|
color="default"
|
|
v-bind="slotProps"
|
|
:icon="false"
|
|
>
|
|
{{ $t('$vuetify.pagination.ariaLabel.next') }}
|
|
</VBtn>
|
|
</template>
|
|
</VPagination>
|
|
</div>
|
|
</template>
|
|
</VDataTableServer>
|
|
</VCard>
|
|
</div>
|
|
</template>
|