feat: enhance order management with additional filters and pagination support

- Added channel, status, and payment type filters to the order index functionality.
- Updated OrderService to handle new filter parameters in pagination queries.
- Introduced filter definitions and values in the OrderGroupedTable component for improved data filtering.
- Enhanced the frontend to support dynamic filtering options for orders based on the new criteria.
This commit is contained in:
Yoga Pangestu 2026-07-29 08:59:56 +07:00
parent 7dec701770
commit 1f9aba6ba5
5 changed files with 73 additions and 4 deletions

View File

@ -30,9 +30,17 @@ public function index(Request $request): Response
{
$tableQuery = $this->parseDataTableQuery($request);
$tableQuery['channel'] = $request->string('channel')->toString();
$tableQuery['status'] = $request->string('status')->toString();
$tableQuery['payment_type'] = $request->string('payment_type')->toString();
return Inertia::render('admin/manage/orders/Index', [
'orders' => $this->orderService->paginateForIndex($tableQuery, $request->user()),
'filters' => $this->dataTableFilters($tableQuery),
'filters' => $this->dataTableFilters($tableQuery, [
'channel' => $tableQuery['channel'],
'status' => $tableQuery['status'],
'payment_type' => $tableQuery['payment_type'],
]),
]);
}

View File

@ -84,7 +84,10 @@ public function paginateForIndex(array $tableQuery, User $user): LengthAwarePagi
->orWhereHas('product', fn (Builder $query) => $query->where('name', 'like', "%{$search}%"));
});
});
});
})
->when(($tableQuery['channel'] ?? '') !== '', fn (Builder $query) => $query->where('channel', $tableQuery['channel']))
->when(($tableQuery['status'] ?? '') !== '', fn (Builder $query) => $query->where('status', $tableQuery['status']))
->when(($tableQuery['payment_type'] ?? '') !== '', fn (Builder $query) => $query->where('payment_type', $tableQuery['payment_type']));
$this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);

View File

@ -1,4 +1,5 @@
export const OrderChannel = {
STORE: 'store',
TIKTOK: 'tiktok',
SHOPEE: 'shopee',
} as const;

View File

@ -14,9 +14,13 @@ import {
getPaperSizeLabel,
useThermalPrinter,
} from '@/composables/useThermalPrinter';
import { OrderChannel } from '@/constants/order-channel';
import { OrderPaymentType } from '@/constants/order-payment-type';
import { OrderStatus } from '@/constants/order-status';
import AdminLayout from '@/layouts/AdminLayout.vue';
import type { PaperSize } from '@/lib/thermal-printer/types';
import { index, create } from '@/routes/admin/manage/orders';
import type { DataTableFilterDef } from '@/types/data-table';
import type { PaginatedOrders } from '@/types/order';
import OrderGroupedTable from './table/OrderGroupedTable.vue';
@ -26,6 +30,9 @@ const props = defineProps<{
search: string;
sort?: string;
direction?: 'asc' | 'desc';
channel?: string;
status?: string;
payment_type?: string;
};
}>();
@ -34,13 +41,55 @@ const page = usePage();
const { isConnected, printOrderReceipt, tryReconnect } = useThermalPrinter();
const search = ref(props.filters.search ?? '');
const { setSearch, resetFilters, syncFromServer } = useDataTableQuery({
const { query, setSearch, setFilter, resetFilters, syncFromServer } = useDataTableQuery({
url: index.url(),
initial: { ...props.filters },
filterKeys: ['channel', 'status', 'payment_type'],
});
useDataTableQuerySync(() => props.filters, syncFromServer);
const filterDefs = computed<DataTableFilterDef[]>(() => [
{
key: 'channel',
label: 'Channel',
type: 'select',
options: [
{ value: OrderChannel.STORE, label: 'Toko' },
{ value: OrderChannel.SHOPEE, label: 'Shopee' },
{ value: OrderChannel.TIKTOK, label: 'TikTok' },
],
},
{
key: 'status',
label: 'Status',
type: 'select',
options: [
{ value: OrderStatus.PENDING, label: 'Menunggu' },
{ value: OrderStatus.PROCESSING, label: 'Diproses' },
{ value: OrderStatus.COMPLETED, label: 'Selesai' },
{ value: OrderStatus.CANCELLED, label: 'Dibatalkan' },
],
},
{
key: 'payment_type',
label: 'Tipe Pembayaran',
type: 'select',
options: [
{ value: OrderPaymentType.CASH, label: 'Cash' },
{ value: OrderPaymentType.TRANSFER, label: 'Transfer' },
{ value: OrderPaymentType.QRIS, label: 'QRIS' },
{ value: OrderPaymentType.MARKETPLACE, label: 'Marketplace' },
],
},
]);
const filterValues = computed(() => ({
channel: query.value.channel ?? '',
status: query.value.status ?? '',
payment_type: query.value.payment_type ?? '',
}));
const tablePagination = computed(() => ({
currentPage: props.orders.current_page,
perPage: props.orders.per_page,
@ -138,6 +187,9 @@ onMounted(async () => {
:first-item="firstItem"
:pagination="tablePagination"
:pagination-links="orders.links"
:filter-defs="filterDefs"
:filter-values="filterValues"
@filter-change="setFilter"
@filters-reset="resetFilters"
/>
</CardContent>

View File

@ -21,6 +21,7 @@ import { usePaginationSummary } from '@/composables/usePaginationSummary';
import { orderStatusBadgeVariant } from '@/constants/order-status';
import { groupedTableRowNumber } from '@/lib/grouped-table';
import type {
DataTableFilterDef,
DataTablePagination,
DataTablePaginationLink,
} from '@/types/data-table';
@ -32,11 +33,14 @@ const props = defineProps<{
firstItem?: number;
pagination?: DataTablePagination;
paginationLinks?: DataTablePaginationLink[];
filterDefs?: DataTableFilterDef[];
filterValues?: Record<string, string>;
}>();
const search = defineModel<string>('search', { default: '' });
const emit = defineEmits<{
'filter-change': [key: string, value: string];
'filters-reset': [];
}>();
@ -50,7 +54,8 @@ function rowNumber(index: number): number {
<template>
<div class="space-y-4">
<DataTableToolbar v-model:search="search" @filters-reset="emit('filters-reset')" />
<DataTableToolbar v-model:search="search" :filter-defs="filterDefs" :filter-values="filterValues"
@filter-change="(key, value) => emit('filter-change', key, value)" @filters-reset="emit('filters-reset')" />
<div v-if="orders.length" class="space-y-4">
<div v-for="(order, index) in orders" :key="order.id" class="overflow-hidden rounded-md border">