siakad-itm/resources/js/pages/admin/settings/profile.tsx
Yoga Pangestu 70d37741ef
Some checks failed
tests / ci (pull_request) Has been cancelled
Refactor admin settings structure and implement profile and security management
- Moved profile and security settings from the general settings route to a dedicated admin settings route.
- Created new components and pages for managing user profile and security settings.
- Updated user menu to link to the new admin settings pages.
- Removed old settings pages and routes that are no longer in use.
- Added tests for profile and security settings functionality.
2026-08-25 17:57:47 +07:00

390 lines
18 KiB
TypeScript

import { Form, Head, Link, usePage } from '@inertiajs/react';
import { format } from 'date-fns';
import { useState } from 'react';
import ProfileController from '@/actions/App/Http/Controllers/Admin/Settings/ProfileController';
import { DatePicker } from '@/components/date-picker';
import Heading from '@/components/heading';
import InputError from '@/components/input-error';
import { Button } from '@/components/ui/button';
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 { edit } from '@/routes/admin/settings/profile';
import { send } from '@/routes/verification';
import type { Auth } from '@/types';
const roleLabels: Record<string, string> = {
mahasiswa: 'Mahasiswa',
dosen: 'Dosen',
'staff-admin': 'Staf Admin',
'staff-keuangan': 'Staf Keuangan',
};
type Profile = {
full_name: string | null;
phone_number: string | null;
address: string | null;
gender: 'male' | 'female' | null;
birth_date: string | null;
birth_place: string | null;
} | null;
type StudentRoleData = {
student_number: string;
department: string | null;
enrollment_year: number;
academic_advisor: string | null;
};
type LecturerRoleData = {
lecturer_number: string;
department: string | null;
};
type AdminRoleData = {
roles: string[];
};
type PageProps = {
auth: Auth;
};
type Props = {
mustVerifyEmail: boolean;
status?: string;
profile: Profile;
role: 'mahasiswa' | 'dosen' | 'admin';
roleData: StudentRoleData | LecturerRoleData | AdminRoleData | null;
};
export default function Profile({
mustVerifyEmail,
status,
profile,
role,
roleData,
}: Props) {
const { auth } = usePage<PageProps>().props;
const [gender, setGender] = useState(profile?.gender ?? '');
const [birthDate, setBirthDate] = useState<Date | undefined>(
profile?.birth_date ? new Date(profile.birth_date) : undefined,
);
return (
<>
<Head title="Pengaturan Profil" />
<h1 className="sr-only">Pengaturan Profil</h1>
<div className="space-y-6">
<Heading
variant="small"
title="Profil"
description="Perbarui nama pengguna dan alamat email Anda"
/>
<Form
{...ProfileController.update.form()}
options={{
preserveScroll: true,
}}
className="space-y-6"
>
{({ processing, errors }) => (
<>
<div className="grid gap-4 md:grid-cols-2">
<div className="grid gap-2">
<Label htmlFor="username">
Nama pengguna
</Label>
<Input
id="username"
defaultValue={auth.user.username}
name="username"
required
autoComplete="username"
placeholder="Nama pengguna"
/>
<InputError message={errors.username} />
</div>
<div className="grid gap-2">
<Label htmlFor="email">Alamat email</Label>
<Input
id="email"
type="email"
defaultValue={auth.user.email}
name="email"
required
autoComplete="email"
placeholder="Alamat email"
/>
<InputError message={errors.email} />
</div>
</div>
{mustVerifyEmail &&
auth.user.email_verified_at === null && (
<div>
<p className="-mt-2 text-sm text-muted-foreground">
Alamat email Anda belum
diverifikasi.{' '}
<Link
href={send()}
as="button"
className="text-foreground underline decoration-neutral-300 underline-offset-4 transition-colors duration-300 ease-out hover:decoration-current! dark:decoration-neutral-500"
>
Klik di sini untuk mengirim
ulang email verifikasi.
</Link>
</p>
{status ===
'verification-link-sent' && (
<div className="mt-2 text-sm font-medium text-green-600">
Tautan verifikasi baru telah
dikirim ke alamat email Anda.
</div>
)}
</div>
)}
<div className="grid gap-4 border-t pt-6 md:grid-cols-2">
<div className="grid gap-2">
<Label htmlFor="full_name">
Nama Lengkap
</Label>
<Input
id="full_name"
name="full_name"
defaultValue={profile?.full_name ?? ''}
placeholder="Masukkan nama lengkap"
/>
<InputError message={errors.full_name} />
</div>
<div className="grid gap-2">
<Label htmlFor="phone_number">
Nomor Telepon
</Label>
<PhoneInput
id="phone_number"
name="phone_number"
value={profile?.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</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</Label>
<Input
name="birth_place"
defaultValue={
profile?.birth_place ?? ''
}
placeholder="Masukkan tempat lahir"
/>
<InputError
message={errors.birth_place}
/>
</div>
<div className="grid gap-2">
<Label>Tanggal Lahir</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</Label>
<Textarea
id="address"
name="address"
defaultValue={profile?.address ?? ''}
placeholder="Masukkan alamat"
/>
<InputError message={errors.address} />
</div>
</div>
{role === 'mahasiswa' && roleData && (
<div className="grid gap-4 border-t pt-6 md:grid-cols-2">
<div className="grid gap-2">
<Label>NIM</Label>
<Input
disabled
value={
(roleData as StudentRoleData)
.student_number
}
/>
</div>
<div className="grid gap-2">
<Label>Jurusan</Label>
<Input
disabled
value={
(roleData as StudentRoleData)
.department ?? '-'
}
/>
</div>
<div className="grid gap-2">
<Label>Tahun Masuk</Label>
<Input
disabled
value={
(roleData as StudentRoleData)
.enrollment_year
}
/>
</div>
<div className="grid gap-2">
<Label>Dosen Wali</Label>
<Input
disabled
value={
(roleData as StudentRoleData)
.academic_advisor ?? '-'
}
/>
</div>
<p className="text-xs text-muted-foreground md:col-span-2">
Informasi akademik dikelola oleh admin
dan tidak dapat diubah sendiri.
</p>
</div>
)}
{role === 'dosen' && roleData && (
<div className="grid gap-4 border-t pt-6 md:grid-cols-2">
<div className="grid gap-2">
<Label>NIDN</Label>
<Input
disabled
value={
(roleData as LecturerRoleData)
.lecturer_number
}
/>
</div>
<div className="grid gap-2">
<Label>Jurusan</Label>
<Input
disabled
value={
(roleData as LecturerRoleData)
.department ?? '-'
}
/>
</div>
<p className="text-xs text-muted-foreground md:col-span-2">
Informasi akademik dikelola oleh admin
dan tidak dapat diubah sendiri.
</p>
</div>
)}
{role === 'admin' && roleData && (
<div className="grid gap-4 border-t pt-6">
<div className="grid gap-2">
<Label>Peran</Label>
<Input
disabled
value={(
roleData as AdminRoleData
).roles
.map((r) => roleLabels[r] ?? r)
.join(', ')}
/>
</div>
</div>
)}
<div className="flex items-center gap-4">
<Button
disabled={processing}
data-test="update-profile-button"
>
Simpan
</Button>
</div>
</>
)}
</Form>
</div>
</>
);
}
Profile.layout = {
breadcrumbs: [
{
title: 'Pengaturan Profil',
href: edit(),
},
],
};