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>
145 lines
4.6 KiB
Dart
145 lines
4.6 KiB
Dart
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 / #175 / #194.
|
||
/// - GET /cards?placementId=
|
||
/// - GET /cards/types
|
||
/// - POST /cards
|
||
/// - POST /cards/:id/respond
|
||
class CardsService {
|
||
static Future<Map<String, String>> _headers() async {
|
||
final token = await TokenService.getToken();
|
||
return token != null
|
||
? ApiConfig.authHeaders(token)
|
||
: Map<String, String>.from(ApiConfig.headers);
|
||
}
|
||
|
||
static String _extractError(String body, String fallback) {
|
||
try {
|
||
final decoded = jsonDecode(body);
|
||
if (decoded is Map<String, dynamic>) {
|
||
final message = decoded['message'];
|
||
if (message is String && message.trim().isNotEmpty) {
|
||
return message;
|
||
}
|
||
if (message is Map && message['message'] is String) {
|
||
return message['message'] as String;
|
||
}
|
||
}
|
||
} catch (_) {}
|
||
return fallback;
|
||
}
|
||
|
||
/// Feed de bulles pour l'utilisateur (filtré par couple / placement).
|
||
static Future<List<CarteBulle>> lister({String? placementId}) async {
|
||
final query = <String, String>{};
|
||
if (placementId != null && placementId.isNotEmpty) {
|
||
query['placementId'] = placementId;
|
||
}
|
||
final uri = Uri.parse(
|
||
'${ApiConfig.baseUrl}${ApiConfig.cards}',
|
||
).replace(queryParameters: query.isEmpty ? null : query);
|
||
|
||
final response = await http.get(uri, headers: await _headers());
|
||
if (response.statusCode != 200) {
|
||
throw Exception(
|
||
_extractError(response.body, 'Erreur chargement des cartes'),
|
||
);
|
||
}
|
||
|
||
final decoded = jsonDecode(response.body);
|
||
final list = (decoded is Map ? decoded['items'] as List? : null) ?? const [];
|
||
return list
|
||
.whereType<Map>()
|
||
.map((e) => CarteBulle.fromJson(Map<String, dynamic>.from(e)))
|
||
.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,
|
||
required String action,
|
||
String? comment,
|
||
}) async {
|
||
final response = await http.post(
|
||
Uri.parse(
|
||
'${ApiConfig.baseUrl}${ApiConfig.cards}/$cardId/respond',
|
||
),
|
||
headers: await _headers(),
|
||
body: jsonEncode({
|
||
'action': action,
|
||
if (comment != null && comment.trim().isNotEmpty)
|
||
'comment': comment.trim(),
|
||
}),
|
||
);
|
||
|
||
if (response.statusCode != 200 && response.statusCode != 201) {
|
||
throw Exception(
|
||
_extractError(response.body, 'Erreur lors de la réponse'),
|
||
);
|
||
}
|
||
|
||
final decoded = jsonDecode(response.body);
|
||
return CarteBulle.fromJson(Map<String, dynamic>.from(decoded as Map));
|
||
}
|
||
}
|