206 lines
6.6 KiB
TypeScript
206 lines
6.6 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import { useNavigate, useParams } from 'react-router';
|
|
import { useCreateUser, useUpdateUser, useUser, UserFormData } from '../../features/users/api/users';
|
|
import { extractApiErrors } from '../../lib/api-client';
|
|
import axios from 'axios';
|
|
|
|
const UserFormPage = () => {
|
|
const { id } = useParams();
|
|
const navigate = useNavigate();
|
|
const isEditing = !!id;
|
|
|
|
// Query dan mutations
|
|
const { data: user, isLoading: isLoadingUser } = useUser(id || '');
|
|
const createUser = useCreateUser();
|
|
const updateUser = useUpdateUser();
|
|
|
|
// Form state
|
|
const [formData, setFormData] = useState<UserFormData>({
|
|
name: '',
|
|
email: '',
|
|
password: '',
|
|
role: 'user', // Default role
|
|
});
|
|
|
|
const [errors, setErrors] = useState<Record<string, string>>({});
|
|
|
|
// Isi form dengan data user jika mode edit
|
|
useEffect(() => {
|
|
if (isEditing && user) {
|
|
setFormData({
|
|
name: user.name,
|
|
email: user.email,
|
|
role: user.role,
|
|
// Password kosong saat edit
|
|
});
|
|
}
|
|
}, [isEditing, user]);
|
|
|
|
const handleChange = (
|
|
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>
|
|
) => {
|
|
const { name, value } = e.target;
|
|
setFormData((prev) => ({
|
|
...prev,
|
|
[name]: value,
|
|
}));
|
|
|
|
// Hapus error untuk field yang sedang diubah
|
|
if (errors[name]) {
|
|
setErrors((prev) => {
|
|
const newErrors = { ...prev };
|
|
delete newErrors[name];
|
|
return newErrors;
|
|
});
|
|
}
|
|
};
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
|
|
try {
|
|
if (isEditing && id) {
|
|
// Jika password kosong, hapus dari data yang dikirim
|
|
const dataToSubmit = { ...formData };
|
|
if (!dataToSubmit.password) {
|
|
delete dataToSubmit.password;
|
|
}
|
|
|
|
await updateUser.mutateAsync({ id, data: dataToSubmit });
|
|
} else {
|
|
await createUser.mutateAsync(formData);
|
|
}
|
|
|
|
navigate('/users');
|
|
} catch (error) {
|
|
if (axios.isAxiosError(error) && error.response?.status === 400) {
|
|
const apiErrors = error.response.data.status.errors;
|
|
setErrors(apiErrors || {});
|
|
} else {
|
|
const extractedErrors = extractApiErrors(error);
|
|
setErrors(extractedErrors);
|
|
}
|
|
}
|
|
};
|
|
|
|
if (isEditing && isLoadingUser) {
|
|
return (
|
|
<div className="flex justify-center items-center h-64">
|
|
<span className="loading loading-spinner loading-lg"></span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="container mx-auto p-4">
|
|
<div className="card bg-base-100 shadow-xl">
|
|
<div className="card-body">
|
|
<h2 className="card-title text-2xl mb-6">
|
|
{isEditing ? 'Edit Pengguna' : 'Tambah Pengguna'}
|
|
</h2>
|
|
|
|
<form onSubmit={handleSubmit}>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div className="form-control">
|
|
<label className="label">
|
|
<span className="label-text">Nama</span>
|
|
</label>
|
|
<input
|
|
type="text"
|
|
name="name"
|
|
value={formData.name}
|
|
onChange={handleChange}
|
|
className={`input input-bordered ${errors.name ? 'input-error' : ''}`}
|
|
/>
|
|
{errors.name && (
|
|
<label className="label">
|
|
<span className="label-text-alt text-error">{errors.name}</span>
|
|
</label>
|
|
)}
|
|
</div>
|
|
|
|
<div className="form-control">
|
|
<label className="label">
|
|
<span className="label-text">Email</span>
|
|
</label>
|
|
<input
|
|
type="email"
|
|
name="email"
|
|
value={formData.email}
|
|
onChange={handleChange}
|
|
className={`input input-bordered ${errors.email ? 'input-error' : ''}`}
|
|
/>
|
|
{errors.email && (
|
|
<label className="label">
|
|
<span className="label-text-alt text-error">{errors.email}</span>
|
|
</label>
|
|
)}
|
|
</div>
|
|
|
|
<div className="form-control">
|
|
<label className="label">
|
|
<span className="label-text">Password {isEditing && '(kosongkan jika tidak ingin mengubah)'}</span>
|
|
</label>
|
|
<input
|
|
type="password"
|
|
name="password"
|
|
value={formData.password || ''}
|
|
onChange={handleChange}
|
|
className={`input input-bordered ${errors.password ? 'input-error' : ''}`}
|
|
/>
|
|
{errors.password && (
|
|
<label className="label">
|
|
<span className="label-text-alt text-error">{errors.password}</span>
|
|
</label>
|
|
)}
|
|
</div>
|
|
|
|
<div className="form-control">
|
|
<label className="label">
|
|
<span className="label-text">Role</span>
|
|
</label>
|
|
<select
|
|
name="role"
|
|
value={formData.role}
|
|
onChange={handleChange}
|
|
className={`select select-bordered ${errors.role ? 'select-error' : ''}`}
|
|
>
|
|
<option value="user">User</option>
|
|
<option value="admin">Admin</option>
|
|
</select>
|
|
{errors.role && (
|
|
<label className="label">
|
|
<span className="label-text-alt text-error">{errors.role}</span>
|
|
</label>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="card-actions justify-end mt-6">
|
|
<button
|
|
type="button"
|
|
className="btn btn-outline"
|
|
onClick={() => navigate('/users')}
|
|
>
|
|
Batal
|
|
</button>
|
|
<button
|
|
type="submit"
|
|
className="btn btn-primary"
|
|
disabled={createUser.isPending || updateUser.isPending}
|
|
>
|
|
{(createUser.isPending || updateUser.isPending) ? (
|
|
<span className="loading loading-spinner loading-sm"></span>
|
|
) : (
|
|
isEditing ? 'Update' : 'Simpan'
|
|
)}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default UserFormPage; |