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