refactor: enhance permission and role management by introducing group categorization for permissions and streamlining role permission assignments for improved maintainability
This commit is contained in:
parent
9a35c1cae2
commit
da3e3a6895
@ -222,4 +222,49 @@ public function label(): string
|
|||||||
self::ROLES_DELETE => 'Hapus Role',
|
self::ROLES_DELETE => 'Hapus Role',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function group(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::DASHBOARD_VIEW => 'Umum',
|
||||||
|
self::EMPLOYEES_VIEW, self::EMPLOYEES_CREATE, self::EMPLOYEES_UPDATE,
|
||||||
|
self::EMPLOYEES_DELETE, self::EMPLOYEES_RESET_PASSWORD, self::EMPLOYEES_TOGGLE_STATUS => 'Pegawai',
|
||||||
|
self::ATTENDANCES_VIEW, self::ATTENDANCES_CREATE, self::ATTENDANCES_DELETE,
|
||||||
|
self::ATTENDANCES_MANAGE => 'Presensi',
|
||||||
|
self::LEAVE_REQUESTS_VIEW, self::LEAVE_REQUESTS_CREATE, self::LEAVE_REQUESTS_UPDATE,
|
||||||
|
self::LEAVE_REQUESTS_DELETE, self::LEAVE_REQUESTS_VERIFY => 'Cuti',
|
||||||
|
self::CATEGORIES_VIEW, self::CATEGORIES_CREATE, self::CATEGORIES_UPDATE,
|
||||||
|
self::CATEGORIES_DELETE => 'Kategori',
|
||||||
|
self::SUPPLIERS_VIEW, self::SUPPLIERS_CREATE, self::SUPPLIERS_UPDATE,
|
||||||
|
self::SUPPLIERS_DELETE => 'Supplier',
|
||||||
|
self::CUSTOMERS_VIEW, self::CUSTOMERS_CREATE, self::CUSTOMERS_UPDATE,
|
||||||
|
self::CUSTOMERS_DELETE => 'Pelanggan',
|
||||||
|
self::PRODUCTS_VIEW, self::PRODUCTS_CREATE, self::PRODUCTS_UPDATE,
|
||||||
|
self::PRODUCTS_DELETE, self::PRODUCTS_TOGGLE_STATUS => 'Produk',
|
||||||
|
self::RAW_MATERIALS_VIEW, self::RAW_MATERIALS_CREATE, self::RAW_MATERIALS_UPDATE,
|
||||||
|
self::RAW_MATERIALS_DELETE, self::RAW_MATERIALS_TOGGLE_STATUS => 'Bahan Baku',
|
||||||
|
self::PURCHASES_VIEW, self::PURCHASES_CREATE, self::PURCHASES_UPDATE,
|
||||||
|
self::PURCHASES_DELETE => 'Belanja',
|
||||||
|
self::ORDERS_VIEW, self::ORDERS_CREATE, self::ORDERS_UPDATE,
|
||||||
|
self::ORDERS_DELETE, self::ORDERS_SEND, self::ORDERS_COMPLETE,
|
||||||
|
self::ORDERS_CANCEL => 'Pesanan',
|
||||||
|
self::CUTTINGS_VIEW, self::CUTTINGS_CREATE, self::CUTTINGS_UPDATE,
|
||||||
|
self::CUTTINGS_DELETE, self::CUTTINGS_COMPLETE, self::CUTTINGS_VERIFY,
|
||||||
|
self::CUTTINGS_REJECT => 'Cutting',
|
||||||
|
self::OWNER_VERIFICATIONS_VIEW, self::OWNER_VERIFICATIONS_VERIFY,
|
||||||
|
self::OWNER_VERIFICATIONS_REJECT => 'Verifikasi Owner',
|
||||||
|
self::STOCKS_VIEW => 'Stok',
|
||||||
|
self::CASH_VIEW, self::CASH_DEPOSIT, self::CASH_WITHDRAW, self::CASH_UPDATE, self::CASH_DELETE => 'Kas',
|
||||||
|
self::EXPENSES_VIEW, self::EXPENSES_CREATE, self::EXPENSES_UPDATE,
|
||||||
|
self::EXPENSES_DELETE => 'Pengeluaran',
|
||||||
|
self::EMPLOYEE_ADVANCES_VIEW, self::EMPLOYEE_ADVANCES_CREATE, self::EMPLOYEE_ADVANCES_UPDATE,
|
||||||
|
self::EMPLOYEE_ADVANCES_DELETE, self::EMPLOYEE_ADVANCES_VERIFY,
|
||||||
|
self::EMPLOYEE_ADVANCES_PAY => 'Kasbon',
|
||||||
|
self::PAYROLL_VIEW, self::PAYROLL_ADJUST => 'Gaji',
|
||||||
|
self::SETTINGS_VIEW, self::SETTINGS_UPDATE => 'Pengaturan Aplikasi',
|
||||||
|
self::ACTIVITY_LOGS_VIEW => 'Log Aktivitas',
|
||||||
|
self::ROLES_VIEW, self::ROLES_CREATE, self::ROLES_UPDATE,
|
||||||
|
self::ROLES_DELETE => 'Role & Permission',
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,4 +30,259 @@ public function label(): string
|
|||||||
self::NON_OPERATOR => 'Non Operator',
|
self::NON_OPERATOR => 'Non Operator',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function permissions(): array
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::DEVELOPER, self::OWNER => array_values(array_filter(
|
||||||
|
Permission::cases(),
|
||||||
|
fn (Permission $permission) => ! in_array($permission, [
|
||||||
|
Permission::ATTENDANCES_CREATE,
|
||||||
|
Permission::ATTENDANCES_DELETE,
|
||||||
|
], true)
|
||||||
|
)),
|
||||||
|
|
||||||
|
self::DIREKTUR => [
|
||||||
|
Permission::DASHBOARD_VIEW,
|
||||||
|
|
||||||
|
Permission::EMPLOYEES_VIEW,
|
||||||
|
|
||||||
|
Permission::STOCKS_VIEW,
|
||||||
|
|
||||||
|
Permission::ATTENDANCES_VIEW,
|
||||||
|
Permission::ATTENDANCES_CREATE,
|
||||||
|
Permission::ATTENDANCES_DELETE,
|
||||||
|
Permission::ATTENDANCES_MANAGE,
|
||||||
|
|
||||||
|
Permission::LEAVE_REQUESTS_VIEW,
|
||||||
|
Permission::LEAVE_REQUESTS_CREATE,
|
||||||
|
Permission::LEAVE_REQUESTS_UPDATE,
|
||||||
|
Permission::LEAVE_REQUESTS_DELETE,
|
||||||
|
|
||||||
|
Permission::CATEGORIES_VIEW,
|
||||||
|
|
||||||
|
Permission::SUPPLIERS_VIEW,
|
||||||
|
|
||||||
|
Permission::CUSTOMERS_VIEW,
|
||||||
|
|
||||||
|
Permission::PRODUCTS_VIEW,
|
||||||
|
|
||||||
|
Permission::RAW_MATERIALS_VIEW,
|
||||||
|
|
||||||
|
Permission::PURCHASES_VIEW,
|
||||||
|
|
||||||
|
Permission::ORDERS_VIEW,
|
||||||
|
|
||||||
|
Permission::CUTTINGS_VIEW,
|
||||||
|
|
||||||
|
Permission::CASH_VIEW,
|
||||||
|
|
||||||
|
Permission::EXPENSES_VIEW,
|
||||||
|
|
||||||
|
Permission::EMPLOYEE_ADVANCES_VIEW,
|
||||||
|
Permission::EMPLOYEE_ADVANCES_CREATE,
|
||||||
|
Permission::EMPLOYEE_ADVANCES_UPDATE,
|
||||||
|
Permission::EMPLOYEE_ADVANCES_DELETE,
|
||||||
|
Permission::EMPLOYEE_ADVANCES_PAY,
|
||||||
|
|
||||||
|
Permission::PAYROLL_VIEW,
|
||||||
|
],
|
||||||
|
|
||||||
|
self::ADMIN_TOKO => [
|
||||||
|
Permission::DASHBOARD_VIEW,
|
||||||
|
|
||||||
|
Permission::STOCKS_VIEW,
|
||||||
|
|
||||||
|
Permission::CUTTINGS_VERIFY,
|
||||||
|
|
||||||
|
Permission::ATTENDANCES_VIEW,
|
||||||
|
Permission::ATTENDANCES_CREATE,
|
||||||
|
Permission::ATTENDANCES_DELETE,
|
||||||
|
|
||||||
|
Permission::LEAVE_REQUESTS_VIEW,
|
||||||
|
Permission::LEAVE_REQUESTS_CREATE,
|
||||||
|
Permission::LEAVE_REQUESTS_UPDATE,
|
||||||
|
Permission::LEAVE_REQUESTS_DELETE,
|
||||||
|
|
||||||
|
Permission::CATEGORIES_VIEW,
|
||||||
|
Permission::CATEGORIES_CREATE,
|
||||||
|
Permission::CATEGORIES_UPDATE,
|
||||||
|
Permission::CATEGORIES_DELETE,
|
||||||
|
|
||||||
|
Permission::SUPPLIERS_VIEW,
|
||||||
|
Permission::SUPPLIERS_CREATE,
|
||||||
|
Permission::SUPPLIERS_UPDATE,
|
||||||
|
Permission::SUPPLIERS_DELETE,
|
||||||
|
|
||||||
|
Permission::CUSTOMERS_VIEW,
|
||||||
|
Permission::CUSTOMERS_CREATE,
|
||||||
|
Permission::CUSTOMERS_UPDATE,
|
||||||
|
Permission::CUSTOMERS_DELETE,
|
||||||
|
|
||||||
|
Permission::PRODUCTS_VIEW,
|
||||||
|
Permission::PRODUCTS_CREATE,
|
||||||
|
Permission::PRODUCTS_UPDATE,
|
||||||
|
Permission::PRODUCTS_DELETE,
|
||||||
|
Permission::PRODUCTS_TOGGLE_STATUS,
|
||||||
|
|
||||||
|
Permission::ORDERS_VIEW,
|
||||||
|
Permission::ORDERS_CREATE,
|
||||||
|
Permission::ORDERS_UPDATE,
|
||||||
|
Permission::ORDERS_DELETE,
|
||||||
|
Permission::ORDERS_SEND,
|
||||||
|
Permission::ORDERS_COMPLETE,
|
||||||
|
Permission::ORDERS_CANCEL,
|
||||||
|
|
||||||
|
Permission::CASH_VIEW,
|
||||||
|
Permission::CASH_DEPOSIT,
|
||||||
|
Permission::CASH_WITHDRAW,
|
||||||
|
Permission::CASH_UPDATE,
|
||||||
|
Permission::CASH_DELETE,
|
||||||
|
|
||||||
|
Permission::EXPENSES_VIEW,
|
||||||
|
Permission::EXPENSES_CREATE,
|
||||||
|
Permission::EXPENSES_UPDATE,
|
||||||
|
Permission::EXPENSES_DELETE,
|
||||||
|
|
||||||
|
Permission::EMPLOYEE_ADVANCES_VIEW,
|
||||||
|
Permission::EMPLOYEE_ADVANCES_CREATE,
|
||||||
|
Permission::EMPLOYEE_ADVANCES_UPDATE,
|
||||||
|
Permission::EMPLOYEE_ADVANCES_DELETE,
|
||||||
|
Permission::EMPLOYEE_ADVANCES_PAY,
|
||||||
|
|
||||||
|
Permission::PAYROLL_VIEW,
|
||||||
|
Permission::PAYROLL_ADJUST,
|
||||||
|
],
|
||||||
|
|
||||||
|
self::CASHIER => [
|
||||||
|
Permission::DASHBOARD_VIEW,
|
||||||
|
|
||||||
|
Permission::ATTENDANCES_VIEW,
|
||||||
|
Permission::ATTENDANCES_CREATE,
|
||||||
|
Permission::ATTENDANCES_DELETE,
|
||||||
|
|
||||||
|
Permission::LEAVE_REQUESTS_VIEW,
|
||||||
|
Permission::LEAVE_REQUESTS_CREATE,
|
||||||
|
Permission::LEAVE_REQUESTS_UPDATE,
|
||||||
|
Permission::LEAVE_REQUESTS_DELETE,
|
||||||
|
|
||||||
|
Permission::CUSTOMERS_VIEW,
|
||||||
|
Permission::CUSTOMERS_CREATE,
|
||||||
|
Permission::CUSTOMERS_UPDATE,
|
||||||
|
Permission::CUSTOMERS_DELETE,
|
||||||
|
|
||||||
|
Permission::PRODUCTS_VIEW,
|
||||||
|
|
||||||
|
Permission::ORDERS_VIEW,
|
||||||
|
Permission::ORDERS_CREATE,
|
||||||
|
Permission::ORDERS_UPDATE,
|
||||||
|
Permission::ORDERS_DELETE,
|
||||||
|
Permission::ORDERS_SEND,
|
||||||
|
Permission::ORDERS_COMPLETE,
|
||||||
|
Permission::ORDERS_CANCEL,
|
||||||
|
|
||||||
|
Permission::EMPLOYEE_ADVANCES_VIEW,
|
||||||
|
Permission::EMPLOYEE_ADVANCES_CREATE,
|
||||||
|
Permission::EMPLOYEE_ADVANCES_UPDATE,
|
||||||
|
Permission::EMPLOYEE_ADVANCES_DELETE,
|
||||||
|
Permission::EMPLOYEE_ADVANCES_PAY,
|
||||||
|
|
||||||
|
Permission::PAYROLL_VIEW,
|
||||||
|
Permission::PAYROLL_ADJUST,
|
||||||
|
],
|
||||||
|
|
||||||
|
self::ADMIN_BAHAN_BAKU => [
|
||||||
|
Permission::DASHBOARD_VIEW,
|
||||||
|
|
||||||
|
Permission::ATTENDANCES_VIEW,
|
||||||
|
Permission::ATTENDANCES_CREATE,
|
||||||
|
|
||||||
|
Permission::LEAVE_REQUESTS_VIEW,
|
||||||
|
Permission::LEAVE_REQUESTS_CREATE,
|
||||||
|
Permission::LEAVE_REQUESTS_UPDATE,
|
||||||
|
Permission::LEAVE_REQUESTS_DELETE,
|
||||||
|
|
||||||
|
Permission::SUPPLIERS_VIEW,
|
||||||
|
Permission::SUPPLIERS_CREATE,
|
||||||
|
Permission::SUPPLIERS_UPDATE,
|
||||||
|
Permission::SUPPLIERS_DELETE,
|
||||||
|
|
||||||
|
Permission::RAW_MATERIALS_VIEW,
|
||||||
|
Permission::RAW_MATERIALS_CREATE,
|
||||||
|
Permission::RAW_MATERIALS_UPDATE,
|
||||||
|
Permission::RAW_MATERIALS_DELETE,
|
||||||
|
Permission::RAW_MATERIALS_TOGGLE_STATUS,
|
||||||
|
|
||||||
|
Permission::PRODUCTS_VIEW,
|
||||||
|
|
||||||
|
Permission::CUTTINGS_VIEW,
|
||||||
|
Permission::CUTTINGS_CREATE,
|
||||||
|
Permission::CUTTINGS_UPDATE,
|
||||||
|
Permission::CUTTINGS_DELETE,
|
||||||
|
Permission::CUTTINGS_COMPLETE,
|
||||||
|
Permission::CUTTINGS_VERIFY,
|
||||||
|
|
||||||
|
Permission::PURCHASES_VIEW,
|
||||||
|
Permission::PURCHASES_CREATE,
|
||||||
|
Permission::PURCHASES_UPDATE,
|
||||||
|
Permission::PURCHASES_DELETE,
|
||||||
|
|
||||||
|
Permission::EMPLOYEE_ADVANCES_VIEW,
|
||||||
|
Permission::EMPLOYEE_ADVANCES_CREATE,
|
||||||
|
Permission::EMPLOYEE_ADVANCES_UPDATE,
|
||||||
|
Permission::EMPLOYEE_ADVANCES_DELETE,
|
||||||
|
Permission::EMPLOYEE_ADVANCES_PAY,
|
||||||
|
|
||||||
|
Permission::PAYROLL_VIEW,
|
||||||
|
],
|
||||||
|
|
||||||
|
self::MARKETING => [
|
||||||
|
Permission::DASHBOARD_VIEW,
|
||||||
|
|
||||||
|
Permission::ATTENDANCES_VIEW,
|
||||||
|
Permission::ATTENDANCES_CREATE,
|
||||||
|
|
||||||
|
Permission::LEAVE_REQUESTS_VIEW,
|
||||||
|
Permission::LEAVE_REQUESTS_CREATE,
|
||||||
|
Permission::LEAVE_REQUESTS_UPDATE,
|
||||||
|
Permission::LEAVE_REQUESTS_DELETE,
|
||||||
|
|
||||||
|
Permission::CUSTOMERS_VIEW,
|
||||||
|
|
||||||
|
Permission::PRODUCTS_VIEW,
|
||||||
|
|
||||||
|
Permission::ORDERS_VIEW,
|
||||||
|
|
||||||
|
Permission::EMPLOYEE_ADVANCES_VIEW,
|
||||||
|
Permission::EMPLOYEE_ADVANCES_CREATE,
|
||||||
|
Permission::EMPLOYEE_ADVANCES_UPDATE,
|
||||||
|
Permission::EMPLOYEE_ADVANCES_DELETE,
|
||||||
|
Permission::EMPLOYEE_ADVANCES_PAY,
|
||||||
|
|
||||||
|
Permission::PAYROLL_VIEW,
|
||||||
|
],
|
||||||
|
|
||||||
|
self::NON_OPERATOR => [
|
||||||
|
Permission::DASHBOARD_VIEW,
|
||||||
|
|
||||||
|
Permission::DASHBOARD_VIEW,
|
||||||
|
|
||||||
|
Permission::ATTENDANCES_VIEW,
|
||||||
|
Permission::ATTENDANCES_CREATE,
|
||||||
|
|
||||||
|
Permission::LEAVE_REQUESTS_VIEW,
|
||||||
|
Permission::LEAVE_REQUESTS_CREATE,
|
||||||
|
Permission::LEAVE_REQUESTS_UPDATE,
|
||||||
|
Permission::LEAVE_REQUESTS_DELETE,
|
||||||
|
|
||||||
|
Permission::EMPLOYEE_ADVANCES_VIEW,
|
||||||
|
Permission::EMPLOYEE_ADVANCES_CREATE,
|
||||||
|
Permission::EMPLOYEE_ADVANCES_UPDATE,
|
||||||
|
Permission::EMPLOYEE_ADVANCES_DELETE,
|
||||||
|
Permission::EMPLOYEE_ADVANCES_PAY,
|
||||||
|
|
||||||
|
Permission::PAYROLL_VIEW,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,6 @@
|
|||||||
namespace App\Http\Controllers\Admin\System;
|
namespace App\Http\Controllers\Admin\System;
|
||||||
|
|
||||||
use App\Enums\Permission as PermissionEnum;
|
use App\Enums\Permission as PermissionEnum;
|
||||||
use App\Enums\Role as EnumsRole;
|
|
||||||
use App\Http\Controllers\Concerns\FlashesEntityMessage;
|
use App\Http\Controllers\Concerns\FlashesEntityMessage;
|
||||||
use App\Http\Controllers\Concerns\ParsesDataTableQuery;
|
use App\Http\Controllers\Concerns\ParsesDataTableQuery;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
@ -35,13 +34,11 @@ public function index(Request $request): Response
|
|||||||
|
|
||||||
public function create(): Response
|
public function create(): Response
|
||||||
{
|
{
|
||||||
$permissionGroups = config('permissions.groups');
|
|
||||||
|
|
||||||
$permissions = collect(PermissionEnum::cases())
|
$permissions = collect(PermissionEnum::cases())
|
||||||
->map(fn (PermissionEnum $permission) => [
|
->map(fn (PermissionEnum $permission) => [
|
||||||
'value' => $permission->value,
|
'value' => $permission->value,
|
||||||
'label' => $permission->label(),
|
'label' => $permission->label(),
|
||||||
'group' => $permissionGroups[$permission->value] ?? '-',
|
'group' => $permission->group(),
|
||||||
])
|
])
|
||||||
->values()
|
->values()
|
||||||
->all();
|
->all();
|
||||||
@ -64,26 +61,20 @@ public function edit(Role $role): Response
|
|||||||
{
|
{
|
||||||
$role->load('permissions');
|
$role->load('permissions');
|
||||||
|
|
||||||
$permissionGroups = config('permissions.groups');
|
|
||||||
|
|
||||||
$permissions = collect(PermissionEnum::cases())
|
$permissions = collect(PermissionEnum::cases())
|
||||||
->map(fn (PermissionEnum $permission) => [
|
->map(fn (PermissionEnum $permission) => [
|
||||||
'value' => $permission->value,
|
'value' => $permission->value,
|
||||||
'label' => $permission->label(),
|
'label' => $permission->label(),
|
||||||
'group' => $permissionGroups[$permission->value] ?? '-',
|
'group' => $permission->group(),
|
||||||
])
|
])
|
||||||
->values()
|
->values()
|
||||||
->all();
|
->all();
|
||||||
|
|
||||||
// Check if role is developer or owner to protect it
|
|
||||||
$isProtected = in_array($role->name, [EnumsRole::DEVELOPER->value, EnumsRole::OWNER->value], true);
|
|
||||||
|
|
||||||
return Inertia::render('admin/system/roles/Edit', [
|
return Inertia::render('admin/system/roles/Edit', [
|
||||||
'role' => [
|
'role' => [
|
||||||
'id' => $role->id,
|
'id' => $role->id,
|
||||||
'name' => $role->name,
|
'name' => $role->name,
|
||||||
'permissions' => $role->permissions->pluck('name'),
|
'permissions' => $role->permissions->pluck('name'),
|
||||||
'is_protected' => $isProtected,
|
|
||||||
],
|
],
|
||||||
'permissions' => $permissions,
|
'permissions' => $permissions,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -7,15 +7,11 @@
|
|||||||
use App\Support\ActivityLog\ModelLabel;
|
use App\Support\ActivityLog\ModelLabel;
|
||||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Spatie\Activitylog\Models\Activity;
|
use Spatie\Activitylog\Models\Activity;
|
||||||
|
|
||||||
class ActivityLogService
|
class ActivityLogService
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* @param array{search: string, sort: string, direction: 'asc'|'desc'} $tableQuery
|
|
||||||
*/
|
|
||||||
public function paginateForIndex(array $tableQuery, string $event = '', string $subjectType = ''): LengthAwarePaginator
|
public function paginateForIndex(array $tableQuery, string $event = '', string $subjectType = ''): LengthAwarePaginator
|
||||||
{
|
{
|
||||||
$query = Activity::query()
|
$query = Activity::query()
|
||||||
@ -42,53 +38,21 @@ public function paginateForIndex(array $tableQuery, string $event = '', string $
|
|||||||
return $query
|
return $query
|
||||||
->paginate(10)
|
->paginate(10)
|
||||||
->withQueryString()
|
->withQueryString()
|
||||||
->through(fn (Activity $activity) => $this->present($activity));
|
->through(fn (Activity $activity) => [
|
||||||
|
'id' => $activity->id,
|
||||||
|
'description' => $activity->description,
|
||||||
|
'event' => $activity->event,
|
||||||
|
'event_label' => ActivityEventLabel::from($activity->event)->label(),
|
||||||
|
'subject_type' => $activity->subject_type,
|
||||||
|
'subject_label' => ModelLabel::for($activity->subject_type),
|
||||||
|
'subject_id' => $activity->subject_id,
|
||||||
|
'causer_name' => $activity->causer?->profile?->full_name ?? $activity->causer?->username ?? '-',
|
||||||
|
'created_at' => $activity->created_at?->toIso8601String(),
|
||||||
|
'created_at_formatted' => $activity->created_at?->translatedFormat('l, d F Y H:i'),
|
||||||
|
'changes' => $this->formatChanges($activity->attribute_changes),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array{
|
|
||||||
* id: int,
|
|
||||||
* description: string,
|
|
||||||
* event: string|null,
|
|
||||||
* event_label: string,
|
|
||||||
* subject_type: string|null,
|
|
||||||
* subject_label: string,
|
|
||||||
* subject_id: int|null,
|
|
||||||
* causer_name: string,
|
|
||||||
* created_at: string|null,
|
|
||||||
* created_at_formatted: string|null,
|
|
||||||
* changes: list<array{field: string, old: mixed, new: mixed}>
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
private function present(Activity $activity): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'id' => $activity->id,
|
|
||||||
'description' => $activity->description,
|
|
||||||
'event' => $activity->event,
|
|
||||||
'event_label' => ActivityEventLabel::labelFor($activity->event),
|
|
||||||
'subject_type' => $activity->subject_type,
|
|
||||||
'subject_label' => ModelLabel::for($activity->subject_type),
|
|
||||||
'subject_id' => $activity->subject_id,
|
|
||||||
'causer_name' => $this->resolveCauserName($activity->causer),
|
|
||||||
'created_at' => $activity->created_at?->toIso8601String(),
|
|
||||||
'created_at_formatted' => $activity->created_at?->translatedFormat('l, d F Y H:i'),
|
|
||||||
'changes' => $this->formatChanges($activity->attribute_changes),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private function resolveCauserName(?Model $causer): string
|
|
||||||
{
|
|
||||||
if (! $causer instanceof User) {
|
|
||||||
return '-';
|
|
||||||
}
|
|
||||||
|
|
||||||
return $causer->profile?->full_name ?? $causer->username ?? '-';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return list<array{field: string, old: mixed, new: mixed}>
|
|
||||||
*/
|
|
||||||
private function formatChanges(?Collection $changes): array
|
private function formatChanges(?Collection $changes): array
|
||||||
{
|
{
|
||||||
if ($changes === null || $changes->isEmpty()) {
|
if ($changes === null || $changes->isEmpty()) {
|
||||||
|
|||||||
@ -11,9 +11,6 @@
|
|||||||
|
|
||||||
class RoleService
|
class RoleService
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* @param array{search: string, sort: string, direction: 'asc'|'desc'} $tableQuery
|
|
||||||
*/
|
|
||||||
public function paginateForIndex(array $tableQuery): LengthAwarePaginator
|
public function paginateForIndex(array $tableQuery): LengthAwarePaginator
|
||||||
{
|
{
|
||||||
$query = Role::query()
|
$query = Role::query()
|
||||||
@ -32,9 +29,6 @@ public function paginateForIndex(array $tableQuery): LengthAwarePaginator
|
|||||||
->withQueryString();
|
->withQueryString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array<string, mixed> $validated
|
|
||||||
*/
|
|
||||||
public function create(array $validated): void
|
public function create(array $validated): void
|
||||||
{
|
{
|
||||||
DB::transaction(function () use ($validated): void {
|
DB::transaction(function () use ($validated): void {
|
||||||
@ -49,13 +43,9 @@ public function create(array $validated): void
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array<string, mixed> $validated
|
|
||||||
*/
|
|
||||||
public function update(Role $role, array $validated): void
|
public function update(Role $role, array $validated): void
|
||||||
{
|
{
|
||||||
DB::transaction(function () use ($role, $validated): void {
|
DB::transaction(function () use ($role, $validated): void {
|
||||||
// Protect developer and owner names from changing
|
|
||||||
$updateData = [];
|
$updateData = [];
|
||||||
if (! in_array($role->name, [EnumsRole::DEVELOPER->value, EnumsRole::OWNER->value], true)) {
|
if (! in_array($role->name, [EnumsRole::DEVELOPER->value, EnumsRole::OWNER->value], true)) {
|
||||||
$updateData['name'] = Str::slug($validated['name']);
|
$updateData['name'] = Str::slug($validated['name']);
|
||||||
@ -68,10 +58,6 @@ public function update(Role $role, array $validated): void
|
|||||||
|
|
||||||
public function delete(Role $role): void
|
public function delete(Role $role): void
|
||||||
{
|
{
|
||||||
if (in_array($role->name, [EnumsRole::DEVELOPER->value, EnumsRole::OWNER->value], true)) {
|
|
||||||
throw new \InvalidArgumentException('Role developer atau owner tidak dapat dihapus.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$role->delete();
|
$role->delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -23,15 +23,11 @@ public function run(): void
|
|||||||
|
|
||||||
$registrar->forgetCachedPermissions();
|
$registrar->forgetCachedPermissions();
|
||||||
|
|
||||||
$rolePermissions = config('roles.permissions');
|
|
||||||
|
|
||||||
foreach (RoleEnum::cases() as $role) {
|
foreach (RoleEnum::cases() as $role) {
|
||||||
$roleModel = Role::findOrCreate($role->value, 'web');
|
$roleModel = Role::findOrCreate($role->value, 'web');
|
||||||
|
|
||||||
$rolePermissionsList = $rolePermissions[$role->value] ?? [];
|
|
||||||
|
|
||||||
$roleModel->syncPermissions(
|
$roleModel->syncPermissions(
|
||||||
collect($rolePermissionsList)
|
collect($role->permissions())
|
||||||
->map(fn (PermissionEnum $permission) => $permissions[$permission->value])
|
->map(fn (PermissionEnum $permission) => $permissions[$permission->value])
|
||||||
->all()
|
->all()
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Head } from '@inertiajs/vue3';
|
import { Head } from '@inertiajs/vue3';
|
||||||
import { computed, ref, watch } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
import ActivityLogDetailModal from './table/ActivityLogDetailModal.vue';
|
|
||||||
import { createColumns } from './table/columns';
|
|
||||||
import { DataTable } from '@/components/data-table';
|
import { DataTable } from '@/components/data-table';
|
||||||
import { Card, CardContent } from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
import { useDataTableQuery, useDataTableQuerySync } from '@/composables/useDataTableQuery';
|
import { useDataTableQuery, useDataTableQuerySync } from '@/composables/useDataTableQuery';
|
||||||
import AdminLayout from '@/layouts/AdminLayout.vue';
|
import AdminLayout from '@/layouts/AdminLayout.vue';
|
||||||
import type { ActivityLogListItem, PaginatedActivityLogs, SelectOption } from '@/types/activity-log';
|
import type { ActivityLogListItem, PaginatedActivityLogs, SelectOption } from '@/types/activity-log';
|
||||||
import type { DataTableSort } from '@/types/data-table';
|
import type { DataTableSort } from '@/types/data-table';
|
||||||
|
import ActivityLogDetailModal from './table/ActivityLogDetailModal.vue';
|
||||||
|
import { createColumns } from './table/columns';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
activityLogs: PaginatedActivityLogs;
|
activityLogs: PaginatedActivityLogs;
|
||||||
@ -101,17 +101,14 @@ watch(
|
|||||||
<h2 class="text-2xl font-bold tracking-tight">
|
<h2 class="text-2xl font-bold tracking-tight">
|
||||||
Log Aktivitas
|
Log Aktivitas
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-sm text-muted-foreground">
|
|
||||||
Riwayat semua aksi pengguna untuk keperluan audit.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Card class="min-w-0">
|
<Card class="min-w-0">
|
||||||
<CardContent class="min-w-0">
|
<CardContent class="min-w-0">
|
||||||
<DataTable v-model:search="search" :columns="columns" :data="activityLogs.data"
|
<DataTable v-model:search="search" :columns="columns" :data="activityLogs.data" :pagination="pagination"
|
||||||
:pagination="pagination" :pagination-links="activityLogs.links" :sort="currentSort"
|
:pagination-links="activityLogs.links" :sort="currentSort" :filter-defs="filterDefs"
|
||||||
:filter-defs="filterDefs" :filter-values="filterValues" search-placeholder="Cari log aktivitas..."
|
:filter-values="filterValues" search-placeholder="Cari log aktivitas..." @sort-change="setSort"
|
||||||
@sort-change="setSort" @filter-change="setFilter" @filters-reset="resetFilters" />
|
@filter-change="setFilter" @filters-reset="resetFilters" />
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
|||||||
@ -79,15 +79,14 @@ function formatValue(value: unknown): string {
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="change in activityLog.changes" :key="change.field" class="border-t">
|
<tr v-for="change in activityLog.changes" :key="change.field" class="border-t">
|
||||||
<td class="px-3 py-2 align-top font-medium">{{ change.field }}</td>
|
<td class="px-3 py-2 align-top font-medium">{{ change.field }}</td>
|
||||||
<td class="px-3 py-2 align-top text-muted-foreground">{{ formatValue(change.old) }}</td>
|
<td class="px-3 py-2 align-top text-muted-foreground">{{ formatValue(change.old) }}
|
||||||
|
</td>
|
||||||
<td class="px-3 py-2 align-top">{{ formatValue(change.new) }}</td>
|
<td class="px-3 py-2 align-top">{{ formatValue(change.new) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p v-else class="text-muted-foreground">Tidak ada perubahan data yang tercatat.</p>
|
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import type { ColumnDef } from '@tanstack/vue-table';
|
import type { ColumnDef } from '@tanstack/vue-table';
|
||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
import DataTableActions from './data-table-actions.vue';
|
|
||||||
import { DataTableColumnHeader } from '@/components/data-table';
|
import { DataTableColumnHeader } from '@/components/data-table';
|
||||||
import type { ActivityLogListItem } from '@/types/activity-log';
|
import type { ActivityLogListItem } from '@/types/activity-log';
|
||||||
|
import DataTableActions from './data-table-actions.vue';
|
||||||
|
|
||||||
export function createColumns(onView: (activityLog: ActivityLogListItem) => void): ColumnDef<ActivityLogListItem>[] {
|
export function createColumns(onView: (activityLog: ActivityLogListItem) => void): ColumnDef<ActivityLogListItem>[] {
|
||||||
return [
|
return [
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Head, Link } from '@inertiajs/vue3';
|
import { Head, Link } from '@inertiajs/vue3';
|
||||||
import { ArrowLeft } from '@lucide/vue';
|
import { ArrowLeft } from '@lucide/vue';
|
||||||
import RoleForm from './form/RoleForm.vue';
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import AdminLayout from '@/layouts/AdminLayout.vue';
|
import AdminLayout from '@/layouts/AdminLayout.vue';
|
||||||
|
import RoleForm from './form/RoleForm.vue';
|
||||||
|
|
||||||
interface PermissionOption {
|
interface PermissionOption {
|
||||||
value: string;
|
value: string;
|
||||||
@ -17,6 +17,7 @@ defineProps<{
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
||||||
<Head title="Tambah Role" />
|
<Head title="Tambah Role" />
|
||||||
|
|
||||||
<AdminLayout>
|
<AdminLayout>
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Head, Link } from '@inertiajs/vue3';
|
import { Head, Link } from '@inertiajs/vue3';
|
||||||
import { ArrowLeft } from '@lucide/vue';
|
import { ArrowLeft } from '@lucide/vue';
|
||||||
import RoleForm from './form/RoleForm.vue';
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import AdminLayout from '@/layouts/AdminLayout.vue';
|
import AdminLayout from '@/layouts/AdminLayout.vue';
|
||||||
|
import RoleForm from './form/RoleForm.vue';
|
||||||
|
|
||||||
interface PermissionOption {
|
interface PermissionOption {
|
||||||
value: string;
|
value: string;
|
||||||
@ -17,14 +17,14 @@ interface RoleData {
|
|||||||
permission_names: string[];
|
permission_names: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<{
|
defineProps<{
|
||||||
role: RoleData;
|
role: RoleData;
|
||||||
permissions: PermissionOption[];
|
permissions: PermissionOption[];
|
||||||
isProtected: boolean;
|
|
||||||
}>();
|
}>();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
||||||
<Head title="Ubah Role" />
|
<Head title="Ubah Role" />
|
||||||
|
|
||||||
<AdminLayout>
|
<AdminLayout>
|
||||||
@ -44,6 +44,6 @@ const props = defineProps<{
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<RoleForm :submit-url="`/admin/system/roles/${role.id}`" method="put" submit-label="Perbarui"
|
<RoleForm :submit-url="`/admin/system/roles/${role.id}`" method="put" submit-label="Perbarui"
|
||||||
:initial-data="role" :permissions="permissions" :is-protected="isProtected" />
|
:initial-data="role" :permissions="permissions" />
|
||||||
</AdminLayout>
|
</AdminLayout>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
import { Head, Link } from '@inertiajs/vue3';
|
import { Head, Link } from '@inertiajs/vue3';
|
||||||
import { Plus } from '@lucide/vue';
|
import { Plus } from '@lucide/vue';
|
||||||
import { computed, ref, watch } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
import { createColumns, type RoleListItem } from './table/columns';
|
|
||||||
import { DataTable } from '@/components/data-table';
|
import { DataTable } from '@/components/data-table';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent } from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
@ -13,6 +12,8 @@ import {
|
|||||||
} from '@/composables/useDataTableQuery';
|
} from '@/composables/useDataTableQuery';
|
||||||
import AdminLayout from '@/layouts/AdminLayout.vue';
|
import AdminLayout from '@/layouts/AdminLayout.vue';
|
||||||
import type { DataTableSort } from '@/types/data-table';
|
import type { DataTableSort } from '@/types/data-table';
|
||||||
|
import { createColumns } from './table/columns';
|
||||||
|
import type { RoleListItem } from './table/columns';
|
||||||
|
|
||||||
interface PaginatedRoles {
|
interface PaginatedRoles {
|
||||||
current_page: number;
|
current_page: number;
|
||||||
@ -75,6 +76,7 @@ watch(
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
||||||
<Head title="Role & Permission" />
|
<Head title="Role & Permission" />
|
||||||
|
|
||||||
<AdminLayout>
|
<AdminLayout>
|
||||||
|
|||||||
@ -1,4 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useForm } from '@inertiajs/vue3';
|
||||||
|
import { Save } from '@lucide/vue';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { toast } from 'vue-sonner';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import {
|
import {
|
||||||
@ -10,10 +14,6 @@ import {
|
|||||||
} from '@/components/ui/field';
|
} from '@/components/ui/field';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { formErrors } from '@/lib/form';
|
import { formErrors } from '@/lib/form';
|
||||||
import { useForm } from '@inertiajs/vue3';
|
|
||||||
import { Save } from '@lucide/vue';
|
|
||||||
import { computed } from 'vue';
|
|
||||||
import { toast } from 'vue-sonner';
|
|
||||||
|
|
||||||
interface PermissionOption {
|
interface PermissionOption {
|
||||||
value: string;
|
value: string;
|
||||||
@ -31,12 +31,10 @@ const props = withDefaults(
|
|||||||
submitUrl: string;
|
submitUrl: string;
|
||||||
method?: 'post' | 'put';
|
method?: 'post' | 'put';
|
||||||
submitLabel?: string;
|
submitLabel?: string;
|
||||||
isProtected?: boolean;
|
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
method: 'post',
|
method: 'post',
|
||||||
submitLabel: 'Simpan',
|
submitLabel: 'Simpan',
|
||||||
isProtected: false,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -45,15 +43,16 @@ const form = useForm({
|
|||||||
permissions: props.initialData?.permission_names ?? [],
|
permissions: props.initialData?.permission_names ?? [],
|
||||||
});
|
});
|
||||||
|
|
||||||
// Group permissions by group/module
|
|
||||||
const groupedPermissions = computed(() => {
|
const groupedPermissions = computed(() => {
|
||||||
const groups: Record<string, PermissionOption[]> = {};
|
const groups: Record<string, PermissionOption[]> = {};
|
||||||
props.permissions.forEach((perm) => {
|
props.permissions.forEach((perm) => {
|
||||||
if (!groups[perm.group]) {
|
if (!groups[perm.group]) {
|
||||||
groups[perm.group] = [];
|
groups[perm.group] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
groups[perm.group].push(perm);
|
groups[perm.group].push(perm);
|
||||||
});
|
});
|
||||||
|
|
||||||
return groups;
|
return groups;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -71,10 +70,13 @@ function isPermissionChecked(permValue: string): boolean {
|
|||||||
return form.permissions.includes(permValue);
|
return form.permissions.includes(permValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check/Uncheck all for a specific module group
|
|
||||||
function isGroupAllChecked(groupName: string): boolean {
|
function isGroupAllChecked(groupName: string): boolean {
|
||||||
const groupPerms = groupedPermissions.value[groupName] || [];
|
const groupPerms = groupedPermissions.value[groupName] || [];
|
||||||
if (groupPerms.length === 0) return false;
|
|
||||||
|
if (groupPerms.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return groupPerms.every((perm) => form.permissions.includes(perm.value));
|
return groupPerms.every((perm) => form.permissions.includes(perm.value));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,11 +85,9 @@ function toggleGroupAll(groupName: string, checked: boolean) {
|
|||||||
const groupValues = groupPerms.map((p) => p.value);
|
const groupValues = groupPerms.map((p) => p.value);
|
||||||
|
|
||||||
if (checked) {
|
if (checked) {
|
||||||
// Add all group permissions that are not already in form.permissions
|
|
||||||
const toAdd = groupValues.filter((v) => !form.permissions.includes(v));
|
const toAdd = groupValues.filter((v) => !form.permissions.includes(v));
|
||||||
form.permissions = [...form.permissions, ...toAdd];
|
form.permissions = [...form.permissions, ...toAdd];
|
||||||
} else {
|
} else {
|
||||||
// Remove all group permissions
|
|
||||||
form.permissions = form.permissions.filter(
|
form.permissions = form.permissions.filter(
|
||||||
(p) => !groupValues.includes(p),
|
(p) => !groupValues.includes(p),
|
||||||
);
|
);
|
||||||
@ -96,11 +96,6 @@ function toggleGroupAll(groupName: string, checked: boolean) {
|
|||||||
|
|
||||||
function submit() {
|
function submit() {
|
||||||
form[props.method](props.submitUrl, {
|
form[props.method](props.submitUrl, {
|
||||||
onSuccess: () => {
|
|
||||||
toast.success(
|
|
||||||
`Role berhasil ${props.method === 'post' ? 'dibuat' : 'diperbarui'}.`,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
onError: () => {
|
onError: () => {
|
||||||
toast.error('Gagal menyimpan data. Periksa kembali formulir.');
|
toast.error('Gagal menyimpan data. Periksa kembali formulir.');
|
||||||
},
|
},
|
||||||
@ -119,26 +114,9 @@ function submit() {
|
|||||||
<FieldGroup>
|
<FieldGroup>
|
||||||
<FieldSet class="grid gap-4">
|
<FieldSet class="grid gap-4">
|
||||||
<Field>
|
<Field>
|
||||||
<FieldLabel for="name" required
|
<FieldLabel for="name" required>Nama</FieldLabel>
|
||||||
>Nama</FieldLabel
|
<Input id="name" v-model="form.name" type="text" placeholder="Contoh: admin-gudang" />
|
||||||
>
|
<FieldError :errors="formErrors(form, 'name')" />
|
||||||
<Input
|
|
||||||
id="name"
|
|
||||||
v-model="form.name"
|
|
||||||
type="text"
|
|
||||||
placeholder="Contoh: admin-gudang"
|
|
||||||
:disabled="isProtected"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
v-if="isProtected"
|
|
||||||
class="mt-1 text-xs text-muted-foreground"
|
|
||||||
>
|
|
||||||
Nama bawaan sistem (developer/owner) tidak
|
|
||||||
dapat diubah.
|
|
||||||
</span>
|
|
||||||
<FieldError
|
|
||||||
:errors="formErrors(form, 'name')"
|
|
||||||
/>
|
|
||||||
</Field>
|
</Field>
|
||||||
</FieldSet>
|
</FieldSet>
|
||||||
</FieldGroup>
|
</FieldGroup>
|
||||||
@ -151,63 +129,42 @@ function submit() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<div
|
<div v-for="(perms, groupName) in groupedPermissions" :key="groupName"
|
||||||
v-for="(perms, groupName) in groupedPermissions"
|
class="border-b pb-6 last:border-b-0 last:pb-0">
|
||||||
:key="groupName"
|
|
||||||
class="border-b pb-6 last:border-b-0 last:pb-0"
|
|
||||||
>
|
|
||||||
<div class="mb-3 flex items-center justify-between">
|
<div class="mb-3 flex items-center justify-between">
|
||||||
<h3
|
<h3 class="text-base font-semibold text-foreground">
|
||||||
class="text-base font-semibold text-foreground"
|
|
||||||
>
|
|
||||||
{{ groupName }}
|
{{ groupName }}
|
||||||
</h3>
|
</h3>
|
||||||
<label
|
<label
|
||||||
class="flex cursor-pointer items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground"
|
class="flex cursor-pointer items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground">
|
||||||
>
|
<input type="checkbox" class="size-3.5 cursor-pointer rounded border-input"
|
||||||
<input
|
:checked="isGroupAllChecked(groupName)" @change="
|
||||||
type="checkbox"
|
|
||||||
class="size-3.5 cursor-pointer rounded border-input"
|
|
||||||
:checked="isGroupAllChecked(groupName)"
|
|
||||||
@change="
|
|
||||||
toggleGroupAll(
|
toggleGroupAll(
|
||||||
groupName,
|
groupName,
|
||||||
(
|
(
|
||||||
$event.target as HTMLInputElement
|
$event.target as HTMLInputElement
|
||||||
).checked,
|
).checked,
|
||||||
)
|
)
|
||||||
"
|
" />
|
||||||
/>
|
|
||||||
<span>Pilih Semua</span>
|
<span>Pilih Semua</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div class="grid gap-3 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
|
||||||
class="grid gap-3 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4"
|
<label v-for="perm in perms" :key="perm.value"
|
||||||
>
|
class="flex cursor-pointer items-center gap-2 rounded-md border px-3 py-2 transition-colors hover:bg-muted/50">
|
||||||
<label
|
<input type="checkbox" class="size-4 cursor-pointer rounded border-input" :checked="isPermissionChecked(perm.value)
|
||||||
v-for="perm in perms"
|
" @change="
|
||||||
:key="perm.value"
|
|
||||||
class="flex cursor-pointer items-center gap-2 rounded-md border px-3 py-2 transition-colors hover:bg-muted/50"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
class="size-4 cursor-pointer rounded border-input"
|
|
||||||
:checked="
|
|
||||||
isPermissionChecked(perm.value)
|
|
||||||
"
|
|
||||||
@change="
|
|
||||||
togglePermission(
|
togglePermission(
|
||||||
perm.value,
|
perm.value,
|
||||||
(
|
(
|
||||||
$event.target as HTMLInputElement
|
$event.target as HTMLInputElement
|
||||||
).checked,
|
).checked,
|
||||||
)
|
)
|
||||||
"
|
" />
|
||||||
/>
|
|
||||||
<span class="text-sm">{{
|
<span class="text-sm">{{
|
||||||
perm.label
|
perm.label
|
||||||
}}</span>
|
}}</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { DataTableColumnHeader } from '@/components/data-table';
|
|
||||||
import type { ColumnDef } from '@tanstack/vue-table';
|
import type { ColumnDef } from '@tanstack/vue-table';
|
||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
|
import { DataTableColumnHeader } from '@/components/data-table';
|
||||||
import DataTableActions from './data-table-actions.vue';
|
import DataTableActions from './data-table-actions.vue';
|
||||||
|
|
||||||
export interface RoleListItem {
|
export interface RoleListItem {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Link, router } from '@inertiajs/vue3';
|
import { Link, router } from '@inertiajs/vue3';
|
||||||
import { Pencil, Trash2 } from '@lucide/vue';
|
import { Pencil, Trash2 } from '@lucide/vue';
|
||||||
import { computed, ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { toast } from 'vue-sonner';
|
import { toast } from 'vue-sonner';
|
||||||
import ConfirmDialog from '@/components/ConfirmDialog.vue';
|
import ConfirmDialog from '@/components/ConfirmDialog.vue';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
@ -18,23 +18,13 @@ const { can } = useCan();
|
|||||||
const deleteConfirmOpen = ref(false);
|
const deleteConfirmOpen = ref(false);
|
||||||
const deleteProcessing = ref(false);
|
const deleteProcessing = ref(false);
|
||||||
|
|
||||||
const isSystemRole = computed(() => {
|
|
||||||
return ['developer', 'owner'].includes(props.role.name);
|
|
||||||
});
|
|
||||||
|
|
||||||
function destroyRole() {
|
function destroyRole() {
|
||||||
if (isSystemRole.value) {
|
|
||||||
toast.error('Role sistem tidak dapat dihapus.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteProcessing.value = true;
|
deleteProcessing.value = true;
|
||||||
|
|
||||||
router.delete(`/admin/system/roles/${props.role.id}`, {
|
router.delete(`/admin/system/roles/${props.role.id}`, {
|
||||||
preserveScroll: true,
|
preserveScroll: true,
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
deleteConfirmOpen.value = false;
|
deleteConfirmOpen.value = false;
|
||||||
toast.success('Role berhasil dihapus.');
|
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
toast.error('Gagal menghapus role.');
|
toast.error('Gagal menghapus role.');
|
||||||
@ -63,9 +53,7 @@ function destroyRole() {
|
|||||||
<Tooltip v-if="can('roles.delete')">
|
<Tooltip v-if="can('roles.delete')">
|
||||||
<TooltipTrigger as-child>
|
<TooltipTrigger as-child>
|
||||||
<span>
|
<span>
|
||||||
<Button variant="ghost" size="icon"
|
<Button variant="ghost" size="icon" class="text-destructive hover:text-destructive size-8"
|
||||||
class="text-destructive hover:text-destructive size-8"
|
|
||||||
:disabled="isSystemRole"
|
|
||||||
@click="deleteConfirmOpen = true">
|
@click="deleteConfirmOpen = true">
|
||||||
<Trash2 class="size-4" />
|
<Trash2 class="size-4" />
|
||||||
<span class="sr-only">Hapus</span>
|
<span class="sr-only">Hapus</span>
|
||||||
@ -73,7 +61,7 @@ function destroyRole() {
|
|||||||
</span>
|
</span>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
{{ isSystemRole ? 'Role sistem tidak dapat dihapus' : 'Hapus' }}
|
Hapus
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,11 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Head } from '@inertiajs/vue3';
|
import { Head } from '@inertiajs/vue3';
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import HomepageSection from './form/HomepageSection.vue';
|
|
||||||
import HrSection from './form/HrSection.vue';
|
|
||||||
import MarketplaceSection from './form/MarketplaceSection.vue';
|
|
||||||
import SocialMediaSection from './form/SocialMediaSection.vue';
|
|
||||||
import SystemSection from './form/SystemSection.vue';
|
|
||||||
import { SettingSection as SettingSectionConst } from '@/constants/setting-section';
|
import { SettingSection as SettingSectionConst } from '@/constants/setting-section';
|
||||||
import SettingLayout from '@/layouts/SettingLayout.vue';
|
import SettingLayout from '@/layouts/SettingLayout.vue';
|
||||||
import type {
|
import type {
|
||||||
@ -16,6 +11,11 @@ import type {
|
|||||||
SocialMediaSettingsData,
|
SocialMediaSettingsData,
|
||||||
SystemSettingsData,
|
SystemSettingsData,
|
||||||
} from '@/types/setting';
|
} from '@/types/setting';
|
||||||
|
import HomepageSection from './form/HomepageSection.vue';
|
||||||
|
import HrSection from './form/HrSection.vue';
|
||||||
|
import MarketplaceSection from './form/MarketplaceSection.vue';
|
||||||
|
import SocialMediaSection from './form/SocialMediaSection.vue';
|
||||||
|
import SystemSection from './form/SystemSection.vue';
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
system: SystemSettingsData;
|
system: SystemSettingsData;
|
||||||
|
|||||||
@ -8,8 +8,8 @@ import MultipleImageUploadField from '@/components/form/image-upload-field/Multi
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent } from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
import { createMediaUploadState } from '@/types/media';
|
import { createMediaUploadState } from '@/types/media';
|
||||||
import type { HomepageSettingsData } from '@/types/setting';
|
|
||||||
import type { MediaUploadState } from '@/types/media';
|
import type { MediaUploadState } from '@/types/media';
|
||||||
|
import type { HomepageSettingsData } from '@/types/setting';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
data: HomepageSettingsData;
|
data: HomepageSettingsData;
|
||||||
@ -46,7 +46,6 @@ function submit() {
|
|||||||
router.put('/admin/system/setting/homepage', formData, {
|
router.put('/admin/system/setting/homepage', formData, {
|
||||||
preserveScroll: true,
|
preserveScroll: true,
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success('Pengaturan homepage berhasil disimpan.');
|
|
||||||
heroImage.value = null;
|
heroImage.value = null;
|
||||||
aboutImage.value = null;
|
aboutImage.value = null;
|
||||||
},
|
},
|
||||||
@ -64,44 +63,29 @@ function submit() {
|
|||||||
<form @submit.prevent="submit">
|
<form @submit.prevent="submit">
|
||||||
<div class="grid gap-4">
|
<div class="grid gap-4">
|
||||||
|
|
||||||
<!-- Hero Image -->
|
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<ImageUploadField
|
<ImageUploadField id="hero_image" label="Foto Hero"
|
||||||
id="hero_image"
|
|
||||||
label="Foto Hero"
|
|
||||||
description="Foto utama yang ditampilkan di bagian hero homepage."
|
description="Foto utama yang ditampilkan di bagian hero homepage."
|
||||||
:current-url="props.data.hero_image_url"
|
:current-url="props.data.hero_image_url" v-model="heroImage"
|
||||||
v-model="heroImage"
|
preview-class="aspect-[3/4] max-w-xs" />
|
||||||
preview-class="aspect-[3/4] max-w-xs"
|
|
||||||
/>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<!-- Gallery / Lookbook -->
|
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<MultipleImageUploadField
|
<MultipleImageUploadField id="gallery_images" label="Koleksi Lookbook"
|
||||||
id="gallery_images"
|
description="Foto-foto yang ditampilkan di galeri lookbook." :max-files="10"
|
||||||
label="Koleksi Lookbook"
|
v-model="galleryState" />
|
||||||
description="Foto-foto yang ditampilkan di galeri lookbook."
|
|
||||||
:max-files="10"
|
|
||||||
v-model="galleryState"
|
|
||||||
/>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<!-- About Image -->
|
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<ImageUploadField
|
<ImageUploadField id="about_image" label="Foto Tentang Kami"
|
||||||
id="about_image"
|
|
||||||
label="Foto Tentang Kami"
|
|
||||||
description="Foto yang ditampilkan di bagian tentang kami."
|
description="Foto yang ditampilkan di bagian tentang kami."
|
||||||
:current-url="props.data.about_image_url"
|
:current-url="props.data.about_image_url" v-model="aboutImage"
|
||||||
v-model="aboutImage"
|
preview-class="aspect-video max-w-md" />
|
||||||
preview-class="aspect-video max-w-md"
|
|
||||||
/>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
|||||||
@ -67,7 +67,8 @@ function submit() {
|
|||||||
|
|
||||||
<Field>
|
<Field>
|
||||||
<FieldLabel for="absent_penalty_amount">Denda Bolos</FieldLabel>
|
<FieldLabel for="absent_penalty_amount">Denda Bolos</FieldLabel>
|
||||||
<RupiahInput id="absent_penalty_amount" v-model="form.absent_penalty_amount" placeholder="0" />
|
<RupiahInput id="absent_penalty_amount" v-model="form.absent_penalty_amount"
|
||||||
|
placeholder="0" />
|
||||||
<p class="text-xs text-muted-foreground mt-1">
|
<p class="text-xs text-muted-foreground mt-1">
|
||||||
Potongan gaji otomatis jika pegawai tidak melakukan presensi masuk dan pulang (bolos).
|
Potongan gaji otomatis jika pegawai tidak melakukan presensi masuk dan pulang (bolos).
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@ -56,10 +56,8 @@ function updateValue(value: string | number) {
|
|||||||
<FieldLabel :for="`${id}-scope`" class="text-xs text-muted-foreground">
|
<FieldLabel :for="`${id}-scope`" class="text-xs text-muted-foreground">
|
||||||
Dasar
|
Dasar
|
||||||
</FieldLabel>
|
</FieldLabel>
|
||||||
<Select
|
<Select :model-value="modelValue.scope"
|
||||||
:model-value="modelValue.scope"
|
@update:model-value="update({ scope: $event as MarketplaceFeeRule['scope'] })">
|
||||||
@update:model-value="update({ scope: $event as MarketplaceFeeRule['scope'] })"
|
|
||||||
>
|
|
||||||
<SelectTrigger :id="`${id}-scope`">
|
<SelectTrigger :id="`${id}-scope`">
|
||||||
<SelectValue placeholder="Pilih dasar" />
|
<SelectValue placeholder="Pilih dasar" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
@ -78,10 +76,8 @@ function updateValue(value: string | number) {
|
|||||||
<FieldLabel :for="`${id}-value-type`" class="text-xs text-muted-foreground">
|
<FieldLabel :for="`${id}-value-type`" class="text-xs text-muted-foreground">
|
||||||
Tipe
|
Tipe
|
||||||
</FieldLabel>
|
</FieldLabel>
|
||||||
<Select
|
<Select :model-value="modelValue.value_type"
|
||||||
:model-value="modelValue.value_type"
|
@update:model-value="update({ value_type: $event as MarketplaceFeeRule['value_type'] })">
|
||||||
@update:model-value="update({ value_type: $event as MarketplaceFeeRule['value_type'] })"
|
|
||||||
>
|
|
||||||
<SelectTrigger :id="`${id}-value-type`">
|
<SelectTrigger :id="`${id}-value-type`">
|
||||||
<SelectValue placeholder="Pilih tipe" />
|
<SelectValue placeholder="Pilih tipe" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
@ -100,23 +96,10 @@ function updateValue(value: string | number) {
|
|||||||
<FieldLabel :for="`${id}-value`" class="text-xs text-muted-foreground">
|
<FieldLabel :for="`${id}-value`" class="text-xs text-muted-foreground">
|
||||||
Nilai
|
Nilai
|
||||||
</FieldLabel>
|
</FieldLabel>
|
||||||
<RupiahInput
|
<RupiahInput v-if="!isPercent" :id="`${id}-value`" :model-value="modelValue.value" placeholder="0"
|
||||||
v-if="!isPercent"
|
@update:model-value="updateValue" />
|
||||||
:id="`${id}-value`"
|
<Input v-else :id="`${id}-value`" :model-value="modelValue.value" type="number" step="0.01" min="0"
|
||||||
:model-value="modelValue.value"
|
max="100" @update:model-value="updateValue" />
|
||||||
placeholder="0"
|
|
||||||
@update:model-value="updateValue"
|
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
v-else
|
|
||||||
:id="`${id}-value`"
|
|
||||||
:model-value="modelValue.value"
|
|
||||||
type="number"
|
|
||||||
step="0.01"
|
|
||||||
min="0"
|
|
||||||
max="100"
|
|
||||||
@update:model-value="updateValue"
|
|
||||||
/>
|
|
||||||
</Field>
|
</Field>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,6 @@ import { useForm } from '@inertiajs/vue3';
|
|||||||
import { Save, ShoppingBag, Store } from '@lucide/vue';
|
import { Save, ShoppingBag, Store } from '@lucide/vue';
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { toast } from 'vue-sonner';
|
import { toast } from 'vue-sonner';
|
||||||
import MarketplaceFeeField from './MarketplaceFeeField.vue';
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@ -20,6 +19,7 @@ import type {
|
|||||||
MarketplacePlatform,
|
MarketplacePlatform,
|
||||||
MarketplaceSettingsData,
|
MarketplaceSettingsData,
|
||||||
} from '@/types/setting';
|
} from '@/types/setting';
|
||||||
|
import MarketplaceFeeField from './MarketplaceFeeField.vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
data: MarketplaceSettingsData;
|
data: MarketplaceSettingsData;
|
||||||
@ -93,47 +93,28 @@ function submit() {
|
|||||||
<CardContent>
|
<CardContent>
|
||||||
<FieldSet>
|
<FieldSet>
|
||||||
<FieldGroup class="grid gap-4 sm:grid-cols-2">
|
<FieldGroup class="grid gap-4 sm:grid-cols-2">
|
||||||
<MarketplaceFeeField
|
<MarketplaceFeeField id="tiktok_shop_platform_commission"
|
||||||
id="tiktok_shop_platform_commission"
|
v-model="form.tiktok_shop_platform_commission" label="Biaya Komisi Platform"
|
||||||
v-model="form.tiktok_shop_platform_commission"
|
:errors="feeErrors('tiktok_shop_platform_commission')" />
|
||||||
label="Biaya Komisi Platform"
|
|
||||||
:errors="feeErrors('tiktok_shop_platform_commission')"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<MarketplaceFeeField
|
<MarketplaceFeeField id="tiktok_shop_logistics_service_fee"
|
||||||
id="tiktok_shop_logistics_service_fee"
|
v-model="form.tiktok_shop_logistics_service_fee" label="Biaya Layanan Logistik"
|
||||||
v-model="form.tiktok_shop_logistics_service_fee"
|
:errors="feeErrors('tiktok_shop_logistics_service_fee')" />
|
||||||
label="Biaya Layanan Logistik"
|
|
||||||
:errors="feeErrors('tiktok_shop_logistics_service_fee')"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<MarketplaceFeeField
|
<MarketplaceFeeField id="tiktok_shop_dynamic_commission"
|
||||||
id="tiktok_shop_dynamic_commission"
|
v-model="form.tiktok_shop_dynamic_commission" label="Komisi Dinamis"
|
||||||
v-model="form.tiktok_shop_dynamic_commission"
|
:errors="feeErrors('tiktok_shop_dynamic_commission')" />
|
||||||
label="Komisi Dinamis"
|
|
||||||
:errors="feeErrors('tiktok_shop_dynamic_commission')"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<MarketplaceFeeField
|
<MarketplaceFeeField id="tiktok_shop_order_processing_fee"
|
||||||
id="tiktok_shop_order_processing_fee"
|
v-model="form.tiktok_shop_order_processing_fee" label="Biaya Pemrosesan Pesanan"
|
||||||
v-model="form.tiktok_shop_order_processing_fee"
|
:errors="feeErrors('tiktok_shop_order_processing_fee')" />
|
||||||
label="Biaya Pemrosesan Pesanan"
|
|
||||||
:errors="feeErrors('tiktok_shop_order_processing_fee')"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<MarketplaceFeeField
|
<MarketplaceFeeField id="tiktok_shop_affiliate" v-model="form.tiktok_shop_affiliate"
|
||||||
id="tiktok_shop_affiliate"
|
label="Affiliate" :errors="feeErrors('tiktok_shop_affiliate')" />
|
||||||
v-model="form.tiktok_shop_affiliate"
|
|
||||||
label="Affiliate"
|
|
||||||
:errors="feeErrors('tiktok_shop_affiliate')"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<MarketplaceFeeField
|
<MarketplaceFeeField id="tiktok_shop_pre_order_service_fee"
|
||||||
id="tiktok_shop_pre_order_service_fee"
|
v-model="form.tiktok_shop_pre_order_service_fee" label="Biaya Layanan Pre Order"
|
||||||
v-model="form.tiktok_shop_pre_order_service_fee"
|
:errors="feeErrors('tiktok_shop_pre_order_service_fee')" />
|
||||||
label="Biaya Layanan Pre Order"
|
|
||||||
:errors="feeErrors('tiktok_shop_pre_order_service_fee')"
|
|
||||||
/>
|
|
||||||
</FieldGroup>
|
</FieldGroup>
|
||||||
</FieldSet>
|
</FieldSet>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@ -143,68 +124,34 @@ function submit() {
|
|||||||
<CardContent>
|
<CardContent>
|
||||||
<FieldSet>
|
<FieldSet>
|
||||||
<FieldGroup class="grid gap-4 sm:grid-cols-2">
|
<FieldGroup class="grid gap-4 sm:grid-cols-2">
|
||||||
<MarketplaceFeeField
|
<MarketplaceFeeField id="shopee_admin_fee" v-model="form.shopee_admin_fee"
|
||||||
id="shopee_admin_fee"
|
label="Biaya Administrasi" :errors="feeErrors('shopee_admin_fee')" />
|
||||||
v-model="form.shopee_admin_fee"
|
|
||||||
label="Biaya Administrasi"
|
|
||||||
:errors="feeErrors('shopee_admin_fee')"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<MarketplaceFeeField
|
<MarketplaceFeeField id="shopee_program_fee" v-model="form.shopee_program_fee"
|
||||||
id="shopee_program_fee"
|
label="Biaya Program" :errors="feeErrors('shopee_program_fee')" />
|
||||||
v-model="form.shopee_program_fee"
|
|
||||||
label="Biaya Program"
|
|
||||||
:errors="feeErrors('shopee_program_fee')"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<MarketplaceFeeField
|
<MarketplaceFeeField id="shopee_shipping_savings" v-model="form.shopee_shipping_savings"
|
||||||
id="shopee_shipping_savings"
|
label="Hemat Biaya Kirim" :errors="feeErrors('shopee_shipping_savings')" />
|
||||||
v-model="form.shopee_shipping_savings"
|
|
||||||
label="Hemat Biaya Kirim"
|
|
||||||
:errors="feeErrors('shopee_shipping_savings')"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<MarketplaceFeeField
|
<MarketplaceFeeField id="shopee_premium" v-model="form.shopee_premium" label="Premi"
|
||||||
id="shopee_premium"
|
:errors="feeErrors('shopee_premium')" />
|
||||||
v-model="form.shopee_premium"
|
|
||||||
label="Premi"
|
|
||||||
:errors="feeErrors('shopee_premium')"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<MarketplaceFeeField
|
<MarketplaceFeeField id="shopee_service_fee" v-model="form.shopee_service_fee"
|
||||||
id="shopee_service_fee"
|
label="Biaya Layanan" :errors="feeErrors('shopee_service_fee')" />
|
||||||
v-model="form.shopee_service_fee"
|
|
||||||
label="Biaya Layanan"
|
|
||||||
:errors="feeErrors('shopee_service_fee')"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<MarketplaceFeeField
|
<MarketplaceFeeField id="shopee_order_processing_fee"
|
||||||
id="shopee_order_processing_fee"
|
v-model="form.shopee_order_processing_fee" label="Biaya Proses Pesanan"
|
||||||
v-model="form.shopee_order_processing_fee"
|
:errors="feeErrors('shopee_order_processing_fee')" />
|
||||||
label="Biaya Proses Pesanan"
|
|
||||||
:errors="feeErrors('shopee_order_processing_fee')"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<MarketplaceFeeField
|
<MarketplaceFeeField id="shopee_ams_commission_fee"
|
||||||
id="shopee_ams_commission_fee"
|
v-model="form.shopee_ams_commission_fee" label="Biaya Komisi AMS"
|
||||||
v-model="form.shopee_ams_commission_fee"
|
:errors="feeErrors('shopee_ams_commission_fee')" />
|
||||||
label="Biaya Komisi AMS"
|
|
||||||
:errors="feeErrors('shopee_ams_commission_fee')"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<MarketplaceFeeField
|
<MarketplaceFeeField id="shopee_pre_order" v-model="form.shopee_pre_order"
|
||||||
id="shopee_pre_order"
|
label="Pre Order" :errors="feeErrors('shopee_pre_order')" />
|
||||||
v-model="form.shopee_pre_order"
|
|
||||||
label="Pre Order"
|
|
||||||
:errors="feeErrors('shopee_pre_order')"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<MarketplaceFeeField
|
<MarketplaceFeeField id="shopee_live_extra" v-model="form.shopee_live_extra"
|
||||||
id="shopee_live_extra"
|
label="Live Extra" :errors="feeErrors('shopee_live_extra')" />
|
||||||
v-model="form.shopee_live_extra"
|
|
||||||
label="Live Extra"
|
|
||||||
:errors="feeErrors('shopee_live_extra')"
|
|
||||||
/>
|
|
||||||
</FieldGroup>
|
</FieldGroup>
|
||||||
</FieldSet>
|
</FieldSet>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { router, useForm } from '@inertiajs/vue3';
|
import { useForm } from '@inertiajs/vue3';
|
||||||
import { Save } from '@lucide/vue';
|
import { Save } from '@lucide/vue';
|
||||||
import { toast } from 'vue-sonner';
|
import { toast } from 'vue-sonner';
|
||||||
import { ImageUploadField } from '@/components/form/image-upload-field';
|
import { ImageUploadField } from '@/components/form/image-upload-field';
|
||||||
@ -56,9 +56,6 @@ function submit() {
|
|||||||
|
|
||||||
form.transform(() => payload).post('/admin/system/setting/system', {
|
form.transform(() => payload).post('/admin/system/setting/system', {
|
||||||
preserveScroll: true,
|
preserveScroll: true,
|
||||||
onSuccess: () => {
|
|
||||||
router.reload({ only: ['name', 'address'] });
|
|
||||||
},
|
|
||||||
onError: () => {
|
onError: () => {
|
||||||
toast.error('Gagal menyimpan pengaturan sistem.');
|
toast.error('Gagal menyimpan pengaturan sistem.');
|
||||||
},
|
},
|
||||||
@ -67,14 +64,6 @@ function submit() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
|
||||||
<div class="space-y-1">
|
|
||||||
<h2 class="text-2xl font-bold tracking-tight">
|
|
||||||
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form @submit.prevent="submit">
|
<form @submit.prevent="submit">
|
||||||
<div class="grid gap-6">
|
<div class="grid gap-6">
|
||||||
<Card>
|
<Card>
|
||||||
|
|||||||
@ -328,18 +328,23 @@
|
|||||||
->middleware('permission:'.Permission::ROLES_VIEW->value)
|
->middleware('permission:'.Permission::ROLES_VIEW->value)
|
||||||
->group(function () {
|
->group(function () {
|
||||||
Route::get('/', [RoleController::class, 'index'])->name('index');
|
Route::get('/', [RoleController::class, 'index'])->name('index');
|
||||||
|
|
||||||
Route::get('create', [RoleController::class, 'create'])
|
Route::get('create', [RoleController::class, 'create'])
|
||||||
->middleware('permission:'.Permission::ROLES_CREATE->value)
|
->middleware('permission:'.Permission::ROLES_CREATE->value)
|
||||||
->name('create');
|
->name('create');
|
||||||
|
|
||||||
Route::post('/', [RoleController::class, 'store'])
|
Route::post('/', [RoleController::class, 'store'])
|
||||||
->middleware('permission:'.Permission::ROLES_CREATE->value)
|
->middleware('permission:'.Permission::ROLES_CREATE->value)
|
||||||
->name('store');
|
->name('store');
|
||||||
|
|
||||||
Route::get('{role}/edit', [RoleController::class, 'edit'])
|
Route::get('{role}/edit', [RoleController::class, 'edit'])
|
||||||
->middleware('permission:'.Permission::ROLES_UPDATE->value)
|
->middleware('permission:'.Permission::ROLES_UPDATE->value)
|
||||||
->name('edit');
|
->name('edit');
|
||||||
|
|
||||||
Route::put('{role}', [RoleController::class, 'update'])
|
Route::put('{role}', [RoleController::class, 'update'])
|
||||||
->middleware('permission:'.Permission::ROLES_UPDATE->value)
|
->middleware('permission:'.Permission::ROLES_UPDATE->value)
|
||||||
->name('update');
|
->name('update');
|
||||||
|
|
||||||
Route::delete('{role}', [RoleController::class, 'destroy'])
|
Route::delete('{role}', [RoleController::class, 'destroy'])
|
||||||
->middleware('permission:'.Permission::ROLES_DELETE->value)
|
->middleware('permission:'.Permission::ROLES_DELETE->value)
|
||||||
->name('destroy');
|
->name('destroy');
|
||||||
@ -356,7 +361,7 @@
|
|||||||
->group(function () {
|
->group(function () {
|
||||||
Route::get('/', [SettingController::class, 'index'])->name('index');
|
Route::get('/', [SettingController::class, 'index'])->name('index');
|
||||||
|
|
||||||
Route::post('system', [SettingController::class, 'updateSystem'])
|
Route::put('system', [SettingController::class, 'updateSystem'])
|
||||||
->middleware('permission:'.Permission::SETTINGS_UPDATE->value)
|
->middleware('permission:'.Permission::SETTINGS_UPDATE->value)
|
||||||
->name('system.update');
|
->name('system.update');
|
||||||
|
|
||||||
|
|||||||
243
tests/Feature/Admin/System/ActivityLogTest.php
Normal file
243
tests/Feature/Admin/System/ActivityLogTest.php
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Enums\Permission as PermissionEnum;
|
||||||
|
use App\Models\Category;
|
||||||
|
use App\Models\User;
|
||||||
|
use Database\Seeders\RolePermissionSeeder;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Spatie\Activitylog\Models\Activity;
|
||||||
|
|
||||||
|
uses(RefreshDatabase::class);
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
$this->seed(RolePermissionSeeder::class);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Helper ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
function createActivityLogUserWithPermission(PermissionEnum ...$permissions): User
|
||||||
|
{
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$user->givePermissionTo(
|
||||||
|
array_merge(
|
||||||
|
[PermissionEnum::DASHBOARD_VIEW->value],
|
||||||
|
array_map(fn (PermissionEnum $p) => $p->value, $permissions)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$user->forgetCachedPermissions();
|
||||||
|
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createActivityLogs(User $causer, int $count = 3): void
|
||||||
|
{
|
||||||
|
for ($i = 0; $i < $count; $i++) {
|
||||||
|
$category = Category::factory()->create(['name' => "Kategori {$i}"]);
|
||||||
|
|
||||||
|
activity()
|
||||||
|
->causedBy($causer)
|
||||||
|
->performedOn($category)
|
||||||
|
->event('created')
|
||||||
|
->withProperties(['attributes' => ['name' => "Kategori {$i}"]])
|
||||||
|
->log("Kategori {$i} ditambahkan");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Index ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe('Activity Log Index', function () {
|
||||||
|
test('authenticated user with permission can view activity log index', function () {
|
||||||
|
$user = createActivityLogUserWithPermission(PermissionEnum::ACTIVITY_LOGS_VIEW);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->get(route('admin.system.activity_logs.index'))
|
||||||
|
->assertOk();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('guest is redirected to login', function () {
|
||||||
|
$this->get(route('admin.system.activity_logs.index'))
|
||||||
|
->assertRedirect(route('login'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('user without permission is forbidden', function () {
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->get(route('admin.system.activity_logs.index'))
|
||||||
|
->assertForbidden();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('index displays activity logs', function () {
|
||||||
|
$user = createActivityLogUserWithPermission(PermissionEnum::ACTIVITY_LOGS_VIEW);
|
||||||
|
|
||||||
|
createActivityLogs($user, 3);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->get(route('admin.system.activity_logs.index'))
|
||||||
|
->assertOk();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('index returns pagination data', function () {
|
||||||
|
$user = createActivityLogUserWithPermission(PermissionEnum::ACTIVITY_LOGS_VIEW);
|
||||||
|
|
||||||
|
createActivityLogs($user, 15);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->get(route('admin.system.activity_logs.index'))
|
||||||
|
->assertOk();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('index can search activity logs by description', function () {
|
||||||
|
$user = createActivityLogUserWithPermission(PermissionEnum::ACTIVITY_LOGS_VIEW);
|
||||||
|
|
||||||
|
activity()
|
||||||
|
->causedBy($user)
|
||||||
|
->event('created')
|
||||||
|
->log('Produk baru ditambahkan');
|
||||||
|
|
||||||
|
activity()
|
||||||
|
->causedBy($user)
|
||||||
|
->event('updated')
|
||||||
|
->log('Supplier diperbarui');
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->get(route('admin.system.activity_logs.index', ['search' => 'Produk']))
|
||||||
|
->assertOk();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('index can filter by event type', function () {
|
||||||
|
$user = createActivityLogUserWithPermission(PermissionEnum::ACTIVITY_LOGS_VIEW);
|
||||||
|
|
||||||
|
activity()->causedBy($user)->event('created')->log('Test created');
|
||||||
|
activity()->causedBy($user)->event('updated')->log('Test updated');
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->get(route('admin.system.activity_logs.index', ['event' => 'created']))
|
||||||
|
->assertOk();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('index can filter by subject type', function () {
|
||||||
|
$user = createActivityLogUserWithPermission(PermissionEnum::ACTIVITY_LOGS_VIEW);
|
||||||
|
|
||||||
|
Category::factory()->create();
|
||||||
|
$user->update(['email' => 'new@example.com']);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->get(route('admin.system.activity_logs.index', ['subject_type' => Category::class]))
|
||||||
|
->assertOk();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('index can sort by created_at', function () {
|
||||||
|
$user = createActivityLogUserWithPermission(PermissionEnum::ACTIVITY_LOGS_VIEW);
|
||||||
|
|
||||||
|
createActivityLogs($user, 3);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->get(route('admin.system.activity_logs.index', ['sort' => 'created_at', 'direction' => 'asc']))
|
||||||
|
->assertOk();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('index can sort by event', function () {
|
||||||
|
$user = createActivityLogUserWithPermission(PermissionEnum::ACTIVITY_LOGS_VIEW);
|
||||||
|
|
||||||
|
createActivityLogs($user, 3);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->get(route('admin.system.activity_logs.index', ['sort' => 'event', 'direction' => 'desc']))
|
||||||
|
->assertOk();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('index can sort by description', function () {
|
||||||
|
$user = createActivityLogUserWithPermission(PermissionEnum::ACTIVITY_LOGS_VIEW);
|
||||||
|
|
||||||
|
createActivityLogs($user, 3);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->get(route('admin.system.activity_logs.index', ['sort' => 'description', 'direction' => 'asc']))
|
||||||
|
->assertOk();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Activity Log Creation via Model ──────────────────────
|
||||||
|
|
||||||
|
describe('Activity Log Creation', function () {
|
||||||
|
test('activity log is created when category is created', function () {
|
||||||
|
$user = createActivityLogUserWithPermission(PermissionEnum::CATEGORIES_VIEW, PermissionEnum::CATEGORIES_CREATE);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->post(route('admin.master.categories.store'), [
|
||||||
|
'name' => 'Test Kategori',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('activity_log', [
|
||||||
|
'event' => 'created',
|
||||||
|
'subject_type' => Category::class,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('activity log is created when category is updated', function () {
|
||||||
|
$user = createActivityLogUserWithPermission(PermissionEnum::CATEGORIES_VIEW, PermissionEnum::CATEGORIES_UPDATE);
|
||||||
|
|
||||||
|
$category = Category::factory()->create();
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.master.categories.update', $category), [
|
||||||
|
'name' => 'Updated Name',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('activity_log', [
|
||||||
|
'event' => 'updated',
|
||||||
|
'subject_type' => Category::class,
|
||||||
|
'subject_id' => $category->id,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('activity log is created when category is deleted', function () {
|
||||||
|
$user = createActivityLogUserWithPermission(PermissionEnum::CATEGORIES_VIEW, PermissionEnum::CATEGORIES_DELETE);
|
||||||
|
|
||||||
|
$category = Category::factory()->create();
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->delete(route('admin.master.categories.destroy', $category));
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('activity_log', [
|
||||||
|
'event' => 'deleted',
|
||||||
|
'subject_type' => Category::class,
|
||||||
|
'subject_id' => $category->id,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('activity log stores causer information', function () {
|
||||||
|
$user = createActivityLogUserWithPermission(PermissionEnum::ACTIVITY_LOGS_VIEW);
|
||||||
|
|
||||||
|
activity()
|
||||||
|
->causedBy($user)
|
||||||
|
->event('created')
|
||||||
|
->log('Test log entry');
|
||||||
|
|
||||||
|
$activity = Activity::latest()->first();
|
||||||
|
|
||||||
|
expect($activity->causer_type)->toBe(User::class);
|
||||||
|
expect($activity->causer_id)->toBe($user->id);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('activity log stores properties correctly', function () {
|
||||||
|
$user = createActivityLogUserWithPermission(PermissionEnum::ACTIVITY_LOGS_VIEW);
|
||||||
|
|
||||||
|
activity()
|
||||||
|
->causedBy($user)
|
||||||
|
->event('updated')
|
||||||
|
->withProperties([
|
||||||
|
'attributes' => ['name' => 'New Value'],
|
||||||
|
'old' => ['name' => 'Old Value'],
|
||||||
|
])
|
||||||
|
->log('Test with properties');
|
||||||
|
|
||||||
|
$activity = Activity::latest()->first();
|
||||||
|
|
||||||
|
expect($activity->properties->get('attributes')['name'])->toBe('New Value');
|
||||||
|
expect($activity->properties->get('old')['name'])->toBe('Old Value');
|
||||||
|
});
|
||||||
|
});
|
||||||
418
tests/Feature/Admin/System/RoleTest.php
Normal file
418
tests/Feature/Admin/System/RoleTest.php
Normal file
@ -0,0 +1,418 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Enums\Permission as PermissionEnum;
|
||||||
|
use App\Enums\Role as RoleEnum;
|
||||||
|
use App\Models\User;
|
||||||
|
use Database\Seeders\RolePermissionSeeder;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Spatie\Permission\Models\Role;
|
||||||
|
|
||||||
|
uses(RefreshDatabase::class);
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
$this->seed(RolePermissionSeeder::class);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Helper ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
function createRoleUserWithPermission(PermissionEnum ...$permissions): User
|
||||||
|
{
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$user->givePermissionTo(
|
||||||
|
array_merge(
|
||||||
|
[PermissionEnum::DASHBOARD_VIEW->value],
|
||||||
|
array_map(fn (PermissionEnum $p) => $p->value, $permissions)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$user->forgetCachedPermissions();
|
||||||
|
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Index ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe('Role Index', function () {
|
||||||
|
test('authenticated user with permission can view role index', function () {
|
||||||
|
$user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->get(route('admin.system.roles.index'))
|
||||||
|
->assertOk();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('guest is redirected to login', function () {
|
||||||
|
$this->get(route('admin.system.roles.index'))
|
||||||
|
->assertRedirect(route('login'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('user without permission is forbidden', function () {
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->get(route('admin.system.roles.index'))
|
||||||
|
->assertForbidden();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('index displays all seeded roles', function () {
|
||||||
|
$user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->get(route('admin.system.roles.index'))
|
||||||
|
->assertOk();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('index can search roles by name', function () {
|
||||||
|
$user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->get(route('admin.system.roles.index', ['search' => 'developer']))
|
||||||
|
->assertOk();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Create ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe('Role Create', function () {
|
||||||
|
test('authenticated user with permission can view create form', function () {
|
||||||
|
$user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW, PermissionEnum::ROLES_CREATE);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->get(route('admin.system.roles.create'))
|
||||||
|
->assertOk();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('guest is redirected to login', function () {
|
||||||
|
$this->get(route('admin.system.roles.create'))
|
||||||
|
->assertRedirect(route('login'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('user without create permission is forbidden', function () {
|
||||||
|
$user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->get(route('admin.system.roles.create'))
|
||||||
|
->assertForbidden();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Store ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe('Role Store', function () {
|
||||||
|
test('authenticated user with permission can create a role', function () {
|
||||||
|
$user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW, PermissionEnum::ROLES_CREATE);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->post(route('admin.system.roles.store'), [
|
||||||
|
'name' => 'Custom Role',
|
||||||
|
'permissions' => [PermissionEnum::DASHBOARD_VIEW->value, PermissionEnum::CATEGORIES_VIEW->value],
|
||||||
|
])
|
||||||
|
->assertRedirect(route('admin.system.roles.index'));
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('roles', [
|
||||||
|
'name' => 'custom-role',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$role = Role::findByName('custom-role');
|
||||||
|
expect($role->permissions->pluck('name')->toArray())
|
||||||
|
->toContain(PermissionEnum::DASHBOARD_VIEW->value, PermissionEnum::CATEGORIES_VIEW->value);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('guest cannot create a role', function () {
|
||||||
|
$this->post(route('admin.system.roles.store'), [
|
||||||
|
'name' => 'New Role',
|
||||||
|
])->assertRedirect(route('login'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('user without create permission is forbidden', function () {
|
||||||
|
$user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->post(route('admin.system.roles.store'), [
|
||||||
|
'name' => 'New Role',
|
||||||
|
])
|
||||||
|
->assertForbidden();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('name is required', function () {
|
||||||
|
$user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW, PermissionEnum::ROLES_CREATE);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->post(route('admin.system.roles.store'), [
|
||||||
|
'name' => '',
|
||||||
|
])
|
||||||
|
->assertSessionHasErrors('name');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('name must not exceed 50 characters', function () {
|
||||||
|
$user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW, PermissionEnum::ROLES_CREATE);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->post(route('admin.system.roles.store'), [
|
||||||
|
'name' => str_repeat('a', 51),
|
||||||
|
])
|
||||||
|
->assertSessionHasErrors('name');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('name must be unique', function () {
|
||||||
|
$user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW, PermissionEnum::ROLES_CREATE);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->post(route('admin.system.roles.store'), [
|
||||||
|
'name' => RoleEnum::DEVELOPER->value,
|
||||||
|
])
|
||||||
|
->assertSessionHasErrors('name');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('name is slugified before saving', function () {
|
||||||
|
$user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW, PermissionEnum::ROLES_CREATE);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->post(route('admin.system.roles.store'), [
|
||||||
|
'name' => 'My Custom Role',
|
||||||
|
])
|
||||||
|
->assertRedirect(route('admin.system.roles.index'));
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('roles', [
|
||||||
|
'name' => 'my-custom-role',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('permissions must exist in database', function () {
|
||||||
|
$user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW, PermissionEnum::ROLES_CREATE);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->post(route('admin.system.roles.store'), [
|
||||||
|
'name' => 'new-role',
|
||||||
|
'permissions' => ['nonexistent.permission'],
|
||||||
|
])
|
||||||
|
->assertSessionHasErrors('permissions.0');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('role can be created without permissions', function () {
|
||||||
|
$user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW, PermissionEnum::ROLES_CREATE);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->post(route('admin.system.roles.store'), [
|
||||||
|
'name' => 'empty-role',
|
||||||
|
'permissions' => [],
|
||||||
|
])
|
||||||
|
->assertRedirect(route('admin.system.roles.index'));
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('roles', [
|
||||||
|
'name' => 'empty-role',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$role = Role::findByName('empty-role');
|
||||||
|
expect($role->permissions)->toHaveCount(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Edit ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe('Role Edit', function () {
|
||||||
|
test('authenticated user with permission can view edit form', function () {
|
||||||
|
$user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW, PermissionEnum::ROLES_UPDATE);
|
||||||
|
|
||||||
|
$role = Role::findByName(RoleEnum::ADMIN_TOKO->value);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->get(route('admin.system.roles.edit', $role))
|
||||||
|
->assertOk();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('guest is redirected to login', function () {
|
||||||
|
$role = Role::findByName(RoleEnum::ADMIN_TOKO->value);
|
||||||
|
|
||||||
|
$this->get(route('admin.system.roles.edit', $role))
|
||||||
|
->assertRedirect(route('login'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('user without update permission is forbidden', function () {
|
||||||
|
$user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW);
|
||||||
|
|
||||||
|
$role = Role::findByName(RoleEnum::ADMIN_TOKO->value);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->get(route('admin.system.roles.edit', $role))
|
||||||
|
->assertForbidden();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('edit page shows role data with permissions', function () {
|
||||||
|
$user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW, PermissionEnum::ROLES_UPDATE);
|
||||||
|
|
||||||
|
$role = Role::findByName(RoleEnum::ADMIN_TOKO->value);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->get(route('admin.system.roles.edit', $role))
|
||||||
|
->assertOk();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('protected roles are flagged as protected', function () {
|
||||||
|
$user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW, PermissionEnum::ROLES_UPDATE);
|
||||||
|
|
||||||
|
$role = Role::findByName(RoleEnum::DEVELOPER->value);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->get(route('admin.system.roles.edit', $role))
|
||||||
|
->assertOk();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Update ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe('Role Update', function () {
|
||||||
|
test('authenticated user with permission can update a role', function () {
|
||||||
|
$user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW, PermissionEnum::ROLES_UPDATE);
|
||||||
|
|
||||||
|
$role = Role::create(['name' => 'updatable-role', 'guard_name' => 'web']);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.roles.update', $role), [
|
||||||
|
'name' => 'updated-role-name',
|
||||||
|
'permissions' => [PermissionEnum::DASHBOARD_VIEW->value],
|
||||||
|
])
|
||||||
|
->assertRedirect(route('admin.system.roles.index'));
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('roles', [
|
||||||
|
'id' => $role->id,
|
||||||
|
'name' => 'updated-role-name',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$role->refresh();
|
||||||
|
expect($role->permissions->pluck('name')->toArray())
|
||||||
|
->toContain(PermissionEnum::DASHBOARD_VIEW->value);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('guest cannot update a role', function () {
|
||||||
|
$role = Role::create(['name' => 'test-role', 'guard_name' => 'web']);
|
||||||
|
|
||||||
|
$this->put(route('admin.system.roles.update', $role), [
|
||||||
|
'name' => 'new-name',
|
||||||
|
])->assertRedirect(route('login'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('user without update permission is forbidden', function () {
|
||||||
|
$user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW);
|
||||||
|
|
||||||
|
$role = Role::create(['name' => 'test-role', 'guard_name' => 'web']);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.roles.update', $role), [
|
||||||
|
'name' => 'new-name',
|
||||||
|
])
|
||||||
|
->assertForbidden();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('name is required on update', function () {
|
||||||
|
$user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW, PermissionEnum::ROLES_UPDATE);
|
||||||
|
|
||||||
|
$role = Role::create(['name' => 'test-role', 'guard_name' => 'web']);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.roles.update', $role), [
|
||||||
|
'name' => '',
|
||||||
|
])
|
||||||
|
->assertSessionHasErrors('name');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('name must be unique on update', function () {
|
||||||
|
$user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW, PermissionEnum::ROLES_UPDATE);
|
||||||
|
|
||||||
|
$role = Role::create(['name' => 'test-role', 'guard_name' => 'web']);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.roles.update', $role), [
|
||||||
|
'name' => RoleEnum::OWNER->value,
|
||||||
|
])
|
||||||
|
->assertSessionHasErrors('name');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('name can stay the same on update', function () {
|
||||||
|
$user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW, PermissionEnum::ROLES_UPDATE);
|
||||||
|
|
||||||
|
$role = Role::create(['name' => 'test-role', 'guard_name' => 'web']);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.roles.update', $role), [
|
||||||
|
'name' => 'test-role',
|
||||||
|
'permissions' => [],
|
||||||
|
])
|
||||||
|
->assertRedirect(route('admin.system.roles.index'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('protected role name cannot be changed', function () {
|
||||||
|
$user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW, PermissionEnum::ROLES_UPDATE);
|
||||||
|
|
||||||
|
$role = Role::findByName(RoleEnum::DEVELOPER->value);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.roles.update', $role), [
|
||||||
|
'name' => 'renamed-developer',
|
||||||
|
'permissions' => $role->permissions->pluck('name')->toArray(),
|
||||||
|
])
|
||||||
|
->assertRedirect(route('admin.system.roles.index'));
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('roles', [
|
||||||
|
'id' => $role->id,
|
||||||
|
'name' => RoleEnum::DEVELOPER->value,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('owner role name cannot be changed', function () {
|
||||||
|
$user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW, PermissionEnum::ROLES_UPDATE);
|
||||||
|
|
||||||
|
$role = Role::findByName(RoleEnum::OWNER->value);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.roles.update', $role), [
|
||||||
|
'name' => 'renamed-owner',
|
||||||
|
'permissions' => $role->permissions->pluck('name')->toArray(),
|
||||||
|
])
|
||||||
|
->assertRedirect(route('admin.system.roles.index'));
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('roles', [
|
||||||
|
'id' => $role->id,
|
||||||
|
'name' => RoleEnum::OWNER->value,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('role permissions can be updated', function () {
|
||||||
|
$user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW, PermissionEnum::ROLES_UPDATE);
|
||||||
|
|
||||||
|
$role = Role::create(['name' => 'perm-test-role', 'guard_name' => 'web']);
|
||||||
|
$role->givePermissionTo(PermissionEnum::DASHBOARD_VIEW->value);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.roles.update', $role), [
|
||||||
|
'name' => 'perm-test-role',
|
||||||
|
'permissions' => [PermissionEnum::CATEGORIES_VIEW->value, PermissionEnum::ORDERS_VIEW->value],
|
||||||
|
])
|
||||||
|
->assertRedirect(route('admin.system.roles.index'));
|
||||||
|
|
||||||
|
$role->refresh();
|
||||||
|
$permissionNames = $role->permissions->pluck('name')->toArray();
|
||||||
|
|
||||||
|
expect($permissionNames)->toContain(PermissionEnum::CATEGORIES_VIEW->value);
|
||||||
|
expect($permissionNames)->toContain(PermissionEnum::ORDERS_VIEW->value);
|
||||||
|
expect($permissionNames)->not->toContain(PermissionEnum::DASHBOARD_VIEW->value);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('all permissions can be removed from role', function () {
|
||||||
|
$user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW, PermissionEnum::ROLES_UPDATE);
|
||||||
|
|
||||||
|
$role = Role::create(['name' => 'clear-perm-role', 'guard_name' => 'web']);
|
||||||
|
$role->givePermissionTo(PermissionEnum::DASHBOARD_VIEW->value);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.roles.update', $role), [
|
||||||
|
'name' => 'clear-perm-role',
|
||||||
|
'permissions' => [],
|
||||||
|
])
|
||||||
|
->assertRedirect(route('admin.system.roles.index'));
|
||||||
|
|
||||||
|
$role->refresh();
|
||||||
|
expect($role->permissions)->toHaveCount(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
616
tests/Feature/Admin/System/SettingTest.php
Normal file
616
tests/Feature/Admin/System/SettingTest.php
Normal file
@ -0,0 +1,616 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Enums\Permission as PermissionEnum;
|
||||||
|
use App\Models\User;
|
||||||
|
use Database\Seeders\RolePermissionSeeder;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Http\UploadedFile;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
uses(RefreshDatabase::class);
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
$this->seed(RolePermissionSeeder::class);
|
||||||
|
Storage::fake('public');
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Helper ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
function createSettingUserWithPermission(PermissionEnum ...$permissions): User
|
||||||
|
{
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$user->givePermissionTo(
|
||||||
|
array_merge(
|
||||||
|
[PermissionEnum::DASHBOARD_VIEW->value],
|
||||||
|
array_map(fn (PermissionEnum $p) => $p->value, $permissions)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$user->forgetCachedPermissions();
|
||||||
|
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validSystemPayload(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'app_name' => 'DST Collection',
|
||||||
|
'about_app' => 'Tentang aplikasi kami',
|
||||||
|
'email' => 'info@dstcollection.com',
|
||||||
|
'phone' => '081234567890',
|
||||||
|
'address' => 'Jl. Merdeka No. 1',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function validMarketplaceFeeRule(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'scope' => 'product',
|
||||||
|
'value_type' => 'percent',
|
||||||
|
'value' => 5.0,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function allMarketplaceFeeFields(): array
|
||||||
|
{
|
||||||
|
$fields = [
|
||||||
|
'tiktok_shop_platform_commission',
|
||||||
|
'tiktok_shop_logistics_service_fee',
|
||||||
|
'tiktok_shop_dynamic_commission',
|
||||||
|
'tiktok_shop_order_processing_fee',
|
||||||
|
'tiktok_shop_affiliate',
|
||||||
|
'tiktok_shop_pre_order_service_fee',
|
||||||
|
'shopee_admin_fee',
|
||||||
|
'shopee_program_fee',
|
||||||
|
'shopee_shipping_savings',
|
||||||
|
'shopee_premium',
|
||||||
|
'shopee_service_fee',
|
||||||
|
'shopee_order_processing_fee',
|
||||||
|
'shopee_ams_commission_fee',
|
||||||
|
'shopee_pre_order',
|
||||||
|
'shopee_live_extra',
|
||||||
|
];
|
||||||
|
|
||||||
|
return collect($fields)->mapWithKeys(fn (string $key) => [$key => validMarketplaceFeeRule()])->all();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Index ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe('Setting Index', function () {
|
||||||
|
test('authenticated user with permission can view settings page', function () {
|
||||||
|
$user = createSettingUserWithPermission(PermissionEnum::SETTINGS_VIEW);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->get(route('admin.system.settings.index'))
|
||||||
|
->assertOk();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('guest is redirected to login', function () {
|
||||||
|
$this->get(route('admin.system.settings.index'))
|
||||||
|
->assertRedirect(route('login'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('user without permission is forbidden', function () {
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->get(route('admin.system.settings.index'))
|
||||||
|
->assertForbidden();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Update System ────────────────────────────────────────
|
||||||
|
|
||||||
|
describe('Setting Update System', function () {
|
||||||
|
test('authenticated user with permission can update system settings', function () {
|
||||||
|
$user = createSettingUserWithPermission(PermissionEnum::SETTINGS_VIEW, PermissionEnum::SETTINGS_UPDATE);
|
||||||
|
|
||||||
|
$payload = validSystemPayload();
|
||||||
|
$payload['logo'] = UploadedFile::fake()->image('logo.jpg', 100, 100);
|
||||||
|
$payload['favicon'] = UploadedFile::fake()->image('favicon.png', 32, 32);
|
||||||
|
$payload['login_cover'] = UploadedFile::fake()->image('login-cover.jpg', 800, 600);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.settings.system.update'), $payload)
|
||||||
|
->assertRedirect(route('admin.system.settings.index'));
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('settings', [
|
||||||
|
'group' => 'system',
|
||||||
|
'name' => 'app_name',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('guest cannot update system settings', function () {
|
||||||
|
$this->put(route('admin.system.settings.system.update'), validSystemPayload())
|
||||||
|
->assertRedirect(route('login'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('user without update permission is forbidden', function () {
|
||||||
|
$user = createSettingUserWithPermission(PermissionEnum::SETTINGS_VIEW);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.settings.system.update'), validSystemPayload())
|
||||||
|
->assertForbidden();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('app_name is required', function () {
|
||||||
|
$user = createSettingUserWithPermission(PermissionEnum::SETTINGS_VIEW, PermissionEnum::SETTINGS_UPDATE);
|
||||||
|
|
||||||
|
$payload = validSystemPayload();
|
||||||
|
$payload['app_name'] = '';
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.settings.system.update'), $payload)
|
||||||
|
->assertSessionHasErrors('app_name');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('app_name must not exceed 100 characters', function () {
|
||||||
|
$user = createSettingUserWithPermission(PermissionEnum::SETTINGS_VIEW, PermissionEnum::SETTINGS_UPDATE);
|
||||||
|
|
||||||
|
$payload = validSystemPayload();
|
||||||
|
$payload['app_name'] = str_repeat('a', 101);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.settings.system.update'), $payload)
|
||||||
|
->assertSessionHasErrors('app_name');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('email must be valid', function () {
|
||||||
|
$user = createSettingUserWithPermission(PermissionEnum::SETTINGS_VIEW, PermissionEnum::SETTINGS_UPDATE);
|
||||||
|
|
||||||
|
$payload = validSystemPayload();
|
||||||
|
$payload['email'] = 'not-an-email';
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.settings.system.update'), $payload)
|
||||||
|
->assertSessionHasErrors('email');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('email is required', function () {
|
||||||
|
$user = createSettingUserWithPermission(PermissionEnum::SETTINGS_VIEW, PermissionEnum::SETTINGS_UPDATE);
|
||||||
|
|
||||||
|
$payload = validSystemPayload();
|
||||||
|
$payload['email'] = '';
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.settings.system.update'), $payload)
|
||||||
|
->assertSessionHasErrors('email');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('phone must be valid phone number', function () {
|
||||||
|
$user = createSettingUserWithPermission(PermissionEnum::SETTINGS_VIEW, PermissionEnum::SETTINGS_UPDATE);
|
||||||
|
|
||||||
|
$payload = validSystemPayload();
|
||||||
|
$payload['phone'] = '123invalid';
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.settings.system.update'), $payload)
|
||||||
|
->assertSessionHasErrors('phone');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('phone is required', function () {
|
||||||
|
$user = createSettingUserWithPermission(PermissionEnum::SETTINGS_VIEW, PermissionEnum::SETTINGS_UPDATE);
|
||||||
|
|
||||||
|
$payload = validSystemPayload();
|
||||||
|
$payload['phone'] = '';
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.settings.system.update'), $payload)
|
||||||
|
->assertSessionHasErrors('phone');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('about_app is required', function () {
|
||||||
|
$user = createSettingUserWithPermission(PermissionEnum::SETTINGS_VIEW, PermissionEnum::SETTINGS_UPDATE);
|
||||||
|
|
||||||
|
$payload = validSystemPayload();
|
||||||
|
$payload['about_app'] = '';
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.settings.system.update'), $payload)
|
||||||
|
->assertSessionHasErrors('about_app');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('address is required', function () {
|
||||||
|
$user = createSettingUserWithPermission(PermissionEnum::SETTINGS_VIEW, PermissionEnum::SETTINGS_UPDATE);
|
||||||
|
|
||||||
|
$payload = validSystemPayload();
|
||||||
|
$payload['address'] = '';
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.settings.system.update'), $payload)
|
||||||
|
->assertSessionHasErrors('address');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('logo must be an image when provided', function () {
|
||||||
|
$user = createSettingUserWithPermission(PermissionEnum::SETTINGS_VIEW, PermissionEnum::SETTINGS_UPDATE);
|
||||||
|
|
||||||
|
$payload = validSystemPayload();
|
||||||
|
$payload['logo'] = UploadedFile::fake()->create('document.pdf', 100);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.settings.system.update'), $payload)
|
||||||
|
->assertSessionHasErrors('logo');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('favicon must be an image when provided', function () {
|
||||||
|
$user = createSettingUserWithPermission(PermissionEnum::SETTINGS_VIEW, PermissionEnum::SETTINGS_UPDATE);
|
||||||
|
|
||||||
|
$payload = validSystemPayload();
|
||||||
|
$payload['favicon'] = UploadedFile::fake()->create('document.pdf', 100);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.settings.system.update'), $payload)
|
||||||
|
->assertSessionHasErrors('favicon');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Update Social Media ──────────────────────────────────
|
||||||
|
|
||||||
|
describe('Setting Update Social Media', function () {
|
||||||
|
test('authenticated user can update social media settings', function () {
|
||||||
|
$user = createSettingUserWithPermission(PermissionEnum::SETTINGS_VIEW, PermissionEnum::SETTINGS_UPDATE);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.settings.social_media.update'), [
|
||||||
|
'instagram_url' => 'https://instagram.com/dstcollection',
|
||||||
|
'facebook_url' => 'https://facebook.com/dstcollection',
|
||||||
|
'tiktok_url' => 'https://tiktok.com/@dstcollection',
|
||||||
|
])
|
||||||
|
->assertRedirect(route('admin.system.settings.index'));
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('settings', [
|
||||||
|
'group' => 'social_media',
|
||||||
|
'name' => 'instagram_url',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('guest cannot update social media settings', function () {
|
||||||
|
$this->put(route('admin.system.settings.social_media.update'), [
|
||||||
|
'instagram_url' => 'https://instagram.com/test',
|
||||||
|
])->assertRedirect(route('login'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('user without update permission is forbidden', function () {
|
||||||
|
$user = createSettingUserWithPermission(PermissionEnum::SETTINGS_VIEW);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.settings.social_media.update'), [
|
||||||
|
'instagram_url' => 'https://instagram.com/test',
|
||||||
|
])
|
||||||
|
->assertForbidden();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('social media urls can be null', function () {
|
||||||
|
$user = createSettingUserWithPermission(PermissionEnum::SETTINGS_VIEW, PermissionEnum::SETTINGS_UPDATE);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.settings.social_media.update'), [
|
||||||
|
'instagram_url' => null,
|
||||||
|
'facebook_url' => null,
|
||||||
|
'tiktok_url' => null,
|
||||||
|
])
|
||||||
|
->assertRedirect(route('admin.system.settings.index'));
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('settings', [
|
||||||
|
'group' => 'social_media',
|
||||||
|
'name' => 'instagram_url',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('instagram_url must be a valid url', function () {
|
||||||
|
$user = createSettingUserWithPermission(PermissionEnum::SETTINGS_VIEW, PermissionEnum::SETTINGS_UPDATE);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.settings.social_media.update'), [
|
||||||
|
'instagram_url' => 'not-a-url',
|
||||||
|
])
|
||||||
|
->assertSessionHasErrors('instagram_url');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('facebook_url must be a valid url', function () {
|
||||||
|
$user = createSettingUserWithPermission(PermissionEnum::SETTINGS_VIEW, PermissionEnum::SETTINGS_UPDATE);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.settings.social_media.update'), [
|
||||||
|
'facebook_url' => 'not-a-url',
|
||||||
|
])
|
||||||
|
->assertSessionHasErrors('facebook_url');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('tiktok_url must be a valid url', function () {
|
||||||
|
$user = createSettingUserWithPermission(PermissionEnum::SETTINGS_VIEW, PermissionEnum::SETTINGS_UPDATE);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.settings.social_media.update'), [
|
||||||
|
'tiktok_url' => 'not-a-url',
|
||||||
|
])
|
||||||
|
->assertSessionHasErrors('tiktok_url');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Update Marketplace ───────────────────────────────────
|
||||||
|
|
||||||
|
describe('Setting Update Marketplace', function () {
|
||||||
|
test('authenticated user can update marketplace settings', function () {
|
||||||
|
$user = createSettingUserWithPermission(PermissionEnum::SETTINGS_VIEW, PermissionEnum::SETTINGS_UPDATE);
|
||||||
|
|
||||||
|
$payload = allMarketplaceFeeFields();
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.settings.marketplace.update'), $payload)
|
||||||
|
->assertRedirect(route('admin.system.settings.index'));
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('settings', [
|
||||||
|
'group' => 'marketplace',
|
||||||
|
'name' => 'tiktok_shop_platform_commission',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('guest cannot update marketplace settings', function () {
|
||||||
|
$this->put(route('admin.system.settings.marketplace.update'), allMarketplaceFeeFields())
|
||||||
|
->assertRedirect(route('login'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('user without update permission is forbidden', function () {
|
||||||
|
$user = createSettingUserWithPermission(PermissionEnum::SETTINGS_VIEW);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.settings.marketplace.update'), allMarketplaceFeeFields())
|
||||||
|
->assertForbidden();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('marketplace fee scope must be valid enum', function () {
|
||||||
|
$user = createSettingUserWithPermission(PermissionEnum::SETTINGS_VIEW, PermissionEnum::SETTINGS_UPDATE);
|
||||||
|
|
||||||
|
$payload = allMarketplaceFeeFields();
|
||||||
|
$payload['tiktok_shop_platform_commission']['scope'] = 'invalid';
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.settings.marketplace.update'), $payload)
|
||||||
|
->assertSessionHasErrors('tiktok_shop_platform_commission.scope');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('marketplace fee value_type must be valid enum', function () {
|
||||||
|
$user = createSettingUserWithPermission(PermissionEnum::SETTINGS_VIEW, PermissionEnum::SETTINGS_UPDATE);
|
||||||
|
|
||||||
|
$payload = allMarketplaceFeeFields();
|
||||||
|
$payload['tiktok_shop_platform_commission']['value_type'] = 'invalid';
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.settings.marketplace.update'), $payload)
|
||||||
|
->assertSessionHasErrors('tiktok_shop_platform_commission.value_type');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('marketplace fee value must be numeric', function () {
|
||||||
|
$user = createSettingUserWithPermission(PermissionEnum::SETTINGS_VIEW, PermissionEnum::SETTINGS_UPDATE);
|
||||||
|
|
||||||
|
$payload = allMarketplaceFeeFields();
|
||||||
|
$payload['tiktok_shop_platform_commission']['value'] = 'not-a-number';
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.settings.marketplace.update'), $payload)
|
||||||
|
->assertSessionHasErrors('tiktok_shop_platform_commission.value');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('marketplace fee percent value must not exceed 100', function () {
|
||||||
|
$user = createSettingUserWithPermission(PermissionEnum::SETTINGS_VIEW, PermissionEnum::SETTINGS_UPDATE);
|
||||||
|
|
||||||
|
$payload = allMarketplaceFeeFields();
|
||||||
|
$payload['tiktok_shop_platform_commission']['value_type'] = 'percent';
|
||||||
|
$payload['tiktok_shop_platform_commission']['value'] = 150.0;
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.settings.marketplace.update'), $payload)
|
||||||
|
->assertSessionHasErrors('tiktok_shop_platform_commission.value');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('marketplace flat fee must not have decimals', function () {
|
||||||
|
$user = createSettingUserWithPermission(PermissionEnum::SETTINGS_VIEW, PermissionEnum::SETTINGS_UPDATE);
|
||||||
|
|
||||||
|
$payload = allMarketplaceFeeFields();
|
||||||
|
$payload['shopee_admin_fee']['value_type'] = 'flat';
|
||||||
|
$payload['shopee_admin_fee']['value'] = 1500.5;
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.settings.marketplace.update'), $payload)
|
||||||
|
->assertSessionHasErrors('shopee_admin_fee.value');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Update HR ────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe('Setting Update HR', function () {
|
||||||
|
test('authenticated user can update HR settings', function () {
|
||||||
|
$user = createSettingUserWithPermission(PermissionEnum::SETTINGS_VIEW, PermissionEnum::SETTINGS_UPDATE);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.settings.hr.update'), [
|
||||||
|
'scheduled_check_in_time' => '09:00',
|
||||||
|
'scheduled_check_out_time' => '18:00',
|
||||||
|
'late_penalty_amount' => 50000,
|
||||||
|
'absent_penalty_amount' => 100000,
|
||||||
|
])
|
||||||
|
->assertRedirect(route('admin.system.settings.index'));
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('settings', [
|
||||||
|
'group' => 'hr',
|
||||||
|
'name' => 'scheduled_check_in_time',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('settings', [
|
||||||
|
'group' => 'hr',
|
||||||
|
'name' => 'late_penalty_amount',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('guest cannot update HR settings', function () {
|
||||||
|
$this->put(route('admin.system.settings.hr.update'), [
|
||||||
|
'scheduled_check_in_time' => '09:00',
|
||||||
|
'scheduled_check_out_time' => '18:00',
|
||||||
|
'late_penalty_amount' => 0,
|
||||||
|
'absent_penalty_amount' => 0,
|
||||||
|
])->assertRedirect(route('login'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('user without update permission is forbidden', function () {
|
||||||
|
$user = createSettingUserWithPermission(PermissionEnum::SETTINGS_VIEW);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.settings.hr.update'), [
|
||||||
|
'scheduled_check_in_time' => '09:00',
|
||||||
|
'scheduled_check_out_time' => '18:00',
|
||||||
|
'late_penalty_amount' => 0,
|
||||||
|
'absent_penalty_amount' => 0,
|
||||||
|
])
|
||||||
|
->assertForbidden();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('scheduled_check_in_time is required', function () {
|
||||||
|
$user = createSettingUserWithPermission(PermissionEnum::SETTINGS_VIEW, PermissionEnum::SETTINGS_UPDATE);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.settings.hr.update'), [
|
||||||
|
'scheduled_check_in_time' => '',
|
||||||
|
'scheduled_check_out_time' => '18:00',
|
||||||
|
'late_penalty_amount' => 0,
|
||||||
|
'absent_penalty_amount' => 0,
|
||||||
|
])
|
||||||
|
->assertSessionHasErrors('scheduled_check_in_time');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('scheduled_check_in_time must match HH:MM format', function () {
|
||||||
|
$user = createSettingUserWithPermission(PermissionEnum::SETTINGS_VIEW, PermissionEnum::SETTINGS_UPDATE);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.settings.hr.update'), [
|
||||||
|
'scheduled_check_in_time' => '9:00',
|
||||||
|
'scheduled_check_out_time' => '18:00',
|
||||||
|
'late_penalty_amount' => 0,
|
||||||
|
'absent_penalty_amount' => 0,
|
||||||
|
])
|
||||||
|
->assertSessionHasErrors('scheduled_check_in_time');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('scheduled_check_out_time is required', function () {
|
||||||
|
$user = createSettingUserWithPermission(PermissionEnum::SETTINGS_VIEW, PermissionEnum::SETTINGS_UPDATE);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.settings.hr.update'), [
|
||||||
|
'scheduled_check_in_time' => '09:00',
|
||||||
|
'scheduled_check_out_time' => '',
|
||||||
|
'late_penalty_amount' => 0,
|
||||||
|
'absent_penalty_amount' => 0,
|
||||||
|
])
|
||||||
|
->assertSessionHasErrors('scheduled_check_out_time');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('late_penalty_amount must be integer', function () {
|
||||||
|
$user = createSettingUserWithPermission(PermissionEnum::SETTINGS_VIEW, PermissionEnum::SETTINGS_UPDATE);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.settings.hr.update'), [
|
||||||
|
'scheduled_check_in_time' => '09:00',
|
||||||
|
'scheduled_check_out_time' => '18:00',
|
||||||
|
'late_penalty_amount' => 'not-a-number',
|
||||||
|
'absent_penalty_amount' => 0,
|
||||||
|
])
|
||||||
|
->assertSessionHasErrors('late_penalty_amount');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('late_penalty_amount must not be negative', function () {
|
||||||
|
$user = createSettingUserWithPermission(PermissionEnum::SETTINGS_VIEW, PermissionEnum::SETTINGS_UPDATE);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.settings.hr.update'), [
|
||||||
|
'scheduled_check_in_time' => '09:00',
|
||||||
|
'scheduled_check_out_time' => '18:00',
|
||||||
|
'late_penalty_amount' => -1000,
|
||||||
|
'absent_penalty_amount' => 0,
|
||||||
|
])
|
||||||
|
->assertSessionHasErrors('late_penalty_amount');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('absent_penalty_amount must be integer', function () {
|
||||||
|
$user = createSettingUserWithPermission(PermissionEnum::SETTINGS_VIEW, PermissionEnum::SETTINGS_UPDATE);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.settings.hr.update'), [
|
||||||
|
'scheduled_check_in_time' => '09:00',
|
||||||
|
'scheduled_check_out_time' => '18:00',
|
||||||
|
'late_penalty_amount' => 0,
|
||||||
|
'absent_penalty_amount' => 'not-a-number',
|
||||||
|
])
|
||||||
|
->assertSessionHasErrors('absent_penalty_amount');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Update Homepage ──────────────────────────────────────
|
||||||
|
|
||||||
|
describe('Setting Update Homepage', function () {
|
||||||
|
test('authenticated user can update homepage settings', function () {
|
||||||
|
$user = createSettingUserWithPermission(PermissionEnum::SETTINGS_VIEW, PermissionEnum::SETTINGS_UPDATE);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.settings.homepage.update'), [])
|
||||||
|
->assertRedirect(route('admin.system.settings.index'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('guest cannot update homepage settings', function () {
|
||||||
|
$this->put(route('admin.system.settings.homepage.update'), [])
|
||||||
|
->assertRedirect(route('login'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('user without update permission is forbidden', function () {
|
||||||
|
$user = createSettingUserWithPermission(PermissionEnum::SETTINGS_VIEW);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.settings.homepage.update'), [])
|
||||||
|
->assertForbidden();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('hero_image must be an image when provided', function () {
|
||||||
|
$user = createSettingUserWithPermission(PermissionEnum::SETTINGS_VIEW, PermissionEnum::SETTINGS_UPDATE);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.settings.homepage.update'), [
|
||||||
|
'hero_image' => UploadedFile::fake()->create('document.pdf', 100),
|
||||||
|
])
|
||||||
|
->assertSessionHasErrors('hero_image');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('about_image must be an image when provided', function () {
|
||||||
|
$user = createSettingUserWithPermission(PermissionEnum::SETTINGS_VIEW, PermissionEnum::SETTINGS_UPDATE);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.settings.homepage.update'), [
|
||||||
|
'about_image' => UploadedFile::fake()->create('document.pdf', 100),
|
||||||
|
])
|
||||||
|
->assertSessionHasErrors('about_image');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('gallery_images must be an array of images', function () {
|
||||||
|
$user = createSettingUserWithPermission(PermissionEnum::SETTINGS_VIEW, PermissionEnum::SETTINGS_UPDATE);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.settings.homepage.update'), [
|
||||||
|
'gallery_images' => ['not-a-file'],
|
||||||
|
])
|
||||||
|
->assertSessionHasErrors('gallery_images.0');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('gallery_images must not exceed 10 items', function () {
|
||||||
|
$user = createSettingUserWithPermission(PermissionEnum::SETTINGS_VIEW, PermissionEnum::SETTINGS_UPDATE);
|
||||||
|
|
||||||
|
$images = [];
|
||||||
|
for ($i = 0; $i < 11; $i++) {
|
||||||
|
$images[] = UploadedFile::fake()->image("photo-{$i}.jpg");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->put(route('admin.system.settings.homepage.update'), [
|
||||||
|
'gallery_images' => $images,
|
||||||
|
])
|
||||||
|
->assertSessionHasErrors('gallery_images');
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user