Checkpoint: Trois améliorations majeures du champ formateur :

 **1. Affichage dans le tableau des séquences**
- Nouvelle colonne "Formateur" ajoutée au tableau de gestion
- Affiche le nom du formateur ou "-" si non renseigné

 **2. Liste prédéfinie avec autocomplétion**
- Nouvelle table `formateurs` dans la base de données
- Procédures tRPC (list, create, delete) pour gérer les formateurs
- Composant `FormateurCombobox` avec autocomplétion intelligente
- Possibilité d'ajouter rapidement un nouveau formateur depuis le formulaire
- Recherche en temps réel parmi les formateurs existants

 **3. Inclusion dans les emails d'invitation**
- Le nom du formateur est maintenant affiché dans les emails de confirmation d'inscription
- Affichage conditionnel (uniquement si un formateur est renseigné)
- Améliore la communication avec les apprenants

Ces améliorations standardisent la gestion des formateurs, évitent les doublons/fautes de frappe, et enrichissent l'information transmise aux apprenants.
This commit is contained in:
Manus Sandbox
2025-11-19 09:38:06 -05:00
parent 703d0e4961
commit 16351ea3d5
11 changed files with 1095 additions and 9 deletions

View File

@@ -0,0 +1,135 @@
import { useState } from "react";
import { Check, ChevronsUpDown, Plus } from "lucide-react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { trpc } from "@/lib/trpc";
import { toast } from "sonner";
interface FormateurComboboxProps {
value: string;
onChange: (value: string) => void;
placeholder?: string;
}
export function FormateurCombobox({ value, onChange, placeholder = "Sélectionner un formateur..." }: FormateurComboboxProps) {
const [open, setOpen] = useState(false);
const [searchValue, setSearchValue] = useState("");
const { data: formateurs = [], refetch } = trpc.formateurs.list.useQuery();
const createMutation = trpc.formateurs.create.useMutation({
onSuccess: () => {
toast.success("Formateur ajouté");
refetch();
},
onError: (error) => {
toast.error(error.message || "Erreur lors de l'ajout du formateur");
},
});
const handleAddNew = () => {
if (!searchValue.trim()) {
toast.error("Veuillez entrer un nom de formateur");
return;
}
// Vérifier si le formateur existe déjà
const exists = formateurs.some(f => f.nom.toLowerCase() === searchValue.toLowerCase());
if (exists) {
toast.error("Ce formateur existe déjà");
return;
}
createMutation.mutate({ nom: searchValue.trim() });
onChange(searchValue.trim());
setSearchValue("");
setOpen(false);
};
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className="w-full justify-between"
>
{value || placeholder}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-full p-0">
<Command>
<CommandInput
placeholder="Rechercher ou ajouter un formateur..."
value={searchValue}
onValueChange={setSearchValue}
/>
<CommandList>
<CommandEmpty>
<div className="flex flex-col items-center gap-2 py-2">
<p className="text-sm text-muted-foreground">Aucun formateur trouvé</p>
{searchValue && (
<Button
size="sm"
variant="outline"
onClick={handleAddNew}
disabled={createMutation.isPending}
>
<Plus className="mr-2 h-4 w-4" />
Ajouter "{searchValue}"
</Button>
)}
</div>
</CommandEmpty>
<CommandGroup>
{formateurs.map((formateur) => (
<CommandItem
key={formateur.id}
value={formateur.nom}
onSelect={(currentValue) => {
onChange(currentValue === value ? "" : currentValue);
setOpen(false);
setSearchValue("");
}}
>
<Check
className={cn(
"mr-2 h-4 w-4",
value === formateur.nom ? "opacity-100" : "opacity-0"
)}
/>
{formateur.nom}
</CommandItem>
))}
</CommandGroup>
{searchValue && !formateurs.some(f => f.nom.toLowerCase() === searchValue.toLowerCase()) && (
<CommandGroup>
<CommandItem
onSelect={handleAddNew}
className="text-primary"
>
<Plus className="mr-2 h-4 w-4" />
Ajouter "{searchValue}"
</CommandItem>
</CommandGroup>
)}
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
}