381 lines
20 KiB
TypeScript
381 lines
20 KiB
TypeScript
import { Button } from '@/components/ui/button';
|
|
import { Calendar } from '@/components/ui/calendar';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Label } from '@/components/ui/label';
|
|
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
|
import { useForm } from '@inertiajs/react';
|
|
import AppLayout from '@/layouts/app-layout';
|
|
import { Head } from '@inertiajs/react';
|
|
import { type BreadcrumbItem } from '@/types';
|
|
import InputError from '@/components/input-error';
|
|
import { format } from 'date-fns';
|
|
import { CalendarIcon } from 'lucide-react';
|
|
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
|
import { cn } from '@/lib/utils';
|
|
import { Textarea } from '@/components/ui/textarea';
|
|
|
|
interface EmployeeFormProps {
|
|
mode: 'create' | 'edit';
|
|
employee?: {
|
|
id?: string;
|
|
employee_id: string;
|
|
name: string;
|
|
email: string;
|
|
gender: string;
|
|
nik?: string;
|
|
birth_date: string;
|
|
birth_place?: string;
|
|
address?: string;
|
|
phone_number?: string;
|
|
specialization?: string;
|
|
license_number?: string;
|
|
join_date: string;
|
|
resign_date?: string;
|
|
is_active: boolean;
|
|
};
|
|
}
|
|
|
|
const toLocalISOString = (date: Date) => {
|
|
const offset = date.getTimezoneOffset();
|
|
const localDate = new Date(date.getTime() - (offset * 60 * 1000));
|
|
return localDate.toISOString().split('T')[0];
|
|
};
|
|
|
|
const breadcrumbs: BreadcrumbItem[] = [
|
|
{ title: 'Dashboard', href: '/dashboard' },
|
|
{ title: 'Employees', href: '/employees' },
|
|
{ title: 'Form', href: '#' },
|
|
];
|
|
|
|
export default function EmployeeForm({ mode, employee }: EmployeeFormProps) {
|
|
const { data, setData, post, put, processing, errors, reset } = useForm({
|
|
// User fields
|
|
name: employee?.name || '',
|
|
email: employee?.email || '',
|
|
password: '',
|
|
|
|
// Employee fields
|
|
employee_id: employee?.employee_id || '',
|
|
gender: employee?.gender || 'laki-laki',
|
|
nik: employee?.nik || '',
|
|
birth_date: employee?.birth_date || '',
|
|
birth_place: employee?.birth_place || '',
|
|
address: employee?.address || '',
|
|
phone_number: employee?.phone_number || '',
|
|
specialization: employee?.specialization || '',
|
|
license_number: employee?.license_number || '',
|
|
join_date: employee?.join_date || '',
|
|
resign_date: employee?.resign_date || '',
|
|
is_active: employee?.is_active ?? true,
|
|
});
|
|
|
|
const onSubmit = (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
if (mode === 'create') {
|
|
post(route('employees.store'), {
|
|
onSuccess: () => reset(),
|
|
});
|
|
} else {
|
|
put(route('employees.update', employee?.id), {
|
|
preserveScroll: true,
|
|
});
|
|
}
|
|
};
|
|
|
|
return (
|
|
<AppLayout breadcrumbs={breadcrumbs}>
|
|
<Head title={`${mode === 'create' ? 'Create' : 'Edit'} Employee`} />
|
|
|
|
<div className="flex h-full flex-1 flex-col gap-4 rounded-xl p-4">
|
|
<div className="flex items-center justify-between">
|
|
<h1 className="text-2xl font-bold">
|
|
{mode === 'create' ? 'Create New Employee' : 'Edit Employee'}
|
|
</h1>
|
|
{mode === 'edit' && (
|
|
<span className="text-xl">
|
|
ID. Pegawai {data.employee_id}
|
|
</span>
|
|
)}
|
|
</div>
|
|
|
|
<div>
|
|
<form onSubmit={onSubmit} className="space-y-6">
|
|
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
|
|
{/* User Information Section */}
|
|
<div className="space-y-2">
|
|
<Label htmlFor="name">Nama Lengkap *</Label>
|
|
<Input
|
|
id="name"
|
|
value={data.name}
|
|
onChange={(e) => setData('name', e.target.value)}
|
|
placeholder="Joko Susanto"
|
|
/>
|
|
<InputError message={errors.name} />
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="email">Email *</Label>
|
|
<Input
|
|
id="email"
|
|
type="email"
|
|
value={data.email}
|
|
onChange={(e) => setData('email', e.target.value)}
|
|
placeholder="joko_susanto@example.com"
|
|
/>
|
|
<InputError message={errors.email} />
|
|
</div>
|
|
|
|
{mode === 'create' && (
|
|
<div className="space-y-2">
|
|
<Label htmlFor="password">Password*</Label>
|
|
<Input
|
|
id="password"
|
|
type="password"
|
|
value={data.password}
|
|
onChange={(e) => setData('password', e.target.value)}
|
|
placeholder="••••••••"
|
|
/>
|
|
<InputError message={errors.password} />
|
|
</div>
|
|
)}
|
|
|
|
<div className="space-y-2">
|
|
<Label>Jenis Kelamin *</Label>
|
|
<RadioGroup
|
|
value={data.gender}
|
|
onValueChange={(value) => setData('gender', value)}
|
|
className="flex gap-4"
|
|
>
|
|
<div className="flex items-center space-x-2">
|
|
<RadioGroupItem value="laki-laki" id="male" />
|
|
<Label htmlFor="male">Laki-laki</Label>
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
<RadioGroupItem value="perempuan" id="female" />
|
|
<Label htmlFor="female">Perempuan</Label>
|
|
</div>
|
|
</RadioGroup>
|
|
<InputError message={errors.gender} />
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="nik">NIK</Label>
|
|
<Input
|
|
id="nik"
|
|
value={data.nik}
|
|
onChange={(e) => setData('nik', e.target.value)}
|
|
placeholder="16-digit NIK"
|
|
/>
|
|
<InputError message={errors.nik} />
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="birth_date">Tanggal Lahir *</Label>
|
|
<Popover>
|
|
<PopoverTrigger asChild>
|
|
<Button
|
|
variant={"outline"}
|
|
className={cn(
|
|
"w-full justify-start text-left font-normal",
|
|
!data.birth_date && "text-muted-foreground"
|
|
)}
|
|
>
|
|
<CalendarIcon className="mr-2 h-4 w-4" />
|
|
{data.birth_date ? (
|
|
format(new Date(data.birth_date), "dd/MM/yyyy")
|
|
) : (
|
|
<span>Pilih tanggal</span>
|
|
)}
|
|
</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent className="w-auto p-0" align="start">
|
|
<div className="flex flex-col">
|
|
<div className="flex justify-between px-3 pt-3">
|
|
<select
|
|
value={data.birth_date ? new Date(data.birth_date).getFullYear() : new Date().getFullYear()}
|
|
onChange={(e) => {
|
|
const currentDate = data.birth_date ? new Date(data.birth_date) : new Date();
|
|
const newDate = new Date(currentDate);
|
|
newDate.setFullYear(parseInt(e.target.value));
|
|
setData('birth_date', toLocalISOString(newDate));
|
|
}}
|
|
className="border rounded px-2 py-1 text-sm"
|
|
>
|
|
{Array.from({ length: 100 }, (_, i) => new Date().getFullYear() - i).map((year) => (
|
|
<option key={year} value={year}>
|
|
{year}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
<Calendar
|
|
mode="single"
|
|
selected={data.birth_date ? new Date(data.birth_date) : undefined}
|
|
onSelect={(date) => date && setData('birth_date', toLocalISOString(date))}
|
|
initialFocus
|
|
showOutsideDays={false}
|
|
/>
|
|
</div>
|
|
</PopoverContent>
|
|
</Popover>
|
|
<InputError message={errors.birth_date} />
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="birth_place">Tempat Kelahiran</Label>
|
|
<Input
|
|
id="birth_place"
|
|
value={data.birth_place}
|
|
onChange={(e) => setData('birth_place', e.target.value)}
|
|
placeholder="Jakarta"
|
|
/>
|
|
<InputError message={errors.birth_place} />
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="phone_number">Nomor Telepon (HP)</Label>
|
|
<Input
|
|
id="phone_number"
|
|
value={data.phone_number}
|
|
onChange={(e) => setData('phone_number', e.target.value)}
|
|
placeholder="08123456789"
|
|
/>
|
|
<InputError message={errors.phone_number} />
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="specialization">Spesialisasi</Label>
|
|
<Input
|
|
id="specialization"
|
|
value={data.specialization}
|
|
onChange={(e) => setData('specialization', e.target.value)}
|
|
placeholder="Dokter Spesialis Saraf (Sp.S)"
|
|
/>
|
|
<InputError message={errors.specialization} />
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="license_number">Nomor STR</Label>
|
|
<Input
|
|
id="license_number"
|
|
value={data.license_number}
|
|
onChange={(e) => setData('license_number', e.target.value)}
|
|
placeholder="1028305"
|
|
/>
|
|
<span className={'text-xs text-gray-400'}>
|
|
Masukkan 7 digit terakhir nomor STR
|
|
</span>
|
|
<InputError message={errors.license_number} />
|
|
</div>
|
|
|
|
<div className="col-span-2">
|
|
<div className="space-y-2 mt-6">
|
|
<Label htmlFor="address">Address</Label>
|
|
<Textarea
|
|
id="address"
|
|
value={data.address}
|
|
onChange={(e) => setData('address', e.target.value)}
|
|
placeholder="Full address"
|
|
/>
|
|
<InputError message={errors.address} />
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="join_date">Tanggal Bergabung *</Label>
|
|
<Popover>
|
|
<PopoverTrigger asChild>
|
|
<Button
|
|
variant={"outline"}
|
|
className={cn(
|
|
"w-full justify-start text-left font-normal",
|
|
!data.join_date && "text-muted-foreground"
|
|
)}
|
|
>
|
|
<CalendarIcon className="mr-2 h-4 w-4" />
|
|
{data.join_date ? (
|
|
format(new Date(data.join_date), "dd/MM/yyyy")
|
|
) : (
|
|
<span>Pick a date</span>
|
|
)}
|
|
</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent className="w-auto p-0">
|
|
<Calendar
|
|
mode="single"
|
|
selected={data.join_date ? new Date(data.join_date) : undefined}
|
|
onSelect={(date) => date && setData('join_date', toLocalISOString(date))}
|
|
initialFocus
|
|
/>
|
|
</PopoverContent>
|
|
</Popover>
|
|
<InputError message={errors.join_date} />
|
|
</div>
|
|
|
|
{mode === 'edit' && (
|
|
<div className="space-y-2">
|
|
<Label htmlFor="resign_date">Resign Date</Label>
|
|
<Popover>
|
|
<PopoverTrigger asChild>
|
|
<Button
|
|
variant={"outline"}
|
|
className={cn(
|
|
"w-full justify-start text-left font-normal",
|
|
!data.resign_date && "text-muted-foreground"
|
|
)}
|
|
>
|
|
<CalendarIcon className="mr-2 h-4 w-4" />
|
|
{data.resign_date ? (
|
|
format(new Date(data.resign_date), "dd/MM/yyyy")
|
|
) : (
|
|
<span>Pilih Tanggal</span>
|
|
)}
|
|
</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent className="w-auto p-0">
|
|
<Calendar
|
|
mode="single"
|
|
selected={data.resign_date ? new Date(data.resign_date) : undefined}
|
|
onSelect={(date) => setData('resign_date', date ? toLocalISOString(date) : '')}
|
|
initialFocus
|
|
/>
|
|
</PopoverContent>
|
|
</Popover>
|
|
<InputError message={errors.resign_date} />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
{mode === 'edit' && (
|
|
<div className="mb-2">
|
|
<span className="mb-2">Pegawai masih bekerja?</span>
|
|
<div className="flex items-center space-x-2 mt-2">
|
|
<input
|
|
type="checkbox"
|
|
id="is_active"
|
|
checked={data.is_active}
|
|
onChange={(e) => setData('is_active', e.target.checked)}
|
|
className="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
|
|
/>
|
|
<Label htmlFor="is_active">Ya</Label>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
</div>
|
|
|
|
<div className="flex justify-end gap-2">
|
|
<Button variant="outline" type="button" onClick={() => window.history.back()}>
|
|
Cancel
|
|
</Button>
|
|
<Button type="submit" disabled={processing}>
|
|
{mode === 'create' ? 'Create' : 'Update'} Employee
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</AppLayout>
|
|
);
|
|
}
|