kontenhumas-fe/components/form/user/user-form.tsx

393 lines
13 KiB
TypeScript

"use client";
import React, { useEffect, useState } from "react";
import { useForm, Controller } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import { Card } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Button } from "@/components/ui/button";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { errorAutoClose, loading, successAutoClose } from "@/lib/swal";
import { close } from "@/config/swal";
import {
createUser,
updateUser,
getUserDetail,
CreateUserRequest,
UpdateUserRequest
} from "@/service/management-user/management-user";
import { getInfoProfile } from "@/service/auth";
import { getUserLevels } from "@/service/approval-workflows";
const createUserSchema = z.object({
address: z.string().trim().min(1, { message: "Address wajib diisi" }),
clientId: z.string().trim().min(1, { message: "Client ID wajib diisi" }),
dateOfBirth: z.string().trim().min(1, { message: "Date of Birth wajib diisi" }),
email: z.string().email({ message: "Email tidak valid" }),
fullname: z.string().trim().min(1, { message: "Full Name wajib diisi" }),
password: z.string().min(6, { message: "Password minimal 6 karakter" }),
phoneNumber: z.string().trim().min(1, { message: "Phone Number wajib diisi" }),
userLevelId: z.number({ invalid_type_error: "User Level harus dipilih" }),
username: z.string().trim().min(1, { message: "Username wajib diisi" }),
});
const editUserSchema = z.object({
address: z.string().trim().min(1, { message: "Address wajib diisi" }),
clientId: z.string().trim().min(1, { message: "Client ID wajib diisi" }),
dateOfBirth: z.string().trim().min(1, { message: "Date of Birth wajib diisi" }),
email: z.string().email({ message: "Email tidak valid" }),
fullname: z.string().trim().min(1, { message: "Full Name wajib diisi" }),
password: z.string().min(6, { message: "Password minimal 6 karakter" }).optional(),
phoneNumber: z.string().trim().min(1, { message: "Phone Number wajib diisi" }),
userLevelId: z.number({ invalid_type_error: "User Level harus dipilih" }),
username: z.string().trim().min(1, { message: "Username wajib diisi" }),
});
type CreateUserSchema = z.infer<typeof createUserSchema>;
type EditUserSchema = z.infer<typeof editUserSchema>;
type UserSchema = CreateUserSchema | EditUserSchema;
interface UserFormProps {
id?: number;
initialData?: CreateUserRequest;
onSuccess?: () => void;
onCancel?: () => void;
mode?: "create" | "edit";
}
export default function UserForm({
id,
onSuccess,
onCancel,
mode = "create",
}: UserFormProps) {
const MySwal = withReactContent(Swal);
const [loadingData, setLoadingData] = useState(false);
const [userLevels, setUserLevels] = useState<{id: number; name: string}[]>([]);
const [currentClientId, setCurrentClientId] = useState<string>("");
const {
control,
handleSubmit,
setValue,
formState: { errors },
} = useForm<UserSchema>({
resolver: zodResolver(mode === "create" ? createUserSchema : editUserSchema),
defaultValues: {
address: "",
clientId: "",
dateOfBirth: "",
email: "",
fullname: "",
password: "",
phoneNumber: "",
userLevelId: 0,
username: "",
},
});
useEffect(() => {
async function loadData() {
setLoadingData(true);
try {
const [userLevelsResponse, userInfoResponse] = await Promise.all([
getUserLevels(),
getInfoProfile(),
]);
if (!userLevelsResponse?.error) {
setUserLevels(userLevelsResponse?.data?.data || []);
}
// Get clientId from current user info
if (!userInfoResponse?.error && userInfoResponse?.data?.data) {
const userInfo = userInfoResponse.data.data;
const clientId = userInfo.instituteId || "78356d32-52fa-4dfc-b836-6cebf4e3eead"; // fallback to default
setCurrentClientId(clientId);
setValue("clientId", clientId);
} else {
// Fallback to default clientId if UserInfo fails
const defaultClientId = "78356d32-52fa-4dfc-b836-6cebf4e3eead";
setCurrentClientId(defaultClientId);
setValue("clientId", defaultClientId);
}
// Load user detail if in edit mode
if (mode === "edit" && id) {
try {
const userResponse = await getUserDetail(id);
if (userResponse && !userResponse.error && userResponse.data) {
const user = userResponse.data;
setValue("address", user.address || "");
setValue("clientId", user.clientId || "");
setValue("dateOfBirth", user.dateOfBirth || "");
setValue("email", user.email || "");
setValue("fullname", user.fullname || "");
setValue("phoneNumber", user.phoneNumber || "");
setValue("userLevelId", user.userLevelId || 0);
setValue("username", user.username || "");
// Don't set password for edit mode
} else {
console.error("Gagal mengambil detail user:", userResponse?.message || "Unknown error");
}
} catch (error) {
console.error("Error loading user detail:", error);
}
}
} catch (err) {
console.error("Error loading data:", err);
} finally {
setLoadingData(false);
}
}
loadData();
}, [id, setValue, mode]);
const onSubmit = async (data: UserSchema) => {
try {
loading();
const payload: CreateUserRequest = {
address: data.address,
clientId: data.clientId,
dateOfBirth: data.dateOfBirth,
email: data.email,
fullname: data.fullname,
password: data.password || "",
phoneNumber: data.phoneNumber,
userLevelId: data.userLevelId,
userRoleId: 3, // Hardcoded as per requirement
username: data.username,
};
console.log("Payload dikirim ke API:", payload);
let response;
if (mode === "edit" && id) {
response = await updateUser(id, payload);
} else {
response = await createUser(payload);
}
close();
if (response?.error) {
errorAutoClose(response.message || `Gagal ${mode === "edit" ? "memperbarui" : "menyimpan"} data.`);
return;
}
successAutoClose(`Data berhasil ${mode === "edit" ? "diperbarui" : "disimpan"}.`);
setTimeout(() => {
if (onSuccess) onSuccess();
}, 3000);
} catch (err) {
close();
errorAutoClose(`Terjadi kesalahan saat ${mode === "edit" ? "memperbarui" : "menyimpan"} data.`);
console.error("User operation error:", err);
}
};
if (loadingData) {
return (
<Card className="p-6 w-full lg:w-2/3">
<div className="flex items-center justify-center py-8">
<div className="text-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900 mx-auto mb-4"></div>
<p>Loading data...</p>
</div>
</div>
</Card>
);
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Card className="p-6 w-full lg:w-2/3">
<h2 className="text-lg font-semibold mb-4">
{mode === "edit" ? "Edit User" : "Add New User"}
</h2>
<div className="space-y-4">
{/* Full Name */}
<div>
<Label>Full Name</Label>
<Controller
control={control}
name="fullname"
render={({ field }) => (
<Input {...field} placeholder="Masukkan full name" />
)}
/>
{errors.fullname && (
<p className="text-red-500 text-sm">{errors.fullname.message}</p>
)}
</div>
{/* Username */}
<div>
<Label>Username</Label>
<Controller
control={control}
name="username"
render={({ field }) => (
<Input {...field} placeholder="Masukkan username" />
)}
/>
{errors.username && (
<p className="text-red-500 text-sm">{errors.username.message}</p>
)}
</div>
{/* Email */}
<div>
<Label>Email</Label>
<Controller
control={control}
name="email"
render={({ field }) => (
<Input {...field} type="email" placeholder="Masukkan email" />
)}
/>
{errors.email && (
<p className="text-red-500 text-sm">{errors.email.message}</p>
)}
</div>
{/* Password - Only show for create mode */}
{mode === "create" && (
<div>
<Label>Password</Label>
<Controller
control={control}
name="password"
render={({ field }) => (
<Input {...field} type="password" placeholder="Masukkan password" />
)}
/>
{errors.password && (
<p className="text-red-500 text-sm">{errors.password.message}</p>
)}
</div>
)}
{/* Phone Number */}
<div>
<Label>Phone Number</Label>
<Controller
control={control}
name="phoneNumber"
render={({ field }) => (
<Input {...field} placeholder="Masukkan phone number" />
)}
/>
{errors.phoneNumber && (
<p className="text-red-500 text-sm">{errors.phoneNumber.message}</p>
)}
</div>
{/* Address */}
<div>
<Label>Address</Label>
<Controller
control={control}
name="address"
render={({ field }) => (
<Input {...field} placeholder="Masukkan address" />
)}
/>
{errors.address && (
<p className="text-red-500 text-sm">{errors.address.message}</p>
)}
</div>
{/* Date of Birth */}
<div>
<Label>Date of Birth</Label>
<Controller
control={control}
name="dateOfBirth"
render={({ field }) => (
<Input {...field} type="date" />
)}
/>
{errors.dateOfBirth && (
<p className="text-red-500 text-sm">{errors.dateOfBirth.message}</p>
)}
</div>
{/* Client ID */}
<div>
<Label>Client ID</Label>
<Controller
control={control}
name="clientId"
render={({ field }) => (
<Input
{...field}
placeholder="Client ID akan diambil dari profil Anda"
readOnly
className="bg-gray-50"
/>
)}
/>
{errors.clientId && (
<p className="text-red-500 text-sm">{errors.clientId.message}</p>
)}
<p className="text-xs text-gray-500 mt-1">
Client ID diambil dari profil pengguna yang sedang login
</p>
</div>
{/* User Level */}
<div>
<Label>User Level</Label>
<Controller
control={control}
name="userLevelId"
render={({ field }) => (
<Select onValueChange={(value) => field.onChange(Number(value))} value={field.value?.toString() || ""}>
<SelectTrigger>
<SelectValue placeholder="Pilih user level" />
</SelectTrigger>
<SelectContent>
{userLevels.length > 0 ? (
userLevels.map((level) => (
<SelectItem key={level.id} value={level.id.toString()}>
{level.name}
</SelectItem>
))
) : (
<SelectItem value="no-data" disabled>
Tidak ada data tersedia
</SelectItem>
)}
</SelectContent>
</Select>
)}
/>
{errors.userLevelId && (
<p className="text-red-500 text-sm">{errors.userLevelId.message}</p>
)}
</div>
</div>
{/* Action Buttons */}
<div className="mt-6 flex justify-end gap-3">
<Button type="submit">
{mode === "edit" ? "Update" : "Create"}
</Button>
<Button type="button" variant="outline" onClick={() => onCancel?.()}>
Cancel
</Button>
</div>
</Card>
</form>
);
}