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 { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
import { Switch } from "@/components/ui/switch";
|
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 { toast } from "sonner";
|
||||||
import DashboardLayout from "@/components/DashboardLayout";
|
import DashboardLayout from "@/components/DashboardLayout";
|
||||||
|
|
||||||
@@ -39,6 +42,7 @@ export default function AdminQuestionnaireEdit() {
|
|||||||
const [envoiAutomatique, setEnvoiAutomatique] = useState(false);
|
const [envoiAutomatique, setEnvoiAutomatique] = useState(false);
|
||||||
const [delaiEnvoiJours, setDelaiEnvoiJours] = useState(1);
|
const [delaiEnvoiJours, setDelaiEnvoiJours] = useState(1);
|
||||||
const [questions, setQuestions] = useState<Question[]>([]);
|
const [questions, setQuestions] = useState<Question[]>([]);
|
||||||
|
const [showPreview, setShowPreview] = useState(false);
|
||||||
|
|
||||||
const { data: questionnaire } = trpc.questionnaires.getById.useQuery(
|
const { data: questionnaire } = trpc.questionnaires.getById.useQuery(
|
||||||
{ id: questionnaireId! },
|
{ id: questionnaireId! },
|
||||||
@@ -412,6 +416,15 @@ export default function AdminQuestionnaireEdit() {
|
|||||||
<Button type="button" variant="outline" onClick={() => navigate("/admin/questionnaires")}>
|
<Button type="button" variant="outline" onClick={() => navigate("/admin/questionnaires")}>
|
||||||
Annuler
|
Annuler
|
||||||
</Button>
|
</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}>
|
<Button type="submit" disabled={createMutation.isPending || updateMutation.isPending}>
|
||||||
<Save className="mr-2 h-4 w-4" />
|
<Save className="mr-2 h-4 w-4" />
|
||||||
{questionnaireId ? "Enregistrer" : "Créer"}
|
{questionnaireId ? "Enregistrer" : "Créer"}
|
||||||
@@ -419,6 +432,82 @@ export default function AdminQuestionnaireEdit() {
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</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>
|
</DashboardLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
5
todo.md
5
todo.md
@@ -13,3 +13,8 @@
|
|||||||
- [ ] Implémenter les exports Excel/PDF des feedbacks
|
- [ ] Implémenter les exports Excel/PDF des feedbacks
|
||||||
- [ ] Tester le système complet (création, envoi, réponse, analyse)
|
- [ ] Tester le système complet (création, envoi, réponse, analyse)
|
||||||
- [ ] Déployer sur le serveur VPS
|
- [ ] 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