Compare commits
No commits in common. "23961969f4c262db5e73d52bb5ada354c0f34e21" and "e9acf29c20bf44555870af3b6edc8c1776763e45" have entirely different histories.
23961969f4
...
e9acf29c20
@ -1,49 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Admin\Developer;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Http\Requests\PaginatedRequest;
|
|
||||||
use App\Services\Admin\Developer\LogViewerService;
|
|
||||||
use Inertia\Inertia;
|
|
||||||
use Inertia\Response;
|
|
||||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
|
||||||
|
|
||||||
class LogController extends Controller
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
private readonly LogViewerService $service,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public function index(PaginatedRequest $request): Response
|
|
||||||
{
|
|
||||||
$files = $this->service->availableFiles();
|
|
||||||
$file = $request->validated('file') ?: $files->first()['name'] ?? null;
|
|
||||||
|
|
||||||
return Inertia::render('admin/developer/logs/index', [
|
|
||||||
'entries' => $file
|
|
||||||
? $this->service->paginated(
|
|
||||||
$file,
|
|
||||||
...$request->validatedWithDefaults(),
|
|
||||||
level: $request->validated('level'),
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
'files' => $files,
|
|
||||||
'selectedFile' => $file,
|
|
||||||
'filters' => $request->only(['file', 'level']),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function download(PaginatedRequest $request): BinaryFileResponse
|
|
||||||
{
|
|
||||||
$file = $request->validated('file');
|
|
||||||
|
|
||||||
abort_unless($file, 404);
|
|
||||||
|
|
||||||
$files = $this->service->availableFiles();
|
|
||||||
|
|
||||||
abort_unless($files->pluck('name')->contains($file), 404);
|
|
||||||
|
|
||||||
return response()->download(storage_path('logs/'.$file));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Admin\Developer;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Http\Requests\Admin\Developer\UpdateRolePermissionsRequest;
|
|
||||||
use App\Services\Admin\Developer\RolePermissionService;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
|
||||||
use Inertia\Inertia;
|
|
||||||
use Inertia\Response;
|
|
||||||
use Spatie\Permission\Models\Role;
|
|
||||||
|
|
||||||
class RolePermissionController extends Controller
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
private readonly RolePermissionService $service,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public function index(): Response
|
|
||||||
{
|
|
||||||
return Inertia::render('admin/developer/roles/index', [
|
|
||||||
'roles' => $this->service->roles(),
|
|
||||||
'permissionGroups' => $this->service->groupedPermissions(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function update(UpdateRolePermissionsRequest $request, Role $role): RedirectResponse
|
|
||||||
{
|
|
||||||
$this->service->updatePermissions($role, $request->validated('permissions'));
|
|
||||||
|
|
||||||
Inertia::flash('toast', ['type' => 'success', 'message' => 'Permission role berhasil diperbarui.']);
|
|
||||||
|
|
||||||
return to_route('admin.developer.roles.index');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -42,7 +42,6 @@ public function share(Request $request): array
|
|||||||
'name' => config('app.name'),
|
'name' => config('app.name'),
|
||||||
'auth' => [
|
'auth' => [
|
||||||
'user' => $request->user()?->load('roles:id,name'),
|
'user' => $request->user()?->load('roles:id,name'),
|
||||||
'permissions' => $request->user()?->getAllPermissions()->pluck('name')->values() ?? [],
|
|
||||||
],
|
],
|
||||||
'sidebarOpen' => ! $request->hasCookie('sidebar_state') || $request->cookie('sidebar_state') === 'true',
|
'sidebarOpen' => ! $request->hasCookie('sidebar_state') || $request->cookie('sidebar_state') === 'true',
|
||||||
'unreadNotificationsCount' => fn () => $request->user()
|
'unreadNotificationsCount' => fn () => $request->user()
|
||||||
|
|||||||
@ -1,23 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Requests\Admin\Developer;
|
|
||||||
|
|
||||||
use App\Support\PermissionCatalog;
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
|
||||||
use Illuminate\Validation\Rule;
|
|
||||||
|
|
||||||
class UpdateRolePermissionsRequest extends FormRequest
|
|
||||||
{
|
|
||||||
public function authorize(): bool
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function rules(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'permissions' => ['present', 'array'],
|
|
||||||
'permissions.*' => ['string', Rule::in(PermissionCatalog::all())],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -35,8 +35,6 @@ public function rules(): array
|
|||||||
'lecturer_id' => ['nullable', 'integer'],
|
'lecturer_id' => ['nullable', 'integer'],
|
||||||
'type' => ['nullable', 'string'],
|
'type' => ['nullable', 'string'],
|
||||||
'payment_method' => ['nullable', 'string', Rule::in(PaymentMethod::values())],
|
'payment_method' => ['nullable', 'string', Rule::in(PaymentMethod::values())],
|
||||||
'file' => ['nullable', 'string'],
|
|
||||||
'level' => ['nullable', 'string'],
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,112 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Services\Admin\Developer;
|
|
||||||
|
|
||||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
|
||||||
use Illuminate\Pagination\LengthAwarePaginator as Paginator;
|
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
use Illuminate\Support\Facades\File;
|
|
||||||
|
|
||||||
class LogViewerService
|
|
||||||
{
|
|
||||||
private const ENTRY_PATTERN = '/^\[(\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:[+-]\d{2}:\d{2})?)\]\s+(\w+)\.(\w+):\s?(.*)$/s';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Collection<int, array{name: string, size: int, modified_at: string}>
|
|
||||||
*/
|
|
||||||
public function availableFiles(): Collection
|
|
||||||
{
|
|
||||||
return collect(File::glob(storage_path('logs/laravel*.log')))
|
|
||||||
->map(fn (string $path) => [
|
|
||||||
'name' => basename($path),
|
|
||||||
'size' => File::size($path),
|
|
||||||
'modified_at' => date('Y-m-d H:i:s', File::lastModified($path)),
|
|
||||||
'modified_timestamp' => File::lastModified($path),
|
|
||||||
])
|
|
||||||
->sortByDesc('modified_timestamp')
|
|
||||||
->values()
|
|
||||||
->map(fn (array $file) => collect($file)->except('modified_timestamp')->all());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function latestFileName(): ?string
|
|
||||||
{
|
|
||||||
return $this->availableFiles()->first()['name'] ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function paginated(string $fileName, int $perPage = 25, string $search = '', ?string $level = null): LengthAwarePaginator
|
|
||||||
{
|
|
||||||
$entries = $this->parse($fileName)
|
|
||||||
->when($level, fn (Collection $q) => $q->where('level', strtoupper($level)))
|
|
||||||
->when($search, fn (Collection $q) => $q->filter(
|
|
||||||
fn (array $entry) => str_contains(strtolower($entry['message']), strtolower($search))
|
|
||||||
))
|
|
||||||
->values();
|
|
||||||
|
|
||||||
$page = request()->integer('page', 1);
|
|
||||||
$items = $entries->forPage($page, $perPage)->values();
|
|
||||||
|
|
||||||
return new Paginator(
|
|
||||||
$items,
|
|
||||||
$entries->count(),
|
|
||||||
$perPage,
|
|
||||||
$page,
|
|
||||||
['path' => request()->url(), 'query' => request()->query()],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Collection<int, array{id: string, timestamp: string, environment: string, level: string, message: string, raw: string}>
|
|
||||||
*/
|
|
||||||
private function parse(string $fileName): Collection
|
|
||||||
{
|
|
||||||
$path = $this->resolvePath($fileName);
|
|
||||||
|
|
||||||
if (! $path) {
|
|
||||||
return collect();
|
|
||||||
}
|
|
||||||
|
|
||||||
$lines = preg_split('/\R/', File::get($path)) ?: [];
|
|
||||||
|
|
||||||
$entries = [];
|
|
||||||
$current = null;
|
|
||||||
|
|
||||||
foreach ($lines as $line) {
|
|
||||||
if (preg_match(self::ENTRY_PATTERN, $line, $matches)) {
|
|
||||||
if ($current) {
|
|
||||||
$entries[] = $current;
|
|
||||||
}
|
|
||||||
|
|
||||||
$current = [
|
|
||||||
'timestamp' => $matches[1],
|
|
||||||
'environment' => $matches[2],
|
|
||||||
'level' => strtoupper($matches[3]),
|
|
||||||
'message' => $matches[4],
|
|
||||||
'raw' => $line,
|
|
||||||
];
|
|
||||||
} elseif ($current !== null && trim($line) !== '') {
|
|
||||||
$current['message'] .= "\n".$line;
|
|
||||||
$current['raw'] .= "\n".$line;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($current) {
|
|
||||||
$entries[] = $current;
|
|
||||||
}
|
|
||||||
|
|
||||||
return collect($entries)
|
|
||||||
->reverse()
|
|
||||||
->values()
|
|
||||||
->map(fn (array $entry, int $index) => [...$entry, 'id' => (string) $index]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function resolvePath(string $fileName): ?string
|
|
||||||
{
|
|
||||||
$allowed = $this->availableFiles()->pluck('name');
|
|
||||||
|
|
||||||
if (! $allowed->contains($fileName)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return storage_path('logs/'.$fileName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,52 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Services\Admin\Developer;
|
|
||||||
|
|
||||||
use App\Support\PermissionCatalog;
|
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
use Spatie\Permission\Models\Role;
|
|
||||||
use Spatie\Permission\PermissionRegistrar;
|
|
||||||
|
|
||||||
class RolePermissionService
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @return array<string, array<int, string>>
|
|
||||||
*/
|
|
||||||
public function groupedPermissions(): array
|
|
||||||
{
|
|
||||||
return PermissionCatalog::groups();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Collection<int, array{id: int, name: string, permissions: array<int, string>}>
|
|
||||||
*/
|
|
||||||
public function roles(): Collection
|
|
||||||
{
|
|
||||||
return Role::with('permissions:id,name')
|
|
||||||
->orderBy('name')
|
|
||||||
->get(['id', 'name'])
|
|
||||||
->map(fn (Role $role) => [
|
|
||||||
'id' => $role->id,
|
|
||||||
'name' => $role->name,
|
|
||||||
'permissions' => $role->permissions->pluck('name')->all(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array<int, string> $permissionNames
|
|
||||||
*/
|
|
||||||
public function updatePermissions(Role $role, array $permissionNames): Role
|
|
||||||
{
|
|
||||||
$valid = array_values(array_intersect($permissionNames, PermissionCatalog::all()));
|
|
||||||
|
|
||||||
if ($role->name === 'developer') {
|
|
||||||
$valid = array_values(array_unique([...$valid, 'view-roles', 'update-roles']));
|
|
||||||
}
|
|
||||||
|
|
||||||
$role->syncPermissions($valid);
|
|
||||||
|
|
||||||
app(PermissionRegistrar::class)->forgetCachedPermissions();
|
|
||||||
|
|
||||||
return $role->load('permissions:id,name');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,75 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Support;
|
|
||||||
|
|
||||||
class PermissionCatalog
|
|
||||||
{
|
|
||||||
public const DASHBOARD = ['view-dashboard'];
|
|
||||||
|
|
||||||
public const MASTER = [
|
|
||||||
'view-academic-terms', 'create-academic-terms', 'update-academic-terms', 'delete-academic-terms', 'update-academic-terms-status',
|
|
||||||
'view-departments', 'create-departments', 'update-departments', 'delete-departments',
|
|
||||||
'view-courses', 'create-courses', 'update-courses', 'delete-courses',
|
|
||||||
];
|
|
||||||
|
|
||||||
public const ACADEMIC_CLASSES = [
|
|
||||||
'view-materials', 'create-materials', 'update-materials', 'delete-materials',
|
|
||||||
'view-assignments', 'create-assignments', 'update-assignments', 'delete-assignments',
|
|
||||||
'view-assignment-submissions', 'create-assignment-submissions', 'update-assignment-submissions', 'delete-assignment-submissions',
|
|
||||||
'view-schedules', 'create-schedules', 'update-schedules', 'delete-schedules',
|
|
||||||
'view-attendances', 'create-attendances', 'delete-attendances',
|
|
||||||
];
|
|
||||||
|
|
||||||
public const MANAGE = [
|
|
||||||
'view-course-classes', 'create-course-classes', 'update-course-classes', 'delete-course-classes',
|
|
||||||
'view-course-class-enrollments', 'create-course-class-enrollments', 'delete-course-class-enrollments',
|
|
||||||
'view-course-registrations', 'create-course-registrations', 'approve-course-registrations', 'reject-course-registrations',
|
|
||||||
'view-announcements', 'create-announcements', 'update-announcements', 'delete-announcements',
|
|
||||||
];
|
|
||||||
|
|
||||||
public const FINANCES = [
|
|
||||||
'view-tuition-invoices', 'create-tuition-invoices', 'update-tuition-invoices', 'delete-tuition-invoices',
|
|
||||||
'view-tuition-payments', 'create-tuition-payments', 'update-tuition-payments', 'delete-tuition-payments',
|
|
||||||
];
|
|
||||||
|
|
||||||
public const SERVICES = [
|
|
||||||
'view-letter-requests', 'create-letter-requests', 'update-letter-requests', 'delete-letter-requests',
|
|
||||||
'view-academic-advising-logs', 'create-academic-advising-logs', 'update-academic-advising-logs', 'delete-academic-advising-logs',
|
|
||||||
];
|
|
||||||
|
|
||||||
public const USERS = [
|
|
||||||
'view-lecturers', 'create-lecturers', 'update-lecturers', 'delete-lecturers', 'reset-lecturers-password', 'update-lecturers-status', 'export-lecturers',
|
|
||||||
'view-students', 'create-students', 'update-students', 'delete-students', 'reset-students-password', 'update-students-academic-status', 'update-students-account-status', 'export-students',
|
|
||||||
'view-administrators', 'create-administrators', 'update-administrators', 'delete-administrators', 'reset-administrators-password', 'update-administrators-status',
|
|
||||||
];
|
|
||||||
|
|
||||||
public const DEVELOPER = [
|
|
||||||
'view-logs',
|
|
||||||
'view-roles', 'update-roles',
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array<string, array<int, string>>
|
|
||||||
*/
|
|
||||||
public static function groups(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'Dashboard' => self::DASHBOARD,
|
|
||||||
'Master' => self::MASTER,
|
|
||||||
'Kelas' => self::ACADEMIC_CLASSES,
|
|
||||||
'Kelola' => self::MANAGE,
|
|
||||||
'Keuangan' => self::FINANCES,
|
|
||||||
'Layanan' => self::SERVICES,
|
|
||||||
'Pengguna' => self::USERS,
|
|
||||||
'Pengembang' => self::DEVELOPER,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array<int, string>
|
|
||||||
*/
|
|
||||||
public static function all(): array
|
|
||||||
{
|
|
||||||
return array_merge(...array_values(self::groups()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -7,7 +7,6 @@
|
|||||||
use Illuminate\Foundation\Configuration\Middleware;
|
use Illuminate\Foundation\Configuration\Middleware;
|
||||||
use Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets;
|
use Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Spatie\Permission\Middleware\PermissionMiddleware;
|
|
||||||
use Spatie\Permission\Middleware\RoleMiddleware;
|
use Spatie\Permission\Middleware\RoleMiddleware;
|
||||||
|
|
||||||
return Application::configure(basePath: dirname(__DIR__))
|
return Application::configure(basePath: dirname(__DIR__))
|
||||||
@ -27,7 +26,6 @@
|
|||||||
|
|
||||||
$middleware->alias([
|
$middleware->alias([
|
||||||
'role' => RoleMiddleware::class,
|
'role' => RoleMiddleware::class,
|
||||||
'permission' => PermissionMiddleware::class,
|
|
||||||
]);
|
]);
|
||||||
})
|
})
|
||||||
->withExceptions(function (Exceptions $exceptions): void {
|
->withExceptions(function (Exceptions $exceptions): void {
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace Database\Seeders;
|
namespace Database\Seeders;
|
||||||
|
|
||||||
use App\Support\PermissionCatalog;
|
|
||||||
use Illuminate\Database\Seeder;
|
use Illuminate\Database\Seeder;
|
||||||
use Spatie\Permission\Models\Permission;
|
use Spatie\Permission\Models\Permission;
|
||||||
use Spatie\Permission\Models\Role;
|
use Spatie\Permission\Models\Role;
|
||||||
@ -14,17 +13,17 @@ public function run(): void
|
|||||||
{
|
{
|
||||||
app()[PermissionRegistrar::class]->forgetCachedPermissions();
|
app()[PermissionRegistrar::class]->forgetCachedPermissions();
|
||||||
|
|
||||||
$master = PermissionCatalog::MASTER;
|
$permissionNames = [
|
||||||
$academicClasses = PermissionCatalog::ACADEMIC_CLASSES;
|
'view-dashboard',
|
||||||
$manage = PermissionCatalog::MANAGE;
|
'view-academic-terms',
|
||||||
$finances = PermissionCatalog::FINANCES;
|
'create-academic-terms',
|
||||||
$services = PermissionCatalog::SERVICES;
|
'update-academic-terms',
|
||||||
$users = PermissionCatalog::USERS;
|
'delete-academic-terms',
|
||||||
|
];
|
||||||
$permissionNames = PermissionCatalog::all();
|
|
||||||
|
|
||||||
|
$permissions = [];
|
||||||
foreach ($permissionNames as $name) {
|
foreach ($permissionNames as $name) {
|
||||||
Permission::firstOrCreate(['name' => $name, 'guard_name' => 'web']);
|
$permissions[] = Permission::firstOrCreate(['name' => $name, 'guard_name' => 'web']);
|
||||||
}
|
}
|
||||||
|
|
||||||
app()[PermissionRegistrar::class]->forgetCachedPermissions();
|
app()[PermissionRegistrar::class]->forgetCachedPermissions();
|
||||||
@ -33,31 +32,20 @@ public function run(): void
|
|||||||
'mahasiswa' => ['view-dashboard'],
|
'mahasiswa' => ['view-dashboard'],
|
||||||
'dosen' => ['view-dashboard'],
|
'dosen' => ['view-dashboard'],
|
||||||
'staff-admin' => [
|
'staff-admin' => [
|
||||||
'view-dashboard',
|
|
||||||
...$master,
|
|
||||||
...$academicClasses,
|
|
||||||
...$manage,
|
|
||||||
...$services,
|
|
||||||
...$users,
|
|
||||||
],
|
|
||||||
'staff-keuangan' => [
|
|
||||||
'view-dashboard',
|
|
||||||
...$finances,
|
|
||||||
],
|
|
||||||
'kaprodi' => [
|
|
||||||
'view-dashboard',
|
'view-dashboard',
|
||||||
'view-academic-terms',
|
'view-academic-terms',
|
||||||
'view-courses',
|
'create-academic-terms',
|
||||||
'view-course-classes',
|
'update-academic-terms',
|
||||||
'view-course-registrations',
|
'delete-academic-terms',
|
||||||
'view-students',
|
|
||||||
],
|
],
|
||||||
|
'staff-keuangan' => ['view-dashboard'],
|
||||||
|
'kaprodi' => ['view-dashboard', 'view-academic-terms'],
|
||||||
'developer' => $permissionNames,
|
'developer' => $permissionNames,
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach ($roles as $roleName => $rolePermissions) {
|
foreach ($roles as $roleName => $rolePermissions) {
|
||||||
$role = Role::firstOrCreate(['name' => $roleName, 'guard_name' => 'web']);
|
$role = Role::firstOrCreate(['name' => $roleName, 'guard_name' => 'web']);
|
||||||
$role->syncPermissions($rolePermissions);
|
$role->givePermissionTo($rolePermissions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,21 +3,15 @@ import { Switch } from '@/components/ui/switch';
|
|||||||
type ActiveStatusSwitchProps = {
|
type ActiveStatusSwitchProps = {
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
onChange: (isActive: boolean) => void;
|
onChange: (isActive: boolean) => void;
|
||||||
disabled?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ActiveStatusSwitch({
|
export function ActiveStatusSwitch({
|
||||||
isActive,
|
isActive,
|
||||||
onChange,
|
onChange,
|
||||||
disabled,
|
|
||||||
}: ActiveStatusSwitchProps) {
|
}: ActiveStatusSwitchProps) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Switch
|
<Switch checked={isActive} onCheckedChange={onChange} />
|
||||||
checked={isActive}
|
|
||||||
onCheckedChange={onChange}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-sm text-muted-foreground">
|
||||||
{isActive ? 'Aktif' : 'Nonaktif'}
|
{isActive ? 'Aktif' : 'Nonaktif'}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@ -16,8 +16,6 @@ import {
|
|||||||
MessageCircle,
|
MessageCircle,
|
||||||
Receipt,
|
Receipt,
|
||||||
School,
|
School,
|
||||||
ScrollText,
|
|
||||||
ShieldCheck,
|
|
||||||
User,
|
User,
|
||||||
Users,
|
Users,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
@ -39,8 +37,6 @@ import { index as assignmentsRoute } from '@/routes/admin/academic-classes/assig
|
|||||||
import { index as attendancesRoute } from '@/routes/admin/academic-classes/attendances';
|
import { index as attendancesRoute } from '@/routes/admin/academic-classes/attendances';
|
||||||
import { index as materialsRoute } from '@/routes/admin/academic-classes/materials';
|
import { index as materialsRoute } from '@/routes/admin/academic-classes/materials';
|
||||||
import { index as schedulesRoute } from '@/routes/admin/academic-classes/schedules';
|
import { index as schedulesRoute } from '@/routes/admin/academic-classes/schedules';
|
||||||
import { index as logsRoute } from '@/routes/admin/developer/logs';
|
|
||||||
import { index as rolePermissionsRoute } from '@/routes/admin/developer/roles';
|
|
||||||
import { index as feedbackRoute } from '@/routes/admin/feedback';
|
import { index as feedbackRoute } from '@/routes/admin/feedback';
|
||||||
import { index as tuitionInvoicesRoute } from '@/routes/admin/finances/tuition-invoices';
|
import { index as tuitionInvoicesRoute } from '@/routes/admin/finances/tuition-invoices';
|
||||||
import { index as announcementsRoute } from '@/routes/admin/manage/announcements';
|
import { index as announcementsRoute } from '@/routes/admin/manage/announcements';
|
||||||
@ -64,13 +60,9 @@ const STAFF_ROLES = ['developer', 'staff-admin', 'staff-keuangan', 'kaprodi'];
|
|||||||
function buildNavMain({
|
function buildNavMain({
|
||||||
isMahasiswa,
|
isMahasiswa,
|
||||||
isDosen,
|
isDosen,
|
||||||
canViewLogs,
|
|
||||||
canViewRoles,
|
|
||||||
}: {
|
}: {
|
||||||
isMahasiswa: boolean;
|
isMahasiswa: boolean;
|
||||||
isDosen: boolean;
|
isDosen: boolean;
|
||||||
canViewLogs: boolean;
|
|
||||||
canViewRoles: boolean;
|
|
||||||
}): (NavGroup | NavItem)[] {
|
}): (NavGroup | NavItem)[] {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@ -194,33 +186,6 @@ function buildNavMain({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
...(canViewLogs || canViewRoles
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
label: 'Pengembang',
|
|
||||||
items: [
|
|
||||||
...(canViewLogs
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
name: 'Logs',
|
|
||||||
url: logsRoute.url(),
|
|
||||||
icon: ScrollText,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
...(canViewRoles
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
name: 'Role & Permission',
|
|
||||||
url: rolePermissionsRoute.url(),
|
|
||||||
icon: ShieldCheck,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,14 +208,7 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
|||||||
const isStaff = roleNames.some((role) => STAFF_ROLES.includes(role));
|
const isStaff = roleNames.some((role) => STAFF_ROLES.includes(role));
|
||||||
const isMahasiswa = !isStaff && roleNames.includes('mahasiswa');
|
const isMahasiswa = !isStaff && roleNames.includes('mahasiswa');
|
||||||
const isDosen = !isStaff && roleNames.includes('dosen');
|
const isDosen = !isStaff && roleNames.includes('dosen');
|
||||||
const canViewLogs = (auth?.permissions ?? []).includes('view-logs');
|
const navMain = buildNavMain({ isMahasiswa, isDosen });
|
||||||
const canViewRoles = (auth?.permissions ?? []).includes('view-roles');
|
|
||||||
const navMain = buildNavMain({
|
|
||||||
isMahasiswa,
|
|
||||||
isDosen,
|
|
||||||
canViewLogs,
|
|
||||||
canViewRoles,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sidebar collapsible="icon" {...props}>
|
<Sidebar collapsible="icon" {...props}>
|
||||||
|
|||||||
@ -13,7 +13,6 @@ type StudentStatusBadgeProps = {
|
|||||||
status: string | null | undefined;
|
status: string | null | undefined;
|
||||||
statuses: StatusOption[];
|
statuses: StatusOption[];
|
||||||
onChange: (status: string) => void;
|
onChange: (status: string) => void;
|
||||||
disabled?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const StudentStatusVariants: Record<
|
const StudentStatusVariants: Record<
|
||||||
@ -30,7 +29,6 @@ export function StudentStatusBadge({
|
|||||||
status,
|
status,
|
||||||
statuses,
|
statuses,
|
||||||
onChange,
|
onChange,
|
||||||
disabled,
|
|
||||||
}: StudentStatusBadgeProps) {
|
}: StudentStatusBadgeProps) {
|
||||||
const label =
|
const label =
|
||||||
statuses.find((option) => option.value === status)?.label ??
|
statuses.find((option) => option.value === status)?.label ??
|
||||||
@ -40,11 +38,10 @@ export function StudentStatusBadge({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild disabled={disabled}>
|
<DropdownMenuTrigger asChild>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
disabled={disabled}
|
className="cursor-pointer rounded-full border-0 bg-transparent p-0 outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||||
className="cursor-pointer rounded-full border-0 bg-transparent p-0 outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-default disabled:opacity-70"
|
|
||||||
>
|
>
|
||||||
<Badge variant={variant}>{label}</Badge>
|
<Badge variant={variant}>{label}</Badge>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -1,17 +0,0 @@
|
|||||||
import { usePage } from '@inertiajs/react';
|
|
||||||
import type { Auth } from '@/types/auth';
|
|
||||||
|
|
||||||
export function usePermissions() {
|
|
||||||
const { auth } = usePage<{ auth: Auth }>().props;
|
|
||||||
const permissions = auth?.permissions ?? [];
|
|
||||||
|
|
||||||
function hasPermission(name: string): boolean {
|
|
||||||
return permissions.includes(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasAnyPermission(names: string[]): boolean {
|
|
||||||
return names.some((name) => permissions.includes(name));
|
|
||||||
}
|
|
||||||
|
|
||||||
return { permissions, hasPermission, hasAnyPermission };
|
|
||||||
}
|
|
||||||
@ -10,21 +10,12 @@ export type { Assignment } from '@/types/assignment';
|
|||||||
type CreateColumnsParams = {
|
type CreateColumnsParams = {
|
||||||
handleEdit: (assignment: Assignment) => void;
|
handleEdit: (assignment: Assignment) => void;
|
||||||
handleDeleteClick: (assignment: Assignment) => void;
|
handleDeleteClick: (assignment: Assignment) => void;
|
||||||
canUpdate: boolean;
|
|
||||||
canDelete: boolean;
|
|
||||||
canViewSubmissions: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createAssignmentColumns(
|
export function createAssignmentColumns(
|
||||||
params: CreateColumnsParams,
|
params: CreateColumnsParams,
|
||||||
): ColumnDef<Assignment>[] {
|
): ColumnDef<Assignment>[] {
|
||||||
const {
|
const { handleEdit, handleDeleteClick } = params;
|
||||||
handleEdit,
|
|
||||||
handleDeleteClick,
|
|
||||||
canUpdate,
|
|
||||||
canDelete,
|
|
||||||
canViewSubmissions,
|
|
||||||
} = params;
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@ -112,13 +103,11 @@ export function createAssignmentColumns(
|
|||||||
{
|
{
|
||||||
label: 'Pengumpulan',
|
label: 'Pengumpulan',
|
||||||
icon: <ClipboardList className="h-4 w-4" />,
|
icon: <ClipboardList className="h-4 w-4" />,
|
||||||
show: canViewSubmissions,
|
|
||||||
href: submissionsIndex.url(assignment.id),
|
href: submissionsIndex.url(assignment.id),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Edit',
|
label: 'Edit',
|
||||||
icon: <Pencil className="h-4 w-4" />,
|
icon: <Pencil className="h-4 w-4" />,
|
||||||
show: canUpdate,
|
|
||||||
onClick: () => handleEdit(assignment),
|
onClick: () => handleEdit(assignment),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -126,7 +115,6 @@ export function createAssignmentColumns(
|
|||||||
icon: (
|
icon: (
|
||||||
<Trash2 className="h-4 w-4 text-destructive" />
|
<Trash2 className="h-4 w-4 text-destructive" />
|
||||||
),
|
),
|
||||||
show: canDelete,
|
|
||||||
onClick: () => handleDeleteClick(assignment),
|
onClick: () => handleDeleteClick(assignment),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
|||||||
@ -22,7 +22,6 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@/components/ui/select';
|
} from '@/components/ui/select';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
import { usePermissions } from '@/hooks/use-permissions';
|
|
||||||
import { useServerTable } from '@/hooks/use-server-table';
|
import { useServerTable } from '@/hooks/use-server-table';
|
||||||
import {
|
import {
|
||||||
index as assignmentIndex,
|
index as assignmentIndex,
|
||||||
@ -66,11 +65,6 @@ export default function AssignmentIndex({
|
|||||||
const [createOpen, setCreateOpen] = useState(false);
|
const [createOpen, setCreateOpen] = useState(false);
|
||||||
const [editing, setEditing] = useState<Assignment | null>(null);
|
const [editing, setEditing] = useState<Assignment | null>(null);
|
||||||
const [deleting, setDeleting] = useState<Assignment | null>(null);
|
const [deleting, setDeleting] = useState<Assignment | null>(null);
|
||||||
const { hasPermission } = usePermissions();
|
|
||||||
const canCreate = hasPermission('create-assignments');
|
|
||||||
const canUpdate = hasPermission('update-assignments');
|
|
||||||
const canDelete = hasPermission('delete-assignments');
|
|
||||||
const canViewSubmissions = hasPermission('view-assignment-submissions');
|
|
||||||
|
|
||||||
const filterFields: FilterField[] = [
|
const filterFields: FilterField[] = [
|
||||||
{
|
{
|
||||||
@ -115,9 +109,6 @@ export default function AssignmentIndex({
|
|||||||
const columns = createAssignmentColumns({
|
const columns = createAssignmentColumns({
|
||||||
handleEdit: (assignment) => setEditing(assignment),
|
handleEdit: (assignment) => setEditing(assignment),
|
||||||
handleDeleteClick: (assignment) => setDeleting(assignment),
|
handleDeleteClick: (assignment) => setDeleting(assignment),
|
||||||
canUpdate,
|
|
||||||
canDelete,
|
|
||||||
canViewSubmissions,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -150,17 +141,15 @@ export default function AssignmentIndex({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
actions={
|
actions={
|
||||||
canCreate && (
|
<Button asChild>
|
||||||
<Button asChild>
|
<button
|
||||||
<button
|
type="button"
|
||||||
type="button"
|
onClick={() => setCreateOpen(true)}
|
||||||
onClick={() => setCreateOpen(true)}
|
>
|
||||||
>
|
<Plus className="h-4 w-4" />
|
||||||
<Plus className="h-4 w-4" />
|
Tambah
|
||||||
Tambah
|
</button>
|
||||||
</button>
|
</Button>
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@ -24,7 +24,6 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@/components/ui/select';
|
} from '@/components/ui/select';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
import { usePermissions } from '@/hooks/use-permissions';
|
|
||||||
import { index as assignmentIndex } from '@/routes/admin/academic-classes/assignments';
|
import { index as assignmentIndex } from '@/routes/admin/academic-classes/assignments';
|
||||||
import {
|
import {
|
||||||
destroy,
|
destroy,
|
||||||
@ -49,10 +48,6 @@ export default function SubmissionIndex({
|
|||||||
const [createOpen, setCreateOpen] = useState(false);
|
const [createOpen, setCreateOpen] = useState(false);
|
||||||
const [editing, setEditing] = useState<Submission | null>(null);
|
const [editing, setEditing] = useState<Submission | null>(null);
|
||||||
const [deleting, setDeleting] = useState<Submission | null>(null);
|
const [deleting, setDeleting] = useState<Submission | null>(null);
|
||||||
const { hasPermission } = usePermissions();
|
|
||||||
const canCreate = hasPermission('create-assignment-submissions');
|
|
||||||
const canUpdate = hasPermission('update-assignment-submissions');
|
|
||||||
const canDelete = hasPermission('delete-assignment-submissions');
|
|
||||||
|
|
||||||
function handleDelete() {
|
function handleDelete() {
|
||||||
if (!deleting) {
|
if (!deleting) {
|
||||||
@ -142,7 +137,6 @@ export default function SubmissionIndex({
|
|||||||
{
|
{
|
||||||
label: 'Edit',
|
label: 'Edit',
|
||||||
icon: <Pencil className="h-4 w-4" />,
|
icon: <Pencil className="h-4 w-4" />,
|
||||||
show: canUpdate,
|
|
||||||
onClick: () => setEditing(row.original),
|
onClick: () => setEditing(row.original),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -150,7 +144,6 @@ export default function SubmissionIndex({
|
|||||||
icon: (
|
icon: (
|
||||||
<Trash2 className="h-4 w-4 text-destructive" />
|
<Trash2 className="h-4 w-4 text-destructive" />
|
||||||
),
|
),
|
||||||
show: canDelete,
|
|
||||||
onClick: () => setDeleting(row.original),
|
onClick: () => setDeleting(row.original),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
@ -200,15 +193,13 @@ export default function SubmissionIndex({
|
|||||||
<h2 className="text-lg font-semibold">
|
<h2 className="text-lg font-semibold">
|
||||||
Daftar Pengumpulan
|
Daftar Pengumpulan
|
||||||
</h2>
|
</h2>
|
||||||
{canCreate && (
|
<Button
|
||||||
<Button
|
onClick={() => setCreateOpen(true)}
|
||||||
onClick={() => setCreateOpen(true)}
|
disabled={availableStudents.length === 0}
|
||||||
disabled={availableStudents.length === 0}
|
>
|
||||||
>
|
<Plus className="h-4 w-4" />
|
||||||
<Plus className="h-4 w-4" />
|
Tambah Pengumpulan
|
||||||
Tambah Pengumpulan
|
</Button>
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DataTable columns={columns} data={submissions} />
|
<DataTable columns={columns} data={submissions} />
|
||||||
|
|||||||
@ -23,7 +23,6 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@/components/ui/select';
|
} from '@/components/ui/select';
|
||||||
import { usePermissions } from '@/hooks/use-permissions';
|
|
||||||
import { destroy, session } from '@/routes/admin/academic-classes/attendances';
|
import { destroy, session } from '@/routes/admin/academic-classes/attendances';
|
||||||
import type {
|
import type {
|
||||||
AttendanceCourseClass,
|
AttendanceCourseClass,
|
||||||
@ -42,9 +41,6 @@ function courseClassLabel(courseClass: AttendanceCourseClass): string {
|
|||||||
export default function AttendanceIndex({ sessions, courseClasses }: Props) {
|
export default function AttendanceIndex({ sessions, courseClasses }: Props) {
|
||||||
const [newSessionOpen, setNewSessionOpen] = useState(false);
|
const [newSessionOpen, setNewSessionOpen] = useState(false);
|
||||||
const [deleting, setDeleting] = useState<AttendanceSession | null>(null);
|
const [deleting, setDeleting] = useState<AttendanceSession | null>(null);
|
||||||
const { hasPermission } = usePermissions();
|
|
||||||
const canCreate = hasPermission('create-attendances');
|
|
||||||
const canDelete = hasPermission('delete-attendances');
|
|
||||||
|
|
||||||
function handleDelete() {
|
function handleDelete() {
|
||||||
if (!deleting || deleting.meeting_number === null) {
|
if (!deleting || deleting.meeting_number === null) {
|
||||||
@ -65,12 +61,10 @@ export default function AttendanceIndex({ sessions, courseClasses }: Props) {
|
|||||||
<PageHeader
|
<PageHeader
|
||||||
title="Kehadiran"
|
title="Kehadiran"
|
||||||
actions={
|
actions={
|
||||||
canCreate && (
|
<Button onClick={() => setNewSessionOpen(true)}>
|
||||||
<Button onClick={() => setNewSessionOpen(true)}>
|
<Plus className="h-4 w-4" />
|
||||||
<Plus className="h-4 w-4" />
|
Ambil Kehadiran
|
||||||
Ambil Kehadiran
|
</Button>
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -135,18 +129,16 @@ export default function AttendanceIndex({ sessions, courseClasses }: Props) {
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
{canDelete && (
|
<Button
|
||||||
<Button
|
variant="ghost"
|
||||||
variant="ghost"
|
size="sm"
|
||||||
size="sm"
|
disabled={!canOpen}
|
||||||
disabled={!canOpen}
|
onClick={() =>
|
||||||
onClick={() =>
|
setDeleting(item)
|
||||||
setDeleting(item)
|
}
|
||||||
}
|
>
|
||||||
>
|
<Trash2 className="h-4 w-4 text-destructive" />
|
||||||
<Trash2 className="h-4 w-4 text-destructive" />
|
</Button>
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import { PageHeader } from '@/components/page-header';
|
|||||||
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 { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { usePermissions } from '@/hooks/use-permissions';
|
|
||||||
import {
|
import {
|
||||||
index as attendanceIndex,
|
index as attendanceIndex,
|
||||||
store,
|
store,
|
||||||
@ -46,8 +45,6 @@ export default function AttendanceSession({
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
const { hasPermission } = usePermissions();
|
|
||||||
const canSave = hasPermission('create-attendances');
|
|
||||||
|
|
||||||
function setAll(status: AttendanceStatus) {
|
function setAll(status: AttendanceStatus) {
|
||||||
setStatuses(
|
setStatuses(
|
||||||
@ -201,7 +198,7 @@ export default function AttendanceSession({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{roster.length > 0 && canSave && (
|
{roster.length > 0 && (
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Button
|
<Button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
|
|||||||
@ -9,14 +9,12 @@ export type { Material } from '@/types/material';
|
|||||||
type CreateColumnsParams = {
|
type CreateColumnsParams = {
|
||||||
handleEdit: (material: Material) => void;
|
handleEdit: (material: Material) => void;
|
||||||
handleDeleteClick: (material: Material) => void;
|
handleDeleteClick: (material: Material) => void;
|
||||||
canUpdate: boolean;
|
|
||||||
canDelete: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createMaterialColumns(
|
export function createMaterialColumns(
|
||||||
params: CreateColumnsParams,
|
params: CreateColumnsParams,
|
||||||
): ColumnDef<Material>[] {
|
): ColumnDef<Material>[] {
|
||||||
const { handleEdit, handleDeleteClick, canUpdate, canDelete } = params;
|
const { handleEdit, handleDeleteClick } = params;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@ -102,7 +100,6 @@ export function createMaterialColumns(
|
|||||||
{
|
{
|
||||||
label: 'Edit',
|
label: 'Edit',
|
||||||
icon: <Pencil className="h-4 w-4" />,
|
icon: <Pencil className="h-4 w-4" />,
|
||||||
show: canUpdate,
|
|
||||||
onClick: () => handleEdit(material),
|
onClick: () => handleEdit(material),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -110,7 +107,6 @@ export function createMaterialColumns(
|
|||||||
icon: (
|
icon: (
|
||||||
<Trash2 className="h-4 w-4 text-destructive" />
|
<Trash2 className="h-4 w-4 text-destructive" />
|
||||||
),
|
),
|
||||||
show: canDelete,
|
|
||||||
onClick: () => handleDeleteClick(material),
|
onClick: () => handleDeleteClick(material),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
|||||||
@ -21,7 +21,6 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@/components/ui/select';
|
} from '@/components/ui/select';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
import { usePermissions } from '@/hooks/use-permissions';
|
|
||||||
import { useServerTable } from '@/hooks/use-server-table';
|
import { useServerTable } from '@/hooks/use-server-table';
|
||||||
import {
|
import {
|
||||||
index as materialIndex,
|
index as materialIndex,
|
||||||
@ -65,10 +64,6 @@ export default function MaterialIndex({
|
|||||||
const [createOpen, setCreateOpen] = useState(false);
|
const [createOpen, setCreateOpen] = useState(false);
|
||||||
const [editing, setEditing] = useState<Material | null>(null);
|
const [editing, setEditing] = useState<Material | null>(null);
|
||||||
const [deleting, setDeleting] = useState<Material | null>(null);
|
const [deleting, setDeleting] = useState<Material | null>(null);
|
||||||
const { hasPermission } = usePermissions();
|
|
||||||
const canCreate = hasPermission('create-materials');
|
|
||||||
const canUpdate = hasPermission('update-materials');
|
|
||||||
const canDelete = hasPermission('delete-materials');
|
|
||||||
|
|
||||||
const filterFields: FilterField[] = [
|
const filterFields: FilterField[] = [
|
||||||
{
|
{
|
||||||
@ -113,8 +108,6 @@ export default function MaterialIndex({
|
|||||||
const columns = createMaterialColumns({
|
const columns = createMaterialColumns({
|
||||||
handleEdit: (material) => setEditing(material),
|
handleEdit: (material) => setEditing(material),
|
||||||
handleDeleteClick: (material) => setDeleting(material),
|
handleDeleteClick: (material) => setDeleting(material),
|
||||||
canUpdate,
|
|
||||||
canDelete,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -147,17 +140,15 @@ export default function MaterialIndex({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
actions={
|
actions={
|
||||||
canCreate && (
|
<Button asChild>
|
||||||
<Button asChild>
|
<button
|
||||||
<button
|
type="button"
|
||||||
type="button"
|
onClick={() => setCreateOpen(true)}
|
||||||
onClick={() => setCreateOpen(true)}
|
>
|
||||||
>
|
<Plus className="h-4 w-4" />
|
||||||
<Plus className="h-4 w-4" />
|
Tambah
|
||||||
Tambah
|
</button>
|
||||||
</button>
|
</Button>
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@ -18,7 +18,6 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@/components/ui/select';
|
} from '@/components/ui/select';
|
||||||
import { usePermissions } from '@/hooks/use-permissions';
|
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import {
|
import {
|
||||||
destroy,
|
destroy,
|
||||||
@ -59,10 +58,6 @@ export default function ScheduleIndex({
|
|||||||
const [createOpen, setCreateOpen] = useState(false);
|
const [createOpen, setCreateOpen] = useState(false);
|
||||||
const [editing, setEditing] = useState<Schedule | null>(null);
|
const [editing, setEditing] = useState<Schedule | null>(null);
|
||||||
const [deleting, setDeleting] = useState<Schedule | null>(null);
|
const [deleting, setDeleting] = useState<Schedule | null>(null);
|
||||||
const { hasPermission } = usePermissions();
|
|
||||||
const canCreate = hasPermission('create-schedules');
|
|
||||||
const canUpdate = hasPermission('update-schedules');
|
|
||||||
const canDelete = hasPermission('delete-schedules');
|
|
||||||
|
|
||||||
function handleDelete() {
|
function handleDelete() {
|
||||||
if (!deleting) {
|
if (!deleting) {
|
||||||
@ -94,17 +89,15 @@ export default function ScheduleIndex({
|
|||||||
<PageHeader
|
<PageHeader
|
||||||
title="Jadwal"
|
title="Jadwal"
|
||||||
actions={
|
actions={
|
||||||
canCreate && (
|
<Button asChild>
|
||||||
<Button asChild>
|
<button
|
||||||
<button
|
type="button"
|
||||||
type="button"
|
onClick={() => setCreateOpen(true)}
|
||||||
onClick={() => setCreateOpen(true)}
|
>
|
||||||
>
|
<Plus className="h-4 w-4" />
|
||||||
<Plus className="h-4 w-4" />
|
Tambah
|
||||||
Tambah
|
</button>
|
||||||
</button>
|
</Button>
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -184,7 +177,6 @@ export default function ScheduleIndex({
|
|||||||
icon: (
|
icon: (
|
||||||
<Pencil className="h-3.5 w-3.5" />
|
<Pencil className="h-3.5 w-3.5" />
|
||||||
),
|
),
|
||||||
show: canUpdate,
|
|
||||||
onClick:
|
onClick:
|
||||||
() =>
|
() =>
|
||||||
setEditing(
|
setEditing(
|
||||||
@ -196,7 +188,6 @@ export default function ScheduleIndex({
|
|||||||
icon: (
|
icon: (
|
||||||
<Trash2 className="h-3.5 w-3.5 text-destructive" />
|
<Trash2 className="h-3.5 w-3.5 text-destructive" />
|
||||||
),
|
),
|
||||||
show: canDelete,
|
|
||||||
onClick:
|
onClick:
|
||||||
() =>
|
() =>
|
||||||
setDeleting(
|
setDeleting(
|
||||||
|
|||||||
@ -1,106 +0,0 @@
|
|||||||
import type { ColumnDef } from '@tanstack/react-table';
|
|
||||||
import { format } from 'date-fns';
|
|
||||||
import { Eye } from 'lucide-react';
|
|
||||||
import { RowActions } from '@/components/row-actions';
|
|
||||||
import { Badge } from '@/components/ui/badge';
|
|
||||||
import type { LogEntry, LogLevel } from '@/types/log-entry';
|
|
||||||
|
|
||||||
function levelVariant(
|
|
||||||
level: LogLevel,
|
|
||||||
): 'default' | 'secondary' | 'destructive' | 'outline' {
|
|
||||||
if (['EMERGENCY', 'ALERT', 'CRITICAL', 'ERROR'].includes(level)) {
|
|
||||||
return 'destructive';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (['WARNING', 'NOTICE'].includes(level)) {
|
|
||||||
return 'secondary';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (level === 'INFO') {
|
|
||||||
return 'default';
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'outline';
|
|
||||||
}
|
|
||||||
|
|
||||||
function firstLine(message: string): string {
|
|
||||||
return message.split('\n')[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
type CreateColumnsParams = {
|
|
||||||
handleViewDetail: (entry: LogEntry) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function createLogColumns(
|
|
||||||
params: CreateColumnsParams,
|
|
||||||
): ColumnDef<LogEntry>[] {
|
|
||||||
const { handleViewDetail } = params;
|
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
accessorKey: 'timestamp',
|
|
||||||
header: () => <span>Waktu</span>,
|
|
||||||
meta: {
|
|
||||||
className: 'w-[180px]',
|
|
||||||
headerClassName: 'w-[180px]',
|
|
||||||
},
|
|
||||||
cell: ({ row }) => {
|
|
||||||
const timestamp = row.getValue('timestamp') as string;
|
|
||||||
const date = new Date(timestamp.replace(' ', 'T'));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<span className="whitespace-nowrap">
|
|
||||||
{Number.isNaN(date.getTime())
|
|
||||||
? timestamp
|
|
||||||
: format(date, 'd MMM yyyy, HH:mm:ss')}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'level',
|
|
||||||
header: () => <span className="block text-center">Level</span>,
|
|
||||||
meta: {
|
|
||||||
className: 'w-[110px] text-center',
|
|
||||||
headerClassName: 'w-[110px] text-center',
|
|
||||||
},
|
|
||||||
cell: ({ row }) => {
|
|
||||||
const level = row.getValue('level') as LogLevel;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex justify-center">
|
|
||||||
<Badge variant={levelVariant(level)}>{level}</Badge>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: 'message',
|
|
||||||
header: () => <span>Pesan</span>,
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<p className="line-clamp-2 max-w-2xl font-mono text-xs break-all">
|
|
||||||
{firstLine(row.original.message)}
|
|
||||||
</p>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'actions',
|
|
||||||
header: () => <span className="block text-center">Aksi</span>,
|
|
||||||
meta: {
|
|
||||||
className: 'w-[80px] text-center',
|
|
||||||
headerClassName: 'w-[80px] text-center',
|
|
||||||
},
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<RowActions
|
|
||||||
actions={[
|
|
||||||
{
|
|
||||||
label: 'Lihat Detail',
|
|
||||||
icon: <Eye className="h-4 w-4" />,
|
|
||||||
onClick: () => handleViewDetail(row.original),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@ -1,219 +0,0 @@
|
|||||||
import { Head, router } from '@inertiajs/react';
|
|
||||||
import { format } from 'date-fns';
|
|
||||||
import { Download } from 'lucide-react';
|
|
||||||
import { useState } from 'react';
|
|
||||||
import type { PaginationState } from '@/components/data-table';
|
|
||||||
import { DataTable } from '@/components/data-table';
|
|
||||||
import type { FilterField } from '@/components/filter-dialog';
|
|
||||||
import { FilterDialog } from '@/components/filter-dialog';
|
|
||||||
import { PageHeader } from '@/components/page-header';
|
|
||||||
import { Badge } from '@/components/ui/badge';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
} from '@/components/ui/dialog';
|
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from '@/components/ui/select';
|
|
||||||
import { useServerTable } from '@/hooks/use-server-table';
|
|
||||||
import { index as logsIndex, download } from '@/routes/admin/developer/logs';
|
|
||||||
import type { LogEntry, LogFile } from '@/types/log-entry';
|
|
||||||
import { LogLevels } from '@/types/log-entry';
|
|
||||||
import { createLogColumns } from './columns';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
entries: {
|
|
||||||
data: LogEntry[];
|
|
||||||
current_page: number;
|
|
||||||
last_page: number;
|
|
||||||
per_page: number;
|
|
||||||
total: number;
|
|
||||||
} | null;
|
|
||||||
files: LogFile[];
|
|
||||||
selectedFile: string | null;
|
|
||||||
filters: {
|
|
||||||
file?: string;
|
|
||||||
level?: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
function formatFileSize(bytes: number): string {
|
|
||||||
if (bytes < 1024) {
|
|
||||||
return `${bytes} B`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bytes < 1024 * 1024) {
|
|
||||||
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function LogIndex({
|
|
||||||
entries,
|
|
||||||
files,
|
|
||||||
selectedFile,
|
|
||||||
filters,
|
|
||||||
}: Props) {
|
|
||||||
const [detail, setDetail] = useState<LogEntry | null>(null);
|
|
||||||
|
|
||||||
const filterFields: FilterField[] = [
|
|
||||||
{
|
|
||||||
key: 'level',
|
|
||||||
label: 'Level',
|
|
||||||
options: LogLevels.map((level) => ({
|
|
||||||
value: level,
|
|
||||||
label: level,
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const pagination: PaginationState = {
|
|
||||||
current_page: entries?.current_page ?? 1,
|
|
||||||
last_page: entries?.last_page ?? 1,
|
|
||||||
per_page: entries?.per_page ?? 25,
|
|
||||||
total: entries?.total ?? 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
const {
|
|
||||||
search,
|
|
||||||
handlePageChange,
|
|
||||||
handlePerPageChange,
|
|
||||||
handleSearchChange,
|
|
||||||
applyFilters,
|
|
||||||
} = useServerTable({
|
|
||||||
route: () => logsIndex.url(),
|
|
||||||
pagination,
|
|
||||||
filters,
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleFileChange(file: string) {
|
|
||||||
router.get(
|
|
||||||
logsIndex.url(),
|
|
||||||
{ file, level: filters.level },
|
|
||||||
{ preserveState: true, replace: true },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const columns = createLogColumns({
|
|
||||||
handleViewDetail: (entry) => setDetail(entry),
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Head title="Logs" />
|
|
||||||
|
|
||||||
<div className="flex h-full flex-1 flex-col gap-6 overflow-x-auto p-4 md:p-6">
|
|
||||||
<PageHeader
|
|
||||||
title="Logs"
|
|
||||||
actions={
|
|
||||||
selectedFile && (
|
|
||||||
<Button variant="outline" asChild>
|
|
||||||
<a
|
|
||||||
href={download.url({
|
|
||||||
query: { file: selectedFile },
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<Download className="h-4 w-4" />
|
|
||||||
Unduh
|
|
||||||
</a>
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="flex flex-wrap items-center gap-3">
|
|
||||||
<Select
|
|
||||||
value={selectedFile ?? undefined}
|
|
||||||
onValueChange={handleFileChange}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="w-full sm:w-80">
|
|
||||||
<SelectValue placeholder="Pilih file log" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{files.map((file) => (
|
|
||||||
<SelectItem key={file.name} value={file.name}>
|
|
||||||
{file.name} ·{' '}
|
|
||||||
{formatFileSize(file.size)}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{entries ? (
|
|
||||||
<DataTable
|
|
||||||
columns={columns}
|
|
||||||
data={entries.data}
|
|
||||||
pagination={pagination}
|
|
||||||
onPageChange={handlePageChange}
|
|
||||||
onPerPageChange={handlePerPageChange}
|
|
||||||
onSearchChange={handleSearchChange}
|
|
||||||
searchValue={search}
|
|
||||||
searchKey="message"
|
|
||||||
searchPlaceholder="Cari pesan log..."
|
|
||||||
emptyText="Tidak ada entri log yang cocok."
|
|
||||||
toolbar={
|
|
||||||
<FilterDialog
|
|
||||||
fields={filterFields}
|
|
||||||
activeFilters={filters}
|
|
||||||
onApply={applyFilters}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
Belum ada file log yang tersedia.
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Dialog
|
|
||||||
open={detail !== null}
|
|
||||||
onOpenChange={(open) => {
|
|
||||||
if (!open) {
|
|
||||||
setDetail(null);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<DialogContent className="flex max-h-[85vh] flex-col overflow-hidden sm:max-w-3xl">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle className="flex items-center gap-2">
|
|
||||||
{detail?.level && (
|
|
||||||
<Badge variant="outline">
|
|
||||||
{detail.level}
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
<span>Detail Log</span>
|
|
||||||
</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
{detail &&
|
|
||||||
!Number.isNaN(
|
|
||||||
new Date(
|
|
||||||
detail.timestamp.replace(' ', 'T'),
|
|
||||||
).getTime(),
|
|
||||||
) &&
|
|
||||||
format(
|
|
||||||
new Date(
|
|
||||||
detail.timestamp.replace(' ', 'T'),
|
|
||||||
),
|
|
||||||
'd MMM yyyy, HH:mm:ss',
|
|
||||||
)}
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<pre className="overflow-y-auto rounded-md bg-muted p-3 font-mono text-xs break-all whitespace-pre-wrap">
|
|
||||||
{detail?.raw}
|
|
||||||
</pre>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,186 +0,0 @@
|
|||||||
import { Head, router } from '@inertiajs/react';
|
|
||||||
import { Save } from 'lucide-react';
|
|
||||||
import { useState } from 'react';
|
|
||||||
import { PageHeader } from '@/components/page-header';
|
|
||||||
import { Badge } from '@/components/ui/badge';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
||||||
import { Checkbox } from '@/components/ui/checkbox';
|
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from '@/components/ui/select';
|
|
||||||
import { update } from '@/routes/admin/developer/roles';
|
|
||||||
import type {
|
|
||||||
PermissionGroups,
|
|
||||||
RoleWithPermissions,
|
|
||||||
} from '@/types/role-permission';
|
|
||||||
import { formatRoleLabel } from '@/types/role-permission';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
roles: RoleWithPermissions[];
|
|
||||||
permissionGroups: PermissionGroups;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function RolePermissionIndex({
|
|
||||||
roles,
|
|
||||||
permissionGroups,
|
|
||||||
}: Props) {
|
|
||||||
const [selectedRoleId, setSelectedRoleId] = useState<number | undefined>(
|
|
||||||
roles[0]?.id,
|
|
||||||
);
|
|
||||||
const selectedRole = roles.find((role) => role.id === selectedRoleId);
|
|
||||||
const [selected, setSelected] = useState<string[]>(
|
|
||||||
selectedRole?.permissions ?? [],
|
|
||||||
);
|
|
||||||
const [processing, setProcessing] = useState(false);
|
|
||||||
|
|
||||||
function handleSelectRole(id: string) {
|
|
||||||
const role = roles.find((r) => r.id === Number(id));
|
|
||||||
setSelectedRoleId(role?.id);
|
|
||||||
setSelected(role?.permissions ?? []);
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggle(name: string, checked: boolean) {
|
|
||||||
setSelected((prev) =>
|
|
||||||
checked ? [...prev, name] : prev.filter((p) => p !== name),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleGroup(names: string[], checked: boolean) {
|
|
||||||
setSelected((prev) => {
|
|
||||||
const withoutGroup = prev.filter((p) => !names.includes(p));
|
|
||||||
|
|
||||||
return checked ? [...withoutGroup, ...names] : withoutGroup;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleSave() {
|
|
||||||
if (!selectedRole) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setProcessing(true);
|
|
||||||
router.put(
|
|
||||||
update.url(selectedRole.id),
|
|
||||||
{ permissions: selected },
|
|
||||||
{
|
|
||||||
preserveScroll: true,
|
|
||||||
onFinish: () => setProcessing(false),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Head title="Role & Permission" />
|
|
||||||
|
|
||||||
<div className="flex h-full flex-1 flex-col gap-6 overflow-x-auto p-4 md:p-6">
|
|
||||||
<PageHeader
|
|
||||||
title="Role & Permission"
|
|
||||||
actions={
|
|
||||||
selectedRole && (
|
|
||||||
<Button onClick={handleSave} disabled={processing}>
|
|
||||||
<Save className="h-4 w-4" />
|
|
||||||
Simpan
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<Select
|
|
||||||
value={
|
|
||||||
selectedRoleId ? String(selectedRoleId) : undefined
|
|
||||||
}
|
|
||||||
onValueChange={handleSelectRole}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="w-full sm:w-72">
|
|
||||||
<SelectValue placeholder="Pilih role" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{roles.map((role) => (
|
|
||||||
<SelectItem
|
|
||||||
key={role.id}
|
|
||||||
value={String(role.id)}
|
|
||||||
>
|
|
||||||
{formatRoleLabel(role.name)}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
{selectedRole && (
|
|
||||||
<Badge variant="secondary">
|
|
||||||
{selected.length} permission
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{selectedRole && (
|
|
||||||
<div className="grid gap-4">
|
|
||||||
{Object.entries(permissionGroups).map(
|
|
||||||
([group, names]) => {
|
|
||||||
const allChecked = names.every((name) =>
|
|
||||||
selected.includes(name),
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card key={group}>
|
|
||||||
<CardHeader className="flex flex-row items-center justify-between gap-4">
|
|
||||||
<CardTitle className="text-base">
|
|
||||||
{group}
|
|
||||||
</CardTitle>
|
|
||||||
<label className="flex items-center gap-2 text-sm font-normal text-muted-foreground">
|
|
||||||
<Checkbox
|
|
||||||
checked={allChecked}
|
|
||||||
onCheckedChange={(
|
|
||||||
checked,
|
|
||||||
) =>
|
|
||||||
toggleGroup(
|
|
||||||
names,
|
|
||||||
checked === true,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
Pilih Semua
|
|
||||||
</label>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="grid grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-3">
|
|
||||||
{names.map((name) => (
|
|
||||||
<label
|
|
||||||
key={name}
|
|
||||||
className="flex items-center gap-2 rounded-md border p-2 text-sm hover:bg-muted"
|
|
||||||
>
|
|
||||||
<Checkbox
|
|
||||||
checked={selected.includes(
|
|
||||||
name,
|
|
||||||
)}
|
|
||||||
onCheckedChange={(
|
|
||||||
checked,
|
|
||||||
) =>
|
|
||||||
toggle(
|
|
||||||
name,
|
|
||||||
checked ===
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<span className="font-mono text-xs">
|
|
||||||
{name}
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
))}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -14,21 +14,12 @@ export type { TuitionInvoice } from '@/types/tuition-invoice';
|
|||||||
type CreateColumnsParams = {
|
type CreateColumnsParams = {
|
||||||
handleEdit: (invoice: TuitionInvoice) => void;
|
handleEdit: (invoice: TuitionInvoice) => void;
|
||||||
handleDeleteClick: (invoice: TuitionInvoice) => void;
|
handleDeleteClick: (invoice: TuitionInvoice) => void;
|
||||||
canUpdate: boolean;
|
|
||||||
canDelete: boolean;
|
|
||||||
canViewPayments: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createTuitionInvoiceColumns(
|
export function createTuitionInvoiceColumns(
|
||||||
params: CreateColumnsParams,
|
params: CreateColumnsParams,
|
||||||
): ColumnDef<TuitionInvoice>[] {
|
): ColumnDef<TuitionInvoice>[] {
|
||||||
const {
|
const { handleEdit, handleDeleteClick } = params;
|
||||||
handleEdit,
|
|
||||||
handleDeleteClick,
|
|
||||||
canUpdate,
|
|
||||||
canDelete,
|
|
||||||
canViewPayments,
|
|
||||||
} = params;
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@ -142,13 +133,11 @@ export function createTuitionInvoiceColumns(
|
|||||||
{
|
{
|
||||||
label: 'Pembayaran',
|
label: 'Pembayaran',
|
||||||
icon: <CreditCard className="h-4 w-4" />,
|
icon: <CreditCard className="h-4 w-4" />,
|
||||||
show: canViewPayments,
|
|
||||||
href: paymentsIndex.url(invoice.id),
|
href: paymentsIndex.url(invoice.id),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Edit',
|
label: 'Edit',
|
||||||
icon: <Pencil className="h-4 w-4" />,
|
icon: <Pencil className="h-4 w-4" />,
|
||||||
show: canUpdate,
|
|
||||||
onClick: () => handleEdit(invoice),
|
onClick: () => handleEdit(invoice),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -156,7 +145,6 @@ export function createTuitionInvoiceColumns(
|
|||||||
icon: (
|
icon: (
|
||||||
<Trash2 className="h-4 w-4 text-destructive" />
|
<Trash2 className="h-4 w-4 text-destructive" />
|
||||||
),
|
),
|
||||||
show: canDelete,
|
|
||||||
onClick: () => handleDeleteClick(invoice),
|
onClick: () => handleDeleteClick(invoice),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
|||||||
@ -47,7 +47,6 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@/components/ui/select';
|
} from '@/components/ui/select';
|
||||||
import { usePermissions } from '@/hooks/use-permissions';
|
|
||||||
import { useServerTable } from '@/hooks/use-server-table';
|
import { useServerTable } from '@/hooks/use-server-table';
|
||||||
import { formatRupiah } from '@/lib/currency';
|
import { formatRupiah } from '@/lib/currency';
|
||||||
import {
|
import {
|
||||||
@ -116,11 +115,6 @@ export default function TuitionInvoiceIndex({
|
|||||||
const [createOpen, setCreateOpen] = useState(false);
|
const [createOpen, setCreateOpen] = useState(false);
|
||||||
const [editing, setEditing] = useState<TuitionInvoice | null>(null);
|
const [editing, setEditing] = useState<TuitionInvoice | null>(null);
|
||||||
const [deleting, setDeleting] = useState<TuitionInvoice | null>(null);
|
const [deleting, setDeleting] = useState<TuitionInvoice | null>(null);
|
||||||
const { hasPermission } = usePermissions();
|
|
||||||
const canCreate = hasPermission('create-tuition-invoices');
|
|
||||||
const canUpdate = hasPermission('update-tuition-invoices');
|
|
||||||
const canDelete = hasPermission('delete-tuition-invoices');
|
|
||||||
const canViewPayments = hasPermission('view-tuition-payments');
|
|
||||||
|
|
||||||
const filterFields: FilterField[] = [
|
const filterFields: FilterField[] = [
|
||||||
{
|
{
|
||||||
@ -181,9 +175,6 @@ export default function TuitionInvoiceIndex({
|
|||||||
const columns = createTuitionInvoiceColumns({
|
const columns = createTuitionInvoiceColumns({
|
||||||
handleEdit: (invoice) => setEditing(invoice),
|
handleEdit: (invoice) => setEditing(invoice),
|
||||||
handleDeleteClick: (invoice) => setDeleting(invoice),
|
handleDeleteClick: (invoice) => setDeleting(invoice),
|
||||||
canUpdate,
|
|
||||||
canDelete,
|
|
||||||
canViewPayments,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -201,17 +192,15 @@ export default function TuitionInvoiceIndex({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
actions={
|
actions={
|
||||||
canCreate && (
|
<Button asChild>
|
||||||
<Button asChild>
|
<button
|
||||||
<button
|
type="button"
|
||||||
type="button"
|
onClick={() => setCreateOpen(true)}
|
||||||
onClick={() => setCreateOpen(true)}
|
>
|
||||||
>
|
<Plus className="h-4 w-4" />
|
||||||
<Plus className="h-4 w-4" />
|
Tambah
|
||||||
Tambah
|
</button>
|
||||||
</button>
|
</Button>
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@ -18,7 +18,6 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
import { usePermissions } from '@/hooks/use-permissions';
|
|
||||||
import { formatRupiah } from '@/lib/currency';
|
import { formatRupiah } from '@/lib/currency';
|
||||||
import { index as tuitionInvoiceIndex } from '@/routes/admin/finances/tuition-invoices';
|
import { index as tuitionInvoiceIndex } from '@/routes/admin/finances/tuition-invoices';
|
||||||
import {
|
import {
|
||||||
@ -44,10 +43,6 @@ export default function TuitionPaymentIndex({ invoice, payments }: Props) {
|
|||||||
const [createOpen, setCreateOpen] = useState(false);
|
const [createOpen, setCreateOpen] = useState(false);
|
||||||
const [editing, setEditing] = useState<TuitionPayment | null>(null);
|
const [editing, setEditing] = useState<TuitionPayment | null>(null);
|
||||||
const [deleting, setDeleting] = useState<TuitionPayment | null>(null);
|
const [deleting, setDeleting] = useState<TuitionPayment | null>(null);
|
||||||
const { hasPermission } = usePermissions();
|
|
||||||
const canCreate = hasPermission('create-tuition-payments');
|
|
||||||
const canUpdate = hasPermission('update-tuition-payments');
|
|
||||||
const canDelete = hasPermission('delete-tuition-payments');
|
|
||||||
|
|
||||||
function handleDelete() {
|
function handleDelete() {
|
||||||
if (!deleting) {
|
if (!deleting) {
|
||||||
@ -161,7 +156,6 @@ export default function TuitionPaymentIndex({ invoice, payments }: Props) {
|
|||||||
{
|
{
|
||||||
label: 'Edit',
|
label: 'Edit',
|
||||||
icon: <Pencil className="h-4 w-4" />,
|
icon: <Pencil className="h-4 w-4" />,
|
||||||
show: canUpdate,
|
|
||||||
onClick: () => setEditing(row.original),
|
onClick: () => setEditing(row.original),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -169,7 +163,6 @@ export default function TuitionPaymentIndex({ invoice, payments }: Props) {
|
|||||||
icon: (
|
icon: (
|
||||||
<Trash2 className="h-4 w-4 text-destructive" />
|
<Trash2 className="h-4 w-4 text-destructive" />
|
||||||
),
|
),
|
||||||
show: canDelete,
|
|
||||||
onClick: () => setDeleting(row.original),
|
onClick: () => setDeleting(row.original),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
@ -238,7 +231,7 @@ export default function TuitionPaymentIndex({ invoice, payments }: Props) {
|
|||||||
<h2 className="text-lg font-semibold">
|
<h2 className="text-lg font-semibold">
|
||||||
Riwayat Pembayaran
|
Riwayat Pembayaran
|
||||||
</h2>
|
</h2>
|
||||||
{!isFullyPaid && canCreate && (
|
{!isFullyPaid && (
|
||||||
<Button onClick={() => setCreateOpen(true)}>
|
<Button onClick={() => setCreateOpen(true)}>
|
||||||
<Plus className="h-4 w-4" />
|
<Plus className="h-4 w-4" />
|
||||||
Tambah Pembayaran
|
Tambah Pembayaran
|
||||||
|
|||||||
@ -10,14 +10,12 @@ export type { Announcement } from '@/types/announcement';
|
|||||||
type CreateColumnsParams = {
|
type CreateColumnsParams = {
|
||||||
handleEdit: (announcement: Announcement) => void;
|
handleEdit: (announcement: Announcement) => void;
|
||||||
handleDeleteClick: (announcement: Announcement) => void;
|
handleDeleteClick: (announcement: Announcement) => void;
|
||||||
canUpdate: boolean;
|
|
||||||
canDelete: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createAnnouncementColumns(
|
export function createAnnouncementColumns(
|
||||||
params: CreateColumnsParams,
|
params: CreateColumnsParams,
|
||||||
): ColumnDef<Announcement>[] {
|
): ColumnDef<Announcement>[] {
|
||||||
const { handleEdit, handleDeleteClick, canUpdate, canDelete } = params;
|
const { handleEdit, handleDeleteClick } = params;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@ -84,7 +82,6 @@ export function createAnnouncementColumns(
|
|||||||
{
|
{
|
||||||
label: 'Edit',
|
label: 'Edit',
|
||||||
icon: <Pencil className="h-4 w-4" />,
|
icon: <Pencil className="h-4 w-4" />,
|
||||||
show: canUpdate,
|
|
||||||
onClick: () => handleEdit(row.original),
|
onClick: () => handleEdit(row.original),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -92,7 +89,6 @@ export function createAnnouncementColumns(
|
|||||||
icon: (
|
icon: (
|
||||||
<Trash2 className="h-4 w-4 text-destructive" />
|
<Trash2 className="h-4 w-4 text-destructive" />
|
||||||
),
|
),
|
||||||
show: canDelete,
|
|
||||||
onClick: () => handleDeleteClick(row.original),
|
onClick: () => handleDeleteClick(row.original),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
|||||||
@ -20,7 +20,6 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@/components/ui/select';
|
} from '@/components/ui/select';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
import { usePermissions } from '@/hooks/use-permissions';
|
|
||||||
import { useServerTable } from '@/hooks/use-server-table';
|
import { useServerTable } from '@/hooks/use-server-table';
|
||||||
import {
|
import {
|
||||||
index as announcementIndex,
|
index as announcementIndex,
|
||||||
@ -59,10 +58,6 @@ export default function AnnouncementIndex({
|
|||||||
const [createOpen, setCreateOpen] = useState(false);
|
const [createOpen, setCreateOpen] = useState(false);
|
||||||
const [editing, setEditing] = useState<Announcement | null>(null);
|
const [editing, setEditing] = useState<Announcement | null>(null);
|
||||||
const [deleting, setDeleting] = useState<Announcement | null>(null);
|
const [deleting, setDeleting] = useState<Announcement | null>(null);
|
||||||
const { hasPermission } = usePermissions();
|
|
||||||
const canCreate = hasPermission('create-announcements');
|
|
||||||
const canUpdate = hasPermission('update-announcements');
|
|
||||||
const canDelete = hasPermission('delete-announcements');
|
|
||||||
|
|
||||||
const filterFields: FilterField[] = [
|
const filterFields: FilterField[] = [
|
||||||
{
|
{
|
||||||
@ -107,8 +102,6 @@ export default function AnnouncementIndex({
|
|||||||
const columns = createAnnouncementColumns({
|
const columns = createAnnouncementColumns({
|
||||||
handleEdit: (announcement) => setEditing(announcement),
|
handleEdit: (announcement) => setEditing(announcement),
|
||||||
handleDeleteClick: (announcement) => setDeleting(announcement),
|
handleDeleteClick: (announcement) => setDeleting(announcement),
|
||||||
canUpdate,
|
|
||||||
canDelete,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -126,17 +119,15 @@ export default function AnnouncementIndex({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
actions={
|
actions={
|
||||||
canCreate && (
|
<Button asChild>
|
||||||
<Button asChild>
|
<button
|
||||||
<button
|
type="button"
|
||||||
type="button"
|
onClick={() => setCreateOpen(true)}
|
||||||
onClick={() => setCreateOpen(true)}
|
>
|
||||||
>
|
<Plus className="h-4 w-4" />
|
||||||
<Plus className="h-4 w-4" />
|
Tambah
|
||||||
Tambah
|
</button>
|
||||||
</button>
|
</Button>
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@ -12,21 +12,12 @@ export type { CourseClass } from '@/types/course-class';
|
|||||||
type CreateColumnsParams = {
|
type CreateColumnsParams = {
|
||||||
handleEdit: (courseClass: CourseClass) => void;
|
handleEdit: (courseClass: CourseClass) => void;
|
||||||
handleDeleteClick: (courseClass: CourseClass) => void;
|
handleDeleteClick: (courseClass: CourseClass) => void;
|
||||||
canUpdate: boolean;
|
|
||||||
canDelete: boolean;
|
|
||||||
canViewEnrollments: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createCourseClassColumns(
|
export function createCourseClassColumns(
|
||||||
params: CreateColumnsParams,
|
params: CreateColumnsParams,
|
||||||
): ColumnDef<CourseClass>[] {
|
): ColumnDef<CourseClass>[] {
|
||||||
const {
|
const { handleEdit, handleDeleteClick } = params;
|
||||||
handleEdit,
|
|
||||||
handleDeleteClick,
|
|
||||||
canUpdate,
|
|
||||||
canDelete,
|
|
||||||
canViewEnrollments,
|
|
||||||
} = params;
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@ -106,13 +97,11 @@ export function createCourseClassColumns(
|
|||||||
{
|
{
|
||||||
label: 'Peserta',
|
label: 'Peserta',
|
||||||
icon: <Users className="h-4 w-4" />,
|
icon: <Users className="h-4 w-4" />,
|
||||||
show: canViewEnrollments,
|
|
||||||
href: enrollmentsIndex.url(courseClass.id),
|
href: enrollmentsIndex.url(courseClass.id),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Edit',
|
label: 'Edit',
|
||||||
icon: <Pencil className="h-4 w-4" />,
|
icon: <Pencil className="h-4 w-4" />,
|
||||||
show: canUpdate,
|
|
||||||
onClick: () => handleEdit(courseClass),
|
onClick: () => handleEdit(courseClass),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -120,7 +109,6 @@ export function createCourseClassColumns(
|
|||||||
icon: (
|
icon: (
|
||||||
<Trash2 className="h-4 w-4 text-destructive" />
|
<Trash2 className="h-4 w-4 text-destructive" />
|
||||||
),
|
),
|
||||||
show: canDelete,
|
|
||||||
onClick: () => handleDeleteClick(courseClass),
|
onClick: () => handleDeleteClick(courseClass),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
|||||||
@ -13,7 +13,6 @@ 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 { Checkbox } from '@/components/ui/checkbox';
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { usePermissions } from '@/hooks/use-permissions';
|
|
||||||
import { index as courseClassIndex } from '@/routes/admin/manage/course-classes';
|
import { index as courseClassIndex } from '@/routes/admin/manage/course-classes';
|
||||||
import {
|
import {
|
||||||
destroy,
|
destroy,
|
||||||
@ -40,9 +39,6 @@ export default function ClassEnrollmentIndex({
|
|||||||
}: Props) {
|
}: Props) {
|
||||||
const [createOpen, setCreateOpen] = useState(false);
|
const [createOpen, setCreateOpen] = useState(false);
|
||||||
const [deleting, setDeleting] = useState<ClassEnrollment | null>(null);
|
const [deleting, setDeleting] = useState<ClassEnrollment | null>(null);
|
||||||
const { hasPermission } = usePermissions();
|
|
||||||
const canCreate = hasPermission('create-course-class-enrollments');
|
|
||||||
const canDelete = hasPermission('delete-course-class-enrollments');
|
|
||||||
|
|
||||||
function handleDelete() {
|
function handleDelete() {
|
||||||
if (!deleting) {
|
if (!deleting) {
|
||||||
@ -97,7 +93,6 @@ export default function ClassEnrollmentIndex({
|
|||||||
icon: (
|
icon: (
|
||||||
<Trash2 className="h-4 w-4 text-destructive" />
|
<Trash2 className="h-4 w-4 text-destructive" />
|
||||||
),
|
),
|
||||||
show: canDelete,
|
|
||||||
onClick: () => setDeleting(row.original),
|
onClick: () => setDeleting(row.original),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
@ -157,12 +152,10 @@ export default function ClassEnrollmentIndex({
|
|||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h2 className="text-lg font-semibold">Daftar Mahasiswa</h2>
|
<h2 className="text-lg font-semibold">Daftar Mahasiswa</h2>
|
||||||
{canCreate && (
|
<Button onClick={() => setCreateOpen(true)}>
|
||||||
<Button onClick={() => setCreateOpen(true)}>
|
<UserPlus className="h-4 w-4" />
|
||||||
<UserPlus className="h-4 w-4" />
|
Tambah Mahasiswa
|
||||||
Tambah Mahasiswa
|
</Button>
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DataTable columns={columns} data={enrollments} />
|
<DataTable columns={columns} data={enrollments} />
|
||||||
|
|||||||
@ -26,7 +26,6 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@/components/ui/select';
|
} from '@/components/ui/select';
|
||||||
import { usePermissions } from '@/hooks/use-permissions';
|
|
||||||
import { useServerTable } from '@/hooks/use-server-table';
|
import { useServerTable } from '@/hooks/use-server-table';
|
||||||
import {
|
import {
|
||||||
index as courseClassIndex,
|
index as courseClassIndex,
|
||||||
@ -77,11 +76,6 @@ export default function CourseClassIndex({
|
|||||||
const [createOpen, setCreateOpen] = useState(false);
|
const [createOpen, setCreateOpen] = useState(false);
|
||||||
const [editing, setEditing] = useState<CourseClass | null>(null);
|
const [editing, setEditing] = useState<CourseClass | null>(null);
|
||||||
const [deleting, setDeleting] = useState<CourseClass | null>(null);
|
const [deleting, setDeleting] = useState<CourseClass | null>(null);
|
||||||
const { hasPermission } = usePermissions();
|
|
||||||
const canCreate = hasPermission('create-course-classes');
|
|
||||||
const canUpdate = hasPermission('update-course-classes');
|
|
||||||
const canDelete = hasPermission('delete-course-classes');
|
|
||||||
const canViewEnrollments = hasPermission('view-course-class-enrollments');
|
|
||||||
|
|
||||||
const filterFields: FilterField[] = [
|
const filterFields: FilterField[] = [
|
||||||
{
|
{
|
||||||
@ -134,9 +128,6 @@ export default function CourseClassIndex({
|
|||||||
const columns = createCourseClassColumns({
|
const columns = createCourseClassColumns({
|
||||||
handleEdit: (courseClass) => setEditing(courseClass),
|
handleEdit: (courseClass) => setEditing(courseClass),
|
||||||
handleDeleteClick: (courseClass) => setDeleting(courseClass),
|
handleDeleteClick: (courseClass) => setDeleting(courseClass),
|
||||||
canUpdate,
|
|
||||||
canDelete,
|
|
||||||
canViewEnrollments,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -169,17 +160,15 @@ export default function CourseClassIndex({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
actions={
|
actions={
|
||||||
canCreate && (
|
<Button asChild>
|
||||||
<Button asChild>
|
<button
|
||||||
<button
|
type="button"
|
||||||
type="button"
|
onClick={() => setCreateOpen(true)}
|
||||||
onClick={() => setCreateOpen(true)}
|
>
|
||||||
>
|
<Plus className="h-4 w-4" />
|
||||||
<Plus className="h-4 w-4" />
|
Tambah
|
||||||
Tambah
|
</button>
|
||||||
</button>
|
</Button>
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@ -11,14 +11,12 @@ export type { CourseRegistrationSubmission } from '@/types/course-registration';
|
|||||||
type CreateColumnsParams = {
|
type CreateColumnsParams = {
|
||||||
handleApprove: (submission: CourseRegistrationSubmission) => void;
|
handleApprove: (submission: CourseRegistrationSubmission) => void;
|
||||||
handleRejectClick: (submission: CourseRegistrationSubmission) => void;
|
handleRejectClick: (submission: CourseRegistrationSubmission) => void;
|
||||||
canApprove: boolean;
|
|
||||||
canReject: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createCourseRegistrationColumns(
|
export function createCourseRegistrationColumns(
|
||||||
params: CreateColumnsParams,
|
params: CreateColumnsParams,
|
||||||
): ColumnDef<CourseRegistrationSubmission>[] {
|
): ColumnDef<CourseRegistrationSubmission>[] {
|
||||||
const { handleApprove, handleRejectClick, canApprove, canReject } = params;
|
const { handleApprove, handleRejectClick } = params;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@ -107,18 +105,14 @@ export function createCourseRegistrationColumns(
|
|||||||
label: 'Setujui',
|
label: 'Setujui',
|
||||||
icon: <Check className="h-4 w-4" />,
|
icon: <Check className="h-4 w-4" />,
|
||||||
iconClassName: 'text-primary',
|
iconClassName: 'text-primary',
|
||||||
show:
|
show: submission.status === 'submitted',
|
||||||
submission.status === 'submitted' &&
|
|
||||||
canApprove,
|
|
||||||
onClick: () => handleApprove(submission),
|
onClick: () => handleApprove(submission),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Tolak',
|
label: 'Tolak',
|
||||||
icon: <X className="h-4 w-4" />,
|
icon: <X className="h-4 w-4" />,
|
||||||
iconClassName: 'text-destructive',
|
iconClassName: 'text-destructive',
|
||||||
show:
|
show: submission.status === 'submitted',
|
||||||
submission.status === 'submitted' &&
|
|
||||||
canReject,
|
|
||||||
onClick: () => handleRejectClick(submission),
|
onClick: () => handleRejectClick(submission),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
|||||||
@ -27,7 +27,6 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@/components/ui/select';
|
} from '@/components/ui/select';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
import { usePermissions } from '@/hooks/use-permissions';
|
|
||||||
import { useServerTable } from '@/hooks/use-server-table';
|
import { useServerTable } from '@/hooks/use-server-table';
|
||||||
import {
|
import {
|
||||||
index as courseRegistrationIndex,
|
index as courseRegistrationIndex,
|
||||||
@ -93,10 +92,6 @@ export default function CourseRegistrationIndex({
|
|||||||
const [createOpen, setCreateOpen] = useState(false);
|
const [createOpen, setCreateOpen] = useState(false);
|
||||||
const [rejecting, setRejecting] =
|
const [rejecting, setRejecting] =
|
||||||
useState<CourseRegistrationSubmission | null>(null);
|
useState<CourseRegistrationSubmission | null>(null);
|
||||||
const { hasPermission } = usePermissions();
|
|
||||||
const canCreate = hasPermission('create-course-registrations');
|
|
||||||
const canApprove = hasPermission('approve-course-registrations');
|
|
||||||
const canReject = hasPermission('reject-course-registrations');
|
|
||||||
|
|
||||||
const filterFields: FilterField[] = [
|
const filterFields: FilterField[] = [
|
||||||
{
|
{
|
||||||
@ -143,8 +138,6 @@ export default function CourseRegistrationIndex({
|
|||||||
const columns = createCourseRegistrationColumns({
|
const columns = createCourseRegistrationColumns({
|
||||||
handleApprove,
|
handleApprove,
|
||||||
handleRejectClick: (submission) => setRejecting(submission),
|
handleRejectClick: (submission) => setRejecting(submission),
|
||||||
canApprove,
|
|
||||||
canReject,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -162,17 +155,15 @@ export default function CourseRegistrationIndex({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
actions={
|
actions={
|
||||||
canCreate && (
|
<Button asChild>
|
||||||
<Button asChild>
|
<button
|
||||||
<button
|
type="button"
|
||||||
type="button"
|
onClick={() => setCreateOpen(true)}
|
||||||
onClick={() => setCreateOpen(true)}
|
>
|
||||||
>
|
<Plus className="h-4 w-4" />
|
||||||
<Plus className="h-4 w-4" />
|
Tambah
|
||||||
Tambah
|
</button>
|
||||||
</button>
|
</Button>
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@ -12,22 +12,12 @@ type CreateColumnsParams = {
|
|||||||
handleEdit: (academicTerm: AcademicTerm) => void;
|
handleEdit: (academicTerm: AcademicTerm) => void;
|
||||||
handleDeleteClick: (academicTerm: AcademicTerm) => void;
|
handleDeleteClick: (academicTerm: AcademicTerm) => void;
|
||||||
handleStatusChange: (academicTerm: AcademicTerm, isActive: boolean) => void;
|
handleStatusChange: (academicTerm: AcademicTerm, isActive: boolean) => void;
|
||||||
canUpdate: boolean;
|
|
||||||
canDelete: boolean;
|
|
||||||
canUpdateStatus: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createAcademicTermColumns(
|
export function createAcademicTermColumns(
|
||||||
params: CreateColumnsParams,
|
params: CreateColumnsParams,
|
||||||
): ColumnDef<AcademicTerm>[] {
|
): ColumnDef<AcademicTerm>[] {
|
||||||
const {
|
const { handleEdit, handleDeleteClick, handleStatusChange } = params;
|
||||||
handleEdit,
|
|
||||||
handleDeleteClick,
|
|
||||||
handleStatusChange,
|
|
||||||
canUpdate,
|
|
||||||
canDelete,
|
|
||||||
canUpdateStatus,
|
|
||||||
} = params;
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@ -83,7 +73,6 @@ export function createAcademicTermColumns(
|
|||||||
onChange={(isActive) =>
|
onChange={(isActive) =>
|
||||||
handleStatusChange(academicTerm, isActive)
|
handleStatusChange(academicTerm, isActive)
|
||||||
}
|
}
|
||||||
disabled={!canUpdateStatus}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -105,7 +94,6 @@ export function createAcademicTermColumns(
|
|||||||
{
|
{
|
||||||
label: 'Edit',
|
label: 'Edit',
|
||||||
icon: <Pencil className="h-4 w-4" />,
|
icon: <Pencil className="h-4 w-4" />,
|
||||||
show: canUpdate,
|
|
||||||
onClick: () => handleEdit(academicTerm),
|
onClick: () => handleEdit(academicTerm),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -113,7 +101,6 @@ export function createAcademicTermColumns(
|
|||||||
icon: (
|
icon: (
|
||||||
<Trash2 className="h-4 w-4 text-destructive" />
|
<Trash2 className="h-4 w-4 text-destructive" />
|
||||||
),
|
),
|
||||||
show: canDelete,
|
|
||||||
onClick: () => handleDeleteClick(academicTerm),
|
onClick: () => handleDeleteClick(academicTerm),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
|||||||
@ -15,7 +15,6 @@ import { Checkbox } from '@/components/ui/checkbox';
|
|||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
||||||
import { usePermissions } from '@/hooks/use-permissions';
|
|
||||||
import { useServerTable } from '@/hooks/use-server-table';
|
import { useServerTable } from '@/hooks/use-server-table';
|
||||||
import {
|
import {
|
||||||
index as academicTermIndex,
|
index as academicTermIndex,
|
||||||
@ -70,11 +69,6 @@ export default function AcademicTermIndex({
|
|||||||
const [createOpen, setCreateOpen] = useState(false);
|
const [createOpen, setCreateOpen] = useState(false);
|
||||||
const [editing, setEditing] = useState<AcademicTerm | null>(null);
|
const [editing, setEditing] = useState<AcademicTerm | null>(null);
|
||||||
const [deleting, setDeleting] = useState<AcademicTerm | null>(null);
|
const [deleting, setDeleting] = useState<AcademicTerm | null>(null);
|
||||||
const { hasPermission } = usePermissions();
|
|
||||||
const canCreate = hasPermission('create-academic-terms');
|
|
||||||
const canUpdate = hasPermission('update-academic-terms');
|
|
||||||
const canDelete = hasPermission('delete-academic-terms');
|
|
||||||
const canUpdateStatus = hasPermission('update-academic-terms-status');
|
|
||||||
|
|
||||||
const pagination: PaginationState = {
|
const pagination: PaginationState = {
|
||||||
current_page: academicTerms.current_page,
|
current_page: academicTerms.current_page,
|
||||||
@ -117,9 +111,6 @@ export default function AcademicTermIndex({
|
|||||||
handleEdit: (academicTerm) => setEditing(academicTerm),
|
handleEdit: (academicTerm) => setEditing(academicTerm),
|
||||||
handleDeleteClick: (academicTerm) => setDeleting(academicTerm),
|
handleDeleteClick: (academicTerm) => setDeleting(academicTerm),
|
||||||
handleStatusChange,
|
handleStatusChange,
|
||||||
canUpdate,
|
|
||||||
canDelete,
|
|
||||||
canUpdateStatus,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -152,17 +143,15 @@ export default function AcademicTermIndex({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
actions={
|
actions={
|
||||||
canCreate && (
|
<Button asChild>
|
||||||
<Button asChild>
|
<button
|
||||||
<button
|
type="button"
|
||||||
type="button"
|
onClick={() => setCreateOpen(true)}
|
||||||
onClick={() => setCreateOpen(true)}
|
>
|
||||||
>
|
<Plus className="h-4 w-4" />
|
||||||
<Plus className="h-4 w-4" />
|
Tambah
|
||||||
Tambah
|
</button>
|
||||||
</button>
|
</Button>
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@ -9,14 +9,12 @@ export type { Course } from '@/types/course';
|
|||||||
type CreateColumnsParams = {
|
type CreateColumnsParams = {
|
||||||
handleEdit: (course: Course) => void;
|
handleEdit: (course: Course) => void;
|
||||||
handleDeleteClick: (course: Course) => void;
|
handleDeleteClick: (course: Course) => void;
|
||||||
canUpdate: boolean;
|
|
||||||
canDelete: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createCourseColumns(
|
export function createCourseColumns(
|
||||||
params: CreateColumnsParams,
|
params: CreateColumnsParams,
|
||||||
): ColumnDef<Course>[] {
|
): ColumnDef<Course>[] {
|
||||||
const { handleEdit, handleDeleteClick, canUpdate, canDelete } = params;
|
const { handleEdit, handleDeleteClick } = params;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@ -89,7 +87,6 @@ export function createCourseColumns(
|
|||||||
{
|
{
|
||||||
label: 'Edit',
|
label: 'Edit',
|
||||||
icon: <Pencil className="h-4 w-4" />,
|
icon: <Pencil className="h-4 w-4" />,
|
||||||
show: canUpdate,
|
|
||||||
onClick: () => handleEdit(course),
|
onClick: () => handleEdit(course),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -97,7 +94,6 @@ export function createCourseColumns(
|
|||||||
icon: (
|
icon: (
|
||||||
<Trash2 className="h-4 w-4 text-destructive" />
|
<Trash2 className="h-4 w-4 text-destructive" />
|
||||||
),
|
),
|
||||||
show: canDelete,
|
|
||||||
onClick: () => handleDeleteClick(course),
|
onClick: () => handleDeleteClick(course),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
|||||||
@ -19,7 +19,6 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@/components/ui/select';
|
} from '@/components/ui/select';
|
||||||
import { usePermissions } from '@/hooks/use-permissions';
|
|
||||||
import { useServerTable } from '@/hooks/use-server-table';
|
import { useServerTable } from '@/hooks/use-server-table';
|
||||||
import {
|
import {
|
||||||
index as courseIndex,
|
index as courseIndex,
|
||||||
@ -60,10 +59,6 @@ export default function CourseIndex({
|
|||||||
const [createOpen, setCreateOpen] = useState(false);
|
const [createOpen, setCreateOpen] = useState(false);
|
||||||
const [editing, setEditing] = useState<Course | null>(null);
|
const [editing, setEditing] = useState<Course | null>(null);
|
||||||
const [deleting, setDeleting] = useState<Course | null>(null);
|
const [deleting, setDeleting] = useState<Course | null>(null);
|
||||||
const { hasPermission } = usePermissions();
|
|
||||||
const canCreate = hasPermission('create-courses');
|
|
||||||
const canUpdate = hasPermission('update-courses');
|
|
||||||
const canDelete = hasPermission('delete-courses');
|
|
||||||
|
|
||||||
const filterFields: FilterField[] = [
|
const filterFields: FilterField[] = [
|
||||||
{
|
{
|
||||||
@ -116,8 +111,6 @@ export default function CourseIndex({
|
|||||||
const columns = createCourseColumns({
|
const columns = createCourseColumns({
|
||||||
handleEdit: (course) => setEditing(course),
|
handleEdit: (course) => setEditing(course),
|
||||||
handleDeleteClick: (course) => setDeleting(course),
|
handleDeleteClick: (course) => setDeleting(course),
|
||||||
canUpdate,
|
|
||||||
canDelete,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -150,17 +143,15 @@ export default function CourseIndex({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
actions={
|
actions={
|
||||||
canCreate && (
|
<Button asChild>
|
||||||
<Button asChild>
|
<button
|
||||||
<button
|
type="button"
|
||||||
type="button"
|
onClick={() => setCreateOpen(true)}
|
||||||
onClick={() => setCreateOpen(true)}
|
>
|
||||||
>
|
<Plus className="h-4 w-4" />
|
||||||
<Plus className="h-4 w-4" />
|
Tambah
|
||||||
Tambah
|
</button>
|
||||||
</button>
|
</Button>
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@ -9,14 +9,12 @@ export type { Department } from '@/types/department';
|
|||||||
type CreateColumnsParams = {
|
type CreateColumnsParams = {
|
||||||
handleEdit: (department: Department) => void;
|
handleEdit: (department: Department) => void;
|
||||||
handleDeleteClick: (department: Department) => void;
|
handleDeleteClick: (department: Department) => void;
|
||||||
canUpdate: boolean;
|
|
||||||
canDelete: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createDepartmentColumns(
|
export function createDepartmentColumns(
|
||||||
params: CreateColumnsParams,
|
params: CreateColumnsParams,
|
||||||
): ColumnDef<Department>[] {
|
): ColumnDef<Department>[] {
|
||||||
const { handleEdit, handleDeleteClick, canUpdate, canDelete } = params;
|
const { handleEdit, handleDeleteClick } = params;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@ -71,7 +69,6 @@ export function createDepartmentColumns(
|
|||||||
{
|
{
|
||||||
label: 'Edit',
|
label: 'Edit',
|
||||||
icon: <Pencil className="h-4 w-4" />,
|
icon: <Pencil className="h-4 w-4" />,
|
||||||
show: canUpdate,
|
|
||||||
onClick: () => handleEdit(department),
|
onClick: () => handleEdit(department),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -79,7 +76,6 @@ export function createDepartmentColumns(
|
|||||||
icon: (
|
icon: (
|
||||||
<Trash2 className="h-4 w-4 text-destructive" />
|
<Trash2 className="h-4 w-4 text-destructive" />
|
||||||
),
|
),
|
||||||
show: canDelete,
|
|
||||||
onClick: () => handleDeleteClick(department),
|
onClick: () => handleDeleteClick(department),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
|||||||
@ -17,7 +17,6 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@/components/ui/select';
|
} from '@/components/ui/select';
|
||||||
import { usePermissions } from '@/hooks/use-permissions';
|
|
||||||
import { useServerTable } from '@/hooks/use-server-table';
|
import { useServerTable } from '@/hooks/use-server-table';
|
||||||
import {
|
import {
|
||||||
index as departmentIndex,
|
index as departmentIndex,
|
||||||
@ -44,10 +43,6 @@ export default function DepartmentIndex({ departments, highlight }: Props) {
|
|||||||
const [createOpen, setCreateOpen] = useState(false);
|
const [createOpen, setCreateOpen] = useState(false);
|
||||||
const [editing, setEditing] = useState<Department | null>(null);
|
const [editing, setEditing] = useState<Department | null>(null);
|
||||||
const [deleting, setDeleting] = useState<Department | null>(null);
|
const [deleting, setDeleting] = useState<Department | null>(null);
|
||||||
const { hasPermission } = usePermissions();
|
|
||||||
const canCreate = hasPermission('create-departments');
|
|
||||||
const canUpdate = hasPermission('update-departments');
|
|
||||||
const canDelete = hasPermission('delete-departments');
|
|
||||||
|
|
||||||
const pagination: PaginationState = {
|
const pagination: PaginationState = {
|
||||||
current_page: departments.current_page,
|
current_page: departments.current_page,
|
||||||
@ -79,8 +74,6 @@ export default function DepartmentIndex({ departments, highlight }: Props) {
|
|||||||
const columns = createDepartmentColumns({
|
const columns = createDepartmentColumns({
|
||||||
handleEdit: (department) => setEditing(department),
|
handleEdit: (department) => setEditing(department),
|
||||||
handleDeleteClick: (department) => setDeleting(department),
|
handleDeleteClick: (department) => setDeleting(department),
|
||||||
canUpdate,
|
|
||||||
canDelete,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -113,17 +106,15 @@ export default function DepartmentIndex({ departments, highlight }: Props) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
actions={
|
actions={
|
||||||
canCreate && (
|
<Button asChild>
|
||||||
<Button asChild>
|
<button
|
||||||
<button
|
type="button"
|
||||||
type="button"
|
onClick={() => setCreateOpen(true)}
|
||||||
onClick={() => setCreateOpen(true)}
|
>
|
||||||
>
|
<Plus className="h-4 w-4" />
|
||||||
<Plus className="h-4 w-4" />
|
Tambah
|
||||||
Tambah
|
</button>
|
||||||
</button>
|
</Button>
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@ -9,14 +9,12 @@ export type { AcademicAdvisingLog } from '@/types/academic-advising-log';
|
|||||||
type CreateColumnsParams = {
|
type CreateColumnsParams = {
|
||||||
handleEdit: (log: AcademicAdvisingLog) => void;
|
handleEdit: (log: AcademicAdvisingLog) => void;
|
||||||
handleDeleteClick: (log: AcademicAdvisingLog) => void;
|
handleDeleteClick: (log: AcademicAdvisingLog) => void;
|
||||||
canUpdate: boolean;
|
|
||||||
canDelete: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createAcademicAdvisingLogColumns(
|
export function createAcademicAdvisingLogColumns(
|
||||||
params: CreateColumnsParams,
|
params: CreateColumnsParams,
|
||||||
): ColumnDef<AcademicAdvisingLog>[] {
|
): ColumnDef<AcademicAdvisingLog>[] {
|
||||||
const { handleEdit, handleDeleteClick, canUpdate, canDelete } = params;
|
const { handleEdit, handleDeleteClick } = params;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@ -85,7 +83,6 @@ export function createAcademicAdvisingLogColumns(
|
|||||||
{
|
{
|
||||||
label: 'Edit',
|
label: 'Edit',
|
||||||
icon: <Pencil className="h-4 w-4" />,
|
icon: <Pencil className="h-4 w-4" />,
|
||||||
show: canUpdate,
|
|
||||||
onClick: () => handleEdit(row.original),
|
onClick: () => handleEdit(row.original),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -93,7 +90,6 @@ export function createAcademicAdvisingLogColumns(
|
|||||||
icon: (
|
icon: (
|
||||||
<Trash2 className="h-4 w-4 text-destructive" />
|
<Trash2 className="h-4 w-4 text-destructive" />
|
||||||
),
|
),
|
||||||
show: canDelete,
|
|
||||||
onClick: () => handleDeleteClick(row.original),
|
onClick: () => handleDeleteClick(row.original),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
|||||||
@ -21,7 +21,6 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@/components/ui/select';
|
} from '@/components/ui/select';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
import { usePermissions } from '@/hooks/use-permissions';
|
|
||||||
import { useServerTable } from '@/hooks/use-server-table';
|
import { useServerTable } from '@/hooks/use-server-table';
|
||||||
import {
|
import {
|
||||||
index as academicAdvisingLogIndex,
|
index as academicAdvisingLogIndex,
|
||||||
@ -70,10 +69,6 @@ export default function AcademicAdvisingLogIndex({
|
|||||||
const [createOpen, setCreateOpen] = useState(false);
|
const [createOpen, setCreateOpen] = useState(false);
|
||||||
const [editing, setEditing] = useState<AcademicAdvisingLog | null>(null);
|
const [editing, setEditing] = useState<AcademicAdvisingLog | null>(null);
|
||||||
const [deleting, setDeleting] = useState<AcademicAdvisingLog | null>(null);
|
const [deleting, setDeleting] = useState<AcademicAdvisingLog | null>(null);
|
||||||
const { hasPermission } = usePermissions();
|
|
||||||
const canCreate = hasPermission('create-academic-advising-logs');
|
|
||||||
const canUpdate = hasPermission('update-academic-advising-logs');
|
|
||||||
const canDelete = hasPermission('delete-academic-advising-logs');
|
|
||||||
|
|
||||||
const filterFields: FilterField[] = [
|
const filterFields: FilterField[] = [
|
||||||
{
|
{
|
||||||
@ -118,8 +113,6 @@ export default function AcademicAdvisingLogIndex({
|
|||||||
const columns = createAcademicAdvisingLogColumns({
|
const columns = createAcademicAdvisingLogColumns({
|
||||||
handleEdit: (log) => setEditing(log),
|
handleEdit: (log) => setEditing(log),
|
||||||
handleDeleteClick: (log) => setDeleting(log),
|
handleDeleteClick: (log) => setDeleting(log),
|
||||||
canUpdate,
|
|
||||||
canDelete,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -137,17 +130,15 @@ export default function AcademicAdvisingLogIndex({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
actions={
|
actions={
|
||||||
canCreate && (
|
<Button asChild>
|
||||||
<Button asChild>
|
<button
|
||||||
<button
|
type="button"
|
||||||
type="button"
|
onClick={() => setCreateOpen(true)}
|
||||||
onClick={() => setCreateOpen(true)}
|
>
|
||||||
>
|
<Plus className="h-4 w-4" />
|
||||||
<Plus className="h-4 w-4" />
|
Tambah
|
||||||
Tambah
|
</button>
|
||||||
</button>
|
</Button>
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@ -11,14 +11,12 @@ export type { LetterRequest } from '@/types/letter-request';
|
|||||||
type CreateColumnsParams = {
|
type CreateColumnsParams = {
|
||||||
handleEdit: (letterRequest: LetterRequest) => void;
|
handleEdit: (letterRequest: LetterRequest) => void;
|
||||||
handleDeleteClick: (letterRequest: LetterRequest) => void;
|
handleDeleteClick: (letterRequest: LetterRequest) => void;
|
||||||
canUpdate: boolean;
|
|
||||||
canDelete: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createLetterRequestColumns(
|
export function createLetterRequestColumns(
|
||||||
params: CreateColumnsParams,
|
params: CreateColumnsParams,
|
||||||
): ColumnDef<LetterRequest>[] {
|
): ColumnDef<LetterRequest>[] {
|
||||||
const { handleEdit, handleDeleteClick, canUpdate, canDelete } = params;
|
const { handleEdit, handleDeleteClick } = params;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@ -145,7 +143,6 @@ export function createLetterRequestColumns(
|
|||||||
{
|
{
|
||||||
label: 'Edit',
|
label: 'Edit',
|
||||||
icon: <Pencil className="h-4 w-4" />,
|
icon: <Pencil className="h-4 w-4" />,
|
||||||
show: canUpdate,
|
|
||||||
onClick: () => handleEdit(row.original),
|
onClick: () => handleEdit(row.original),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -153,7 +150,6 @@ export function createLetterRequestColumns(
|
|||||||
icon: (
|
icon: (
|
||||||
<Trash2 className="h-4 w-4 text-destructive" />
|
<Trash2 className="h-4 w-4 text-destructive" />
|
||||||
),
|
),
|
||||||
show: canDelete,
|
|
||||||
onClick: () => handleDeleteClick(row.original),
|
onClick: () => handleDeleteClick(row.original),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
|||||||
@ -21,7 +21,6 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@/components/ui/select';
|
} from '@/components/ui/select';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
import { usePermissions } from '@/hooks/use-permissions';
|
|
||||||
import { useServerTable } from '@/hooks/use-server-table';
|
import { useServerTable } from '@/hooks/use-server-table';
|
||||||
import {
|
import {
|
||||||
index as letterRequestIndex,
|
index as letterRequestIndex,
|
||||||
@ -64,10 +63,6 @@ export default function LetterRequestIndex({
|
|||||||
const [createOpen, setCreateOpen] = useState(false);
|
const [createOpen, setCreateOpen] = useState(false);
|
||||||
const [editing, setEditing] = useState<LetterRequest | null>(null);
|
const [editing, setEditing] = useState<LetterRequest | null>(null);
|
||||||
const [deleting, setDeleting] = useState<LetterRequest | null>(null);
|
const [deleting, setDeleting] = useState<LetterRequest | null>(null);
|
||||||
const { hasPermission } = usePermissions();
|
|
||||||
const canCreate = hasPermission('create-letter-requests');
|
|
||||||
const canUpdate = hasPermission('update-letter-requests');
|
|
||||||
const canDelete = hasPermission('delete-letter-requests');
|
|
||||||
|
|
||||||
const filterFields: FilterField[] = [
|
const filterFields: FilterField[] = [
|
||||||
{
|
{
|
||||||
@ -112,8 +107,6 @@ export default function LetterRequestIndex({
|
|||||||
const columns = createLetterRequestColumns({
|
const columns = createLetterRequestColumns({
|
||||||
handleEdit: (letterRequest) => setEditing(letterRequest),
|
handleEdit: (letterRequest) => setEditing(letterRequest),
|
||||||
handleDeleteClick: (letterRequest) => setDeleting(letterRequest),
|
handleDeleteClick: (letterRequest) => setDeleting(letterRequest),
|
||||||
canUpdate,
|
|
||||||
canDelete,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -131,17 +124,15 @@ export default function LetterRequestIndex({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
actions={
|
actions={
|
||||||
canCreate && (
|
<Button asChild>
|
||||||
<Button asChild>
|
<button
|
||||||
<button
|
type="button"
|
||||||
type="button"
|
onClick={() => setCreateOpen(true)}
|
||||||
onClick={() => setCreateOpen(true)}
|
>
|
||||||
>
|
<Plus className="h-4 w-4" />
|
||||||
<Plus className="h-4 w-4" />
|
Tambah
|
||||||
Tambah
|
</button>
|
||||||
</button>
|
</Button>
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@ -18,10 +18,6 @@ type CreateColumnsParams = {
|
|||||||
handleDeleteClick: (admin: Administrator) => void;
|
handleDeleteClick: (admin: Administrator) => void;
|
||||||
handleResetPassword: (admin: Administrator) => void;
|
handleResetPassword: (admin: Administrator) => void;
|
||||||
handleUserStatusChange: (admin: Administrator, isActive: boolean) => void;
|
handleUserStatusChange: (admin: Administrator, isActive: boolean) => void;
|
||||||
canUpdate: boolean;
|
|
||||||
canResetPassword: boolean;
|
|
||||||
canDelete: boolean;
|
|
||||||
canUpdateStatus: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createAdministratorColumns(
|
export function createAdministratorColumns(
|
||||||
@ -32,10 +28,6 @@ export function createAdministratorColumns(
|
|||||||
handleDeleteClick,
|
handleDeleteClick,
|
||||||
handleResetPassword,
|
handleResetPassword,
|
||||||
handleUserStatusChange,
|
handleUserStatusChange,
|
||||||
canUpdate,
|
|
||||||
canResetPassword,
|
|
||||||
canDelete,
|
|
||||||
canUpdateStatus,
|
|
||||||
} = params;
|
} = params;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
@ -82,7 +74,6 @@ export function createAdministratorColumns(
|
|||||||
onChange={(isActive) =>
|
onChange={(isActive) =>
|
||||||
handleUserStatusChange(row.original, isActive)
|
handleUserStatusChange(row.original, isActive)
|
||||||
}
|
}
|
||||||
disabled={!canUpdateStatus}
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -102,13 +93,11 @@ export function createAdministratorColumns(
|
|||||||
{
|
{
|
||||||
label: 'Edit',
|
label: 'Edit',
|
||||||
icon: <Pencil className="h-4 w-4" />,
|
icon: <Pencil className="h-4 w-4" />,
|
||||||
show: canUpdate,
|
|
||||||
onClick: () => handleEdit(admin),
|
onClick: () => handleEdit(admin),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Reset Kata Sandi',
|
label: 'Reset Kata Sandi',
|
||||||
icon: <Key className="h-4 w-4" />,
|
icon: <Key className="h-4 w-4" />,
|
||||||
show: canResetPassword,
|
|
||||||
onClick: () => handleResetPassword(admin),
|
onClick: () => handleResetPassword(admin),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -116,7 +105,6 @@ export function createAdministratorColumns(
|
|||||||
icon: (
|
icon: (
|
||||||
<Trash2 className="h-4 w-4 text-destructive" />
|
<Trash2 className="h-4 w-4 text-destructive" />
|
||||||
),
|
),
|
||||||
show: canDelete,
|
|
||||||
onClick: () => handleDeleteClick(admin),
|
onClick: () => handleDeleteClick(admin),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import type { FilterField } from '@/components/filter-dialog';
|
|||||||
import { FilterDialog } from '@/components/filter-dialog';
|
import { FilterDialog } from '@/components/filter-dialog';
|
||||||
import { PageHeader } from '@/components/page-header';
|
import { PageHeader } from '@/components/page-header';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { usePermissions } from '@/hooks/use-permissions';
|
|
||||||
import { useServerTable } from '@/hooks/use-server-table';
|
import { useServerTable } from '@/hooks/use-server-table';
|
||||||
import {
|
import {
|
||||||
index as administratorsIndex,
|
index as administratorsIndex,
|
||||||
@ -49,8 +48,6 @@ const filterFields: FilterField[] = [
|
|||||||
export default function AdministratorIndex({ administrators, filters }: Props) {
|
export default function AdministratorIndex({ administrators, filters }: Props) {
|
||||||
const [deleting, setDeleting] = useState<Administrator | null>(null);
|
const [deleting, setDeleting] = useState<Administrator | null>(null);
|
||||||
const [resetting, setResetting] = useState<Administrator | null>(null);
|
const [resetting, setResetting] = useState<Administrator | null>(null);
|
||||||
const { hasPermission } = usePermissions();
|
|
||||||
const canCreate = hasPermission('create-administrators');
|
|
||||||
|
|
||||||
const pagination: PaginationState = {
|
const pagination: PaginationState = {
|
||||||
current_page: administrators.current_page,
|
current_page: administrators.current_page,
|
||||||
@ -110,10 +107,6 @@ export default function AdministratorIndex({ administrators, filters }: Props) {
|
|||||||
handleDeleteClick: (admin) => setDeleting(admin),
|
handleDeleteClick: (admin) => setDeleting(admin),
|
||||||
handleResetPassword: (admin) => setResetting(admin),
|
handleResetPassword: (admin) => setResetting(admin),
|
||||||
handleUserStatusChange,
|
handleUserStatusChange,
|
||||||
canUpdate: hasPermission('update-administrators'),
|
|
||||||
canResetPassword: hasPermission('reset-administrators-password'),
|
|
||||||
canDelete: hasPermission('delete-administrators'),
|
|
||||||
canUpdateStatus: hasPermission('update-administrators-status'),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -124,14 +117,12 @@ export default function AdministratorIndex({ administrators, filters }: Props) {
|
|||||||
<PageHeader
|
<PageHeader
|
||||||
title="Administrator"
|
title="Administrator"
|
||||||
actions={
|
actions={
|
||||||
canCreate && (
|
<Button asChild>
|
||||||
<Button asChild>
|
<Link href={create.url()}>
|
||||||
<Link href={create.url()}>
|
<Plus className="h-4 w-4" />
|
||||||
<Plus className="h-4 w-4" />
|
Tambah
|
||||||
Tambah
|
</Link>
|
||||||
</Link>
|
</Button>
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@ -21,10 +21,6 @@ type CreateColumnsParams = {
|
|||||||
handleDeleteClick: (lecturer: Lecturer) => void;
|
handleDeleteClick: (lecturer: Lecturer) => void;
|
||||||
handleResetPassword: (lecturer: Lecturer) => void;
|
handleResetPassword: (lecturer: Lecturer) => void;
|
||||||
handleUserStatusChange: (lecturer: Lecturer, isActive: boolean) => void;
|
handleUserStatusChange: (lecturer: Lecturer, isActive: boolean) => void;
|
||||||
canUpdate: boolean;
|
|
||||||
canDelete: boolean;
|
|
||||||
canResetPassword: boolean;
|
|
||||||
canUpdateStatus: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createLecturerColumns(
|
export function createLecturerColumns(
|
||||||
@ -35,10 +31,6 @@ export function createLecturerColumns(
|
|||||||
handleDeleteClick,
|
handleDeleteClick,
|
||||||
handleResetPassword,
|
handleResetPassword,
|
||||||
handleUserStatusChange,
|
handleUserStatusChange,
|
||||||
canUpdate,
|
|
||||||
canDelete,
|
|
||||||
canResetPassword,
|
|
||||||
canUpdateStatus,
|
|
||||||
} = params;
|
} = params;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
@ -100,7 +92,6 @@ export function createLecturerColumns(
|
|||||||
onChange={(isActive) =>
|
onChange={(isActive) =>
|
||||||
handleUserStatusChange(row.original, isActive)
|
handleUserStatusChange(row.original, isActive)
|
||||||
}
|
}
|
||||||
disabled={!canUpdateStatus}
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -120,13 +111,11 @@ export function createLecturerColumns(
|
|||||||
{
|
{
|
||||||
label: 'Edit',
|
label: 'Edit',
|
||||||
icon: <Pencil className="h-4 w-4" />,
|
icon: <Pencil className="h-4 w-4" />,
|
||||||
show: canUpdate,
|
|
||||||
onClick: () => handleEdit(lecturer),
|
onClick: () => handleEdit(lecturer),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Reset Kata Sandi',
|
label: 'Reset Kata Sandi',
|
||||||
icon: <Key className="h-4 w-4" />,
|
icon: <Key className="h-4 w-4" />,
|
||||||
show: canResetPassword,
|
|
||||||
onClick: () => handleResetPassword(lecturer),
|
onClick: () => handleResetPassword(lecturer),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -134,7 +123,6 @@ export function createLecturerColumns(
|
|||||||
icon: (
|
icon: (
|
||||||
<Trash2 className="h-4 w-4 text-destructive" />
|
<Trash2 className="h-4 w-4 text-destructive" />
|
||||||
),
|
),
|
||||||
show: canDelete,
|
|
||||||
onClick: () => handleDeleteClick(lecturer),
|
onClick: () => handleDeleteClick(lecturer),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import type { FilterField } from '@/components/filter-dialog';
|
|||||||
import { FilterDialog } from '@/components/filter-dialog';
|
import { FilterDialog } from '@/components/filter-dialog';
|
||||||
import { PageHeader } from '@/components/page-header';
|
import { PageHeader } from '@/components/page-header';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { usePermissions } from '@/hooks/use-permissions';
|
|
||||||
import { useServerTable } from '@/hooks/use-server-table';
|
import { useServerTable } from '@/hooks/use-server-table';
|
||||||
import {
|
import {
|
||||||
index as lecturersIndex,
|
index as lecturersIndex,
|
||||||
@ -48,13 +47,6 @@ export default function LecturerIndex({
|
|||||||
}: Props) {
|
}: Props) {
|
||||||
const [deleting, setDeleting] = useState<Lecturer | null>(null);
|
const [deleting, setDeleting] = useState<Lecturer | null>(null);
|
||||||
const [resetting, setResetting] = useState<Lecturer | null>(null);
|
const [resetting, setResetting] = useState<Lecturer | null>(null);
|
||||||
const { hasPermission } = usePermissions();
|
|
||||||
const canCreate = hasPermission('create-lecturers');
|
|
||||||
const canUpdate = hasPermission('update-lecturers');
|
|
||||||
const canDelete = hasPermission('delete-lecturers');
|
|
||||||
const canResetPassword = hasPermission('reset-lecturers-password');
|
|
||||||
const canUpdateStatus = hasPermission('update-lecturers-status');
|
|
||||||
const canExport = hasPermission('export-lecturers');
|
|
||||||
|
|
||||||
const filterFields: FilterField[] = [
|
const filterFields: FilterField[] = [
|
||||||
{
|
{
|
||||||
@ -133,10 +125,6 @@ export default function LecturerIndex({
|
|||||||
handleDeleteClick: (lecturer) => setDeleting(lecturer),
|
handleDeleteClick: (lecturer) => setDeleting(lecturer),
|
||||||
handleResetPassword: (lecturer) => setResetting(lecturer),
|
handleResetPassword: (lecturer) => setResetting(lecturer),
|
||||||
handleUserStatusChange,
|
handleUserStatusChange,
|
||||||
canUpdate,
|
|
||||||
canDelete,
|
|
||||||
canResetPassword,
|
|
||||||
canUpdateStatus,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -148,26 +136,22 @@ export default function LecturerIndex({
|
|||||||
title="Dosen"
|
title="Dosen"
|
||||||
actions={
|
actions={
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{canExport && (
|
<Button variant="outline" asChild>
|
||||||
<Button variant="outline" asChild>
|
<a
|
||||||
<a
|
href={exportLecturers.url({
|
||||||
href={exportLecturers.url({
|
query: { search, ...filters },
|
||||||
query: { search, ...filters },
|
})}
|
||||||
})}
|
>
|
||||||
>
|
<FileSpreadsheet className="h-4 w-4" />
|
||||||
<FileSpreadsheet className="h-4 w-4" />
|
Export
|
||||||
Export
|
</a>
|
||||||
</a>
|
</Button>
|
||||||
</Button>
|
<Button asChild>
|
||||||
)}
|
<Link href={create.url()}>
|
||||||
{canCreate && (
|
<Plus className="h-4 w-4" />
|
||||||
<Button asChild>
|
Tambah
|
||||||
<Link href={create.url()}>
|
</Link>
|
||||||
<Plus className="h-4 w-4" />
|
</Button>
|
||||||
Tambah
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -29,11 +29,6 @@ type CreateColumnsParams = {
|
|||||||
handleStatusChange: (student: Student, status: string) => void;
|
handleStatusChange: (student: Student, status: string) => void;
|
||||||
handleUserStatusChange: (student: Student, isActive: boolean) => void;
|
handleUserStatusChange: (student: Student, isActive: boolean) => void;
|
||||||
statuses: StatusOption[];
|
statuses: StatusOption[];
|
||||||
canUpdate: boolean;
|
|
||||||
canDelete: boolean;
|
|
||||||
canResetPassword: boolean;
|
|
||||||
canUpdateAcademicStatus: boolean;
|
|
||||||
canUpdateAccountStatus: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createStudentColumns(
|
export function createStudentColumns(
|
||||||
@ -46,11 +41,6 @@ export function createStudentColumns(
|
|||||||
handleStatusChange,
|
handleStatusChange,
|
||||||
handleUserStatusChange,
|
handleUserStatusChange,
|
||||||
statuses,
|
statuses,
|
||||||
canUpdate,
|
|
||||||
canDelete,
|
|
||||||
canResetPassword,
|
|
||||||
canUpdateAcademicStatus,
|
|
||||||
canUpdateAccountStatus,
|
|
||||||
} = params;
|
} = params;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
@ -103,7 +93,6 @@ export function createStudentColumns(
|
|||||||
onChange={(status) =>
|
onChange={(status) =>
|
||||||
handleStatusChange(row.original, status)
|
handleStatusChange(row.original, status)
|
||||||
}
|
}
|
||||||
disabled={!canUpdateAcademicStatus}
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -128,7 +117,6 @@ export function createStudentColumns(
|
|||||||
onChange={(isActive) =>
|
onChange={(isActive) =>
|
||||||
handleUserStatusChange(row.original, isActive)
|
handleUserStatusChange(row.original, isActive)
|
||||||
}
|
}
|
||||||
disabled={!canUpdateAccountStatus}
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -148,13 +136,11 @@ export function createStudentColumns(
|
|||||||
{
|
{
|
||||||
label: 'Edit',
|
label: 'Edit',
|
||||||
icon: <Pencil className="h-4 w-4" />,
|
icon: <Pencil className="h-4 w-4" />,
|
||||||
show: canUpdate,
|
|
||||||
onClick: () => handleEdit(student),
|
onClick: () => handleEdit(student),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Reset Kata Sandi',
|
label: 'Reset Kata Sandi',
|
||||||
icon: <Key className="h-4 w-4" />,
|
icon: <Key className="h-4 w-4" />,
|
||||||
show: canResetPassword,
|
|
||||||
onClick: () => handleResetPassword(student),
|
onClick: () => handleResetPassword(student),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -162,7 +148,6 @@ export function createStudentColumns(
|
|||||||
icon: (
|
icon: (
|
||||||
<Trash2 className="h-4 w-4 text-destructive" />
|
<Trash2 className="h-4 w-4 text-destructive" />
|
||||||
),
|
),
|
||||||
show: canDelete,
|
|
||||||
onClick: () => handleDeleteClick(student),
|
onClick: () => handleDeleteClick(student),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import { FilterDialog } from '@/components/filter-dialog';
|
|||||||
import { PageHeader } from '@/components/page-header';
|
import { PageHeader } from '@/components/page-header';
|
||||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { usePermissions } from '@/hooks/use-permissions';
|
|
||||||
import { useServerTable } from '@/hooks/use-server-table';
|
import { useServerTable } from '@/hooks/use-server-table';
|
||||||
import {
|
import {
|
||||||
create,
|
create,
|
||||||
@ -57,18 +56,6 @@ export default function StudentIndex({
|
|||||||
}: Props) {
|
}: Props) {
|
||||||
const [deleting, setDeleting] = useState<Student | null>(null);
|
const [deleting, setDeleting] = useState<Student | null>(null);
|
||||||
const [resetting, setResetting] = useState<Student | null>(null);
|
const [resetting, setResetting] = useState<Student | null>(null);
|
||||||
const { hasPermission } = usePermissions();
|
|
||||||
const canCreate = hasPermission('create-students');
|
|
||||||
const canUpdate = hasPermission('update-students');
|
|
||||||
const canDelete = hasPermission('delete-students');
|
|
||||||
const canResetPassword = hasPermission('reset-students-password');
|
|
||||||
const canUpdateAcademicStatus = hasPermission(
|
|
||||||
'update-students-academic-status',
|
|
||||||
);
|
|
||||||
const canUpdateAccountStatus = hasPermission(
|
|
||||||
'update-students-account-status',
|
|
||||||
);
|
|
||||||
const canExport = hasPermission('export-students');
|
|
||||||
|
|
||||||
const filterFields: FilterField[] = [
|
const filterFields: FilterField[] = [
|
||||||
{
|
{
|
||||||
@ -170,11 +157,6 @@ export default function StudentIndex({
|
|||||||
handleStatusChange,
|
handleStatusChange,
|
||||||
handleUserStatusChange,
|
handleUserStatusChange,
|
||||||
statuses,
|
statuses,
|
||||||
canUpdate,
|
|
||||||
canDelete,
|
|
||||||
canResetPassword,
|
|
||||||
canUpdateAcademicStatus,
|
|
||||||
canUpdateAccountStatus,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -186,26 +168,22 @@ export default function StudentIndex({
|
|||||||
title="Mahasiswa"
|
title="Mahasiswa"
|
||||||
actions={
|
actions={
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{canExport && (
|
<Button variant="outline" asChild>
|
||||||
<Button variant="outline" asChild>
|
<a
|
||||||
<a
|
href={exportStudents.url({
|
||||||
href={exportStudents.url({
|
query: { search, ...filters },
|
||||||
query: { search, ...filters },
|
})}
|
||||||
})}
|
>
|
||||||
>
|
<FileSpreadsheet className="h-4 w-4" />
|
||||||
<FileSpreadsheet className="h-4 w-4" />
|
Export
|
||||||
Export
|
</a>
|
||||||
</a>
|
</Button>
|
||||||
</Button>
|
<Button asChild>
|
||||||
)}
|
<Link href={create.url()}>
|
||||||
{canCreate && (
|
<Plus className="h-4 w-4" />
|
||||||
<Button asChild>
|
Tambah
|
||||||
<Link href={create.url()}>
|
</Link>
|
||||||
<Plus className="h-4 w-4" />
|
</Button>
|
||||||
Tambah
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -14,5 +14,4 @@ export type User = {
|
|||||||
|
|
||||||
export type Auth = {
|
export type Auth = {
|
||||||
user: User;
|
user: User;
|
||||||
permissions?: string[];
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,27 +0,0 @@
|
|||||||
export const LogLevels = [
|
|
||||||
'EMERGENCY',
|
|
||||||
'ALERT',
|
|
||||||
'CRITICAL',
|
|
||||||
'ERROR',
|
|
||||||
'WARNING',
|
|
||||||
'NOTICE',
|
|
||||||
'INFO',
|
|
||||||
'DEBUG',
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export type LogLevel = (typeof LogLevels)[number];
|
|
||||||
|
|
||||||
export type LogEntry = {
|
|
||||||
id: string;
|
|
||||||
timestamp: string;
|
|
||||||
environment: string;
|
|
||||||
level: LogLevel;
|
|
||||||
message: string;
|
|
||||||
raw: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type LogFile = {
|
|
||||||
name: string;
|
|
||||||
size: number;
|
|
||||||
modified_at: string;
|
|
||||||
};
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
export type RoleWithPermissions = {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
permissions: string[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type PermissionGroups = Record<string, string[]>;
|
|
||||||
|
|
||||||
export const RoleLabels: Record<string, string> = {
|
|
||||||
mahasiswa: 'Mahasiswa',
|
|
||||||
dosen: 'Dosen',
|
|
||||||
'staff-admin': 'Staff Admin',
|
|
||||||
'staff-keuangan': 'Staff Keuangan',
|
|
||||||
kaprodi: 'Kaprodi',
|
|
||||||
developer: 'Developer',
|
|
||||||
};
|
|
||||||
|
|
||||||
export function formatRoleLabel(name: string): string {
|
|
||||||
return RoleLabels[name] ?? name;
|
|
||||||
}
|
|
||||||
178
routes/admin.php
178
routes/admin.php
@ -5,8 +5,6 @@
|
|||||||
use App\Http\Controllers\Admin\AcademicClasses\MaterialController;
|
use App\Http\Controllers\Admin\AcademicClasses\MaterialController;
|
||||||
use App\Http\Controllers\Admin\AcademicClasses\ScheduleController;
|
use App\Http\Controllers\Admin\AcademicClasses\ScheduleController;
|
||||||
use App\Http\Controllers\Admin\AcademicClasses\SubmissionController;
|
use App\Http\Controllers\Admin\AcademicClasses\SubmissionController;
|
||||||
use App\Http\Controllers\Admin\Developer\LogController;
|
|
||||||
use App\Http\Controllers\Admin\Developer\RolePermissionController;
|
|
||||||
use App\Http\Controllers\Admin\FeedbackController;
|
use App\Http\Controllers\Admin\FeedbackController;
|
||||||
use App\Http\Controllers\Admin\Finances\TuitionInvoiceController;
|
use App\Http\Controllers\Admin\Finances\TuitionInvoiceController;
|
||||||
use App\Http\Controllers\Admin\Finances\TuitionPaymentController;
|
use App\Http\Controllers\Admin\Finances\TuitionPaymentController;
|
||||||
@ -28,105 +26,62 @@
|
|||||||
|
|
||||||
Route::middleware(['auth', 'verified'])->group(function () {
|
Route::middleware(['auth', 'verified'])->group(function () {
|
||||||
Route::prefix('admin/master')->name('admin.master.')->group(function () {
|
Route::prefix('admin/master')->name('admin.master.')->group(function () {
|
||||||
Route::resource('academic-terms', AcademicTermController::class)
|
Route::resource('academic-terms', AcademicTermController::class)->except(['create', 'edit', 'show']);
|
||||||
->except(['create', 'edit', 'show'])
|
Route::patch('academic-terms/{academic_term}/status', [AcademicTermController::class, 'updateStatus'])->name('academic-terms.update_status');
|
||||||
->middlewareFor(['index'], 'permission:view-academic-terms')
|
Route::resource('departments', DepartmentController::class)->except(['create', 'edit', 'show']);
|
||||||
->middlewareFor(['store'], 'permission:create-academic-terms')
|
Route::resource('courses', CourseController::class)->except(['create', 'edit', 'show']);
|
||||||
->middlewareFor(['update'], 'permission:update-academic-terms')
|
|
||||||
->middlewareFor(['destroy'], 'permission:delete-academic-terms');
|
|
||||||
Route::patch('academic-terms/{academic_term}/status', [AcademicTermController::class, 'updateStatus'])->name('academic-terms.update_status')->middleware('permission:update-academic-terms-status');
|
|
||||||
|
|
||||||
Route::resource('departments', DepartmentController::class)
|
|
||||||
->except(['create', 'edit', 'show'])
|
|
||||||
->middlewareFor(['index'], 'permission:view-departments')
|
|
||||||
->middlewareFor(['store'], 'permission:create-departments')
|
|
||||||
->middlewareFor(['update'], 'permission:update-departments')
|
|
||||||
->middlewareFor(['destroy'], 'permission:delete-departments');
|
|
||||||
|
|
||||||
Route::resource('courses', CourseController::class)
|
|
||||||
->except(['create', 'edit', 'show'])
|
|
||||||
->middlewareFor(['index'], 'permission:view-courses')
|
|
||||||
->middlewareFor(['store'], 'permission:create-courses')
|
|
||||||
->middlewareFor(['update'], 'permission:update-courses')
|
|
||||||
->middlewareFor(['destroy'], 'permission:delete-courses');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::prefix('admin/academic-classes')->name('admin.academic-classes.')->group(function () {
|
Route::prefix('admin/academic-classes')->name('admin.academic-classes.')->group(function () {
|
||||||
Route::resource('materials', MaterialController::class)
|
Route::resource('materials', MaterialController::class)->except(['create', 'edit', 'show']);
|
||||||
->except(['create', 'edit', 'show'])
|
|
||||||
->middlewareFor(['index'], 'permission:view-materials')
|
|
||||||
->middlewareFor(['store'], 'permission:create-materials')
|
|
||||||
->middlewareFor(['update'], 'permission:update-materials')
|
|
||||||
->middlewareFor(['destroy'], 'permission:delete-materials');
|
|
||||||
|
|
||||||
Route::resource('assignments', AssignmentController::class)
|
Route::resource('assignments', AssignmentController::class)->except(['create', 'edit', 'show']);
|
||||||
->except(['create', 'edit', 'show'])
|
|
||||||
->middlewareFor(['index'], 'permission:view-assignments')
|
|
||||||
->middlewareFor(['store'], 'permission:create-assignments')
|
|
||||||
->middlewareFor(['update'], 'permission:update-assignments')
|
|
||||||
->middlewareFor(['destroy'], 'permission:delete-assignments');
|
|
||||||
|
|
||||||
Route::prefix('assignments/{assignment}/submissions')->name('assignments.submissions.')->group(function () {
|
Route::prefix('assignments/{assignment}/submissions')->name('assignments.submissions.')->group(function () {
|
||||||
Route::get('/', [SubmissionController::class, 'index'])->name('index')->middleware('permission:view-assignment-submissions');
|
Route::get('/', [SubmissionController::class, 'index'])->name('index');
|
||||||
Route::post('/', [SubmissionController::class, 'store'])->name('store')->middleware('permission:create-assignment-submissions');
|
Route::post('/', [SubmissionController::class, 'store'])->name('store');
|
||||||
Route::put('{submission}', [SubmissionController::class, 'update'])->name('update')->middleware('permission:update-assignment-submissions');
|
Route::put('{submission}', [SubmissionController::class, 'update'])->name('update');
|
||||||
Route::delete('{submission}', [SubmissionController::class, 'destroy'])->name('destroy')->middleware('permission:delete-assignment-submissions');
|
Route::delete('{submission}', [SubmissionController::class, 'destroy'])->name('destroy');
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::resource('schedules', ScheduleController::class)
|
Route::resource('schedules', ScheduleController::class)->except(['create', 'edit', 'show']);
|
||||||
->except(['create', 'edit', 'show'])
|
|
||||||
->middlewareFor(['index'], 'permission:view-schedules')
|
|
||||||
->middlewareFor(['store'], 'permission:create-schedules')
|
|
||||||
->middlewareFor(['update'], 'permission:update-schedules')
|
|
||||||
->middlewareFor(['destroy'], 'permission:delete-schedules');
|
|
||||||
|
|
||||||
Route::prefix('attendances')->name('attendances.')->group(function () {
|
Route::prefix('attendances')->name('attendances.')->group(function () {
|
||||||
Route::get('/', [AttendanceController::class, 'index'])->name('index')->middleware('permission:view-attendances');
|
Route::get('/', [AttendanceController::class, 'index'])->name('index');
|
||||||
Route::get('{course_class}/{meeting_number}', [AttendanceController::class, 'session'])
|
Route::get('{course_class}/{meeting_number}', [AttendanceController::class, 'session'])
|
||||||
->whereNumber('meeting_number')
|
->whereNumber('meeting_number')
|
||||||
->name('session')
|
->name('session');
|
||||||
->middleware('permission:view-attendances');
|
|
||||||
Route::post('{course_class}/{meeting_number}', [AttendanceController::class, 'store'])
|
Route::post('{course_class}/{meeting_number}', [AttendanceController::class, 'store'])
|
||||||
->whereNumber('meeting_number')
|
->whereNumber('meeting_number')
|
||||||
->name('store')
|
->name('store');
|
||||||
->middleware('permission:create-attendances');
|
|
||||||
Route::delete('{course_class}/{meeting_number}', [AttendanceController::class, 'destroy'])
|
Route::delete('{course_class}/{meeting_number}', [AttendanceController::class, 'destroy'])
|
||||||
->whereNumber('meeting_number')
|
->whereNumber('meeting_number')
|
||||||
->name('destroy')
|
->name('destroy');
|
||||||
->middleware('permission:delete-attendances');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::prefix('admin/manage')->name('admin.manage.')->group(function () {
|
Route::prefix('admin/manage')->name('admin.manage.')->group(function () {
|
||||||
Route::resource('course-registrations', CourseRegistrationController::class)
|
Route::resource('course-registrations', CourseRegistrationController::class)->only(['index', 'store']);
|
||||||
->only(['index', 'store'])
|
|
||||||
->middlewareFor(['index'], 'permission:view-course-registrations')
|
|
||||||
->middlewareFor(['store'], 'permission:create-course-registrations');
|
|
||||||
|
|
||||||
Route::prefix('course-registrations/{submission}')->name('course-registrations.')->group(function () {
|
Route::prefix('course-registrations/{submission}')->name('course-registrations.')->group(function () {
|
||||||
Route::get('/', [CourseRegistrationController::class, 'show'])->name('show')->middleware('permission:view-course-registrations');
|
Route::get('/', [CourseRegistrationController::class, 'show'])->name('show');
|
||||||
Route::patch('approve', [CourseRegistrationController::class, 'approve'])->name('approve')->middleware('permission:approve-course-registrations');
|
Route::patch('approve', [CourseRegistrationController::class, 'approve'])->name('approve');
|
||||||
Route::patch('reject', [CourseRegistrationController::class, 'reject'])->name('reject')->middleware('permission:reject-course-registrations');
|
Route::patch('reject', [CourseRegistrationController::class, 'reject'])->name('reject');
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::resource('course-classes', CourseClassController::class)
|
Route::resource('course-classes', CourseClassController::class)->except(['create', 'edit', 'show']);
|
||||||
->except(['create', 'edit', 'show'])
|
|
||||||
->middlewareFor(['index'], 'permission:view-course-classes')
|
|
||||||
->middlewareFor(['store'], 'permission:create-course-classes')
|
|
||||||
->middlewareFor(['update'], 'permission:update-course-classes')
|
|
||||||
->middlewareFor(['destroy'], 'permission:delete-course-classes');
|
|
||||||
|
|
||||||
Route::prefix('course-classes/{course_class}/enrollments')->name('course-classes.enrollments.')->group(function () {
|
Route::prefix('course-classes/{course_class}/enrollments')->name('course-classes.enrollments.')->group(function () {
|
||||||
Route::get('/', [ClassEnrollmentController::class, 'index'])->name('index')->middleware('permission:view-course-class-enrollments');
|
Route::get('/', [ClassEnrollmentController::class, 'index'])->name('index');
|
||||||
Route::post('/', [ClassEnrollmentController::class, 'store'])->name('store')->middleware('permission:create-course-class-enrollments');
|
Route::post('/', [ClassEnrollmentController::class, 'store'])->name('store');
|
||||||
Route::delete('{enrollment}', [ClassEnrollmentController::class, 'destroy'])->name('destroy')->middleware('permission:delete-course-class-enrollments');
|
Route::delete('{enrollment}', [ClassEnrollmentController::class, 'destroy'])->name('destroy');
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::prefix('announcements')->name('announcements.')->group(function () {
|
Route::prefix('announcements')->name('announcements.')->group(function () {
|
||||||
Route::get('/', [AnnouncementController::class, 'index'])->name('index')->middleware('permission:view-announcements');
|
Route::get('/', [AnnouncementController::class, 'index'])->name('index');
|
||||||
Route::post('/', [AnnouncementController::class, 'store'])->name('store')->middleware('permission:create-announcements');
|
Route::post('/', [AnnouncementController::class, 'store'])->name('store');
|
||||||
Route::put('{announcement}', [AnnouncementController::class, 'update'])->name('update')->middleware('permission:update-announcements');
|
Route::put('{announcement}', [AnnouncementController::class, 'update'])->name('update');
|
||||||
Route::delete('{announcement}', [AnnouncementController::class, 'destroy'])->name('destroy')->middleware('permission:delete-announcements');
|
Route::delete('{announcement}', [AnnouncementController::class, 'destroy'])->name('destroy');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -152,35 +107,20 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
Route::prefix('admin/finances')->name('admin.finances.')->group(function () {
|
Route::prefix('admin/finances')->name('admin.finances.')->group(function () {
|
||||||
Route::resource('tuition-invoices', TuitionInvoiceController::class)
|
Route::resource('tuition-invoices', TuitionInvoiceController::class)->except(['create', 'edit', 'show']);
|
||||||
->except(['create', 'edit', 'show'])
|
|
||||||
->middlewareFor(['index'], 'permission:view-tuition-invoices')
|
|
||||||
->middlewareFor(['store'], 'permission:create-tuition-invoices')
|
|
||||||
->middlewareFor(['update'], 'permission:update-tuition-invoices')
|
|
||||||
->middlewareFor(['destroy'], 'permission:delete-tuition-invoices');
|
|
||||||
|
|
||||||
Route::prefix('tuition-invoices/{tuition_invoice}/payments')->name('tuition-invoices.payments.')->group(function () {
|
Route::prefix('tuition-invoices/{tuition_invoice}/payments')->name('tuition-invoices.payments.')->group(function () {
|
||||||
Route::get('/', [TuitionPaymentController::class, 'index'])->name('index')->middleware('permission:view-tuition-payments');
|
Route::get('/', [TuitionPaymentController::class, 'index'])->name('index');
|
||||||
Route::post('/', [TuitionPaymentController::class, 'store'])->name('store')->middleware('permission:create-tuition-payments');
|
Route::post('/', [TuitionPaymentController::class, 'store'])->name('store');
|
||||||
Route::put('{payment}', [TuitionPaymentController::class, 'update'])->name('update')->middleware('permission:update-tuition-payments');
|
Route::put('{payment}', [TuitionPaymentController::class, 'update'])->name('update');
|
||||||
Route::delete('{payment}', [TuitionPaymentController::class, 'destroy'])->name('destroy')->middleware('permission:delete-tuition-payments');
|
Route::delete('{payment}', [TuitionPaymentController::class, 'destroy'])->name('destroy');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::prefix('admin/services')->name('admin.services.')->group(function () {
|
Route::prefix('admin/services')->name('admin.services.')->group(function () {
|
||||||
Route::resource('letter-requests', LetterRequestController::class)
|
Route::resource('letter-requests', LetterRequestController::class)->except(['create', 'edit', 'show']);
|
||||||
->except(['create', 'edit', 'show'])
|
|
||||||
->middlewareFor(['index'], 'permission:view-letter-requests')
|
|
||||||
->middlewareFor(['store'], 'permission:create-letter-requests')
|
|
||||||
->middlewareFor(['update'], 'permission:update-letter-requests')
|
|
||||||
->middlewareFor(['destroy'], 'permission:delete-letter-requests');
|
|
||||||
|
|
||||||
Route::resource('academic-advising-logs', AcademicAdvisingLogController::class)
|
Route::resource('academic-advising-logs', AcademicAdvisingLogController::class)->except(['create', 'edit', 'show']);
|
||||||
->except(['create', 'edit', 'show'])
|
|
||||||
->middlewareFor(['index'], 'permission:view-academic-advising-logs')
|
|
||||||
->middlewareFor(['store'], 'permission:create-academic-advising-logs')
|
|
||||||
->middlewareFor(['update'], 'permission:update-academic-advising-logs')
|
|
||||||
->middlewareFor(['destroy'], 'permission:delete-academic-advising-logs');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::prefix('admin/settings')->name('admin.settings.')->group(function () {
|
Route::prefix('admin/settings')->name('admin.settings.')->group(function () {
|
||||||
@ -205,45 +145,19 @@
|
|||||||
->names('admin.feedback');
|
->names('admin.feedback');
|
||||||
|
|
||||||
Route::prefix('admin/users')->name('admin.users.')->group(function () {
|
Route::prefix('admin/users')->name('admin.users.')->group(function () {
|
||||||
Route::resource('lecturers', LecturerController::class)
|
Route::resource('lecturers', LecturerController::class)->except(['show'])->parameters(['lecturers' => 'user']);
|
||||||
->except(['show'])
|
Route::patch('lecturers/{user}/reset-password', [LecturerController::class, 'resetPassword'])->name('lecturers.reset_password');
|
||||||
->parameters(['lecturers' => 'user'])
|
Route::patch('lecturers/{user}/user-status', [LecturerController::class, 'updateUserStatus'])->name('lecturers.update_user_status');
|
||||||
->middlewareFor(['index'], 'permission:view-lecturers')
|
Route::get('lecturers/export', [LecturerController::class, 'export'])->name('lecturers.export');
|
||||||
->middlewareFor(['create', 'store'], 'permission:create-lecturers')
|
|
||||||
->middlewareFor(['edit', 'update'], 'permission:update-lecturers')
|
|
||||||
->middlewareFor(['destroy'], 'permission:delete-lecturers');
|
|
||||||
Route::patch('lecturers/{user}/reset-password', [LecturerController::class, 'resetPassword'])->name('lecturers.reset_password')->middleware('permission:reset-lecturers-password');
|
|
||||||
Route::patch('lecturers/{user}/user-status', [LecturerController::class, 'updateUserStatus'])->name('lecturers.update_user_status')->middleware('permission:update-lecturers-status');
|
|
||||||
Route::get('lecturers/export', [LecturerController::class, 'export'])->name('lecturers.export')->middleware('permission:export-lecturers');
|
|
||||||
|
|
||||||
Route::resource('students', StudentController::class)
|
Route::resource('students', StudentController::class)->except(['show'])->parameters(['students' => 'user']);
|
||||||
->except(['show'])
|
Route::patch('students/{user}/reset-password', [StudentController::class, 'resetPassword'])->name('students.reset_password');
|
||||||
->parameters(['students' => 'user'])
|
Route::patch('students/{user}/status', [StudentController::class, 'updateStatus'])->name('students.update_status');
|
||||||
->middlewareFor(['index'], 'permission:view-students')
|
Route::patch('students/{user}/user-status', [StudentController::class, 'updateUserStatus'])->name('students.update_user_status');
|
||||||
->middlewareFor(['create', 'store'], 'permission:create-students')
|
Route::get('students/export', [StudentController::class, 'export'])->name('students.export');
|
||||||
->middlewareFor(['edit', 'update'], 'permission:update-students')
|
|
||||||
->middlewareFor(['destroy'], 'permission:delete-students');
|
|
||||||
Route::patch('students/{user}/reset-password', [StudentController::class, 'resetPassword'])->name('students.reset_password')->middleware('permission:reset-students-password');
|
|
||||||
Route::patch('students/{user}/status', [StudentController::class, 'updateStatus'])->name('students.update_status')->middleware('permission:update-students-academic-status');
|
|
||||||
Route::patch('students/{user}/user-status', [StudentController::class, 'updateUserStatus'])->name('students.update_user_status')->middleware('permission:update-students-account-status');
|
|
||||||
Route::get('students/export', [StudentController::class, 'export'])->name('students.export')->middleware('permission:export-students');
|
|
||||||
|
|
||||||
Route::resource('administrators', AdministratorController::class)
|
Route::resource('administrators', AdministratorController::class)->except(['show'])->parameters(['administrators' => 'user']);
|
||||||
->except(['show'])
|
Route::patch('administrators/{user}/reset-password', [AdministratorController::class, 'resetPassword'])->name('administrators.reset_password');
|
||||||
->parameters(['administrators' => 'user'])
|
Route::patch('administrators/{user}/user-status', [AdministratorController::class, 'updateUserStatus'])->name('administrators.update_user_status');
|
||||||
->middlewareFor(['index'], 'permission:view-administrators')
|
|
||||||
->middlewareFor(['create', 'store'], 'permission:create-administrators')
|
|
||||||
->middlewareFor(['edit', 'update'], 'permission:update-administrators')
|
|
||||||
->middlewareFor(['destroy'], 'permission:delete-administrators');
|
|
||||||
Route::patch('administrators/{user}/reset-password', [AdministratorController::class, 'resetPassword'])->name('administrators.reset_password')->middleware('permission:reset-administrators-password');
|
|
||||||
Route::patch('administrators/{user}/user-status', [AdministratorController::class, 'updateUserStatus'])->name('administrators.update_user_status')->middleware('permission:update-administrators-status');
|
|
||||||
});
|
|
||||||
|
|
||||||
Route::prefix('admin/developer')->name('admin.developer.')->group(function () {
|
|
||||||
Route::get('logs', [LogController::class, 'index'])->name('logs.index')->middleware('permission:view-logs');
|
|
||||||
Route::get('logs/download', [LogController::class, 'download'])->name('logs.download')->middleware('permission:view-logs');
|
|
||||||
|
|
||||||
Route::get('roles', [RolePermissionController::class, 'index'])->name('roles.index')->middleware('permission:view-roles');
|
|
||||||
Route::put('roles/{role}', [RolePermissionController::class, 'update'])->name('roles.update')->middleware('permission:update-roles');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user