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:
@@ -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!);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user