From ed93fd3eac1cf6e17cdbc899c40dabc7b553507c Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Thu, 13 Aug 2026 00:09:33 +0700 Subject: [PATCH] feat: enhance cutting management with product name and status filters in the index view --- .../Admin/Manage/CuttingController.php | 7 ++ app/Services/Admin/Manage/CuttingService.php | 16 ++- .../js/pages/admin/manage/cutting/index.tsx | 115 +++++++++++++++++- 3 files changed, 135 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/Admin/Manage/CuttingController.php b/app/Http/Controllers/Admin/Manage/CuttingController.php index df6ae22..dbb5079 100644 --- a/app/Http/Controllers/Admin/Manage/CuttingController.php +++ b/app/Http/Controllers/Admin/Manage/CuttingController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\Admin\Manage; +use App\Enums\CuttingStatus; use App\Http\Controllers\Controller; use App\Http\Requests\Admin\Manage\CuttingRequest; use App\Http\Requests\PaginatedRequest; @@ -24,7 +25,13 @@ public function index(PaginatedRequest $request): Response return Inertia::render('admin/manage/cutting/index', [ 'cuttings' => $this->service->paginated( ...$request->validatedWithDefaults(), + filters: $request->only(['product_name', 'status']), ), + 'filters' => $request->only(['product_name', 'status']), + 'filterOptions' => [ + 'statusOptions' => CuttingStatus::toSelect(), + 'productNames' => $this->service->getProductNames(), + ], ]); } diff --git a/app/Services/Admin/Manage/CuttingService.php b/app/Services/Admin/Manage/CuttingService.php index a9869b5..3f5783c 100644 --- a/app/Services/Admin/Manage/CuttingService.php +++ b/app/Services/Admin/Manage/CuttingService.php @@ -10,6 +10,7 @@ use App\Services\Concerns\RegistersMedia; use App\Services\S3PresignedService; use Illuminate\Contracts\Pagination\LengthAwarePaginator; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; use Illuminate\Validation\ValidationException; @@ -21,7 +22,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 = Cutting::query() ->select(['id', 'created_by_id', 'status', 'description', 'total_material_cost', 'cost_per_unit', 'created_at']) @@ -38,6 +39,8 @@ public function paginated(int $perPage = 25, string $search = '', string $sort = $q->whereHas('cuttingResults', fn ($rq) => $rq->where('product_name', 'like', "%{$search}%")) ->orWhere('description', 'like', "%{$search}%"); }) + ->when($filters['product_name'] ?? null, fn ($q, $productName) => $q->whereHas('cuttingResults', fn ($rq) => $rq->where('product_name', $productName))) + ->when($filters['status'] ?? null, fn ($q, $status) => $q->where('status', $status)) ->orderBy($sort, $direction) ->paginate($perPage); @@ -66,6 +69,17 @@ public function paginated(int $perPage = 25, string $search = '', string $sort = return $paginator; } + public function getProductNames(): Collection + { + return CuttingResult::query() + ->select('product_name') + ->whereNotNull('product_name') + ->where('product_name', '!=', '') + ->distinct() + ->orderBy('product_name') + ->get(); + } + public function getForEdit(Cutting $cutting): array { $cutting->load([ diff --git a/resources/js/pages/admin/manage/cutting/index.tsx b/resources/js/pages/admin/manage/cutting/index.tsx index 4538750..7192645 100644 --- a/resources/js/pages/admin/manage/cutting/index.tsx +++ b/resources/js/pages/admin/manage/cutting/index.tsx @@ -1,11 +1,27 @@ import { Head, Link, router } from '@inertiajs/react'; import { Plus } from 'lucide-react'; -import { useState } from 'react'; +import { useMemo, useState } from 'react'; import { CardTable } from '@/components/data-display'; import { DeleteConfirmDialog } from '@/components/dialogs'; +import { FilterPopover } from '@/components/data-display'; import { useCardTableExpand } from '@/components/hooks/use-card-table-expand'; import { PageHeader } from '@/components/layout'; import { Button } from '@/components/ui/button'; +import { + Combobox, + ComboboxContent, + ComboboxEmpty, + ComboboxInput, + ComboboxItem, + ComboboxList, +} from '@/components/ui/combobox'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; import { useCan } from '@/hooks/use-can'; import { useServerTable } from '@/hooks/use-server-table'; import { @@ -26,9 +42,21 @@ type Props = { per_page: number; total: number; }; + filters: { + product_name?: string; + status?: string; + }; + filterOptions: { + statusOptions: Array<{ value: string; label: string }>; + productNames: Array<{ product_name: string }>; + }; }; -export default function CuttingIndex({ cuttings }: Props) { +export default function CuttingIndex({ + cuttings, + filters, + filterOptions, +}: Props) { const { can } = useCan(); const [deleting, setDeleting] = useState(null); const expand = useCardTableExpand(true); @@ -42,14 +70,22 @@ export default function CuttingIndex({ cuttings }: Props) { const { search, + filterOpen, + setFilterOpen, handlePageChange, handlePerPageChange, handleSearchChange, + applyFilter, + clearFilters, } = useServerTable({ route: () => cuttingIndex.url(), pagination, + filters, + filterWithParams: false, }); + const hasActiveFilters = Boolean(filters.product_name || filters.status); + function handleDelete() { if (!deleting) { return; @@ -60,6 +96,80 @@ export default function CuttingIndex({ cuttings }: Props) { }); } + const selectedProductName = useMemo( + () => + filterOptions.productNames.find( + (p) => p.product_name === filters.product_name, + ) ?? null, + [filterOptions.productNames, filters.product_name], + ); + + const filterToolbar = ( + +
+ + product.product_name} + value={selectedProductName} + onValueChange={(value) => + applyFilter( + 'product_name', + value ? value.product_name : '', + ) + } + > + + + + Tidak ada produk ditemukan. + + + {(product) => ( + + {product.product_name} + + )} + + + +
+
+ + +
+
+ ); + return ( <> @@ -87,6 +197,7 @@ export default function CuttingIndex({ cuttings }: Props) { searchValue={search} onSearchChange={handleSearchChange} + toolbar={filterToolbar} pagination={pagination} onPageChange={handlePageChange} onPerPageChange={handlePerPageChange}