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:
9
.manus/db/db-query-1765128325099.json
Normal file
9
.manus/db/db-query-1765128325099.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"query": "CREATE TABLE IF NOT EXISTS `questionnaires` (\n `id` int AUTO_INCREMENT NOT NULL,\n `nom` varchar(255) NOT NULL,\n `type` enum('satisfaction', 'evaluation_pre', 'evaluation_post') NOT NULL,\n `description` text,\n `formationId` int,\n `actif` boolean NOT NULL DEFAULT true,\n `envoiAutomatique` boolean NOT NULL DEFAULT false,\n `delaiEnvoiJours` int NOT NULL DEFAULT 1,\n `createdAt` timestamp NOT NULL DEFAULT (now()),\n `updatedAt` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP,\n CONSTRAINT `questionnaires_id` PRIMARY KEY(`id`)\n)",
|
||||
"command": "mysql --batch --raw --column-names --default-character-set=utf8mb4 --host gateway02.us-east-1.prod.aws.tidbcloud.com --port 4000 --user 4CrrYuB5tme73Qo.root --database 7PAT67UmWoxv8vwp8Bbcv6 --execute CREATE TABLE IF NOT EXISTS `questionnaires` (\n `id` int AUTO_INCREMENT NOT NULL,\n `nom` varchar(255) NOT NULL,\n `type` enum('satisfaction', 'evaluation_pre', 'evaluation_post') NOT NULL,\n `description` text,\n `formationId` int,\n `actif` boolean NOT NULL DEFAULT true,\n `envoiAutomatique` boolean NOT NULL DEFAULT false,\n `delaiEnvoiJours` int NOT NULL DEFAULT 1,\n `createdAt` timestamp NOT NULL DEFAULT (now()),\n `updatedAt` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP,\n CONSTRAINT `questionnaires_id` PRIMARY KEY(`id`)\n)",
|
||||
"rows": [],
|
||||
"messages": [],
|
||||
"stdout": "",
|
||||
"stderr": "",
|
||||
"execution_time_ms": 225
|
||||
}
|
||||
9
.manus/db/db-query-1765128330756.json
Normal file
9
.manus/db/db-query-1765128330756.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"query": "CREATE TABLE IF NOT EXISTS `questions` (\n `id` int AUTO_INCREMENT NOT NULL,\n `questionnaireId` int NOT NULL,\n `ordre` int NOT NULL,\n `texte` text NOT NULL,\n `typeQuestion` enum('choix_multiple', 'echelle', 'texte_libre', 'oui_non') NOT NULL,\n `options` text,\n `echelleMin` int,\n `echelleMax` int,\n `echelleLabelMin` varchar(255),\n `echelleLabelMax` varchar(255),\n `obligatoire` boolean NOT NULL DEFAULT false,\n `createdAt` timestamp NOT NULL DEFAULT (now()),\n `updatedAt` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP,\n CONSTRAINT `questions_id` PRIMARY KEY(`id`)\n)",
|
||||
"command": "mysql --batch --raw --column-names --default-character-set=utf8mb4 --host gateway02.us-east-1.prod.aws.tidbcloud.com --port 4000 --user 4CrrYuB5tme73Qo.root --database 7PAT67UmWoxv8vwp8Bbcv6 --execute CREATE TABLE IF NOT EXISTS `questions` (\n `id` int AUTO_INCREMENT NOT NULL,\n `questionnaireId` int NOT NULL,\n `ordre` int NOT NULL,\n `texte` text NOT NULL,\n `typeQuestion` enum('choix_multiple', 'echelle', 'texte_libre', 'oui_non') NOT NULL,\n `options` text,\n `echelleMin` int,\n `echelleMax` int,\n `echelleLabelMin` varchar(255),\n `echelleLabelMax` varchar(255),\n `obligatoire` boolean NOT NULL DEFAULT false,\n `createdAt` timestamp NOT NULL DEFAULT (now()),\n `updatedAt` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP,\n CONSTRAINT `questions_id` PRIMARY KEY(`id`)\n)",
|
||||
"rows": [],
|
||||
"messages": [],
|
||||
"stdout": "",
|
||||
"stderr": "",
|
||||
"execution_time_ms": 228
|
||||
}
|
||||
9
.manus/db/db-query-1765128338404.json
Normal file
9
.manus/db/db-query-1765128338404.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"query": "CREATE TABLE IF NOT EXISTS `envoisQuestionnaires` (\n `id` int AUTO_INCREMENT NOT NULL,\n `questionnaireId` int NOT NULL,\n `apprenantId` int NOT NULL,\n `sequenceId` int NOT NULL,\n `token` varchar(255) NOT NULL UNIQUE,\n `dateEnvoi` timestamp NOT NULL DEFAULT (now()),\n `dateReponse` timestamp,\n `createdAt` timestamp NOT NULL DEFAULT (now()),\n `updatedAt` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP,\n CONSTRAINT `envoisQuestionnaires_id` PRIMARY KEY(`id`)\n)",
|
||||
"command": "mysql --batch --raw --column-names --default-character-set=utf8mb4 --host gateway02.us-east-1.prod.aws.tidbcloud.com --port 4000 --user 4CrrYuB5tme73Qo.root --database 7PAT67UmWoxv8vwp8Bbcv6 --execute CREATE TABLE IF NOT EXISTS `envoisQuestionnaires` (\n `id` int AUTO_INCREMENT NOT NULL,\n `questionnaireId` int NOT NULL,\n `apprenantId` int NOT NULL,\n `sequenceId` int NOT NULL,\n `token` varchar(255) NOT NULL UNIQUE,\n `dateEnvoi` timestamp NOT NULL DEFAULT (now()),\n `dateReponse` timestamp,\n `createdAt` timestamp NOT NULL DEFAULT (now()),\n `updatedAt` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP,\n CONSTRAINT `envoisQuestionnaires_id` PRIMARY KEY(`id`)\n)",
|
||||
"rows": [],
|
||||
"messages": [],
|
||||
"stdout": "",
|
||||
"stderr": "",
|
||||
"execution_time_ms": 218
|
||||
}
|
||||
9
.manus/db/db-query-1765128343916.json
Normal file
9
.manus/db/db-query-1765128343916.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"query": "CREATE TABLE IF NOT EXISTS `reponsesQuestionnaires` (\n `id` int AUTO_INCREMENT NOT NULL,\n `envoiId` int NOT NULL,\n `questionnaireId` int NOT NULL,\n `apprenantId` int NOT NULL,\n `sequenceId` int NOT NULL,\n `dateReponse` timestamp NOT NULL DEFAULT (now()),\n `createdAt` timestamp NOT NULL DEFAULT (now()),\n `updatedAt` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP,\n CONSTRAINT `reponsesQuestionnaires_id` PRIMARY KEY(`id`)\n)",
|
||||
"command": "mysql --batch --raw --column-names --default-character-set=utf8mb4 --host gateway02.us-east-1.prod.aws.tidbcloud.com --port 4000 --user 4CrrYuB5tme73Qo.root --database 7PAT67UmWoxv8vwp8Bbcv6 --execute CREATE TABLE IF NOT EXISTS `reponsesQuestionnaires` (\n `id` int AUTO_INCREMENT NOT NULL,\n `envoiId` int NOT NULL,\n `questionnaireId` int NOT NULL,\n `apprenantId` int NOT NULL,\n `sequenceId` int NOT NULL,\n `dateReponse` timestamp NOT NULL DEFAULT (now()),\n `createdAt` timestamp NOT NULL DEFAULT (now()),\n `updatedAt` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP,\n CONSTRAINT `reponsesQuestionnaires_id` PRIMARY KEY(`id`)\n)",
|
||||
"rows": [],
|
||||
"messages": [],
|
||||
"stdout": "",
|
||||
"stderr": "",
|
||||
"execution_time_ms": 236
|
||||
}
|
||||
9
.manus/db/db-query-1765128349724.json
Normal file
9
.manus/db/db-query-1765128349724.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"query": "CREATE TABLE IF NOT EXISTS `reponsesQuestions` (\n `id` int AUTO_INCREMENT NOT NULL,\n `reponseQuestionnaireId` int NOT NULL,\n `questionId` int NOT NULL,\n `reponseNumerique` int,\n `reponseTexte` text,\n `createdAt` timestamp NOT NULL DEFAULT (now()),\n `updatedAt` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP,\n CONSTRAINT `reponsesQuestions_id` PRIMARY KEY(`id`)\n)",
|
||||
"command": "mysql --batch --raw --column-names --default-character-set=utf8mb4 --host gateway02.us-east-1.prod.aws.tidbcloud.com --port 4000 --user 4CrrYuB5tme73Qo.root --database 7PAT67UmWoxv8vwp8Bbcv6 --execute CREATE TABLE IF NOT EXISTS `reponsesQuestions` (\n `id` int AUTO_INCREMENT NOT NULL,\n `reponseQuestionnaireId` int NOT NULL,\n `questionId` int NOT NULL,\n `reponseNumerique` int,\n `reponseTexte` text,\n `createdAt` timestamp NOT NULL DEFAULT (now()),\n `updatedAt` timestamp NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP,\n CONSTRAINT `reponsesQuestions_id` PRIMARY KEY(`id`)\n)",
|
||||
"rows": [],
|
||||
"messages": [],
|
||||
"stdout": "",
|
||||
"stderr": "",
|
||||
"execution_time_ms": 266
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
5
todo.md
5
todo.md
@@ -13,3 +13,8 @@
|
||||
- [ ] Implémenter les exports Excel/PDF des feedbacks
|
||||
- [ ] Tester le système complet (création, envoi, réponse, analyse)
|
||||
- [ ] Déployer sur le serveur VPS
|
||||
|
||||
## Amélioration : Prévisualisation des questionnaires
|
||||
|
||||
- [x] Ajouter un bouton "Prévisualiser" dans la page d'édition de questionnaire
|
||||
- [x] Créer un modal de prévisualisation avec le rendu exact du questionnaire
|
||||
|
||||
Reference in New Issue
Block a user