Checkpoint: Ajout de la prévisualisation des questionnaires

 **Nouvelle fonctionnalité** :
- Bouton "Prévisualiser" dans la page d'édition de questionnaire
- Modal de prévisualisation avec rendu exact du questionnaire
- Affichage de tous les types de questions (texte libre, choix multiple, échelle, oui/non)
- Désactivation des champs pour montrer le rendu final
- Indication des questions obligatoires avec astérisque rouge

Le système de questionnaires est maintenant complet avec :
- Création et édition de questionnaires 
- Prévisualisation avant envoi 
- Envoi automatique post-formation 
- Analyse des résultats avec graphiques 

Prochaines étapes suggérées :
- Exports Excel/PDF des feedbacks
- Tests complets du système
- Déploiement sur le serveur VPS
This commit is contained in:
Manus Sandbox
2025-12-07 12:49:41 -05:00
parent cc8f634c16
commit b21ad8f978
7 changed files with 140 additions and 1 deletions

View File

@@ -8,7 +8,10 @@ import { Textarea } from "@/components/ui/textarea";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { Plus, Trash2, GripVertical, Save } from "lucide-react";
import { Plus, Trash2, GripVertical, Save, Eye } from "lucide-react";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Slider } from "@/components/ui/slider";
import { toast } from "sonner";
import DashboardLayout from "@/components/DashboardLayout";
@@ -39,6 +42,7 @@ export default function AdminQuestionnaireEdit() {
const [envoiAutomatique, setEnvoiAutomatique] = useState(false);
const [delaiEnvoiJours, setDelaiEnvoiJours] = useState(1);
const [questions, setQuestions] = useState<Question[]>([]);
const [showPreview, setShowPreview] = useState(false);
const { data: questionnaire } = trpc.questionnaires.getById.useQuery(
{ id: questionnaireId! },
@@ -412,6 +416,15 @@ export default function AdminQuestionnaireEdit() {
<Button type="button" variant="outline" onClick={() => navigate("/admin/questionnaires")}>
Annuler
</Button>
<Button
type="button"
variant="secondary"
onClick={() => setShowPreview(true)}
disabled={questions.length === 0}
>
<Eye className="mr-2 h-4 w-4" />
Prévisualiser
</Button>
<Button type="submit" disabled={createMutation.isPending || updateMutation.isPending}>
<Save className="mr-2 h-4 w-4" />
{questionnaireId ? "Enregistrer" : "Créer"}
@@ -419,6 +432,82 @@ export default function AdminQuestionnaireEdit() {
</div>
</form>
</div>
{/* Modal de prévisualisation */}
<Dialog open={showPreview} onOpenChange={setShowPreview}>
<DialogContent className="max-w-3xl max-h-[80vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>{nom || "Questionnaire sans titre"}</DialogTitle>
{description && <p className="text-sm text-muted-foreground mt-2">{description}</p>}
</DialogHeader>
<div className="space-y-6 mt-4">
{questions.map((question, index) => (
<div key={index} className="space-y-3">
<div className="font-medium">
{index + 1}. {question.texte}
{question.obligatoire && <span className="text-red-500 ml-1">*</span>}
</div>
{question.typeQuestion === "texte_libre" && (
<Textarea
placeholder="Votre réponse..."
disabled
className="bg-muted"
/>
)}
{question.typeQuestion === "oui_non" && (
<RadioGroup disabled>
<div className="flex items-center space-x-2">
<RadioGroupItem value="oui" id={`preview-${index}-oui`} />
<Label htmlFor={`preview-${index}-oui`}>Oui</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="non" id={`preview-${index}-non`} />
<Label htmlFor={`preview-${index}-non`}>Non</Label>
</div>
</RadioGroup>
)}
{question.typeQuestion === "choix_multiple" && (
<RadioGroup disabled>
{(question.options || "").split("\n").filter(o => o.trim()).map((option, optIndex) => (
<div key={optIndex} className="flex items-center space-x-2">
<RadioGroupItem value={option} id={`preview-${index}-${optIndex}`} />
<Label htmlFor={`preview-${index}-${optIndex}`}>{option}</Label>
</div>
))}
</RadioGroup>
)}
{question.typeQuestion === "echelle" && (
<div className="space-y-2">
<div className="flex justify-between text-sm text-muted-foreground">
<span>{question.echelleLabelMin || `${question.echelleMin || 1}`}</span>
<span>{question.echelleLabelMax || `${question.echelleMax || 5}`}</span>
</div>
<Slider
disabled
min={question.echelleMin || 1}
max={question.echelleMax || 5}
step={1}
defaultValue={[Math.floor(((question.echelleMin || 1) + (question.echelleMax || 5)) / 2)]}
className="w-full"
/>
<div className="flex justify-between text-xs text-muted-foreground">
{Array.from(
{ length: (question.echelleMax || 5) - (question.echelleMin || 1) + 1 },
(_, i) => (question.echelleMin || 1) + i
).map((val) => (
<span key={val}>{val}</span>
))}
</div>
</div>
)}
</div>
))}
</div>
</DialogContent>
</Dialog>
</DashboardLayout>
);
}