Files
latosa-escrima/frontend/components/member-dialog.tsx

376 lines
8.9 KiB
TypeScript

import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectTrigger,
SelectValue,
} from "./ui/select";
import { useApi } from "@/hooks/use-api";
import { Role, User } from "@/types/types";
import { Badge } from "./ui/badge";
import { Building, X } from "lucide-react";
// Define the Role schema based on assumed Role type
const roleSchema = z.object({
id: z.string().min(1, "Role ID is required"),
name: z.string().min(1, "Role name is required"),
});
const memberSchema = z.object({
userId: z.string().optional(),
firstname: z.string().min(1, "Prénom est requis."),
lastname: z.string().min(1, "Nom de famille est requis."),
email: z.string().email("Adresse email invalide."),
password: z
.string()
.min(6, "Le mot de passe doit avoir au moins 6 caractères.")
.optional(),
phone: z.string().regex(/^\d{10}$/, "Le numéro doit avoir 10 chiffres."),
roles: z
.array(roleSchema)
.min(1, "At least one role is required")
.optional(),
});
const updateMemberSchema = memberSchema.partial();
export type Member = z.infer<typeof memberSchema>;
interface MemberDialogProps {
isOpen: boolean;
onClose: () => void;
member: Member | null;
onSave: (member: Member) => void;
}
export default function MemberDialog({
isOpen,
onClose,
member,
onSave,
}: MemberDialogProps) {
const schema = member?.userId ? updateMemberSchema : memberSchema;
const { data: availableRoles = [] } = useApi<Role[]>("/roles", {}, true); // Fetch roles
const form = useForm<Member>({
resolver: zodResolver(schema),
defaultValues: member?.userId
? member
: {
userId: "",
firstname: "",
lastname: "",
email: "",
password: "",
phone: "",
roles: [], // Default to empty array
},
});
useEffect(() => {
if (member) {
form.reset(member);
} else {
form.reset({
userId: "",
firstname: "",
lastname: "",
email: "",
password: "",
phone: "",
roles: [],
});
}
}, [member, form]);
const [selectedRole, setSelectedRole] = useState<Role | null>(null);
const addRole = () => {
if (selectedRole) {
const currentRoles = form.getValues("roles");
if (!currentRoles?.some((role) => role.id === selectedRole.id)) {
form.setValue(
"roles",
[...(currentRoles || []), selectedRole],
{
shouldValidate: true,
},
);
}
setSelectedRole(null); // Reset selection
}
};
const removeRole = (roleToRemove: Role) => {
const currentRoles = form.getValues("roles");
form.setValue(
"roles",
currentRoles?.filter((role) => role.id !== roleToRemove.id),
{ shouldValidate: true },
);
};
const onSubmit = (data: Member) => {
onSave(data);
onClose();
};
return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent>
<DialogHeader>
<DialogTitle>
{member
? "Mise à jour du membre"
: "Créer un nouveau membre"}
</DialogTitle>
</DialogHeader>
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-8"
>
<div className="grid gap-4">
{/* Firstname Field */}
<FormField
control={form.control}
name="firstname"
render={({ field }) => (
<FormItem className="grid gap-2">
<FormLabel htmlFor="firstname">
Prénom
</FormLabel>
<FormControl>
<Input
id="firstname"
placeholder="John"
type="text"
autoComplete="firstname"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Lastname Field */}
<FormField
control={form.control}
name="lastname"
render={({ field }) => (
<FormItem className="grid gap-2">
<FormLabel htmlFor="lastname">
Nom
</FormLabel>
<FormControl>
<Input
id="lastname"
placeholder="Doe"
type="text"
autoComplete="lastname"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Email Field */}
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem className="grid gap-2">
<FormLabel htmlFor="email">
Email
</FormLabel>
<FormControl>
<Input
id="email"
placeholder="johndoe@mail.com"
type="email"
autoComplete="email webauthn"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Password Field (only for new members) */}
{!member?.userId && (
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem className="grid gap-2">
<FormLabel htmlFor="password">
Mot de passe
</FormLabel>
<FormControl>
<Input
id="password"
placeholder=""
type="password"
autoComplete="new-password webauthn"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
)}
{/* Phone Field */}
<FormField
control={form.control}
name="phone"
render={({ field }) => (
<FormItem className="grid gap-2">
<FormLabel htmlFor="phone">
Phone number
</FormLabel>
<FormControl>
<Input
id="phone"
placeholder="0648265441"
type="tel"
autoComplete="phone"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Roles Field */}
<FormField
control={form.control}
name="roles"
render={({ field }) => (
<FormItem className="grid gap-2">
<FormLabel>Roles</FormLabel>
<div className="flex flex-wrap gap-2">
{field.value?.map((role) => (
<Badge
key={role.id}
variant="secondary"
className="text-sm py-1 px-2"
>
{role.name}
<button
onClick={() =>
removeRole(role)
}
className="ml-2 text-gray-500 hover:text-gray-700"
>
<X className="h-3 w-3" />
</button>
</Badge>
))}
</div>
<div className="flex space-x-2 mt-2">
<Select
value={
selectedRole
? selectedRole.name
: ""
}
onValueChange={(value) => {
const role =
availableRoles.find(
(r) =>
r.name ===
value,
);
if (role)
setSelectedRole(role);
}}
>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Sélectionner un rôle" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>
Roles
</SelectLabel>
{availableRoles
.filter(
(role) =>
!field.value?.some(
(r) =>
r.id ===
role.id,
),
)
.map((role) => (
<SelectItem
key={
role.id
}
value={
role.name
}
>
{role.name}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
<Button
type="button"
disabled={!selectedRole}
onClick={addRole}
className="flex items-center"
>
<Building className="mr-2 h-4 w-4" />
Ajouter le rôle
</Button>
</div>
<FormMessage />
</FormItem>
)}
/>
</div>
<DialogFooter>
<Button type="submit">
{member ? "Actualiser" : "Ajouter"}
</Button>
</DialogFooter>
</form>
</Form>
</DialogContent>
</Dialog>
);
}