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

@@ -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
}

View 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
}

View 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
}

View 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
}

View 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
}

View File

@@ -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>
); );
} }

View File

@@ -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