Checkpoint: Réorganisation complète de la modal des rappels : largeur optimisée (50vw), labels et champs sur une seule ligne horizontale pour Configuration générale et Timing d'envoi, pièces jointes compactes avec icône et textes alignés, 4 zones colorées distinctes, espacement optimisé pour écran 24 pouces

This commit is contained in:
Manus
2026-02-02 08:00:46 -05:00
parent 014c2cd68b
commit fec271c19a
3 changed files with 263 additions and 239 deletions

View File

@@ -116,31 +116,28 @@ export function FileUpload({ value, onChange, accept, maxSize = 10, label = "Pi
{!value && !uploading ? ( {!value && !uploading ? (
<label className="block"> <label className="block">
<div className="relative border-2 border-dashed rounded-xl p-6 text-center cursor-pointer transition-all duration-300 ease-in-out border-gray-300 bg-gray-50 hover:border-blue-400 hover:bg-blue-50"> <div className="relative border-2 border-dashed rounded-xl p-4 cursor-pointer transition-all duration-300 ease-in-out border-gray-300 bg-gray-50 hover:border-blue-400 hover:bg-blue-50">
<div className="space-y-3"> <div className="flex items-center gap-4">
<div className="mx-auto w-14 h-14 bg-gradient-to-br from-blue-100 to-purple-100 rounded-full flex items-center justify-center"> <div className="w-12 h-12 bg-gradient-to-br from-blue-100 to-purple-100 rounded-full flex items-center justify-center flex-shrink-0">
<FileText className="w-7 h-7 text-blue-600" /> <FileText className="w-6 h-6 text-blue-600" />
</div> </div>
<div> <div className="flex-1">
<p className="text-sm font-semibold text-gray-700 mb-1"> <p className="text-sm font-semibold text-gray-700">
Cliquez pour choisir un fichier Cliquez pour choisir un fichier <span className="text-xs text-gray-500 font-normal">ou glissez-déposez</span>
</p> </p>
<p className="text-xs text-gray-500"> <div className="flex items-center gap-2 text-xs text-gray-400 mt-1">
ou glissez-déposez votre fichier ici
</p>
</div>
<div className="flex items-center justify-center gap-2 text-xs text-gray-400">
{accept && ( {accept && (
<span className="px-2 py-1 bg-white rounded-full border border-gray-200"> <span className="px-2 py-0.5 bg-white rounded-full border border-gray-200">
{accept} {accept}
</span> </span>
)} )}
<span className="px-2 py-1 bg-white rounded-full border border-gray-200"> <span className="px-2 py-0.5 bg-white rounded-full border border-gray-200">
Max {maxSize} Mo Max {maxSize} Mo
</span> </span>
</div> </div>
</div> </div>
</div> </div>
</div>
<input <input
type="file" type="file"
accept={accept} accept={accept}

View File

@@ -586,7 +586,7 @@ export default function AdminRappels() {
{/* Dialog de création */} {/* Dialog de création */}
<Dialog open={isCreateOpen} onOpenChange={setIsCreateOpen}> <Dialog open={isCreateOpen} onOpenChange={setIsCreateOpen}>
<DialogContent className="max-w-2xl"> <DialogContent className="!max-w-[2000px] w-[50vw]">
<DialogHeader> <DialogHeader>
<DialogTitle>Créer un nouveau rappel</DialogTitle> <DialogTitle>Créer un nouveau rappel</DialogTitle>
<DialogDescription> <DialogDescription>
@@ -594,14 +594,18 @@ export default function AdminRappels() {
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<div className="space-y-4"> <div className="space-y-4">
<div className="space-y-2"> {/* Zone 1: Template + Nom + Rappel actif */}
<Label htmlFor="templateType">Template d'email utilisé *</Label> <div className="border-l-4 border-blue-500 pl-4 py-2 bg-blue-50/50 rounded-r">
<h3 className="text-sm font-semibold text-blue-700 mb-2">Configuration générale</h3>
<div className="flex items-center gap-6">
<div className="flex items-center gap-2 flex-1">
<Label htmlFor="templateType" className="whitespace-nowrap">Template *</Label>
<Select <Select
value={formData.templateType} value={formData.templateType}
onValueChange={handleTemplateTypeChange} onValueChange={handleTemplateTypeChange}
> >
<SelectTrigger> <SelectTrigger className="flex-1">
<SelectValue placeholder="Sélectionnez un type de rappel" /> <SelectValue placeholder="Sélectionnez un type" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{TEMPLATE_TYPES.map((template) => ( {TEMPLATE_TYPES.map((template) => (
@@ -611,23 +615,36 @@ export default function AdminRappels() {
))} ))}
</SelectContent> </SelectContent>
</Select> </Select>
<p className="text-sm text-muted-foreground">
Le template d'email correspondant sera utilisé pour l'envoi
</p>
</div> </div>
<div className="space-y-2"> <div className="flex items-center gap-2 flex-1">
<Label htmlFor="nom">Nom du rappel *</Label> <Label htmlFor="nom" className="whitespace-nowrap">Nom *</Label>
<Input <Input
id="nom" id="nom"
value={formData.nom} value={formData.nom}
onChange={(e) => setFormData({ ...formData, nom: e.target.value })} onChange={(e) => setFormData({ ...formData, nom: e.target.value })}
placeholder="Ex: Rappel automatique J-7" placeholder="Ex: Rappel J-7"
className="flex-1"
/> />
</div> </div>
<div className="space-y-2"> <div className="flex items-center gap-2">
<Label>Timing d'envoi *</Label> <Switch
id="actif"
checked={formData.actif}
onCheckedChange={(checked) => setFormData({ ...formData, actif: checked })}
/>
<Label htmlFor="actif" className="cursor-pointer whitespace-nowrap">Rappel actif</Label>
</div>
</div>
</div>
{/* Zone 2: Timing d'envoi */}
<div className="border-l-4 border-green-500 pl-4 py-2 bg-green-50/50 rounded-r">
<h3 className="text-sm font-semibold text-green-700 mb-2">Timing d'envoi</h3>
<div className="flex items-center gap-6">
<div className="flex items-center gap-4">
<Label className="whitespace-nowrap">Type *</Label>
<div className="flex gap-4"> <div className="flex gap-4">
<label className="flex items-center gap-2 cursor-pointer"> <label className="flex items-center gap-2 cursor-pointer">
<input <input
@@ -654,11 +671,9 @@ export default function AdminRappels() {
</div> </div>
</div> </div>
<div className="space-y-2"> <div className="flex items-center gap-2 flex-1">
<Label htmlFor="joursAvant"> <Label htmlFor="joursAvant" className="whitespace-nowrap">
{formData.timing === "pre_formation" {formData.timing === "pre_formation" ? "Jours avant *" : "Jours après *"}
? "Nombre de jours avant la séquence *"
: "Nombre de jours après la séquence *"}
</Label> </Label>
<Input <Input
id="joursAvant" id="joursAvant"
@@ -669,29 +684,26 @@ export default function AdminRappels() {
setFormData({ ...formData, joursAvant: parseInt(e.target.value) || 1 }) setFormData({ ...formData, joursAvant: parseInt(e.target.value) || 1 })
} }
placeholder="Ex: 7" placeholder="Ex: 7"
className="w-24"
/> />
<p className="text-sm text-muted-foreground">
{formData.timing === "pre_formation"
? "Le rappel sera envoyé ce nombre de jours avant le début de la séquence"
: "Le rappel sera envoyé ce nombre de jours après la fin de la séquence"}
</p>
</div> </div>
<div className="space-y-2"> <div className="flex items-center gap-2">
<Label htmlFor="heureEnvoi">Heure d'envoi *</Label> <Label htmlFor="heureEnvoi" className="whitespace-nowrap">Heure *</Label>
<Input <Input
id="heureEnvoi" id="heureEnvoi"
type="time" type="time"
value={formData.heureEnvoi} value={formData.heureEnvoi}
onChange={(e) => setFormData({ ...formData, heureEnvoi: e.target.value })} onChange={(e) => setFormData({ ...formData, heureEnvoi: e.target.value })}
className="w-32"
/> />
<p className="text-sm text-muted-foreground"> </div>
L'heure à laquelle le rappel sera envoyé chaque jour </div>
</p>
</div> </div>
<div className="space-y-2"> {/* Zone 3: Dates concernées */}
<Label>Dates de formation concernées (optionnel)</Label> <div className="border-l-4 border-purple-500 pl-4 py-2 bg-purple-50/50 rounded-r">
<h3 className="text-sm font-semibold text-purple-700 mb-3">Dates de formation concernées</h3>
<DateFormationMultiSelect <DateFormationMultiSelect
sequences={sequences?.map(seq => ({ sequences={sequences?.map(seq => ({
id: seq.id, id: seq.id,
@@ -709,6 +721,10 @@ export default function AdminRappels() {
/> />
</div> </div>
{/* Zone 4: Pièces jointes */}
<div className="border-l-4 border-orange-500 pl-4 py-2 bg-orange-50/50 rounded-r">
<h3 className="text-sm font-semibold text-orange-700 mb-2">Pièces jointes</h3>
<div className="space-y-3">
<FileUpload <FileUpload
label="Pièce jointe 1 (optionnel)" label="Pièce jointe 1 (optionnel)"
value={formData.fichier} value={formData.fichier}
@@ -724,14 +740,7 @@ export default function AdminRappels() {
accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.jpg,.jpeg,.png" accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.jpg,.jpeg,.png"
maxSize={10} maxSize={10}
/> />
</div>
<div className="flex items-center space-x-2">
<Switch
id="actif"
checked={formData.actif}
onCheckedChange={(checked) => setFormData({ ...formData, actif: checked })}
/>
<Label htmlFor="actif">Rappel actif</Label>
</div> </div>
</div> </div>
<DialogFooter> <DialogFooter>
@@ -747,7 +756,7 @@ export default function AdminRappels() {
{/* Dialog de modification */} {/* Dialog de modification */}
<Dialog open={isEditOpen} onOpenChange={setIsEditOpen}> <Dialog open={isEditOpen} onOpenChange={setIsEditOpen}>
<DialogContent className="max-w-2xl"> <DialogContent className="!max-w-[2000px] w-[50vw]">
<DialogHeader> <DialogHeader>
<DialogTitle>Modifier le rappel</DialogTitle> <DialogTitle>Modifier le rappel</DialogTitle>
<DialogDescription> <DialogDescription>
@@ -755,14 +764,18 @@ export default function AdminRappels() {
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<div className="space-y-4"> <div className="space-y-4">
<div className="space-y-2"> {/* Zone 1: Template + Nom + Rappel actif */}
<Label htmlFor="edit-templateType">Template d'email utilisé *</Label> <div className="border-l-4 border-blue-500 pl-4 py-2 bg-blue-50/50 rounded-r">
<h3 className="text-sm font-semibold text-blue-700 mb-2">Configuration générale</h3>
<div className="flex items-center gap-6">
<div className="flex items-center gap-2 flex-1">
<Label htmlFor="edit-templateType" className="whitespace-nowrap">Template *</Label>
<Select <Select
value={formData.templateType} value={formData.templateType}
onValueChange={handleTemplateTypeChange} onValueChange={handleTemplateTypeChange}
> >
<SelectTrigger> <SelectTrigger className="flex-1">
<SelectValue placeholder="Sélectionnez un type de rappel" /> <SelectValue placeholder="Sélectionnez un type" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{TEMPLATE_TYPES.map((template) => ( {TEMPLATE_TYPES.map((template) => (
@@ -774,18 +787,34 @@ export default function AdminRappels() {
</Select> </Select>
</div> </div>
<div className="space-y-2"> <div className="flex items-center gap-2 flex-1">
<Label htmlFor="edit-nom">Nom du rappel *</Label> <Label htmlFor="edit-nom" className="whitespace-nowrap">Nom *</Label>
<Input <Input
id="edit-nom" id="edit-nom"
value={formData.nom} value={formData.nom}
onChange={(e) => setFormData({ ...formData, nom: e.target.value })} onChange={(e) => setFormData({ ...formData, nom: e.target.value })}
placeholder="Ex: Rappel automatique J-7" placeholder="Ex: Rappel J-7"
className="flex-1"
/> />
</div> </div>
<div className="space-y-2"> <div className="flex items-center gap-2">
<Label>Timing d'envoi *</Label> <Switch
id="edit-actif"
checked={formData.actif}
onCheckedChange={(checked) => setFormData({ ...formData, actif: checked })}
/>
<Label htmlFor="edit-actif" className="cursor-pointer whitespace-nowrap">Rappel actif</Label>
</div>
</div>
</div>
{/* Zone 2: Timing d'envoi */}
<div className="border-l-4 border-green-500 pl-4 py-2 bg-green-50/50 rounded-r">
<h3 className="text-sm font-semibold text-green-700 mb-2">Timing d'envoi</h3>
<div className="flex items-center gap-6">
<div className="flex items-center gap-4">
<Label className="whitespace-nowrap">Type *</Label>
<div className="flex gap-4"> <div className="flex gap-4">
<label className="flex items-center gap-2 cursor-pointer"> <label className="flex items-center gap-2 cursor-pointer">
<input <input
@@ -812,11 +841,9 @@ export default function AdminRappels() {
</div> </div>
</div> </div>
<div className="space-y-2"> <div className="flex items-center gap-2 flex-1">
<Label htmlFor="edit-joursAvant"> <Label htmlFor="edit-joursAvant" className="whitespace-nowrap">
{formData.timing === "pre_formation" {formData.timing === "pre_formation" ? "Jours avant *" : "Jours après *"}
? "Nombre de jours avant la séquence *"
: "Nombre de jours après la séquence *"}
</Label> </Label>
<Input <Input
id="edit-joursAvant" id="edit-joursAvant"
@@ -827,26 +854,26 @@ export default function AdminRappels() {
setFormData({ ...formData, joursAvant: parseInt(e.target.value) || 1 }) setFormData({ ...formData, joursAvant: parseInt(e.target.value) || 1 })
} }
placeholder="Ex: 7" placeholder="Ex: 7"
className="w-24"
/> />
<p className="text-sm text-muted-foreground">
{formData.timing === "pre_formation"
? "Le rappel sera envoyé ce nombre de jours avant le début de la séquence"
: "Le rappel sera envoyé ce nombre de jours après la fin de la séquence"}
</p>
</div> </div>
<div className="space-y-2"> <div className="flex items-center gap-2">
<Label htmlFor="edit-heureEnvoi">Heure d'envoi *</Label> <Label htmlFor="edit-heureEnvoi" className="whitespace-nowrap">Heure *</Label>
<Input <Input
id="edit-heureEnvoi" id="edit-heureEnvoi"
type="time" type="time"
value={formData.heureEnvoi} value={formData.heureEnvoi}
onChange={(e) => setFormData({ ...formData, heureEnvoi: e.target.value })} onChange={(e) => setFormData({ ...formData, heureEnvoi: e.target.value })}
className="w-32"
/> />
</div> </div>
</div>
</div>
<div className="space-y-2"> {/* Zone 3: Dates concernées */}
<Label>Dates de formation concernées (optionnel)</Label> <div className="border-l-4 border-purple-500 pl-4 py-2 bg-purple-50/50 rounded-r">
<h3 className="text-sm font-semibold text-purple-700 mb-3">Dates de formation concernées</h3>
<DateFormationMultiSelect <DateFormationMultiSelect
sequences={sequences?.map(seq => ({ sequences={sequences?.map(seq => ({
id: seq.id, id: seq.id,
@@ -864,6 +891,10 @@ export default function AdminRappels() {
/> />
</div> </div>
{/* Zone 4: Pièces jointes */}
<div className="border-l-4 border-orange-500 pl-4 py-2 bg-orange-50/50 rounded-r">
<h3 className="text-sm font-semibold text-orange-700 mb-2">Pièces jointes</h3>
<div className="space-y-3">
<FileUpload <FileUpload
label="Pièce jointe 1 (optionnel)" label="Pièce jointe 1 (optionnel)"
value={formData.fichier} value={formData.fichier}
@@ -879,14 +910,7 @@ export default function AdminRappels() {
accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.jpg,.jpeg,.png" accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.jpg,.jpeg,.png"
maxSize={10} maxSize={10}
/> />
</div>
<div className="flex items-center space-x-2">
<Switch
id="edit-actif"
checked={formData.actif}
onCheckedChange={(checked) => setFormData({ ...formData, actif: checked })}
/>
<Label htmlFor="edit-actif">Rappel actif</Label>
</div> </div>
</div> </div>
<DialogFooter> <DialogFooter>

View File

@@ -1482,3 +1482,6 @@
- [x] Afficher immédiatement le nom du fichier sélectionné dans la boîte de dialogue - [x] Afficher immédiatement le nom du fichier sélectionné dans la boîte de dialogue
- [x] Appliquer la même ergonomie de boîte de dialogue pour les pièces jointes des rappels - [x] Appliquer la même ergonomie de boîte de dialogue pour les pièces jointes des rappels
- [ ] Réorganiser la boîte de dialogue des rappels pour réduire la hauteur et élargir la largeur
- [x] Déployer la réorganisation complète de la modal des rappels sur le VPS