feat: Version 3.3.4 - Nouvelle architecture pages, optimisations widgets Flutter et API
- Mise à jour VERSION vers 3.3.4 - Optimisations et révisions architecture API (deploy-api.sh, scripts de migration) - Ajout documentation Stripe Tap to Pay complète - Migration vers polices Inter Variable pour Flutter - Optimisations build Android et nettoyage fichiers temporaires - Amélioration système de déploiement avec gestion backups - Ajout scripts CRON et migrations base de données 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
39
app/lib/presentation/pages/amicale_page.dart
Normal file
39
app/lib/presentation/pages/amicale_page.dart
Normal file
@@ -0,0 +1,39 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:geosector_app/presentation/widgets/app_scaffold.dart';
|
||||
import 'package:geosector_app/presentation/admin/admin_amicale_page.dart';
|
||||
import 'package:geosector_app/app.dart';
|
||||
|
||||
/// Page de l'amicale unifiée utilisant AppScaffold
|
||||
/// Accessible uniquement aux administrateurs (rôle 2)
|
||||
class AmicalePage extends StatelessWidget {
|
||||
const AmicalePage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Vérifier le rôle pour l'accès
|
||||
final currentUser = userRepository.getCurrentUser();
|
||||
final userRole = currentUser?.role ?? 1;
|
||||
|
||||
// Vérifier que l'utilisateur a le rôle 2 (admin amicale)
|
||||
if (userRole < 2) {
|
||||
// Rediriger ou afficher un message d'erreur
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
Navigator.of(context).pushReplacementNamed('/user/dashboard');
|
||||
});
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return AppScaffold(
|
||||
key: const ValueKey('amicale_scaffold_admin'),
|
||||
selectedIndex: 4, // Amicale est l'index 4
|
||||
pageTitle: 'Amicale & membres',
|
||||
body: AdminAmicalePage(
|
||||
userRepository: userRepository,
|
||||
amicaleRepository: amicaleRepository,
|
||||
membreRepository: membreRepository,
|
||||
passageRepository: passageRepository,
|
||||
operationRepository: operationRepository,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
32
app/lib/presentation/pages/field_mode_page.dart
Normal file
32
app/lib/presentation/pages/field_mode_page.dart
Normal file
@@ -0,0 +1,32 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:geosector_app/presentation/widgets/app_scaffold.dart';
|
||||
import 'package:geosector_app/presentation/user/user_field_mode_page.dart';
|
||||
import 'package:geosector_app/core/services/current_user_service.dart';
|
||||
|
||||
/// Page de mode terrain unifiée utilisant AppScaffold (users seulement)
|
||||
class FieldModePage extends StatelessWidget {
|
||||
const FieldModePage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Déterminer le mode d'affichage (prend en compte le mode choisi à la connexion)
|
||||
final isAdmin = CurrentUserService.instance.shouldShowAdminUI;
|
||||
|
||||
// Rediriger les admins vers le dashboard
|
||||
if (isAdmin) {
|
||||
// Les admins ne devraient pas avoir accès à cette page
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
Navigator.of(context).pushReplacementNamed('/admin');
|
||||
});
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return AppScaffold(
|
||||
key: const ValueKey('field_mode_scaffold_user'),
|
||||
selectedIndex: 4, // Field mode est l'index 4 pour les users (après Dashboard, Historique, Messages, Carte)
|
||||
pageTitle: 'Mode Terrain',
|
||||
showBackground: false, // Pas de fond inutile, le mode terrain a son propre fond
|
||||
body: const UserFieldModePage(), // Réutiliser la page existante
|
||||
);
|
||||
}
|
||||
}
|
||||
1737
app/lib/presentation/pages/history_page.dart
Normal file
1737
app/lib/presentation/pages/history_page.dart
Normal file
File diff suppressed because it is too large
Load Diff
276
app/lib/presentation/pages/home_page.dart
Normal file
276
app/lib/presentation/pages/home_page.dart
Normal file
@@ -0,0 +1,276 @@
|
||||
import 'package:geosector_app/app.dart'; // Pour accéder aux instances globales
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:geosector_app/core/data/models/sector_model.dart';
|
||||
import 'package:geosector_app/core/services/current_user_service.dart';
|
||||
import 'package:geosector_app/presentation/widgets/sector_distribution_card.dart';
|
||||
import 'package:geosector_app/presentation/widgets/charts/charts.dart';
|
||||
import 'package:geosector_app/presentation/widgets/members_board_passages.dart';
|
||||
import 'package:geosector_app/core/constants/app_keys.dart';
|
||||
import 'package:geosector_app/core/theme/app_theme.dart';
|
||||
import 'package:geosector_app/presentation/widgets/app_scaffold.dart';
|
||||
|
||||
/// Widget de contenu du tableau de bord unifié (sans scaffold)
|
||||
class HomeContent extends StatefulWidget {
|
||||
const HomeContent({super.key});
|
||||
|
||||
@override
|
||||
State<HomeContent> createState() => _HomeContentState();
|
||||
}
|
||||
|
||||
class _HomeContentState extends State<HomeContent> {
|
||||
// Détection du rôle
|
||||
late final bool isAdmin;
|
||||
late final int currentUserId;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
// Déterminer le rôle de l'utilisateur et le mode d'affichage
|
||||
final currentUser = userRepository.getCurrentUser();
|
||||
isAdmin = CurrentUserService.instance.shouldShowAdminUI;
|
||||
currentUserId = currentUser?.id ?? 0;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
debugPrint('Building HomeContent (isAdmin: $isAdmin)');
|
||||
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
final isDesktop = screenWidth > 800;
|
||||
|
||||
// Récupérer l'opération en cours
|
||||
final currentOperation = userRepository.getCurrentOperation();
|
||||
|
||||
// Titre dynamique avec l'ID et le nom de l'opération
|
||||
final String title = currentOperation != null
|
||||
? 'Opération #${currentOperation.id} ${currentOperation.name}'
|
||||
: 'Opération';
|
||||
|
||||
// Retourner seulement le contenu (sans scaffold)
|
||||
return SingleChildScrollView(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: isDesktop ? AppTheme.spacingL : AppTheme.spacingS,
|
||||
vertical: AppTheme.spacingL,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Titre
|
||||
Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppTheme.spacingM),
|
||||
|
||||
// LIGNE 1 : Graphiques de répartition (type de passage et mode de paiement)
|
||||
isDesktop
|
||||
? Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildPassageTypeCard(context),
|
||||
),
|
||||
const SizedBox(width: AppTheme.spacingM),
|
||||
Expanded(
|
||||
child: _buildPaymentTypeCard(context),
|
||||
),
|
||||
],
|
||||
)
|
||||
: Column(
|
||||
children: [
|
||||
_buildPassageTypeCard(context),
|
||||
const SizedBox(height: AppTheme.spacingM),
|
||||
_buildPaymentTypeCard(context),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: AppTheme.spacingL),
|
||||
|
||||
// Tableau détaillé des membres - uniquement pour admin sur Web
|
||||
if (isAdmin && kIsWeb) ...[
|
||||
const MembersBoardPassages(
|
||||
height: 700,
|
||||
),
|
||||
const SizedBox(height: AppTheme.spacingL),
|
||||
],
|
||||
|
||||
// LIGNE 2 : Carte de répartition par secteur
|
||||
// Le widget filtre automatiquement selon le rôle de l'utilisateur
|
||||
ValueListenableBuilder<Box<SectorModel>>(
|
||||
valueListenable: Hive.box<SectorModel>(AppKeys.sectorsBoxName).listenable(),
|
||||
builder: (context, Box<SectorModel> box, child) {
|
||||
// Filtrer les secteurs pour les users
|
||||
int sectorCount;
|
||||
if (isAdmin) {
|
||||
sectorCount = box.values.length;
|
||||
} else {
|
||||
final userSectors = userRepository.getUserSectors();
|
||||
sectorCount = userSectors.length;
|
||||
}
|
||||
|
||||
return SectorDistributionCard(
|
||||
title: '$sectorCount secteur${sectorCount > 1 ? 's' : ''}',
|
||||
height: 500,
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(height: AppTheme.spacingL),
|
||||
|
||||
// LIGNE 3 : Graphique d'activité
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(AppTheme.borderRadiusMedium),
|
||||
boxShadow: AppTheme.cardShadow,
|
||||
),
|
||||
child: ActivityChart(
|
||||
height: 350,
|
||||
showAllPassages: isAdmin, // Admin voit tout, user voit tous les passages de ses secteurs
|
||||
title: isAdmin
|
||||
? 'Passages réalisés par jour (15 derniers jours)'
|
||||
: 'Passages de mes secteurs par jour (15 derniers jours)',
|
||||
daysToShow: 15,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: AppTheme.spacingL),
|
||||
|
||||
// Actions rapides - uniquement pour admin sur le web
|
||||
if (isAdmin && kIsWeb) ...[
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(AppTheme.borderRadiusMedium),
|
||||
boxShadow: AppTheme.cardShadow,
|
||||
),
|
||||
padding: const EdgeInsets.all(AppTheme.spacingM),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Actions sur cette opération',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
color: AppTheme.primaryColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppTheme.spacingM),
|
||||
Wrap(
|
||||
spacing: AppTheme.spacingM,
|
||||
runSpacing: AppTheme.spacingM,
|
||||
children: [
|
||||
_buildActionButton(
|
||||
context,
|
||||
'Exporter les données',
|
||||
Icons.file_download_outlined,
|
||||
AppTheme.primaryColor,
|
||||
() {},
|
||||
),
|
||||
_buildActionButton(
|
||||
context,
|
||||
'Gérer les secteurs',
|
||||
Icons.map_outlined,
|
||||
AppTheme.accentColor,
|
||||
() {},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Construit la carte de répartition par type de passage
|
||||
Widget _buildPassageTypeCard(BuildContext context) {
|
||||
return PassageSummaryCard(
|
||||
title: isAdmin ? 'Passages' : 'Passages de mes secteurs',
|
||||
titleColor: AppTheme.primaryColor,
|
||||
titleIcon: Icons.route,
|
||||
height: 300,
|
||||
useValueListenable: true,
|
||||
showAllPassages: isAdmin, // Admin voit tout, user voit tous les passages de ses secteurs
|
||||
userId: null, // Pas de filtre par userId, on filtre par secteurs assignés
|
||||
excludePassageTypes: const [], // Afficher tous les types de passages
|
||||
customTotalDisplay: (total) => '$total passage${total > 1 ? 's' : ''}',
|
||||
isDesktop: MediaQuery.of(context).size.width > 800,
|
||||
backgroundIcon: Icons.route,
|
||||
backgroundIconColor: AppTheme.primaryColor,
|
||||
backgroundIconOpacity: 0.07,
|
||||
backgroundIconSize: 180,
|
||||
);
|
||||
}
|
||||
|
||||
// Construit la carte de répartition par mode de paiement
|
||||
Widget _buildPaymentTypeCard(BuildContext context) {
|
||||
return PaymentSummaryCard(
|
||||
title: isAdmin ? 'Règlements' : 'Mes règlements',
|
||||
titleColor: AppTheme.buttonSuccessColor,
|
||||
titleIcon: Icons.euro,
|
||||
height: 300,
|
||||
useValueListenable: true,
|
||||
showAllPayments: isAdmin, // Admin voit tout, user voit uniquement ses règlements (fkUser)
|
||||
userId: null, // Le filtre fkUser est géré automatiquement dans PaymentSummaryCard
|
||||
customTotalDisplay: (total) => '${total.toStringAsFixed(2)} €',
|
||||
isDesktop: MediaQuery.of(context).size.width > 800,
|
||||
backgroundIcon: Icons.euro,
|
||||
backgroundIconColor: AppTheme.primaryColor,
|
||||
backgroundIconOpacity: 0.07,
|
||||
backgroundIconSize: 180,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildActionButton(
|
||||
BuildContext context,
|
||||
String label,
|
||||
IconData icon,
|
||||
Color color,
|
||||
VoidCallback onPressed,
|
||||
) {
|
||||
return ElevatedButton.icon(
|
||||
onPressed: onPressed,
|
||||
icon: Icon(icon),
|
||||
label: Text(label),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: color,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: AppTheme.spacingL,
|
||||
vertical: AppTheme.spacingM,
|
||||
),
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppTheme.borderRadiusMedium),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Page autonome du tableau de bord unifié utilisant AppScaffold
|
||||
class HomePage extends StatelessWidget {
|
||||
const HomePage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Utiliser le mode d'affichage pour déterminer l'UI
|
||||
final isAdmin = CurrentUserService.instance.shouldShowAdminUI;
|
||||
|
||||
return AppScaffold(
|
||||
key: ValueKey('home_scaffold_${isAdmin ? 'admin' : 'user'}'),
|
||||
selectedIndex: 0, // Dashboard/Home est toujours l'index 0
|
||||
pageTitle: 'Tableau de bord',
|
||||
body: const HomeContent(),
|
||||
);
|
||||
}
|
||||
}
|
||||
4283
app/lib/presentation/pages/map_page.dart
Normal file
4283
app/lib/presentation/pages/map_page.dart
Normal file
File diff suppressed because it is too large
Load Diff
23
app/lib/presentation/pages/messages_page.dart
Normal file
23
app/lib/presentation/pages/messages_page.dart
Normal file
@@ -0,0 +1,23 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:geosector_app/presentation/widgets/app_scaffold.dart';
|
||||
import 'package:geosector_app/presentation/chat/chat_communication_page.dart';
|
||||
import 'package:geosector_app/core/services/current_user_service.dart';
|
||||
|
||||
/// Page de messages unifiée utilisant AppScaffold
|
||||
class MessagesPage extends StatelessWidget {
|
||||
const MessagesPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Utiliser le mode d'affichage pour déterminer l'UI
|
||||
final isAdmin = CurrentUserService.instance.shouldShowAdminUI;
|
||||
|
||||
return AppScaffold(
|
||||
key: ValueKey('messages_scaffold_${isAdmin ? 'admin' : 'user'}'),
|
||||
selectedIndex: 3, // Messages est l'index 3
|
||||
pageTitle: 'Messages',
|
||||
showBackground: false, // Pas de fond inutile, le chat a son propre fond
|
||||
body: const ChatCommunicationPage(), // Réutiliser la page de chat existante
|
||||
);
|
||||
}
|
||||
}
|
||||
36
app/lib/presentation/pages/operations_page.dart
Normal file
36
app/lib/presentation/pages/operations_page.dart
Normal file
@@ -0,0 +1,36 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:geosector_app/presentation/widgets/app_scaffold.dart';
|
||||
import 'package:geosector_app/presentation/admin/admin_operations_page.dart';
|
||||
import 'package:geosector_app/app.dart';
|
||||
|
||||
/// Page des opérations unifiée utilisant AppScaffold
|
||||
/// Accessible uniquement aux administrateurs (rôle 2)
|
||||
class OperationsPage extends StatelessWidget {
|
||||
const OperationsPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Vérifier le rôle pour l'accès
|
||||
final currentUser = userRepository.getCurrentUser();
|
||||
final userRole = currentUser?.role ?? 1;
|
||||
|
||||
// Vérifier que l'utilisateur a le rôle 2 (admin amicale)
|
||||
if (userRole < 2) {
|
||||
// Rediriger ou afficher un message d'erreur
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
Navigator.of(context).pushReplacementNamed('/user/dashboard');
|
||||
});
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return AppScaffold(
|
||||
key: const ValueKey('operations_scaffold_admin'),
|
||||
selectedIndex: 5, // Opérations est l'index 5
|
||||
pageTitle: 'Opérations',
|
||||
body: AdminOperationsPage(
|
||||
operationRepository: operationRepository,
|
||||
userRepository: userRepository,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user