330 lines
10 KiB
TypeScript
330 lines
10 KiB
TypeScript
|
|
"use client";
|
||
|
|
|
||
|
|
import { Button } from "@/shared/components/ui/button";
|
||
|
|
import {
|
||
|
|
Form,
|
||
|
|
FormControl,
|
||
|
|
FormField,
|
||
|
|
FormItem,
|
||
|
|
FormLabel,
|
||
|
|
FormMessage,
|
||
|
|
} from "@/shared/components/ui/form";
|
||
|
|
import { Input } from "@/shared/components/ui/input";
|
||
|
|
import { Switch } from "@/shared/components/ui/switch";
|
||
|
|
import { ImageUpload } from "@/shared/components/image-upload";
|
||
|
|
import {
|
||
|
|
Select,
|
||
|
|
SelectContent,
|
||
|
|
SelectItem,
|
||
|
|
SelectTrigger,
|
||
|
|
SelectValue,
|
||
|
|
} from "@/shared/components/ui/select";
|
||
|
|
|
||
|
|
import { useForm } from "react-hook-form";
|
||
|
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||
|
|
import { z } from "zod";
|
||
|
|
|
||
|
|
import { userSchema } from "@/shared/schemas/user"; // You should define this Zod schema
|
||
|
|
import { useQuery } from "@tanstack/react-query";
|
||
|
|
import organizationApi from "@/shared/services/organization";
|
||
|
|
import { Organization } from "@/shared/types/organization";
|
||
|
|
import { Role } from "@/shared/types/role";
|
||
|
|
import roleApi from "@/shared/services/role";
|
||
|
|
import { getRoleLabelById } from "@/shared/config/role";
|
||
|
|
import { User } from "@/shared/types/user";
|
||
|
|
|
||
|
|
type UserFormValues = z.infer<typeof userSchema>;
|
||
|
|
|
||
|
|
// Helper component for required field labels
|
||
|
|
const RequiredFormLabel = ({ children }: { children: React.ReactNode }) => (
|
||
|
|
<FormLabel>
|
||
|
|
{children} <span className="text-red-500">*</span>
|
||
|
|
</FormLabel>
|
||
|
|
);
|
||
|
|
|
||
|
|
interface UserFormProps {
|
||
|
|
defaultValues?: Partial<User>;
|
||
|
|
onSubmitAction: (data: UserFormValues) => void;
|
||
|
|
isSubmitting?: boolean;
|
||
|
|
onCancelAction?: () => void;
|
||
|
|
isAdministrator?: boolean;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function UserForm({
|
||
|
|
defaultValues,
|
||
|
|
onSubmitAction,
|
||
|
|
isSubmitting,
|
||
|
|
onCancelAction,
|
||
|
|
isAdministrator = false,
|
||
|
|
}: UserFormProps) {
|
||
|
|
const form = useForm<UserFormValues>({
|
||
|
|
resolver: zodResolver(userSchema),
|
||
|
|
defaultValues: {
|
||
|
|
name: defaultValues?.name || "",
|
||
|
|
email: defaultValues?.email || "",
|
||
|
|
username: defaultValues?.username || "",
|
||
|
|
employee_id: defaultValues?.employee_id || "",
|
||
|
|
position: defaultValues?.position || "",
|
||
|
|
profile_picture: defaultValues?.profile_picture || "",
|
||
|
|
role_id: defaultValues?.role?.id || "",
|
||
|
|
organization_id: defaultValues?.organization?.id || "",
|
||
|
|
is_active: defaultValues?.is_active ?? true,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
const { data: organizations, isLoading: isOrgLoading } = useQuery<
|
||
|
|
Organization[]
|
||
|
|
>({
|
||
|
|
queryKey: ["organizations"],
|
||
|
|
queryFn: () =>
|
||
|
|
organizationApi.getOrganizations().then((response) => response.items),
|
||
|
|
});
|
||
|
|
const { data: roles, isLoading: isRolesLoading } = useQuery<Role[]>({
|
||
|
|
queryKey: ["roles"],
|
||
|
|
queryFn: () => roleApi.getRoles().then((response) => response.items),
|
||
|
|
});
|
||
|
|
|
||
|
|
return (
|
||
|
|
<Form {...form}>
|
||
|
|
<form onSubmit={form.handleSubmit(onSubmitAction)} className="space-y-6">
|
||
|
|
<FormField
|
||
|
|
control={form.control}
|
||
|
|
name="name"
|
||
|
|
render={({ field }) => (
|
||
|
|
<FormItem>
|
||
|
|
<RequiredFormLabel>Nama Lengkap</RequiredFormLabel>
|
||
|
|
<FormControl>
|
||
|
|
<Input placeholder="Masukkan nama lengkap" {...field} />
|
||
|
|
</FormControl>
|
||
|
|
<FormMessage />
|
||
|
|
</FormItem>
|
||
|
|
)}
|
||
|
|
/>
|
||
|
|
|
||
|
|
<FormField
|
||
|
|
control={form.control}
|
||
|
|
name="username"
|
||
|
|
render={({ field }) => (
|
||
|
|
<FormItem>
|
||
|
|
<RequiredFormLabel>Username</RequiredFormLabel>
|
||
|
|
<FormControl>
|
||
|
|
<Input placeholder="Masukkan username" {...field} />
|
||
|
|
</FormControl>
|
||
|
|
<FormMessage />
|
||
|
|
</FormItem>
|
||
|
|
)}
|
||
|
|
/>
|
||
|
|
|
||
|
|
<FormField
|
||
|
|
control={form.control}
|
||
|
|
name="password"
|
||
|
|
render={({ field }) => (
|
||
|
|
<FormItem>
|
||
|
|
<RequiredFormLabel>Password</RequiredFormLabel>
|
||
|
|
<FormControl>
|
||
|
|
<Input
|
||
|
|
type="password"
|
||
|
|
placeholder="Masukkan password"
|
||
|
|
{...field}
|
||
|
|
/>
|
||
|
|
</FormControl>
|
||
|
|
<FormMessage />
|
||
|
|
</FormItem>
|
||
|
|
)}
|
||
|
|
/>
|
||
|
|
|
||
|
|
<FormField
|
||
|
|
control={form.control}
|
||
|
|
name="confirm_password"
|
||
|
|
render={({ field }) => (
|
||
|
|
<FormItem>
|
||
|
|
<RequiredFormLabel>Konfirmasi Password</RequiredFormLabel>
|
||
|
|
<FormControl>
|
||
|
|
<Input
|
||
|
|
type="password"
|
||
|
|
placeholder="Ulangi password"
|
||
|
|
{...field}
|
||
|
|
/>
|
||
|
|
</FormControl>
|
||
|
|
<FormMessage />
|
||
|
|
</FormItem>
|
||
|
|
)}
|
||
|
|
/>
|
||
|
|
|
||
|
|
<FormField
|
||
|
|
control={form.control}
|
||
|
|
name="email"
|
||
|
|
render={({ field }) => (
|
||
|
|
<FormItem>
|
||
|
|
<RequiredFormLabel>Email</RequiredFormLabel>
|
||
|
|
<FormControl>
|
||
|
|
<Input placeholder="Masukkan email" type="email" {...field} />
|
||
|
|
</FormControl>
|
||
|
|
<FormMessage />
|
||
|
|
</FormItem>
|
||
|
|
)}
|
||
|
|
/>
|
||
|
|
|
||
|
|
<FormField
|
||
|
|
control={form.control}
|
||
|
|
name="employee_id"
|
||
|
|
render={({ field }) => (
|
||
|
|
<FormItem>
|
||
|
|
<FormLabel>NIP</FormLabel>
|
||
|
|
<FormControl>
|
||
|
|
<Input placeholder="Masukkan NIP" {...field} />
|
||
|
|
</FormControl>
|
||
|
|
<FormMessage />
|
||
|
|
</FormItem>
|
||
|
|
)}
|
||
|
|
/>
|
||
|
|
|
||
|
|
<FormField
|
||
|
|
control={form.control}
|
||
|
|
name="position"
|
||
|
|
render={({ field }) => (
|
||
|
|
<FormItem>
|
||
|
|
<FormLabel>Jabatan</FormLabel>
|
||
|
|
<FormControl>
|
||
|
|
<Input placeholder="Masukkan jabatan" {...field} />
|
||
|
|
</FormControl>
|
||
|
|
<FormMessage />
|
||
|
|
</FormItem>
|
||
|
|
)}
|
||
|
|
/>
|
||
|
|
|
||
|
|
<FormField
|
||
|
|
control={form.control}
|
||
|
|
name="role_id"
|
||
|
|
render={({ field }) => (
|
||
|
|
<FormItem>
|
||
|
|
<RequiredFormLabel>Role</RequiredFormLabel>
|
||
|
|
<FormControl>
|
||
|
|
<Select
|
||
|
|
value={field.value}
|
||
|
|
onValueChange={field.onChange}
|
||
|
|
disabled={isRolesLoading}
|
||
|
|
>
|
||
|
|
<SelectTrigger className="w-full">
|
||
|
|
<SelectValue placeholder="Pilih role">
|
||
|
|
{roles?.find((role) => role.id === field.value)?.name
|
||
|
|
? getRoleLabelById(
|
||
|
|
roles?.find((role) => role.id === field.value)
|
||
|
|
?.name || ""
|
||
|
|
)
|
||
|
|
: ""}
|
||
|
|
</SelectValue>
|
||
|
|
</SelectTrigger>
|
||
|
|
<SelectContent>
|
||
|
|
{roles
|
||
|
|
?.filter(
|
||
|
|
(role) =>
|
||
|
|
isAdministrator || role.name !== "administrator"
|
||
|
|
)
|
||
|
|
.map((role: Role) => (
|
||
|
|
<SelectItem key={role.id} value={role.id}>
|
||
|
|
{getRoleLabelById(role.name)}
|
||
|
|
</SelectItem>
|
||
|
|
))}
|
||
|
|
</SelectContent>
|
||
|
|
</Select>
|
||
|
|
</FormControl>
|
||
|
|
<FormMessage />
|
||
|
|
</FormItem>
|
||
|
|
)}
|
||
|
|
/>
|
||
|
|
|
||
|
|
<FormField
|
||
|
|
control={form.control}
|
||
|
|
name="organization_id"
|
||
|
|
render={({ field }) => (
|
||
|
|
<FormItem>
|
||
|
|
<RequiredFormLabel>Organisasi</RequiredFormLabel>
|
||
|
|
<FormControl>
|
||
|
|
<Select
|
||
|
|
value={field.value}
|
||
|
|
onValueChange={field.onChange}
|
||
|
|
disabled={isOrgLoading}
|
||
|
|
>
|
||
|
|
<SelectTrigger className="w-full">
|
||
|
|
<SelectValue placeholder="Pilih organisasi">
|
||
|
|
{Array.isArray(organizations)
|
||
|
|
? organizations.find((org) => org.id === field.value)
|
||
|
|
?.name ?? ""
|
||
|
|
: ""}
|
||
|
|
</SelectValue>
|
||
|
|
</SelectTrigger>
|
||
|
|
<SelectContent>
|
||
|
|
{Array.isArray(organizations)
|
||
|
|
? organizations.map((org: Organization) => (
|
||
|
|
<SelectItem key={org.id} value={org.id}>
|
||
|
|
{org.name}
|
||
|
|
</SelectItem>
|
||
|
|
))
|
||
|
|
: null}
|
||
|
|
</SelectContent>
|
||
|
|
</Select>
|
||
|
|
</FormControl>
|
||
|
|
<FormMessage />
|
||
|
|
</FormItem>
|
||
|
|
)}
|
||
|
|
/>
|
||
|
|
|
||
|
|
<FormField
|
||
|
|
control={form.control}
|
||
|
|
name="profile_picture"
|
||
|
|
render={({ field }) => (
|
||
|
|
<FormItem>
|
||
|
|
<FormLabel>Foto Profil</FormLabel>
|
||
|
|
<FormControl>
|
||
|
|
<ImageUpload
|
||
|
|
value={field.value}
|
||
|
|
onChange={field.onChange}
|
||
|
|
onRemove={() => field.onChange("")}
|
||
|
|
/>
|
||
|
|
</FormControl>
|
||
|
|
<FormMessage />
|
||
|
|
</FormItem>
|
||
|
|
)}
|
||
|
|
/>
|
||
|
|
|
||
|
|
<FormField
|
||
|
|
control={form.control}
|
||
|
|
name="is_active"
|
||
|
|
render={({ field }) => (
|
||
|
|
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
|
||
|
|
<div className="space-y-0.5">
|
||
|
|
<FormLabel className="text-base">Status</FormLabel>
|
||
|
|
<div className="text-sm text-muted-foreground">
|
||
|
|
Aktifkan atau nonaktifkan pengguna
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<FormControl>
|
||
|
|
<Switch
|
||
|
|
checked={field.value}
|
||
|
|
onCheckedChange={field.onChange}
|
||
|
|
/>
|
||
|
|
</FormControl>
|
||
|
|
</FormItem>
|
||
|
|
)}
|
||
|
|
/>
|
||
|
|
|
||
|
|
<div className="flex justify-end space-x-4">
|
||
|
|
{onCancelAction && (
|
||
|
|
<Button
|
||
|
|
type="button"
|
||
|
|
variant="outline"
|
||
|
|
onClick={onCancelAction}
|
||
|
|
disabled={isSubmitting}
|
||
|
|
>
|
||
|
|
Batal
|
||
|
|
</Button>
|
||
|
|
)}
|
||
|
|
<Button type="submit" disabled={isSubmitting}>
|
||
|
|
{isSubmitting ? "Menyimpan..." : "Simpan"}
|
||
|
|
</Button>
|
||
|
|
</div>
|
||
|
|
</form>
|
||
|
|
</Form>
|
||
|
|
);
|
||
|
|
}
|