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,
),
),
],
),
],
),
),
);
},
);
},
);
}