296 lines
12 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
import 'package:manager_api_new/api.dart';
import 'package:manager_app/constants.dart';
import 'package:manager_app/Screens/Configurations/Section/SubSection/Parcours/showNewOrUpdateGuidedPath.dart';
import 'package:provider/provider.dart';
import 'package:manager_app/app_context.dart';
import 'package:manager_app/Models/managerContext.dart';
import 'package:manager_app/Components/message_notification.dart';
class ParcoursConfig extends StatefulWidget {
final List<GuidedPathDTO> initialValue;
final String parentId;
final bool isEvent;
final bool isEscapeMode;
final ValueChanged<List<GuidedPathDTO>> onChanged;
const ParcoursConfig({
Key? key,
required this.initialValue,
required this.parentId,
required this.isEvent,
this.isEscapeMode = false,
required this.onChanged,
}) : super(key: key);
@override
_ParcoursConfigState createState() => _ParcoursConfigState();
}
class _ParcoursConfigState extends State<ParcoursConfig> {
late List<GuidedPathDTO> paths;
@override
void initState() {
super.initState();
paths = List.from(widget.initialValue);
paths.sort((a, b) => (a.order ?? 0).compareTo(b.order ?? 0));
WidgetsBinding.instance.addPostFrameCallback((_) => _loadFromApi());
}
Future<void> _loadFromApi() async {
final appContext = Provider.of<AppContext>(context, listen: false);
final api = (appContext.getContext() as ManagerAppContext)
.clientAPI!
.sectionMapApi!;
try {
// Backend already includes steps + quiz questions via .Include()
final fetchedPaths =
await api.sectionMapGetAllGuidedPathFromSection(widget.parentId);
if (fetchedPaths == null || !mounted) return;
fetchedPaths.sort((a, b) => (a.order ?? 0).compareTo(b.order ?? 0));
setState(() {
paths = List.from(fetchedPaths);
});
} catch (e) {
// Silently keep initial value on error
}
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("Parcours Guidés",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
ElevatedButton.icon(
icon: Icon(Icons.add),
label: Text("Ajouter un parcours"),
onPressed: () {
final appContext =
Provider.of<AppContext>(context, listen: false);
showNewOrUpdateGuidedPath(
context,
null,
widget.parentId,
widget.isEvent,
widget.isEscapeMode,
(newPath) async {
try {
newPath.order = paths.length;
newPath.instanceId =
(appContext.getContext() as ManagerAppContext)
.instanceId;
if (widget.isEscapeMode) {
newPath.sectionGameId = widget.parentId;
} else if (widget.isEvent) {
newPath.sectionEventId = widget.parentId;
} else {
newPath.sectionMapId = widget.parentId;
}
final createdPath =
await (appContext.getContext() as ManagerAppContext)
.clientAPI!
.sectionMapApi!
.sectionMapCreateGuidedPath(
widget.parentId, newPath);
if (createdPath != null) {
if (mounted) {
setState(() {
paths.add(createdPath);
widget.onChanged(paths);
});
}
showNotification(kSuccess, kWhite,
'Parcours créé avec succès', context, null);
}
} catch (e) {
showNotification(
kError,
kWhite,
'Erreur lors de la création du parcours',
context,
null);
rethrow; // Important so showNewOrUpdateGuidedPath knows it failed
}
},
);
},
style: ElevatedButton.styleFrom(
backgroundColor: kSuccess, foregroundColor: kWhite),
),
],
),
),
Expanded(
child: paths.isEmpty
? Center(
child: Text("Aucun parcours configuré",
style: TextStyle(fontStyle: FontStyle.italic)))
: ReorderableListView.builder(
buildDefaultDragHandles: false,
itemCount: paths.length,
onReorder: (oldIndex, newIndex) async {
if (newIndex > oldIndex) newIndex -= 1;
final item = paths.removeAt(oldIndex);
paths.insert(newIndex, item);
for (int i = 0; i < paths.length; i++) {
paths[i].order = i;
}
setState(() {});
widget.onChanged(paths);
final appContext =
Provider.of<AppContext>(context, listen: false);
final api = (appContext.getContext() as ManagerAppContext)
.clientAPI!
.sectionMapApi!;
try {
// Update all affected paths orders
await Future.wait(
paths.map((p) => api.sectionMapUpdateGuidedPath(p)));
} catch (e) {
showNotification(
kError,
kWhite,
'Erreur lors de la mise à jour de l\'ordre',
context,
null);
}
},
itemBuilder: (context, index) {
final path = paths[index];
return Card(
key: ValueKey(path.id ?? index.toString()),
margin: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
child: ListTile(
leading: CircleAvatar(
child: Text("${index + 1}"),
backgroundColor: kPrimaryColor,
foregroundColor: kWhite),
title: path.title != null && path.title!.isNotEmpty
? HtmlWidget(
path.title!
.firstWhere((t) => t.language == 'FR',
orElse: () => path.title![0])
.value ??
"Parcours sans titre",
)
: Text("Parcours sans titre"),
subtitle: Text("${path.steps?.length ?? 0} étapes"),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: Icon(Icons.edit, color: kPrimaryColor),
onPressed: () {
final appContext = Provider.of<AppContext>(
context,
listen: false);
showNewOrUpdateGuidedPath(
context,
path,
widget.parentId,
widget.isEvent,
widget.isEscapeMode,
(updatedPath) async {
try {
final api = (appContext.getContext()
as ManagerAppContext)
.clientAPI!
.sectionMapApi!;
final result =
await api.sectionMapUpdateGuidedPath(
updatedPath);
if (result != null) {
if (mounted) {
setState(() {
paths[index] = result;
widget.onChanged(paths);
});
}
showNotification(
kSuccess,
kWhite,
'Parcours mis à jour avec succès',
context,
null);
}
} catch (e) {
showNotification(
kError,
kWhite,
'Erreur lors de la mise à jour du parcours',
context,
null);
rethrow;
}
},
);
},
),
IconButton(
icon: Icon(Icons.delete, color: kError),
onPressed: () async {
final appContext = Provider.of<AppContext>(
context,
listen: false);
try {
if (path.id != null) {
await (appContext.getContext()
as ManagerAppContext)
.clientAPI!
.sectionMapApi!
.sectionMapDeleteGuidedPath(path.id!);
}
setState(() {
paths.removeAt(index);
for (int i = 0; i < paths.length; i++) {
paths[i].order = i;
}
widget.onChanged(paths);
});
showNotification(
kSuccess,
kWhite,
'Parcours supprimé avec succès',
context,
null);
} catch (e) {
showNotification(
kError,
kWhite,
'Erreur lors de la suppression du parcours',
context,
null);
}
},
),
ReorderableDragStartListener(
index: index,
child: Icon(Icons.drag_handle),
),
],
),
),
);
},
),
),
],
);
}
}