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:
parent
16909d076b
commit
2a3e70b78d
@ -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']),
|
||||
]);
|
||||
|
||||
@ -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()),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@ -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()),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@ -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()),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@ -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']),
|
||||
]);
|
||||
}
|
||||
|
||||
@ -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']),
|
||||
]);
|
||||
|
||||
@ -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()),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@ -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()),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@ -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']),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@ -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()),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@ -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()),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
35
app/Http/Requests/PaginatedRequest.php
Normal file
35
app/Http/Requests/PaginatedRequest.php
Normal 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',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -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');
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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([
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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>
|
||||
</>
|
||||
|
||||
@ -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[] = [
|
||||
{
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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}` : ''}?`}
|
||||
|
||||
@ -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}
|
||||
/>
|
||||
|
||||
|
||||
@ -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}
|
||||
/>
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@ -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)
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@ -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)
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@ -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)
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@ -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)
|
||||
);
|
||||
});
|
||||
|
||||
@ -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)
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@ -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)
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@ -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)
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@ -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)
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@ -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)
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user