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:
@@ -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">
|
||||||
|
|||||||
@@ -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
15
todo.md
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user