Checkpoint: Refonte complète du système de rappels pour permettre une gestion granulaire par séquence

Fonctionnalités ajoutées :
- Table de liaison rappelSequences pour associer rappels et séquences
- Composant SequenceMultiSelect avec recherche et multi-sélection
- Gestion des associations dans les procédures create/update
- Modification du scheduler pour filtrer les séquences selon les associations
- Si aucune séquence n'est sélectionnée, le rappel s'applique à toutes les séquences (comportement par défaut)

Le système permet maintenant de configurer des rappels spécifiques pour certaines dates/séquences particulières, offrant une flexibilité complète dans la gestion des rappels automatiques.
This commit is contained in:
Manus
2026-01-08 08:27:20 -05:00
parent 5de295a18c
commit 6ee87f9b22
12 changed files with 2462 additions and 15 deletions

View File

@@ -0,0 +1,122 @@
import { useState } from "react";
import { Check, ChevronsUpDown } from "lucide-react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
} from "@/components/ui/command";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Badge } from "@/components/ui/badge";
interface Sequence {
id: number;
nom: string;
formationNom?: string;
}
interface SequenceMultiSelectProps {
sequences: Sequence[];
selectedIds: number[];
onChange: (selectedIds: number[]) => void;
placeholder?: string;
}
export function SequenceMultiSelect({
sequences,
selectedIds,
onChange,
placeholder = "Sélectionnez des séquences...",
}: SequenceMultiSelectProps) {
const [open, setOpen] = useState(false);
const toggleSequence = (sequenceId: number) => {
const newSelection = selectedIds.includes(sequenceId)
? selectedIds.filter((id) => id !== sequenceId)
: [...selectedIds, sequenceId];
onChange(newSelection);
};
const selectedSequences = sequences.filter((seq) =>
selectedIds.includes(seq.id)
);
return (
<div className="space-y-2">
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className="w-full justify-between"
>
{selectedIds.length === 0 ? (
<span className="text-muted-foreground">{placeholder}</span>
) : (
<span>
{selectedIds.length} séquence(s) sélectionnée(s)
</span>
)}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-full p-0" align="start">
<Command>
<CommandInput placeholder="Rechercher une séquence..." />
<CommandEmpty>Aucune séquence trouvée.</CommandEmpty>
<CommandGroup className="max-h-64 overflow-auto">
{sequences.map((sequence) => (
<CommandItem
key={sequence.id}
onSelect={() => toggleSequence(sequence.id)}
className="cursor-pointer"
>
<Check
className={cn(
"mr-2 h-4 w-4",
selectedIds.includes(sequence.id)
? "opacity-100"
: "opacity-0"
)}
/>
<div className="flex flex-col">
<span>{sequence.nom}</span>
{sequence.formationNom && (
<span className="text-xs text-muted-foreground">
{sequence.formationNom}
</span>
)}
</div>
</CommandItem>
))}
</CommandGroup>
</Command>
</PopoverContent>
</Popover>
{selectedSequences.length > 0 && (
<div className="flex flex-wrap gap-2">
{selectedSequences.map((sequence) => (
<Badge
key={sequence.id}
variant="secondary"
className="cursor-pointer hover:bg-destructive hover:text-destructive-foreground"
onClick={() => toggleSequence(sequence.id)}
>
{sequence.nom}
<span className="ml-1">×</span>
</Badge>
))}
</div>
)}
</div>
);
}