From 2885de2c2058d5d97ebc9442232d1ecd67e821d7 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Fri, 21 Aug 2026 14:14:01 +0700 Subject: [PATCH] feat: add lecturer and student management pages with CRUD functionality - Implemented LecturerEdit and LecturerIndex components for managing lecturers. - Created StudentCreate and StudentEdit components for adding and editing students. - Developed StudentIndex component for listing students with search and pagination. - Added department and user types for better type safety. - Updated routes for lecturers and students, including reset password functionality. - Removed AppSidebar from dashboard for a cleaner layout. --- app/Enums/StudentStatus.php | 21 ++ .../Admin/Master/DepartmentController.php | 53 ++++ .../Admin/Users/AdministratorController.php | 77 +++++ .../Admin/Users/LecturerController.php | 78 +++++ .../Admin/Users/StudentController.php | 82 +++++ .../Admin/Master/DepartmentRequest.php | 28 ++ .../Admin/Users/AdministratorRequest.php | 51 +++ .../Requests/Admin/Users/LecturerRequest.php | 63 ++++ .../Requests/Admin/Users/StudentRequest.php | 79 +++++ app/Models/Department.php | 36 +++ app/Models/DepartmentLeadership.php | 32 ++ app/Models/Lecturer.php | 36 +++ app/Models/Student.php | 38 +++ app/Models/User.php | 14 +- .../Admin/Master/DepartmentService.php | 38 +++ .../Admin/Users/AdministratorService.php | 86 +++++ .../Admin/Users/DepartmentService.php | 14 + app/Services/Admin/Users/LecturerService.php | 103 ++++++ app/Services/Admin/Users/StudentService.php | 102 ++++++ config/app.php | 1 + ...8_03_000002_create_user_profiles_table.php | 10 +- ..._08_03_000004_create_departments_table.php | 25 ++ ...26_08_03_000005_create_lecturers_table.php | 25 ++ ...06_create_department_leaderships_table.php | 25 ++ ...026_08_03_000007_create_students_table.php | 29 ++ database/seeders/DatabaseSeeder.php | 2 + .../seeders/DepartmentLeadershipSeeder.php | 39 +++ database/seeders/DepartmentSeeder.php | 36 +++ database/seeders/UserSeeder.php | 101 +++++- resources/js/components/app-sidebar.tsx | 179 ++++++----- .../admin/master/departments/columns.tsx | 87 +++++ .../pages/admin/master/departments/index.tsx | 298 ++++++++++++++++++ .../admin/users/administrators/columns.tsx | 92 ++++++ .../admin/users/administrators/create.tsx | 160 ++++++++++ .../pages/admin/users/administrators/edit.tsx | 171 ++++++++++ .../admin/users/administrators/index.tsx | 144 +++++++++ .../pages/admin/users/lecturers/columns.tsx | 84 +++++ .../js/pages/admin/users/lecturers/create.tsx | 174 ++++++++++ .../js/pages/admin/users/lecturers/edit.tsx | 184 +++++++++++ .../js/pages/admin/users/lecturers/index.tsx | 144 +++++++++ .../js/pages/admin/users/students/columns.tsx | 94 ++++++ .../js/pages/admin/users/students/create.tsx | 202 ++++++++++++ .../js/pages/admin/users/students/edit.tsx | 228 ++++++++++++++ .../js/pages/admin/users/students/index.tsx | 143 +++++++++ resources/js/pages/dashboard.tsx | 2 - resources/js/types/department.ts | 12 + resources/js/types/index.ts | 1 + resources/js/types/user.ts | 76 +++++ routes/admin.php | 22 +- 49 files changed, 3732 insertions(+), 89 deletions(-) create mode 100644 app/Enums/StudentStatus.php create mode 100644 app/Http/Controllers/Admin/Master/DepartmentController.php create mode 100644 app/Http/Controllers/Admin/Users/AdministratorController.php create mode 100644 app/Http/Controllers/Admin/Users/LecturerController.php create mode 100644 app/Http/Controllers/Admin/Users/StudentController.php create mode 100644 app/Http/Requests/Admin/Master/DepartmentRequest.php create mode 100644 app/Http/Requests/Admin/Users/AdministratorRequest.php create mode 100644 app/Http/Requests/Admin/Users/LecturerRequest.php create mode 100644 app/Http/Requests/Admin/Users/StudentRequest.php create mode 100644 app/Models/Department.php create mode 100644 app/Models/DepartmentLeadership.php create mode 100644 app/Models/Lecturer.php create mode 100644 app/Models/Student.php create mode 100644 app/Services/Admin/Master/DepartmentService.php create mode 100644 app/Services/Admin/Users/AdministratorService.php create mode 100644 app/Services/Admin/Users/DepartmentService.php create mode 100644 app/Services/Admin/Users/LecturerService.php create mode 100644 app/Services/Admin/Users/StudentService.php create mode 100644 database/migrations/2026_08_03_000004_create_departments_table.php create mode 100644 database/migrations/2026_08_03_000005_create_lecturers_table.php create mode 100644 database/migrations/2026_08_03_000006_create_department_leaderships_table.php create mode 100644 database/migrations/2026_08_03_000007_create_students_table.php create mode 100644 database/seeders/DepartmentLeadershipSeeder.php create mode 100644 database/seeders/DepartmentSeeder.php create mode 100644 resources/js/pages/admin/master/departments/columns.tsx create mode 100644 resources/js/pages/admin/master/departments/index.tsx create mode 100644 resources/js/pages/admin/users/administrators/columns.tsx create mode 100644 resources/js/pages/admin/users/administrators/create.tsx create mode 100644 resources/js/pages/admin/users/administrators/edit.tsx create mode 100644 resources/js/pages/admin/users/administrators/index.tsx create mode 100644 resources/js/pages/admin/users/lecturers/columns.tsx create mode 100644 resources/js/pages/admin/users/lecturers/create.tsx create mode 100644 resources/js/pages/admin/users/lecturers/edit.tsx create mode 100644 resources/js/pages/admin/users/lecturers/index.tsx create mode 100644 resources/js/pages/admin/users/students/columns.tsx create mode 100644 resources/js/pages/admin/users/students/create.tsx create mode 100644 resources/js/pages/admin/users/students/edit.tsx create mode 100644 resources/js/pages/admin/users/students/index.tsx create mode 100644 resources/js/types/department.ts create mode 100644 resources/js/types/user.ts diff --git a/app/Enums/StudentStatus.php b/app/Enums/StudentStatus.php new file mode 100644 index 0000000..0c16336 --- /dev/null +++ b/app/Enums/StudentStatus.php @@ -0,0 +1,21 @@ + 'Aktif', + self::OnLeave => 'Cuti', + self::Graduated => 'Lulus', + self::DroppedOut => 'Drop Out', + }; + } +} diff --git a/app/Http/Controllers/Admin/Master/DepartmentController.php b/app/Http/Controllers/Admin/Master/DepartmentController.php new file mode 100644 index 0000000..f4ee014 --- /dev/null +++ b/app/Http/Controllers/Admin/Master/DepartmentController.php @@ -0,0 +1,53 @@ + $this->service->paginated(...$request->validatedWithDefaults()), + ]); + } + + public function store(DepartmentRequest $request): RedirectResponse + { + $this->service->create($request->validated()); + + Inertia::flash('toast', ['type' => 'success', 'message' => 'Jurusan berhasil ditambahkan.']); + + return to_route('admin.master.departments.index'); + } + + public function update(DepartmentRequest $request, Department $department): RedirectResponse + { + $this->service->update($department, $request->validated()); + + Inertia::flash('toast', ['type' => 'success', 'message' => 'Jurusan berhasil diperbarui.']); + + return to_route('admin.master.departments.index'); + } + + public function destroy(Department $department): RedirectResponse + { + $this->service->delete($department); + + Inertia::flash('toast', ['type' => 'success', 'message' => 'Jurusan berhasil dihapus.']); + + return back(); + } +} diff --git a/app/Http/Controllers/Admin/Users/AdministratorController.php b/app/Http/Controllers/Admin/Users/AdministratorController.php new file mode 100644 index 0000000..99cf316 --- /dev/null +++ b/app/Http/Controllers/Admin/Users/AdministratorController.php @@ -0,0 +1,77 @@ + $this->service->getPaginated($request->only(['search', 'per_page'])), + 'filters' => $request->only(['search', 'per_page']), + ]); + } + + public function create(): Response + { + return Inertia::render('admin/users/administrators/create', [ + 'roles' => Role::whereIn('name', ['staff-admin', 'staff-keuangan'])->get(['id', 'name']), + ]); + } + + public function store(AdministratorRequest $request): RedirectResponse + { + $this->service->create($request->validated()); + + Inertia::flash('toast', ['type' => 'success', 'message' => 'Administrator berhasil ditambahkan.']); + + return to_route('admin.users.administrators.index'); + } + + public function edit(User $user): Response + { + $user->load(['profile', 'roles']); + + return Inertia::render('admin/users/administrators/edit', [ + 'user' => $user, + 'roles' => Role::whereIn('name', ['staff-admin', 'staff-keuangan'])->get(['id', 'name']), + ]); + } + + public function update(AdministratorRequest $request, User $user): RedirectResponse + { + $this->service->update($user, $request->validated()); + + Inertia::flash('toast', ['type' => 'success', 'message' => 'Administrator berhasil diperbarui.']); + + return to_route('admin.users.administrators.index'); + } + + public function destroy(User $user): RedirectResponse + { + $this->service->delete($user); + + return Inertia::flash('toast', ['type' => 'success', 'message' => 'Administrator berhasil dihapus.'])->back(); + } + + public function resetPassword(User $user): RedirectResponse + { + $this->service->resetPassword($user); + + return Inertia::flash('toast', ['type' => 'success', 'message' => 'Kata sandi berhasil direset.'])->back(); + } +} diff --git a/app/Http/Controllers/Admin/Users/LecturerController.php b/app/Http/Controllers/Admin/Users/LecturerController.php new file mode 100644 index 0000000..a4dca0e --- /dev/null +++ b/app/Http/Controllers/Admin/Users/LecturerController.php @@ -0,0 +1,78 @@ + $this->service->getPaginated($request->only(['search', 'per_page'])), + 'filters' => $request->only(['search', 'per_page']), + ]); + } + + public function create(): Response + { + return Inertia::render('admin/users/lecturers/create', [ + 'departments' => $this->departmentService->getAll(), + ]); + } + + public function store(LecturerRequest $request): RedirectResponse + { + $this->service->create($request->validated()); + + Inertia::flash('toast', ['type' => 'success', 'message' => 'Dosen berhasil ditambahkan.']); + + return to_route('admin.users.lecturers.index'); + } + + public function edit(User $user): Response + { + $user->load(['profile', 'lecturer.department']); + + return Inertia::render('admin/users/lecturers/edit', [ + 'user' => $user, + 'departments' => $this->departmentService->getAll(), + ]); + } + + public function update(LecturerRequest $request, User $user): RedirectResponse + { + $this->service->update($user, $request->validated()); + + Inertia::flash('toast', ['type' => 'success', 'message' => 'Dosen berhasil diperbarui.']); + + return to_route('admin.users.lecturers.index'); + } + + public function destroy(User $user): RedirectResponse + { + $this->service->delete($user); + + return Inertia::flash('toast', ['type' => 'success', 'message' => 'Dosen berhasil dihapus.'])->back(); + } + + public function resetPassword(User $user): RedirectResponse + { + $this->service->resetPassword($user); + + return Inertia::flash('toast', ['type' => 'success', 'message' => 'Kata sandi berhasil direset.'])->back(); + } +} diff --git a/app/Http/Controllers/Admin/Users/StudentController.php b/app/Http/Controllers/Admin/Users/StudentController.php new file mode 100644 index 0000000..47c54d4 --- /dev/null +++ b/app/Http/Controllers/Admin/Users/StudentController.php @@ -0,0 +1,82 @@ + $this->service->getPaginated($request->only(['search', 'per_page'])), + 'filters' => $request->only(['search', 'per_page']), + ]); + } + + public function create(): Response + { + return Inertia::render('admin/users/students/create', [ + 'departments' => $this->departmentService->getAll(), + 'lecturers' => $this->lecturerService->getAllForSelect(), + ]); + } + + public function store(StudentRequest $request): RedirectResponse + { + $this->service->create($request->validated()); + + Inertia::flash('toast', ['type' => 'success', 'message' => 'Mahasiswa berhasil ditambahkan.']); + + return to_route('admin.users.students.index'); + } + + public function edit(User $user): Response + { + $user->load(['profile', 'student.department', 'student.advisor.user.profile']); + + return Inertia::render('admin/users/students/edit', [ + 'user' => $user, + 'departments' => $this->departmentService->getAll(), + 'lecturers' => $this->lecturerService->getAllForSelect(), + ]); + } + + public function update(StudentRequest $request, User $user): RedirectResponse + { + $this->service->update($user, $request->validated()); + + Inertia::flash('toast', ['type' => 'success', 'message' => 'Mahasiswa berhasil diperbarui.']); + + return to_route('admin.users.students.index'); + } + + public function destroy(User $user): RedirectResponse + { + $this->service->delete($user); + + return Inertia::flash('toast', ['type' => 'success', 'message' => 'Mahasiswa berhasil dihapus.'])->back(); + } + + public function resetPassword(User $user): RedirectResponse + { + $this->service->resetPassword($user); + + return Inertia::flash('toast', ['type' => 'success', 'message' => 'Password berhasil direset.'])->back(); + } +} diff --git a/app/Http/Requests/Admin/Master/DepartmentRequest.php b/app/Http/Requests/Admin/Master/DepartmentRequest.php new file mode 100644 index 0000000..5df1d55 --- /dev/null +++ b/app/Http/Requests/Admin/Master/DepartmentRequest.php @@ -0,0 +1,28 @@ + [ + 'required', + 'string', + 'max:10', + Rule::unique('departments')->ignore($this->route('department')), + ], + 'name' => ['required', 'string', 'max:100'], + 'degree_level' => ['nullable', 'string', Rule::in(['D3', 'D4', 'S1', 'S2', 'S3'])], + ]; + } +} diff --git a/app/Http/Requests/Admin/Users/AdministratorRequest.php b/app/Http/Requests/Admin/Users/AdministratorRequest.php new file mode 100644 index 0000000..be5679f --- /dev/null +++ b/app/Http/Requests/Admin/Users/AdministratorRequest.php @@ -0,0 +1,51 @@ +route('user')?->id; + + return [ + 'username' => [ + 'required', + 'string', + 'max:20', + 'min:5', + 'alpha_dash', + $userId === null + ? Rule::unique('users') + : Rule::unique('users')->ignore($userId), + ], + 'email' => [ + 'required', + 'string', + 'email', + 'max:255', + $userId === null + ? Rule::unique('users') + : Rule::unique('users')->ignore($userId), + ], + 'role' => ['required', 'string', Rule::in(['staff-admin', 'staff-keuangan'])], + + // Profile + 'full_name' => ['required', 'string', 'max:150'], + 'phone_number' => ['required', 'string', 'max:20'], + 'address' => ['required', 'string'], + 'gender' => ['required', 'string', Rule::in(array_values(Gender::cases()))], + 'birth_date' => ['required', 'date'], + 'birth_place' => ['required', 'string', 'max:100'], + ]; + } +} diff --git a/app/Http/Requests/Admin/Users/LecturerRequest.php b/app/Http/Requests/Admin/Users/LecturerRequest.php new file mode 100644 index 0000000..240a4fc --- /dev/null +++ b/app/Http/Requests/Admin/Users/LecturerRequest.php @@ -0,0 +1,63 @@ +route('user')?->id; + + return [ + 'username' => [ + 'required', + 'string', + 'max:20', + 'min:5', + 'alpha_dash', + $userId === null + ? Rule::unique('users') + : Rule::unique('users')->ignore($userId), + ], + 'email' => [ + 'required', + 'string', + 'email', + 'max:255', + $userId === null + ? Rule::unique('users') + : Rule::unique('users')->ignore($userId), + ], + + // Profile + 'full_name' => ['required', 'string', 'max:150'], + 'phone_number' => ['required', 'string', 'max:20'], + 'address' => ['required', 'string'], + 'gender' => ['required', 'string', Rule::in(array_values(Gender::cases()))], + 'birth_date' => ['required', 'date'], + 'birth_place' => ['required', 'string', 'max:100'], + + // Lecturer fields + 'lecturer_number' => [ + 'required', + 'string', + 'max:20', + Rule::unique('lecturers')->ignore($userId, 'user_id'), + ], + 'department_id' => [ + 'required', + 'integer', + Rule::exists('departments', 'id'), + ], + ]; + } +} diff --git a/app/Http/Requests/Admin/Users/StudentRequest.php b/app/Http/Requests/Admin/Users/StudentRequest.php new file mode 100644 index 0000000..000306b --- /dev/null +++ b/app/Http/Requests/Admin/Users/StudentRequest.php @@ -0,0 +1,79 @@ +route('user')?->id; + + return [ + 'username' => [ + 'required', + 'string', + 'max:20', + 'min:5', + 'alpha_dash', + $userId === null + ? Rule::unique('users') + : Rule::unique('users')->ignore($userId), + ], + 'email' => [ + 'required', + 'string', + 'email', + 'max:255', + $userId === null + ? Rule::unique('users') + : Rule::unique('users')->ignore($userId), + ], + + // Profile + 'full_name' => ['required', 'string', 'max:150'], + 'phone_number' => ['required', 'string', 'max:20'], + 'address' => ['required', 'string'], + 'gender' => ['required', 'string', Rule::in(array_values(Gender::cases()))], + 'birth_date' => ['required', 'date'], + 'birth_place' => ['required', 'string', 'max:100'], + + // Student fields + 'student_number' => [ + 'required', + 'string', + 'max:10', + Rule::unique('students')->ignore($userId, 'user_id'), + ], + 'department_id' => [ + 'required', + 'integer', + Rule::exists('departments', 'id'), + ], + 'enrollment_year' => [ + 'required', + 'integer', + 'digits:4', + ], + 'academic_advisor_id' => [ + 'required', + 'integer', + Rule::exists('lecturers', 'id'), + ], + 'status' => [ + 'nullable', + 'string', + Rule::in(array_values(StudentStatus::cases())), + ], + ]; + } +} diff --git a/app/Models/Department.php b/app/Models/Department.php new file mode 100644 index 0000000..ed7cca5 --- /dev/null +++ b/app/Models/Department.php @@ -0,0 +1,36 @@ +hasMany(Lecturer::class); + } + + public function students(): HasMany + { + return $this->hasMany(Student::class); + } + + public function leaderships(): HasMany + { + return $this->hasMany(DepartmentLeadership::class); + } + + public function currentLeader(): HasOne + { + return $this->hasOne(DepartmentLeadership::class)->whereNull('ended_at'); + } +} diff --git a/app/Models/DepartmentLeadership.php b/app/Models/DepartmentLeadership.php new file mode 100644 index 0000000..a44b067 --- /dev/null +++ b/app/Models/DepartmentLeadership.php @@ -0,0 +1,32 @@ + 'date', + 'ended_at' => 'date', + ]; + } + + public function department(): BelongsTo + { + return $this->belongsTo(Department::class); + } + + public function lecturer(): BelongsTo + { + return $this->belongsTo(Lecturer::class); + } +} diff --git a/app/Models/Lecturer.php b/app/Models/Lecturer.php new file mode 100644 index 0000000..015c483 --- /dev/null +++ b/app/Models/Lecturer.php @@ -0,0 +1,36 @@ +belongsTo(User::class); + } + + public function department(): BelongsTo + { + return $this->belongsTo(Department::class); + } + + public function advisees(): HasMany + { + return $this->hasMany(Student::class, 'academic_advisor_id'); + } + + public function leaderships(): HasMany + { + return $this->hasMany(DepartmentLeadership::class); + } +} diff --git a/app/Models/Student.php b/app/Models/Student.php new file mode 100644 index 0000000..4197293 --- /dev/null +++ b/app/Models/Student.php @@ -0,0 +1,38 @@ + StudentStatus::class, + ]; + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + + public function department(): BelongsTo + { + return $this->belongsTo(Department::class); + } + + public function academicAdvisor(): BelongsTo + { + return $this->belongsTo(Lecturer::class, 'academic_advisor_id'); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index fb599d7..f95b5ea 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -32,13 +32,21 @@ protected function casts(): array protected function fullName(): Attribute { - return Attribute::get(function () { - return $this->profile?->full_name ?? $this->username; - }); + return Attribute::get(fn () => $this->profile?->full_name ?? $this->username); } public function profile(): HasOne { return $this->hasOne(UserProfile::class); } + + public function student(): HasOne + { + return $this->hasOne(Student::class); + } + + public function lecturer(): HasOne + { + return $this->hasOne(Lecturer::class); + } } diff --git a/app/Services/Admin/Master/DepartmentService.php b/app/Services/Admin/Master/DepartmentService.php new file mode 100644 index 0000000..7814ef4 --- /dev/null +++ b/app/Services/Admin/Master/DepartmentService.php @@ -0,0 +1,38 @@ +select(['id', 'code', 'name', 'degree_level']) + ->when($search, fn ($q) => $q->where('name', 'like', "%{$search}%")->orWhere('code', 'like', "%{$search}%")) + ->orderBy($sort, $direction) + ->paginate($perPage); + } + + public function create(array $data): Department + { + return Department::create($data); + } + + public function update(Department $department, array $data): Department + { + $department->code = $data['code']; + $department->name = $data['name']; + $department->degree_level = $data['degree_level'] ?? null; + $department->update(); + + return $department; + } + + public function delete(Department $department): bool + { + return $department->delete(); + } +} diff --git a/app/Services/Admin/Users/AdministratorService.php b/app/Services/Admin/Users/AdministratorService.php new file mode 100644 index 0000000..ec8fad2 --- /dev/null +++ b/app/Services/Admin/Users/AdministratorService.php @@ -0,0 +1,86 @@ +whereHas('roles', fn($q) => $q->whereIn('name', ['staff-admin', 'staff-keuangan'])) + ->when($filters['search'] ?? null, fn($q, $search) => $q->where(function ($query) use ($search) { + $query->where('username', 'like', "%{$search}%") + ->orWhere('email', 'like', "%{$search}%") + ->orWhereHas('profile', fn($q) => $q->where('full_name', 'like', "%{$search}%")); + })) + ->latest() + ->paginate($filters['per_page'] ?? 25); + } + + public function create(array $data): User + { + return DB::transaction(function () use ($data) { + $user = User::create([ + 'username' => $data['username'], + 'email' => $data['email'], + 'password' => Hash::make(config('app.default_password')), + ]); + + $user->assignRole($data['role']); + + $user->profile()->create([ + 'full_name' => $data['full_name'], + 'phone_number' => $data['phone_number'], + 'address' => $data['address'], + 'gender' => $data['gender'], + 'birth_date' => $data['birth_date'], + 'birth_place' => $data['birth_place'], + ]); + + return $user; + }); + } + + public function update(User $user, array $data): User + { + DB::transaction(function () use ($user, $data) { + $user->update([ + 'username' => $data['username'], + 'email' => $data['email'], + ]); + + $user->syncRoles($data['role']); + + $user->profile()->updateOrCreate([], [ + 'full_name' => $data['full_name'], + 'phone_number' => $data['phone_number'], + 'address' => $data['address'], + 'gender' => $data['gender'], + 'birth_date' => $data['birth_date'], + 'birth_place' => $data['birth_place'], + ]); + }); + + return $user->fresh(['profile', 'roles']); + } + + public function delete(User $user): void + { + DB::transaction(function () use ($user) { + $user->profile()->delete(); + $user->delete(); + }); + } + + public function resetPassword(User $user): void + { + $user->update([ + 'password' => Hash::make(config('app.default_password')), + ]); + } +} diff --git a/app/Services/Admin/Users/DepartmentService.php b/app/Services/Admin/Users/DepartmentService.php new file mode 100644 index 0000000..67cf55f --- /dev/null +++ b/app/Services/Admin/Users/DepartmentService.php @@ -0,0 +1,14 @@ +get(); + } +} diff --git a/app/Services/Admin/Users/LecturerService.php b/app/Services/Admin/Users/LecturerService.php new file mode 100644 index 0000000..adcbaf3 --- /dev/null +++ b/app/Services/Admin/Users/LecturerService.php @@ -0,0 +1,103 @@ +get(); + } + + public function getPaginated(array $filters = []): LengthAwarePaginator + { + return User::with(['profile', 'lecturer.department']) + ->whereHas('roles', fn($q) => $q->where('name', 'dosen')) + ->when($filters['search'] ?? null, fn($q, $search) => $q->where(function ($query) use ($search) { + $query->where('username', 'like', "%{$search}%") + ->orWhere('email', 'like', "%{$search}%") + ->orWhereHas('profile', fn($q) => $q->where('full_name', 'like', "%{$search}%")) + ->orWhereHas('lecturer', fn($q) => $q->where('lecturer_number', 'like', "%{$search}%")); + })) + ->latest() + ->paginate($filters['per_page'] ?? 25); + } + + public function create(array $data): User + { + return DB::transaction(function () use ($data) { + $user = User::create([ + 'username' => $data['username'], + 'email' => $data['email'], + 'password' => Hash::make(config('app.default_password')), + ]); + + $user->assignRole('dosen'); + + $user->profile()->create([ + 'full_name' => $data['full_name'], + 'phone_number' => $data['phone_number'], + 'address' => $data['address'], + 'gender' => $data['gender'], + 'birth_date' => $data['birth_date'], + 'birth_place' => $data['birth_place'], + ]); + + $user->lecturer()->create([ + 'lecturer_number' => $data['lecturer_number'], + 'department_id' => $data['department_id'], + ]); + + return $user; + }); + } + + public function update(User $user, array $data): User + { + DB::transaction(function () use ($user, $data) { + $user->update([ + 'username' => $data['username'], + 'email' => $data['email'], + ]); + + $user->profile()->updateOrCreate([], [ + 'full_name' => $data['full_name'], + 'phone_number' => $data['phone_number'], + 'address' => $data['address'], + 'gender' => $data['gender'], + 'birth_date' => $data['birth_date'], + 'birth_place' => $data['birth_place'], + ]); + + $user->lecturer()->updateOrCreate([], [ + 'lecturer_number' => $data['lecturer_number'], + 'department_id' => $data['department_id'], + ]); + }); + + return $user->fresh(['profile', 'lecturer.department', 'roles']); + } + + public function delete(User $user): void + { + DB::transaction(function () use ($user) { + $user->lecturer()->delete(); + $user->profile()->delete(); + $user->delete(); + }); + } + + public function resetPassword(User $user): void + { + $user->update([ + 'password' => Hash::make(config('app.default_password')), + ]); + } +} diff --git a/app/Services/Admin/Users/StudentService.php b/app/Services/Admin/Users/StudentService.php new file mode 100644 index 0000000..2a9d869 --- /dev/null +++ b/app/Services/Admin/Users/StudentService.php @@ -0,0 +1,102 @@ +whereHas('roles', fn($q) => $q->where('name', 'mahasiswa')) + ->when($filters['search'] ?? null, fn($q, $search) => $q->where(function ($query) use ($search) { + $query->where('username', 'like', "%{$search}%") + ->orWhere('email', 'like', "%{$search}%") + ->orWhereHas('profile', fn($q) => $q->where('full_name', 'like', "%{$search}%")) + ->orWhereHas('student', fn($q) => $q->where('student_number', 'like', "%{$search}%")); + })) + ->latest() + ->paginate($filters['per_page'] ?? 25); + } + + public function create(array $data): User + { + return DB::transaction(function () use ($data) { + $user = User::create([ + 'username' => $data['username'], + 'email' => $data['email'], + 'password' => Hash::make(config('app.default_password')), + ]); + + $user->assignRole('mahasiswa'); + + $user->profile()->create([ + 'full_name' => $data['full_name'], + 'phone_number' => $data['phone_number'], + 'address' => $data['address'], + 'gender' => $data['gender'], + 'birth_date' => $data['birth_date'], + 'birth_place' => $data['birth_place'], + ]); + + $user->student()->create([ + 'student_number' => $data['student_number'], + 'department_id' => $data['department_id'], + 'enrollment_year' => $data['enrollment_year'], + 'academic_advisor_id' => $data['academic_advisor_id'], + 'status' => 'active', + ]); + + return $user; + }); + } + + public function update(User $user, array $data): User + { + DB::transaction(function () use ($user, $data) { + $user->update([ + 'username' => $data['username'], + 'email' => $data['email'], + ]); + + $user->profile()->updateOrCreate([], [ + 'full_name' => $data['full_name'], + 'phone_number' => $data['phone_number'], + 'address' => $data['address'], + 'gender' => $data['gender'], + 'birth_date' => $data['birth_date'], + 'birth_place' => $data['birth_place'], + ]); + + $user->student()->updateOrCreate([], [ + 'student_number' => $data['student_number'], + 'department_id' => $data['department_id'], + 'enrollment_year' => $data['enrollment_year'], + 'academic_advisor_id' => $data['academic_advisor_id'], + 'status' => $data['status'] ?? null, + ]); + }); + + return $user->fresh(['profile', 'student.department', 'roles']); + } + + public function delete(User $user): void + { + DB::transaction(function () use ($user) { + $user->student()->delete(); + $user->profile()->delete(); + $user->delete(); + }); + } + + public function resetPassword(User $user): void + { + $user->update([ + 'password' => Hash::make(config('app.default_password')), + ]); + } +} diff --git a/config/app.php b/config/app.php index 1f8dd27..9023bfc 100644 --- a/config/app.php +++ b/config/app.php @@ -123,4 +123,5 @@ 'store' => env('APP_MAINTENANCE_STORE', 'database'), ], + 'default_password' => env('APP_DEFAULT_PASSWORD', 'Minimal8@'), ]; diff --git a/database/migrations/2026_08_03_000002_create_user_profiles_table.php b/database/migrations/2026_08_03_000002_create_user_profiles_table.php index 594f390..c83bb93 100644 --- a/database/migrations/2026_08_03_000002_create_user_profiles_table.php +++ b/database/migrations/2026_08_03_000002_create_user_profiles_table.php @@ -13,11 +13,11 @@ public function up(): void $table->id(); $table->foreignId('user_id')->constrained()->cascadeOnDelete(); $table->string('full_name', 150); - $table->string('phone_number', 20)->nullable(); - $table->text('address')->nullable(); - $table->enum('gender', array_values(Gender::cases()))->nullable(); - $table->string('birth_place', 100)->nullable(); - $table->date('birth_date')->nullable(); + $table->string('phone_number', 20); + $table->text('address'); + $table->enum('gender', array_values(Gender::cases())); + $table->date('birth_date'); + $table->string('birth_place', 100); $table->timestamps(); $table->softDeletes(); }); diff --git a/database/migrations/2026_08_03_000004_create_departments_table.php b/database/migrations/2026_08_03_000004_create_departments_table.php new file mode 100644 index 0000000..1d1f28c --- /dev/null +++ b/database/migrations/2026_08_03_000004_create_departments_table.php @@ -0,0 +1,25 @@ +id(); + $table->string('code', 10)->unique(); + $table->string('name', 100); + $table->string('degree_level', 10)->nullable()->default('S1'); + $table->timestamps(); + $table->softDeletes(); + }); + } + + public function down(): void + { + Schema::dropIfExists('departments'); + } +}; diff --git a/database/migrations/2026_08_03_000005_create_lecturers_table.php b/database/migrations/2026_08_03_000005_create_lecturers_table.php new file mode 100644 index 0000000..9b06c3b --- /dev/null +++ b/database/migrations/2026_08_03_000005_create_lecturers_table.php @@ -0,0 +1,25 @@ +id(); + $table->foreignId('user_id')->constrained()->cascadeOnDelete(); + $table->string('lecturer_number', 10)->unique(); + $table->foreignId('department_id')->nullable()->constrained()->nullOnDelete(); + $table->timestamps(); + $table->softDeletes(); + }); + } + + public function down(): void + { + Schema::dropIfExists('lecturers'); + } +}; diff --git a/database/migrations/2026_08_03_000006_create_department_leaderships_table.php b/database/migrations/2026_08_03_000006_create_department_leaderships_table.php new file mode 100644 index 0000000..569f801 --- /dev/null +++ b/database/migrations/2026_08_03_000006_create_department_leaderships_table.php @@ -0,0 +1,25 @@ +id(); + $table->foreignId('department_id')->constrained()->cascadeOnDelete(); + $table->foreignId('lecturer_id')->constrained()->cascadeOnDelete(); + $table->date('started_at'); + $table->date('ended_at')->nullable(); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('department_leaderships'); + } +}; diff --git a/database/migrations/2026_08_03_000007_create_students_table.php b/database/migrations/2026_08_03_000007_create_students_table.php new file mode 100644 index 0000000..ccc615b --- /dev/null +++ b/database/migrations/2026_08_03_000007_create_students_table.php @@ -0,0 +1,29 @@ +id(); + $table->foreignId('user_id')->constrained()->cascadeOnDelete(); + $table->string('student_number', 10)->unique(); + $table->foreignId('department_id')->constrained()->cascadeOnDelete(); + $table->integer('enrollment_year'); + $table->foreignId('academic_advisor_id')->nullable()->constrained('lecturers')->nullOnDelete(); + $table->enum('status', array_values(StudentStatus::cases()))->nullable()->default(StudentStatus::Active); + $table->timestamps(); + $table->softDeletes(); + }); + } + + public function down(): void + { + Schema::dropIfExists('students'); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 08b5556..5fb7cbf 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -13,8 +13,10 @@ public function run(): void { $this->call([ RolePermissionSeeder::class, + DepartmentSeeder::class, UserSeeder::class, AcademicTermSeeder::class, + DepartmentLeadershipSeeder::class, ]); } } diff --git a/database/seeders/DepartmentLeadershipSeeder.php b/database/seeders/DepartmentLeadershipSeeder.php new file mode 100644 index 0000000..9d234a1 --- /dev/null +++ b/database/seeders/DepartmentLeadershipSeeder.php @@ -0,0 +1,39 @@ + 1, + 'lecturer_id' => 1, + 'started_at' => '2024-01-10', + 'ended_at' => null, + 'created_at' => now(), + 'updated_at' => now(), + ], + [ + 'department_id' => 2, + 'lecturer_id' => 2, + 'started_at' => '2024-01-10', + 'ended_at' => null, + 'created_at' => now(), + 'updated_at' => now(), + ], + [ + 'department_id' => 3, + 'lecturer_id' => 3, + 'started_at' => '2023-06-01', + 'ended_at' => null, + 'created_at' => now(), + 'updated_at' => now(), + ], + ]); + } +} diff --git a/database/seeders/DepartmentSeeder.php b/database/seeders/DepartmentSeeder.php new file mode 100644 index 0000000..1ccd287 --- /dev/null +++ b/database/seeders/DepartmentSeeder.php @@ -0,0 +1,36 @@ + 'SI', + 'name' => 'Sistem Informasi', + 'degree_level' => 'S1', + 'created_at' => now(), + 'updated_at' => now(), + ], + [ + 'code' => 'TI', + 'name' => 'Teknik Industri', + 'degree_level' => 'S1', + 'created_at' => now(), + 'updated_at' => now(), + ], + [ + 'code' => 'BD', + 'name' => 'Bisnis Digital', + 'degree_level' => 'S1', + 'created_at' => now(), + 'updated_at' => now(), + ], + ]); + } +} diff --git a/database/seeders/UserSeeder.php b/database/seeders/UserSeeder.php index 261b296..6ac9969 100644 --- a/database/seeders/UserSeeder.php +++ b/database/seeders/UserSeeder.php @@ -2,19 +2,67 @@ namespace Database\Seeders; +use App\Enums\StudentStatus; use App\Models\User; use Illuminate\Database\Seeder; use Illuminate\Support\Facades\Hash; class UserSeeder extends Seeder { - /** - * Run the database seeds. - */ public function run(): void { - $users = [ + [ + 'username' => 'dwi', + 'email' => 'dwi@lecturer.itmpwk.ac.id', + 'roles' => ['dosen'], + 'profile' => [ + 'full_name' => 'Dwi Saputra', + 'phone_number' => '081234567801', + 'address' => 'Jl. Kampus No. 1, Purwakarta', + 'gender' => 'male', + 'birth_place' => 'Bandung', + 'birth_date' => '1985-03-15', + ], + 'lecturer' => [ + 'lecturer_number' => '0012345601', + 'department_id' => 1, + ], + ], + [ + 'username' => 'rina', + 'email' => 'rina@lecturer.itmpwk.ac.id', + 'roles' => ['dosen'], + 'profile' => [ + 'full_name' => 'Rina Marlina', + 'phone_number' => '081234567802', + 'address' => 'Jl. Kampus No. 1, Purwakarta', + 'gender' => 'female', + 'birth_place' => 'Jakarta', + 'birth_date' => '1987-07-20', + ], + 'lecturer' => [ + 'lecturer_number' => '0012345602', + 'department_id' => 2, + ], + ], + [ + 'username' => 'budi', + 'email' => 'budi@lecturer.itmpwk.ac.id', + 'roles' => ['dosen'], + 'profile' => [ + 'full_name' => 'Budi Hartono', + 'phone_number' => '081234567803', + 'address' => 'Jl. Kampus No. 1, Purwakarta', + 'gender' => 'male', + 'birth_place' => 'Surabaya', + 'birth_date' => '1983-11-10', + ], + 'lecturer' => [ + 'lecturer_number' => '0012345603', + 'department_id' => 3, + ], + ], [ 'username' => 'pangestu', 'email' => 'pangestu@student.itmpwk.ac.id', @@ -27,6 +75,13 @@ public function run(): void 'birth_place' => 'Subang', 'birth_date' => '2005-03-13', ], + 'student' => [ + 'student_number' => '23010001', + 'department_id' => 1, + 'enrollment_year' => 2023, + 'academic_advisor_id' => 1, + 'status' => StudentStatus::Active, + ], ], [ 'username' => 'doni', @@ -40,6 +95,13 @@ public function run(): void 'birth_place' => 'Purwakarta', 'birth_date' => '2004-10-30', ], + 'student' => [ + 'student_number' => '23020002', + 'department_id' => 2, + 'enrollment_year' => 2023, + 'academic_advisor_id' => 2, + 'status' => StudentStatus::Active, + ], ], [ 'username' => 'asep', @@ -53,6 +115,13 @@ public function run(): void 'birth_place' => 'Purwakarta', 'birth_date' => '2005-09-24', ], + 'student' => [ + 'student_number' => '24010003', + 'department_id' => 1, + 'enrollment_year' => 2024, + 'academic_advisor_id' => 1, + 'status' => StudentStatus::Active, + ], ], [ 'username' => 'komoala', @@ -66,19 +135,41 @@ public function run(): void 'birth_place' => 'Purwakarta', 'birth_date' => '2005-05-04', ], + 'student' => [ + 'student_number' => '24020004', + 'department_id' => 2, + 'enrollment_year' => 2024, + 'academic_advisor_id' => 2, + 'status' => StudentStatus::Active, + ], ], ]; foreach ($users as $userData) { $roles = $userData['roles']; $profile = $userData['profile']; + unset($userData['profile'], $userData['roles']); - $userData['password'] = Hash::make('Minimal8@'); + $student = $userData['student'] ?? null; + $lecturer = $userData['lecturer'] ?? null; + + unset($userData['profile'], $userData['student'], $userData['lecturer']); + + $userData['password'] = Hash::make(config('app.default_password')); $user = User::create($userData); $user->profile()->create($profile); + $user->assignRole($roles); + + if ($lecturer) { + $user->lecturer()->create($lecturer); + } + + if ($student) { + $user->student()->create($student); + } } } } diff --git a/resources/js/components/app-sidebar.tsx b/resources/js/components/app-sidebar.tsx index ab1d120..02176d8 100644 --- a/resources/js/components/app-sidebar.tsx +++ b/resources/js/components/app-sidebar.tsx @@ -1,82 +1,117 @@ -import { usePage } from "@inertiajs/react"; -import type { Icon } from "@tabler/icons-react"; +import { usePage } from '@inertiajs/react'; +import type { Icon } from '@tabler/icons-react'; import { - IconGridDots, - IconHelp, - IconMessageDots -} from "@tabler/icons-react"; -import * as React from "react"; + IconGridDots, + IconHelp, + IconMessageDots, + IconUsers, +} from '@tabler/icons-react'; +import * as React from 'react'; -import AppLogoIcon from "@/components/app-logo-icon"; -import { NavMain, type NavGroup, type NavItem } from "@/components/nav-main"; -import { NavSecondary } from "@/components/nav-secondary"; +import AppLogoIcon from '@/components/app-logo-icon'; +import { NavMain, type NavGroup, type NavItem } from '@/components/nav-main'; +import { NavSecondary } from '@/components/nav-secondary'; import { - Sidebar, - SidebarContent, - SidebarHeader, - SidebarMenu, - SidebarMenuButton, - SidebarMenuItem, -} from "@/components/ui/sidebar"; -import { index as academicTerm } from "@/routes/admin/master/academic-terms"; -import { Calendar } from "lucide-react"; + Sidebar, + SidebarContent, + SidebarHeader, + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, +} from '@/components/ui/sidebar'; +import { index as academicTerm } from '@/routes/admin/master/academic-terms'; +import { index as departmentsRoute } from '@/routes/admin/master/departments'; +import { index as lecturersRoute } from '@/routes/admin/users/lecturers'; +import { index as studentsRoute } from '@/routes/admin/users/students'; +import { index as administratorsRoute } from '@/routes/admin/users/administrators'; +import { Building2, Calendar, GraduationCap, User, Users } from 'lucide-react'; -const data: { navMain: (NavGroup | NavItem)[]; navSecondary: { title: string; url: string; icon: Icon }[] } = { - navMain: [ - { - name: "Dasbor", - url: "#", - icon: IconGridDots, - }, - { - label: "Master", - items: [ +const data: { + navMain: (NavGroup | NavItem)[]; + navSecondary: { title: string; url: string; icon: Icon }[]; +} = { + navMain: [ { - name: "Periode Akademik", - url: academicTerm.url(), - icon: Calendar, + name: 'Dasbor', + url: '#', + icon: IconGridDots, }, - ], - }, - ], - navSecondary: [ - { - title: "Kritik dan Saran", - url: "#", - icon: IconMessageDots, - }, - { - title: "Bantuan", - url: "#", - icon: IconHelp, - }, - ], -} + { + label: 'Master', + items: [ + { + name: 'Periode Akademik', + url: academicTerm.url(), + icon: Calendar, + }, + { + name: 'Jurusan', + url: departmentsRoute.url(), + icon: Building2, + }, + ], + }, + { + label: 'Pengguna', + items: [ + { + name: 'Administrator', + url: administratorsRoute.url(), + icon: User, + }, + { + name: 'Dosen', + url: lecturersRoute.url(), + icon: Users, + }, + { + name: 'Mahasiswa', + url: studentsRoute.url(), + icon: GraduationCap, + }, + ], + }, + ], + navSecondary: [ + { + title: 'Kritik dan Saran', + url: '#', + icon: IconMessageDots, + }, + { + title: 'Bantuan', + url: '#', + icon: IconHelp, + }, + ], +}; export function AppSidebar({ ...props }: React.ComponentProps) { - const { name } = usePage().props; + const { name } = usePage().props; - return ( - - - - - - - - {name} - - - - - - - - - - - ) + return ( + + + + + + + + + {name} + + + + + + + + + + + + ); } diff --git a/resources/js/pages/admin/master/departments/columns.tsx b/resources/js/pages/admin/master/departments/columns.tsx new file mode 100644 index 0000000..2b72cae --- /dev/null +++ b/resources/js/pages/admin/master/departments/columns.tsx @@ -0,0 +1,87 @@ +import type { ColumnDef } from '@tanstack/react-table'; +import { Pencil, Trash2 } from 'lucide-react'; +import { RowActions } from '@/components/row-actions'; +import { Badge } from '@/components/ui/badge'; +import type { Department } from '@/types/department'; + +export type { Department } from '@/types/department'; + +type CreateColumnsParams = { + handleEdit: (department: Department) => void; + handleDeleteClick: (department: Department) => void; +}; + +export function createDepartmentColumns( + params: CreateColumnsParams, +): ColumnDef[] { + const { handleEdit, handleDeleteClick } = params; + + return [ + { + accessorKey: 'code', + header: () => Kode, + cell: ({ row }) => ( + + {row.getValue('code') as string} + + ), + }, + { + accessorKey: 'name', + header: () => Nama, + cell: ({ row }) => {row.getValue('name') as string}, + }, + { + accessorKey: 'degree_level', + header: () => Jenjang, + meta: { + className: 'w-[100px] text-center', + headerClassName: 'w-[100px] text-center', + }, + cell: ({ row }) => { + const degreeLevel = row.getValue('degree_level') as + string | null; + + return ( +
+ {degreeLevel ? ( + {degreeLevel} + ) : ( + - + )} +
+ ); + }, + }, + { + id: 'actions', + header: () => Aksi, + meta: { + className: 'w-[100px] text-center', + headerClassName: 'w-[100px] text-center', + }, + cell: ({ row }) => { + const department = row.original; + + return ( + , + onClick: () => handleEdit(department), + }, + { + label: 'Hapus', + icon: ( + + ), + onClick: () => handleDeleteClick(department), + }, + ]} + /> + ); + }, + }, + ]; +} diff --git a/resources/js/pages/admin/master/departments/index.tsx b/resources/js/pages/admin/master/departments/index.tsx new file mode 100644 index 0000000..5bd8af0 --- /dev/null +++ b/resources/js/pages/admin/master/departments/index.tsx @@ -0,0 +1,298 @@ +import { Head, router } from '@inertiajs/react'; +import { Plus } from 'lucide-react'; +import { useState } from 'react'; +import type { PaginationState } from '@/components/data-table'; +import { DataTable } from '@/components/data-table'; +import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog'; +import { FormDialog } from '@/components/form-dialog'; +import InputError from '@/components/input-error'; +import { PageHeader } from '@/components/page-header'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { useServerTable } from '@/hooks/use-server-table'; +import { + index as departmentIndex, + destroy, + store, + update, +} from '@/routes/admin/master/departments'; +import { DegreeLevels } from '@/types/department'; +import type { Department } from '@/types/department'; +import { createDepartmentColumns } from './columns'; + +type Props = { + departments: { + data: Department[]; + current_page: number; + last_page: number; + per_page: number; + total: number; + }; + highlight?: number; +}; + +export default function DepartmentIndex({ departments, highlight }: Props) { + const [createOpen, setCreateOpen] = useState(false); + const [editing, setEditing] = useState(null); + const [deleting, setDeleting] = useState(null); + + const pagination: PaginationState = { + current_page: departments.current_page, + last_page: departments.last_page, + per_page: departments.per_page, + total: departments.total, + }; + + const { + search, + handlePageChange, + handlePerPageChange, + handleSearchChange, + } = useServerTable({ + route: () => departmentIndex.url(), + pagination, + }); + + function handleDelete() { + if (!deleting) { + return; + } + + router.delete(destroy(deleting.id), { + onSuccess: () => setDeleting(null), + }); + } + + const columns = createDepartmentColumns({ + handleEdit: (department) => setEditing(department), + handleDeleteClick: (department) => setDeleting(department), + }); + + return ( + <> + + +
+ + Menampilkan jurusan dari notifikasi. + +

+ ) + } + actions={ + + + } + /> + + + + { + if (!open) { + setEditing(null); + } + }} + editing={editing} + /> + + + + { + if (!open) { + setDeleting(null); + } + }} + title="Hapus Jurusan" + description={(department) => + `Apakah Anda yakin ingin menghapus jurusan "${department.name}"? Tindakan ini tidak dapat dibatalkan.` + } + onConfirm={handleDelete} + /> +
+ + ); +} + +function CreateForm({ + open, + onOpenChange, +}: { + open: boolean; + onOpenChange: (open: boolean) => void; +}) { + return ( + onOpenChange(false)} + > + {({ errors }) => ( +
+
+ + + +
+
+ + + +
+
+ + + + +
+
+ )} +
+ ); +} + +function EditForm({ + open, + onOpenChange, + editing, +}: { + open: boolean; + onOpenChange: (open: boolean) => void; + editing: Department | null; +}) { + return ( + onOpenChange(false)} + > + {({ errors }) => + editing && ( +
+
+ + + +
+
+ + + +
+
+ + + + +
+
+ ) + } +
+ ); +} diff --git a/resources/js/pages/admin/users/administrators/columns.tsx b/resources/js/pages/admin/users/administrators/columns.tsx new file mode 100644 index 0000000..6c95a87 --- /dev/null +++ b/resources/js/pages/admin/users/administrators/columns.tsx @@ -0,0 +1,92 @@ +import { RowActions } from '@/components/row-actions'; +import type { ColumnDef } from '@tanstack/react-table'; +import { Key, Pencil, Trash2 } from 'lucide-react'; + +export type Administrator = { + id: number; + username: string; + email: string; + profile: { full_name: string; phone_number: string; gender: string } | null; + roles: { id: number; name: string }[]; +}; + +type CreateColumnsParams = { + handleEdit: (admin: Administrator) => void; + handleDeleteClick: (admin: Administrator) => void; + handleResetPassword: (admin: Administrator) => void; +}; + +export function createAdministratorColumns( + params: CreateColumnsParams, +): ColumnDef[] { + const { handleEdit, handleDeleteClick, handleResetPassword } = params; + + return [ + { + accessorKey: 'profile.full_name', + header: () => Nama Lengkap, + cell: ({ row }) => row.original.profile?.full_name ?? '-', + }, + { + id: 'account', + header: () => Akun, + cell: ({ row }) => ( +
+ {row.original.username} + {row.original.email} +
+ ), + }, + { + id: 'role', + header: () => Role, + cell: ({ row }) => row.original.roles?.[0]?.name ?? '-', + }, + { + accessorKey: 'profile.phone_number', + header: () => Nomor Telepon, + cell: ({ row }) => row.original.profile?.phone_number ?? '-', + }, + { + accessorKey: 'profile.gender', + header: () => Jenis Kelamin, + cell: ({ row }) => { + const gender = row.original.profile?.gender; + return gender === 'male' ? 'Laki-laki' : gender === 'female' ? 'Perempuan' : '-'; + }, + }, + { + id: 'actions', + header: () => Aksi, + meta: { + className: 'w-[100px] text-center', + headerClassName: 'w-[100px] text-center', + }, + cell: ({ row }) => { + const admin = row.original; + + return ( + , + onClick: () => handleEdit(admin), + }, + { + label: 'Reset Kata Sandi', + icon: , + onClick: () => handleResetPassword(admin), + }, + { + label: 'Hapus', + icon: , + onClick: () => handleDeleteClick(admin), + }, + ]} + /> + ); + }, + }, + ]; +} diff --git a/resources/js/pages/admin/users/administrators/create.tsx b/resources/js/pages/admin/users/administrators/create.tsx new file mode 100644 index 0000000..178d21e --- /dev/null +++ b/resources/js/pages/admin/users/administrators/create.tsx @@ -0,0 +1,160 @@ +import { DatePicker } from '@/components/date-picker'; +import InputError from '@/components/input-error'; +import { PageHeader } from '@/components/page-header'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Combobox, ComboboxContent, ComboboxInput, ComboboxItem, ComboboxList } from '@/components/ui/combobox'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { PhoneInput } from '@/components/ui/phone-input'; +import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; +import { Textarea } from '@/components/ui/textarea'; +import { index, store } from '@/routes/admin/users/administrators'; +import { Form, Head } from '@inertiajs/react'; +import { format } from 'date-fns'; +import { useState } from 'react'; + +type Role = { id: number; name: string }; + +type Props = { + roles: Role[]; +}; + +export default function AdministratorCreate({ roles }: Props) { + const [gender, setGender] = useState(''); + const [birthDate, setBirthDate] = useState(); + const [role, setRole] = useState(''); + + return ( + <> + + +
+ + Kembali + + } + /> + +
+ {({ errors, processing }) => ( + <> + + + Informasi Akun + + +
+ + + +
+
+ + + +
+
+ + + setRole(v ?? '')}> + + + + {roles.map((r) => ( + + {r.name} + + ))} + + + + +
+
+
+ + + + Profil Pribadi + + +
+ + + +
+
+ + + +
+
+
+ + + +
+ + +
+
+ + +
+
+ +
+
+ + + +
+
+ + + + +
+
+
+ +