946 lines
31 KiB
Dart
946 lines
31 KiB
Dart
import 'package:geosector_app/app.dart'; // Pour accéder aux instances globales
|
|
import 'package:flutter/material.dart';
|
|
import 'package:geosector_app/app.dart'; // Pour accéder aux instances globales
|
|
import 'package:geosector_app/core/constants/app_keys.dart';
|
|
import 'package:geosector_app/core/data/models/passage_model.dart';
|
|
import 'package:geosector_app/core/data/models/sector_model.dart';
|
|
import 'package:geosector_app/core/data/models/user_model.dart';
|
|
import 'package:geosector_app/core/repositories/passage_repository.dart';
|
|
import 'package:geosector_app/core/repositories/sector_repository.dart';
|
|
import 'package:geosector_app/core/repositories/user_repository.dart';
|
|
import 'package:geosector_app/core/theme/app_theme.dart';
|
|
import 'package:geosector_app/presentation/widgets/passages/passages_list_widget.dart';
|
|
import 'dart:math' as math;
|
|
|
|
/// Class pour dessiner les petits points blancs sur le fond
|
|
class DotsPainter extends CustomPainter {
|
|
@override
|
|
void paint(Canvas canvas, Size size) {
|
|
final paint = Paint()
|
|
..color = Colors.white.withOpacity(0.5)
|
|
..style = PaintingStyle.fill;
|
|
|
|
final random = math.Random(42); // Seed fixe pour consistance
|
|
final numberOfDots = (size.width * size.height) ~/ 1500;
|
|
|
|
for (int i = 0; i < numberOfDots; i++) {
|
|
final x = random.nextDouble() * size.width;
|
|
final y = random.nextDouble() * size.height;
|
|
final radius = 1.0 + random.nextDouble() * 2.0;
|
|
canvas.drawCircle(Offset(x, y), radius, paint);
|
|
}
|
|
}
|
|
|
|
@override
|
|
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
|
}
|
|
|
|
class AdminHistoryPage extends StatefulWidget {
|
|
const AdminHistoryPage({Key? key}) : super(key: key);
|
|
|
|
@override
|
|
State<AdminHistoryPage> createState() => _AdminHistoryPageState();
|
|
}
|
|
|
|
class _AdminHistoryPageState extends State<AdminHistoryPage> {
|
|
// État des filtres
|
|
String searchQuery = '';
|
|
String selectedSector = 'Tous';
|
|
String selectedUser = 'Tous';
|
|
String selectedType = 'Tous';
|
|
String selectedPaymentMethod = 'Tous';
|
|
String selectedPeriod = 'Dernier mois'; // Période par défaut
|
|
DateTimeRange? selectedDateRange;
|
|
|
|
// IDs pour les filtres
|
|
int? selectedSectorId;
|
|
int? selectedUserId;
|
|
|
|
// Listes pour les filtres
|
|
List<SectorModel> _sectors = [];
|
|
List<UserModel> _users = [];
|
|
|
|
// Repositories
|
|
late PassageRepository _passageRepository;
|
|
late SectorRepository _sectorRepository;
|
|
late UserRepository _userRepository;
|
|
|
|
// Passages formatés
|
|
List<Map<String, dynamic>> _formattedPassages = [];
|
|
|
|
// État de chargement
|
|
bool _isLoading = true;
|
|
String _errorMessage = '';
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
// Initialiser les filtres
|
|
_initializeFilters();
|
|
}
|
|
|
|
@override
|
|
void didChangeDependencies() {
|
|
super.didChangeDependencies();
|
|
// Récupérer les repositories une seule fois
|
|
_loadRepositories();
|
|
}
|
|
|
|
// Charger les repositories et les données
|
|
void _loadRepositories() {
|
|
try {
|
|
// Utiliser les instances globales définies dans app.dart
|
|
_passageRepository = passageRepository;
|
|
_userRepository = userRepository;
|
|
_sectorRepository = sectorRepository;
|
|
|
|
// Charger les secteurs et les utilisateurs
|
|
_loadSectorsAndUsers();
|
|
|
|
// Charger les passages
|
|
_loadPassages();
|
|
} catch (e) {
|
|
setState(() {
|
|
_isLoading = false;
|
|
_errorMessage = 'Erreur lors du chargement des repositories: $e';
|
|
});
|
|
}
|
|
}
|
|
|
|
// Charger les secteurs et les utilisateurs
|
|
void _loadSectorsAndUsers() {
|
|
try {
|
|
// Récupérer la liste des secteurs
|
|
_sectors = _sectorRepository.getAllSectors();
|
|
debugPrint('Nombre de secteurs récupérés: ${_sectors.length}');
|
|
|
|
// Récupérer la liste des utilisateurs
|
|
_users = _userRepository.getAllUsers();
|
|
debugPrint('Nombre d\'utilisateurs récupérés: ${_users.length}');
|
|
} catch (e) {
|
|
debugPrint('Erreur lors du chargement des secteurs et utilisateurs: $e');
|
|
}
|
|
}
|
|
|
|
// Charger les passages
|
|
void _loadPassages() {
|
|
setState(() {
|
|
_isLoading = true;
|
|
});
|
|
|
|
try {
|
|
// Récupérer les passages
|
|
final List<PassageModel> allPassages =
|
|
_passageRepository.getAllPassages();
|
|
|
|
// Convertir les passages en format attendu par PassagesListWidget
|
|
_formattedPassages = _formatPassagesForWidget(
|
|
allPassages, _sectorRepository, _userRepository);
|
|
|
|
setState(() {
|
|
_isLoading = false;
|
|
});
|
|
} catch (e) {
|
|
setState(() {
|
|
_isLoading = false;
|
|
_errorMessage = 'Erreur lors du chargement des passages: $e';
|
|
});
|
|
}
|
|
}
|
|
|
|
// Initialiser les filtres
|
|
void _initializeFilters() {
|
|
// Par défaut, on n'applique pas de filtre par utilisateur ou secteur
|
|
selectedSectorId = null;
|
|
selectedUserId = null;
|
|
|
|
// Période par défaut : dernier mois
|
|
selectedPeriod = 'Dernier mois';
|
|
|
|
// Plage de dates par défaut : dernier mois
|
|
final DateTime now = DateTime.now();
|
|
final DateTime oneMonthAgo = DateTime(now.year, now.month - 1, now.day);
|
|
selectedDateRange = DateTimeRange(start: oneMonthAgo, end: now);
|
|
}
|
|
|
|
// Mettre à jour le filtre par secteur
|
|
void _updateSectorFilter(String sectorName, int? sectorId) {
|
|
setState(() {
|
|
selectedSector = sectorName;
|
|
selectedSectorId = sectorId;
|
|
});
|
|
}
|
|
|
|
// Mettre à jour le filtre par utilisateur
|
|
void _updateUserFilter(String userName, int? userId) {
|
|
setState(() {
|
|
selectedUser = userName;
|
|
selectedUserId = userId;
|
|
});
|
|
}
|
|
|
|
// Mettre à jour le filtre par période
|
|
void _updatePeriodFilter(String period) {
|
|
setState(() {
|
|
selectedPeriod = period;
|
|
|
|
// Mettre à jour la plage de dates en fonction de la période
|
|
final DateTime now = DateTime.now();
|
|
|
|
switch (period) {
|
|
case 'Derniers 15 jours':
|
|
selectedDateRange = DateTimeRange(
|
|
start: now.subtract(const Duration(days: 15)),
|
|
end: now,
|
|
);
|
|
break;
|
|
case 'Dernière semaine':
|
|
selectedDateRange = DateTimeRange(
|
|
start: now.subtract(const Duration(days: 7)),
|
|
end: now,
|
|
);
|
|
break;
|
|
case 'Dernier mois':
|
|
selectedDateRange = DateTimeRange(
|
|
start: DateTime(now.year, now.month - 1, now.day),
|
|
end: now,
|
|
);
|
|
break;
|
|
case 'Tous':
|
|
selectedDateRange = null;
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
// Afficher un widget de chargement ou d'erreur si nécessaire
|
|
if (_isLoading) {
|
|
return Stack(
|
|
children: [
|
|
// Fond dégradé avec petits points blancs
|
|
Container(
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
begin: Alignment.topCenter,
|
|
end: Alignment.bottomCenter,
|
|
colors: [Colors.white, Colors.red.shade300],
|
|
),
|
|
),
|
|
child: CustomPaint(
|
|
painter: DotsPainter(),
|
|
child: Container(width: double.infinity, height: double.infinity),
|
|
),
|
|
),
|
|
const Center(
|
|
child: CircularProgressIndicator(),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
if (_errorMessage.isNotEmpty) {
|
|
return _buildErrorWidget(_errorMessage);
|
|
}
|
|
|
|
// Retourner le widget principal avec les données chargées
|
|
return Stack(
|
|
children: [
|
|
// Fond dégradé avec petits points blancs
|
|
Container(
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
begin: Alignment.topCenter,
|
|
end: Alignment.bottomCenter,
|
|
colors: [Colors.white, Colors.red.shade300],
|
|
),
|
|
),
|
|
child: CustomPaint(
|
|
painter: DotsPainter(),
|
|
child: Container(width: double.infinity, height: double.infinity),
|
|
),
|
|
),
|
|
// Contenu de la page
|
|
Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Titre de la page
|
|
Text(
|
|
'Historique des passages',
|
|
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
color: Theme.of(context).colorScheme.primary,
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
// Filtres supplémentaires (secteur, utilisateur, période)
|
|
_buildAdditionalFilters(context),
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
// Widget de liste des passages
|
|
Expanded(
|
|
child: PassagesListWidget(
|
|
passages: _formattedPassages,
|
|
showFilters: true,
|
|
showSearch: true,
|
|
showActions: true,
|
|
initialSearchQuery: searchQuery,
|
|
initialTypeFilter: selectedType,
|
|
initialPaymentFilter: selectedPaymentMethod,
|
|
// Exclure les passages de type 2 (À finaliser)
|
|
excludePassageTypes: [2],
|
|
// Filtres par utilisateur et secteur
|
|
filterByUserId: selectedUserId,
|
|
filterBySectorId: selectedSectorId,
|
|
// Période par défaut (dernier mois)
|
|
periodFilter: 'lastMonth',
|
|
// Plage de dates personnalisée si définie
|
|
dateRange: selectedDateRange,
|
|
onPassageSelected: (passage) {
|
|
_showDetailsDialog(context, passage);
|
|
},
|
|
onReceiptView: (passage) {
|
|
_showReceiptDialog(context, passage);
|
|
},
|
|
onDetailsView: (passage) {
|
|
_showDetailsDialog(context, passage);
|
|
},
|
|
onPassageEdit: (passage) {
|
|
// Action pour modifier le passage
|
|
// Cette fonctionnalité pourrait être implémentée ultérieurement
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
// Widget d'erreur pour afficher un message d'erreur
|
|
Widget _buildErrorWidget(String message) {
|
|
return Stack(
|
|
children: [
|
|
// Fond dégradé avec petits points blancs
|
|
Container(
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
begin: Alignment.topCenter,
|
|
end: Alignment.bottomCenter,
|
|
colors: [Colors.white, Colors.red.shade300],
|
|
),
|
|
),
|
|
child: CustomPaint(
|
|
painter: DotsPainter(),
|
|
child: Container(width: double.infinity, height: double.infinity),
|
|
),
|
|
),
|
|
Center(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
const Icon(
|
|
Icons.error_outline,
|
|
color: Colors.red,
|
|
size: 64,
|
|
),
|
|
const SizedBox(height: 16),
|
|
Text(
|
|
'Erreur',
|
|
style: TextStyle(
|
|
fontSize: 24,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.red[700],
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
message,
|
|
textAlign: TextAlign.center,
|
|
style: const TextStyle(fontSize: 16),
|
|
),
|
|
const SizedBox(height: 24),
|
|
ElevatedButton(
|
|
onPressed: () {
|
|
// Recharger la page
|
|
setState(() {});
|
|
},
|
|
child: const Text('Réessayer'),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
// Convertir les passages du modèle Hive vers le format attendu par le widget
|
|
List<Map<String, dynamic>> _formatPassagesForWidget(
|
|
List<PassageModel> passages,
|
|
SectorRepository sectorRepository,
|
|
UserRepository userRepository) {
|
|
return passages.map((passage) {
|
|
// Récupérer le secteur associé au passage
|
|
final SectorModel? sector =
|
|
sectorRepository.getSectorById(passage.fkSector);
|
|
|
|
// Récupérer l'utilisateur associé au passage
|
|
final UserModel? user = userRepository.getUserById(passage.fkUser);
|
|
|
|
// Construire l'adresse complète
|
|
final String address =
|
|
'${passage.numero} ${passage.rue}${passage.rueBis.isNotEmpty ? ' ${passage.rueBis}' : ''}, ${passage.ville}';
|
|
|
|
// Déterminer si le passage a une erreur d'envoi de reçu
|
|
final bool hasError = passage.emailErreur.isNotEmpty;
|
|
|
|
return {
|
|
'id': passage.id,
|
|
'date': passage.passedAt,
|
|
'address': address,
|
|
'fkSector': passage.fkSector,
|
|
'sector': sector?.libelle ?? 'Secteur inconnu',
|
|
'fkUser': passage.fkUser,
|
|
'user': user?.name ?? 'Utilisateur inconnu',
|
|
'type': passage.fkType,
|
|
'amount': double.tryParse(passage.montant) ?? 0.0,
|
|
'payment': passage.fkTypeReglement,
|
|
'email': passage.email,
|
|
'hasReceipt': passage.nomRecu.isNotEmpty,
|
|
'hasError': hasError,
|
|
'notes': passage.remarque,
|
|
'name': passage.name,
|
|
'phone': passage.phone,
|
|
// Ajouter d'autres champs nécessaires pour le widget
|
|
};
|
|
}).toList();
|
|
}
|
|
|
|
void _showReceiptDialog(BuildContext context, Map<String, dynamic> passage) {
|
|
final int passageId = passage['id'] as int;
|
|
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
title: Text('Reçu du passage #$passageId'),
|
|
content: const SizedBox(
|
|
width: 500,
|
|
height: 600,
|
|
child: Center(
|
|
child: Text('Aperçu du reçu PDF'),
|
|
),
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
child: const Text('Fermer'),
|
|
),
|
|
ElevatedButton(
|
|
onPressed: () {
|
|
// Action pour télécharger le reçu
|
|
Navigator.pop(context);
|
|
},
|
|
child: const Text('Télécharger'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
void _showDetailsDialog(BuildContext context, Map<String, dynamic> passage) {
|
|
final int passageId = passage['id'] as int;
|
|
final DateTime date = passage['date'] as DateTime;
|
|
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
title: Text('Détails du passage #$passageId'),
|
|
content: SizedBox(
|
|
width: 500,
|
|
child: SingleChildScrollView(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
_buildDetailRow('Date',
|
|
'${date.day}/${date.month}/${date.year} à ${date.hour}h${date.minute.toString().padLeft(2, '0')}'),
|
|
_buildDetailRow('Adresse', passage['address'] as String),
|
|
_buildDetailRow('Secteur', passage['sector'] as String),
|
|
_buildDetailRow('Collecteur', passage['user'] as String),
|
|
_buildDetailRow(
|
|
'Type',
|
|
AppKeys.typesPassages[passage['type']]?['titre'] ??
|
|
'Inconnu'),
|
|
_buildDetailRow('Montant', '${passage['amount']} €'),
|
|
_buildDetailRow(
|
|
'Mode de paiement',
|
|
AppKeys.typesReglements[passage['payment']]?['titre'] ??
|
|
'Inconnu'),
|
|
_buildDetailRow('Email', passage['email'] as String),
|
|
_buildDetailRow(
|
|
'Reçu envoyé', passage['hasReceipt'] ? 'Oui' : 'Non'),
|
|
_buildDetailRow(
|
|
'Erreur d\'envoi', passage['hasError'] ? 'Oui' : 'Non'),
|
|
_buildDetailRow(
|
|
'Notes',
|
|
(passage['notes'] as String).isEmpty
|
|
? '-'
|
|
: passage['notes'] as String),
|
|
const SizedBox(height: 16),
|
|
const Text(
|
|
'Historique des actions',
|
|
style: TextStyle(fontWeight: FontWeight.bold),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Container(
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
color: Colors.grey[100],
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_buildHistoryItem(
|
|
date,
|
|
passage['user'] as String,
|
|
'Création du passage',
|
|
),
|
|
if (passage['hasReceipt'])
|
|
_buildHistoryItem(
|
|
date.add(const Duration(minutes: 5)),
|
|
'Système',
|
|
'Envoi du reçu par email',
|
|
),
|
|
if (passage['hasError'])
|
|
_buildHistoryItem(
|
|
date.add(const Duration(minutes: 6)),
|
|
'Système',
|
|
'Erreur lors de l\'envoi du reçu',
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
child: const Text('Fermer'),
|
|
),
|
|
ElevatedButton(
|
|
onPressed: () {
|
|
// Action pour modifier le passage
|
|
Navigator.pop(context);
|
|
},
|
|
child: const Text('Modifier'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildDetailRow(String label, String value) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(bottom: 8),
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
SizedBox(
|
|
width: 150,
|
|
child: Text(
|
|
'$label :',
|
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
|
),
|
|
),
|
|
Expanded(
|
|
child: Text(value),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildHistoryItem(DateTime date, String user, String action) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(bottom: 8),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'${date.day}/${date.month}/${date.year} à ${date.hour}h${date.minute.toString().padLeft(2, '0')}',
|
|
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 12),
|
|
),
|
|
Text('$user - $action'),
|
|
const Divider(),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
// Construction des filtres supplémentaires
|
|
Widget _buildAdditionalFilters(BuildContext context) {
|
|
final theme = Theme.of(context);
|
|
final size = MediaQuery.of(context).size;
|
|
final isDesktop = size.width > 900;
|
|
|
|
return Card(
|
|
elevation: 2,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
color: Colors.white, // Fond opaque
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Filtres avancés',
|
|
style: theme.textTheme.titleMedium?.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
color: theme.colorScheme.primary,
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// Disposition des filtres en fonction de la taille de l'écran
|
|
isDesktop
|
|
? Row(
|
|
children: [
|
|
// Filtre par secteur
|
|
Expanded(
|
|
child: _buildSectorFilter(theme, _sectors),
|
|
),
|
|
const SizedBox(width: 16),
|
|
|
|
// Filtre par utilisateur
|
|
Expanded(
|
|
child: _buildUserFilter(theme, _users),
|
|
),
|
|
const SizedBox(width: 16),
|
|
|
|
// Filtre par période
|
|
Expanded(
|
|
child: _buildPeriodFilter(theme),
|
|
),
|
|
],
|
|
)
|
|
: Column(
|
|
children: [
|
|
// Filtre par secteur
|
|
_buildSectorFilter(theme, _sectors),
|
|
const SizedBox(height: 16),
|
|
|
|
// Filtre par utilisateur
|
|
_buildUserFilter(theme, _users),
|
|
const SizedBox(height: 16),
|
|
|
|
// Filtre par période
|
|
_buildPeriodFilter(theme),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Construction du filtre par secteur
|
|
Widget _buildSectorFilter(ThemeData theme, List<SectorModel> sectors) {
|
|
// Vérifier si la liste des secteurs est vide ou si selectedSector n'est pas dans la liste
|
|
bool isSelectedSectorValid = selectedSector == 'Tous' ||
|
|
sectors.any((s) => s.libelle == selectedSector);
|
|
|
|
// Si selectedSector n'est pas valide, le réinitialiser à 'Tous'
|
|
if (!isSelectedSectorValid) {
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
if (mounted) {
|
|
setState(() {
|
|
selectedSector = 'Tous';
|
|
selectedSectorId = null;
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Secteur',
|
|
style: theme.textTheme.bodyMedium?.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
|
decoration: BoxDecoration(
|
|
border: Border.all(color: theme.colorScheme.outline),
|
|
borderRadius: BorderRadius.circular(8.0),
|
|
),
|
|
child: DropdownButtonHideUnderline(
|
|
child: DropdownButton<String>(
|
|
value: isSelectedSectorValid ? selectedSector : 'Tous',
|
|
isExpanded: true,
|
|
icon: const Icon(Icons.arrow_drop_down),
|
|
items: [
|
|
const DropdownMenuItem<String>(
|
|
value: 'Tous',
|
|
child: Text('Tous les secteurs'),
|
|
),
|
|
...sectors.map((sector) {
|
|
final String libelle = sector.libelle.isNotEmpty
|
|
? sector.libelle
|
|
: 'Secteur ${sector.id}';
|
|
return DropdownMenuItem<String>(
|
|
value: libelle,
|
|
child: Text(
|
|
libelle,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
);
|
|
}).toList(),
|
|
],
|
|
onChanged: (String? value) {
|
|
if (value != null) {
|
|
if (value == 'Tous') {
|
|
_updateSectorFilter('Tous', null);
|
|
} else {
|
|
try {
|
|
// Trouver le secteur correspondant
|
|
final sector = sectors.firstWhere(
|
|
(s) => s.libelle == value,
|
|
orElse: () => sectors.isNotEmpty
|
|
? sectors.first
|
|
: throw Exception('Liste de secteurs vide'),
|
|
);
|
|
// Convertir sector.id en int? si nécessaire
|
|
_updateSectorFilter(value, sector.id);
|
|
} catch (e) {
|
|
debugPrint('Erreur lors de la sélection du secteur: $e');
|
|
_updateSectorFilter('Tous', null);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
// Construction du filtre par utilisateur
|
|
Widget _buildUserFilter(ThemeData theme, List<UserModel> users) {
|
|
// Vérifier si la liste des utilisateurs est vide ou si selectedUser n'est pas dans la liste
|
|
bool isSelectedUserValid = selectedUser == 'Tous' ||
|
|
users.any((u) => (u.name ?? 'Utilisateur inconnu') == selectedUser);
|
|
|
|
// Si selectedUser n'est pas valide, le réinitialiser à 'Tous'
|
|
if (!isSelectedUserValid) {
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
if (mounted) {
|
|
setState(() {
|
|
selectedUser = 'Tous';
|
|
selectedUserId = null;
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Utilisateur',
|
|
style: theme.textTheme.bodyMedium?.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
|
decoration: BoxDecoration(
|
|
border: Border.all(color: theme.colorScheme.outline),
|
|
borderRadius: BorderRadius.circular(8.0),
|
|
),
|
|
child: DropdownButtonHideUnderline(
|
|
child: DropdownButton<String>(
|
|
value: isSelectedUserValid ? selectedUser : 'Tous',
|
|
isExpanded: true,
|
|
icon: const Icon(Icons.arrow_drop_down),
|
|
items: [
|
|
const DropdownMenuItem<String>(
|
|
value: 'Tous',
|
|
child: Text('Tous les utilisateurs'),
|
|
),
|
|
...users.map((user) {
|
|
// S'assurer que user.name n'est pas null
|
|
final String userName = user.name ?? 'Utilisateur inconnu';
|
|
return DropdownMenuItem<String>(
|
|
value: userName,
|
|
child: Text(
|
|
userName,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
);
|
|
}).toList(),
|
|
],
|
|
onChanged: (String? value) {
|
|
if (value != null) {
|
|
if (value == 'Tous') {
|
|
_updateUserFilter('Tous', null);
|
|
} else {
|
|
try {
|
|
// Trouver l'utilisateur correspondant
|
|
final user = users.firstWhere(
|
|
(u) => (u.name ?? 'Utilisateur inconnu') == value,
|
|
orElse: () => users.isNotEmpty
|
|
? users.first
|
|
: throw Exception('Liste d\'utilisateurs vide'),
|
|
);
|
|
// S'assurer que user.name et user.id ne sont pas null
|
|
final String userName =
|
|
user.name ?? 'Utilisateur inconnu';
|
|
final int? userId = user.id;
|
|
_updateUserFilter(userName, userId);
|
|
} catch (e) {
|
|
debugPrint(
|
|
'Erreur lors de la sélection de l\'utilisateur: $e');
|
|
_updateUserFilter('Tous', null);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
// Construction du filtre par période
|
|
Widget _buildPeriodFilter(ThemeData theme) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Période',
|
|
style: theme.textTheme.bodyMedium?.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
|
decoration: BoxDecoration(
|
|
border: Border.all(color: theme.colorScheme.outline),
|
|
borderRadius: BorderRadius.circular(8.0),
|
|
),
|
|
child: DropdownButtonHideUnderline(
|
|
child: DropdownButton<String>(
|
|
value: selectedPeriod,
|
|
isExpanded: true,
|
|
icon: const Icon(Icons.arrow_drop_down),
|
|
items: const [
|
|
DropdownMenuItem<String>(
|
|
value: 'Tous',
|
|
child: Text('Toutes les périodes'),
|
|
),
|
|
DropdownMenuItem<String>(
|
|
value: 'Derniers 15 jours',
|
|
child: Text('Derniers 15 jours'),
|
|
),
|
|
DropdownMenuItem<String>(
|
|
value: 'Dernière semaine',
|
|
child: Text('Dernière semaine'),
|
|
),
|
|
DropdownMenuItem<String>(
|
|
value: 'Dernier mois',
|
|
child: Text('Dernier mois'),
|
|
),
|
|
],
|
|
onChanged: (String? value) {
|
|
if (value != null) {
|
|
_updatePeriodFilter(value);
|
|
}
|
|
},
|
|
),
|
|
),
|
|
),
|
|
|
|
// Afficher la plage de dates sélectionnée si elle existe
|
|
if (selectedDateRange != null && selectedPeriod != 'Tous')
|
|
Padding(
|
|
padding: const EdgeInsets.only(top: 8.0),
|
|
child: Row(
|
|
children: [
|
|
Icon(
|
|
Icons.date_range,
|
|
size: 16,
|
|
color: theme.colorScheme.primary,
|
|
),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
'Du ${selectedDateRange!.start.day}/${selectedDateRange!.start.month}/${selectedDateRange!.start.year} au ${selectedDateRange!.end.day}/${selectedDateRange!.end.month}/${selectedDateRange!.end.year}',
|
|
style: theme.textTheme.bodySmall?.copyWith(
|
|
color: theme.colorScheme.primary,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
void _showResendConfirmation(BuildContext context, int passageId) {
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
title: const Text('Renvoyer le reçu'),
|
|
content: Text(
|
|
'Êtes-vous sûr de vouloir renvoyer le reçu du passage #$passageId ?'),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
child: const Text('Annuler'),
|
|
),
|
|
ElevatedButton(
|
|
onPressed: () {
|
|
// Action pour renvoyer le reçu
|
|
Navigator.pop(context);
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content:
|
|
Text('Reçu du passage #$passageId renvoyé avec succès'),
|
|
backgroundColor: Colors.green,
|
|
),
|
|
);
|
|
},
|
|
child: const Text('Renvoyer'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|