refactor: implement server-side pagination across admin modules and update corresponding frontend components and tests

This commit is contained in:
Yoga Pangestu 2026-04-30 22:41:43 +07:00
parent f5698ee47f
commit dc68c73c2a
27 changed files with 67 additions and 42 deletions

View File

@ -15,7 +15,7 @@ class ExpenseController extends Controller
public function index(): Response public function index(): Response
{ {
return Inertia::render('admin/finance/expense/index', [ return Inertia::render('admin/finance/expense/index', [
'expenses' => Expense::with(['media', 'user'])->latest()->get(), 'expenses' => Expense::with(['media', 'user'])->latest()->paginate(10),
]); ]);
} }

View File

@ -28,7 +28,7 @@ public function index(): Response
$query->where('user_id', auth()->id()); $query->where('user_id', auth()->id());
}) })
->latest() ->latest()
->get(), ->paginate(10),
'currentMonthPayrolls' => Payroll::with(['user.profile', 'adjustments']) 'currentMonthPayrolls' => Payroll::with(['user.profile', 'adjustments'])
->where('period_month', $currentMonth) ->where('period_month', $currentMonth)
->when(auth()->user()->hasRole('Admin'), function ($query) { ->when(auth()->user()->hasRole('Admin'), function ($query) {

View File

@ -23,7 +23,7 @@ class OrderController extends Controller
public function index(): Response public function index(): Response
{ {
return Inertia::render('admin/manage/order/index', [ return Inertia::render('admin/manage/order/index', [
'orders' => Order::with(['items.product', 'user'])->latest()->get(), 'orders' => Order::with(['items.product', 'user'])->latest()->paginate(10),
]); ]);
} }

View File

@ -19,7 +19,7 @@ class PurchaseController extends Controller
public function index(): Response public function index(): Response
{ {
return Inertia::render('admin/manage/purchase/index', [ return Inertia::render('admin/manage/purchase/index', [
'purchases' => Purchase::with(['items.product'])->latest()->get(), 'purchases' => Purchase::with(['items.product'])->latest()->paginate(10),
]); ]);
} }

View File

@ -15,7 +15,7 @@ class CategoryController extends Controller
public function index(): Response public function index(): Response
{ {
return Inertia::render('admin/master/category/index', [ return Inertia::render('admin/master/category/index', [
'categories' => Category::latest()->get(), 'categories' => Category::latest()->paginate(10),
]); ]);
} }

View File

@ -20,7 +20,7 @@ class ProductController extends Controller
public function index(): Response public function index(): Response
{ {
return Inertia::render('admin/master/product/index', [ return Inertia::render('admin/master/product/index', [
'products' => Product::with(['prices', 'categories', 'media'])->latest()->get(), 'products' => Product::with(['prices', 'categories', 'media'])->latest()->paginate(10),
'categories' => Category::active()->get(), 'categories' => Category::active()->get(),
]); ]);
} }

View File

@ -19,7 +19,7 @@ class UserController extends Controller
public function index(): Response public function index(): Response
{ {
return Inertia::render('admin/master/user/index', [ return Inertia::render('admin/master/user/index', [
'users' => User::with(['profile', 'roles'])->latest()->get(), 'users' => User::with(['profile', 'roles'])->latest()->paginate(10),
'roles' => Role::all(), 'roles' => Role::all(),
'defaultPassword' => config('auth.password_default', 'password'), 'defaultPassword' => config('auth.password_default', 'password'),
]); ]);

View File

@ -13,9 +13,8 @@ public function index(): Response
{ {
$activities = Activity::with('causer') $activities = Activity::with('causer')
->latest() ->latest()
->limit(500) ->paginate(10)
->get() ->through(fn ($activity) => [
->map(fn ($activity) => [
'id' => $activity->id, 'id' => $activity->id,
'description' => $activity->description, 'description' => $activity->description,
'module' => $activity->log_name, 'module' => $activity->log_name,

View File

@ -18,7 +18,7 @@ class RoleController extends Controller
public function index(): Response public function index(): Response
{ {
return Inertia::render('admin/system/role/index', [ return Inertia::render('admin/system/role/index', [
'roles' => Role::with('permissions')->latest()->get(), 'roles' => Role::with('permissions')->latest()->paginate(10),
]); ]);
} }

View File

@ -6,13 +6,14 @@ import { ImagePreviewDialog } from '@/components/modal/image-preview';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
import type { Expense } from '@/types'; import type { Expense } from '@/types';
import type { PaginatedData } from '@/types/pagination';
import { useExpenseIndex } from './hooks/use-expense-index'; import { useExpenseIndex } from './hooks/use-expense-index';
import { getColumns } from './partials/columns'; import { getColumns } from './partials/columns';
import { ExpenseFormModal } from './partials/expense-form-modal'; import { ExpenseFormModal } from './partials/expense-form-modal';
import { usePermission } from '@/hooks/use-permission'; import { usePermission } from '@/hooks/use-permission';
export default function ExpenseIndex({ expenses }: { expenses: Expense[] }) { export default function ExpenseIndex({ expenses }: { expenses: PaginatedData<Expense> }) {
const { hasPermission } = usePermission(); const { hasPermission } = usePermission();
const { const {
selectedProof, selectedProof,
@ -67,7 +68,8 @@ export default function ExpenseIndex({ expenses }: { expenses: Expense[] }) {
<CardContent className="p-0"> <CardContent className="p-0">
<DataTable <DataTable
columns={columns} columns={columns}
data={expenses} data={expenses.data}
meta={expenses}
showSelection={hasPermission('DeleteAny:Expense')} showSelection={hasPermission('DeleteAny:Expense')}
rowSelection={rowSelection} rowSelection={rowSelection}
onRowSelectionChange={setRowSelection} onRowSelectionChange={setRowSelection}

View File

@ -6,6 +6,7 @@ import { DeleteConfirmation } from '@/components/modal/delete-confirmation';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
import type { Payroll } from '@/types'; import type { Payroll } from '@/types';
import type { PaginatedData } from '@/types/pagination';
import { PayrollCard } from '@/components/cards/payroll-card'; import { PayrollCard } from '@/components/cards/payroll-card';
import { usePermission } from '@/hooks/use-permission'; import { usePermission } from '@/hooks/use-permission';
@ -17,7 +18,7 @@ export default function PayrollIndex({
payrolls, payrolls,
currentMonthPayrolls, currentMonthPayrolls,
}: { }: {
payrolls: Payroll[]; payrolls: PaginatedData<Payroll>;
currentMonthPayrolls: Payroll[]; currentMonthPayrolls: Payroll[];
}) { }) {
const { hasPermission } = usePermission(); const { hasPermission } = usePermission();
@ -106,7 +107,8 @@ export default function PayrollIndex({
<CardContent className="p-0"> <CardContent className="p-0">
<DataTable <DataTable
columns={columns} columns={columns}
data={payrolls} data={payrolls.data}
meta={payrolls}
showSelection={hasPermission('DeleteAny:Payroll')} showSelection={hasPermission('DeleteAny:Payroll')}
rowSelection={rowSelection} rowSelection={rowSelection}
onRowSelectionChange={setRowSelection} onRowSelectionChange={setRowSelection}

View File

@ -8,12 +8,13 @@ import { Plus, Trash2 } from 'lucide-react';
import { usePermission } from '@/hooks/use-permission'; import { usePermission } from '@/hooks/use-permission';
import * as orderRoutes from '@/routes/order'; import * as orderRoutes from '@/routes/order';
import type { Order } from '@/types/order'; import type { Order } from '@/types/order';
import type { PaginatedData } from '@/types/pagination';
import { useOrderIndex } from './hooks/use-order-index'; import { useOrderIndex } from './hooks/use-order-index';
import { useOrderPrint } from './hooks/use-order-print'; import { useOrderPrint } from './hooks/use-order-print';
import { getColumns } from './partials/columns'; import { getColumns } from './partials/columns';
import { PrinterSettings } from './partials/printer-settings'; import { PrinterSettings } from './partials/printer-settings';
export default function OrderIndex({ orders }: { orders: Order[] }) { export default function OrderIndex({ orders }: { orders: PaginatedData<Order> }) {
const { hasPermission } = usePermission(); const { hasPermission } = usePermission();
const { const {
isDeleteDialogOpen, isDeleteDialogOpen,
@ -84,7 +85,8 @@ export default function OrderIndex({ orders }: { orders: Order[] }) {
<CardContent className="p-0"> <CardContent className="p-0">
<DataTable <DataTable
columns={columns} columns={columns}
data={orders} data={orders.data}
meta={orders}
showSelection={hasPermission('DeleteAny:Order')} showSelection={hasPermission('DeleteAny:Order')}
rowSelection={rowSelection} rowSelection={rowSelection}
onRowSelectionChange={setRowSelection} onRowSelectionChange={setRowSelection}

View File

@ -10,11 +10,12 @@ import { Card, CardContent } from '@/components/ui/card';
import { formatDate } from '@/lib/formatters'; import { formatDate } from '@/lib/formatters';
import purchaseRoutes from '@/routes/purchase'; import purchaseRoutes from '@/routes/purchase';
import type { Purchase } from '@/types'; import type { Purchase } from '@/types';
import type { PaginatedData } from '@/types/pagination';
import { usePurchaseIndex } from './hooks/use-purchase-index'; import { usePurchaseIndex } from './hooks/use-purchase-index';
import { getColumns } from './partials/columns'; import { getColumns } from './partials/columns';
import { usePermission } from '@/hooks/use-permission'; import { usePermission } from '@/hooks/use-permission';
export default function PurchaseIndex({ purchases }: { purchases: Purchase[] }) { export default function PurchaseIndex({ purchases }: { purchases: PaginatedData<Purchase> }) {
const { hasPermission } = usePermission(); const { hasPermission } = usePermission();
const { const {
isDeleteDialogOpen, isDeleteDialogOpen,
@ -57,7 +58,8 @@ export default function PurchaseIndex({ purchases }: { purchases: Purchase[] })
<CardContent className="p-0"> <CardContent className="p-0">
<DataTable <DataTable
columns={columns} columns={columns}
data={purchases} data={purchases.data}
meta={purchases}
showSelection={hasPermission('DeleteAny:Purchase')} showSelection={hasPermission('DeleteAny:Purchase')}
rowSelection={rowSelection} rowSelection={rowSelection}
onRowSelectionChange={setRowSelection} onRowSelectionChange={setRowSelection}

View File

@ -4,14 +4,14 @@ import { DataTable } from '@/components/data-table';
import { DeleteConfirmation } from '@/components/modal/delete-confirmation'; import { DeleteConfirmation } from '@/components/modal/delete-confirmation';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
import type { Category } from '@/types'; import type { Category, PaginatedData } from '@/types';
import { useCategoryIndex } from './hooks/use-category-index'; import { useCategoryIndex } from './hooks/use-category-index';
import { CategoryFormModal } from './partials/category-form-modal'; import { CategoryFormModal } from './partials/category-form-modal';
import { getColumns } from './partials/columns'; import { getColumns } from './partials/columns';
import { usePermission } from '@/hooks/use-permission'; import { usePermission } from '@/hooks/use-permission';
export default function CategoryIndex({ categories }: { categories: Category[] }) { export default function CategoryIndex({ categories }: { categories: PaginatedData<Category> }) {
const { hasPermission } = usePermission(); const { hasPermission } = usePermission();
const { const {
isFormOpen, isFormOpen,
@ -64,7 +64,8 @@ export default function CategoryIndex({ categories }: { categories: Category[] }
<CardContent className="p-0"> <CardContent className="p-0">
<DataTable <DataTable
columns={columns} columns={columns}
data={categories} data={categories.data}
meta={categories}
showSelection={hasPermission('DeleteAny:Category')} showSelection={hasPermission('DeleteAny:Category')}
rowSelection={rowSelection} rowSelection={rowSelection}
onRowSelectionChange={setRowSelection} onRowSelectionChange={setRowSelection}

View File

@ -6,13 +6,14 @@ import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
import productRoutes from '@/routes/product'; import productRoutes from '@/routes/product';
import type { Product, Category } from '@/types'; import type { Product, Category } from '@/types';
import type { PaginatedData } from '@/types/pagination';
import { ImagePreviewDialog } from '../../../../components/modal/image-preview'; import { ImagePreviewDialog } from '../../../../components/modal/image-preview';
import { useProductIndex } from './hooks/use-product-index'; import { useProductIndex } from './hooks/use-product-index';
import { getColumns } from './partials/columns'; import { getColumns } from './partials/columns';
import { usePermission } from '@/hooks/use-permission'; import { usePermission } from '@/hooks/use-permission';
export default function ProductIndex({ products, categories }: { products: Product[], categories: Category[] }) { export default function ProductIndex({ products, categories }: { products: PaginatedData<Product>, categories: Category[] }) {
const { hasPermission } = usePermission(); const { hasPermission } = usePermission();
const { const {
selectedImage, selectedImage,
@ -59,7 +60,8 @@ export default function ProductIndex({ products, categories }: { products: Produ
<CardContent className="p-0"> <CardContent className="p-0">
<DataTable <DataTable
columns={columns} columns={columns}
data={products} data={products.data}
meta={products}
showSelection={hasPermission('DeleteAny:Product')} showSelection={hasPermission('DeleteAny:Product')}
rowSelection={rowSelection} rowSelection={rowSelection}
onRowSelectionChange={setRowSelection} onRowSelectionChange={setRowSelection}

View File

@ -15,6 +15,7 @@ import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
import userRoutes from '@/routes/user'; import userRoutes from '@/routes/user';
import type { User } from '@/types'; import type { User } from '@/types';
import type { PaginatedData } from '@/types/pagination';
import { Head, Link } from '@inertiajs/react'; import { Head, Link } from '@inertiajs/react';
import { KeyRound, Plus, Trash2, X } from 'lucide-react'; import { KeyRound, Plus, Trash2, X } from 'lucide-react';
@ -22,7 +23,7 @@ import { usePermission } from '@/hooks/use-permission';
import { useUserIndex } from './hooks/use-user-index'; import { useUserIndex } from './hooks/use-user-index';
import { getColumns } from './partials/columns'; import { getColumns } from './partials/columns';
export default function UserIndex({ users, defaultPassword }: { users: User[], defaultPassword: string }) { export default function UserIndex({ users, defaultPassword }: { users: PaginatedData<User>, defaultPassword: string }) {
const { hasPermission } = usePermission(); const { hasPermission } = usePermission();
const { const {
isDeleteDialogOpen, isDeleteDialogOpen,
@ -71,7 +72,8 @@ export default function UserIndex({ users, defaultPassword }: { users: User[], d
<CardContent className="p-0"> <CardContent className="p-0">
<DataTable <DataTable
columns={columns} columns={columns}
data={users} data={users.data}
meta={users}
showSelection={hasPermission('DeleteAny:User')} showSelection={hasPermission('DeleteAny:User')}
rowSelection={rowSelection} rowSelection={rowSelection}
onRowSelectionChange={setRowSelection} onRowSelectionChange={setRowSelection}

View File

@ -2,10 +2,11 @@ import { Head } from '@inertiajs/react';
import { DataTable } from '@/components/data-table'; import { DataTable } from '@/components/data-table';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
import type { Activity } from '@/types'; import type { Activity } from '@/types';
import type { PaginatedData } from '@/types/pagination';
import { getColumns } from './partials/columns'; import { getColumns } from './partials/columns';
interface Props { interface Props {
activities: Activity[]; activities: PaginatedData<Activity>;
} }
export default function ActivityLogIndex({ activities }: Props) { export default function ActivityLogIndex({ activities }: Props) {
@ -25,7 +26,8 @@ export default function ActivityLogIndex({ activities }: Props) {
<CardContent className="p-0"> <CardContent className="p-0">
<DataTable <DataTable
columns={columns} columns={columns}
data={activities} data={activities.data}
meta={activities}
filters={[ filters={[
{ {
columnId: 'description', columnId: 'description',

View File

@ -6,11 +6,12 @@ import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
import roleRoutes from '@/routes/system/role'; import roleRoutes from '@/routes/system/role';
import type { Role } from '@/types'; import type { Role } from '@/types';
import type { PaginatedData } from '@/types/pagination';
import { useRoleIndex } from './hooks/use-role-index'; import { useRoleIndex } from './hooks/use-role-index';
import { getColumns } from './partials/columns'; import { getColumns } from './partials/columns';
import { usePermission } from '@/hooks/use-permission'; import { usePermission } from '@/hooks/use-permission';
export default function RoleIndex({ roles }: { roles: Role[] }) { export default function RoleIndex({ roles }: { roles: PaginatedData<Role> }) {
const { hasPermission } = usePermission(); const { hasPermission } = usePermission();
const { const {
isDeleteDialogOpen, isDeleteDialogOpen,
@ -53,7 +54,8 @@ export default function RoleIndex({ roles }: { roles: Role[] }) {
<CardContent className="p-0"> <CardContent className="p-0">
<DataTable <DataTable
columns={columns} columns={columns}
data={roles} data={roles.data}
meta={roles}
showSelection={hasPermission('DeleteAny:Role')} showSelection={hasPermission('DeleteAny:Role')}
rowSelection={rowSelection} rowSelection={rowSelection}
onRowSelectionChange={setRowSelection} onRowSelectionChange={setRowSelection}

View File

@ -51,7 +51,7 @@
->assertInertia(fn ($page) => $page ->assertInertia(fn ($page) => $page
->component('admin/finance/expense/index') ->component('admin/finance/expense/index')
->has('expenses', fn ($page) => $page ->has('expenses', fn ($page) => $page
->has('0.user') ->has('data.0.user')
->etc() ->etc()
) )
); );

View File

@ -51,7 +51,7 @@
->assertOk() ->assertOk()
->assertInertia(fn ($page) => $page ->assertInertia(fn ($page) => $page
->component('admin/finance/payroll/index') ->component('admin/finance/payroll/index')
->has('payrolls') ->has('payrolls', fn ($page) => $page->has('data')->etc())
->has('currentMonthPayrolls') ->has('currentMonthPayrolls')
); );
}); });

View File

@ -55,10 +55,10 @@
->assertInertia(fn ($page) => $page ->assertInertia(fn ($page) => $page
->component('admin/manage/order/index') ->component('admin/manage/order/index')
->has('orders', fn ($page) => $page ->has('orders', fn ($page) => $page
->has('0.user') ->has('data.0.user')
->has('0.cogs_formatted') ->has('data.0.cogs_formatted')
->has('0.payment_formatted') ->has('data.0.payment_formatted')
->has('0.created_at_formatted') ->has('data.0.created_at_formatted')
->etc() ->etc()
) )
); );

View File

@ -48,7 +48,10 @@
->assertOk() ->assertOk()
->assertInertia(fn ($page) => $page ->assertInertia(fn ($page) => $page
->component('admin/manage/purchase/index') ->component('admin/manage/purchase/index')
->has('purchases') ->has('purchases', fn ($page) => $page
->has('data')
->etc()
)
); );
}); });

View File

@ -47,7 +47,10 @@
->assertOk() ->assertOk()
->assertInertia(fn ($page) => $page ->assertInertia(fn ($page) => $page
->component('admin/master/category/index') ->component('admin/master/category/index')
->has('categories') ->has('categories', fn ($page) => $page
->has('data')
->etc()
)
); );
}); });

View File

@ -51,7 +51,10 @@
->assertOk() ->assertOk()
->assertInertia(fn ($page) => $page ->assertInertia(fn ($page) => $page
->component('admin/master/product/index') ->component('admin/master/product/index')
->has('products') ->has('products', fn ($page) => $page
->has('data')
->etc()
)
->has('categories') ->has('categories')
); );
}); });

View File

@ -49,7 +49,7 @@
->assertOk() ->assertOk()
->assertInertia(fn ($page) => $page ->assertInertia(fn ($page) => $page
->component('admin/master/user/index') ->component('admin/master/user/index')
->has('users') ->has('users', fn ($page) => $page->has('data')->etc())
->has('roles') ->has('roles')
->has('defaultPassword') ->has('defaultPassword')
); );

View File

@ -42,7 +42,7 @@
->assertOk() ->assertOk()
->assertInertia(fn ($page) => $page ->assertInertia(fn ($page) => $page
->component('admin/system/activity-log/index') ->component('admin/system/activity-log/index')
->has('activities') ->has('activities', fn ($page) => $page->has('data')->etc())
); );
}); });
}); });

View File

@ -47,7 +47,7 @@
->assertOk() ->assertOk()
->assertInertia(fn ($page) => $page ->assertInertia(fn ($page) => $page
->component('admin/system/role/index') ->component('admin/system/role/index')
->has('roles') ->has('roles', fn ($page) => $page->has('data')->etc())
); );
}); });