Files
web/app/components/orders/OrdersTable.vue
2026-07-02 08:48:51 +07:00

243 lines
7.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>