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 = $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', [ return Inertia::render('admin/manage/orders/Index', [
'orders' => $this->orderService->paginateForIndex($tableQuery, $request->user()), '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}%")); ->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']); $this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);

View File

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

View File

@ -14,9 +14,13 @@ import {
getPaperSizeLabel, getPaperSizeLabel,
useThermalPrinter, useThermalPrinter,
} from '@/composables/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 AdminLayout from '@/layouts/AdminLayout.vue';
import type { PaperSize } from '@/lib/thermal-printer/types'; import type { PaperSize } from '@/lib/thermal-printer/types';
import { index, create } from '@/routes/admin/manage/orders'; import { index, create } from '@/routes/admin/manage/orders';
import type { DataTableFilterDef } from '@/types/data-table';
import type { PaginatedOrders } from '@/types/order'; import type { PaginatedOrders } from '@/types/order';
import OrderGroupedTable from './table/OrderGroupedTable.vue'; import OrderGroupedTable from './table/OrderGroupedTable.vue';
@ -26,6 +30,9 @@ const props = defineProps<{
search: string; search: string;
sort?: string; sort?: string;
direction?: 'asc' | 'desc'; direction?: 'asc' | 'desc';
channel?: string;
status?: string;
payment_type?: string;
}; };
}>(); }>();
@ -34,13 +41,55 @@ const page = usePage();
const { isConnected, printOrderReceipt, tryReconnect } = useThermalPrinter(); const { isConnected, printOrderReceipt, tryReconnect } = useThermalPrinter();
const search = ref(props.filters.search ?? ''); const search = ref(props.filters.search ?? '');
const { setSearch, resetFilters, syncFromServer } = useDataTableQuery({ const { query, setSearch, setFilter, resetFilters, syncFromServer } = useDataTableQuery({
url: index.url(), url: index.url(),
initial: { ...props.filters }, initial: { ...props.filters },
filterKeys: ['channel', 'status', 'payment_type'],
}); });
useDataTableQuerySync(() => props.filters, syncFromServer); 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(() => ({ const tablePagination = computed(() => ({
currentPage: props.orders.current_page, currentPage: props.orders.current_page,
perPage: props.orders.per_page, perPage: props.orders.per_page,
@ -138,6 +187,9 @@ onMounted(async () => {
:first-item="firstItem" :first-item="firstItem"
:pagination="tablePagination" :pagination="tablePagination"
:pagination-links="orders.links" :pagination-links="orders.links"
:filter-defs="filterDefs"
:filter-values="filterValues"
@filter-change="setFilter"
@filters-reset="resetFilters" @filters-reset="resetFilters"
/> />
</CardContent> </CardContent>

View File

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