feat(#175,#176): modale générique Ajouter une carte + CTA pilule

Creation absence (parent) et conge/arret (AM) via une seule modale
Material, catalogue UI extensible, calendrier de periode compact.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-05 22:36:29 +02:00
co-authored by Cursor
parent 5d36b0f4c1
commit a36e9ef176
8 changed files with 677 additions and 32 deletions
+59 -2
View File
@@ -2,11 +2,14 @@ import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:p_tits_pas/models/carte_bulle.dart';
import 'package:p_tits_pas/models/carte_type_ui.dart';
import 'package:p_tits_pas/services/api/api_config.dart';
import 'package:p_tits_pas/services/api/tokenService.dart';
/// Accès API Cartes (file d'attention) — tickets #173 / #194.
/// Accès API Cartes (file d'attention) — tickets #173 / #175 / #194.
/// - GET /cards?placementId=
/// - GET /cards/types
/// - POST /cards
/// - POST /cards/:id/respond
class CardsService {
static Future<Map<String, String>> _headers() async {
@@ -57,6 +60,60 @@ class CardsService {
.toList();
}
/// Types SYSTEM émissibles pour le rôle connecté.
static Future<List<CarteTypeEmissible>> listerTypes() async {
final response = await http.get(
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.cards}/types'),
headers: await _headers(),
);
if (response.statusCode != 200) {
throw Exception(
_extractError(response.body, 'Erreur chargement des types de cartes'),
);
}
final decoded = jsonDecode(response.body);
final list = decoded is List
? decoded
: (decoded is Map ? decoded['items'] as List? : null) ?? const [];
return list
.whereType<Map>()
.map((e) => CarteTypeEmissible.fromJson(Map<String, dynamic>.from(e)))
.where((t) => t.code.isNotEmpty)
// Modif d’absence = agenda, pas la modale cartes (#175).
.where((t) => t.code != 'absence_enfant_modif')
.toList();
}
/// Créer une bulle (+ événement agenda côté back).
static Future<CarteBulle> creer({
required String typeCode,
required String idPlacement,
required String dateDebut,
required String dateFin,
String? motif,
}) async {
final response = await http.post(
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.cards}'),
headers: await _headers(),
body: jsonEncode({
'type_code': typeCode,
'id_placement': idPlacement,
'date_debut': dateDebut,
'date_fin': dateFin,
if (motif != null && motif.trim().isNotEmpty) 'motif': motif.trim(),
}),
);
if (response.statusCode != 201 && response.statusCode != 200) {
throw Exception(
_extractError(response.body, 'Erreur lors de la création'),
);
}
final decoded = jsonDecode(response.body);
return CarteBulle.fromJson(Map<String, dynamic>.from(decoded as Map));
}
/// Répondre : accept | refuse | ack.
static Future<CarteBulle> repondre({
required String cardId,
@@ -75,7 +132,7 @@ class CardsService {
}),
);
if (response.statusCode != 200) {
if (response.statusCode != 200 && response.statusCode != 201) {
throw Exception(
_extractError(response.body, 'Erreur lors de la réponse'),
);