Files
web/app/components/orders/OrdersTable.vue
2026-06-18 16:22:37 +07:00

250 lines
6.8 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 InvoiceRow from "~/components/orders/InvoiceRow.vue";
import OrdersKanban from "~/components/orders/OrdersKanban.vue";
const props = defineProps({
viewMode: String,
});
const { $getdata, $dayjs } = useNuxtApp();
const { invoices } = inject("orders");
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 paymentStatuses = [
{
id: 1,
name: "Chưa thanh toán",
color: "red",
},
{
id: 2,
name: "Một phần",
color: "yellow",
},
{
id: 3,
name: "Đã thanh toán",
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.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 paymentStatuses"
: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 statuses"
: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>
<InvoiceRow
v-for="invoice in filteredInvoices"
:key="invoice.id"
:invoice="invoice"
/>
</tbody>
</table>
<OrdersKanban
v-else
:orders="filteredInvoices"
:statuses="statuses"
/>
</div>
</div>
</div>
</template>