feat: enhance Purchase management with supplier filtering and pagination adjustments
This commit is contained in:
parent
b4cb3f4d48
commit
bcf5842c4a
@ -7,6 +7,7 @@
|
|||||||
use App\Http\Requests\PaginatedRequest;
|
use App\Http\Requests\PaginatedRequest;
|
||||||
use App\Models\Purchase;
|
use App\Models\Purchase;
|
||||||
use App\Services\Admin\Manage\PurchaseService;
|
use App\Services\Admin\Manage\PurchaseService;
|
||||||
|
use App\Services\Admin\Master\SupplierService;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Inertia\Inertia;
|
use Inertia\Inertia;
|
||||||
use Inertia\Response;
|
use Inertia\Response;
|
||||||
@ -14,7 +15,8 @@
|
|||||||
class PurchaseController extends Controller
|
class PurchaseController extends Controller
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private PurchaseService $service,
|
private readonly PurchaseService $service,
|
||||||
|
private readonly SupplierService $supplierService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public function index(PaginatedRequest $request): Response
|
public function index(PaginatedRequest $request): Response
|
||||||
@ -22,7 +24,10 @@ public function index(PaginatedRequest $request): Response
|
|||||||
return Inertia::render('admin/manage/purchase/index', [
|
return Inertia::render('admin/manage/purchase/index', [
|
||||||
'purchases' => $this->service->paginated(
|
'purchases' => $this->service->paginated(
|
||||||
...$request->validatedWithDefaults(),
|
...$request->validatedWithDefaults(),
|
||||||
|
filters: $request->only(['supplier_id']),
|
||||||
),
|
),
|
||||||
|
'suppliers' => $this->supplierService->getAll(),
|
||||||
|
'filters' => $request->only(['supplier_id']),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -21,7 +21,7 @@ public function __construct(
|
|||||||
private S3PresignedService $s3Service,
|
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()
|
$paginator = Purchase::query()
|
||||||
->select(['id', 'supplier_id', 'created_by_id', 'subtotal', 'discount', 'shipping_cost', 'total', 'notes', 'created_at'])
|
->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}%"))
|
$q->whereHas('supplier', fn ($sq) => $sq->where('name', 'like', "%{$search}%"))
|
||||||
->orWhere('notes', 'like', "%{$search}%");
|
->orWhere('notes', 'like', "%{$search}%");
|
||||||
})
|
})
|
||||||
|
->when($filters['supplier_id'] ?? null, fn ($q, $supplierId) => $q->where('supplier_id', $supplierId))
|
||||||
->orderBy($sort, $direction)
|
->orderBy($sort, $direction)
|
||||||
->paginate($perPage);
|
->paginate($perPage);
|
||||||
|
|
||||||
|
|||||||
@ -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 { CardTable } from '@/components/card-table';
|
||||||
import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog';
|
import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog';
|
||||||
|
import { FilterPopover } from '@/components/filter-popover';
|
||||||
import { useCardTableExpand } from '@/components/hooks/use-card-table-expand';
|
import { useCardTableExpand } from '@/components/hooks/use-card-table-expand';
|
||||||
import { PageHeader } from '@/components/page-header';
|
import { PageHeader } from '@/components/page-header';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
|
import {
|
||||||
|
Combobox,
|
||||||
|
ComboboxContent,
|
||||||
|
ComboboxEmpty,
|
||||||
|
ComboboxInput,
|
||||||
|
ComboboxItem,
|
||||||
|
ComboboxList,
|
||||||
|
} from '@/components/ui/combobox';
|
||||||
import { useCan } from '@/hooks/use-can';
|
import { useCan } from '@/hooks/use-can';
|
||||||
import { useServerTable } from '@/hooks/use-server-table';
|
import { useServerTable } from '@/hooks/use-server-table';
|
||||||
import {
|
import {
|
||||||
destroy,
|
destroy,
|
||||||
create as purchaseCreate,
|
create as purchaseCreate,
|
||||||
index as purchaseIndex,
|
|
||||||
edit as purchaseEdit,
|
edit as purchaseEdit,
|
||||||
|
index as purchaseIndex,
|
||||||
} from '@/routes/admin/manage/purchases';
|
} 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 type { Purchase } from './columns';
|
||||||
import { PurchaseCardRow } from './purchase-card';
|
import { PurchaseCardRow } from './purchase-card';
|
||||||
import { PurchaseItemSubRow } from './purchase-sub-row';
|
import { PurchaseItemSubRow } from './purchase-sub-row';
|
||||||
@ -26,9 +35,20 @@ type Props = {
|
|||||||
per_page: number;
|
per_page: number;
|
||||||
total: 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 { can } = useCan();
|
||||||
const [deleting, setDeleting] = useState<Purchase | null>(null);
|
const [deleting, setDeleting] = useState<Purchase | null>(null);
|
||||||
const expand = useCardTableExpand(true);
|
const expand = useCardTableExpand(true);
|
||||||
@ -42,14 +62,29 @@ export default function PurchaseIndex({ purchases }: Props) {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
search,
|
search,
|
||||||
|
filterOpen,
|
||||||
|
setFilterOpen,
|
||||||
handlePageChange,
|
handlePageChange,
|
||||||
handlePerPageChange,
|
handlePerPageChange,
|
||||||
handleSearchChange,
|
handleSearchChange,
|
||||||
|
applyFilter,
|
||||||
|
clearFilters,
|
||||||
} = useServerTable({
|
} = useServerTable({
|
||||||
route: () => purchaseIndex.url(),
|
route: () => purchaseIndex.url(),
|
||||||
pagination,
|
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() {
|
function handleDelete() {
|
||||||
if (!deleting) {
|
if (!deleting) {
|
||||||
return;
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head title="Belanja" />
|
<Head title="Belanja" />
|
||||||
@ -87,6 +166,7 @@ export default function PurchaseIndex({ purchases }: Props) {
|
|||||||
searchValue={search}
|
searchValue={search}
|
||||||
onSearchChange={handleSearchChange}
|
onSearchChange={handleSearchChange}
|
||||||
searchPlaceholder="Cari berdasarkan supplier..."
|
searchPlaceholder="Cari berdasarkan supplier..."
|
||||||
|
toolbar={filterToolbar}
|
||||||
pagination={pagination}
|
pagination={pagination}
|
||||||
onPageChange={handlePageChange}
|
onPageChange={handlePageChange}
|
||||||
onPerPageChange={handlePerPageChange}
|
onPerPageChange={handlePerPageChange}
|
||||||
@ -100,7 +180,7 @@ export default function PurchaseIndex({ purchases }: Props) {
|
|||||||
purchase={item}
|
purchase={item}
|
||||||
index={
|
index={
|
||||||
(pagination.current_page - 1) *
|
(pagination.current_page - 1) *
|
||||||
pagination.per_page +
|
pagination.per_page +
|
||||||
index +
|
index +
|
||||||
1
|
1
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user