feat: Mise à jour des interfaces mobiles v3.2.3
- Amélioration des interfaces utilisateur sur mobile - Optimisation de la responsivité des composants Flutter - Mise à jour des widgets de chat et communication - Amélioration des formulaires et tableaux - Ajout de nouveaux composants pour l'administration - Optimisation des thèmes et styles visuels 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:geosector_app/core/theme/app_theme.dart';
|
||||
import '../custom_text_field.dart';
|
||||
|
||||
class PassageForm extends StatefulWidget {
|
||||
@@ -217,21 +218,21 @@ class _PassageFormState extends State<PassageForm> {
|
||||
decoration: InputDecoration(
|
||||
hintText: '0.00 €',
|
||||
hintStyle: theme.textTheme.bodyLarge?.copyWith(
|
||||
color: theme.colorScheme.onSurface.withOpacity(0.5),
|
||||
color: theme.colorScheme.onSurface.withValues(alpha: 0.5),
|
||||
),
|
||||
fillColor: const Color(0xFFF4F5F6),
|
||||
filled: true,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderSide: BorderSide(
|
||||
color: theme.colorScheme.onSurface.withOpacity(0.1),
|
||||
color: theme.colorScheme.onSurface.withValues(alpha: 0.1),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderSide: BorderSide(
|
||||
color: theme.colorScheme.onSurface.withOpacity(0.1),
|
||||
color: theme.colorScheme.onSurface.withValues(alpha: 0.1),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
@@ -312,10 +313,10 @@ class _PassageFormState extends State<PassageForm> {
|
||||
),
|
||||
minimumSize: const Size(200, 50),
|
||||
),
|
||||
child: const Text(
|
||||
child: Text(
|
||||
'Enregistrer',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontSize: AppTheme.r(context, 18),
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
@@ -332,7 +333,6 @@ class _PassageFormState extends State<PassageForm> {
|
||||
required Function(String?) onChanged,
|
||||
}) {
|
||||
final theme = Theme.of(context);
|
||||
final isSelected = value == groupValue;
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
@@ -359,10 +359,10 @@ class _PassageFormState extends State<PassageForm> {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 5),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF4F5F6).withOpacity(0.85),
|
||||
color: const Color(0xFFF4F5F6).withValues(alpha: 0.85),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color: const Color(0xFF20335E).withOpacity(0.1),
|
||||
color: const Color(0xFF20335E).withValues(alpha: 0.1),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:geosector_app/core/constants/app_keys.dart';
|
||||
import 'package:geosector_app/core/theme/app_theme.dart';
|
||||
import 'package:geosector_app/core/services/current_amicale_service.dart';
|
||||
import 'package:geosector_app/core/repositories/passage_repository.dart';
|
||||
import 'package:geosector_app/core/utils/api_exception.dart';
|
||||
import 'package:geosector_app/app.dart';
|
||||
import 'package:geosector_app/core/data/models/passage_model.dart';
|
||||
@@ -40,7 +40,7 @@ class PassagesListWidget extends StatefulWidget {
|
||||
|
||||
/// Callback appelé lorsque les détails sont demandés
|
||||
final Function(Map<String, dynamic>)? onDetailsView;
|
||||
|
||||
|
||||
/// Callback appelé lorsqu'un passage est supprimé (optionnel)
|
||||
final Function(Map<String, dynamic>)? onPassageDelete;
|
||||
|
||||
@@ -64,16 +64,16 @@ class PassagesListWidget extends StatefulWidget {
|
||||
|
||||
/// Plage de dates personnalisée pour le filtrage (utilisé si periodFilter = 'custom')
|
||||
final DateTimeRange? dateRange;
|
||||
|
||||
|
||||
/// Méthode de tri des passages ('date' par défaut, ou 'distance' pour le mode terrain)
|
||||
final String? sortBy;
|
||||
|
||||
|
||||
/// Widgets personnalisés pour les boutons de tri à afficher dans le header
|
||||
final Widget? sortingButtons;
|
||||
|
||||
|
||||
/// Si vrai, affiche un bouton pour ajouter un nouveau passage
|
||||
final bool showAddButton;
|
||||
|
||||
|
||||
/// Callback appelé lorsque le bouton d'ajout est cliqué
|
||||
final VoidCallback? onAddPassage;
|
||||
|
||||
@@ -126,7 +126,7 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
_searchQuery = widget.initialSearchQuery ?? '';
|
||||
_searchController.text = _searchQuery;
|
||||
}
|
||||
|
||||
|
||||
// Vérifier si l'amicale autorise la suppression des passages
|
||||
bool _canDeletePassages() {
|
||||
try {
|
||||
@@ -135,11 +135,12 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
return amicale.chkUserDeletePass == true;
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la vérification des permissions de suppression: $e');
|
||||
debugPrint(
|
||||
'Erreur lors de la vérification des permissions de suppression: $e');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Gestion du clic sur un passage avec flux conditionnel
|
||||
void _handlePassageClick(Map<String, dynamic> passage) {
|
||||
// Si un callback personnalisé est fourni, l'utiliser
|
||||
@@ -151,11 +152,11 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
// Sinon, utiliser le flux conditionnel par défaut
|
||||
final int passageType = passage['type'] as int? ?? 1;
|
||||
final int passageId = passage['id'] as int;
|
||||
|
||||
|
||||
// Récupérer le PassageModel depuis Hive
|
||||
final passagesBox = Hive.box<PassageModel>(AppKeys.passagesBoxName);
|
||||
final passageModel = passagesBox.get(passageId);
|
||||
|
||||
|
||||
if (passageModel == null) {
|
||||
ApiException.showError(context, Exception('Passage introuvable'));
|
||||
return;
|
||||
@@ -171,14 +172,15 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
}
|
||||
|
||||
// Afficher le dialog de détails avec option de modification
|
||||
void _showDetailsDialogWithEditOption(BuildContext context, Map<String, dynamic> passage, PassageModel passageModel) {
|
||||
void _showDetailsDialogWithEditOption(BuildContext context,
|
||||
Map<String, dynamic> passage, PassageModel passageModel) {
|
||||
final int passageId = passage['id'] as int;
|
||||
final DateTime date = passage['date'] as DateTime;
|
||||
final theme = Theme.of(context);
|
||||
final int passageType = passage['type'] as int? ?? 1;
|
||||
final typeInfo = AppKeys.typesPassages[passageType];
|
||||
final paymentInfo = AppKeys.typesReglements[passage['payment']];
|
||||
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext dialogContext) {
|
||||
@@ -191,7 +193,7 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: theme.dividerColor.withOpacity(0.3),
|
||||
color: theme.dividerColor.withValues(alpha: 0.3),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
@@ -202,7 +204,8 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: Color(typeInfo?['couleur1'] ?? Colors.blue.value).withOpacity(0.1),
|
||||
color: Color(typeInfo?['couleur1'] ?? Colors.blue.value)
|
||||
.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Icon(
|
||||
@@ -224,16 +227,20 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: Color(typeInfo?['couleur1'] ?? Colors.blue.value).withOpacity(0.1),
|
||||
color:
|
||||
Color(typeInfo?['couleur1'] ?? Colors.blue.value)
|
||||
.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
typeInfo?['titre'] ?? 'Inconnu',
|
||||
style: TextStyle(
|
||||
color: Color(typeInfo?['couleur1'] ?? Colors.blue.value),
|
||||
fontSize: 12,
|
||||
color: Color(
|
||||
typeInfo?['couleur1'] ?? Colors.blue.value),
|
||||
fontSize: AppTheme.r(context, 12),
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
@@ -257,44 +264,50 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.surfaceVariant.withOpacity(0.3),
|
||||
color: theme.colorScheme.surfaceContainerHighest
|
||||
.withValues(alpha: 0.3),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
_buildDetailRow('Adresse', passage['address'] as String? ?? '', Icons.home),
|
||||
if (passage.containsKey('name') && passage['name'] != null && (passage['name'] as String).isNotEmpty)
|
||||
_buildDetailRow('Nom', passage['name'] as String, Icons.person),
|
||||
_buildDetailRow('Adresse',
|
||||
passage['address'] as String? ?? '', Icons.home),
|
||||
if (passage.containsKey('name') &&
|
||||
passage['name'] != null &&
|
||||
(passage['name'] as String).isNotEmpty)
|
||||
_buildDetailRow(
|
||||
'Nom', passage['name'] as String, Icons.person),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
|
||||
// Section Informations
|
||||
_buildSectionHeader(Icons.info, 'Informations', theme),
|
||||
const SizedBox(height: 12),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.surfaceVariant.withOpacity(0.3),
|
||||
color: theme.colorScheme.surfaceContainerHighest
|
||||
.withValues(alpha: 0.3),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
_buildDetailRow(
|
||||
'Date',
|
||||
'${date.day.toString().padLeft(2, '0')}/${date.month.toString().padLeft(2, '0')}/${date.year} à ${date.hour}h${date.minute.toString().padLeft(2, '0')}',
|
||||
Icons.calendar_today
|
||||
),
|
||||
'Date',
|
||||
'${date.day.toString().padLeft(2, '0')}/${date.month.toString().padLeft(2, '0')}/${date.year} à ${date.hour}h${date.minute.toString().padLeft(2, '0')}',
|
||||
Icons.calendar_today),
|
||||
_buildDetailRow(
|
||||
'Montant',
|
||||
'${passage['amount']?.toStringAsFixed(2) ?? '0.00'} €',
|
||||
Icons.euro
|
||||
),
|
||||
'Montant',
|
||||
'${passage['amount']?.toStringAsFixed(2) ?? '0.00'} €',
|
||||
Icons.euro),
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.payment, size: 16, color: theme.colorScheme.onSurfaceVariant),
|
||||
Icon(Icons.payment,
|
||||
size: 16,
|
||||
color: theme.colorScheme.onSurfaceVariant),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
@@ -306,16 +319,20 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Color(paymentInfo?['couleur'] ?? Colors.grey.value).withOpacity(0.1),
|
||||
color: Color(paymentInfo?['couleur'] ??
|
||||
Colors.grey.value)
|
||||
.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Text(
|
||||
paymentInfo?['titre'] ?? 'Inconnu',
|
||||
style: TextStyle(
|
||||
color: Color(paymentInfo?['couleur'] ?? Colors.grey.value),
|
||||
fontSize: 12,
|
||||
color: Color(paymentInfo?['couleur'] ??
|
||||
Colors.grey.value),
|
||||
fontSize: AppTheme.r(context, 12),
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
@@ -325,9 +342,11 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
|
||||
// Section Notes (si présentes)
|
||||
if (passage.containsKey('notes') && passage['notes'] != null && (passage['notes'] as String).isNotEmpty) ...[
|
||||
if (passage.containsKey('notes') &&
|
||||
passage['notes'] != null &&
|
||||
(passage['notes'] as String).isNotEmpty) ...[
|
||||
const SizedBox(height: 20),
|
||||
_buildSectionHeader(Icons.note, 'Notes', theme),
|
||||
const SizedBox(height: 12),
|
||||
@@ -335,24 +354,25 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.amber.withOpacity(0.05),
|
||||
color: Colors.amber.withValues(alpha: 0.05),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color: Colors.amber.withOpacity(0.2),
|
||||
color: Colors.amber.withValues(alpha: 0.2),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(Icons.comment, size: 16, color: Colors.amber[700]),
|
||||
Icon(Icons.comment,
|
||||
size: 16, color: Colors.amber[700]),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
passage['notes'] as String,
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.onSurface,
|
||||
fontSize: 14,
|
||||
fontSize: AppTheme.r(context, 14),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -380,7 +400,8 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(dialogContext).pop(); // Fermer le dialog de détails
|
||||
Navigator.of(dialogContext)
|
||||
.pop(); // Fermer le dialog de détails
|
||||
_showEditDialog(context, passageModel); // Ouvrir le formulaire
|
||||
},
|
||||
),
|
||||
@@ -389,7 +410,7 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Helper pour construire un header de section
|
||||
Widget _buildSectionHeader(IconData icon, String title, ThemeData theme) {
|
||||
return Row(
|
||||
@@ -406,7 +427,7 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Helper pour construire une ligne de détail avec icône
|
||||
Widget _buildDetailRow(String label, String value, [IconData? icon]) {
|
||||
final theme = Theme.of(context);
|
||||
@@ -462,7 +483,7 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Gérer l'ajout d'un nouveau passage
|
||||
void _handleAddPassage() {
|
||||
// Si un callback personnalisé est fourni, l'utiliser
|
||||
@@ -470,7 +491,7 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
widget.onAddPassage!();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Sinon, ouvrir directement le dialog de création
|
||||
showDialog(
|
||||
context: context,
|
||||
@@ -494,7 +515,7 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
void _showDeleteConfirmationDialog(Map<String, dynamic> passage) {
|
||||
final TextEditingController confirmController = TextEditingController();
|
||||
String? streetNumber;
|
||||
|
||||
|
||||
// Extraire le numéro de rue de l'adresse
|
||||
try {
|
||||
final address = passage['address'] as String? ?? '';
|
||||
@@ -506,7 +527,7 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
} catch (e) {
|
||||
debugPrint('Erreur extraction numéro de rue: $e');
|
||||
}
|
||||
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
@@ -524,12 +545,12 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
Text(
|
||||
'ATTENTION : Cette action est irréversible !',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.red,
|
||||
fontSize: 16,
|
||||
fontSize: AppTheme.r(context, 16),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
@@ -547,9 +568,9 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
),
|
||||
child: Text(
|
||||
passage['address'] as String? ?? 'Adresse inconnue',
|
||||
style: const TextStyle(
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 14,
|
||||
fontSize: AppTheme.r(context, 14),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -563,7 +584,9 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
controller: confirmController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Numéro de rue',
|
||||
hintText: streetNumber != null ? 'Ex: $streetNumber' : 'Saisir le numéro',
|
||||
hintText: streetNumber != null
|
||||
? 'Ex: $streetNumber'
|
||||
: 'Saisir le numéro',
|
||||
border: const OutlineInputBorder(),
|
||||
prefixIcon: const Icon(Icons.home),
|
||||
),
|
||||
@@ -594,8 +617,9 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (streetNumber != null && enteredNumber.toUpperCase() != streetNumber.toUpperCase()) {
|
||||
|
||||
if (streetNumber != null &&
|
||||
enteredNumber.toUpperCase() != streetNumber.toUpperCase()) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Le numéro de rue ne correspond pas'),
|
||||
@@ -604,11 +628,11 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Fermer le dialog
|
||||
confirmController.dispose();
|
||||
Navigator.of(dialogContext).pop();
|
||||
|
||||
|
||||
// Effectuer la suppression
|
||||
await _deletePassage(passage);
|
||||
},
|
||||
@@ -623,7 +647,7 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Supprimer un passage
|
||||
Future<void> _deletePassage(Map<String, dynamic> passage) async {
|
||||
try {
|
||||
@@ -632,25 +656,27 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
if (passageId == null) {
|
||||
throw Exception('ID du passage non trouvé');
|
||||
}
|
||||
|
||||
|
||||
// Convertir l'ID en int si nécessaire
|
||||
final int id = passageId is String ? int.parse(passageId) : passageId as int;
|
||||
|
||||
final int id =
|
||||
passageId is String ? int.parse(passageId) : passageId as int;
|
||||
|
||||
// Appeler le repository pour supprimer via l'API
|
||||
final success = await passageRepository.deletePassageViaApi(id);
|
||||
|
||||
|
||||
if (success && mounted) {
|
||||
ApiException.showSuccess(context, 'Passage supprimé avec succès');
|
||||
|
||||
|
||||
// Appeler le callback si défini
|
||||
if (widget.onPassageDelete != null) {
|
||||
widget.onPassageDelete!(passage);
|
||||
}
|
||||
|
||||
|
||||
// Forcer le rafraîchissement de la liste
|
||||
setState(() {});
|
||||
} else if (mounted) {
|
||||
ApiException.showError(context, Exception('Erreur lors de la suppression'));
|
||||
ApiException.showError(
|
||||
context, Exception('Erreur lors de la suppression'));
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Erreur suppression passage: $e');
|
||||
@@ -689,7 +715,8 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
}
|
||||
|
||||
// Limiter le nombre de passages si maxPassages est défini
|
||||
if (widget.maxPassages != null && filtered.length > widget.maxPassages!) {
|
||||
if (widget.maxPassages != null &&
|
||||
filtered.length > widget.maxPassages!) {
|
||||
filtered = filtered.sublist(0, widget.maxPassages!);
|
||||
}
|
||||
|
||||
@@ -801,7 +828,8 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
if (a.containsKey('distance') && b.containsKey('distance')) {
|
||||
final double distanceA = a['distance'] as double;
|
||||
final double distanceB = b['distance'] as double;
|
||||
return distanceA.compareTo(distanceB); // Ordre croissant pour la distance
|
||||
return distanceA
|
||||
.compareTo(distanceB); // Ordre croissant pour la distance
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
@@ -845,7 +873,7 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
if (passage.containsKey('type') && passage['type'] == 2) {
|
||||
return true; // Tous les membres peuvent agir sur les passages type 2
|
||||
}
|
||||
|
||||
|
||||
// Utiliser directement le champ isOwnedByCurrentUser s'il existe
|
||||
if (passage.containsKey('isOwnedByCurrentUser')) {
|
||||
return passage['isOwnedByCurrentUser'] == true;
|
||||
@@ -869,8 +897,6 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
final double amount =
|
||||
passage.containsKey('amount') ? passage['amount'] as double : 0.0;
|
||||
final bool hasValidAmount = amount > 0;
|
||||
final bool isTypeEffectue = passage.containsKey('type') &&
|
||||
passage['type'] == 1; // Type 1 = Effectué
|
||||
final bool isOwnedByCurrentUser = _isPassageOwnedByCurrentUser(passage);
|
||||
|
||||
// Déterminer si nous sommes dans une page admin (pas de filterByUserId)
|
||||
@@ -878,13 +904,6 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
|
||||
// Dans les pages admin, tous les passages sont affichés normalement
|
||||
// Dans les pages user, seuls les passages de l'utilisateur courant sont affichés normalement
|
||||
final bool shouldGreyOut = !isAdminPage && !isOwnedByCurrentUser;
|
||||
|
||||
// Définir des styles différents en fonction du propriétaire du passage et du type de page
|
||||
final TextStyle? baseTextStyle = shouldGreyOut
|
||||
? theme.textTheme.bodyMedium
|
||||
?.copyWith(color: theme.colorScheme.onSurface.withOpacity(0.5))
|
||||
: theme.textTheme.bodyMedium;
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
@@ -899,16 +918,18 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
Icon(
|
||||
Icons.calendar_today,
|
||||
size: 15,
|
||||
color: theme.colorScheme.onSurface.withOpacity(0.7),
|
||||
color: theme.colorScheme.onSurface.withValues(alpha: 0.7),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
passage.containsKey('date')
|
||||
? dateFormat.format(passage['date'] as DateTime)
|
||||
: 'Date non disponible',
|
||||
style: theme.textTheme.bodyMedium?.copyWith( // Changé de bodySmall à bodyMedium
|
||||
color: theme.colorScheme.onSurface.withOpacity(0.75),
|
||||
fontSize: 14, // Taille explicite
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
// Changé de bodySmall à bodyMedium
|
||||
color:
|
||||
theme.colorScheme.onSurface.withValues(alpha: 0.75),
|
||||
fontSize: AppTheme.r(context, 14), // Taille explicite
|
||||
fontWeight: FontWeight.w500, // Un peu plus gras
|
||||
),
|
||||
),
|
||||
@@ -925,15 +946,19 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
Icon(
|
||||
Icons.person,
|
||||
size: 16,
|
||||
color: theme.colorScheme.onSurface.withOpacity(0.7),
|
||||
color:
|
||||
theme.colorScheme.onSurface.withValues(alpha: 0.7),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Flexible(
|
||||
child: Text(
|
||||
passage['name'] as String,
|
||||
style: theme.textTheme.bodyMedium?.copyWith( // Changé pour être plus visible
|
||||
color: theme.colorScheme.onSurface.withOpacity(0.8),
|
||||
fontSize: 14, // Taille explicite
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
// Changé pour être plus visible
|
||||
color: theme.colorScheme.onSurface
|
||||
.withValues(alpha: 0.8),
|
||||
fontSize:
|
||||
AppTheme.r(context, 14), // Taille explicite
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
@@ -947,13 +972,15 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
Icon(
|
||||
Icons.euro,
|
||||
size: 16,
|
||||
color: theme.colorScheme.onSurface.withOpacity(0.6),
|
||||
color:
|
||||
theme.colorScheme.onSurface.withValues(alpha: 0.6),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'${passage['amount']}€',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.onSurface.withOpacity(0.6),
|
||||
color: theme.colorScheme.onSurface
|
||||
.withValues(alpha: 0.6),
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
@@ -964,14 +991,14 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
horizontal: 6, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: Color(typeReglement['couleur'] as int)
|
||||
.withOpacity(0.1),
|
||||
.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Text(
|
||||
typeReglement['titre'] as String,
|
||||
style: TextStyle(
|
||||
color: Color(typeReglement['couleur'] as int),
|
||||
fontSize: 12,
|
||||
fontSize: AppTheme.r(context, 12),
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
@@ -994,12 +1021,11 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
}
|
||||
|
||||
// Construction d'une carte pour un passage (mode compact uniquement)
|
||||
Widget _buildPassageCard(
|
||||
Map<String, dynamic> passage, ThemeData theme) {
|
||||
Widget _buildPassageCard(Map<String, dynamic> passage, ThemeData theme) {
|
||||
try {
|
||||
// Vérification des données et valeurs par défaut
|
||||
final int type = passage.containsKey('type') ? passage['type'] as int : 1;
|
||||
|
||||
|
||||
// S'assurer que le type existe dans la map, sinon utiliser type 1 par défaut
|
||||
final Map<String, dynamic> typePassage =
|
||||
AppKeys.typesPassages[type] ?? AppKeys.typesPassages[1]!;
|
||||
@@ -1025,18 +1051,16 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
// Toujours fond blanc, avec opacité réduite si grisé
|
||||
color: shouldGreyOut
|
||||
? Colors.white.withOpacity(0.7)
|
||||
: Colors.white,
|
||||
color:
|
||||
shouldGreyOut ? Colors.white.withValues(alpha: 0.7) : Colors.white,
|
||||
child: InkWell(
|
||||
// Rendre le passage cliquable uniquement s'il appartient à l'utilisateur courant
|
||||
// ou si nous sommes dans la page admin
|
||||
onTap: isClickable
|
||||
? () => _handlePassageClick(passage)
|
||||
: null,
|
||||
onTap: isClickable ? () => _handlePassageClick(passage) : null,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 10.0), // Réduit de 16 à 12/10
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12.0, vertical: 10.0), // Réduit de 16 à 12/10
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@@ -1049,7 +1073,7 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: Color(typePassage['couleur1'] as int)
|
||||
.withOpacity(0.1),
|
||||
.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color: Color(typePassage['couleur2'] as int),
|
||||
@@ -1059,7 +1083,8 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
child: Icon(
|
||||
typePassage['icon_data'] as IconData,
|
||||
color: Color(typePassage['couleur1'] as int),
|
||||
size: 20, // Légèrement réduit pour tenir compte de la bordure
|
||||
size:
|
||||
20, // Légèrement réduit pour tenir compte de la bordure
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
@@ -1082,10 +1107,11 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
// Badge du type de passage
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 6, vertical: 3), // Réduit de 8/4 à 6/3
|
||||
horizontal: 6,
|
||||
vertical: 3), // Réduit de 8/4 à 6/3
|
||||
decoration: BoxDecoration(
|
||||
color: Color(typePassage['couleur1'] as int)
|
||||
.withOpacity(0.1),
|
||||
.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
@@ -1094,29 +1120,35 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
color:
|
||||
Color(typePassage['couleur1'] as int),
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 11, // Réduit de 12 à 11
|
||||
fontSize: AppTheme.r(context, 11),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Boutons d'action
|
||||
if (widget.showActions) ...[
|
||||
// Bouton PDF pour les passages effectués
|
||||
if (type == 1 && widget.onReceiptView != null && isOwnedByCurrentUser)
|
||||
if (type == 1 &&
|
||||
widget.onReceiptView != null &&
|
||||
isOwnedByCurrentUser)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.picture_as_pdf, size: 20),
|
||||
icon: const Icon(Icons.picture_as_pdf,
|
||||
size: 20),
|
||||
color: Colors.green,
|
||||
padding: const EdgeInsets.all(4),
|
||||
constraints: const BoxConstraints(),
|
||||
onPressed: () => widget.onReceiptView!(passage),
|
||||
onPressed: () =>
|
||||
widget.onReceiptView!(passage),
|
||||
),
|
||||
// Bouton suppression si autorisé
|
||||
if (_canDeletePassages() && isOwnedByCurrentUser)
|
||||
if (_canDeletePassages() &&
|
||||
isOwnedByCurrentUser)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete, size: 20),
|
||||
color: Colors.red,
|
||||
padding: const EdgeInsets.all(4),
|
||||
constraints: const BoxConstraints(),
|
||||
onPressed: () => _showDeleteConfirmationDialog(passage),
|
||||
onPressed: () =>
|
||||
_showDeleteConfirmationDialog(passage),
|
||||
),
|
||||
],
|
||||
],
|
||||
@@ -1134,11 +1166,12 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
_formatDistance(passage['distance'] as double),
|
||||
_formatDistance(
|
||||
passage['distance'] as double),
|
||||
style: TextStyle(
|
||||
color: Colors.green[700],
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 13,
|
||||
fontSize: AppTheme.r(context, 13),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -1163,7 +1196,8 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
'Notes: ${passage['notes']}',
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
fontStyle: FontStyle.italic,
|
||||
color: theme.colorScheme.onSurface.withOpacity(0.7),
|
||||
color:
|
||||
theme.colorScheme.onSurface.withValues(alpha: 0.7),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -1334,152 +1368,160 @@ class _PassagesListWidgetState extends State<PassagesListWidget> {
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
// Fond transparent si c'est pour le dashboard (pas de filtres ni recherche)
|
||||
color: (!widget.showFilters && !widget.showSearch)
|
||||
? Colors.transparent
|
||||
: Colors.transparent,
|
||||
color: (!widget.showFilters && !widget.showSearch)
|
||||
? Colors.transparent
|
||||
: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: (!widget.showFilters && !widget.showSearch)
|
||||
? Border.all(color: Colors.transparent) // Pas de bordure pour le dashboard
|
||||
: Border.all(
|
||||
color: theme.colorScheme.outline.withOpacity(0.2),
|
||||
width: 1,
|
||||
),
|
||||
boxShadow: (!widget.showFilters && !widget.showSearch)
|
||||
? [] // Pas d'ombre pour le dashboard
|
||||
: [
|
||||
BoxShadow(
|
||||
color: theme.shadowColor.withOpacity(0.1),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
? Border.all(
|
||||
color: Colors
|
||||
.transparent) // Pas de bordure pour le dashboard
|
||||
: Border.all(
|
||||
color: theme.colorScheme.outline.withValues(alpha: 0.2),
|
||||
width: 1,
|
||||
),
|
||||
],
|
||||
boxShadow: (!widget.showFilters && !widget.showSearch)
|
||||
? [] // Pas d'ombre pour le dashboard
|
||||
: [
|
||||
BoxShadow(
|
||||
color: theme.shadowColor.withValues(alpha: 0.1),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
// Header avec le nombre de passages trouvés
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
decoration: BoxDecoration(
|
||||
// Header toujours avec fond coloré
|
||||
color: Color.alphaBlend(
|
||||
theme.colorScheme.primary.withOpacity(0.1),
|
||||
theme.colorScheme.surface,
|
||||
children: [
|
||||
// Header avec le nombre de passages trouvés
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
decoration: BoxDecoration(
|
||||
// Header toujours avec fond coloré
|
||||
color: Color.alphaBlend(
|
||||
theme.colorScheme.primary.withValues(alpha: 0.1),
|
||||
theme.colorScheme.surface,
|
||||
),
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(12),
|
||||
topRight: Radius.circular(12),
|
||||
),
|
||||
),
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(12),
|
||||
topRight: Radius.circular(12),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.list_alt,
|
||||
size: 20,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
widget.maxPassages != null && widget.maxPassages! <= 20 && !widget.showFilters && !widget.showSearch
|
||||
? '${_filteredPassages.length} dernier${_filteredPassages.length > 1 ? 's' : ''} passage${_filteredPassages.length > 1 ? 's' : ''} trouvé${_filteredPassages.length > 1 ? 's' : ''}'
|
||||
: '${_filteredPassages.length} passage${_filteredPassages.length > 1 ? 's' : ''} trouvé${_filteredPassages.length > 1 ? 's' : ''}',
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.list_alt,
|
||||
size: 20,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (widget.sortingButtons != null) ...[
|
||||
widget.sortingButtons!,
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
if (widget.showAddButton)
|
||||
Container(
|
||||
height: 36,
|
||||
width: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green,
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.green.withOpacity(0.3),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
Text(
|
||||
widget.maxPassages != null &&
|
||||
widget.maxPassages! <= 20 &&
|
||||
!widget.showFilters &&
|
||||
!widget.showSearch
|
||||
? '${_filteredPassages.length} dernier${_filteredPassages.length > 1 ? 's' : ''} passage${_filteredPassages.length > 1 ? 's' : ''}'
|
||||
: '${_filteredPassages.length} passage${_filteredPassages.length > 1 ? 's' : ''}',
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (widget.sortingButtons != null) ...[
|
||||
widget.sortingButtons!,
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
if (widget.showAddButton)
|
||||
Container(
|
||||
height: 36,
|
||||
width: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green,
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
onTap: _handleAddPassage,
|
||||
child: const Tooltip(
|
||||
message: 'Nouveau passage',
|
||||
child: Icon(
|
||||
Icons.add,
|
||||
color: Colors.white,
|
||||
size: 24,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.green.withValues(alpha: 0.3),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
onTap: _handleAddPassage,
|
||||
child: const Tooltip(
|
||||
message: 'Nouveau passage',
|
||||
child: Icon(
|
||||
Icons.add,
|
||||
color: Colors.white,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Contenu de la liste
|
||||
Expanded(
|
||||
child: _filteredPassages.isEmpty
|
||||
? Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(32.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.search_off,
|
||||
size: 64,
|
||||
color: theme.colorScheme.onSurface.withOpacity(0.3),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Aucun passage trouvé',
|
||||
style: theme.textTheme.titleLarge?.copyWith(
|
||||
color: theme.colorScheme.onSurface.withOpacity(0.5),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Essayez de modifier vos filtres de recherche',
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: theme.colorScheme.onSurface.withOpacity(0.5),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: ListView.builder(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
itemCount: _filteredPassages.length,
|
||||
itemBuilder: (context, index) {
|
||||
final passage = _filteredPassages[index];
|
||||
return _buildPassageCard(passage, theme);
|
||||
},
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Contenu de la liste
|
||||
Expanded(
|
||||
child: _filteredPassages.isEmpty
|
||||
? Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(32.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.search_off,
|
||||
size: 64,
|
||||
color: theme.colorScheme.onSurface
|
||||
.withValues(alpha: 0.3),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Aucun passage trouvé',
|
||||
style: theme.textTheme.titleLarge?.copyWith(
|
||||
color: theme.colorScheme.onSurface
|
||||
.withValues(alpha: 0.5),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Essayez de modifier vos filtres de recherche',
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: theme.colorScheme.onSurface
|
||||
.withValues(alpha: 0.5),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: ListView.builder(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
itemCount: _filteredPassages.length,
|
||||
itemBuilder: (context, index) {
|
||||
final passage = _filteredPassages[index];
|
||||
return _buildPassageCard(passage, theme);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user