Checkpoint: V1.23 - Amélioration boîte de dialogue d'ajout manuel d'apprenant : tri alphabétique (nom puis prénom) et champ de recherche en temps réel sur nom/prénom/email

This commit is contained in:
Manus
2026-02-26 12:52:34 -05:00
parent feee0d803e
commit 429d6cf264
3 changed files with 70 additions and 17 deletions

View File

@@ -2,8 +2,10 @@ import { useState } from "react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Input } from "@/components/ui/input";
import { trpc } from "@/lib/trpc"; import { trpc } from "@/lib/trpc";
import { toast } from "sonner"; import { toast } from "sonner";
import { Search } from "lucide-react";
interface AddInscritFormProps { interface AddInscritFormProps {
sequenceId: number; sequenceId: number;
@@ -14,6 +16,7 @@ interface AddInscritFormProps {
export function AddInscritForm({ sequenceId, onSuccess, onCancel }: AddInscritFormProps) { export function AddInscritForm({ sequenceId, onSuccess, onCancel }: AddInscritFormProps) {
const [selectedApprenantId, setSelectedApprenantId] = useState<string>(""); const [selectedApprenantId, setSelectedApprenantId] = useState<string>("");
const [statut, setStatut] = useState<"confirme" | "liste_attente">("confirme"); const [statut, setStatut] = useState<"confirme" | "liste_attente">("confirme");
const [searchQuery, setSearchQuery] = useState<string>("");
// Récupérer tous les apprenants // Récupérer tous les apprenants
const { data: apprenants, isLoading: loadingApprenants } = trpc.apprenants.list.useQuery(); const { data: apprenants, isLoading: loadingApprenants } = trpc.apprenants.list.useQuery();
@@ -32,10 +35,28 @@ export function AddInscritForm({ sequenceId, onSuccess, onCancel }: AddInscritFo
}); });
// Filtrer les apprenants déjà inscrits // Filtrer les apprenants déjà inscrits
const apprenantsDisponibles = apprenants?.filter( let apprenantsDisponibles = apprenants?.filter(
(apprenant) => !inscriptions?.some((inscription) => inscription.apprenantId === apprenant.id) (apprenant) => !inscriptions?.some((inscription) => inscription.apprenantId === apprenant.id)
) || []; ) || [];
// Trier par ordre alphabétique (nom puis prénom)
apprenantsDisponibles = apprenantsDisponibles.sort((a, b) => {
const nomCompare = a.nom.localeCompare(b.nom, 'fr', { sensitivity: 'base' });
if (nomCompare !== 0) return nomCompare;
return a.prenom.localeCompare(b.prenom, 'fr', { sensitivity: 'base' });
});
// Filtrer par recherche
if (searchQuery.trim()) {
const query = searchQuery.toLowerCase();
apprenantsDisponibles = apprenantsDisponibles.filter(
(apprenant) =>
apprenant.nom.toLowerCase().includes(query) ||
apprenant.prenom.toLowerCase().includes(query) ||
apprenant.email.toLowerCase().includes(query)
);
}
const handleSubmit = (e: React.FormEvent) => { const handleSubmit = (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
@@ -57,10 +78,26 @@ export function AddInscritForm({ sequenceId, onSuccess, onCancel }: AddInscritFo
<Label htmlFor="apprenant">Apprenant *</Label> <Label htmlFor="apprenant">Apprenant *</Label>
{loadingApprenants ? ( {loadingApprenants ? (
<div className="text-sm text-muted-foreground">Chargement...</div> <div className="text-sm text-muted-foreground">Chargement...</div>
) : apprenantsDisponibles.length === 0 ? ( ) : apprenantsDisponibles.length === 0 && !searchQuery ? (
<div className="text-sm text-muted-foreground"> <div className="text-sm text-muted-foreground">
Tous les apprenants sont déjà inscrits à cette séquence Tous les apprenants sont déjà inscrits à cette séquence
</div> </div>
) : (
<>
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
type="text"
placeholder="Rechercher par nom, prénom ou email..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-9"
/>
</div>
{apprenantsDisponibles.length === 0 ? (
<div className="text-sm text-muted-foreground">
Aucun apprenant trouvé
</div>
) : ( ) : (
<Select value={selectedApprenantId} onValueChange={setSelectedApprenantId}> <Select value={selectedApprenantId} onValueChange={setSelectedApprenantId}>
<SelectTrigger id="apprenant"> <SelectTrigger id="apprenant">
@@ -69,12 +106,14 @@ export function AddInscritForm({ sequenceId, onSuccess, onCancel }: AddInscritFo
<SelectContent> <SelectContent>
{apprenantsDisponibles.map((apprenant) => ( {apprenantsDisponibles.map((apprenant) => (
<SelectItem key={apprenant.id} value={apprenant.id.toString()}> <SelectItem key={apprenant.id} value={apprenant.id.toString()}>
{apprenant.prenom} {apprenant.nom} ({apprenant.email}) {apprenant.nom} {apprenant.prenom} ({apprenant.email})
</SelectItem> </SelectItem>
))} ))}
</SelectContent> </SelectContent>
</Select> </Select>
)} )}
</>
)}
</div> </div>
<div className="space-y-2"> <div className="space-y-2">

View File

@@ -57,7 +57,7 @@ export async function sendInscriptionConfirmation(params: {
params.apprenantFonction === 'chef_service' ? 'Chef de service' : 'Autre'; params.apprenantFonction === 'chef_service' ? 'Chef de service' : 'Autre';
// Générer la liste des dates // Générer la liste des dates
const datesHTML = params.dates.map(date => ` const datesHTML = params.dates.map(date => (`
<div class="date-item"> <div class="date-item">
<strong>Date ${date.ordre} :</strong><br/> <strong>Date ${date.ordre} :</strong><br/>
Du ${date.dateDebut.toLocaleDateString('fr-FR', { Du ${date.dateDebut.toLocaleDateString('fr-FR', {
@@ -77,8 +77,7 @@ export async function sendInscriptionConfirmation(params: {
minute: '2-digit' minute: '2-digit'
})} })}
</div> </div>
`).join(''); `)).join('');
const content = ` const content = `
<h2>Confirmation d'inscription</h2> <h2>Confirmation d'inscription</h2>
<p>Bonjour ${fonctionLabel} ${params.apprenantPrenom} ${params.apprenantNom},</p> <p>Bonjour ${fonctionLabel} ${params.apprenantPrenom} ${params.apprenantNom},</p>

15
todo.md
View File

@@ -1678,3 +1678,18 @@
- [x] Corriger le code d'envoi des invitations (conversion Buffer vers base64 UTF-8) - [x] Corriger le code d'envoi des invitations (conversion Buffer vers base64 UTF-8)
- [x] Tester sur TEST - [x] Tester sur TEST
- [x] Déployer sur PROD - [x] Déployer sur PROD
## Amélioration - Liens "Ajouter au calendrier" dans les emails (ANNULÉE)
- [x] Concevoir la solution technique (data: URI avec base64)
- [x] Ajouter les boutons "Ajouter au calendrier" dans l'email de confirmation
- [x] Testé sur TEST
- [x] Annulé à la demande du client - retour à la version V1.22
## Amélioration - Boîte de dialogue d'ajout manuel d'apprenant
- [x] Localiser le composant de dialogue d'ajout manuel (AddInscritForm.tsx)
- [x] Ajouter le tri alphabétique de la liste des apprenants (nom puis prénom)
- [x] Ajouter un champ de recherche/filtre sur le nom, prénom et email
- [x] Tester sur TEST
- [x] Déployer sur PROD