ManagementSystem/TrackingToolWeb/client/src/pages/main/components/modals/user-modal.tsx

258 lines
6.7 KiB
TypeScript

/* eslint-disable @typescript-eslint/no-explicit-any */
"use client";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect, useState, type ReactNode } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { useConfirm } from "@/components/confirm-modal-provider";
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import useAppStore from "@/stores/use-app-store";
import { Camera, Loader } from "lucide-react";
import { checkingApi } from "@/api/checking-api";
import { toast } from "sonner";
interface UserModalProps {
user?: IUser | null;
onSave?: (user: IUser) => void;
children?: ReactNode;
trackingOpen?: boolean;
}
const userSchema = z.object({
name: z.string().min(1, "Tên là bắt buộc"),
email: z.string().email("Email không hợp lệ"),
avatar: z.string().optional(),
});
type UserFormData = z.infer<typeof userSchema>;
export function UserModal({
user,
onSave,
children,
trackingOpen,
}: UserModalProps) {
const confirm = useConfirm();
const isEditMode = !!user;
const [open, setOpenChange] = useState(false);
const [loading, setLoading] = useState(false);
const { captureRegisterImage, setCaptureRegisterImage } = useAppStore();
const form = useForm<UserFormData>({
resolver: zodResolver(userSchema),
defaultValues: {
name: "",
email: "",
avatar: "",
},
});
useEffect(() => {
if (user) {
form.reset({
name: user.name,
email: user.email,
avatar: user.avatar ?? "",
});
} else {
form.reset({
name: "",
email: "",
avatar: "",
});
}
}, [user, open, form]);
const handleClose = async () => {
const result = await confirm({
title: "Thông báo",
message: "Bạn muốn hủy đăng ký. Mọi dữ liệu bạn nhập sẽ bị mất",
confirmText: "Discard",
cancelText: "Hủy",
variant: "warning",
});
if (!result) return;
setOpenChange(false);
setCaptureRegisterImage(null);
};
const onSubmit = async (values: UserFormData) => {
try {
setLoading(true);
const dataToSubmit: IUser = {
// eslint-disable-next-line react-hooks/purity
id: user?.id || Date.now().toString(),
...values,
avatar: values.avatar || null,
};
const { data } = await checkingApi.register({
user: dataToSubmit,
file: captureRegisterImage,
});
console.log({ data });
onSave?.(dataToSubmit);
setOpenChange(false);
setCaptureRegisterImage(null);
toast.success(data?.message || "Đăng ký thành công !");
} catch (error) {
console.log({ error });
toast.error((error as any)?.message || "Internal Server Error");
} finally {
setLoading(false);
}
};
useEffect(() => {
if (trackingOpen === undefined) return;
setOpenChange(trackingOpen);
}, [trackingOpen]);
useEffect(() => {
if (!captureRegisterImage) return;
return () => {
URL.revokeObjectURL(captureRegisterImage);
};
}, [captureRegisterImage]);
return (
<Dialog
open={open}
onOpenChange={(nextOpen) => {
if (!nextOpen) {
handleClose();
return;
}
setOpenChange(true); // mở
}}
>
<DialogTrigger>{children}</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>
{isEditMode ? "Cập nhật thông tin" : "Tạo người dùng mới"}
</DialogTitle>
<DialogDescription>
{isEditMode
? "Cập nhật thông tin người dùng của bạn"
: "Nhập thông tin để tạo người dùng mới"}
</DialogDescription>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
{/* Name */}
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>
Tên <span className="text-red-600">*</span>
</FormLabel>
<FormControl>
<Input placeholder="Nhập tên người dùng" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Email */}
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>
Email <span className="text-red-600">*</span>
</FormLabel>
<FormControl>
<Input type="email" placeholder="Nhập email" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Avatar */}
<FormField
control={form.control}
name="avatar"
render={({ field }) => (
<FormItem>
<FormLabel>Avatar URL</FormLabel>
<FormControl>
<Input
placeholder="https://example.com/avatar.jpg"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* File */}
<FormLabel>nh từ camera</FormLabel>
<a target="_blank" href={URL.createObjectURL(captureRegisterImage)}>
<Button
size="sm"
variant="outline"
className="w-full bg-transparent"
type="button"
>
<Camera className="w-4 h-4 mr-2" />
Xem nh
</Button>
</a>
<div className="flex justify-end gap-2 pt-4">
<Button type="button" variant="outline" onClick={handleClose}>
Hủy
</Button>
<Button type="submit">
{!loading && isEditMode ? "Cập nhật" : "Tạo mới"}
{loading && <Loader className="animate-spin" />}
</Button>
</div>
</form>
</Form>
</DialogContent>
</Dialog>
);
}