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.
390 lines
20 KiB
TypeScript
390 lines
20 KiB
TypeScript
import { Form, Head } from '@inertiajs/react';
|
|
import { format } from 'date-fns';
|
|
import { ArrowLeft, Save } from 'lucide-react';
|
|
import { useState } from 'react';
|
|
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,
|
|
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 { Textarea } from '@/components/ui/textarea';
|
|
import { index, update } from '@/routes/admin/users/lecturers';
|
|
|
|
type Department = { id: number; name: string };
|
|
|
|
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;
|
|
departments: { id: number; name: string }[];
|
|
} | null;
|
|
};
|
|
|
|
type Props = {
|
|
user: User;
|
|
departments: Department[];
|
|
};
|
|
|
|
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,
|
|
);
|
|
const [selectedDepartments, setSelectedDepartments] = useState<
|
|
Department[]
|
|
>(user.lecturer?.departments ?? []);
|
|
const departmentAnchor = useComboboxAnchor();
|
|
|
|
return (
|
|
<>
|
|
<Head title="Edit Dosen" />
|
|
|
|
<div className="flex h-full flex-1 flex-col gap-6 overflow-x-auto p-4 md:p-6">
|
|
<PageHeader
|
|
title="Edit Dosen"
|
|
actions={
|
|
<Button variant="outline" asChild>
|
|
<a href={index.url()}>
|
|
<ArrowLeft className="h-4 w-4" />
|
|
Kembali
|
|
</a>
|
|
</Button>
|
|
}
|
|
/>
|
|
|
|
<Form action={update(user.id)} className="space-y-6">
|
|
{({ errors, processing }) => (
|
|
<>
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Informasi Akun</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="grid gap-4 md:grid-cols-2">
|
|
<div className="grid gap-2">
|
|
<Label htmlFor="username">
|
|
Username{' '}
|
|
<span className="text-red-500">
|
|
*
|
|
</span>
|
|
</Label>
|
|
<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>
|
|
</Label>
|
|
<Input
|
|
id="email"
|
|
name="email"
|
|
type="email"
|
|
defaultValue={user.email}
|
|
/>
|
|
<InputError message={errors.email} />
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Profil Pribadi</CardTitle>
|
|
</CardHeader>
|
|
<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>
|
|
</Label>
|
|
<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>
|
|
</Label>
|
|
<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>
|
|
</Label>
|
|
<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>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<RadioGroupItem
|
|
value="female"
|
|
id="female"
|
|
/>
|
|
<Label
|
|
htmlFor="female"
|
|
className="font-normal"
|
|
>
|
|
Perempuan
|
|
</Label>
|
|
</div>
|
|
</RadioGroup>
|
|
<InputError
|
|
message={errors.gender}
|
|
/>
|
|
</div>
|
|
<div className="grid gap-2">
|
|
<Label>
|
|
Tempat Lahir{' '}
|
|
<span className="text-red-500">
|
|
*
|
|
</span>
|
|
</Label>
|
|
<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>
|
|
</Label>
|
|
<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>
|
|
</Label>
|
|
<Textarea
|
|
id="address"
|
|
name="address"
|
|
defaultValue={
|
|
user.profile?.address ?? ''
|
|
}
|
|
/>
|
|
<InputError message={errors.address} />
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Informasi Dosen</CardTitle>
|
|
</CardHeader>
|
|
<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>
|
|
</Label>
|
|
<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>
|
|
</Label>
|
|
{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>
|
|
|
|
<div className="flex justify-end">
|
|
<Button type="submit" disabled={processing}>
|
|
<Save className="h-4 w-4" />
|
|
Simpan
|
|
</Button>
|
|
</div>
|
|
</>
|
|
)}
|
|
</Form>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|