243 lines
7.0 KiB
Vue
243 lines
7.0 KiB
Vue
<script setup>
|
||
import { pull } from "es-toolkit";
|
||
import { useInvoices } from "~/components/orders/composables/useInvoices";
|
||
import InvoiceRow from "~/components/orders/InvoiceRow.vue";
|
||
import OrdersKanban from "~/components/orders/OrdersKanban.vue";
|
||
|
||
const props = defineProps({
|
||
viewMode: String,
|
||
});
|
||
|
||
const { $getdata, $dayjs } = useNuxtApp();
|
||
const { invoices, isPending } = useInvoices();
|
||
const store = useStore();
|
||
|
||
const statuses = [
|
||
{
|
||
id: 1,
|
||
name: "Nháp",
|
||
color: "yellow",
|
||
},
|
||
{
|
||
id: 2,
|
||
name: "Đã xác nhận",
|
||
color: "blue",
|
||
},
|
||
{
|
||
id: 3,
|
||
name: "Đang giao",
|
||
color: "orange",
|
||
},
|
||
{
|
||
id: 4,
|
||
name: "Hoàn thành",
|
||
color: "green",
|
||
},
|
||
];
|
||
|
||
const employees = ref([]);
|
||
|
||
onMounted(async () => {
|
||
employees.value = await $getdata("Staff");
|
||
});
|
||
|
||
const input = ref();
|
||
const dateRange = ref({
|
||
from: null,
|
||
to: null,
|
||
});
|
||
|
||
const selectedStatuses = ref([]);
|
||
const selectedPaymentStatus = ref();
|
||
const selectedEmployee = ref();
|
||
|
||
const filteredInvoices = computed(() => {
|
||
if (!invoices.value) return [];
|
||
const filteredByInput = invoices.value.filter((order) => {
|
||
if (!input.value) return true;
|
||
const values = Object.values(order);
|
||
const strValues = values.filter((v) => typeof v === "string");
|
||
return strValues.some((str) => str.toLowerCase().includes(input.value.toLowerCase()));
|
||
});
|
||
|
||
const filteredByStatuses = filteredByInput.filter((order) => {
|
||
if (selectedStatuses.value.length === 0) return true;
|
||
return selectedStatuses.value.includes(order.invoice_status);
|
||
});
|
||
|
||
const filteredByPaymentStatuses = filteredByStatuses.filter((order) => {
|
||
if (!selectedPaymentStatus.value) return true;
|
||
return order.payment_status === selectedPaymentStatus.value;
|
||
});
|
||
|
||
const filteredByEmployees = filteredByPaymentStatuses.filter((order) => {
|
||
if (!selectedEmployee.value) return true;
|
||
return order.employee === selectedEmployee.value;
|
||
});
|
||
|
||
const filteredByDates = filteredByEmployees.filter((order) => {
|
||
if (!dateRange.value) return true;
|
||
const from = $dayjs(dateRange.value.from || 0);
|
||
const to = $dayjs(dateRange.value.to || undefined).endOf("day");
|
||
const createTime = $dayjs(order.create_time);
|
||
return createTime.isSameOrAfter(from) && createTime.isSameOrBefore(to);
|
||
});
|
||
|
||
return filteredByDates;
|
||
});
|
||
|
||
const selectedOrder = ref(null);
|
||
|
||
watch(filteredInvoices, () => {
|
||
selectedOrder.value = null;
|
||
});
|
||
|
||
function toggleStatus(id) {
|
||
if (selectedStatuses.value.includes(id)) {
|
||
selectedStatuses.value = pull(selectedStatuses.value, [id]);
|
||
} else {
|
||
selectedStatuses.value.push(id);
|
||
}
|
||
}
|
||
</script>
|
||
<template>
|
||
<div>
|
||
<div class="card">
|
||
<div class="card-content">
|
||
<div class="is-flex is-gap-2 is-align-items-center">
|
||
<div class="field is-flex-grow-1 m-0">
|
||
<p class="control has-icons-left">
|
||
<input
|
||
v-model="input"
|
||
class="input"
|
||
type="text"
|
||
placeholder="Tìm kiếm mã đơn, khách hàng..."
|
||
/>
|
||
<span class="icon is-small is-left">
|
||
<Icon
|
||
name="material-symbols:search-rounded"
|
||
:size="24"
|
||
/>
|
||
</span>
|
||
</p>
|
||
</div>
|
||
<div class="is-flex is-gap-1 is-align-items-center">
|
||
<!-- Date pickers -->
|
||
<Datepicker
|
||
v-bind="{
|
||
record: dateRange,
|
||
attr: 'from',
|
||
maxdate: new Date(),
|
||
onDate: (e) => (dateRange.from = e),
|
||
}"
|
||
/>
|
||
<span>–</span>
|
||
<Datepicker
|
||
v-bind="{
|
||
record: dateRange,
|
||
attr: 'to',
|
||
maxdate: new Date(),
|
||
onDate: (e) => (dateRange.to = e),
|
||
}"
|
||
/>
|
||
</div>
|
||
<div class="select">
|
||
<select v-model="selectedPaymentStatus">
|
||
<option :value="undefined">Thanh toán</option>
|
||
<option
|
||
v-for="paymentStatus in store.Payment_Status"
|
||
:key="paymentStatus.id"
|
||
:value="paymentStatus.id"
|
||
>
|
||
{{ paymentStatus.name }}
|
||
</option>
|
||
</select>
|
||
</div>
|
||
<div class="select">
|
||
<select v-model="selectedEmployee">
|
||
<option :value="undefined">Nhân viên</option>
|
||
<option
|
||
v-for="employee in employees"
|
||
:key="employee.id"
|
||
:value="employee.id"
|
||
>
|
||
{{ employee.fullname }}
|
||
</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="is-flex is-gap-1 mt-3">
|
||
<button
|
||
v-for="status in store.Invoice_Status"
|
||
:key="status.id"
|
||
:class="[
|
||
'tag fs-13 is-rounded',
|
||
selectedStatuses.includes(status.id) && `has-background-${status.color}-80`,
|
||
]"
|
||
@click="toggleStatus(status.id)"
|
||
>
|
||
{{ status.name }}
|
||
<Icon
|
||
v-if="selectedStatuses.includes(status.id)"
|
||
name="material-symbols:check-rounded"
|
||
:size="18"
|
||
class="ml-1"
|
||
/>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="card is-clipped">
|
||
<div class="card-content p-0">
|
||
<p class="p-5 icon-text fs-16 font-semibold w-full">
|
||
<span class="icon">
|
||
<Icon
|
||
name="material-symbols:list-alt-outline-rounded"
|
||
:size="22"
|
||
/>
|
||
</span>
|
||
<span>Danh sách đơn hàng ({{ filteredInvoices.length }})</span>
|
||
</p>
|
||
<table
|
||
v-if="viewMode === 'list'"
|
||
class="table is-fullwidth is-hoverable fs-13"
|
||
>
|
||
<thead>
|
||
<tr>
|
||
<th class="font-semibold">Đơn hàng</th>
|
||
<th class="font-semibold">Khách hàng</th>
|
||
<th class="font-semibold has-text-right">Tổng tiền</th>
|
||
<th class="font-semibold has-text-centered">Trạng thái</th>
|
||
<th class="font-semibold">Thanh toán</th>
|
||
<th class="font-semibold">Giao hàng</th>
|
||
<th class="font-semibold">Ngày tạo</th>
|
||
<th class="font-semibold">Thao tác</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr v-if="isPending">
|
||
<td colspan="8">
|
||
<Icon
|
||
name="svg-spinners:180-ring-with-bg"
|
||
:size="20"
|
||
class="has-text-primary is-block mx-auto"
|
||
/>
|
||
</td>
|
||
</tr>
|
||
<InvoiceRow
|
||
v-for="invoice in filteredInvoices"
|
||
:key="invoice.id"
|
||
:invoice="invoice"
|
||
/>
|
||
</tbody>
|
||
</table>
|
||
<OrdersKanban
|
||
v-else
|
||
:orders="filteredInvoices"
|
||
:statuses="statuses"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|