Regroupe la configuration de carte en deux blocs (Carte / Points d'interet), ajoute le filtre par categorie et l'editeur de point geographique, et passe les ecrans Users et les jauges de quota par AppLocalizations (FR/EN/NL). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
194 lines
6.5 KiB
Dart
194 lines
6.5 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:manager_app/Components/common_loader.dart';
|
||
import 'package:manager_app/l10n/app_localizations.dart';
|
||
import 'package:manager_app/Components/message_notification.dart';
|
||
import 'package:manager_app/Models/managerContext.dart';
|
||
import 'package:manager_app/Screens/Configurations/listView_card_section.dart';
|
||
import 'package:manager_app/Screens/Configurations/new_section_popup.dart';
|
||
import 'package:manager_app/app_context.dart';
|
||
import 'package:manager_app/constants.dart';
|
||
import 'package:manager_api_new/api.dart';
|
||
import 'dart:convert';
|
||
|
||
import 'package:provider/provider.dart';
|
||
|
||
/// Les sous-sections d'un Menu, dans la grille de tuiles des sections.
|
||
///
|
||
/// Ce sont des sections à part entière, avec leur propre type et leur propre
|
||
/// écran de configuration : on continue donc de **naviguer** vers cet écran au
|
||
/// clic, au lieu de les éditer en place. Seule la liste change — la liste
|
||
/// horizontale enfermée dans `0,35 × hauteur` devient la même grille que les
|
||
/// sections d'une configuration.
|
||
class MenuConfig extends StatefulWidget {
|
||
final String? color;
|
||
final String? label;
|
||
final MenuDTO initialValue;
|
||
final ValueChanged<String> onChanged;
|
||
const MenuConfig({
|
||
Key? key,
|
||
this.color,
|
||
this.label,
|
||
required this.initialValue,
|
||
required this.onChanged,
|
||
}) : super(key: key);
|
||
|
||
@override
|
||
_MenuConfigState createState() => _MenuConfigState();
|
||
}
|
||
|
||
class _MenuConfigState extends State<MenuConfig> {
|
||
late MenuDTO menuDTO;
|
||
List<SectionDTO> subSections = [];
|
||
|
||
/// L'écran de sous-section lit le JSON brut renvoyé par l'API, pas le DTO :
|
||
/// on le garde indexé par identifiant, la grille étant triée par ordre.
|
||
Map<String, dynamic> rawById = {};
|
||
bool isLoading = true;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
menuDTO = widget.initialValue;
|
||
WidgetsBinding.instance.addPostFrameCallback((_) => _loadFromApi());
|
||
}
|
||
|
||
SectionApi _api() {
|
||
final appContext = Provider.of<AppContext>(context, listen: false);
|
||
return (appContext.getContext() as ManagerAppContext).clientAPI!.sectionApi!;
|
||
}
|
||
|
||
Future<void> _loadFromApi() async {
|
||
if (menuDTO.id == null || !mounted) return;
|
||
try {
|
||
final rawList =
|
||
await _api().sectionGetAllSectionSubSections(menuDTO.id!);
|
||
if (!mounted) return;
|
||
final decoded = rawList == null
|
||
? <dynamic>[]
|
||
: jsonDecode(jsonEncode(rawList)) as List<dynamic>;
|
||
|
||
final sections = <SectionDTO>[];
|
||
final byId = <String, dynamic>{};
|
||
for (var i = 0; i < decoded.length; i++) {
|
||
final section = SectionDTO.fromJson(decoded[i]);
|
||
if (section == null) continue;
|
||
sections.add(section);
|
||
if (section.id != null) byId[section.id!] = rawList![i];
|
||
}
|
||
sections.sort((a, b) => (a.order ?? 0).compareTo(b.order ?? 0));
|
||
|
||
setState(() {
|
||
rawById = byId;
|
||
subSections = sections;
|
||
menuDTO.sections = sections;
|
||
isLoading = false;
|
||
});
|
||
} catch (e) {
|
||
if (mounted) setState(() => isLoading = false);
|
||
}
|
||
}
|
||
|
||
/// ⚠️ La liste entière est renumérotée : l'ancien code n'écrivait que
|
||
/// l'`order` de la sous-section déplacée, ce qui laissait des rangs en double.
|
||
Future<void> _onReorder(int oldIndex, int newIndex) async {
|
||
final l = AppLocalizations.of(context)!;
|
||
final previous = List<SectionDTO>.from(subSections);
|
||
|
||
setState(() {
|
||
final item = subSections.removeAt(oldIndex);
|
||
subSections.insert(newIndex, item);
|
||
for (var i = 0; i < subSections.length; i++) {
|
||
subSections[i].order = i;
|
||
}
|
||
});
|
||
|
||
try {
|
||
await Future.wait(subSections.map((s) => _api().sectionUpdate(s)));
|
||
showNotification(
|
||
kSuccess, kWhite, l.subSectionOrderUpdatedSuccess, context, null);
|
||
menuDTO.sections = subSections;
|
||
widget.onChanged(jsonEncode(menuDTO).toString());
|
||
} catch (e) {
|
||
setState(() {
|
||
subSections = previous;
|
||
for (var i = 0; i < subSections.length; i++) {
|
||
subSections[i].order = i;
|
||
}
|
||
});
|
||
showNotification(
|
||
kError, kWhite, l.subSectionOrderUpdateError, context, null);
|
||
}
|
||
}
|
||
|
||
Future<void> _createSubSection() async {
|
||
final l = AppLocalizations.of(context)!;
|
||
final appContext = Provider.of<AppContext>(context, listen: false);
|
||
final managerContext = appContext.getContext() as ManagerAppContext;
|
||
|
||
final newSubSection = await showNewSection(
|
||
managerContext.selectedConfiguration!.id!, appContext, context, true);
|
||
if (newSubSection == null) return;
|
||
|
||
try {
|
||
newSubSection.instanceId = managerContext.instanceId;
|
||
newSubSection.isBeacon = false;
|
||
newSubSection.isActive = true;
|
||
newSubSection.order = subSections.length;
|
||
await _api().sectionCreate(newSubSection);
|
||
showNotification(
|
||
kSuccess, kWhite, l.subSectionCreatedSuccess, context, null);
|
||
await _loadFromApi();
|
||
} catch (e) {
|
||
showNotification(kError, kWhite, l.subSectionCreateError, context, null);
|
||
}
|
||
}
|
||
|
||
Future<void> _toggleVisibility(SectionDTO section) async {
|
||
final l = AppLocalizations.of(context)!;
|
||
final newValue = section.isActive == false;
|
||
|
||
setState(() => section.isActive = newValue);
|
||
|
||
try {
|
||
await _api().sectionSetVisibility(section.id!, newValue);
|
||
showNotification(
|
||
kSuccess,
|
||
kWhite,
|
||
newValue ? l.sectionShownSuccess : l.sectionHiddenSuccess,
|
||
context,
|
||
null);
|
||
} catch (e) {
|
||
setState(() => section.isActive = !newValue);
|
||
showNotification(kError, kWhite, l.sectionVisibilityError, context, null);
|
||
}
|
||
}
|
||
|
||
/// Ouvre l'écran de la sous-section. Il lit `selectedSubSectionRawData`, donc
|
||
/// on lui passe le JSON brut correspondant, pas seulement le DTO.
|
||
void _openSubSection(SectionDTO section, int index) {
|
||
final appContext = Provider.of<AppContext>(context, listen: false);
|
||
final managerContext = appContext.getContext() as ManagerAppContext;
|
||
managerContext.selectedSubSection = section;
|
||
managerContext.selectedSubSectionRawData = rawById[section.id];
|
||
appContext.setContext(managerContext);
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
if (isLoading) {
|
||
return const Padding(
|
||
padding: EdgeInsets.all(kSpace8),
|
||
child: Center(child: CommonLoader()),
|
||
);
|
||
}
|
||
|
||
return SectionGrid(
|
||
sections: subSections,
|
||
onReorder: _onReorder,
|
||
onAdd: _createSubSection,
|
||
onTap: _openSubSection,
|
||
onToggleVisibility: _toggleVisibility,
|
||
);
|
||
}
|
||
}
|