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/guided_path_api.dart'; import 'package:manager_app/Screens/Configurations/Section/SubSection/Parcours/guided_path_editor.dart'; import 'package:provider/provider.dart'; import 'package:manager_app/app_context.dart'; import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/Components/message_notification.dart'; class ParcoursConfig extends StatefulWidget { final List initialValue; final String parentId; final bool isEvent; final bool isParcours; /// Parcours géolocalisé (`ShowMap`) : conditionne l'affichage des champs de /// position et de zone sur les étapes. Un parcours en salle n'en a pas besoin. final bool isGeolocated; final ValueChanged> onChanged; const ParcoursConfig({ Key? key, required this.initialValue, required this.parentId, required this.isEvent, this.isParcours = false, this.isGeolocated = true, required this.onChanged, }) : super(key: key); @override _ParcoursConfigState createState() => _ParcoursConfigState(); } class _ParcoursConfigState extends State { late List 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 _loadFromApi() async { final appContext = Provider.of(context, listen: false); final clientAPI = (appContext.getContext() as ManagerAppContext).clientAPI!; try { final fetchedPaths = widget.isParcours ? await clientAPI.sectionParcoursApi! .sectionParcoursGetAllGuidedPathFromSection(widget.parentId) : await clientAPI.sectionMapApi! .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 } } /// Ouvre l'éditeur à fenêtre unique. Il enregistre au fil de la saisie : il /// n'y a donc rien à récupérer d'un callback, seulement la liste à relire à /// la fermeture pour repartir de l'état réel du serveur. Future _openEditor(GuidedPathDTO? path) async { final appContext = Provider.of(context, listen: false); final managerContext = appContext.getContext() as ManagerAppContext; await showGuidedPathEditor( context, path: path, sectionId: widget.parentId, instanceId: managerContext.instanceId, isEvent: widget.isEvent, isParcours: widget.isParcours, isGeolocated: widget.isGeolocated, api: GuidedPathApi(managerContext.clientAPI!, isParcours: widget.isParcours), newPathOrder: paths.length, ); if (!mounted) return; await _loadFromApi(); if (mounted) widget.onChanged(paths); } @override Widget build(BuildContext context) { return Column( children: [ Padding( padding: const EdgeInsets.all(8.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(AppLocalizations.of(context)!.guidedPathsLabel, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), ElevatedButton.icon( icon: Icon(Icons.add), label: Text(AppLocalizations.of(context)!.addPath), onPressed: () => _openEditor(null), style: ElevatedButton.styleFrom( backgroundColor: kSuccess, foregroundColor: kWhite), ), ], ), ), Expanded( child: paths.isEmpty ? Center( child: Text(AppLocalizations.of(context)!.noPathConfigured, 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(context, listen: false); final clientAPI = (appContext.getContext() as ManagerAppContext).clientAPI!; try { await Future.wait(widget.isParcours ? paths.map((p) => clientAPI.sectionParcoursApi!.sectionParcoursUpdateGuidedPath(p)) : paths.map((p) => clientAPI.sectionMapApi!.sectionMapUpdateGuidedPath(p))); } catch (e) { showNotification( kError, kWhite, AppLocalizations.of(context)!.pathOrderUpdateError, 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 ?? AppLocalizations.of(context)!.pathNoTitle, ) : Text(AppLocalizations.of(context)!.pathNoTitle), subtitle: Text(AppLocalizations.of(context)!.stepsCount(path.steps?.length ?? 0)), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ IconButton( icon: Icon(Icons.edit, color: kPrimaryColor), onPressed: () => _openEditor(path), ), IconButton( icon: Icon(Icons.delete, color: kError), onPressed: () async { final appContext = Provider.of( context, listen: false); try { if (path.id != null) { final clientAPI = (appContext.getContext() as ManagerAppContext).clientAPI!; if (widget.isParcours) { await clientAPI.sectionParcoursApi!.sectionParcoursDeleteGuidedPath(path.id!); } else { await 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, AppLocalizations.of(context)!.pathDeletedSuccess, context, null); } catch (e) { showNotification( kError, kWhite, AppLocalizations.of(context)!.pathDeleteError, context, null); } }, ), ReorderableDragStartListener( index: index, child: Icon(Icons.drag_handle), ), ], ), ), ); }, ), ), ], ); } }