feat: enhance Purchase management with supplier filtering and pagination adjustments

This commit is contained in:
Yoga Pangestu 2026-08-06 09:52:17 +07:00
parent b4cb3f4d48
commit bcf5842c4a
3 changed files with 94 additions and 8 deletions

View File

@ -7,6 +7,7 @@
use App\Http\Requests\PaginatedRequest;
use App\Models\Purchase;
use App\Services\Admin\Manage\PurchaseService;
use App\Services\Admin\Master\SupplierService;
use Illuminate\Http\RedirectResponse;
use Inertia\Inertia;
use Inertia\Response;
@ -14,7 +15,8 @@
class PurchaseController extends Controller
{
public function __construct(
private PurchaseService $service,
private readonly PurchaseService $service,
private readonly SupplierService $supplierService,
) {}
public function index(PaginatedRequest $request): Response
@ -22,7 +24,10 @@ public function index(PaginatedRequest $request): Response
return Inertia::render('admin/manage/purchase/index', [
'purchases' => $this->service->paginated(
...$request->validatedWithDefaults(),
filters: $request->only(['supplier_id']),
),
'suppliers' => $this->supplierService->getAll(),
'filters' => $request->only(['supplier_id']),
]);
}

View File

@ -21,7 +21,7 @@ public function __construct(
private S3PresignedService $s3Service,
) {}
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc'): LengthAwarePaginator
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', array $filters = []): LengthAwarePaginator
{
$paginator = Purchase::query()
->select(['id', 'supplier_id', 'created_by_id', 'subtotal', 'discount', 'shipping_cost', 'total', 'notes', 'created_at'])
@ -39,6 +39,7 @@ public function paginated(int $perPage = 25, string $search = '', string $sort =
$q->whereHas('supplier', fn ($sq) => $sq->where('name', 'like', "%{$search}%"))
->orWhere('notes', 'like', "%{$search}%");
})
->when($filters['supplier_id'] ?? null, fn ($q, $supplierId) => $q->where('supplier_id', $supplierId))
->orderBy($sort, $direction)
->paginate($perPage);

View File

@ -1,19 +1,28 @@
import { Head, Link, router } from '@inertiajs/react';
import { Plus } from 'lucide-react';
import { useState } from 'react';
import { CardTable } from '@/components/card-table';
import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog';
import { FilterPopover } from '@/components/filter-popover';
import { useCardTableExpand } from '@/components/hooks/use-card-table-expand';
import { PageHeader } from '@/components/page-header';
import { Button } from '@/components/ui/button';
import {
Combobox,
ComboboxContent,
ComboboxEmpty,
ComboboxInput,
ComboboxItem,
ComboboxList,
} from '@/components/ui/combobox';
import { useCan } from '@/hooks/use-can';
import { useServerTable } from '@/hooks/use-server-table';
import {
destroy,
create as purchaseCreate,
index as purchaseIndex,
edit as purchaseEdit,
index as purchaseIndex,
} from '@/routes/admin/manage/purchases';
import { Head, Link, router } from '@inertiajs/react';
import { Plus } from 'lucide-react';
import { useMemo, useState } from 'react';
import type { Purchase } from './columns';
import { PurchaseCardRow } from './purchase-card';
import { PurchaseItemSubRow } from './purchase-sub-row';
@ -26,9 +35,20 @@ type Props = {
per_page: number;
total: number;
};
suppliers: {
id: number;
name: string;
}[];
filters: {
supplier_id?: string;
};
};
export default function PurchaseIndex({ purchases }: Props) {
export default function PurchaseIndex({
purchases,
filters,
suppliers,
}: Props) {
const { can } = useCan();
const [deleting, setDeleting] = useState<Purchase | null>(null);
const expand = useCardTableExpand(true);
@ -42,14 +62,29 @@ export default function PurchaseIndex({ purchases }: Props) {
const {
search,
filterOpen,
setFilterOpen,
handlePageChange,
handlePerPageChange,
handleSearchChange,
applyFilter,
clearFilters,
} = useServerTable({
route: () => purchaseIndex.url(),
pagination,
filters,
filterWithParams: false,
reloadOnly: ['purchases', 'filters'],
});
const selectedSupplier = useMemo(
() =>
suppliers.find(
(s) => String(s.id) === filters.supplier_id,
) ?? null,
[suppliers, filters.supplier_id],
);
function handleDelete() {
if (!deleting) {
return;
@ -60,6 +95,50 @@ export default function PurchaseIndex({ purchases }: Props) {
});
}
const filterToolbar = (
<FilterPopover
open={filterOpen}
onOpenChange={setFilterOpen}
filters={filters}
hasActiveFilters={Boolean(filters.supplier_id)}
onClear={clearFilters}
>
<div className="flex flex-col gap-2">
<label className="text-xs text-muted-foreground">
Supplier
</label>
<Combobox
items={suppliers}
itemToStringLabel={(supplier) => supplier.name}
value={selectedSupplier}
onValueChange={(value) =>
applyFilter(
'supplier_id',
value ? String(value.id) : '',
)
}
>
<ComboboxInput
placeholder="Pilih supplier..."
className="w-full"
/>
<ComboboxContent>
<ComboboxEmpty>
Tidak ada supplier ditemukan.
</ComboboxEmpty>
<ComboboxList>
{(supplier) => (
<ComboboxItem value={supplier}>
{supplier.name}
</ComboboxItem>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>
</div>
</FilterPopover>
);
return (
<>
<Head title="Belanja" />
@ -87,6 +166,7 @@ export default function PurchaseIndex({ purchases }: Props) {
searchValue={search}
onSearchChange={handleSearchChange}
searchPlaceholder="Cari berdasarkan supplier..."
toolbar={filterToolbar}
pagination={pagination}
onPageChange={handlePageChange}
onPerPageChange={handlePerPageChange}
@ -100,7 +180,7 @@ export default function PurchaseIndex({ purchases }: Props) {
purchase={item}
index={
(pagination.current_page - 1) *
pagination.per_page +
pagination.per_page +
index +
1
}