Initial commit: unionflow-mobile-apps

Application Flutter complète (sans build artifacts).

Signed-off-by: lions dev Team
This commit is contained in:
dahoud
2026-03-15 16:30:08 +00:00
commit d094d6db9c
1790 changed files with 507435 additions and 0 deletions

View File

@@ -0,0 +1,72 @@
/// Modèle pour le "compte adhérent" unifié (GET /api/membres/mon-compte).
class CompteAdherentModel {
final String numeroMembre;
final String nomComplet;
final String? organisationNom;
final String? dateAdhesion;
final String statutCompte;
final double soldeCotisations;
final double soldeEpargne;
final double soldeBloque;
final double soldeTotalDisponible;
final double encoursCreditTotal;
final double capaciteEmprunt;
final int nombreCotisationsPayees;
final int nombreCotisationsTotal;
final int nombreCotisationsEnRetard;
final int? tauxEngagement;
final int nombreComptesEpargne;
final String dateCalcul;
const CompteAdherentModel({
required this.numeroMembre,
required this.nomComplet,
this.organisationNom,
this.dateAdhesion,
this.statutCompte = 'ACTIF',
this.soldeCotisations = 0,
this.soldeEpargne = 0,
this.soldeBloque = 0,
this.soldeTotalDisponible = 0,
this.encoursCreditTotal = 0,
this.capaciteEmprunt = 0,
this.nombreCotisationsPayees = 0,
this.nombreCotisationsTotal = 0,
this.nombreCotisationsEnRetard = 0,
this.tauxEngagement,
this.nombreComptesEpargne = 0,
required this.dateCalcul,
});
factory CompteAdherentModel.fromJson(Map<String, dynamic> json) {
return CompteAdherentModel(
numeroMembre: json['numeroMembre'] as String? ?? 'N/A',
nomComplet: json['nomComplet'] as String? ?? '',
organisationNom: json['organisationNom'] as String?,
dateAdhesion: json['dateAdhesion'] as String?,
statutCompte: json['statutCompte'] as String? ?? 'ACTIF',
soldeCotisations: _toDouble(json['soldeCotisations']),
soldeEpargne: _toDouble(json['soldeEpargne']),
soldeBloque: _toDouble(json['soldeBloque']),
soldeTotalDisponible: _toDouble(json['soldeTotalDisponible']),
encoursCreditTotal: _toDouble(json['encoursCreditTotal']),
capaciteEmprunt: _toDouble(json['capaciteEmprunt']),
nombreCotisationsPayees: (json['nombreCotisationsPayees'] as num?)?.toInt() ?? 0,
nombreCotisationsTotal: (json['nombreCotisationsTotal'] as num?)?.toInt() ?? 0,
nombreCotisationsEnRetard: (json['nombreCotisationsEnRetard'] as num?)?.toInt() ?? 0,
tauxEngagement: (json['tauxEngagement'] as num?)?.toInt(),
nombreComptesEpargne: (json['nombreComptesEpargne'] as num?)?.toInt() ?? 0,
dateCalcul: json['dateCalcul'] as String? ?? '',
);
}
static double _toDouble(dynamic v) {
if (v == null) return 0;
if (v is num) return v.toDouble();
if (v is String) return double.tryParse(v) ?? 0;
return 0;
}
}

View File

@@ -0,0 +1,222 @@
import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart';
part 'dashboard_stats_model.g.dart';
/// Modèle pour les statistiques du dashboard
@JsonSerializable()
class DashboardStatsModel extends Equatable {
final int totalMembers;
final int activeMembers;
final int totalEvents;
final int upcomingEvents;
final int totalContributions;
final double totalContributionAmount;
final int pendingRequests;
final int completedProjects;
final double monthlyGrowth;
final double engagementRate;
final DateTime lastUpdated;
final int? totalOrganizations;
final Map<String, int>? organizationTypeDistribution;
const DashboardStatsModel({
required this.totalMembers,
required this.activeMembers,
required this.totalEvents,
required this.upcomingEvents,
required this.totalContributions,
required this.totalContributionAmount,
required this.pendingRequests,
required this.completedProjects,
required this.monthlyGrowth,
required this.engagementRate,
required this.lastUpdated,
this.totalOrganizations,
this.organizationTypeDistribution,
});
factory DashboardStatsModel.fromJson(Map<String, dynamic> json) =>
_$DashboardStatsModelFromJson(json);
Map<String, dynamic> toJson() => _$DashboardStatsModelToJson(this);
// Getters calculés
String get formattedContributionAmount {
return '${totalContributionAmount.toStringAsFixed(2)}';
}
bool get hasGrowth => monthlyGrowth > 0;
bool get isHighEngagement => engagementRate > 0.7;
double get activeMemberPercentage {
return totalMembers > 0 ? (activeMembers / totalMembers) : 0.0;
}
@override
List<Object?> get props => [
totalMembers,
activeMembers,
totalEvents,
upcomingEvents,
totalContributions,
totalContributionAmount,
pendingRequests,
completedProjects,
monthlyGrowth,
engagementRate,
lastUpdated,
totalOrganizations,
organizationTypeDistribution,
];
}
/// Modèle pour les activités récentes
@JsonSerializable()
class RecentActivityModel extends Equatable {
final String id;
final String type;
final String title;
final String description;
final String? userAvatar;
final String userName;
final DateTime timestamp;
final String? actionUrl;
final Map<String, dynamic>? metadata;
const RecentActivityModel({
required this.id,
required this.type,
required this.title,
required this.description,
this.userAvatar,
required this.userName,
required this.timestamp,
this.actionUrl,
this.metadata,
});
factory RecentActivityModel.fromJson(Map<String, dynamic> json) =>
_$RecentActivityModelFromJson(json);
Map<String, dynamic> toJson() => _$RecentActivityModelToJson(this);
// Getter calculé pour l'affichage du temps
String get timeAgo {
final now = DateTime.now();
final difference = now.difference(timestamp);
if (difference.inDays > 0) {
return 'il y a ${difference.inDays} jour${difference.inDays > 1 ? 's' : ''}';
} else if (difference.inHours > 0) {
return 'il y a ${difference.inHours} heure${difference.inHours > 1 ? 's' : ''}';
} else if (difference.inMinutes > 0) {
return 'il y a ${difference.inMinutes} minute${difference.inMinutes > 1 ? 's' : ''}';
} else {
return 'à l\'instant';
}
}
@override
List<Object?> get props => [
id,
type,
title,
description,
userAvatar,
userName,
timestamp,
actionUrl,
metadata,
];
}
/// Modèle pour les événements à venir
@JsonSerializable()
class UpcomingEventModel extends Equatable {
final String id;
final String title;
final String description;
final DateTime startDate;
final DateTime? endDate;
final String location;
final int maxParticipants;
final int currentParticipants;
final String status;
final String? imageUrl;
final List<String> tags;
const UpcomingEventModel({
required this.id,
required this.title,
required this.description,
required this.startDate,
this.endDate,
required this.location,
required this.maxParticipants,
required this.currentParticipants,
required this.status,
this.imageUrl,
required this.tags,
});
factory UpcomingEventModel.fromJson(Map<String, dynamic> json) =>
_$UpcomingEventModelFromJson(json);
Map<String, dynamic> toJson() => _$UpcomingEventModelToJson(this);
bool get isAlmostFull => currentParticipants >= (maxParticipants * 0.8);
bool get isFull => currentParticipants >= maxParticipants;
double get fillPercentage => maxParticipants > 0 ? currentParticipants / maxParticipants : 0.0;
@override
List<Object?> get props => [
id,
title,
description,
startDate,
endDate,
location,
maxParticipants,
currentParticipants,
status,
imageUrl,
tags,
];
}
/// Modèle pour les données du dashboard complet
@JsonSerializable()
class DashboardDataModel extends Equatable {
final DashboardStatsModel stats;
final List<RecentActivityModel> recentActivities;
final List<UpcomingEventModel> upcomingEvents;
final Map<String, dynamic> userPreferences;
final String organizationId;
final String userId;
const DashboardDataModel({
required this.stats,
required this.recentActivities,
required this.upcomingEvents,
required this.userPreferences,
required this.organizationId,
required this.userId,
});
factory DashboardDataModel.fromJson(Map<String, dynamic> json) =>
_$DashboardDataModelFromJson(json);
Map<String, dynamic> toJson() => _$DashboardDataModelToJson(this);
@override
List<Object?> get props => [
stats,
recentActivities,
upcomingEvents,
userPreferences,
organizationId,
userId,
];
}

View File

@@ -0,0 +1,130 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'dashboard_stats_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
DashboardStatsModel _$DashboardStatsModelFromJson(Map<String, dynamic> json) =>
DashboardStatsModel(
totalMembers: (json['totalMembers'] as num).toInt(),
activeMembers: (json['activeMembers'] as num).toInt(),
totalEvents: (json['totalEvents'] as num).toInt(),
upcomingEvents: (json['upcomingEvents'] as num).toInt(),
totalContributions: (json['totalContributions'] as num).toInt(),
totalContributionAmount:
(json['totalContributionAmount'] as num).toDouble(),
pendingRequests: (json['pendingRequests'] as num).toInt(),
completedProjects: (json['completedProjects'] as num).toInt(),
monthlyGrowth: (json['monthlyGrowth'] as num).toDouble(),
engagementRate: (json['engagementRate'] as num).toDouble(),
lastUpdated: DateTime.parse(json['lastUpdated'] as String),
totalOrganizations: (json['totalOrganizations'] as num?)?.toInt(),
organizationTypeDistribution:
(json['organizationTypeDistribution'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, (e as num).toInt()),
),
);
Map<String, dynamic> _$DashboardStatsModelToJson(
DashboardStatsModel instance) =>
<String, dynamic>{
'totalMembers': instance.totalMembers,
'activeMembers': instance.activeMembers,
'totalEvents': instance.totalEvents,
'upcomingEvents': instance.upcomingEvents,
'totalContributions': instance.totalContributions,
'totalContributionAmount': instance.totalContributionAmount,
'pendingRequests': instance.pendingRequests,
'completedProjects': instance.completedProjects,
'monthlyGrowth': instance.monthlyGrowth,
'engagementRate': instance.engagementRate,
'lastUpdated': instance.lastUpdated.toIso8601String(),
'totalOrganizations': instance.totalOrganizations,
'organizationTypeDistribution': instance.organizationTypeDistribution,
};
RecentActivityModel _$RecentActivityModelFromJson(Map<String, dynamic> json) =>
RecentActivityModel(
id: json['id'] as String,
type: json['type'] as String,
title: json['title'] as String,
description: json['description'] as String,
userAvatar: json['userAvatar'] as String?,
userName: json['userName'] as String,
timestamp: DateTime.parse(json['timestamp'] as String),
actionUrl: json['actionUrl'] as String?,
metadata: json['metadata'] as Map<String, dynamic>?,
);
Map<String, dynamic> _$RecentActivityModelToJson(
RecentActivityModel instance) =>
<String, dynamic>{
'id': instance.id,
'type': instance.type,
'title': instance.title,
'description': instance.description,
'userAvatar': instance.userAvatar,
'userName': instance.userName,
'timestamp': instance.timestamp.toIso8601String(),
'actionUrl': instance.actionUrl,
'metadata': instance.metadata,
};
UpcomingEventModel _$UpcomingEventModelFromJson(Map<String, dynamic> json) =>
UpcomingEventModel(
id: json['id'] as String,
title: json['title'] as String,
description: json['description'] as String,
startDate: DateTime.parse(json['startDate'] as String),
endDate: json['endDate'] == null
? null
: DateTime.parse(json['endDate'] as String),
location: json['location'] as String,
maxParticipants: (json['maxParticipants'] as num).toInt(),
currentParticipants: (json['currentParticipants'] as num).toInt(),
status: json['status'] as String,
imageUrl: json['imageUrl'] as String?,
tags: (json['tags'] as List<dynamic>).map((e) => e as String).toList(),
);
Map<String, dynamic> _$UpcomingEventModelToJson(UpcomingEventModel instance) =>
<String, dynamic>{
'id': instance.id,
'title': instance.title,
'description': instance.description,
'startDate': instance.startDate.toIso8601String(),
'endDate': instance.endDate?.toIso8601String(),
'location': instance.location,
'maxParticipants': instance.maxParticipants,
'currentParticipants': instance.currentParticipants,
'status': instance.status,
'imageUrl': instance.imageUrl,
'tags': instance.tags,
};
DashboardDataModel _$DashboardDataModelFromJson(Map<String, dynamic> json) =>
DashboardDataModel(
stats:
DashboardStatsModel.fromJson(json['stats'] as Map<String, dynamic>),
recentActivities: (json['recentActivities'] as List<dynamic>)
.map((e) => RecentActivityModel.fromJson(e as Map<String, dynamic>))
.toList(),
upcomingEvents: (json['upcomingEvents'] as List<dynamic>)
.map((e) => UpcomingEventModel.fromJson(e as Map<String, dynamic>))
.toList(),
userPreferences: json['userPreferences'] as Map<String, dynamic>,
organizationId: json['organizationId'] as String,
userId: json['userId'] as String,
);
Map<String, dynamic> _$DashboardDataModelToJson(DashboardDataModel instance) =>
<String, dynamic>{
'stats': instance.stats,
'recentActivities': instance.recentActivities,
'upcomingEvents': instance.upcomingEvents,
'userPreferences': instance.userPreferences,
'organizationId': instance.organizationId,
'userId': instance.userId,
};

View File

@@ -0,0 +1,85 @@
/// Modèle pour la réponse GET /api/dashboard/membre/me (backend MembreDashboardSyntheseResponse).
/// Utilisé quand l'utilisateur est un membre sans organisationId (dashboard personnel).
class MembreDashboardSyntheseModel {
final String prenom;
final String nom;
final String? dateInscription; // ISO date string
final double mesCotisationsPaiement;
/// Total des cotisations payées sur l'année (pour dashboard).
final double totalCotisationsPayeesAnnee;
/// Total des cotisations payées tout temps (pour carte « Contribution Totale »).
final double totalCotisationsPayeesToutTemps;
/// Nombre de cotisations payées (pour carte « Cotisations »).
final int nombreCotisationsPayees;
/// Nombre total de cotisations (toutes années, tous statuts).
final int nombreCotisationsTotal;
final String statutCotisations;
final int? tauxCotisationsPerso;
final double monSoldeEpargne;
final double evolutionEpargneNombre;
final String evolutionEpargne;
final int objectifEpargne;
final int mesEvenementsInscrits;
final int evenementsAVenir;
final int? tauxParticipationPerso;
final int mesDemandesAide;
final int aidesEnCours;
final int? tauxAidesApprouvees;
const MembreDashboardSyntheseModel({
required this.prenom,
required this.nom,
this.dateInscription,
this.mesCotisationsPaiement = 0,
this.totalCotisationsPayeesAnnee = 0,
this.totalCotisationsPayeesToutTemps = 0,
this.nombreCotisationsPayees = 0,
this.nombreCotisationsTotal = 0,
this.statutCotisations = 'À jour',
this.tauxCotisationsPerso,
this.monSoldeEpargne = 0,
this.evolutionEpargneNombre = 0,
this.evolutionEpargne = '+0%',
this.objectifEpargne = 0,
this.mesEvenementsInscrits = 0,
this.evenementsAVenir = 0,
this.tauxParticipationPerso,
this.mesDemandesAide = 0,
this.aidesEnCours = 0,
this.tauxAidesApprouvees,
});
factory MembreDashboardSyntheseModel.fromJson(Map<String, dynamic> json) {
return MembreDashboardSyntheseModel(
prenom: json['prenom'] as String? ?? '',
nom: json['nom'] as String? ?? '',
dateInscription: json['dateInscription'] as String?,
mesCotisationsPaiement: _toDouble(json['mesCotisationsPaiement']),
totalCotisationsPayeesAnnee: _toDouble(json['totalCotisationsPayeesAnnee']),
totalCotisationsPayeesToutTemps: _toDouble(json['totalCotisationsPayeesToutTemps']),
nombreCotisationsPayees: (json['nombreCotisationsPayees'] as num?)?.toInt() ?? 0,
nombreCotisationsTotal: (json['nombreCotisationsTotal'] as num?)?.toInt() ??
(json['nombreCotisationsPayees'] as num?)?.toInt() ?? 0,
statutCotisations: json['statutCotisations'] as String? ?? 'À jour',
tauxCotisationsPerso: (json['tauxCotisationsPerso'] as num?)?.toInt(),
monSoldeEpargne: _toDouble(json['monSoldeEpargne']),
evolutionEpargneNombre: _toDouble(json['evolutionEpargneNombre']),
evolutionEpargne: json['evolutionEpargne'] as String? ?? '+0%',
objectifEpargne: (json['objectifEpargne'] as num?)?.toInt() ?? 0,
mesEvenementsInscrits: (json['mesEvenementsInscrits'] as num?)?.toInt() ?? 0,
evenementsAVenir: (json['evenementsAVenir'] as num?)?.toInt() ?? 0,
tauxParticipationPerso: (json['tauxParticipationPerso'] as num?)?.toInt(),
mesDemandesAide: (json['mesDemandesAide'] as num?)?.toInt() ?? 0,
aidesEnCours: (json['aidesEnCours'] as num?)?.toInt() ?? 0,
tauxAidesApprouvees: (json['tauxAidesApprouvees'] as num?)?.toInt(),
);
}
static double _toDouble(dynamic v) {
if (v == null) return 0;
if (v is num) return v.toDouble();
if (v is String) return double.tryParse(v) ?? 0;
return 0;
}
}