Refactor role management and pagination in admin panel

- Updated RoleIndex component to handle pagination, sorting, and searching for roles.
- Adjusted data structure for roles to include pagination details.
- Enhanced tests for various admin features (Finance, HR, Master) to validate pagination and data structure.
- Ensured all relevant tests check for data structure consistency, including total counts and pagination details.
This commit is contained in:
Yoga Pangestu 2026-08-01 02:54:24 +07:00
parent 16909d076b
commit 2a3e70b78d
46 changed files with 1659 additions and 284 deletions

View File

@ -3,11 +3,11 @@
namespace App\Http\Controllers\Admin\Finance;
use App\Http\Controllers\Controller;
use App\Http\Requests\PaginatedRequest;
use App\Http\Requests\Admin\Finance\CashTransactionRequest;
use App\Models\CashTransaction;
use App\Services\Admin\Finance\CashAccountService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
@ -17,14 +17,15 @@ public function __construct(
private CashAccountService $service
) {}
public function index(Request $request): Response
public function index(PaginatedRequest $request): Response
{
$cashAccount = $this->service->get();
return Inertia::render('admin/finance/cash-account/index', [
'cashAccount' => $cashAccount,
'transactions' => $this->service->getAllTransactions(
$request->only(['type'])
'transactions' => $this->service->paginatedTransactions(
...$request->validatedWithDefaults(),
filters: $request->only(['type']),
),
'filters' => $request->only(['type']),
]);

View File

@ -3,6 +3,7 @@
namespace App\Http\Controllers\Admin\Finance;
use App\Http\Controllers\Controller;
use App\Http\Requests\PaginatedRequest;
use App\Http\Requests\Admin\Finance\EmployeeAdvanceRequest;
use App\Models\EmployeeAdvance;
use App\Services\Admin\Finance\EmployeeAdvanceService;
@ -16,10 +17,10 @@ public function __construct(
private EmployeeAdvanceService $service
) {}
public function index(): Response
public function index(PaginatedRequest $request): Response
{
return Inertia::render('admin/finance/employee-advance/index', [
'employeeAdvances' => $this->service->getAll(),
'employeeAdvances' => $this->service->paginated(...$request->validatedWithDefaults()),
]);
}

View File

@ -3,6 +3,7 @@
namespace App\Http\Controllers\Admin\Finance;
use App\Http\Controllers\Controller;
use App\Http\Requests\PaginatedRequest;
use App\Http\Requests\Admin\Finance\ExpenseRequest;
use App\Models\Expense;
use App\Services\Admin\Finance\ExpenseService;
@ -16,10 +17,10 @@ public function __construct(
private ExpenseService $service
) {}
public function index(): Response
public function index(PaginatedRequest $request): Response
{
return Inertia::render('admin/finance/expense/index', [
'expenses' => $this->service->getAll(),
'expenses' => $this->service->paginated(...$request->validatedWithDefaults()),
]);
}

View File

@ -4,6 +4,7 @@
use App\Enums\PayrollPeriodStatus;
use App\Http\Controllers\Controller;
use App\Http\Requests\PaginatedRequest;
use App\Models\PayrollPeriod;
use App\Services\Admin\Finance\PayrollPeriodService;
use Illuminate\Http\RedirectResponse;
@ -16,10 +17,10 @@ public function __construct(
private PayrollPeriodService $service
) {}
public function index(): Response
public function index(PaginatedRequest $request): Response
{
return Inertia::render('admin/finance/payroll-period/index', [
'payrollPeriods' => $this->service->getAll(),
'payrollPeriods' => $this->service->paginated(...$request->validatedWithDefaults()),
]);
}

View File

@ -3,11 +3,11 @@
namespace App\Http\Controllers\Admin\HR;
use App\Http\Controllers\Controller;
use App\Http\Requests\PaginatedRequest;
use App\Http\Requests\Admin\HR\EmployeeRequest;
use App\Models\User;
use App\Services\Admin\HR\EmployeeService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
@ -17,10 +17,13 @@ public function __construct(
private EmployeeService $service
) {}
public function index(Request $request): Response
public function index(PaginatedRequest $request): Response
{
return Inertia::render('admin/hr/employee/index', [
'employees' => $this->service->getAll($request->only(['employment_status', 'is_active', 'gender'])),
'employees' => $this->service->paginated(
...$request->validatedWithDefaults(),
filters: $request->only(['employment_status', 'is_active', 'gender']),
),
'filters' => $request->only(['employment_status', 'is_active', 'gender']),
]);
}

View File

@ -3,11 +3,11 @@
namespace App\Http\Controllers\Admin\HR;
use App\Http\Controllers\Controller;
use App\Http\Requests\PaginatedRequest;
use App\Http\Requests\Admin\HR\LeaveRequestRequest;
use App\Models\LeaveRequest;
use App\Services\Admin\HR\LeaveRequestService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
@ -17,11 +17,12 @@ public function __construct(
private LeaveRequestService $service
) {}
public function index(Request $request): Response
public function index(PaginatedRequest $request): Response
{
return Inertia::render('admin/hr/leave-request/index', [
'leaveRequests' => $this->service->getAll(
$request->only(['status'])
'leaveRequests' => $this->service->paginated(
...$request->validatedWithDefaults(),
filters: $request->only(['status']),
),
'filters' => $request->only(['status']),
]);

View File

@ -3,6 +3,7 @@
namespace App\Http\Controllers\Admin\Master;
use App\Http\Controllers\Controller;
use App\Http\Requests\PaginatedRequest;
use App\Http\Requests\Admin\Master\CategoryRequest;
use App\Models\Category;
use App\Services\Admin\Master\CategoryService;
@ -16,10 +17,10 @@ public function __construct(
private CategoryService $service
) {}
public function index(): Response
public function index(PaginatedRequest $request): Response
{
return Inertia::render('admin/master/category/index', [
'categories' => $this->service->getAll(),
'categories' => $this->service->paginated(...$request->validatedWithDefaults()),
]);
}

View File

@ -3,6 +3,7 @@
namespace App\Http\Controllers\Admin\Master;
use App\Http\Controllers\Controller;
use App\Http\Requests\PaginatedRequest;
use App\Http\Requests\Admin\Master\CustomerRequest;
use App\Models\Customer;
use App\Services\Admin\Master\CustomerService;
@ -16,10 +17,10 @@ public function __construct(
private CustomerService $service
) {}
public function index(): Response
public function index(PaginatedRequest $request): Response
{
return Inertia::render('admin/master/customer/index', [
'customers' => $this->service->getAll(),
'customers' => $this->service->paginated(...$request->validatedWithDefaults()),
]);
}

View File

@ -4,12 +4,12 @@
use App\Enums\ProductStatus;
use App\Http\Controllers\Controller;
use App\Http\Requests\PaginatedRequest;
use App\Http\Requests\Admin\Master\ProductRequest;
use App\Models\Product;
use App\Services\Admin\Master\CategoryService;
use App\Services\Admin\Master\ProductService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
@ -20,11 +20,14 @@ public function __construct(
private CategoryService $categoryService,
) {}
public function index(Request $request): Response
public function index(PaginatedRequest $request): Response
{
return Inertia::render('admin/master/product/index', [
'products' => $this->service->getAll($request->only(['status', 'name'])),
'filters' => $request->only(['status', 'name']),
'products' => $this->service->paginated(
...$request->validatedWithDefaults(),
filters: $request->only(['status']),
),
'filters' => $request->only(['status']),
]);
}

View File

@ -3,6 +3,7 @@
namespace App\Http\Controllers\Admin\Master;
use App\Http\Controllers\Controller;
use App\Http\Requests\PaginatedRequest;
use App\Http\Requests\Admin\Master\SupplierRequest;
use App\Models\Supplier;
use App\Services\Admin\Master\SupplierService;
@ -16,10 +17,10 @@ public function __construct(
private SupplierService $service
) {}
public function index(): Response
public function index(PaginatedRequest $request): Response
{
return Inertia::render('admin/master/supplier/index', [
'suppliers' => $this->service->getAll(),
'suppliers' => $this->service->paginated(...$request->validatedWithDefaults()),
]);
}

View File

@ -3,6 +3,7 @@
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Http\Requests\PaginatedRequest;
use App\Http\Requests\Admin\RoleRequest;
use App\Services\Admin\Settings\RoleService;
use Illuminate\Http\RedirectResponse;
@ -16,10 +17,10 @@ public function __construct(
private RoleService $service
) {}
public function index(): Response
public function index(PaginatedRequest $request): Response
{
return Inertia::render('admin/roles/index', [
'roles' => $this->service->getAll(),
'roles' => $this->service->paginated(...$request->validatedWithDefaults()),
]);
}

View File

@ -0,0 +1,35 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class PaginatedRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'per_page' => 'nullable|integer|in:25,50,100,999999',
'search' => 'nullable|string|max:255',
'sort' => 'nullable|string',
'direction' => 'nullable|string|in:asc,desc',
];
}
public function validatedWithDefaults(): array
{
$validated = $this->validated();
return [
'perPage' => $validated['per_page'] ?? 25,
'search' => $validated['search'] ?? '',
'sort' => $validated['sort'] ?? 'created_at',
'direction' => $validated['direction'] ?? 'desc',
];
}
}

View File

@ -6,6 +6,7 @@
use App\Models\CashAccount;
use App\Models\CashTransaction;
use App\Services\S3PresignedService;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
@ -42,6 +43,29 @@ public function getAllTransactions(array $filters = []): Collection
->map(fn (CashTransaction $transaction) => $this->formatTransaction($transaction));
}
public function paginatedTransactions(int $perPage = 15, string $search = '', string $sort = 'created_at', string $direction = 'desc', array $filters = []): LengthAwarePaginator
{
$cashAccount = $this->get();
if (! $cashAccount) {
return new \Illuminate\Pagination\LengthAwarePaginator(collect(), 0, $perPage);
}
$paginator = $cashAccount->cashTransactions()
->select('id', 'created_by_id', 'reference_type', 'reference_id', 'amount', 'balance_after', 'type', 'description', 'created_at')
->with('createdBy.userProfile', 'media')
->when($search, fn ($q) => $q->where('description', 'like', "%{$search}%"))
->when($filters['type'] ?? null, function ($query, $type) {
$query->where('type', $type);
})
->orderBy($sort, $direction)
->paginate($perPage);
$paginator->getCollection()->map(fn (CashTransaction $transaction) => $this->formatTransaction($transaction));
return $paginator;
}
private function formatTransaction(CashTransaction $transaction): array
{
$media = $transaction->getFirstMedia('receipts');

View File

@ -7,6 +7,7 @@
use App\Models\CashAccount;
use App\Models\CashTransaction;
use App\Models\EmployeeAdvance;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;
@ -21,6 +22,16 @@ public function getAll(): Collection
->get();
}
public function paginated(int $perPage = 15, string $search = '', string $sort = 'created_at', string $direction = 'desc'): LengthAwarePaginator
{
return EmployeeAdvance::query()
->select('id', 'employee_id', 'amount', 'paid_amount', 'description', 'due_date', 'status', 'created_at')
->with(['employee.user.userProfile'])
->when($search, fn ($q) => $q->where('description', 'like', "%{$search}%"))
->orderBy($sort, $direction)
->paginate($perPage);
}
public function create(array $data): EmployeeAdvance
{
return DB::transaction(function () use ($data) {

View File

@ -7,6 +7,7 @@
use App\Models\CashTransaction;
use App\Models\Expense;
use App\Services\S3PresignedService;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
@ -29,6 +30,20 @@ public function getAll(): Collection
->map(fn (Expense $expense) => $this->formatExpense($expense));
}
public function paginated(int $perPage = 15, string $search = '', string $sort = 'created_at', string $direction = 'desc'): LengthAwarePaginator
{
$paginator = Expense::query()
->select('id', 'created_by_id', 'amount', 'description', 'created_at')
->with('createdBy.userProfile', 'media')
->when($search, fn ($q) => $q->where('description', 'like', "%{$search}%"))
->orderBy($sort, $direction)
->paginate($perPage);
$paginator->getCollection()->map(fn (Expense $expense) => $this->formatExpense($expense));
return $paginator;
}
private function formatExpense(Expense $expense): array
{
$media = $expense->getFirstMedia('receipts');

View File

@ -9,6 +9,7 @@
use App\Models\CashTransaction;
use App\Models\Payroll;
use App\Models\PayrollPeriod;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;
@ -33,6 +34,25 @@ public function getAll(): Collection
->get();
}
public function paginated(int $perPage = 15, string $search = '', string $sort = 'created_at', string $direction = 'desc'): LengthAwarePaginator
{
return PayrollPeriod::query()
->select('id', 'year', 'month', 'status', 'closed_at', 'created_at')
->withCount('payrolls')
->withSum('payrolls', 'total_amount')
->withSum('payrolls', 'bonus_amount')
->withSum('payrolls', 'deduction_amount')
->withCount(['payrolls as paid_count' => function ($q) {
$q->paid();
}])
->withCount(['payrolls as cancelled_count' => function ($q) {
$q->cancelled();
}])
->when($search, fn ($q) => $q->where('year', 'like', "%{$search}%"))
->orderBy($sort, $direction)
->paginate($perPage);
}
public function getDetail(PayrollPeriod $period): PayrollPeriod
{
return $period->load([

View File

@ -3,6 +3,7 @@
namespace App\Services\Admin\HR;
use App\Models\User;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\DB;
@ -23,6 +24,23 @@ public function getAll(array $filters = []): Collection
->get();
}
public function paginated(int $perPage = 15, string $search = '', string $sort = 'created_at', string $direction = 'desc', array $filters = []): LengthAwarePaginator
{
return User::query()
->select('id', 'email', 'username', 'is_active')
->whereHas('employee')
->with([
'userProfile' => fn ($q) => $q->select('id', 'user_id', 'full_name', 'phone_number', 'gender'),
'employee' => fn ($q) => $q->select('id', 'user_id', 'join_date', 'employment_status', 'base_salary'),
])
->when($search, fn ($q) => $q->whereHas('userProfile', fn ($uq) => $uq->where('full_name', 'like', "%{$search}%")))
->when($filters['employment_status'] ?? null, fn ($q, $status) => $q->whereHas('employee', fn ($eq) => $eq->where('employment_status', $status)))
->when(isset($filters['is_active']) && $filters['is_active'] !== '', fn ($q) => $q->where('is_active', filter_var($filters['is_active'], FILTER_VALIDATE_BOOLEAN)))
->when($filters['gender'] ?? null, fn ($q, $gender) => $q->whereHas('userProfile', fn ($uq) => $uq->where('gender', $gender)))
->orderBy($sort, $direction)
->paginate($perPage);
}
public function getById(int $id): User
{
return User::with(['userProfile', 'employee'])->findOrFail($id);

View File

@ -5,6 +5,7 @@
use App\Enums\LeaveRequestStatus;
use App\Models\LeaveRequest;
use Carbon\Carbon;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
@ -21,6 +22,19 @@ public function getAll(array $filters = []): Collection
->get();
}
public function paginated(int $perPage = 15, string $search = '', string $sort = 'created_at', string $direction = 'desc', array $filters = []): LengthAwarePaginator
{
return LeaveRequest::query()
->select('id', 'employee_id', 'start_date', 'end_date', 'total_days', 'status', 'created_at')
->with(['employee.user.userProfile'])
->when($search, fn ($q) => $q->whereHas('employee.user.userProfile', fn ($uq) => $uq->where('full_name', 'like', "%{$search}%")))
->when($filters['status'] ?? null, function ($query, $status) {
$query->where('status', $status);
})
->orderBy($sort, $direction)
->paginate($perPage);
}
public function create(array $data): LeaveRequest
{
return DB::transaction(function () use ($data) {

View File

@ -3,6 +3,7 @@
namespace App\Services\Admin\Master;
use App\Models\Category;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Collection;
class CategoryService
@ -12,6 +13,15 @@ public function getAll(): Collection
return Category::select('id', 'name')->latest()->get();
}
public function paginated(int $perPage = 15, string $search = '', string $sort = 'created_at', string $direction = 'desc'): LengthAwarePaginator
{
return Category::query()
->select('id', 'name')
->when($search, fn ($q) => $q->where('name', 'like', "%{$search}%"))
->orderBy($sort, $direction)
->paginate($perPage);
}
public function create(array $data): Category
{
return Category::create($data);

View File

@ -3,6 +3,7 @@
namespace App\Services\Admin\Master;
use App\Models\Customer;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Collection;
class CustomerService
@ -12,6 +13,15 @@ public function getAll(): Collection
return Customer::select('id', 'name', 'phone_number', 'address')->latest()->get();
}
public function paginated(int $perPage = 15, string $search = '', string $sort = 'created_at', string $direction = 'desc'): LengthAwarePaginator
{
return Customer::query()
->select('id', 'name', 'phone_number', 'address')
->when($search, fn ($q) => $q->where('name', 'like', "%{$search}%"))
->orderBy($sort, $direction)
->paginate($perPage);
}
public function create(array $data): Customer
{
return Customer::create($data);

View File

@ -7,6 +7,7 @@
use App\Models\ProductVariant;
use App\Models\ProductPrice;
use App\Services\S3PresignedService;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
@ -44,6 +45,32 @@ public function getAll(array $filters = []): Collection
return $products;
}
public function paginated(int $perPage = 15, string $search = '', string $sort = 'created_at', string $direction = 'desc', array $filters = []): LengthAwarePaginator
{
$paginator = Product::query()
->select('id', 'name', 'slug', 'description', 'status')
->with([
'categories:id,name',
'productVariants:id,product_id,name,stock,reject_stock,retail_stock',
'productVariants.productPrices:id,variant_id,type,price',
])
->when($search, fn ($q) => $q->where('name', 'like', "%{$search}%"))
->when($filters['status'] ?? null, fn ($q, $status) => $q->where('status', $status))
->orderBy($sort, $direction)
->paginate($perPage);
$paginator->getCollection()->each(function ($product) {
$product->productVariants->each(function ($variant) {
$media = $variant->getMedia('photos')->first();
$variant->photo_url = $media
? $this->s3Service->getTemporaryUrl($media->file_name)
: null;
});
});
return $paginator;
}
public function create(array $data): Product
{
return DB::transaction(function () use ($data) {

View File

@ -3,6 +3,7 @@
namespace App\Services\Admin\Master;
use App\Models\Supplier;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Collection;
class SupplierService
@ -12,6 +13,15 @@ public function getAll(): Collection
return Supplier::select('id', 'name', 'phone_number', 'address')->latest()->get();
}
public function paginated(int $perPage = 15, string $search = '', string $sort = 'created_at', string $direction = 'desc'): LengthAwarePaginator
{
return Supplier::query()
->select('id', 'name', 'phone_number', 'address')
->when($search, fn ($q) => $q->where('name', 'like', "%{$search}%"))
->orderBy($sort, $direction)
->paginate($perPage);
}
public function create(array $data): Supplier
{
return Supplier::create($data);

View File

@ -2,6 +2,7 @@
namespace App\Services\Admin\Settings;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Spatie\Permission\Models\Permission;
@ -14,6 +15,15 @@ public function getAll(): Collection
return Role::withCount('permissions')->get();
}
public function paginated(int $perPage = 15, string $search = '', string $sort = 'created_at', string $direction = 'desc'): LengthAwarePaginator
{
return Role::query()
->withCount('permissions')
->when($search, fn ($q) => $q->where('name', 'like', "%{$search}%"))
->orderBy($sort, $direction)
->paginate($perPage);
}
public function getById(int $id): Role
{
return Role::with('permissions')->findOrFail($id);

View File

@ -1,35 +1,37 @@
import type { DragEndEvent } from '@dnd-kit/core';
import {
DndContext,
KeyboardSensor,
PointerSensor,
useSensor,
useSensors
} from '@dnd-kit/core';
import type { DragEndEvent } from '@dnd-kit/core';
import {
SortableContext,
useSortable,
verticalListSortingStrategy,
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import type { ColumnDef, ExpandedState, Row } from '@tanstack/react-table';
import {
flexRender,
getCoreRowModel,
getExpandedRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable
} from '@tanstack/react-table';
import type { ColumnDef, ColumnFiltersState, ExpandedState, SortingState, Row } from '@tanstack/react-table';
import { GripVertical } from 'lucide-react';
import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-react';
import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, GripVertical } from 'lucide-react';
import * as React from 'react';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import {
Table,
TableBody,
@ -39,6 +41,18 @@ import {
TableRow,
} from '@/components/ui/table';
export interface PaginationState {
current_page: number;
last_page: number;
per_page: number;
total: number;
}
export interface SortState {
column: string;
direction: 'asc' | 'desc';
}
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[];
data: TData[];
@ -50,6 +64,13 @@ interface DataTableProps<TData, TValue> {
toolbar?: React.ReactNode;
renderSubRow?: (row: Row<TData>, searchValue?: string) => React.ReactNode;
defaultExpanded?: boolean;
pagination?: PaginationState;
onPageChange?: (page: number) => void;
onPerPageChange?: (perPage: number) => void;
onSearchChange?: (search: string) => void;
onSortChange?: (column: string, direction: 'asc' | 'desc') => void;
currentSort?: SortState;
searchValue?: string;
}
const DragHandleContext = React.createContext<{
@ -108,6 +129,23 @@ function SortableTableRow({
);
}
function useDebounce(callback: (value: string) => void, delay: number) {
const timeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
return React.useCallback(
(value: string) => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = setTimeout(() => {
callback(value);
}, delay);
},
[callback, delay],
);
}
export function DataTable<TData, TValue>({
columns,
data,
@ -119,21 +157,41 @@ export function DataTable<TData, TValue>({
toolbar,
renderSubRow,
defaultExpanded = false,
pagination,
onPageChange,
onPerPageChange,
onSearchChange,
onSortChange,
currentSort,
searchValue,
}: DataTableProps<TData, TValue>) {
const [sorting, setSorting] = React.useState<SortingState>([]);
const [columnFilters, setColumnFilters] =
React.useState<ColumnFiltersState>([]);
const [expanded, setExpanded] = React.useState<ExpandedState>(() => {
if (!defaultExpanded || !data.length) return {};
if (!defaultExpanded || !data.length) {
return {};
}
const initial: Record<string, boolean> = {};
data.forEach((item, index) => {
initial[String(index)] = true;
});
return initial;
});
const [localSearch, setLocalSearch] = React.useState(searchValue ?? '');
React.useEffect(() => {
setLocalSearch(searchValue ?? '');
}, [searchValue]);
const isServerMode = !!pagination && !!onPageChange;
const isSortable = !!onReorder && !!getRowId;
const handleSearchDebounced = useDebounce(
(value: string) => onSearchChange?.(value),
300,
);
const visibleColumns = isSortable
? [
{
@ -160,16 +218,9 @@ export function DataTable<TData, TValue>({
data,
columns: visibleColumns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getExpandedRowModel: renderSubRow ? getExpandedRowModel() : undefined,
onExpandedChange: setExpanded,
onSortingChange: setSorting,
getSortedRowModel: getSortedRowModel(),
onColumnFiltersChange: setColumnFilters,
getFilteredRowModel: getFilteredRowModel(),
state: {
sorting,
columnFilters,
expanded,
},
});
@ -200,27 +251,60 @@ export function DataTable<TData, TValue>({
onReorder?.(reordered);
}
function handleSearchChange(value: string) {
setLocalSearch(value);
if (isServerMode) {
handleSearchDebounced(value);
}
}
function handleSort(columnId: string) {
if (!onSortChange) {
return;
}
const newDirection =
currentSort?.column === columnId && currentSort?.direction === 'asc'
? 'desc'
: 'asc';
onSortChange(columnId, newDirection);
}
const totalPages = pagination?.last_page ?? 1;
const currentPage = pagination?.current_page ?? 1;
return (
<>
{(searchKey || toolbar) && (
{(searchKey || toolbar || isServerMode) && (
<div className="flex items-center gap-2">
{searchKey && (
<Input
placeholder={searchPlaceholder}
value={
(table
.getColumn(searchKey)
?.getFilterValue() as string) ?? ''
}
onChange={(event) =>
table
.getColumn(searchKey)
?.setFilterValue(event.target.value)
}
value={localSearch}
onChange={(event) => handleSearchChange(event.target.value)}
className="max-w-sm"
/>
)}
{toolbar}
<div className="flex items-center gap-2 ml-auto">
{isServerMode && onPerPageChange && (
<Select
value={String(pagination?.per_page ?? 25)}
onValueChange={(value) => onPerPageChange(Number(value))}
>
<SelectTrigger className="h-8 w-[70px]">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="25">25</SelectItem>
<SelectItem value="50">50</SelectItem>
<SelectItem value="100">100</SelectItem>
<SelectItem value="999999">Semua</SelectItem>
</SelectContent>
</Select>
)}
</div>
</div>
)}
<Card className="bg-sidebar p-0">
@ -352,7 +436,7 @@ export function DataTable<TData, TValue>({
className="bg-muted/50 p-0"
>
<div className="p-4">
{renderSubRow(row, (table.getColumn(searchKey ?? '')?.getFilterValue() as string) ?? '')}
{renderSubRow(row, localSearch)}
</div>
</TableCell>
</TableRow>
@ -375,44 +459,84 @@ export function DataTable<TData, TValue>({
</CardContent>
</Card>
<div className="flex items-center justify-between">
<div className="text-sm text-muted-foreground">
Halaman {table.getState().pagination.pageIndex + 1} dari{' '}
{table.getPageCount()}
</div>
<div className="flex items-center gap-2">
<Button
variant="outline"
size="sm"
onClick={() => table.setPageIndex(0)}
disabled={!table.getCanPreviousPage()}
>
<ChevronsLeft className="h-4 w-4" />
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
<ChevronLeft className="h-4 w-4" />
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
<ChevronRight className="h-4 w-4" />
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
disabled={!table.getCanNextPage()}
>
<ChevronsRight className="h-4 w-4" />
</Button>
<div className="flex items-center justify-end gap-2">
<span className="text-sm text-muted-foreground">
{isServerMode
? `Halaman ${currentPage} dari ${totalPages}`
: `Halaman ${table.getState().pagination.pageIndex + 1} dari ${table.getPageCount()}`}
</span>
<div className="flex items-center gap-1">
{isServerMode ? (
<>
<Button
variant="outline"
size="sm"
onClick={() => onPageChange(1)}
disabled={currentPage <= 1}
>
<ChevronsLeft className="h-4 w-4" />
</Button>
<Button
variant="outline"
size="sm"
onClick={() => onPageChange(currentPage - 1)}
disabled={currentPage <= 1}
>
<ChevronLeft className="h-4 w-4" />
</Button>
<Button
variant="outline"
size="sm"
onClick={() => onPageChange(currentPage + 1)}
disabled={currentPage >= totalPages}
>
<ChevronRight className="h-4 w-4" />
</Button>
<Button
variant="outline"
size="sm"
onClick={() => onPageChange(totalPages)}
disabled={currentPage >= totalPages}
>
<ChevronsRight className="h-4 w-4" />
</Button>
</>
) : (
<>
<Button
variant="outline"
size="sm"
onClick={() => table.setPageIndex(0)}
disabled={!table.getCanPreviousPage()}
>
<ChevronsLeft className="h-4 w-4" />
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
<ChevronLeft className="h-4 w-4" />
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
<ChevronRight className="h-4 w-4" />
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
disabled={!table.getCanNextPage()}
>
<ChevronsRight className="h-4 w-4" />
</Button>
</>
)}
</div>
</div>
</>

View File

@ -1,3 +1,5 @@
import { Link } from '@inertiajs/react';
import type { PropsWithChildren } from 'react';
import Heading from '@/components/heading';
import { Button } from '@/components/ui/button';
import { Separator } from '@/components/ui/separator';
@ -8,8 +10,6 @@ import { edit as editPermissions } from '@/routes/permissions';
import { edit } from '@/routes/profile';
import { edit as editSecurity } from '@/routes/security';
import type { NavItem } from '@/types';
import { Link } from '@inertiajs/react';
import type { PropsWithChildren } from 'react';
const sidebarNavItems: NavItem[] = [
{

View File

@ -1,8 +1,9 @@
import { Form, Head, router } from '@inertiajs/react';
import { ArrowDownToLine, ArrowUpFromLine, Filter, Wallet, X } from 'lucide-react';
import { useState } from 'react';
import { useCallback, useState } from 'react';
import { ConfirmDialog } from '@/components/confirm-dialog';
import { DataTable } from '@/components/data-table';
import type { PaginationState, SortState } from '@/components/data-table';
import { FileUpload } from '@/components/file-upload';
import InputError from '@/components/input-error';
import { RupiahInput } from '@/components/rupiah-input';
@ -33,7 +34,13 @@ type CashAccount = {
type Props = {
cashAccount: CashAccount | null;
transactions: CashTransaction[];
transactions: {
data: CashTransaction[];
current_page: number;
last_page: number;
per_page: number;
total: number;
};
filters: {
type?: string;
};
@ -58,6 +65,16 @@ export default function CashAccountIndex({ cashAccount, transactions, filters }:
const [editUploading, setEditUploading] = useState(false);
const [editFileMeta, setEditFileMeta] = useState<{ size: number; type: string } | null>(null);
const [search, setSearch] = useState('');
const [sort, setSort] = useState<SortState>({ column: 'created_at', direction: 'desc' });
const pagination: PaginationState = {
current_page: transactions.current_page,
last_page: transactions.last_page,
per_page: transactions.per_page,
total: transactions.total,
};
const hasActiveFilters = filters.type;
function applyFilter(key: string, value: string) {
@ -69,17 +86,24 @@ export default function CashAccountIndex({ cashAccount, transactions, filters }:
newFilters[key as keyof typeof newFilters] = value;
}
router.get(cashAccountIndex(), newFilters, {
preserveState: true,
replace: true,
});
router.get(cashAccountIndex(), {
...newFilters,
page: 1,
per_page: pagination.per_page,
search,
sort: sort.column,
direction: sort.direction,
}, { preserveState: true, replace: true });
}
function clearFilters() {
router.get(cashAccountIndex(), {}, {
preserveState: true,
replace: true,
});
router.get(cashAccountIndex(), {
page: 1,
per_page: pagination.per_page,
search,
sort: sort.column,
direction: sort.direction,
}, { preserveState: true, replace: true });
setFilterOpen(false);
}
@ -93,6 +117,52 @@ export default function CashAccountIndex({ cashAccount, transactions, filters }:
});
}
function handlePageChange(page: number) {
router.get(cashAccountIndex(), {
...filters,
page,
per_page: pagination.per_page,
search,
sort: sort.column,
direction: sort.direction,
}, { preserveState: true, replace: true });
}
function handlePerPageChange(perPage: number) {
router.get(cashAccountIndex(), {
...filters,
page: 1,
per_page: perPage,
search,
sort: sort.column,
direction: sort.direction,
}, { preserveState: true, replace: true });
}
const handleSearchChange = useCallback((value: string) => {
setSearch(value);
router.get(cashAccountIndex(), {
...filters,
page: 1,
per_page: pagination.per_page,
search: value,
sort: sort.column,
direction: sort.direction,
}, { preserveState: true, replace: true });
}, [pagination.per_page, sort, filters]);
function handleSortChange(column: string, direction: 'asc' | 'desc') {
setSort({ column, direction });
router.get(cashAccountIndex(), {
...filters,
page: 1,
per_page: pagination.per_page,
search,
sort: column,
direction,
}, { preserveState: true, replace: true });
}
const columns = createTransactionColumns({
handleEdit: (transaction) => {
setEditing(transaction);
@ -195,15 +265,23 @@ export default function CashAccountIndex({ cashAccount, transactions, filters }:
<DataTable
columns={columns}
data={transactions}
data={transactions.data}
searchKey="description"
searchPlaceholder="Cari transaksi..."
emptyText="Belum ada riwayat transaksi."
pagination={pagination}
onPageChange={handlePageChange}
onPerPageChange={handlePerPageChange}
onSearchChange={handleSearchChange}
onSortChange={handleSortChange}
currentSort={sort}
searchValue={search}
toolbar={filterToolbar}
/>
<Dialog open={depositOpen} onOpenChange={(open) => {
setDepositOpen(open);
if (!open) {
setDepositReceiptKey(null);
setDepositFileMeta(null);
@ -273,6 +351,7 @@ export default function CashAccountIndex({ cashAccount, transactions, filters }:
<Dialog open={withdrawalOpen} onOpenChange={(open) => {
setWithdrawalOpen(open);
if (!open) {
setWithdrawalReceiptKey(null);
setWithdrawalFileMeta(null);

View File

@ -1,9 +1,12 @@
import { Form, Head, router } from '@inertiajs/react';
import { Plus } from 'lucide-react';
import { useEffect, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import { ConfirmDialog } from '@/components/confirm-dialog';
import { DataTable } from '@/components/data-table';
import type { PaginationState, SortState } from '@/components/data-table';
import { DatePicker } from '@/components/date-picker';
import InputError from '@/components/input-error';
import { RupiahInput } from '@/components/rupiah-input';
import { DatePicker } from '@/components/date-picker';
import { Button } from '@/components/ui/button';
import {
Dialog,
@ -17,11 +20,15 @@ import { Label } from '@/components/ui/label';
import { destroy, index as employeeAdvanceIndex, store, update, approve, pay } from '@/routes/admin/finance/employee-advances';
import { createEmployeeAdvanceColumns } from './columns';
import type { EmployeeAdvance } from './columns';
import { ConfirmDialog } from '@/components/confirm-dialog';
import { DataTable } from '@/components/data-table';
type Props = {
employeeAdvances: EmployeeAdvance[];
employeeAdvances: {
data: EmployeeAdvance[];
current_page: number;
last_page: number;
per_page: number;
total: number;
};
};
export default function EmployeeAdvanceIndex({ employeeAdvances }: Props) {
@ -32,6 +39,15 @@ export default function EmployeeAdvanceIndex({ employeeAdvances }: Props) {
const [paying, setPaying] = useState<EmployeeAdvance | null>(null);
const [dueDate, setDueDate] = useState<Date | undefined>(undefined);
const [editingDueDate, setEditingDueDate] = useState<Date | undefined>(undefined);
const [search, setSearch] = useState('');
const [sort, setSort] = useState<SortState>({ column: 'created_at', direction: 'desc' });
const pagination: PaginationState = {
current_page: employeeAdvances.current_page,
last_page: employeeAdvances.last_page,
per_page: employeeAdvances.per_page,
total: employeeAdvances.total,
};
useEffect(() => {
if (editing) {
@ -71,6 +87,48 @@ export default function EmployeeAdvanceIndex({ employeeAdvances }: Props) {
});
}
function handlePageChange(page: number) {
router.get(employeeAdvanceIndex.url(), {
page,
per_page: pagination.per_page,
search,
sort: sort.column,
direction: sort.direction,
}, { preserveState: true, replace: true });
}
function handlePerPageChange(perPage: number) {
router.get(employeeAdvanceIndex.url(), {
page: 1,
per_page: perPage,
search,
sort: sort.column,
direction: sort.direction,
}, { preserveState: true, replace: true });
}
const handleSearchChange = useCallback((value: string) => {
setSearch(value);
router.get(employeeAdvanceIndex.url(), {
page: 1,
per_page: pagination.per_page,
search: value,
sort: sort.column,
direction: sort.direction,
}, { preserveState: true, replace: true });
}, [pagination.per_page, sort]);
function handleSortChange(column: string, direction: 'asc' | 'desc') {
setSort({ column, direction });
router.get(employeeAdvanceIndex.url(), {
page: 1,
per_page: pagination.per_page,
search,
sort: column,
direction,
}, { preserveState: true, replace: true });
}
const columns = createEmployeeAdvanceColumns({
handleEdit: (employeeAdvance) => setEditing(employeeAdvance),
handleDeleteClick: (employeeAdvance) => setDeleting(employeeAdvance),
@ -91,6 +149,7 @@ export default function EmployeeAdvanceIndex({ employeeAdvances }: Props) {
</div>
<Dialog open={createOpen} onOpenChange={(open) => {
setCreateOpen(open);
if (!open) {
setDueDate(undefined);
}
@ -173,10 +232,17 @@ export default function EmployeeAdvanceIndex({ employeeAdvances }: Props) {
<DataTable
columns={columns}
data={employeeAdvances}
data={employeeAdvances.data}
searchKey="description"
searchPlaceholder="Cari kasbon..."
emptyText="Belum ada data kasbon."
pagination={pagination}
onPageChange={handlePageChange}
onPerPageChange={handlePerPageChange}
onSearchChange={handleSearchChange}
onSortChange={handleSortChange}
currentSort={sort}
searchValue={search}
/>
<Dialog

View File

@ -1,7 +1,8 @@
import { Form, Head, router } from '@inertiajs/react';
import { Plus } from 'lucide-react';
import { useState } from 'react';
import { useCallback, useState } from 'react';
import { ConfirmDialog } from '@/components/confirm-dialog';
import type { PaginationState, SortState } from '@/components/data-table';
import { DataTable } from '@/components/data-table';
import { FileUpload } from '@/components/file-upload';
import InputError from '@/components/input-error';
@ -21,7 +22,13 @@ import { createExpenseColumns } from './columns';
import type { Expense } from './columns';
type Props = {
expenses: Expense[];
expenses: {
data: Expense[];
current_page: number;
last_page: number;
per_page: number;
total: number;
};
};
export default function ExpenseIndex({ expenses }: Props) {
@ -34,6 +41,57 @@ export default function ExpenseIndex({ expenses }: Props) {
const [editUploading, setEditUploading] = useState(false);
const [createFileMeta, setCreateFileMeta] = useState<{ size: number; type: string } | null>(null);
const [editFileMeta, setEditFileMeta] = useState<{ size: number; type: string } | null>(null);
const [search, setSearch] = useState('');
const [sort, setSort] = useState<SortState>({ column: 'created_at', direction: 'desc' });
const pagination: PaginationState = {
current_page: expenses.current_page,
last_page: expenses.last_page,
per_page: expenses.per_page,
total: expenses.total,
};
function handlePageChange(page: number) {
router.get(expenseIndex.url(), {
page,
per_page: pagination.per_page,
search,
sort: sort.column,
direction: sort.direction,
}, { preserveState: true, replace: true });
}
function handlePerPageChange(perPage: number) {
router.get(expenseIndex.url(), {
page: 1,
per_page: perPage,
search,
sort: sort.column,
direction: sort.direction,
}, { preserveState: true, replace: true });
}
const handleSearchChange = useCallback((value: string) => {
setSearch(value);
router.get(expenseIndex.url(), {
page: 1,
per_page: pagination.per_page,
search: value,
sort: sort.column,
direction: sort.direction,
}, { preserveState: true, replace: true });
}, [pagination.per_page, sort]);
function handleSortChange(column: string, direction: 'asc' | 'desc') {
setSort({ column, direction });
router.get(expenseIndex.url(), {
page: 1,
per_page: pagination.per_page,
search,
sort: column,
direction,
}, { preserveState: true, replace: true });
}
function handleDelete() {
if (!deleting) {
@ -157,10 +215,17 @@ export default function ExpenseIndex({ expenses }: Props) {
<DataTable
columns={columns}
data={expenses}
data={expenses.data}
searchKey="description"
searchPlaceholder="Cari pengeluaran..."
emptyText="Belum ada data pengeluaran."
pagination={pagination}
onPageChange={handlePageChange}
onPerPageChange={handlePerPageChange}
onSearchChange={handleSearchChange}
onSortChange={handleSortChange}
currentSort={sort}
searchValue={search}
/>
<Dialog

View File

@ -1,6 +1,7 @@
import { Head, router } from '@inertiajs/react';
import { useState } from 'react';
import { useCallback, useState } from 'react';
import { ConfirmDialog } from '@/components/confirm-dialog';
import type { PaginationState, SortState } from '@/components/data-table';
import { DataTable } from '@/components/data-table';
import { Button } from '@/components/ui/button';
import {
@ -17,7 +18,13 @@ import { createPayrollPeriodColumns } from './columns';
import type { PayrollPeriod } from './columns';
type Props = {
payrollPeriods: PayrollPeriod[];
payrollPeriods: {
data: PayrollPeriod[];
current_page: number;
last_page: number;
per_page: number;
total: number;
};
};
const MONTH_NAMES = [
@ -28,9 +35,20 @@ const MONTH_NAMES = [
export default function PayrollPeriodIndex({ payrollPeriods }: Props) {
const [closing, setClosing] = useState<PayrollPeriod | null>(null);
const [reopening, setReopening] = useState<PayrollPeriod | null>(null);
const [search, setSearch] = useState('');
const [sort, setSort] = useState<SortState>({ column: 'created_at', direction: 'desc' });
const pagination: PaginationState = {
current_page: payrollPeriods.current_page,
last_page: payrollPeriods.last_page,
per_page: payrollPeriods.per_page,
total: payrollPeriods.total,
};
function handleClose() {
if (!closing) return;
if (!closing) {
return;
}
router.post(close(closing.id), {}, {
onSuccess: () => setClosing(null),
@ -38,13 +56,57 @@ export default function PayrollPeriodIndex({ payrollPeriods }: Props) {
}
function handleReopen() {
if (!reopening) return;
if (!reopening) {
return;
}
router.post(reopen(reopening.id), {}, {
onSuccess: () => setReopening(null),
});
}
function handlePageChange(page: number) {
router.get(payrollPeriodsIndex(), {
page,
per_page: pagination.per_page,
search,
sort: sort.column,
direction: sort.direction,
}, { preserveState: true, replace: true });
}
function handlePerPageChange(perPage: number) {
router.get(payrollPeriodsIndex(), {
page: 1,
per_page: perPage,
search,
sort: sort.column,
direction: sort.direction,
}, { preserveState: true, replace: true });
}
const handleSearchChange = useCallback((value: string) => {
setSearch(value);
router.get(payrollPeriodsIndex(), {
page: 1,
per_page: pagination.per_page,
search: value,
sort: sort.column,
direction: sort.direction,
}, { preserveState: true, replace: true });
}, [pagination.per_page, sort]);
function handleSortChange(column: string, direction: 'asc' | 'desc') {
setSort({ column, direction });
router.get(payrollPeriodsIndex(), {
page: 1,
per_page: pagination.per_page,
search,
sort: column,
direction,
}, { preserveState: true, replace: true });
}
const columns = createPayrollPeriodColumns({
showUrl: (id) => payrollPeriodShow(id).url,
handleClose: (period) => setClosing(period),
@ -66,16 +128,25 @@ export default function PayrollPeriodIndex({ payrollPeriods }: Props) {
<DataTable
columns={columns}
data={payrollPeriods}
data={payrollPeriods.data}
searchKey="year"
searchPlaceholder="Cari periode..."
emptyText="Belum ada periode gaji."
pagination={pagination}
onPageChange={handlePageChange}
onPerPageChange={handlePerPageChange}
onSearchChange={handleSearchChange}
onSortChange={handleSortChange}
currentSort={sort}
searchValue={search}
/>
<ConfirmDialog
open={closing !== null}
onOpenChange={(open) => {
if (!open) setClosing(null);
if (!open) {
setClosing(null);
}
}}
title="Tutup Periode Gaji"
description={`Apakah Anda yakin ingin menutup periode gaji ${closing ? `${MONTH_NAMES[closing.month]} ${closing.year}` : ''}? Semua gaji harus sudah dibayar sebelum periode ditutup.`}
@ -86,7 +157,9 @@ export default function PayrollPeriodIndex({ payrollPeriods }: Props) {
<ConfirmDialog
open={reopening !== null}
onOpenChange={(open) => {
if (!open) setReopening(null);
if (!open) {
setReopening(null);
}
}}
title="Buka Periode Gaji"
description={`Apakah Anda yakin ingin membuka kembali periode gaji ${reopening ? `${MONTH_NAMES[reopening.month]} ${reopening.year}` : ''}?`}

View File

@ -1,17 +1,24 @@
import { Head, router } from '@inertiajs/react';
import { Filter, Plus, X } from 'lucide-react';
import { useCallback, useState } from 'react';
import { ConfirmDialog } from '@/components/confirm-dialog';
import { DataTable } from '@/components/data-table';
import type { PaginationState, SortState } from '@/components/data-table';
import { Button } from '@/components/ui/button';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { destroy, create as employeeCreate, edit as employeeEdit, index as employeeIndex, toggleActive, resetPassword as resetPasswordRoute } from '@/routes/admin/hr/employees';
import { Head, router } from '@inertiajs/react';
import { Filter, Plus, X } from 'lucide-react';
import { useState } from 'react';
import type { Employee } from './columns';
import { createEmployeeColumns } from './columns';
type Props = {
employees: Employee[];
employees: {
data: Employee[];
current_page: number;
last_page: number;
per_page: number;
total: number;
};
filters: {
employment_status?: string;
is_active?: string;
@ -23,6 +30,15 @@ export default function EmployeeIndex({ employees, filters }: Props) {
const [deleting, setDeleting] = useState<Employee | null>(null);
const [resetPasswordTarget, setResetPasswordTarget] = useState<Employee | null>(null);
const [filterOpen, setFilterOpen] = useState(false);
const [search, setSearch] = useState('');
const [sort, setSort] = useState<SortState>({ column: 'created_at', direction: 'desc' });
const pagination: PaginationState = {
current_page: employees.current_page,
last_page: employees.last_page,
per_page: employees.per_page,
total: employees.total,
};
const hasActiveFilters = filters.employment_status || filters.is_active;
@ -35,20 +51,73 @@ export default function EmployeeIndex({ employees, filters }: Props) {
newFilters[key as keyof typeof newFilters] = value;
}
router.get(employeeIndex.url(), newFilters, {
preserveState: true,
replace: true,
});
router.get(employeeIndex.url(), {
...newFilters,
page: 1,
per_page: pagination.per_page,
search,
sort: sort.column,
direction: sort.direction,
}, { preserveState: true, replace: true });
}
function clearFilters() {
router.get(employeeIndex.url(), {}, {
preserveState: true,
replace: true,
});
router.get(employeeIndex.url(), {
page: 1,
per_page: pagination.per_page,
search,
sort: sort.column,
direction: sort.direction,
}, { preserveState: true, replace: true });
setFilterOpen(false);
}
function handlePageChange(page: number) {
router.get(employeeIndex.url(), {
...filters,
page,
per_page: pagination.per_page,
search,
sort: sort.column,
direction: sort.direction,
}, { preserveState: true, replace: true });
}
function handlePerPageChange(perPage: number) {
router.get(employeeIndex.url(), {
...filters,
page: 1,
per_page: perPage,
search,
sort: sort.column,
direction: sort.direction,
}, { preserveState: true, replace: true });
}
const handleSearchChange = useCallback((value: string) => {
setSearch(value);
router.get(employeeIndex.url(), {
...filters,
page: 1,
per_page: pagination.per_page,
search: value,
sort: sort.column,
direction: sort.direction,
}, { preserveState: true, replace: true });
}, [pagination.per_page, sort, filters]);
function handleSortChange(column: string, direction: 'asc' | 'desc') {
setSort({ column, direction });
router.get(employeeIndex.url(), {
...filters,
page: 1,
per_page: pagination.per_page,
search,
sort: column,
direction,
}, { preserveState: true, replace: true });
}
function handleDelete() {
if (!deleting) {
return;
@ -193,10 +262,17 @@ export default function EmployeeIndex({ employees, filters }: Props) {
<DataTable
columns={columns}
data={employees}
data={employees.data}
searchKey="full_name"
searchPlaceholder="Cari pegawai..."
emptyText="Belum ada data pegawai."
pagination={pagination}
onPageChange={handlePageChange}
onPerPageChange={handlePerPageChange}
onSearchChange={handleSearchChange}
onSortChange={handleSortChange}
currentSort={sort}
searchValue={search}
toolbar={filterToolbar}
/>

View File

@ -1,5 +1,9 @@
import { Form, Head, router } from '@inertiajs/react';
import { Filter, Plus, X } from 'lucide-react';
import { useCallback, useEffect, useState } from 'react';
import { ConfirmDialog } from '@/components/confirm-dialog';
import { DataTable } from '@/components/data-table';
import type { PaginationState, SortState } from '@/components/data-table';
import { DatePicker } from '@/components/date-picker';
import InputError from '@/components/input-error';
import { Button } from '@/components/ui/button';
@ -14,14 +18,17 @@ import { Label } from '@/components/ui/label';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { approve, destroy, index as leaveRequestIndex, reject, store, update } from '@/routes/admin/hr/leave-requests';
import { Form, Head, router } from '@inertiajs/react';
import { Filter, Plus, X } from 'lucide-react';
import { useEffect, useState } from 'react';
import type { LeaveRequest } from './columns';
import { createLeaveRequestColumns } from './columns';
type Props = {
leaveRequests: LeaveRequest[];
leaveRequests: {
data: LeaveRequest[];
current_page: number;
last_page: number;
per_page: number;
total: number;
};
filters: {
status?: string;
};
@ -38,6 +45,15 @@ export default function LeaveRequestIndex({ leaveRequests, filters }: Props) {
const [editingStartDate, setEditingStartDate] = useState<Date | undefined>(undefined);
const [editingEndDate, setEditingEndDate] = useState<Date | undefined>(undefined);
const [filterOpen, setFilterOpen] = useState(false);
const [search, setSearch] = useState('');
const [sort, setSort] = useState<SortState>({ column: 'created_at', direction: 'desc' });
const pagination: PaginationState = {
current_page: leaveRequests.current_page,
last_page: leaveRequests.last_page,
per_page: leaveRequests.per_page,
total: leaveRequests.total,
};
const hasActiveFilters = filters.status;
@ -60,17 +76,24 @@ export default function LeaveRequestIndex({ leaveRequests, filters }: Props) {
newFilters[key as keyof typeof newFilters] = value;
}
router.get(leaveRequestIndex(), newFilters, {
preserveState: true,
replace: true,
});
router.get(leaveRequestIndex(), {
...newFilters,
page: 1,
per_page: pagination.per_page,
search,
sort: sort.column,
direction: sort.direction,
}, { preserveState: true, replace: true });
}
function clearFilters() {
router.get(leaveRequestIndex(), {}, {
preserveState: true,
replace: true,
});
router.get(leaveRequestIndex(), {
page: 1,
per_page: pagination.per_page,
search,
sort: sort.column,
direction: sort.direction,
}, { preserveState: true, replace: true });
setFilterOpen(false);
}
@ -104,6 +127,52 @@ export default function LeaveRequestIndex({ leaveRequests, filters }: Props) {
});
}
function handlePageChange(page: number) {
router.get(leaveRequestIndex(), {
...filters,
page,
per_page: pagination.per_page,
search,
sort: sort.column,
direction: sort.direction,
}, { preserveState: true, replace: true });
}
function handlePerPageChange(perPage: number) {
router.get(leaveRequestIndex(), {
...filters,
page: 1,
per_page: perPage,
search,
sort: sort.column,
direction: sort.direction,
}, { preserveState: true, replace: true });
}
const handleSearchChange = useCallback((value: string) => {
setSearch(value);
router.get(leaveRequestIndex(), {
...filters,
page: 1,
per_page: pagination.per_page,
search: value,
sort: sort.column,
direction: sort.direction,
}, { preserveState: true, replace: true });
}, [pagination.per_page, sort, filters]);
function handleSortChange(column: string, direction: 'asc' | 'desc') {
setSort({ column, direction });
router.get(leaveRequestIndex(), {
...filters,
page: 1,
per_page: pagination.per_page,
search,
sort: column,
direction,
}, { preserveState: true, replace: true });
}
const columns = createLeaveRequestColumns({
handleEdit: (leaveRequest) => setEditing(leaveRequest),
handleDeleteClick: (leaveRequest) => setDeleting(leaveRequest),
@ -179,6 +248,7 @@ export default function LeaveRequestIndex({ leaveRequests, filters }: Props) {
</div>
<Dialog open={createOpen} onOpenChange={(open) => {
setCreateOpen(open);
if (!open) {
setStartDate(undefined);
setEndDate(undefined);
@ -257,10 +327,17 @@ export default function LeaveRequestIndex({ leaveRequests, filters }: Props) {
<DataTable
columns={columns}
data={leaveRequests}
data={leaveRequests.data}
searchKey="employee.user.user_profile.full_name"
searchPlaceholder="Cari nama karyawan..."
emptyText="Belum ada data permohonan cuti."
pagination={pagination}
onPageChange={handlePageChange}
onPerPageChange={handlePerPageChange}
onSearchChange={handleSearchChange}
onSortChange={handleSortChange}
currentSort={sort}
searchValue={search}
toolbar={filterToolbar}
/>

View File

@ -1,6 +1,9 @@
import { Form, Head, router } from '@inertiajs/react';
import { Plus } from 'lucide-react';
import { useState } from 'react';
import { useCallback, useState } from 'react';
import { ConfirmDialog } from '@/components/confirm-dialog';
import { DataTable } from '@/components/data-table';
import type { PaginationState, SortState } from '@/components/data-table';
import InputError from '@/components/input-error';
import { Button } from '@/components/ui/button';
import {
@ -15,17 +18,84 @@ import { Label } from '@/components/ui/label';
import { destroy, index as categoryIndex, store, update } from '@/routes/admin/master/categories';
import { createCategoryColumns } from './columns';
import type { Category } from './columns';
import { ConfirmDialog } from '@/components/confirm-dialog';
import { DataTable } from '@/components/data-table';
type Props = {
categories: Category[];
categories: {
data: Category[];
current_page: number;
last_page: number;
per_page: number;
total: number;
};
};
export default function CategoryIndex({ categories }: Props) {
const [createOpen, setCreateOpen] = useState(false);
const [editing, setEditing] = useState<Category | null>(null);
const [deleting, setDeleting] = useState<Category | null>(null);
const [search, setSearch] = useState('');
const [sort, setSort] = useState<SortState>({ column: 'created_at', direction: 'desc' });
const pagination: PaginationState = {
current_page: categories.current_page,
last_page: categories.last_page,
per_page: categories.per_page,
total: categories.total,
};
function handlePageChange(page: number) {
router.get(categoryIndex.url(), {
page,
per_page: pagination.per_page,
search,
sort: sort.column,
direction: sort.direction,
}, {
preserveState: true,
replace: true,
});
}
function handlePerPageChange(perPage: number) {
router.get(categoryIndex.url(), {
page: 1,
per_page: perPage,
search,
sort: sort.column,
direction: sort.direction,
}, {
preserveState: true,
replace: true,
});
}
const handleSearchChange = useCallback((value: string) => {
setSearch(value);
router.get(categoryIndex.url(), {
page: 1,
per_page: pagination.per_page,
search: value,
sort: sort.column,
direction: sort.direction,
}, {
preserveState: true,
replace: true,
});
}, [pagination.per_page, sort]);
function handleSortChange(column: string, direction: 'asc' | 'desc') {
setSort({ column, direction });
router.get(categoryIndex.url(), {
page: 1,
per_page: pagination.per_page,
search,
sort: column,
direction,
}, {
preserveState: true,
replace: true,
});
}
function handleDelete() {
if (!deleting) {
@ -112,10 +182,17 @@ export default function CategoryIndex({ categories }: Props) {
<DataTable
columns={columns}
data={categories}
data={categories.data}
searchKey="name"
searchPlaceholder="Cari kategori..."
emptyText="Belum ada data kategori."
pagination={pagination}
onPageChange={handlePageChange}
onPerPageChange={handlePerPageChange}
onSearchChange={handleSearchChange}
onSortChange={handleSortChange}
currentSort={sort}
searchValue={search}
/>
<Dialog

View File

@ -1,5 +1,9 @@
import { Form, Head, router } from '@inertiajs/react';
import { Plus } from 'lucide-react';
import { useCallback, useState } from 'react';
import { ConfirmDialog } from '@/components/confirm-dialog';
import { DataTable } from '@/components/data-table';
import type { PaginationState, SortState } from '@/components/data-table';
import InputError from '@/components/input-error';
import { PhoneNumberInput } from '@/components/phone-number-input';
import { Button } from '@/components/ui/button';
@ -13,20 +17,86 @@ import {
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { index as customerIndex, destroy, store, update } from '@/routes/admin/master/customers';
import { Form, Head, router } from '@inertiajs/react';
import { Plus } from 'lucide-react';
import { useState } from 'react';
import type { Customer } from './columns';
import { createCustomerColumns } from './columns';
type Props = {
customers: Customer[];
customers: {
data: Customer[];
current_page: number;
last_page: number;
per_page: number;
total: number;
};
};
export default function CustomerIndex({ customers }: Props) {
const [createOpen, setCreateOpen] = useState(false);
const [editing, setEditing] = useState<Customer | null>(null);
const [deleting, setDeleting] = useState<Customer | null>(null);
const [search, setSearch] = useState('');
const [sort, setSort] = useState<SortState>({ column: 'created_at', direction: 'desc' });
const pagination: PaginationState = {
current_page: customers.current_page,
last_page: customers.last_page,
per_page: customers.per_page,
total: customers.total,
};
function handlePageChange(page: number) {
router.get(customerIndex.url(), {
page,
per_page: pagination.per_page,
search,
sort: sort.column,
direction: sort.direction,
}, {
preserveState: true,
replace: true,
});
}
function handlePerPageChange(perPage: number) {
router.get(customerIndex.url(), {
page: 1,
per_page: perPage,
search,
sort: sort.column,
direction: sort.direction,
}, {
preserveState: true,
replace: true,
});
}
const handleSearchChange = useCallback((value: string) => {
setSearch(value);
router.get(customerIndex.url(), {
page: 1,
per_page: pagination.per_page,
search: value,
sort: sort.column,
direction: sort.direction,
}, {
preserveState: true,
replace: true,
});
}, [pagination.per_page, sort]);
function handleSortChange(column: string, direction: 'asc' | 'desc') {
setSort({ column, direction });
router.get(customerIndex.url(), {
page: 1,
per_page: pagination.per_page,
search,
sort: column,
direction,
}, {
preserveState: true,
replace: true,
});
}
function handleDelete() {
if (!deleting) {
@ -131,10 +201,17 @@ export default function CustomerIndex({ customers }: Props) {
<DataTable
columns={columns}
data={customers}
data={customers.data}
searchKey="name"
searchPlaceholder="Cari customer..."
emptyText="Belum ada data customer."
pagination={pagination}
onPageChange={handlePageChange}
onPerPageChange={handlePerPageChange}
onSearchChange={handleSearchChange}
onSortChange={handleSortChange}
currentSort={sort}
searchValue={search}
/>
<Dialog

View File

@ -1,4 +1,9 @@
import { Head, router } from '@inertiajs/react';
import type { Row } from '@tanstack/react-table';
import { Filter, Plus, X } from 'lucide-react';
import { useCallback, useMemo, useState } from 'react';
import { ConfirmDialog } from '@/components/confirm-dialog';
import type { PaginationState, SortState } from '@/components/data-table';
import { DataTable } from '@/components/data-table';
import { ImagePreviewModal } from '@/components/image-preview-modal';
import { Button } from '@/components/ui/button';
@ -14,15 +19,17 @@ import {
TableRow,
} from '@/components/ui/table';
import { destroy, create as productCreate, index as productIndex, edit as productEdit, toggleStatus } from '@/routes/admin/master/products';
import { Head, router } from '@inertiajs/react';
import type { Row } from '@tanstack/react-table';
import { Filter, Plus, X } from 'lucide-react';
import { useMemo, useState } from 'react';
import type { Product } from './columns';
import { createProductColumns } from './columns';
type Props = {
products: Product[];
products: {
data: Product[];
current_page: number;
last_page: number;
per_page: number;
total: number;
};
filters: {
status?: string;
name?: string;
@ -134,13 +141,23 @@ function VariantSubRow({ row, searchValue }: { row: Row<Product>; searchValue?:
export default function ProductIndex({ products, filters }: Props) {
const [deleting, setDeleting] = useState<Product | null>(null);
const [filterOpen, setFilterOpen] = useState(false);
const [search, setSearch] = useState('');
const [sort, setSort] = useState<SortState>({ column: 'created_at', direction: 'desc' });
const hasActiveFilters = filters.status || filters.name;
const pagination: PaginationState = {
current_page: products.current_page,
last_page: products.last_page,
per_page: products.per_page,
total: products.total,
};
const productNames = useMemo(() => {
const names = products.map((p) => p.name);
const names = products.data.map((p) => p.name);
return [...new Set(names)].sort();
}, [products]);
}, [products.data]);
function applyFilter(key: string, value: string) {
const newFilters = { ...filters };
@ -165,6 +182,52 @@ export default function ProductIndex({ products, filters }: Props) {
setFilterOpen(false);
}
function handlePageChange(page: number) {
router.get(productIndex.url(), {
page,
per_page: pagination.per_page,
search,
sort: sort.column,
direction: sort.direction,
...filters,
}, { preserveState: true, replace: true });
}
function handlePerPageChange(perPage: number) {
router.get(productIndex.url(), {
page: 1,
per_page: perPage,
search,
sort: sort.column,
direction: sort.direction,
...filters,
}, { preserveState: true, replace: true });
}
const handleSearchChange = useCallback((value: string) => {
setSearch(value);
router.get(productIndex.url(), {
page: 1,
per_page: pagination.per_page,
search: value,
sort: sort.column,
direction: sort.direction,
...filters,
}, { preserveState: true, replace: true });
}, [pagination.per_page, sort, filters]);
function handleSortChange(column: string, direction: 'asc' | 'desc') {
setSort({ column, direction });
router.get(productIndex.url(), {
page: 1,
per_page: pagination.per_page,
search,
sort: column,
direction,
...filters,
}, { preserveState: true, replace: true });
}
function handleDelete() {
if (!deleting) {
return;
@ -280,10 +343,17 @@ export default function ProductIndex({ products, filters }: Props) {
<DataTable
columns={columns}
data={products}
searchKey="variant_names"
searchPlaceholder="Cari varian..."
data={products.data}
searchKey="name"
searchPlaceholder="Cari produk..."
emptyText="Belum ada data produk."
pagination={pagination}
onPageChange={handlePageChange}
onPerPageChange={handlePerPageChange}
onSearchChange={handleSearchChange}
onSortChange={handleSortChange}
currentSort={sort}
searchValue={search}
renderSubRow={(row, searchValue) => <VariantSubRow row={row} searchValue={searchValue} />}
defaultExpanded
toolbar={filterToolbar}

View File

@ -1,5 +1,9 @@
import { Form, Head, router } from '@inertiajs/react';
import { Plus } from 'lucide-react';
import { useCallback, useState } from 'react';
import { ConfirmDialog } from '@/components/confirm-dialog';
import { DataTable } from '@/components/data-table';
import type { PaginationState, SortState } from '@/components/data-table';
import InputError from '@/components/input-error';
import { PhoneNumberInput } from '@/components/phone-number-input';
import { Button } from '@/components/ui/button';
@ -13,20 +17,86 @@ import {
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { destroy, store, index as supplierIndex, update } from '@/routes/admin/master/suppliers';
import { Form, Head, router } from '@inertiajs/react';
import { Plus } from 'lucide-react';
import { useState } from 'react';
import type { Supplier } from './columns';
import { createSupplierColumns } from './columns';
type Props = {
suppliers: Supplier[];
suppliers: {
data: Supplier[];
current_page: number;
last_page: number;
per_page: number;
total: number;
};
};
export default function SupplierIndex({ suppliers }: Props) {
const [createOpen, setCreateOpen] = useState(false);
const [editing, setEditing] = useState<Supplier | null>(null);
const [deleting, setDeleting] = useState<Supplier | null>(null);
const [search, setSearch] = useState('');
const [sort, setSort] = useState<SortState>({ column: 'created_at', direction: 'desc' });
const pagination: PaginationState = {
current_page: suppliers.current_page,
last_page: suppliers.last_page,
per_page: suppliers.per_page,
total: suppliers.total,
};
function handlePageChange(page: number) {
router.get(supplierIndex.url(), {
page,
per_page: pagination.per_page,
search,
sort: sort.column,
direction: sort.direction,
}, {
preserveState: true,
replace: true,
});
}
function handlePerPageChange(perPage: number) {
router.get(supplierIndex.url(), {
page: 1,
per_page: perPage,
search,
sort: sort.column,
direction: sort.direction,
}, {
preserveState: true,
replace: true,
});
}
const handleSearchChange = useCallback((value: string) => {
setSearch(value);
router.get(supplierIndex.url(), {
page: 1,
per_page: pagination.per_page,
search: value,
sort: sort.column,
direction: sort.direction,
}, {
preserveState: true,
replace: true,
});
}, [pagination.per_page, sort]);
function handleSortChange(column: string, direction: 'asc' | 'desc') {
setSort({ column, direction });
router.get(supplierIndex.url(), {
page: 1,
per_page: pagination.per_page,
search,
sort: column,
direction,
}, {
preserveState: true,
replace: true,
});
}
function handleDelete() {
if (!deleting) {
@ -131,10 +201,17 @@ export default function SupplierIndex({ suppliers }: Props) {
<DataTable
columns={columns}
data={suppliers}
data={suppliers.data}
searchKey="name"
searchPlaceholder="Cari supplier..."
emptyText="Belum ada data supplier."
pagination={pagination}
onPageChange={handlePageChange}
onPerPageChange={handlePerPageChange}
onSearchChange={handleSearchChange}
onSortChange={handleSortChange}
currentSort={sort}
searchValue={search}
/>
<Dialog

View File

@ -1,18 +1,77 @@
import { ConfirmDialog } from '@/components/confirm-dialog';
import { DataTable } from '@/components/data-table';
import { Button } from '@/components/ui/button';
import { index as rolesIndex, create as roleCreate, edit as roleEdit, destroy as roleDestroy } from '@/routes/admin/settings/roles';
import { Head, router } from '@inertiajs/react';
import { Plus } from 'lucide-react';
import { useState } from 'react';
import { createRoleColumns, type Role } from './columns';
import { useCallback, useState } from 'react';
import { ConfirmDialog } from '@/components/confirm-dialog';
import { DataTable } from '@/components/data-table';
import type {PaginationState, SortState} from '@/components/data-table';
import { Button } from '@/components/ui/button';
import { index as rolesIndex, create as roleCreate, edit as roleEdit, destroy as roleDestroy } from '@/routes/admin/settings/roles';
import { createRoleColumns } from './columns';
import type {Role} from './columns';
type Props = {
roles: Role[];
roles: {
data: Role[];
current_page: number;
last_page: number;
per_page: number;
total: number;
};
};
export default function RoleIndex({ roles }: Props) {
const [deleting, setDeleting] = useState<Role | null>(null);
const [search, setSearch] = useState('');
const [sort, setSort] = useState<SortState>({ column: 'created_at', direction: 'desc' });
const pagination: PaginationState = {
current_page: roles.current_page,
last_page: roles.last_page,
per_page: roles.per_page,
total: roles.total,
};
function handlePageChange(page: number) {
router.get(rolesIndex.url(), {
page,
per_page: pagination.per_page,
search,
sort: sort.column,
direction: sort.direction,
}, { preserveState: true, replace: true });
}
function handlePerPageChange(perPage: number) {
router.get(rolesIndex.url(), {
page: 1,
per_page: perPage,
search,
sort: sort.column,
direction: sort.direction,
}, { preserveState: true, replace: true });
}
const handleSearchChange = useCallback((value: string) => {
setSearch(value);
router.get(rolesIndex.url(), {
page: 1,
per_page: pagination.per_page,
search: value,
sort: sort.column,
direction: sort.direction,
}, { preserveState: true, replace: true });
}, [pagination.per_page, sort]);
function handleSortChange(column: string, direction: 'asc' | 'desc') {
setSort({ column, direction });
router.get(rolesIndex.url(), {
page: 1,
per_page: pagination.per_page,
search,
sort: column,
direction,
}, { preserveState: true, replace: true });
}
function handleDelete() {
if (!deleting) {
@ -52,10 +111,17 @@ export default function RoleIndex({ roles }: Props) {
<DataTable
columns={columns}
data={roles}
data={roles.data}
searchKey="name"
searchPlaceholder="Cari role..."
emptyText="Belum ada data role."
pagination={pagination}
onPageChange={handlePageChange}
onPerPageChange={handlePerPageChange}
onSearchChange={handleSearchChange}
onSortChange={handleSortChange}
currentSort={sort}
searchValue={search}
/>
<ConfirmDialog

View File

@ -45,7 +45,9 @@
$response->assertInertia(fn (Assert $page) => $page
->component('admin/finance/cash-account/index')
->has('cashAccount')
->has('transactions')
->has('transactions.data')
->where('transactions.current_page', 1)
->where('transactions.per_page', 25)
);
});
@ -57,7 +59,10 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/finance/cash-account/index')
->has('transactions', 0)
->has('transactions.data', 0)
->where('transactions.total', 0)
->where('transactions.current_page', 1)
->where('transactions.per_page', 25)
);
});
@ -118,7 +123,10 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/finance/cash-account/index')
->has('transactions', 2)
->has('transactions.data', 2)
->where('transactions.total', 2)
->where('transactions.current_page', 1)
->where('transactions.per_page', 25)
);
});
@ -159,7 +167,10 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/finance/cash-account/index')
->has('transactions', 2)
->has('transactions.data', 2)
->where('transactions.total', 2)
->where('transactions.current_page', 1)
->where('transactions.per_page', 25)
);
});
@ -191,7 +202,10 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/finance/cash-account/index')
->has('transactions', 1)
->has('transactions.data', 1)
->where('transactions.total', 1)
->where('transactions.current_page', 1)
->where('transactions.per_page', 25)
);
});
@ -223,7 +237,10 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/finance/cash-account/index')
->has('transactions', 1)
->has('transactions.data', 1)
->where('transactions.total', 1)
->where('transactions.current_page', 1)
->where('transactions.per_page', 25)
);
});
@ -264,7 +281,10 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/finance/cash-account/index')
->has('transactions', 3)
->has('transactions.data', 3)
->where('transactions.total', 3)
->where('transactions.current_page', 1)
->where('transactions.per_page', 25)
);
});
@ -309,7 +329,10 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/finance/cash-account/index')
->has('transactions', 0)
->has('transactions.data', 0)
->where('transactions.total', 0)
->where('transactions.current_page', 1)
->where('transactions.per_page', 25)
);
});

View File

@ -39,7 +39,9 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/finance/expense/index')
->has('expenses')
->has('expenses.data')
->where('expenses.current_page', 1)
->where('expenses.per_page', 25)
);
});

View File

@ -55,7 +55,10 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/hr/attendance/index')
->has('attendances', 1)
->has('attendances.data', 1)
->where('attendances.total', 1)
->where('attendances.current_page', 1)
->where('attendances.per_page', 25)
);
});
@ -67,7 +70,10 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/hr/attendance/index')
->has('attendances', 0)
->has('attendances.data', 0)
->where('attendances.total', 0)
->where('attendances.current_page', 1)
->where('attendances.per_page', 25)
);
});
@ -106,7 +112,10 @@
$response->assertInertia(fn (Assert $page) => $page
->where('currentYear', 2024)
->where('currentMonth', 3)
->has('attendances', 1)
->has('attendances.data', 1)
->where('attendances.total', 1)
->where('attendances.current_page', 1)
->where('attendances.per_page', 25)
);
});
@ -130,7 +139,10 @@
$response = $this->get(route('admin.hr.attendances.index', ['year' => 2024, 'month' => 4]));
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->has('attendances', 0)
->has('attendances.data', 0)
->where('attendances.total', 0)
->where('attendances.current_page', 1)
->where('attendances.per_page', 25)
);
});
@ -620,7 +632,10 @@
$response = $this->get(route('admin.hr.attendances.index', ['year' => 2024, 'month' => 1]));
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->has('attendances', 2)
->has('attendances.data', 2)
->where('attendances.total', 2)
->where('attendances.current_page', 1)
->where('attendances.per_page', 25)
);
});
@ -656,7 +671,10 @@
$response = $this->get(route('admin.hr.attendances.index', ['year' => 2024, 'month' => 1]));
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->has('attendances', 3)
->has('attendances.data', 3)
->where('attendances.total', 3)
->where('attendances.current_page', 1)
->where('attendances.per_page', 25)
);
});

View File

@ -51,7 +51,10 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/hr/employee/index')
->has('employees', 1)
->has('employees.data', 1)
->where('employees.total', 1)
->where('employees.current_page', 1)
->where('employees.per_page', 25)
);
});
@ -63,7 +66,10 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/hr/employee/index')
->has('employees', 0)
->has('employees.data', 0)
->where('employees.total', 0)
->where('employees.current_page', 1)
->where('employees.per_page', 25)
);
});
@ -77,7 +83,10 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/hr/employee/index')
->has('employees', 0)
->has('employees.data', 0)
->where('employees.total', 0)
->where('employees.current_page', 1)
->where('employees.per_page', 25)
);
});
@ -99,7 +108,10 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/hr/employee/index')
->has('employees', 1)
->has('employees.data', 1)
->where('employees.total', 1)
->where('employees.current_page', 1)
->where('employees.per_page', 25)
);
});
@ -125,8 +137,11 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/hr/employee/index')
->has('employees', 1)
->where('employees.0.user_profile.full_name', 'Full Time Employee')
->has('employees.data', 1)
->where('employees.total', 1)
->where('employees.current_page', 1)
->where('employees.per_page', 25)
->where('employees.data.0.user_profile.full_name', 'Full Time Employee')
);
});
@ -146,8 +161,11 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/hr/employee/index')
->has('employees', 1)
->where('employees.0.user_profile.full_name', 'Contract Employee')
->has('employees.data', 1)
->where('employees.total', 1)
->where('employees.current_page', 1)
->where('employees.per_page', 25)
->where('employees.data.0.user_profile.full_name', 'Contract Employee')
);
});
@ -167,8 +185,11 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/hr/employee/index')
->has('employees', 1)
->where('employees.0.user_profile.full_name', 'Resigned Employee')
->has('employees.data', 1)
->where('employees.total', 1)
->where('employees.current_page', 1)
->where('employees.per_page', 25)
->where('employees.data.0.user_profile.full_name', 'Resigned Employee')
);
});
@ -194,8 +215,11 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/hr/employee/index')
->has('employees', 1)
->where('employees.0.user_profile.full_name', 'Active Employee')
->has('employees.data', 1)
->where('employees.total', 1)
->where('employees.current_page', 1)
->where('employees.per_page', 25)
->where('employees.data.0.user_profile.full_name', 'Active Employee')
);
});
@ -215,8 +239,11 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/hr/employee/index')
->has('employees', 1)
->where('employees.0.user_profile.full_name', 'Inactive Employee')
->has('employees.data', 1)
->where('employees.total', 1)
->where('employees.current_page', 1)
->where('employees.per_page', 25)
->where('employees.data.0.user_profile.full_name', 'Inactive Employee')
);
});
@ -242,8 +269,11 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/hr/employee/index')
->has('employees', 1)
->where('employees.0.user_profile.full_name', 'Male Employee')
->has('employees.data', 1)
->where('employees.total', 1)
->where('employees.current_page', 1)
->where('employees.per_page', 25)
->where('employees.data.0.user_profile.full_name', 'Male Employee')
);
});
@ -263,8 +293,11 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/hr/employee/index')
->has('employees', 1)
->where('employees.0.user_profile.full_name', 'Female Employee')
->has('employees.data', 1)
->where('employees.total', 1)
->where('employees.current_page', 1)
->where('employees.per_page', 25)
->where('employees.data.0.user_profile.full_name', 'Female Employee')
);
});
@ -302,8 +335,11 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/hr/employee/index')
->has('employees', 1)
->where('employees.0.user_profile.full_name', 'Match Employee')
->has('employees.data', 1)
->where('employees.total', 1)
->where('employees.current_page', 1)
->where('employees.per_page', 25)
->where('employees.data.0.user_profile.full_name', 'Match Employee')
);
});
@ -1018,7 +1054,10 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/hr/employee/index')
->has('employees', 1)
->has('employees.data', 1)
->where('employees.total', 1)
->where('employees.current_page', 1)
->where('employees.per_page', 25)
);
});

View File

@ -44,7 +44,10 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/hr/leave-request/index')
->has('leaveRequests', 3)
->has('leaveRequests.data', 3)
->where('leaveRequests.total', 3)
->where('leaveRequests.current_page', 1)
->where('leaveRequests.per_page', 25)
);
});
@ -56,7 +59,10 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/hr/leave-request/index')
->has('leaveRequests', 0)
->has('leaveRequests.data', 0)
->where('leaveRequests.total', 0)
->where('leaveRequests.current_page', 1)
->where('leaveRequests.per_page', 25)
);
});
@ -73,7 +79,10 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/hr/leave-request/index')
->has('leaveRequests', 1)
->has('leaveRequests.data', 1)
->where('leaveRequests.total', 1)
->where('leaveRequests.current_page', 1)
->where('leaveRequests.per_page', 25)
);
});
@ -95,7 +104,10 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/hr/leave-request/index')
->has('leaveRequests', 2)
->has('leaveRequests.data', 2)
->where('leaveRequests.total', 2)
->where('leaveRequests.current_page', 1)
->where('leaveRequests.per_page', 25)
);
});
@ -111,7 +123,10 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/hr/leave-request/index')
->has('leaveRequests', 2)
->has('leaveRequests.data', 2)
->where('leaveRequests.total', 2)
->where('leaveRequests.current_page', 1)
->where('leaveRequests.per_page', 25)
);
});
@ -127,7 +142,10 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/hr/leave-request/index')
->has('leaveRequests', 2)
->has('leaveRequests.data', 2)
->where('leaveRequests.total', 2)
->where('leaveRequests.current_page', 1)
->where('leaveRequests.per_page', 25)
);
});
@ -143,7 +161,10 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/hr/leave-request/index')
->has('leaveRequests', 2)
->has('leaveRequests.data', 2)
->where('leaveRequests.total', 2)
->where('leaveRequests.current_page', 1)
->where('leaveRequests.per_page', 25)
);
});
@ -160,7 +181,10 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/hr/leave-request/index')
->has('leaveRequests', 4)
->has('leaveRequests.data', 4)
->where('leaveRequests.total', 4)
->where('leaveRequests.current_page', 1)
->where('leaveRequests.per_page', 25)
);
});
@ -196,7 +220,10 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/hr/leave-request/index')
->has('leaveRequests', 0)
->has('leaveRequests.data', 0)
->where('leaveRequests.total', 0)
->where('leaveRequests.current_page', 1)
->where('leaveRequests.per_page', 25)
);
});
@ -795,7 +822,10 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/hr/leave-request/index')
->has('leaveRequests', 1)
->has('leaveRequests.data', 1)
->where('leaveRequests.total', 1)
->where('leaveRequests.current_page', 1)
->where('leaveRequests.per_page', 25)
);
});
@ -827,7 +857,10 @@
$response = $this->get(route('admin.hr.leave-requests.index', ['status' => 'pending']));
$response->assertInertia(fn (Assert $page) => $page
->has('leaveRequests', 2)
->has('leaveRequests.data', 2)
->where('leaveRequests.total', 2)
->where('leaveRequests.current_page', 1)
->where('leaveRequests.per_page', 25)
);
$leaveRequest = LeaveRequest::where('status', LeaveRequestStatus::PENDING)->first();
@ -835,11 +868,17 @@
$response = $this->get(route('admin.hr.leave-requests.index', ['status' => 'pending']));
$response->assertInertia(fn (Assert $page) => $page
->has('leaveRequests', 1)
->has('leaveRequests.data', 1)
->where('leaveRequests.total', 1)
->where('leaveRequests.current_page', 1)
->where('leaveRequests.per_page', 25)
);
$response = $this->get(route('admin.hr.leave-requests.index', ['status' => 'approved']));
$response->assertInertia(fn (Assert $page) => $page
->has('leaveRequests', 2)
->has('leaveRequests.data', 2)
->where('leaveRequests.total', 2)
->where('leaveRequests.current_page', 1)
->where('leaveRequests.per_page', 25)
);
});

View File

@ -45,7 +45,10 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/category/index')
->has('categories', 3)
->has('categories.data', 3)
->where('categories.total', 3)
->where('categories.current_page', 1)
->where('categories.per_page', 25)
);
});
@ -57,7 +60,10 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/category/index')
->has('categories', 0)
->has('categories.data', 0)
->where('categories.total', 0)
->where('categories.current_page', 1)
->where('categories.per_page', 25)
);
});
@ -72,8 +78,11 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/category/index')
->has('categories', 1)
->where('categories.0.name', 'Active Category')
->has('categories.data', 1)
->where('categories.total', 1)
->where('categories.current_page', 1)
->where('categories.per_page', 25)
->where('categories.data.0.name', 'Active Category')
);
});
@ -90,7 +99,10 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/category/index')
->has('categories', 4)
->has('categories.data', 4)
->where('categories.total', 4)
->where('categories.current_page', 1)
->where('categories.per_page', 25)
);
});
@ -690,8 +702,11 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/category/index')
->has('categories', 1)
->where('categories.0.name', 'Active')
->has('categories.data', 1)
->where('categories.total', 1)
->where('categories.current_page', 1)
->where('categories.per_page', 25)
->where('categories.data.0.name', 'Active')
);
});
@ -820,7 +835,10 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/category/index')
->has('categories', 1)
->has('categories.data', 1)
->where('categories.total', 1)
->where('categories.current_page', 1)
->where('categories.per_page', 25)
);
});
@ -1267,7 +1285,10 @@
$response = $this->get(route('admin.master.categories.index'));
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/category/index')
->has('categories', 0)
->has('categories.data', 0)
->where('categories.total', 0)
->where('categories.current_page', 1)
->where('categories.per_page', 25)
);
$category->restore();
@ -1275,8 +1296,11 @@
$response = $this->get(route('admin.master.categories.index'));
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/category/index')
->has('categories', 1)
->where('categories.0.name', 'Restored Category')
->has('categories.data', 1)
->where('categories.total', 1)
->where('categories.current_page', 1)
->where('categories.per_page', 25)
->where('categories.data.0.name', 'Restored Category')
);
});
@ -1333,7 +1357,10 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/category/index')
->has('categories', 2)
->has('categories.data', 2)
->where('categories.total', 2)
->where('categories.current_page', 1)
->where('categories.per_page', 25)
);
});

View File

@ -43,7 +43,10 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/customer/index')
->has('customers', 3)
->has('customers.data', 3)
->where('customers.total', 3)
->where('customers.current_page', 1)
->where('customers.per_page', 25)
);
});
@ -55,7 +58,10 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/customer/index')
->has('customers', 0)
->has('customers.data', 0)
->where('customers.total', 0)
->where('customers.current_page', 1)
->where('customers.per_page', 25)
);
});
@ -70,8 +76,11 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/customer/index')
->has('customers', 1)
->where('customers.0.name', 'Active Customer')
->has('customers.data', 1)
->where('customers.total', 1)
->where('customers.current_page', 1)
->where('customers.per_page', 25)
->where('customers.data.0.name', 'Active Customer')
);
});
@ -88,7 +97,10 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/customer/index')
->has('customers', 4)
->has('customers.data', 4)
->where('customers.total', 4)
->where('customers.current_page', 1)
->where('customers.per_page', 25)
);
});
@ -1078,8 +1090,11 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/customer/index')
->has('customers', 1)
->where('customers.0.name', 'Active')
->has('customers.data', 1)
->where('customers.total', 1)
->where('customers.current_page', 1)
->where('customers.per_page', 25)
->where('customers.data.0.name', 'Active')
);
});
@ -1174,7 +1189,10 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/customer/index')
->has('customers', 1)
->has('customers.data', 1)
->where('customers.total', 1)
->where('customers.current_page', 1)
->where('customers.per_page', 25)
);
});
@ -1305,7 +1323,10 @@
$response = $this->get(route('admin.master.customers.index'));
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/customer/index')
->has('customers', 0)
->has('customers.data', 0)
->where('customers.total', 0)
->where('customers.current_page', 1)
->where('customers.per_page', 25)
);
$customer->restore();
@ -1313,8 +1334,11 @@
$response = $this->get(route('admin.master.customers.index'));
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/customer/index')
->has('customers', 1)
->where('customers.0.name', 'Restored Customer')
->has('customers.data', 1)
->where('customers.total', 1)
->where('customers.current_page', 1)
->where('customers.per_page', 25)
->where('customers.data.0.name', 'Restored Customer')
);
});
@ -1370,7 +1394,10 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/customer/index')
->has('customers', 2)
->has('customers.data', 2)
->where('customers.total', 2)
->where('customers.current_page', 1)
->where('customers.per_page', 25)
);
});

View File

@ -190,7 +190,10 @@ function allPriceTypes(): array
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/product/index')
->has('products', 3)
->has('products.data', 3)
->where('products.total', 3)
->where('products.current_page', 1)
->where('products.per_page', 25)
);
});
@ -202,7 +205,10 @@ function allPriceTypes(): array
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/product/index')
->has('products', 0)
->has('products.data', 0)
->where('products.total', 0)
->where('products.current_page', 1)
->where('products.per_page', 25)
);
});
@ -217,8 +223,11 @@ function allPriceTypes(): array
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/product/index')
->has('products', 1)
->where('products.0.name', 'Active Product')
->has('products.data', 1)
->where('products.total', 1)
->where('products.current_page', 1)
->where('products.per_page', 25)
->where('products.data.0.name', 'Active Product')
);
});
@ -235,7 +244,10 @@ function allPriceTypes(): array
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/product/index')
->has('products', 4)
->has('products.data', 4)
->where('products.total', 4)
->where('products.current_page', 1)
->where('products.per_page', 25)
);
});
@ -254,7 +266,7 @@ function allPriceTypes(): array
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/product/index')
->has('products.0.categories', 1)
->has('products.data.0.categories', 1)
);
});
@ -269,7 +281,7 @@ function allPriceTypes(): array
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/product/index')
->has('products.0.product_variants', 1)
->has('products.data.0.product_variants', 1)
);
});
@ -285,7 +297,7 @@ function allPriceTypes(): array
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/product/index')
->has('products.0.product_variants.0.product_prices', 1)
->has('products.data.0.product_variants.0.product_prices', 1)
);
});
@ -1775,7 +1787,10 @@ function allPriceTypes(): array
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/product/index')
->has('products', 2)
->has('products.data', 2)
->where('products.total', 2)
->where('products.current_page', 1)
->where('products.per_page', 25)
);
});

View File

@ -43,7 +43,10 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/supplier/index')
->has('suppliers', 3)
->has('suppliers.data', 3)
->where('suppliers.total', 3)
->where('suppliers.current_page', 1)
->where('suppliers.per_page', 25)
);
});
@ -55,7 +58,10 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/supplier/index')
->has('suppliers', 0)
->has('suppliers.data', 0)
->where('suppliers.total', 0)
->where('suppliers.current_page', 1)
->where('suppliers.per_page', 25)
);
});
@ -70,8 +76,11 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/supplier/index')
->has('suppliers', 1)
->where('suppliers.0.name', 'Active Supplier')
->has('suppliers.data', 1)
->where('suppliers.total', 1)
->where('suppliers.current_page', 1)
->where('suppliers.per_page', 25)
->where('suppliers.data.0.name', 'Active Supplier')
);
});
@ -88,7 +97,10 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/supplier/index')
->has('suppliers', 4)
->has('suppliers.data', 4)
->where('suppliers.total', 4)
->where('suppliers.current_page', 1)
->where('suppliers.per_page', 25)
);
});
@ -1078,8 +1090,11 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/supplier/index')
->has('suppliers', 1)
->where('suppliers.0.name', 'Active')
->has('suppliers.data', 1)
->where('suppliers.total', 1)
->where('suppliers.current_page', 1)
->where('suppliers.per_page', 25)
->where('suppliers.data.0.name', 'Active')
);
});
@ -1174,7 +1189,10 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/supplier/index')
->has('suppliers', 1)
->has('suppliers.data', 1)
->where('suppliers.total', 1)
->where('suppliers.current_page', 1)
->where('suppliers.per_page', 25)
);
});
@ -1305,7 +1323,10 @@
$response = $this->get(route('admin.master.suppliers.index'));
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/supplier/index')
->has('suppliers', 0)
->has('suppliers.data', 0)
->where('suppliers.total', 0)
->where('suppliers.current_page', 1)
->where('suppliers.per_page', 25)
);
$supplier->restore();
@ -1313,8 +1334,11 @@
$response = $this->get(route('admin.master.suppliers.index'));
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/supplier/index')
->has('suppliers', 1)
->where('suppliers.0.name', 'Restored Supplier')
->has('suppliers.data', 1)
->where('suppliers.total', 1)
->where('suppliers.current_page', 1)
->where('suppliers.per_page', 25)
->where('suppliers.data.0.name', 'Restored Supplier')
);
});
@ -1370,7 +1394,10 @@
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/master/supplier/index')
->has('suppliers', 2)
->has('suppliers.data', 2)
->where('suppliers.total', 2)
->where('suppliers.current_page', 1)
->where('suppliers.per_page', 25)
);
});

View File

@ -90,7 +90,10 @@ function createUserWithRole(string $roleName = 'Developer'): User
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/roles/index')
->has('roles', 3)
->has('roles.data', 3)
->where('roles.total', 3)
->where('roles.current_page', 1)
->where('roles.per_page', 25)
);
});
@ -101,7 +104,10 @@ function createUserWithRole(string $roleName = 'Developer'): User
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/roles/index')
->has('roles', 1)
->has('roles.data', 1)
->where('roles.total', 1)
->where('roles.current_page', 1)
->where('roles.per_page', 25)
);
});
@ -116,7 +122,10 @@ function createUserWithRole(string $roleName = 'Developer'): User
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/roles/index')
->has('roles', 1)
->has('roles.data', 1)
->where('roles.total', 1)
->where('roles.current_page', 1)
->where('roles.per_page', 25)
);
});
@ -736,7 +745,10 @@ function createUserWithRole(string $roleName = 'Developer'): User
$response->assertOk();
$response->assertInertia(fn (Assert $page) => $page
->component('admin/roles/index')
->has('roles', 3)
->has('roles.data', 3)
->where('roles.total', 3)
->where('roles.current_page', 1)
->where('roles.per_page', 25)
);
});