feat: Version 3.5.2 - Configuration Stripe et gestion des immeubles

- Configuration complète Stripe pour les 3 environnements (DEV/REC/PROD)
  * DEV: Clés TEST Pierre (mode test)
  * REC: Clés TEST Client (mode test)
  * PROD: Clés LIVE Client (mode live)
- Ajout de la gestion des bases de données immeubles/bâtiments
  * Configuration buildings_database pour DEV/REC/PROD
  * Service BuildingService pour enrichissement des adresses
- Optimisations pages et améliorations ergonomie
- Mises à jour des dépendances Composer
- Nettoyage des fichiers obsolètes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
pierre
2025-11-09 18:26:27 +01:00
parent 21657a3820
commit 2f5946a184
812 changed files with 142105 additions and 25992 deletions

View File

@@ -119,9 +119,9 @@ class SectorActionResultDialog extends StatelessWidget {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.orange.withValues(alpha: 0.1),
color: Colors.orange.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.orange.withValues(alpha: 0.3)),
border: Border.all(color: Colors.orange.withOpacity(0.3)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,

View File

@@ -9,7 +9,7 @@ import 'package:geosector_app/core/constants/app_keys.dart';
class SectorDialog extends StatefulWidget {
final SectorModel? existingSector;
final List<List<double>> coordinates;
final Future<void> Function(String name, String color, List<int> memberIds) onSave;
final Future<void> Function(String name, String color, List<int> memberIds, bool updatePassages) onSave;
const SectorDialog({
super.key,
@@ -31,6 +31,8 @@ class _SectorDialogState extends State<SectorDialog> {
final List<int> _selectedMemberIds = [];
bool _isLoading = false;
String _searchQuery = '';
bool _updatePassages = true; // Par défaut activé
bool _initialUpdatePassages = true; // Valeur initiale pour détecter les changements
@override
void initState() {
@@ -68,24 +70,24 @@ class _SectorDialogState extends State<SectorDialog> {
for (var i = 0; i < userSectorBox.length; i++) {
final us = userSectorBox.getAt(i);
if (us != null) {
debugPrint(' - UserSector[$i]: membreId=${us.id}, fkSector=${us.fkSector}, name="${us.firstName} ${us.name}"');
debugPrint(' - UserSector[$i]: userId=${us.userId}, opeUserId=${us.opeUserId}, fkSector=${us.fkSector}, name="${us.firstName} ${us.name}"');
}
}
// Récupérer tous les UserSectorModel pour ce secteur
final userSectors = userSectorBox.values
.where((us) => us.fkSector == widget.existingSector!.id)
.toList();
debugPrint('Trouvé ${userSectors.length} UserSectorModel pour le secteur ${widget.existingSector!.id}');
// Pré-sélectionner les IDs des membres affectés
// Pré-sélectionner les IDs des membres affectés (ope_users.id)
setState(() {
_selectedMemberIds.clear();
for (final userSector in userSectors) {
// userSector.id est l'ID du membre (pas de l'utilisateur)
_selectedMemberIds.add(userSector.id);
debugPrint('Membre présélectionné: ${userSector.firstName} ${userSector.name} (membreId: ${userSector.id}, fkSector: ${userSector.fkSector})');
// Utiliser opeUserId (ope_users.id) pour la sélection
_selectedMemberIds.add(userSector.opeUserId);
debugPrint('Membre présélectionné: ${userSector.firstName} ${userSector.name} (userId: ${userSector.userId}, opeUserId: ${userSector.opeUserId}, fkSector: ${userSector.fkSector})');
}
});
@@ -118,7 +120,51 @@ class _SectorDialogState extends State<SectorDialog> {
}
String _colorToHex(Color color) {
return '#${color.toARGB32().toRadixString(16).substring(2).toUpperCase()}';
return '#${color.value.toRadixString(16).substring(2).toUpperCase()}';
}
// Dialogue de confirmation pour le changement du switch
Future<bool> _showUpdatePassagesConfirmation(bool newValue) async {
final result = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Row(
children: [
Icon(
Icons.warning_amber_rounded,
color: Colors.orange,
size: 28,
),
const SizedBox(width: 8),
const Text('Confirmation'),
],
),
content: Text(
newValue
? 'Êtes-vous sûr de vouloir recalculer les passages ?\n\n'
'Tous les passages du secteur seront réaffectés selon les nouvelles limites.'
: 'Êtes-vous sûr de vouloir conserver les passages existants ?\n\n'
'Les passages actuels ne seront pas modifiés même si les limites du secteur changent.',
style: const TextStyle(fontSize: 15),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('Annuler'),
),
ElevatedButton(
onPressed: () => Navigator.of(context).pop(true),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
),
child: const Text('Confirmer'),
),
],
),
);
return result ?? false;
}
void _handleSave() async {
@@ -144,6 +190,7 @@ class _SectorDialogState extends State<SectorDialog> {
_nameController.text.trim(),
_colorToHex(_selectedColor),
_selectedMemberIds,
_updatePassages, // Passer le paramètre updatePassages
);
// Si tout s'est bien passé, fermer le dialog
@@ -197,8 +244,8 @@ class _SectorDialogState extends State<SectorDialog> {
itemCount: colors.length,
itemBuilder: (context, index) {
final color = colors[index];
final isSelected = _selectedColor.toARGB32() == color.toARGB32();
final isSelected = _selectedColor.value == color.value;
return InkWell(
onTap: () {
setState(() {
@@ -219,7 +266,7 @@ class _SectorDialogState extends State<SectorDialog> {
boxShadow: isSelected
? [
BoxShadow(
color: Colors.black.withValues(alpha: 0.3),
color: Colors.black.withOpacity(0.3),
blurRadius: 4,
offset: const Offset(0, 2),
),
@@ -441,6 +488,73 @@ class _SectorDialogState extends State<SectorDialog> {
),
const SizedBox(height: 20),
// Switch pour la mise à jour des passages (uniquement en mode édition)
if (widget.existingSector != null) ...[
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
decoration: BoxDecoration(
color: Colors.blue.shade50,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.blue.shade200),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Mise à jour des passages',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 15,
color: Colors.blue.shade900,
),
),
const SizedBox(height: 4),
Text(
_updatePassages
? 'Les passages seront recalculés et réaffectés'
: 'Les passages existants ne seront pas modifiés',
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade700,
fontStyle: FontStyle.italic,
),
),
],
),
),
Switch(
value: _updatePassages,
onChanged: (value) async {
// Afficher confirmation uniquement si la valeur change par rapport à l'initiale
if (value != _initialUpdatePassages) {
final confirmed = await _showUpdatePassagesConfirmation(value);
if (confirmed) {
setState(() {
_updatePassages = value;
});
}
} else {
setState(() {
_updatePassages = value;
});
}
},
activeColor: Colors.blue,
),
],
),
],
),
),
const SizedBox(height: 20),
],
// Sélection des membres
Row(
children: [
@@ -510,33 +624,50 @@ class _SectorDialogState extends State<SectorDialog> {
valueListenable: Hive.box<MembreModel>(AppKeys.membresBoxName).listenable(),
builder: (context, box, _) {
debugPrint('=== Build liste membres - IDs présélectionnés: $_selectedMemberIds ===');
// Filtrer les membres de l'amicale
// Récupérer tous les membres (déjà uniques dans la box)
// Filtrer uniquement ceux qui ont un opeUserId (dans l'opération courante)
var membres = box.values
.where((m) => m.fkEntite == currentAmicale.id)
.whereType<MembreModel>()
.where((membre) => membre.opeUserId != null)
.toList();
// Appliquer le filtre de recherche
if (_searchQuery.isNotEmpty) {
membres = membres.where((membre) {
final firstName = membre.firstName?.toLowerCase() ?? '';
final lastName = membre.name?.toLowerCase() ?? '';
final sectName = membre.sectName?.toLowerCase() ?? '';
return firstName.contains(_searchQuery) ||
lastName.contains(_searchQuery) ||
sectName.contains(_searchQuery);
}).toList();
}
// Trier : membres affectés en premier, puis les autres
membres.sort((a, b) {
final aSelected = _selectedMemberIds.contains(a.opeUserId);
final bSelected = _selectedMemberIds.contains(b.opeUserId);
// Si l'un est sélectionné et pas l'autre, le mettre en premier
if (aSelected && !bSelected) return -1;
if (!aSelected && bSelected) return 1;
// Sinon, trier alphabétiquement par prénom puis nom
final firstNameCompare = (a.firstName ?? '').compareTo(b.firstName ?? '');
if (firstNameCompare != 0) return firstNameCompare;
return (a.name ?? '').compareTo(b.name ?? '');
});
if (membres.isEmpty) {
return Center(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Text(
_searchQuery.isNotEmpty
_searchQuery.isNotEmpty
? 'Aucun membre trouvé pour "$_searchQuery"'
: 'Aucun membre disponible',
: 'Aucun membre disponible pour cette opération',
style: TextStyle(
color: Colors.grey[600],
fontSize: 14,
@@ -545,7 +676,7 @@ class _SectorDialogState extends State<SectorDialog> {
),
);
}
// Afficher le nombre de résultats
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -570,13 +701,13 @@ class _SectorDialogState extends State<SectorDialog> {
itemCount: membres.length,
itemBuilder: (context, index) {
final membre = membres[index];
final isSelected = _selectedMemberIds.contains(membre.id);
final isSelected = _selectedMemberIds.contains(membre.opeUserId);
// Log pour debug
if (index < 3) { // Limiter les logs aux 3 premiers membres
debugPrint('Membre ${index}: ${membre.firstName} ${membre.name} (ID: ${membre.id}) - isSelected: $isSelected');
debugPrint('Membre ${index}: ${membre.firstName} ${membre.name} (userId: ${membre.id}, opeUserId: ${membre.opeUserId}) - isSelected: $isSelected');
}
return CheckboxListTile(
dense: true,
contentPadding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 0.0),
@@ -592,9 +723,10 @@ class _SectorDialogState extends State<SectorDialog> {
onChanged: (bool? value) {
setState(() {
if (value == true) {
_selectedMemberIds.add(membre.id);
// opeUserId ne peut pas être null grâce au filtre ligne 517
_selectedMemberIds.add(membre.opeUserId!);
} else {
_selectedMemberIds.remove(membre.id);
_selectedMemberIds.remove(membre.opeUserId!);
}
});
},