Refactor lecturer management to support multiple departments
Some checks failed
tests / ci (pull_request) Has been cancelled
Some checks failed
tests / ci (pull_request) Has been cancelled
- Updated LecturerController to load multiple departments for lecturers. - Modified LecturerRequest to accept an array of department IDs instead of a single department ID. - Changed Department model to establish a many-to-many relationship with Lecturer. - Adjusted Lecturer model to reflect the new many-to-many relationship with Department. - Updated LecturerService to handle multiple departments during creation and updates. - Created LecturerFactory and StudentFactory for generating test data. - Added a migration for the new lecturer_department pivot table. - Refactored seeder classes to accommodate the new structure for lecturers and departments. - Enhanced frontend components for creating and editing lecturers to support multiple department selection.
This commit is contained in:
parent
bf6f4b19b7
commit
07a00a92a2
@ -58,7 +58,7 @@ public function map($user): array
|
||||
$user->username,
|
||||
$user->email,
|
||||
$user->profile?->phone_number ?? '-',
|
||||
$user->lecturer?->department?->name ?? '-',
|
||||
$user->lecturer?->departments->pluck('name')->join(', ') ?: '-',
|
||||
$user->profile?->gender?->label() ?? '-',
|
||||
$user->profile?->birth_place ?? '-',
|
||||
$user->profile?->birth_date?->format('d M Y') ?? '-',
|
||||
|
||||
@ -83,11 +83,11 @@ private function studentRoleData(User $user): array
|
||||
|
||||
private function lecturerRoleData(User $user): array
|
||||
{
|
||||
$user->load(['profile', 'lecturer.department']);
|
||||
$user->load(['profile', 'lecturer.departments']);
|
||||
|
||||
return ['dosen', $user->lecturer ? [
|
||||
'lecturer_number' => $user->lecturer->lecturer_number,
|
||||
'department' => $user->lecturer->department?->name,
|
||||
'department' => $user->lecturer->departments->pluck('name')->join(', ') ?: null,
|
||||
] : null];
|
||||
}
|
||||
|
||||
|
||||
@ -54,7 +54,7 @@ public function store(LecturerRequest $request): RedirectResponse
|
||||
|
||||
public function edit(User $user): Response
|
||||
{
|
||||
$user->load(['profile', 'lecturer.department']);
|
||||
$user->load(['profile', 'lecturer.departments']);
|
||||
|
||||
return Inertia::render('admin/users/lecturers/edit', [
|
||||
'user' => $user,
|
||||
|
||||
@ -53,11 +53,8 @@ public function rules(): array
|
||||
'max:20',
|
||||
Rule::unique('lecturers')->ignore($userId, 'user_id'),
|
||||
],
|
||||
'department_id' => [
|
||||
'required',
|
||||
'integer',
|
||||
Rule::exists('departments', 'id'),
|
||||
],
|
||||
'department_ids' => ['required', 'array', 'min:1'],
|
||||
'department_ids.*' => ['integer', Rule::exists('departments', 'id')],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
use Illuminate\Database\Eloquent\Attributes\Guarded;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
@ -14,9 +15,9 @@ class Department extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
public function lecturers(): HasMany
|
||||
public function lecturers(): BelongsToMany
|
||||
{
|
||||
return $this->hasMany(Lecturer::class);
|
||||
return $this->belongsToMany(Lecturer::class, 'lecturer_department');
|
||||
}
|
||||
|
||||
public function students(): HasMany
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
@ -19,9 +20,9 @@ public function user(): BelongsTo
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function department(): BelongsTo
|
||||
public function departments(): BelongsToMany
|
||||
{
|
||||
return $this->belongsTo(Department::class);
|
||||
return $this->belongsToMany(Department::class, 'lecturer_department');
|
||||
}
|
||||
|
||||
public function advisees(): HasMany
|
||||
|
||||
@ -41,8 +41,8 @@ private function filteredQuery(string $search, ?string $gender, ?int $department
|
||||
return User::select(['id', 'username', 'email', 'is_active'])
|
||||
->with([
|
||||
'profile:id,user_id,full_name,phone_number,gender,birth_place,birth_date,address',
|
||||
'lecturer:id,user_id,lecturer_number,department_id',
|
||||
'lecturer.department:id,name',
|
||||
'lecturer:id,user_id,lecturer_number',
|
||||
'lecturer.departments:id,name',
|
||||
])
|
||||
->whereHas('roles', fn ($q) => $q->where('name', 'dosen'))
|
||||
->when($search, fn ($q) => $q->where(function ($query) use ($search) {
|
||||
@ -52,7 +52,7 @@ private function filteredQuery(string $search, ?string $gender, ?int $department
|
||||
->orWhereHas('lecturer', fn ($q) => $q->where('lecturer_number', 'like', "%{$search}%"));
|
||||
}))
|
||||
->when($gender, fn ($q) => $q->whereHas('profile', fn ($q) => $q->where('gender', $gender)))
|
||||
->when($departmentId, fn ($q) => $q->whereHas('lecturer', fn ($q) => $q->where('department_id', $departmentId)));
|
||||
->when($departmentId, fn ($q) => $q->whereHas('lecturer.departments', fn ($q) => $q->where('departments.id', $departmentId)));
|
||||
}
|
||||
|
||||
public function create(array $data): User
|
||||
@ -75,11 +75,12 @@ public function create(array $data): User
|
||||
'birth_place' => $data['birth_place'],
|
||||
]);
|
||||
|
||||
$user->lecturer()->create([
|
||||
$lecturer = $user->lecturer()->create([
|
||||
'lecturer_number' => $data['lecturer_number'],
|
||||
'department_id' => $data['department_id'],
|
||||
]);
|
||||
|
||||
$lecturer->departments()->sync($data['department_ids']);
|
||||
|
||||
return $user;
|
||||
});
|
||||
}
|
||||
@ -101,13 +102,14 @@ public function update(User $user, array $data): User
|
||||
'birth_place' => $data['birth_place'],
|
||||
]);
|
||||
|
||||
$user->lecturer()->updateOrCreate([], [
|
||||
$lecturer = $user->lecturer()->updateOrCreate([], [
|
||||
'lecturer_number' => $data['lecturer_number'],
|
||||
'department_id' => $data['department_id'],
|
||||
]);
|
||||
|
||||
$lecturer->departments()->sync($data['department_ids']);
|
||||
});
|
||||
|
||||
return $user->fresh(['profile', 'lecturer.department', 'roles']);
|
||||
return $user->fresh(['profile', 'lecturer.departments', 'roles']);
|
||||
}
|
||||
|
||||
public function delete(User $user): void
|
||||
|
||||
21
database/factories/LecturerFactory.php
Normal file
21
database/factories/LecturerFactory.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\Lecturer;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends Factory<Lecturer>
|
||||
*/
|
||||
class LecturerFactory extends Factory
|
||||
{
|
||||
protected $model = Lecturer::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'lecturer_number' => fake()->unique()->numerify('##########'),
|
||||
];
|
||||
}
|
||||
}
|
||||
27
database/factories/StudentFactory.php
Normal file
27
database/factories/StudentFactory.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Enums\StudentStatus;
|
||||
use App\Models\Department;
|
||||
use App\Models\Student;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends Factory<Student>
|
||||
*/
|
||||
class StudentFactory extends Factory
|
||||
{
|
||||
protected $model = Student::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'student_number' => fake()->unique()->numerify('########'),
|
||||
'department_id' => fn () => Department::query()->inRandomOrder()->value('id'),
|
||||
'enrollment_year' => fake()->numberBetween(now()->year - 4, now()->year),
|
||||
'academic_advisor_id' => null,
|
||||
'status' => StudentStatus::Active,
|
||||
];
|
||||
}
|
||||
}
|
||||
30
database/factories/UserProfileFactory.php
Normal file
30
database/factories/UserProfileFactory.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Enums\Gender;
|
||||
use App\Models\UserProfile;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends Factory<UserProfile>
|
||||
*/
|
||||
class UserProfileFactory extends Factory
|
||||
{
|
||||
protected $model = UserProfile::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
$gender = fake()->randomElement(Gender::cases());
|
||||
$faker = fake('id_ID');
|
||||
|
||||
return [
|
||||
'full_name' => $faker->name($gender === Gender::Male ? 'male' : 'female'),
|
||||
'phone_number' => '08'.fake()->numerify('##########'),
|
||||
'address' => $faker->address(),
|
||||
'gender' => $gender,
|
||||
'birth_date' => fake()->dateTimeBetween('-55 years', '-18 years')->format('Y-m-d'),
|
||||
'birth_place' => $faker->city(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -12,7 +12,6 @@ public function up(): void
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
|
||||
$table->string('lecturer_number', 10)->unique();
|
||||
$table->foreignId('department_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('lecturer_department', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('lecturer_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('department_id')->constrained()->cascadeOnDelete();
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['lecturer_id', 'department_id']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('lecturer_department');
|
||||
}
|
||||
};
|
||||
@ -14,8 +14,8 @@ public function run(): void
|
||||
$this->call([
|
||||
RolePermissionSeeder::class,
|
||||
DepartmentSeeder::class,
|
||||
UserSeeder::class,
|
||||
AcademicTermSeeder::class,
|
||||
UserSeeder::class,
|
||||
DepartmentLeadershipSeeder::class,
|
||||
CourseSeeder::class,
|
||||
CourseClassSeeder::class,
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\Department;
|
||||
use App\Models\DepartmentLeadership;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
@ -9,31 +10,21 @@ class DepartmentLeadershipSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
DepartmentLeadership::insert([
|
||||
[
|
||||
'department_id' => 1,
|
||||
'lecturer_id' => 1,
|
||||
'started_at' => '2024-01-10',
|
||||
Department::all()->each(function (Department $department) {
|
||||
$lecturer = $department->lecturers()
|
||||
->whereHas('user.roles', fn ($q) => $q->where('name', 'kaprodi'))
|
||||
->first();
|
||||
|
||||
if (! $lecturer) {
|
||||
return;
|
||||
}
|
||||
|
||||
DepartmentLeadership::create([
|
||||
'department_id' => $department->id,
|
||||
'lecturer_id' => $lecturer->id,
|
||||
'started_at' => now()->subYear(),
|
||||
'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(),
|
||||
],
|
||||
]);
|
||||
]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,8 +2,14 @@
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Enums\Semester;
|
||||
use App\Enums\StudentStatus;
|
||||
use App\Models\AcademicTerm;
|
||||
use App\Models\Department;
|
||||
use App\Models\Lecturer;
|
||||
use App\Models\Student;
|
||||
use App\Models\User;
|
||||
use App\Models\UserProfile;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
@ -12,57 +18,6 @@ class UserSeeder extends Seeder
|
||||
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',
|
||||
@ -79,7 +34,7 @@ public function run(): void
|
||||
'student_number' => '23010001',
|
||||
'department_id' => 1,
|
||||
'enrollment_year' => 2023,
|
||||
'academic_advisor_id' => 1,
|
||||
'academic_advisor_id' => null,
|
||||
'status' => StudentStatus::Active,
|
||||
],
|
||||
],
|
||||
@ -97,9 +52,9 @@ public function run(): void
|
||||
],
|
||||
'student' => [
|
||||
'student_number' => '23020002',
|
||||
'department_id' => 2,
|
||||
'department_id' => 1,
|
||||
'enrollment_year' => 2023,
|
||||
'academic_advisor_id' => 2,
|
||||
'academic_advisor_id' => null,
|
||||
'status' => StudentStatus::Active,
|
||||
],
|
||||
],
|
||||
@ -119,7 +74,7 @@ public function run(): void
|
||||
'student_number' => '24010003',
|
||||
'department_id' => 1,
|
||||
'enrollment_year' => 2024,
|
||||
'academic_advisor_id' => 1,
|
||||
'academic_advisor_id' => null,
|
||||
'status' => StudentStatus::Active,
|
||||
],
|
||||
],
|
||||
@ -137,9 +92,9 @@ public function run(): void
|
||||
],
|
||||
'student' => [
|
||||
'student_number' => '24020004',
|
||||
'department_id' => 2,
|
||||
'department_id' => 1,
|
||||
'enrollment_year' => 2024,
|
||||
'academic_advisor_id' => 2,
|
||||
'academic_advisor_id' => null,
|
||||
'status' => StudentStatus::Active,
|
||||
],
|
||||
],
|
||||
@ -164,12 +119,134 @@ public function run(): void
|
||||
$user->assignRole($roles);
|
||||
|
||||
if ($lecturer) {
|
||||
$user->lecturer()->create($lecturer);
|
||||
$departmentId = $lecturer['department_id'];
|
||||
unset($lecturer['department_id']);
|
||||
|
||||
$createdLecturer = $user->lecturer()->create($lecturer);
|
||||
$createdLecturer->departments()->attach($departmentId);
|
||||
}
|
||||
|
||||
if ($student) {
|
||||
$user->student()->create($student);
|
||||
}
|
||||
}
|
||||
|
||||
$this->seedRandomLecturers();
|
||||
$this->seedRandomStudents();
|
||||
$this->seedOtherStaff();
|
||||
}
|
||||
|
||||
/**
|
||||
* 5-8 dosen acak per jurusan. Sebagian dosen diafiliasikan ke lebih dari
|
||||
* satu jurusan lewat pivot lecturer_department, sebagian cukup 1 jurusan.
|
||||
*/
|
||||
private function seedRandomLecturers(): void
|
||||
{
|
||||
$departments = Department::all();
|
||||
|
||||
foreach ($departments as $department) {
|
||||
$count = fake()->numberBetween(5, 8);
|
||||
$lecturers = collect();
|
||||
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$lecturer = $this->createLecturer($department);
|
||||
$lecturers->push($lecturer);
|
||||
|
||||
if (fake()->boolean(35)) {
|
||||
$otherDepartmentIds = Department::query()
|
||||
->where('id', '!=', $department->id)
|
||||
->inRandomOrder()
|
||||
->limit(fake()->numberBetween(1, 2))
|
||||
->pluck('id');
|
||||
|
||||
$lecturer->departments()->attach($otherDepartmentIds);
|
||||
}
|
||||
}
|
||||
|
||||
$lecturers->random()->user->assignRole('kaprodi');
|
||||
}
|
||||
}
|
||||
|
||||
private function createLecturer(Department $department): Lecturer
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$user->profile()->save(UserProfile::factory()->make());
|
||||
$user->assignRole('dosen');
|
||||
|
||||
$lecturer = $user->lecturer()->save(Lecturer::factory()->makeOne());
|
||||
|
||||
$lecturer->departments()->attach($department->id);
|
||||
|
||||
return $lecturer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 8-10 mahasiswa acak per jurusan untuk setiap periode akademik, supaya
|
||||
* enrollment_year-nya mengikuti tahun ajaran yang benar-benar ada.
|
||||
*/
|
||||
private function seedRandomStudents(): void
|
||||
{
|
||||
$departments = Department::all();
|
||||
|
||||
foreach (AcademicTerm::all() as $term) {
|
||||
[$startYear, $endYear] = explode('/', $term->name);
|
||||
$enrollmentYear = $term->semester === Semester::even ? (int) $endYear : (int) $startYear;
|
||||
|
||||
foreach ($departments as $department) {
|
||||
$count = fake()->numberBetween(8, 10);
|
||||
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$this->createStudent($department, $enrollmentYear);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function createStudent(Department $department, int $enrollmentYear): Student
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$user->profile()->save(UserProfile::factory()->make());
|
||||
$user->assignRole('mahasiswa');
|
||||
|
||||
$advisorId = Lecturer::query()
|
||||
->whereRelation('departments', 'departments.id', $department->id)
|
||||
->inRandomOrder()
|
||||
->value('id');
|
||||
|
||||
$status = fake()->randomElement([
|
||||
StudentStatus::Active,
|
||||
StudentStatus::Active,
|
||||
StudentStatus::Active,
|
||||
StudentStatus::OnLeave,
|
||||
StudentStatus::Graduated,
|
||||
StudentStatus::DroppedOut,
|
||||
]);
|
||||
|
||||
return $user->student()->save(Student::factory()->makeOne([
|
||||
'department_id' => $department->id,
|
||||
'enrollment_year' => $enrollmentYear,
|
||||
'academic_advisor_id' => $advisorId,
|
||||
'status' => $status,
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sejumlah kecil akun staf lain supaya tiap role punya data acak juga.
|
||||
*/
|
||||
private function seedOtherStaff(): void
|
||||
{
|
||||
$roleCounts = [
|
||||
'staff-admin' => 2,
|
||||
'staff-keuangan' => 2,
|
||||
'developer' => 2,
|
||||
];
|
||||
|
||||
foreach ($roleCounts as $role => $count) {
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$user = User::factory()->create();
|
||||
$user->profile()->save(UserProfile::factory()->make());
|
||||
$user->assignRole($role);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@ export type Lecturer = {
|
||||
profile: { full_name: string; phone_number: string; gender: string } | null;
|
||||
lecturer: {
|
||||
lecturer_number: string;
|
||||
department: { name: string } | null;
|
||||
departments: { id: number; name: string }[];
|
||||
} | null;
|
||||
};
|
||||
|
||||
@ -59,9 +59,17 @@ export function createLecturerColumns(
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'lecturer.department.name',
|
||||
id: 'departments',
|
||||
header: () => <span>Jurusan</span>,
|
||||
cell: ({ row }) => row.original.lecturer?.department?.name ?? '-',
|
||||
cell: ({ row }) => {
|
||||
const departments = row.original.lecturer?.departments ?? [];
|
||||
|
||||
return departments.length > 0
|
||||
? departments
|
||||
.map((department) => department.name)
|
||||
.join(', ')
|
||||
: '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'profile.phone_number',
|
||||
|
||||
@ -7,11 +7,21 @@ 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,
|
||||
ComboboxChip,
|
||||
ComboboxChips,
|
||||
ComboboxChipsInput,
|
||||
ComboboxContent,
|
||||
ComboboxEmpty,
|
||||
ComboboxItem,
|
||||
ComboboxList,
|
||||
useComboboxAnchor,
|
||||
} 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 { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { index, store } from '@/routes/admin/users/lecturers';
|
||||
|
||||
@ -24,6 +34,10 @@ type Props = {
|
||||
export default function LecturerCreate({ departments }: Props) {
|
||||
const [gender, setGender] = useState('');
|
||||
const [birthDate, setBirthDate] = useState<Date | undefined>();
|
||||
const [selectedDepartments, setSelectedDepartments] = useState<
|
||||
Department[]
|
||||
>([]);
|
||||
const departmentAnchor = useComboboxAnchor();
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -52,16 +66,31 @@ export default function LecturerCreate({ departments }: Props) {
|
||||
<CardContent className="grid gap-4 md:grid-cols-2">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="username">
|
||||
Username <span className="text-red-500">*</span>
|
||||
Username{' '}
|
||||
<span className="text-red-500">
|
||||
*
|
||||
</span>
|
||||
</Label>
|
||||
<Input id="username" name="username" placeholder="Masukkan username" />
|
||||
<Input
|
||||
id="username"
|
||||
name="username"
|
||||
placeholder="Masukkan username"
|
||||
/>
|
||||
<InputError message={errors.username} />
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="email">
|
||||
Email <span className="text-red-500">*</span>
|
||||
Email{' '}
|
||||
<span className="text-red-500">
|
||||
*
|
||||
</span>
|
||||
</Label>
|
||||
<Input id="email" name="email" type="email" placeholder="Masukkan email" />
|
||||
<Input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
placeholder="Masukkan email"
|
||||
/>
|
||||
<InputError message={errors.email} />
|
||||
</div>
|
||||
</CardContent>
|
||||
@ -74,57 +103,138 @@ export default function LecturerCreate({ departments }: Props) {
|
||||
<CardContent className="grid gap-4 md:grid-cols-2">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="full_name">
|
||||
Nama Lengkap <span className="text-red-500">*</span>
|
||||
Nama Lengkap{' '}
|
||||
<span className="text-red-500">
|
||||
*
|
||||
</span>
|
||||
</Label>
|
||||
<Input id="full_name" name="full_name" placeholder="Masukkan nama lengkap" />
|
||||
<InputError message={errors.full_name} />
|
||||
<Input
|
||||
id="full_name"
|
||||
name="full_name"
|
||||
placeholder="Masukkan nama lengkap"
|
||||
/>
|
||||
<InputError
|
||||
message={errors.full_name}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="phone_number">
|
||||
Nomor Telepon <span className="text-red-500">*</span>
|
||||
Nomor Telepon{' '}
|
||||
<span className="text-red-500">
|
||||
*
|
||||
</span>
|
||||
</Label>
|
||||
<PhoneInput id="phone_number" name="phone_number" placeholder="08xx xxxx xxxx" />
|
||||
<InputError message={errors.phone_number} />
|
||||
<PhoneInput
|
||||
id="phone_number"
|
||||
name="phone_number"
|
||||
placeholder="08xx xxxx xxxx"
|
||||
/>
|
||||
<InputError
|
||||
message={errors.phone_number}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-4 md:col-span-2 md:grid-cols-3">
|
||||
<div className="grid gap-2">
|
||||
<Label>
|
||||
Jenis Kelamin <span className="text-red-500">*</span>
|
||||
Jenis Kelamin{' '}
|
||||
<span className="text-red-500">
|
||||
*
|
||||
</span>
|
||||
</Label>
|
||||
<input type="hidden" name="gender" value={gender} />
|
||||
<RadioGroup value={gender} onValueChange={setGender} className="flex gap-4">
|
||||
<input
|
||||
type="hidden"
|
||||
name="gender"
|
||||
value={gender}
|
||||
/>
|
||||
<RadioGroup
|
||||
value={gender}
|
||||
onValueChange={setGender}
|
||||
className="flex gap-4"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="male" id="male" />
|
||||
<Label htmlFor="male" className="font-normal">Laki-laki</Label>
|
||||
<RadioGroupItem
|
||||
value="male"
|
||||
id="male"
|
||||
/>
|
||||
<Label
|
||||
htmlFor="male"
|
||||
className="font-normal"
|
||||
>
|
||||
Laki-laki
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="female" id="female" />
|
||||
<Label htmlFor="female" className="font-normal">Perempuan</Label>
|
||||
<RadioGroupItem
|
||||
value="female"
|
||||
id="female"
|
||||
/>
|
||||
<Label
|
||||
htmlFor="female"
|
||||
className="font-normal"
|
||||
>
|
||||
Perempuan
|
||||
</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
<InputError message={errors.gender} />
|
||||
<InputError
|
||||
message={errors.gender}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label>
|
||||
Tempat Lahir <span className="text-red-500">*</span>
|
||||
Tempat Lahir{' '}
|
||||
<span className="text-red-500">
|
||||
*
|
||||
</span>
|
||||
</Label>
|
||||
<Input name="birth_place" placeholder="Masukkan tempat lahir" />
|
||||
<InputError message={errors.birth_place} />
|
||||
<Input
|
||||
name="birth_place"
|
||||
placeholder="Masukkan tempat lahir"
|
||||
/>
|
||||
<InputError
|
||||
message={errors.birth_place}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label>
|
||||
Tanggal Lahir <span className="text-red-500">*</span>
|
||||
Tanggal Lahir{' '}
|
||||
<span className="text-red-500">
|
||||
*
|
||||
</span>
|
||||
</Label>
|
||||
<input type="hidden" name="birth_date" value={birthDate ? format(birthDate, 'yyyy-MM-dd') : ''} />
|
||||
<DatePicker value={birthDate} onChange={setBirthDate} />
|
||||
<InputError message={errors.birth_date} />
|
||||
<input
|
||||
type="hidden"
|
||||
name="birth_date"
|
||||
value={
|
||||
birthDate
|
||||
? format(
|
||||
birthDate,
|
||||
'yyyy-MM-dd',
|
||||
)
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
<DatePicker
|
||||
value={birthDate}
|
||||
onChange={setBirthDate}
|
||||
/>
|
||||
<InputError
|
||||
message={errors.birth_date}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid gap-2 md:col-span-2">
|
||||
<Label htmlFor="address">
|
||||
Alamat <span className="text-red-500">*</span>
|
||||
Alamat{' '}
|
||||
<span className="text-red-500">
|
||||
*
|
||||
</span>
|
||||
</Label>
|
||||
<Textarea id="address" name="address" placeholder="Masukkan alamat" />
|
||||
<Textarea
|
||||
id="address"
|
||||
name="address"
|
||||
placeholder="Masukkan alamat"
|
||||
/>
|
||||
<InputError message={errors.address} />
|
||||
</div>
|
||||
</CardContent>
|
||||
@ -137,29 +247,94 @@ export default function LecturerCreate({ departments }: Props) {
|
||||
<CardContent className="grid gap-4 md:grid-cols-2">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="lecturer_number">
|
||||
NIDN <span className="text-red-500">*</span>
|
||||
NIDN{' '}
|
||||
<span className="text-red-500">
|
||||
*
|
||||
</span>
|
||||
</Label>
|
||||
<Input id="lecturer_number" name="lecturer_number" placeholder="Masukkan NIDN" />
|
||||
<InputError message={errors.lecturer_number} />
|
||||
<Input
|
||||
id="lecturer_number"
|
||||
name="lecturer_number"
|
||||
placeholder="Masukkan NIDN"
|
||||
/>
|
||||
<InputError
|
||||
message={errors.lecturer_number}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label>
|
||||
Jurusan <span className="text-red-500">*</span>
|
||||
Jurusan{' '}
|
||||
<span className="text-red-500">
|
||||
*
|
||||
</span>
|
||||
</Label>
|
||||
<input type="hidden" name="department_id" />
|
||||
<Select name="department_id">
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="Pilih jurusan" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{departments.map((dept) => (
|
||||
<SelectItem key={dept.id} value={String(dept.id)}>
|
||||
{dept.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<InputError message={errors.department_id} />
|
||||
{selectedDepartments.map((dept) => (
|
||||
<input
|
||||
key={dept.id}
|
||||
type="hidden"
|
||||
name="department_ids[]"
|
||||
value={dept.id}
|
||||
/>
|
||||
))}
|
||||
<Combobox
|
||||
items={departments}
|
||||
multiple
|
||||
value={selectedDepartments}
|
||||
onValueChange={
|
||||
setSelectedDepartments
|
||||
}
|
||||
itemToStringLabel={(dept) =>
|
||||
dept.name
|
||||
}
|
||||
isItemEqualToValue={(a, b) =>
|
||||
a.id === b.id
|
||||
}
|
||||
>
|
||||
<ComboboxChips
|
||||
ref={departmentAnchor}
|
||||
>
|
||||
{selectedDepartments.map(
|
||||
(dept) => (
|
||||
<ComboboxChip
|
||||
key={dept.id}
|
||||
aria-label={
|
||||
dept.name
|
||||
}
|
||||
>
|
||||
{dept.name}
|
||||
</ComboboxChip>
|
||||
),
|
||||
)}
|
||||
<ComboboxChipsInput
|
||||
placeholder={
|
||||
selectedDepartments.length ===
|
||||
0
|
||||
? 'Pilih jurusan'
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
</ComboboxChips>
|
||||
<ComboboxContent
|
||||
anchor={departmentAnchor}
|
||||
>
|
||||
<ComboboxEmpty>
|
||||
Jurusan tidak ditemukan.
|
||||
</ComboboxEmpty>
|
||||
<ComboboxList>
|
||||
{departments.map((dept) => (
|
||||
<ComboboxItem
|
||||
key={dept.id}
|
||||
value={dept}
|
||||
>
|
||||
{dept.name}
|
||||
</ComboboxItem>
|
||||
))}
|
||||
</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
<InputError
|
||||
message={errors.department_ids}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@ -7,11 +7,21 @@ 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,
|
||||
ComboboxChip,
|
||||
ComboboxChips,
|
||||
ComboboxChipsInput,
|
||||
ComboboxContent,
|
||||
ComboboxEmpty,
|
||||
ComboboxItem,
|
||||
ComboboxList,
|
||||
useComboboxAnchor,
|
||||
} 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 { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { index, update } from '@/routes/admin/users/lecturers';
|
||||
|
||||
@ -21,8 +31,18 @@ type User = {
|
||||
id: number;
|
||||
username: string;
|
||||
email: string;
|
||||
profile: { full_name: string; phone_number: string; address: string; gender: string; birth_date: string; birth_place: string } | null;
|
||||
lecturer: { lecturer_number: string; department_id: number } | null;
|
||||
profile: {
|
||||
full_name: string;
|
||||
phone_number: string;
|
||||
address: string;
|
||||
gender: string;
|
||||
birth_date: string;
|
||||
birth_place: string;
|
||||
} | null;
|
||||
lecturer: {
|
||||
lecturer_number: string;
|
||||
departments: { id: number; name: string }[];
|
||||
} | null;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
@ -33,8 +53,14 @@ type Props = {
|
||||
export default function LecturerEdit({ user, departments }: Props) {
|
||||
const [gender, setGender] = useState(user.profile?.gender ?? '');
|
||||
const [birthDate, setBirthDate] = useState<Date | undefined>(
|
||||
user.profile?.birth_date ? new Date(user.profile.birth_date) : undefined
|
||||
user.profile?.birth_date
|
||||
? new Date(user.profile.birth_date)
|
||||
: undefined,
|
||||
);
|
||||
const [selectedDepartments, setSelectedDepartments] = useState<
|
||||
Department[]
|
||||
>(user.lecturer?.departments ?? []);
|
||||
const departmentAnchor = useComboboxAnchor();
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -63,16 +89,31 @@ export default function LecturerEdit({ user, departments }: Props) {
|
||||
<CardContent className="grid gap-4 md:grid-cols-2">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="username">
|
||||
Username <span className="text-red-500">*</span>
|
||||
Username{' '}
|
||||
<span className="text-red-500">
|
||||
*
|
||||
</span>
|
||||
</Label>
|
||||
<Input id="username" name="username" defaultValue={user.username} />
|
||||
<Input
|
||||
id="username"
|
||||
name="username"
|
||||
defaultValue={user.username}
|
||||
/>
|
||||
<InputError message={errors.username} />
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="email">
|
||||
Email <span className="text-red-500">*</span>
|
||||
Email{' '}
|
||||
<span className="text-red-500">
|
||||
*
|
||||
</span>
|
||||
</Label>
|
||||
<Input id="email" name="email" type="email" defaultValue={user.email} />
|
||||
<Input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
defaultValue={user.email}
|
||||
/>
|
||||
<InputError message={errors.email} />
|
||||
</div>
|
||||
</CardContent>
|
||||
@ -85,57 +126,147 @@ export default function LecturerEdit({ user, departments }: Props) {
|
||||
<CardContent className="grid gap-4 md:grid-cols-2">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="full_name">
|
||||
Nama Lengkap <span className="text-red-500">*</span>
|
||||
Nama Lengkap{' '}
|
||||
<span className="text-red-500">
|
||||
*
|
||||
</span>
|
||||
</Label>
|
||||
<Input id="full_name" name="full_name" defaultValue={user.profile?.full_name ?? ''} />
|
||||
<InputError message={errors.full_name} />
|
||||
<Input
|
||||
id="full_name"
|
||||
name="full_name"
|
||||
defaultValue={
|
||||
user.profile?.full_name ?? ''
|
||||
}
|
||||
/>
|
||||
<InputError
|
||||
message={errors.full_name}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="phone_number">
|
||||
Nomor Telepon <span className="text-red-500">*</span>
|
||||
Nomor Telepon{' '}
|
||||
<span className="text-red-500">
|
||||
*
|
||||
</span>
|
||||
</Label>
|
||||
<PhoneInput id="phone_number" name="phone_number" value={user.profile?.phone_number ?? ''} />
|
||||
<InputError message={errors.phone_number} />
|
||||
<PhoneInput
|
||||
id="phone_number"
|
||||
name="phone_number"
|
||||
value={
|
||||
user.profile?.phone_number ?? ''
|
||||
}
|
||||
/>
|
||||
<InputError
|
||||
message={errors.phone_number}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-4 md:col-span-2 md:grid-cols-3">
|
||||
<div className="grid gap-2">
|
||||
<Label>
|
||||
Jenis Kelamin <span className="text-red-500">*</span>
|
||||
Jenis Kelamin{' '}
|
||||
<span className="text-red-500">
|
||||
*
|
||||
</span>
|
||||
</Label>
|
||||
<input type="hidden" name="gender" value={gender} />
|
||||
<RadioGroup value={gender} onValueChange={setGender} className="flex gap-4">
|
||||
<input
|
||||
type="hidden"
|
||||
name="gender"
|
||||
value={gender}
|
||||
/>
|
||||
<RadioGroup
|
||||
value={gender}
|
||||
onValueChange={setGender}
|
||||
className="flex gap-4"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="male" id="male" />
|
||||
<Label htmlFor="male" className="font-normal">Laki-laki</Label>
|
||||
<RadioGroupItem
|
||||
value="male"
|
||||
id="male"
|
||||
/>
|
||||
<Label
|
||||
htmlFor="male"
|
||||
className="font-normal"
|
||||
>
|
||||
Laki-laki
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="female" id="female" />
|
||||
<Label htmlFor="female" className="font-normal">Perempuan</Label>
|
||||
<RadioGroupItem
|
||||
value="female"
|
||||
id="female"
|
||||
/>
|
||||
<Label
|
||||
htmlFor="female"
|
||||
className="font-normal"
|
||||
>
|
||||
Perempuan
|
||||
</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
<InputError message={errors.gender} />
|
||||
<InputError
|
||||
message={errors.gender}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label>
|
||||
Tempat Lahir <span className="text-red-500">*</span>
|
||||
Tempat Lahir{' '}
|
||||
<span className="text-red-500">
|
||||
*
|
||||
</span>
|
||||
</Label>
|
||||
<Input name="birth_place" defaultValue={user.profile?.birth_place ?? ''} />
|
||||
<InputError message={errors.birth_place} />
|
||||
<Input
|
||||
name="birth_place"
|
||||
defaultValue={
|
||||
user.profile?.birth_place ??
|
||||
''
|
||||
}
|
||||
/>
|
||||
<InputError
|
||||
message={errors.birth_place}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label>
|
||||
Tanggal Lahir <span className="text-red-500">*</span>
|
||||
Tanggal Lahir{' '}
|
||||
<span className="text-red-500">
|
||||
*
|
||||
</span>
|
||||
</Label>
|
||||
<input type="hidden" name="birth_date" value={birthDate ? format(birthDate, 'yyyy-MM-dd') : ''} />
|
||||
<DatePicker value={birthDate} onChange={setBirthDate} />
|
||||
<InputError message={errors.birth_date} />
|
||||
<input
|
||||
type="hidden"
|
||||
name="birth_date"
|
||||
value={
|
||||
birthDate
|
||||
? format(
|
||||
birthDate,
|
||||
'yyyy-MM-dd',
|
||||
)
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
<DatePicker
|
||||
value={birthDate}
|
||||
onChange={setBirthDate}
|
||||
/>
|
||||
<InputError
|
||||
message={errors.birth_date}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid gap-2 md:col-span-2">
|
||||
<Label htmlFor="address">
|
||||
Alamat <span className="text-red-500">*</span>
|
||||
Alamat{' '}
|
||||
<span className="text-red-500">
|
||||
*
|
||||
</span>
|
||||
</Label>
|
||||
<Textarea id="address" name="address" defaultValue={user.profile?.address ?? ''} />
|
||||
<Textarea
|
||||
id="address"
|
||||
name="address"
|
||||
defaultValue={
|
||||
user.profile?.address ?? ''
|
||||
}
|
||||
/>
|
||||
<InputError message={errors.address} />
|
||||
</div>
|
||||
</CardContent>
|
||||
@ -148,28 +279,97 @@ export default function LecturerEdit({ user, departments }: Props) {
|
||||
<CardContent className="grid gap-4 md:grid-cols-2">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="lecturer_number">
|
||||
NIDN <span className="text-red-500">*</span>
|
||||
NIDN{' '}
|
||||
<span className="text-red-500">
|
||||
*
|
||||
</span>
|
||||
</Label>
|
||||
<Input id="lecturer_number" name="lecturer_number" defaultValue={user.lecturer?.lecturer_number ?? ''} />
|
||||
<InputError message={errors.lecturer_number} />
|
||||
<Input
|
||||
id="lecturer_number"
|
||||
name="lecturer_number"
|
||||
defaultValue={
|
||||
user.lecturer
|
||||
?.lecturer_number ?? ''
|
||||
}
|
||||
/>
|
||||
<InputError
|
||||
message={errors.lecturer_number}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label>
|
||||
Jurusan <span className="text-red-500">*</span>
|
||||
Jurusan{' '}
|
||||
<span className="text-red-500">
|
||||
*
|
||||
</span>
|
||||
</Label>
|
||||
<Select name="department_id" defaultValue={user.lecturer?.department_id ? String(user.lecturer.department_id) : undefined}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="Pilih jurusan" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{departments.map((dept) => (
|
||||
<SelectItem key={dept.id} value={String(dept.id)}>
|
||||
{dept.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<InputError message={errors.department_id} />
|
||||
{selectedDepartments.map((dept) => (
|
||||
<input
|
||||
key={dept.id}
|
||||
type="hidden"
|
||||
name="department_ids[]"
|
||||
value={dept.id}
|
||||
/>
|
||||
))}
|
||||
<Combobox
|
||||
items={departments}
|
||||
multiple
|
||||
value={selectedDepartments}
|
||||
onValueChange={
|
||||
setSelectedDepartments
|
||||
}
|
||||
itemToStringLabel={(dept) =>
|
||||
dept.name
|
||||
}
|
||||
isItemEqualToValue={(a, b) =>
|
||||
a.id === b.id
|
||||
}
|
||||
>
|
||||
<ComboboxChips
|
||||
ref={departmentAnchor}
|
||||
>
|
||||
{selectedDepartments.map(
|
||||
(dept) => (
|
||||
<ComboboxChip
|
||||
key={dept.id}
|
||||
aria-label={
|
||||
dept.name
|
||||
}
|
||||
>
|
||||
{dept.name}
|
||||
</ComboboxChip>
|
||||
),
|
||||
)}
|
||||
<ComboboxChipsInput
|
||||
placeholder={
|
||||
selectedDepartments.length ===
|
||||
0
|
||||
? 'Pilih jurusan'
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
</ComboboxChips>
|
||||
<ComboboxContent
|
||||
anchor={departmentAnchor}
|
||||
>
|
||||
<ComboboxEmpty>
|
||||
Jurusan tidak ditemukan.
|
||||
</ComboboxEmpty>
|
||||
<ComboboxList>
|
||||
{departments.map((dept) => (
|
||||
<ComboboxItem
|
||||
key={dept.id}
|
||||
value={dept}
|
||||
>
|
||||
{dept.name}
|
||||
</ComboboxItem>
|
||||
))}
|
||||
</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
<InputError
|
||||
message={errors.department_ids}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user