Files
geo/app/lib/presentation/pages/home_page.dart

277 lines
10 KiB
Dart

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(),
const SizedBox(height: AppTheme.spacingL),
],
// LIGNE 2 : Carte de répartition par secteur (uniquement si > 1 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;
}
// N'afficher que s'il y a plus d'un secteur
if (sectorCount <= 1) {
return const SizedBox.shrink();
}
return SectorDistributionCard(
title: '$sectorCount secteurs',
);
},
),
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' : 'Mes passages',
daysToShow: 7,
showPeriodButtons: true,
),
),
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(),
);
}
}