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:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user