Files
web/app/components/orders/OrdersTable.vue
2026-05-06 09:34:22 +07:00

638 lines
16 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 OrderRow from "@/components/orders/OrderRow.vue";
import SelectedOrder from "@/components/orders/SelectedOrder.vue";
import { pull } from "es-toolkit";
const props = defineProps({
viewMode: String,
});
const { $dayjs } = useNuxtApp();
const orders = [
{
id: 1,
code: "SO001",
employee: 1,
employee__name: "Trần Thị B",
customer: 1,
customer__name: "Nguyễn Văn A",
customer__phone: "0901234567",
total: "5200000.00",
status: 1,
status__name: "Nháp",
status__color: "yellow",
payment_status: 1,
payment_status__name: "Chưa thanh toán",
payment_status__color: "red",
delivery_status: 1,
delivery_status__name: "Chờ xử lý",
delivery_status__color: "grey",
order__products: [
{
id: 1,
name: "Kem chống nắng SPF 50+",
unit_price: "280000.00",
quantity: 10,
discount: null,
total: "2800000.00",
},
{
id: 2,
name: "Serum Vitamin C 30ml",
unit_price: "450000.00",
quantity: 5,
discount: 0.1,
total: "2025000.00",
},
{
id: 3,
name: "Son dưỡng môi vitamin E",
unit_price: "75000.00",
quantity: 5,
discount: null,
total: "375000.00",
},
],
create_time: "2026-02-11T08:51:04.587660+07:00",
},
{
id: 2,
code: "SO002",
employee: 1,
employee__name: "Trần Thị B",
customer: 3,
customer__name: "Lê Thị C",
customer__phone: "0912345678",
total: "8500000.00",
status: 2,
status__name: "Đã xác nhận",
status__color: "blue",
payment_status: 2,
payment_status__name: "Một phần",
payment_status__color: "yellow",
delivery_status: 2,
delivery_status__name: "Sẵn sàng",
delivery_status__color: "blue",
order__products: [
{
id: 3,
name: "Mặt nạ collagen 10 miếng",
unit_price: "320000.00",
quantity: 15,
discount: 0.05,
total: "4560000.00",
},
{
id: 4,
name: "Toner cân bằng da 200ml",
unit_price: "180000.00",
quantity: 20,
discount: 0.1,
total: "3240000.00",
},
{
id: 5,
name: "Gel rửa mặt làm sạch sâu",
unit_price: "140000.00",
quantity: 5,
discount: null,
total: "700000.00",
},
],
create_time: "2026-04-06T09:10:09.587660+07:00",
},
{
id: 3,
code: "SO003",
employee: 5,
employee__name: "Hoàng Văn E",
customer: 4,
customer__name: "Phạm Văn D",
customer__phone: "0923456703",
total: "12300000.00",
status: 3,
status__name: "Đang giao",
status__color: "orange",
payment_status: 3,
payment_status__name: "Đã thanh toán",
payment_status__color: "green",
delivery_status: 3,
delivery_status__name: "Hoàn thành",
delivery_status__color: "green",
order__products: [
{
id: 6,
name: "Kem dưỡng ẩm ban đêm",
unit_price: "380000.00",
quantity: 20,
discount: 0.05,
total: "7220000.00",
},
{
id: 7,
name: "Phấn nền BB cream",
unit_price: "220000.00",
quantity: 15,
discount: null,
total: "3300000.00",
},
{
id: 8,
name: "Nước tẩy trang 3 trong 1",
unit_price: "195000.00",
quantity: 10,
discount: 0.1,
total: "1755000.00",
},
],
create_time: "2026-04-05T02:33:24.587660+07:00",
},
{
id: 4,
code: "SO004",
employee: 1,
employee__name: "Trần Thị B",
customer: 6,
customer__name: "Vũ Thị F",
customer__phone: "0934835222",
total: "6800000.00",
status: 4,
status__name: "Hoàn thành",
status__color: "green",
payment_status: 3,
payment_status__name: "Đã thanh toán",
payment_status__color: "green",
delivery_status: 3,
delivery_status__name: "Hoàn thành",
delivery_status__color: "green",
order__products: [
{
id: 9,
name: "Dầu gội thảo mộc 500ml",
unit_price: "120000.00",
quantity: 30,
discount: 0.1,
total: "3240000.00",
},
{
id: 10,
name: "Sữa rửa mặt trà xanh 150ml",
unit_price: "150000.00",
quantity: 20,
discount: 0.05,
total: "2850000.00",
},
{
id: 11,
name: "Son dưỡng môi vitamin E",
unit_price: "75000.00",
quantity: 10,
discount: 0.05,
total: "712500.00",
},
],
create_time: "2026-04-04T23:21:11.587660+07:00",
},
{
id: 5,
code: "SO005",
employee: 5,
employee__name: "Hoàng Văn E",
customer: 7,
customer__name: "Đỗ Văn G",
customer__phone: "0945781113",
total: "9400000.00",
status: 2,
status__name: "Đã xác nhận",
status__color: "blue",
payment_status: 1,
payment_status__name: "Chưa thanh toán",
payment_status__color: "red",
delivery_status: 1,
delivery_status__name: "Chờ xử lý",
delivery_status__color: "grey",
order__products: [
{
id: 12,
name: "Kem mắt chống lão hóa",
unit_price: "550000.00",
quantity: 10,
discount: null,
total: "5500000.00",
},
{
id: 13,
name: "Serum Vitamin C 30ml",
unit_price: "450000.00",
quantity: 8,
discount: 0.05,
total: "3420000.00",
},
{
id: 14,
name: "Gel rửa mặt làm sạch sâu",
unit_price: "140000.00",
quantity: 5,
discount: 0.15,
total: "595000.00",
},
],
create_time: "2026-04-07T11:21:46.587660+07:00",
},
{
id: 6,
code: "SO006",
employee: 1,
employee__name: "Trần Thị B",
customer: 8,
customer__name: "Bùi Thị H",
customer__phone: "0933184392",
total: "4200000.00",
status: 1,
status__name: "Nháp",
status__color: "yellow",
payment_status: 1,
payment_status__name: "Chưa thanh toán",
payment_status__color: "red",
delivery_status: 1,
delivery_status__name: "Chờ xử lý",
delivery_status__color: "grey",
order__products: [
{
id: 15,
name: "Toner cân bằng da 200ml",
unit_price: "180000.00",
quantity: 15,
discount: null,
total: "2700000.00",
},
{
id: 16,
name: "Mặt nạ collagen 10 miếng",
unit_price: "320000.00",
quantity: 5,
discount: 0.05,
total: "1520000.00",
},
],
create_time: "2026-04-07T13:17:36.587660+07:00",
},
{
id: 7,
code: "SO007",
employee: 5,
employee__name: "Hoàng Văn E",
customer: 9,
customer__name: "Ngô Văn I",
customer__phone: "0978335172",
total: "15600000.00",
status: 3,
status__name: "Đang giao",
status__color: "orange",
payment_status: 3,
payment_status__name: "Đã thanh toán",
payment_status__color: "green",
delivery_status: 2,
delivery_status__name: "Hoàn thành",
delivery_status__color: "green",
order__products: [
{
id: 17,
name: "Kem chống nắng SPF 50+",
unit_price: "280000.00",
quantity: 30,
discount: 0.1,
total: "7560000.00",
},
{
id: 18,
name: "Phấn nền BB cream",
unit_price: "220000.00",
quantity: 25,
discount: 0.05,
total: "5225000.00",
},
{
id: 19,
name: "Kem dưỡng ẩm ban đêm",
unit_price: "380000.00",
quantity: 8,
discount: 0.05,
total: "2880000.00",
},
],
create_time: "2026-06-04T09:30:12.587660+07:00",
},
{
id: 8,
code: "SO008",
employee: 1,
employee__name: "Trần Thị B",
customer: 11,
customer__name: "Đinh Thị K",
customer__phone: "0922104853",
total: "7900000.00",
status: 2,
status__name: "Đã xác nhận",
status__color: "blue",
payment_status: 2,
payment_status__name: "Một phần",
payment_status__color: "yellow",
delivery_status: 2,
delivery_status__name: "Sẵn sàng",
delivery_status__color: "blue",
order__products: [
{
id: 20,
name: "Nước tẩy trang 3 trong 1",
unit_price: "195000.00",
quantity: 20,
discount: 0.05,
total: "3705000.00",
},
{
id: 21,
name: "Sữa rửa mặt trà xanh 150ml",
unit_price: "150000.00",
quantity: 18,
discount: null,
total: "2700000.00",
},
{
id: 22,
name: "Dầu gội thảo mộc 500ml",
unit_price: "120000.00",
quantity: 13,
discount: 0.05,
total: "1482000.00",
},
],
create_time: "2026-04-06T16:01:22.587660+07:00",
},
];
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 = [
{
id: 1,
name: "Trần Thị B",
},
{
id: 5,
name: "Hoàng Văn E",
},
];
const input = ref();
const dateRange = ref({
from: null,
to: null,
});
const selectedStatuses = ref([]);
const selectedPaymentStatus = ref();
const selectedEmployee = ref();
const filteredOrders = computed(() => {
const filteredByInput = orders.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);
const createTime = $dayjs(order.create_time);
return createTime.isSameOrAfter(from) && createTime.isSameOrBefore(to);
});
return filteredByDates;
});
const selectedOrder = ref(null);
watch(filteredOrders, () => {
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">Phương thức 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.name }}
</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="fixed-grid has-3-cols">
<div class="grid">
<div :class="['cell', selectedOrder ? 'is-col-span-2' : 'is-col-span-3']">
<div class="card is-clipped">
<div class="card-content p-0">
<template v-if="viewMode === 'list'">
<p class="p-5 fs-17 font-semibold is-flex is-align-items-center is-gap-1">
<Icon
name="material-symbols:list-alt-outline-rounded"
:size="22"
/>
<span>Danh sách đơn hàng ({{ filteredOrders.length }})</span>
</p>
<table 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>
<OrderRow
v-for="order in filteredOrders"
:key="order.id"
v-bind="{
order,
selected: order.id === selectedOrder?.id,
}"
@selectOrder="
(id) => {
selectedOrder = filteredOrders.find((order) => order.id === id);
}
"
@unselect="selectedOrder = null"
/>
</tbody>
</table>
</template>
<OrdersKanban
v-else
:orders="filteredOrders"
:statuses="statuses"
@selectOrder="
(id) => {
selectedOrder = filteredOrders.find((order) => order.id === id);
}
"
@unselect="selectedOrder = null"
/>
</div>
</div>
</div>
<SelectedOrder
:order="selectedOrder"
@unselect="selectedOrder = null"
/>
</div>
</div>
</div>
</template>