598 lines
16 KiB
Vue
598 lines
16 KiB
Vue
<script setup>
|
||
import OrderRow from '@/components/orders/OrderRow.vue';
|
||
import SelectedOrder from '@/components/orders/SelectedOrder.vue';
|
||
import { pull } from 'es-toolkit';
|
||
|
||
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 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">
|
||
<div class="card-content p-0">
|
||
<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>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<SelectedOrder :order="selectedOrder" @unselect="selectedOrder = null" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|