feat: add base salary field to user profile management with validation and UI integration

This commit is contained in:
Yoga Pangestu 2026-04-18 14:51:49 +07:00
parent 6c21c66e12
commit 8e32df7307
7 changed files with 78 additions and 26 deletions

View File

@ -45,6 +45,7 @@ public function store(UserRequest $request): RedirectResponse
'address' => $validated['address'], 'address' => $validated['address'],
'birth_place' => $validated['birth_place'], 'birth_place' => $validated['birth_place'],
'birth_date' => $validated['birth_date'], 'birth_date' => $validated['birth_date'],
'base_salary' => $validated['base_salary'],
]); ]);
}); });
@ -77,6 +78,7 @@ public function update(UserRequest $request, User $user): RedirectResponse
'address' => $validated['address'], 'address' => $validated['address'],
'birth_place' => $validated['birth_place'], 'birth_place' => $validated['birth_place'],
'birth_date' => $validated['birth_date'], 'birth_date' => $validated['birth_date'],
'base_salary' => $validated['base_salary'],
]); ]);
}); });

View File

@ -36,6 +36,7 @@ public function rules(): array
'address' => ['required', 'string'], 'address' => ['required', 'string'],
'birth_place' => ['required', 'string', 'max:100'], 'birth_place' => ['required', 'string', 'max:100'],
'birth_date' => ['required', 'date'], 'birth_date' => ['required', 'date'],
'base_salary' => ['required', 'integer', 'min:0'],
]; ];
} }
} }

View File

@ -13,6 +13,13 @@ class UserProfile extends Model
{ {
use HasFactory, SoftDeletes; use HasFactory, SoftDeletes;
protected function casts(): array
{
return [
'base_salary' => 'integer',
];
}
public function user(): BelongsTo public function user(): BelongsTo
{ {
return $this->belongsTo(User::class); return $this->belongsTo(User::class);

View File

@ -20,6 +20,7 @@ public function up(): void
$table->text('address'); $table->text('address');
$table->string('birth_place', 100); $table->string('birth_place', 100);
$table->date('birth_date'); $table->date('birth_date');
$table->unsignedInteger('base_salary')->default(0);
$table->timestamp('created_at')->useCurrent(); $table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->nullable()->useCurrentOnUpdate(); $table->timestamp('updated_at')->nullable()->useCurrentOnUpdate();
$table->softDeletes(); $table->softDeletes();

View File

@ -26,6 +26,7 @@ public function run(): void
'address' => 'Jl. Contoh No. 123', 'address' => 'Jl. Contoh No. 123',
'birth_place' => 'Jakarta', 'birth_place' => 'Jakarta',
'birth_date' => '1990-01-01', 'birth_date' => '1990-01-01',
'base_salary' => 1500000,
]); ]);
} }
} }

View File

@ -15,6 +15,7 @@ import {
PopoverContent, PopoverContent,
PopoverTrigger, PopoverTrigger,
} from "@/components/ui/popover" } from "@/components/ui/popover"
import { NumericFormat } from 'react-number-format';
export default function UserCreate() { export default function UserCreate() {
const [isCalendarOpen, setIsCalendarOpen] = React.useState(false); const [isCalendarOpen, setIsCalendarOpen] = React.useState(false);
@ -28,6 +29,7 @@ export default function UserCreate() {
address: '', address: '',
birth_place: '', birth_place: '',
birth_date: '', birth_date: '',
base_salary: 0,
}); });
const onSubmit = (e: React.FormEvent) => { const onSubmit = (e: React.FormEvent) => {
@ -62,20 +64,20 @@ export default function UserCreate() {
<CardTitle>Informasi Profil</CardTitle> <CardTitle>Informasi Profil</CardTitle>
</CardHeader> </CardHeader>
<CardContent className="space-y-6"> <CardContent className="space-y-6">
<Field>
<Label htmlFor="full_name" required>Nama Lengkap</Label>
<Input
id="full_name"
name="full_name"
value={data.full_name}
onChange={e => setData('full_name', e.target.value)}
autoComplete='off'
placeholder='Contoh: John Doe'
/>
{errors.full_name && <p className="text-xs text-red-500">{errors.full_name}</p>}
</Field>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<Field>
<Label htmlFor="full_name" required>Nama Lengkap</Label>
<Input
id="full_name"
name="full_name"
value={data.full_name}
onChange={e => setData('full_name', e.target.value)}
autoComplete='off'
placeholder='Contoh: John Doe'
/>
{errors.full_name && <p className="text-xs text-red-500">{errors.full_name}</p>}
</Field>
<Field> <Field>
<Label htmlFor="nik" required>NIK</Label> <Label htmlFor="nik" required>NIK</Label>
<Input <Input
@ -103,6 +105,24 @@ export default function UserCreate() {
{errors.phone_number && <p className="text-xs text-red-500">{errors.phone_number}</p>} {errors.phone_number && <p className="text-xs text-red-500">{errors.phone_number}</p>}
</Field> </Field>
<Field>
<Label htmlFor="base_salary" required>Gaji Pokok</Label>
<NumericFormat
id="base_salary"
customInput={Input}
thousandSeparator="."
decimalSeparator=","
prefix="Rp "
value={data.base_salary}
onValueChange={(values) => {
setData('base_salary', values.floatValue || 0)
}}
placeholder="Rp 0"
autoComplete='off'
/>
{errors.base_salary && <p className="text-xs text-red-500">{errors.base_salary}</p>}
</Field>
<Field> <Field>
<Label htmlFor="birth_place" required>Tempat Lahir</Label> <Label htmlFor="birth_place" required>Tempat Lahir</Label>
<Input <Input

View File

@ -15,6 +15,7 @@ import {
PopoverContent, PopoverContent,
PopoverTrigger, PopoverTrigger,
} from "@/components/ui/popover" } from "@/components/ui/popover"
import { NumericFormat } from 'react-number-format';
export default function UserEdit({ user }: { user: any }) { export default function UserEdit({ user }: { user: any }) {
const [isCalendarOpen, setIsCalendarOpen] = React.useState(false); const [isCalendarOpen, setIsCalendarOpen] = React.useState(false);
@ -28,6 +29,7 @@ export default function UserEdit({ user }: { user: any }) {
address: user.profile?.address || '', address: user.profile?.address || '',
birth_place: user.profile?.birth_place || '', birth_place: user.profile?.birth_place || '',
birth_date: user.profile?.birth_date || '', birth_date: user.profile?.birth_date || '',
base_salary: user.profile?.base_salary || 0,
_method: 'PATCH', _method: 'PATCH',
}); });
@ -63,19 +65,6 @@ export default function UserEdit({ user }: { user: any }) {
<CardTitle>Informasi Profil</CardTitle> <CardTitle>Informasi Profil</CardTitle>
</CardHeader> </CardHeader>
<CardContent className="space-y-6"> <CardContent className="space-y-6">
<Field>
<Label htmlFor="full_name" required>Nama Lengkap</Label>
<Input
id="full_name"
name="full_name"
value={data.full_name}
onChange={e => setData('full_name', e.target.value)}
autoComplete='off'
placeholder='Contoh: John Doe'
/>
{errors.full_name && <p className="text-xs text-red-500">{errors.full_name}</p>}
</Field>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<Field> <Field>
<Label htmlFor="nik" required>NIK</Label> <Label htmlFor="nik" required>NIK</Label>
@ -91,6 +80,19 @@ export default function UserEdit({ user }: { user: any }) {
{errors.nik && <p className="text-xs text-red-500">{errors.nik}</p>} {errors.nik && <p className="text-xs text-red-500">{errors.nik}</p>}
</Field> </Field>
<Field>
<Label htmlFor="full_name" required>Nama Lengkap</Label>
<Input
id="full_name"
name="full_name"
value={data.full_name}
onChange={e => setData('full_name', e.target.value)}
autoComplete='off'
placeholder='Contoh: John Doe'
/>
{errors.full_name && <p className="text-xs text-red-500">{errors.full_name}</p>}
</Field>
<Field> <Field>
<Label htmlFor="phone_number" required>Nomor Telepon</Label> <Label htmlFor="phone_number" required>Nomor Telepon</Label>
<Input <Input
@ -104,6 +106,24 @@ export default function UserEdit({ user }: { user: any }) {
{errors.phone_number && <p className="text-xs text-red-500">{errors.phone_number}</p>} {errors.phone_number && <p className="text-xs text-red-500">{errors.phone_number}</p>}
</Field> </Field>
<Field>
<Label htmlFor="base_salary" required>Gaji Pokok</Label>
<NumericFormat
id="base_salary"
customInput={Input}
thousandSeparator="."
decimalSeparator=","
prefix="Rp "
value={data.base_salary}
onValueChange={(values) => {
setData('base_salary', values.floatValue || 0)
}}
placeholder="Rp 0"
autoComplete='off'
/>
{errors.base_salary && <p className="text-xs text-red-500">{errors.base_salary}</p>}
</Field>
<Field> <Field>
<Label htmlFor="birth_place" required>Tempat Lahir</Label> <Label htmlFor="birth_place" required>Tempat Lahir</Label>
<Input <Input