import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:youmazgestion/Components/app_bar.dart'; import 'package:youmazgestion/Components/appDrawer.dart'; import 'package:youmazgestion/Services/stock_managementDatabase.dart'; import 'package:youmazgestion/controller/userController.dart'; import '../Models/produit.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; class DemandeSortiePersonnellePage extends StatefulWidget { const DemandeSortiePersonnellePage({super.key}); @override _DemandeSortiePersonnellePageState createState() => _DemandeSortiePersonnellePageState(); } class _DemandeSortiePersonnellePageState extends State with TickerProviderStateMixin { final AppDatabase _database = AppDatabase.instance; final UserController _userController = Get.find(); final _formKey = GlobalKey(); final _quantiteController = TextEditingController(text: '1'); final _motifController = TextEditingController(); final _notesController = TextEditingController(); final _searchController = TextEditingController(); Product? _selectedProduct; List _products = []; List _filteredProducts = []; bool _isLoading = false; bool _isSearching = false; late AnimationController _animationController; late Animation _fadeAnimation; late Animation _slideAnimation; @override void initState() { super.initState(); _animationController = AnimationController( duration: const Duration(milliseconds: 800), vsync: this, ); _fadeAnimation = Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation(parent: _animationController, curve: Curves.easeInOut), ); _slideAnimation = Tween(begin: const Offset(0, 0.3), end: Offset.zero).animate( CurvedAnimation(parent: _animationController, curve: Curves.easeOutCubic), ); _loadProducts(); _searchController.addListener(_filterProducts); } void _scanQrOrBarcode() async { await showDialog( context: context, builder: (context) { return AlertDialog( content: Container( width: double.maxFinite, height: 400, child: MobileScanner( onDetect: (BarcodeCapture barcodeCap) { print("BarcodeCapture: $barcodeCap"); // Now accessing the barcodes attribute final List barcodes = barcodeCap.barcodes; if (barcodes.isNotEmpty) { // Get the first detected barcode value String? scanResult = barcodes.first.rawValue; print("Scanned Result: $scanResult"); if (scanResult != null && scanResult.isNotEmpty) { setState(() { _searchController.text = scanResult; print( "Updated Search Controller: ${_searchController.text}"); }); // Close dialog after scanning Navigator.of(context).pop(); // Refresh product list based on new search input _filterProducts(); } else { print("Scan result was empty or null."); Navigator.of(context).pop(); } } else { print("No barcodes detected."); Navigator.of(context).pop(); } }, ), ), ); }, ); } void _filterProducts() { final query = _searchController.text.toLowerCase(); setState(() { if (query.isEmpty) { _filteredProducts = _products; _isSearching = false; } else { _isSearching = true; _filteredProducts = _products.where((product) { return product.name.toLowerCase().contains(query) || (product.reference?.toLowerCase().contains(query) ?? false); }).toList(); } }); } Future _loadProducts() async { setState(() => _isLoading = true); try { final products = await _database.getProducts(); setState(() { _products = products.where((p) { // Check stock availability print("point de vente id: ${_userController.pointDeVenteId}"); bool hasStock = _userController.pointDeVenteId == 0 ? (p.stock ?? 0) > 0 : (p.stock ?? 0) > 0 && p.pointDeVenteId == _userController.pointDeVenteId; return hasStock; }).toList(); // Setting filtered products _filteredProducts = _products; // End loading state _isLoading = false; }); // Start the animation _animationController.forward(); } catch (e) { // Handle any errors setState(() { _isLoading = false; }); _showErrorSnackbar('Impossible de charger les produits: $e'); } } Future _soumettreDemandePersonnelle() async { if (!_formKey.currentState!.validate() || _selectedProduct == null) { _showErrorSnackbar('Veuillez remplir tous les champs obligatoires'); return; } final quantite = int.tryParse(_quantiteController.text) ?? 0; if (quantite <= 0) { _showErrorSnackbar('La quantité doit être supérieure à 0'); return; } if ((_selectedProduct!.stock ?? 0) < quantite) { _showErrorSnackbar( 'Stock insuffisant (disponible: ${_selectedProduct!.stock})'); return; } // Confirmation dialog final confirmed = await _showConfirmationDialog(); if (!confirmed) return; setState(() => _isLoading = true); try { await _database.createSortieStockPersonnelle( produitId: _selectedProduct!.id!, adminId: _userController.userId, quantite: quantite, motif: _motifController.text.trim(), pointDeVenteId: _userController.pointDeVenteId > 0 ? _userController.pointDeVenteId : null, notes: _notesController.text.trim().isNotEmpty ? _notesController.text.trim() : null, ); _showSuccessSnackbar( 'Votre demande de sortie personnelle a été soumise pour approbation'); // Réinitialiser le formulaire avec animation _resetForm(); _loadProducts(); } catch (e) { _showErrorSnackbar('Impossible de soumettre la demande: $e'); } finally { setState(() => _isLoading = false); } } void _resetForm() { _formKey.currentState!.reset(); _quantiteController.text = '1'; _motifController.clear(); _notesController.clear(); _searchController.clear(); setState(() { _selectedProduct = null; _isSearching = false; }); } Future _showConfirmationDialog() async { return await showDialog( context: context, builder: (context) => AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), title: Row( children: [ Icon(Icons.help_outline, color: Colors.orange.shade700), const SizedBox(width: 8), const Text('Confirmer la demande'), ], ), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Êtes-vous sûr de vouloir soumettre cette demande ?'), const SizedBox(height: 16), Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.grey.shade50, borderRadius: BorderRadius.circular(8), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Produit: ${_selectedProduct?.name}'), Text('Quantité: ${_quantiteController.text}'), Text('Motif: ${_motifController.text}'), ], ), ), ], ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), child: const Text('Annuler'), ), ElevatedButton( onPressed: () => Navigator.of(context).pop(true), style: ElevatedButton.styleFrom( backgroundColor: Colors.orange.shade700, foregroundColor: Colors.white, ), child: const Text('Confirmer'), ), ], ), ) ?? false; } void _showSuccessSnackbar(String message) { Get.snackbar( '', '', titleText: Row( children: [ Icon(Icons.check_circle, color: Colors.white), const SizedBox(width: 8), const Text('Succès', style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold)), ], ), messageText: Text(message, style: const TextStyle(color: Colors.white)), backgroundColor: Colors.green.shade600, colorText: Colors.white, duration: const Duration(seconds: 4), margin: const EdgeInsets.all(16), borderRadius: 12, icon: Icon(Icons.check_circle_outline, color: Colors.white), ); } void _showErrorSnackbar(String message) { Get.snackbar( '', '', titleText: Row( children: [ Icon(Icons.error, color: Colors.white), const SizedBox(width: 8), const Text('Erreur', style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold)), ], ), messageText: Text(message, style: const TextStyle(color: Colors.white)), backgroundColor: Colors.red.shade600, colorText: Colors.white, duration: const Duration(seconds: 4), margin: const EdgeInsets.all(16), borderRadius: 12, ); } Widget _buildHeaderCard() { return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( gradient: LinearGradient( colors: [Colors.blue.shade600, Colors.blue.shade400], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.blue.shade200, blurRadius: 12, offset: const Offset(0, 4), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(12), ), child: Icon(Icons.inventory_2, color: Colors.white, size: 28), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Sortie personnelle de stock', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: Colors.white, ), ), const SizedBox(height: 4), Text( 'Demande d\'approbation requise', style: TextStyle( fontSize: 14, color: Colors.white.withOpacity(0.8), ), ), ], ), ), ], ), const SizedBox(height: 16), Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: const Text( 'Cette fonctionnalité permet aux administrateurs de demander ' 'la sortie d\'un produit du stock pour usage personnel. ' 'Toute demande nécessite une approbation avant traitement.', style: TextStyle(fontSize: 14, color: Colors.white), ), ), ], ), ); } Widget _buildProductSelector() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Sélection du produit *', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.grey.shade800, ), ), const SizedBox(height: 12), // Barre de recherche Row( children: [ Expanded( child: TextField( controller: _searchController, decoration: InputDecoration( hintText: 'Rechercher un produit...', prefixIcon: Icon(Icons.search, color: Colors.grey.shade600), ), onChanged: (value) { _filterProducts(); // Call to filter products }, ), ), IconButton( icon: Icon(Icons.qr_code_scanner, color: Colors.blue), onPressed: _scanQrOrBarcode, tooltip: 'Scanner QR ou code-barres', ), ], ), const SizedBox(height: 12), // Liste des produits Container( height: 200, decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey.shade300), ), child: _filteredProducts.isEmpty ? Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.search_off, size: 48, color: Colors.grey.shade400), const SizedBox(height: 8), Text( _isSearching ? 'Aucun produit trouvé' : 'Aucun produit disponible', style: TextStyle(color: Colors.grey.shade600), ), ], ), ) : ListView.builder( itemCount: _filteredProducts.length, itemBuilder: (context, index) { final product = _filteredProducts[index]; final isSelected = _selectedProduct?.id == product.id; return AnimatedContainer( duration: const Duration(milliseconds: 200), margin: const EdgeInsets.symmetric( horizontal: 8, vertical: 4), decoration: BoxDecoration( color: isSelected ? Colors.orange.shade50 : Colors.transparent, borderRadius: BorderRadius.circular(8), border: Border.all( color: isSelected ? Colors.orange.shade300 : Colors.transparent, width: 2, ), ), child: ListTile( leading: Container( width: 48, height: 48, decoration: BoxDecoration( color: isSelected ? Colors.orange.shade100 : Colors.grey.shade100, borderRadius: BorderRadius.circular(8), ), child: Icon( Icons.inventory, color: isSelected ? Colors.orange.shade700 : Colors.grey.shade600, ), ), title: Text( product.name, style: TextStyle( fontWeight: isSelected ? FontWeight.bold : FontWeight.w500, color: isSelected ? Colors.orange.shade800 : Colors.grey.shade800, ), ), subtitle: Text( 'Stock: ${product.stock} • Réf: ${product.reference ?? 'N/A'}', style: TextStyle( color: isSelected ? Colors.orange.shade600 : Colors.grey.shade600, ), ), trailing: isSelected ? Icon(Icons.check_circle, color: Colors.orange.shade700) : Icon(Icons.radio_button_unchecked, color: Colors.grey.shade400), onTap: () { setState(() { _selectedProduct = product; }); }, ), ); }, ), ), ], ); } Widget _buildFormSection() { return Column( children: [ // Quantité _buildInputField( label: 'Quantité *', controller: _quantiteController, keyboardType: TextInputType.number, icon: Icons.format_list_numbered, suffix: _selectedProduct != null ? Text('max: ${_selectedProduct!.stock}', style: TextStyle(color: Colors.grey.shade600)) : null, validator: (value) { if (value == null || value.isEmpty) { return 'Veuillez entrer une quantité'; } final quantite = int.tryParse(value); if (quantite == null || quantite <= 0) { return 'Quantité invalide'; } if (_selectedProduct != null && quantite > (_selectedProduct!.stock ?? 0)) { return 'Quantité supérieure au stock disponible'; } return null; }, ), const SizedBox(height: 20), // Motif _buildInputField( label: 'Motif *', controller: _motifController, icon: Icons.description, hintText: 'Raison de cette sortie personnelle', validator: (value) { if (value == null || value.trim().isEmpty) { return 'Veuillez indiquer le motif'; } if (value.trim().length < 5) { return 'Le motif doit contenir au moins 5 caractères'; } return null; }, ), const SizedBox(height: 20), // Notes _buildInputField( label: 'Notes complémentaires', controller: _notesController, icon: Icons.note_add, hintText: 'Informations complémentaires (optionnel)', maxLines: 3, ), ], ); } Widget _buildInputField({ required String label, required TextEditingController controller, required IconData icon, String? hintText, TextInputType? keyboardType, int maxLines = 1, Widget? suffix, String? Function(String?)? validator, }) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: Colors.grey.shade800, ), ), const SizedBox(height: 8), TextFormField( controller: controller, keyboardType: keyboardType, maxLines: maxLines, validator: validator, decoration: InputDecoration( hintText: hintText, prefixIcon: Icon(icon, color: Colors.grey.shade600), suffix: suffix, border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Colors.grey.shade300), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Colors.grey.shade300), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Colors.orange.shade400, width: 2), ), filled: true, fillColor: Colors.grey.shade50, contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), ), ), ], ); } Widget _buildUserInfoCard() { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.grey.shade50, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey.shade200), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(Icons.person, color: Colors.grey.shade700), const SizedBox(width: 8), Text( 'Informations de la demande', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Colors.grey.shade800, ), ), ], ), const SizedBox(height: 12), _buildInfoRow( Icons.account_circle, 'Demandeur', _userController.name), if (_userController.pointDeVenteId > 0) _buildInfoRow(Icons.store, 'Point de vente', _userController.pointDeVenteDesignation), _buildInfoRow(Icons.calendar_today, 'Date', DateTime.now().toLocal().toString().split(' ')[0]), ], ), ); } Widget _buildInfoRow(IconData icon, String label, String value) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Row( children: [ Icon(icon, size: 16, color: Colors.grey.shade600), const SizedBox(width: 8), Text( '$label: ', style: TextStyle( fontWeight: FontWeight.w500, color: Colors.grey.shade700, ), ), Expanded( child: Text( value, style: TextStyle(color: Colors.grey.shade800), ), ), ], ), ); } Widget _buildSubmitButton() { return Container( width: double.infinity, height: 56, decoration: BoxDecoration( borderRadius: BorderRadius.circular(16), gradient: LinearGradient( colors: [Colors.orange.shade700, Colors.orange.shade500], begin: Alignment.topLeft, end: Alignment.bottomRight, ), boxShadow: [ BoxShadow( color: Colors.orange.shade300, blurRadius: 12, offset: const Offset(0, 4), ), ], ), child: ElevatedButton( onPressed: _isLoading ? null : _soumettreDemandePersonnelle, style: ElevatedButton.styleFrom( backgroundColor: Colors.transparent, shadowColor: Colors.transparent, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), ), child: _isLoading ? const Row( mainAxisAlignment: MainAxisAlignment.center, children: [ SizedBox( width: 24, height: 24, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white, ), ), SizedBox(width: 12), Text( 'Traitement...', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white, ), ), ], ) : const Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.send, color: Colors.white), SizedBox(width: 8), Text( 'Soumettre la demande', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white, ), ), ], ), ), ); } @override Widget build(BuildContext context) { return Scaffold( appBar: CustomAppBar(title: 'Demande sortie personnelle'), drawer: CustomDrawer(), body: _isLoading && _products.isEmpty ? const Center(child: CircularProgressIndicator()) : FadeTransition( opacity: _fadeAnimation, child: SlideTransition( position: _slideAnimation, child: SingleChildScrollView( padding: const EdgeInsets.all(16), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildHeaderCard(), const SizedBox(height: 24), _buildProductSelector(), const SizedBox(height: 24), _buildFormSection(), const SizedBox(height: 24), _buildUserInfoCard(), const SizedBox(height: 32), _buildSubmitButton(), const SizedBox(height: 16), ], ), ), ), ), ), ); } @override void dispose() { _animationController.dispose(); _quantiteController.dispose(); _motifController.dispose(); _notesController.dispose(); _searchController.dispose(); super.dispose(); } } extension on BarcodeCapture { get rawValue => null; }