260 lines
12 KiB
Dart
260 lines
12 KiB
Dart
import 'dart:convert';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:manager_api_new/api.dart';
|
|
import 'package:manager_app/Components/confirmation_dialog.dart';
|
|
import 'package:manager_app/Components/geometry_input_container.dart';
|
|
import 'package:manager_app/Components/multi_string_input_container.dart';
|
|
import 'package:manager_app/Components/rounded_button.dart';
|
|
import 'package:manager_app/constants.dart';
|
|
import 'showNewOrUpdateQuizQuestion.dart';
|
|
|
|
void showNewOrUpdateGuidedStep(
|
|
BuildContext context,
|
|
GuidedStepDTO? step,
|
|
String pathId,
|
|
bool isEscapeMode,
|
|
Function(GuidedStepDTO) onSave,
|
|
) {
|
|
// Use jsonEncode/jsonDecode for a robust deep copy that handles nested DTOs correctly
|
|
GuidedStepDTO workingStep = step != null
|
|
? GuidedStepDTO.fromJson(jsonDecode(jsonEncode(step)))!
|
|
: GuidedStepDTO(
|
|
title: [],
|
|
description: [],
|
|
quizQuestions: [],
|
|
order: 0,
|
|
);
|
|
|
|
showDialog(
|
|
context: context,
|
|
builder: (BuildContext context) {
|
|
return StatefulBuilder(
|
|
builder: (context, setState) {
|
|
final double screenWidth = MediaQuery.of(context).size.width;
|
|
final double screenHeight = MediaQuery.of(context).size.height;
|
|
final double dialogWidth = screenWidth * 0.75;
|
|
final double contentWidth = dialogWidth - 48;
|
|
final double halfWidth = (contentWidth - 20) / 2;
|
|
|
|
return Dialog(
|
|
shape:
|
|
RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
|
child: Container(
|
|
width: dialogWidth,
|
|
constraints: BoxConstraints(maxHeight: screenHeight * 0.85),
|
|
padding: const EdgeInsets.all(24),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
step == null ? "Nouvelle Étape" : "Modifier l'Étape",
|
|
style: TextStyle(
|
|
color: kPrimaryColor,
|
|
fontSize: 20,
|
|
fontWeight: FontWeight.bold),
|
|
),
|
|
SizedBox(height: 16),
|
|
Flexible(
|
|
child: SingleChildScrollView(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Titre + Description côte à côte
|
|
Row(
|
|
children: [
|
|
SizedBox(
|
|
width: halfWidth,
|
|
child: MultiStringInputContainer(
|
|
label: "Titre :",
|
|
modalLabel: "Titre de l'étape",
|
|
initialValue: workingStep.title ?? [],
|
|
onGetResult: (val) =>
|
|
setState(() => workingStep.title = val),
|
|
maxLines: 1,
|
|
isTitle: true,
|
|
),
|
|
),
|
|
SizedBox(width: 20),
|
|
SizedBox(
|
|
width: halfWidth,
|
|
child: MultiStringInputContainer(
|
|
label: "Description :",
|
|
modalLabel: "Description de l'étape",
|
|
initialValue: workingStep.description ?? [],
|
|
onGetResult: (val) => setState(
|
|
() => workingStep.description = val),
|
|
maxLines: 1,
|
|
isTitle: false,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
Divider(height: 24),
|
|
// Géométrie — Directement avec GeometryDTO
|
|
GeometryInputContainer(
|
|
label: "Emplacement de l'étape :",
|
|
initialGeometry: workingStep.geometry,
|
|
initialColor: null,
|
|
onSave: (geometry, color) {
|
|
setState(() {
|
|
workingStep.geometry = geometry;
|
|
});
|
|
},
|
|
),
|
|
// Questions — uniquement en mode Escape Game
|
|
if (isEscapeMode) ...[
|
|
Divider(height: 24),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text("Questions / Défis",
|
|
style: TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 15)),
|
|
IconButton(
|
|
icon: Icon(Icons.add_circle_outline,
|
|
color: kSuccess),
|
|
onPressed: () {
|
|
showNewOrUpdateQuizQuestion(
|
|
context,
|
|
null,
|
|
workingStep.id ?? "temp",
|
|
isEscapeMode,
|
|
(newQuestion) {
|
|
setState(() {
|
|
workingStep.quizQuestions = [
|
|
...(workingStep.quizQuestions ??
|
|
[]),
|
|
newQuestion
|
|
];
|
|
});
|
|
},
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
if (workingStep.quizQuestions == null ||
|
|
workingStep.quizQuestions!.isEmpty)
|
|
Padding(
|
|
padding:
|
|
const EdgeInsets.symmetric(vertical: 8),
|
|
child: Text(
|
|
"Aucune question configurée.",
|
|
style: TextStyle(
|
|
fontStyle: FontStyle.italic,
|
|
color: Colors.grey[600]),
|
|
),
|
|
)
|
|
else
|
|
ListView.builder(
|
|
shrinkWrap: true,
|
|
physics: const NeverScrollableScrollPhysics(),
|
|
itemCount: workingStep.quizQuestions!.length,
|
|
itemBuilder: (context, qIndex) {
|
|
final question =
|
|
workingStep.quizQuestions![qIndex];
|
|
return ListTile(
|
|
dense: true,
|
|
title: Text(question.label.isNotEmpty
|
|
? question.label
|
|
.firstWhere(
|
|
(t) => t.language == 'FR',
|
|
orElse: () =>
|
|
question.label[0])
|
|
.value ??
|
|
"Question $qIndex"
|
|
: "Question $qIndex"),
|
|
subtitle: Text(
|
|
"Type: ${question.validationQuestionType?.value == 2 ? 'Puzzle' : question.validationQuestionType?.value == 1 ? 'QCM' : 'Texte'}"),
|
|
trailing: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
IconButton(
|
|
icon: Icon(Icons.edit,
|
|
size: 18, color: kPrimaryColor),
|
|
onPressed: () {
|
|
showNewOrUpdateQuizQuestion(
|
|
context,
|
|
question,
|
|
workingStep.id ?? "temp",
|
|
isEscapeMode,
|
|
(updatedQuestion) {
|
|
setState(() {
|
|
workingStep.quizQuestions![
|
|
qIndex] = updatedQuestion;
|
|
});
|
|
},
|
|
);
|
|
},
|
|
),
|
|
IconButton(
|
|
icon: Icon(Icons.delete,
|
|
size: 18, color: kError),
|
|
onPressed: () {
|
|
showConfirmationDialog(
|
|
"Supprimer cette question ?",
|
|
() {},
|
|
() => setState(() => workingStep
|
|
.quizQuestions!
|
|
.removeAt(qIndex)),
|
|
context,
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
),
|
|
SizedBox(height: 16),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.end,
|
|
children: [
|
|
SizedBox(
|
|
height: 46,
|
|
child: RoundedButton(
|
|
text: "Annuler",
|
|
press: () => Navigator.pop(context),
|
|
color: kSecond,
|
|
fontSize: 15,
|
|
horizontal: 24,
|
|
),
|
|
),
|
|
SizedBox(width: 12),
|
|
SizedBox(
|
|
height: 46,
|
|
child: RoundedButton(
|
|
text: "Sauvegarder",
|
|
press: () {
|
|
// Initialise les booleans null → false
|
|
// pour éviter l'erreur backend "Error converting null to Boolean"
|
|
workingStep.isHiddenInitially ??= false;
|
|
workingStep.isStepTimer ??= false;
|
|
workingStep.isStepLocked ??= false;
|
|
onSave(workingStep);
|
|
Navigator.pop(context);
|
|
},
|
|
color: kPrimaryColor,
|
|
fontSize: 15,
|
|
horizontal: 24,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
);
|
|
}
|