import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:intl/intl.dart'; import '../providers/ventes_provider.dart'; import '../providers/article_provider.dart'; import '../models/client.dart'; import '../models/article.dart'; import '../models/sales_order.dart'; class SalesOrderFormScreen extends StatefulWidget { const SalesOrderFormScreen({super.key}); @override State createState() => _SalesOrderFormScreenState(); } class _SalesOrderFormScreenState extends State { final _formKey = GlobalKey(); Client? _selectedClient; DateTime? _dateLivraison; final _notesCtrl = TextEditingController(); final List<_LigneSaisie> _lignes = []; bool _saving = false; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { if (context.read().articles.isEmpty) { context.read().load(); } }); } @override void dispose() { _notesCtrl.dispose(); for (final l in _lignes) { l.dispose(); } super.dispose(); } void _addLigne() { setState(() => _lignes.add(_LigneSaisie())); } void _removeLigne(int i) { setState(() { _lignes[i].dispose(); _lignes.removeAt(i); }); } double get _totalHT => _lignes.fold(0, (s, l) { final qte = double.tryParse(l.qteCtrl.text) ?? 0; final prix = double.tryParse(l.prixCtrl.text) ?? 0; return s + qte * prix; }); double get _totalTTC => _totalHT * 1.19; Future _save() async { if (!_formKey.currentState!.validate()) return; if (_selectedClient == null) { _showError('Veuillez sélectionner un client'); return; } if (_lignes.isEmpty) { _showError('Ajoutez au moins une ligne'); return; } for (int i = 0; i < _lignes.length; i++) { if (_lignes[i].article == null) { _showError('Sélectionnez un article pour la ligne ${i + 1}'); return; } } setState(() => _saving = true); final lignes = _lignes.map((l) => SalesOrderLine( article: l.article, quantiteCommandee: double.tryParse(l.qteCtrl.text) ?? 0, prixUnitaireHT: double.tryParse(l.prixCtrl.text) ?? 0, tauxTVA: 19.0, )).toList(); final order = SalesOrder( client: _selectedClient, dateCommande: DateTime.now().toIso8601String().substring(0, 10), dateLivraisonSouhaitee: _dateLivraison?.toIso8601String().substring(0, 10), notes: _notesCtrl.text.trim().isEmpty ? null : _notesCtrl.text.trim(), lignes: lignes, ); final err = await context.read().createOrder(order); if (mounted) { setState(() => _saving = false); if (err == null) { Navigator.pop(context); ScaffoldMessenger.of(context).showSnackBar(const SnackBar( content: Text('Commande créée avec succès'), backgroundColor: Colors.green, )); } else { _showError(err); } } } void _showError(String msg) { ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text(msg), backgroundColor: Colors.red)); } @override Widget build(BuildContext context) { final clients = context.watch().clients; final articles = context.watch().articles; final fmt = NumberFormat.currency(locale: 'fr_TN', symbol: 'TND', decimalDigits: 3); return Scaffold( backgroundColor: const Color(0xFFF5F7FA), appBar: AppBar( backgroundColor: Colors.white, elevation: 0, title: const Text('Nouvelle commande vente', style: TextStyle(fontWeight: FontWeight.bold)), ), body: Form( key: _formKey, child: ListView( padding: const EdgeInsets.all(16), children: [ // Client _SectionCard( title: 'Client', child: DropdownButtonFormField( value: _selectedClient, hint: const Text('Sélectionner un client'), decoration: _deco('Client'), items: clients .map((c) => DropdownMenuItem( value: c, child: Text(c.raisonSociale), )) .toList(), onChanged: (v) => setState(() => _selectedClient = v), validator: (v) => v == null ? 'Obligatoire' : null, ), ), const SizedBox(height: 12), // Date livraison souhaitée _SectionCard( title: 'Livraison souhaitée (optionnel)', child: GestureDetector( onTap: () async { final d = await showDatePicker( context: context, initialDate: DateTime.now().add(const Duration(days: 7)), firstDate: DateTime.now(), lastDate: DateTime.now().add(const Duration(days: 365)), ); if (d != null) setState(() => _dateLivraison = d); }, child: Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 14), decoration: BoxDecoration( color: const Color(0xFFF8F9FA), border: Border.all(color: Colors.grey[400]!), borderRadius: BorderRadius.circular(8), ), child: Row( children: [ const Icon(Icons.calendar_today_outlined, size: 18, color: Colors.grey), const SizedBox(width: 8), Text( _dateLivraison != null ? DateFormat('dd/MM/yyyy').format(_dateLivraison!) : 'Choisir une date', style: TextStyle( color: _dateLivraison != null ? Colors.black87 : Colors.grey[600], ), ), ], ), ), ), ), const SizedBox(height: 12), // Lignes de commande _SectionCard( title: 'Lignes de commande', child: Column( children: [ ..._lignes.asMap().entries.map((e) => _LigneWidget( index: e.key, ligne: e.value, articles: articles, onRemove: () => _removeLigne(e.key), onChanged: () => setState(() {}), )), const SizedBox(height: 8), OutlinedButton.icon( onPressed: _addLigne, icon: const Icon(Icons.add), label: const Text('Ajouter une ligne'), ), ], ), ), const SizedBox(height: 12), // Notes _SectionCard( title: 'Notes (optionnel)', child: TextFormField( controller: _notesCtrl, maxLines: 3, decoration: _deco('Notes / instructions').copyWith( hintText: 'Remarques, conditions particulières…'), ), ), const SizedBox(height: 12), // Total if (_lignes.isNotEmpty) Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Theme.of(context).colorScheme.primary, borderRadius: BorderRadius.circular(12), ), child: Column( children: [ _TotalRow( label: 'Total HT', value: fmt.format(_totalHT), white: true), _TotalRow( label: 'TVA (19%)', value: fmt.format(_totalTTC - _totalHT), white: true), const Divider(color: Colors.white30), _TotalRow( label: 'Total TTC', value: fmt.format(_totalTTC), white: true, bold: true), ], ), ), const SizedBox(height: 24), SizedBox( height: 52, child: ElevatedButton( onPressed: _saving ? null : _save, style: ElevatedButton.styleFrom( backgroundColor: Theme.of(context).colorScheme.primary, foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10)), ), child: _saving ? const SizedBox( width: 22, height: 22, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white)) : const Text('Créer la commande', style: TextStyle( fontSize: 15, fontWeight: FontWeight.w600)), ), ), const SizedBox(height: 32), ], ), ), ); } } class _LigneWidget extends StatelessWidget { final int index; final _LigneSaisie ligne; final List
articles; final VoidCallback onRemove; final VoidCallback onChanged; const _LigneWidget({ required this.index, required this.ligne, required this.articles, required this.onRemove, required this.onChanged, }); @override Widget build(BuildContext context) { return Container( margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: const Color(0xFFF5F7FA), borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.grey[300]!), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text('Ligne ${index + 1}', style: const TextStyle( fontWeight: FontWeight.w600, fontSize: 12)), const Spacer(), GestureDetector( onTap: onRemove, child: const Icon(Icons.close, size: 18, color: Colors.red), ), ], ), const SizedBox(height: 8), DropdownButtonFormField
( value: ligne.article, hint: const Text('Article', style: TextStyle(fontSize: 13)), decoration: _deco('Article').copyWith( contentPadding: const EdgeInsets.symmetric( horizontal: 10, vertical: 10)), items: articles .map((a) => DropdownMenuItem( value: a, child: Text('${a.reference} — ${a.designation}', style: const TextStyle(fontSize: 13)), )) .toList(), onChanged: (a) { ligne.article = a; if (a != null) { ligne.prixCtrl.text = a.prixUnitaire.toString(); } onChanged(); }, ), const SizedBox(height: 8), Row( children: [ Expanded( child: TextFormField( controller: ligne.qteCtrl, keyboardType: TextInputType.number, decoration: _deco('Quantité').copyWith( contentPadding: const EdgeInsets.symmetric( horizontal: 10, vertical: 10)), onChanged: (_) => onChanged(), validator: (v) { if (v == null || v.isEmpty) return 'Requis'; if ((double.tryParse(v) ?? 0) <= 0) return '> 0'; return null; }, ), ), const SizedBox(width: 8), Expanded( child: TextFormField( controller: ligne.prixCtrl, keyboardType: TextInputType.number, decoration: _deco('Prix HT').copyWith( contentPadding: const EdgeInsets.symmetric( horizontal: 10, vertical: 10)), onChanged: (_) => onChanged(), validator: (v) { if (v == null || v.isEmpty) return 'Requis'; if ((double.tryParse(v) ?? 0) <= 0) return '> 0'; return null; }, ), ), ], ), ], ), ); } } class _LigneSaisie { Article? article; final qteCtrl = TextEditingController(); final prixCtrl = TextEditingController(); void dispose() { qteCtrl.dispose(); prixCtrl.dispose(); } } class _SectionCard extends StatelessWidget { final String title; final Widget child; const _SectionCard({required this.title, required this.child}); @override Widget build(BuildContext context) { return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.04), blurRadius: 8, offset: const Offset(0, 2)), ], ), padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, style: const TextStyle( fontWeight: FontWeight.w700, fontSize: 12, color: Color(0xFF6B7280))), const SizedBox(height: 10), child, ], ), ); } } class _TotalRow extends StatelessWidget { final String label; final String value; final bool white; final bool bold; const _TotalRow( {required this.label, required this.value, this.white = false, this.bold = false}); @override Widget build(BuildContext context) { final color = white ? Colors.white : const Color(0xFF374151); return Padding( padding: const EdgeInsets.symmetric(vertical: 3), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(label, style: TextStyle( color: white ? Colors.white70 : Colors.grey[600], fontSize: 13)), Text(value, style: TextStyle( color: color, fontWeight: bold ? FontWeight.bold : FontWeight.normal, fontSize: bold ? 16 : 13)), ], ), ); } } InputDecoration _deco(String label) => InputDecoration( labelText: label, border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)), filled: true, fillColor: const Color(0xFFF8F9FA), contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14), );