Files
geo/app/lib/presentation/widgets/passages/passage_form.dart
Pierre f7baa7492c 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>
2025-09-02 20:35:40 +02:00

420 lines
13 KiB
Dart
Executable File

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 {
final Function(Map<String, dynamic>)? onSubmit;
final Map<String, dynamic>? initialData;
const PassageForm({
super.key,
this.onSubmit,
this.initialData,
});
@override
State<PassageForm> createState() => _PassageFormState();
}
class _PassageFormState extends State<PassageForm> {
final _formKey = GlobalKey<FormState>();
// Controllers
late final TextEditingController _villeController;
late final TextEditingController _adresseController;
late final TextEditingController _nomHabitantController;
late final TextEditingController _emailController;
late final TextEditingController _montantController;
late final TextEditingController _commentairesController;
// Form values
String _typeHabitat = 'Individuel';
String _typeReglement = 'Espèces';
@override
void initState() {
super.initState();
// Initialize controllers with initial data if available
final data = widget.initialData ?? {};
_villeController = TextEditingController(text: data['ville'] ?? '');
_adresseController = TextEditingController(text: data['adresse'] ?? '');
_nomHabitantController =
TextEditingController(text: data['nomHabitant'] ?? '');
_emailController = TextEditingController(text: data['email'] ?? '');
_montantController = TextEditingController(text: data['montant'] ?? '');
_commentairesController =
TextEditingController(text: data['commentaires'] ?? '');
_typeHabitat = data['typeHabitat'] ?? 'Individuel';
_typeReglement = data['typeReglement'] ?? 'Espèces';
}
@override
void dispose() {
_villeController.dispose();
_adresseController.dispose();
_nomHabitantController.dispose();
_emailController.dispose();
_montantController.dispose();
_commentairesController.dispose();
super.dispose();
}
void _submitForm() {
if (_formKey.currentState!.validate()) {
final formData = {
'ville': _villeController.text,
'adresse': _adresseController.text,
'typeHabitat': _typeHabitat,
'nomHabitant': _nomHabitantController.text,
'email': _emailController.text,
'montant': _montantController.text,
'typeReglement': _typeReglement,
'commentaires': _commentairesController.text,
};
if (widget.onSubmit != null) {
widget.onSubmit!(formData);
}
}
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Ville
CustomTextField(
controller: _villeController,
label: 'Ville',
validator: (value) {
if (value == null || value.isEmpty) {
return 'Veuillez entrer une ville';
}
return null;
},
),
const SizedBox(height: 16),
// Adresse
CustomTextField(
controller: _adresseController,
label: 'Adresse',
validator: (value) {
if (value == null || value.isEmpty) {
return 'Veuillez entrer une adresse';
}
return null;
},
),
const SizedBox(height: 16),
// Type d'habitat
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Type d'habitat",
style: theme.textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.w500,
color: theme.colorScheme.onSurface,
),
),
const SizedBox(height: 8),
Row(
children: [
_buildRadioOption(
value: 'Individuel',
groupValue: _typeHabitat,
onChanged: (value) {
setState(() {
_typeHabitat = value!;
});
},
),
const SizedBox(width: 40),
_buildRadioOption(
value: 'Collectif',
groupValue: _typeHabitat,
onChanged: (value) {
setState(() {
_typeHabitat = value!;
});
},
),
],
),
],
),
const SizedBox(height: 16),
// Nom de l'habitant
CustomTextField(
controller: _nomHabitantController,
label: "Nom de l'habitant",
validator: (value) {
if (value == null || value.isEmpty) {
return "Veuillez entrer le nom de l'habitant";
}
return null;
},
),
const SizedBox(height: 16),
// Email de l'habitant
CustomTextField(
controller: _emailController,
label: "Adresse email de l'habitant",
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value == null || value.isEmpty) {
return null; // Email optionnel
}
// Simple email validation
if (!value.contains('@') || !value.contains('.')) {
return "Veuillez entrer une adresse email valide";
}
return null;
},
),
const SizedBox(height: 16),
// Montant et Type de règlement
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Montant reçu
Expanded(
flex: 1,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Montant reçu",
style: theme.textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.w500,
color: theme.colorScheme.onSurface,
),
),
const SizedBox(height: 8),
TextFormField(
controller: _montantController,
keyboardType:
const TextInputType.numberWithOptions(decimal: true),
inputFormatters: [
FilteringTextInputFormatter.allow(
RegExp(r'^\d+\.?\d{0,2}')),
],
style: theme.textTheme.bodyLarge?.copyWith(
color: theme.colorScheme.onSurface,
),
decoration: InputDecoration(
hintText: '0.00 €',
hintStyle: theme.textTheme.bodyLarge?.copyWith(
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.withValues(alpha: 0.1),
width: 1,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: theme.colorScheme.onSurface.withValues(alpha: 0.1),
width: 1,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: theme.colorScheme.primary,
width: 2,
),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 16,
),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Requis';
}
return null;
},
),
],
),
),
const SizedBox(width: 20),
// Type de règlement
Expanded(
flex: 2,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Type de règlement",
style: theme.textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.w500,
color: theme.colorScheme.onSurface,
),
),
const SizedBox(height: 8),
_buildDropdown(),
],
),
),
],
),
const SizedBox(height: 16),
// Commentaires
CustomTextField(
controller: _commentairesController,
label: "Commentaires",
hintText: "Placeholder",
maxLines: 3,
),
const SizedBox(height: 25),
// Titre de section
Text(
"Mise à jour du passage effectué",
style: theme.textTheme.bodyLarge?.copyWith(
color: const Color(0xFF20335E),
),
),
const SizedBox(height: 16),
// Bouton Enregistrer
Center(
child: ElevatedButton(
onPressed: _submitForm,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF20335E),
foregroundColor: Colors.white,
padding:
const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50),
),
minimumSize: const Size(200, 50),
),
child: Text(
'Enregistrer',
style: TextStyle(
fontSize: AppTheme.r(context, 18),
fontWeight: FontWeight.w500,
),
),
),
),
],
),
);
}
Widget _buildRadioOption({
required String value,
required String groupValue,
required Function(String?) onChanged,
}) {
final theme = Theme.of(context);
return Row(
children: [
Radio<String>(
value: value,
groupValue: groupValue,
onChanged: onChanged,
activeColor: const Color(0xFF20335E),
),
Text(
value,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurface,
fontWeight: FontWeight.w500,
),
),
],
);
}
Widget _buildDropdown() {
final theme = Theme.of(context);
return Container(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 5),
decoration: BoxDecoration(
color: const Color(0xFFF4F5F6).withValues(alpha: 0.85),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: const Color(0xFF20335E).withValues(alpha: 0.1),
width: 1,
),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
value: _typeReglement,
isExpanded: true,
icon: const Icon(
Icons.keyboard_arrow_down,
color: Color(0xFF20335E),
),
style: theme.textTheme.bodyMedium?.copyWith(
color: const Color(0xFF20335E),
),
dropdownColor: Colors.white,
items: <String>['Espèces', 'CB', 'Chèque']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Row(
children: [
_getPaymentIcon(value),
const SizedBox(width: 8),
Text(value),
],
),
);
}).toList(),
onChanged: (String? newValue) {
setState(() {
_typeReglement = newValue!;
});
},
),
),
);
}
Widget _getPaymentIcon(String type) {
switch (type) {
case 'Espèces':
return const Icon(Icons.payments_outlined,
color: Color(0xFF20335E), size: 20);
case 'CB':
return const Icon(Icons.credit_card,
color: Color(0xFF20335E), size: 20);
case 'Chèque':
return const Icon(Icons.account_balance_wallet_outlined,
color: Color(0xFF20335E), size: 20);
default:
return const SizedBox.shrink();
}
}
}