258 lines
6.7 KiB
TypeScript
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>
|
|
);
|
|
}
|