You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
430 lines
14 KiB
430 lines
14 KiB
import 'package:flutter/material.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:youmazgestion/Models/client.dart';
|
|
|
|
import '../Services/stock_managementDatabase.dart';
|
|
|
|
class ClientFormController extends GetxController {
|
|
final _formKey = GlobalKey<FormState>();
|
|
|
|
// Controllers pour les champs
|
|
final _nomController = TextEditingController();
|
|
final _prenomController = TextEditingController();
|
|
final _emailController = TextEditingController();
|
|
final _telephoneController = TextEditingController();
|
|
final _adresseController = TextEditingController();
|
|
|
|
// Variables observables pour la recherche
|
|
var suggestedClients = <Client>[].obs;
|
|
var isSearching = false.obs;
|
|
var selectedClient = Rxn<Client>();
|
|
|
|
@override
|
|
void onClose() {
|
|
_nomController.dispose();
|
|
_prenomController.dispose();
|
|
_emailController.dispose();
|
|
_telephoneController.dispose();
|
|
_adresseController.dispose();
|
|
super.onClose();
|
|
}
|
|
|
|
// Méthode pour rechercher les clients existants
|
|
Future<void> searchClients(String query) async {
|
|
if (query.length < 2) {
|
|
suggestedClients.clear();
|
|
return;
|
|
}
|
|
|
|
isSearching.value = true;
|
|
try {
|
|
final clients = await AppDatabase.instance.suggestClients(query);
|
|
suggestedClients.value = clients;
|
|
} catch (e) {
|
|
print("Erreur recherche clients: $e");
|
|
suggestedClients.clear();
|
|
} finally {
|
|
isSearching.value = false;
|
|
}
|
|
}
|
|
|
|
// Méthode pour remplir automatiquement le formulaire
|
|
void fillFormWithClient(Client client) {
|
|
selectedClient.value = client;
|
|
_nomController.text = client.nom;
|
|
_prenomController.text = client.prenom;
|
|
_emailController.text = client.email;
|
|
_telephoneController.text = client.telephone;
|
|
_adresseController.text = client.adresse ?? '';
|
|
suggestedClients.clear();
|
|
}
|
|
|
|
// Méthode pour vider le formulaire
|
|
void clearForm() {
|
|
selectedClient.value = null;
|
|
_nomController.clear();
|
|
_prenomController.clear();
|
|
_emailController.clear();
|
|
_telephoneController.clear();
|
|
_adresseController.clear();
|
|
suggestedClients.clear();
|
|
}
|
|
|
|
// Méthode pour valider et soumettre
|
|
Future<void> submitForm() async {
|
|
if (!_formKey.currentState!.validate()) return;
|
|
|
|
try {
|
|
Client clientToUse;
|
|
|
|
if (selectedClient.value != null) {
|
|
// Utiliser le client existant
|
|
clientToUse = selectedClient.value!;
|
|
} else {
|
|
// Créer un nouveau client
|
|
final newClient = Client(
|
|
nom: _nomController.text.trim(),
|
|
prenom: _prenomController.text.trim(),
|
|
email: _emailController.text.trim(),
|
|
telephone: _telephoneController.text.trim(),
|
|
adresse: _adresseController.text.trim().isEmpty
|
|
? null
|
|
: _adresseController.text.trim(),
|
|
dateCreation: DateTime.now(),
|
|
);
|
|
|
|
clientToUse = await AppDatabase.instance.createOrGetClient(newClient);
|
|
}
|
|
|
|
// Procéder avec la commande
|
|
Get.back();
|
|
_submitOrderWithClient(clientToUse);
|
|
} catch (e) {
|
|
Get.snackbar(
|
|
'Erreur',
|
|
'Erreur lors de la création/récupération du client: $e',
|
|
backgroundColor: Colors.red.shade100,
|
|
colorText: Colors.red.shade800,
|
|
);
|
|
}
|
|
}
|
|
|
|
void _submitOrderWithClient(Client client) {
|
|
// Votre logique existante pour soumettre la commande
|
|
// avec le client fourni
|
|
}
|
|
}
|
|
|
|
// Widget pour le formulaire avec auto-completion
|
|
// ignore: unused_element
|
|
void _showClientFormDialog() {
|
|
final controller = Get.put(ClientFormController());
|
|
|
|
Get.dialog(
|
|
AlertDialog(
|
|
title: Row(
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.all(8),
|
|
decoration: BoxDecoration(
|
|
color: Colors.blue.shade100,
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Icon(Icons.person_add, color: Colors.blue.shade700),
|
|
),
|
|
const SizedBox(width: 12),
|
|
const Text('Informations Client'),
|
|
const Spacer(),
|
|
// Bouton pour vider le formulaire
|
|
IconButton(
|
|
onPressed: controller.clearForm,
|
|
icon: const Icon(Icons.clear),
|
|
tooltip: 'Vider le formulaire',
|
|
),
|
|
],
|
|
),
|
|
content: Container(
|
|
width: 600,
|
|
constraints: const BoxConstraints(maxHeight: 700),
|
|
child: SingleChildScrollView(
|
|
child: Form(
|
|
key: controller._formKey,
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Section de recherche rapide
|
|
_buildSearchSection(controller),
|
|
const SizedBox(height: 16),
|
|
|
|
// Indicateur client sélectionné
|
|
Obx(() {
|
|
if (controller.selectedClient.value != null) {
|
|
return Container(
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
color: Colors.green.shade50,
|
|
border: Border.all(color: Colors.green.shade200),
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(Icons.check_circle,
|
|
color: Colors.green.shade600),
|
|
const SizedBox(width: 8),
|
|
Expanded(
|
|
child: Text(
|
|
'Client existant sélectionné: ${controller.selectedClient.value!.nomComplet}',
|
|
style: TextStyle(
|
|
color: Colors.green.shade800,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
return const SizedBox.shrink();
|
|
}),
|
|
const SizedBox(height: 12),
|
|
|
|
// Champs du formulaire
|
|
_buildTextFormField(
|
|
controller: controller._nomController,
|
|
label: 'Nom',
|
|
validator: (value) =>
|
|
value?.isEmpty ?? true ? 'Veuillez entrer un nom' : null,
|
|
onChanged: (value) {
|
|
if (controller.selectedClient.value != null) {
|
|
controller.selectedClient.value = null;
|
|
}
|
|
},
|
|
),
|
|
const SizedBox(height: 12),
|
|
|
|
_buildTextFormField(
|
|
controller: controller._prenomController,
|
|
label: 'Prénom',
|
|
validator: (value) => value?.isEmpty ?? true
|
|
? 'Veuillez entrer un prénom'
|
|
: null,
|
|
onChanged: (value) {
|
|
if (controller.selectedClient.value != null) {
|
|
controller.selectedClient.value = null;
|
|
}
|
|
},
|
|
),
|
|
const SizedBox(height: 12),
|
|
|
|
_buildTextFormField(
|
|
controller: controller._emailController,
|
|
label: 'Email',
|
|
keyboardType: TextInputType.emailAddress,
|
|
validator: (value) {
|
|
// if (value?.isEmpty ?? true) return 'Veuillez entrer un email';
|
|
if (value?.isEmpty ?? true) return null;
|
|
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$')
|
|
.hasMatch(value!)) {
|
|
return 'Email invalide';
|
|
}
|
|
return null;
|
|
},
|
|
onChanged: (value) {
|
|
if (controller.selectedClient.value != null) {
|
|
controller.selectedClient.value = null;
|
|
}
|
|
// Recherche automatique par email
|
|
controller.searchClients(value);
|
|
},
|
|
),
|
|
const SizedBox(height: 12),
|
|
|
|
_buildTextFormField(
|
|
controller: controller._telephoneController,
|
|
label: 'Téléphone',
|
|
keyboardType: TextInputType.phone,
|
|
validator: (value) => value?.isEmpty ?? true
|
|
? 'Veuillez entrer un téléphone'
|
|
: null,
|
|
onChanged: (value) {
|
|
if (controller.selectedClient.value != null) {
|
|
controller.selectedClient.value = null;
|
|
}
|
|
// Recherche automatique par téléphone
|
|
controller.searchClients(value);
|
|
},
|
|
),
|
|
const SizedBox(height: 12),
|
|
|
|
_buildTextFormField(
|
|
controller: controller._adresseController,
|
|
label: 'Adresse',
|
|
maxLines: 2,
|
|
validator: (value) => value?.isEmpty ?? true
|
|
? 'Veuillez entrer une adresse'
|
|
: null,
|
|
onChanged: (value) {
|
|
if (controller.selectedClient.value != null) {
|
|
controller.selectedClient.value = null;
|
|
}
|
|
},
|
|
),
|
|
const SizedBox(height: 12),
|
|
|
|
_buildCommercialDropdown(),
|
|
|
|
// Liste des suggestions
|
|
Obx(() {
|
|
if (controller.isSearching.value) {
|
|
return const Padding(
|
|
padding: EdgeInsets.all(16),
|
|
child: Center(child: CircularProgressIndicator()),
|
|
);
|
|
}
|
|
|
|
if (controller.suggestedClients.isEmpty) {
|
|
return const SizedBox.shrink();
|
|
}
|
|
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const Divider(),
|
|
Text(
|
|
'Clients trouvés:',
|
|
style: TextStyle(
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.blue.shade700,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
...controller.suggestedClients.map(
|
|
(client) =>
|
|
_buildClientSuggestionTile(client, controller),
|
|
),
|
|
],
|
|
);
|
|
}),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Get.back(),
|
|
child: const Text('Annuler'),
|
|
),
|
|
ElevatedButton(
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.blue.shade800,
|
|
foregroundColor: Colors.white,
|
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
|
),
|
|
onPressed: controller.submitForm,
|
|
child: const Text('Valider la commande'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
// Widget pour la section de recherche
|
|
Widget _buildSearchSection(ClientFormController controller) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Recherche rapide',
|
|
style: TextStyle(
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.blue.shade700,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
TextFormField(
|
|
decoration: InputDecoration(
|
|
labelText: 'Rechercher un client existant',
|
|
hintText: 'Nom, prénom, email ou téléphone...',
|
|
prefixIcon: const Icon(Icons.search),
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
filled: true,
|
|
fillColor: Colors.grey.shade50,
|
|
),
|
|
onChanged: controller.searchClients,
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
// Widget pour afficher une suggestion de client
|
|
Widget _buildClientSuggestionTile(
|
|
Client client, ClientFormController controller) {
|
|
return Card(
|
|
margin: const EdgeInsets.only(bottom: 8),
|
|
child: ListTile(
|
|
leading: CircleAvatar(
|
|
backgroundColor: Colors.blue.shade100,
|
|
child: Icon(Icons.person, color: Colors.blue.shade700),
|
|
),
|
|
title: Text(
|
|
client.nomComplet,
|
|
style: const TextStyle(fontWeight: FontWeight.w500),
|
|
),
|
|
subtitle: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text('📧 ${client.email}'),
|
|
Text('📞 ${client.telephone}'),
|
|
if (client.adresse != null && client.adresse!.isNotEmpty)
|
|
Text('📍 ${client.adresse}'),
|
|
],
|
|
),
|
|
trailing: ElevatedButton(
|
|
onPressed: () => controller.fillFormWithClient(client),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.green,
|
|
foregroundColor: Colors.white,
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
),
|
|
child: const Text('Utiliser'),
|
|
),
|
|
isThreeLine: true,
|
|
),
|
|
);
|
|
}
|
|
|
|
// Widget helper pour les champs de texte
|
|
Widget _buildTextFormField({
|
|
required TextEditingController controller,
|
|
required String label,
|
|
TextInputType? keyboardType,
|
|
String? Function(String?)? validator,
|
|
int maxLines = 1,
|
|
void Function(String)? onChanged,
|
|
}) {
|
|
return TextFormField(
|
|
controller: controller,
|
|
decoration: InputDecoration(
|
|
labelText: label,
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
filled: true,
|
|
fillColor: Colors.grey.shade50,
|
|
),
|
|
keyboardType: keyboardType,
|
|
validator: validator,
|
|
maxLines: maxLines,
|
|
onChanged: onChanged,
|
|
);
|
|
}
|
|
|
|
// Votre méthode _buildCommercialDropdown existante
|
|
Widget _buildCommercialDropdown() {
|
|
// Votre implémentation existante
|
|
return Container(); // Remplacez par votre code existant
|
|
}
|
|
|