import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:intl/intl.dart'; import '../providers/production_provider.dart'; import '../providers/article_provider.dart'; import '../models/article.dart'; import '../models/production_order.dart'; import '../services/production_service.dart'; class ProductionFormScreen extends StatefulWidget { const ProductionFormScreen({super.key}); @override State createState() => _ProductionFormScreenState(); } class _ProductionFormScreenState extends State { final _formKey = GlobalKey(); Article? _selectedProduit; final _qteCtrl = TextEditingController(); DateTime? _datePlanifiee; List _bom = []; bool _loadingBom = false; bool _saving = false; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { if (context.read().articles.isEmpty) { context.read().load(); } }); } @override void dispose() { _qteCtrl.dispose(); super.dispose(); } List
get _produitsFinis => context.read().articles.where((a) => a.type == 'PF').toList(); Future _onProduitSelected(Article? article) async { setState(() { _selectedProduit = article; _bom = []; }); if (article?.id != null) { setState(() => _loadingBom = true); try { final bom = await ProductionService.getBom(article!.id!); setState(() => _bom = bom); } catch (_) {} setState(() => _loadingBom = false); } } Future _save() async { if (!_formKey.currentState!.validate()) return; if (_selectedProduit == null) { _showErr('Sélectionnez un produit fini'); return; } if (_datePlanifiee == null) { _showErr('Choisissez une date planifiée'); return; } if (_bom.isEmpty) { _showErr('Ce produit n\'a pas de nomenclature BOM définie'); return; } setState(() => _saving = true); final err = await context.read().plan( produitFiniId: _selectedProduit!.id!, quantite: double.tryParse(_qteCtrl.text) ?? 0, datePlanifiee: _datePlanifiee!.toIso8601String().substring(0, 10), ); if (mounted) { setState(() => _saving = false); if (err == null) { Navigator.pop(context); ScaffoldMessenger.of(context).showSnackBar(const SnackBar( content: Text('Ordre de fabrication planifié'), backgroundColor: Colors.green, )); } else { _showErr(err); } } } void _showErr(String msg) => ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text(msg), backgroundColor: Colors.red)); @override Widget build(BuildContext context) { final articles = context.watch(); final qte = double.tryParse(_qteCtrl.text) ?? 0; return Scaffold( backgroundColor: const Color(0xFFF5F7FA), appBar: AppBar( backgroundColor: Colors.white, elevation: 0, title: const Text('Nouvel ordre de fabrication', style: TextStyle(fontWeight: FontWeight.bold)), ), body: Form( key: _formKey, child: ListView( padding: const EdgeInsets.all(16), children: [ _Card( title: 'Produit à fabriquer', child: articles.isLoading ? const Center(child: CircularProgressIndicator()) : DropdownButtonFormField
( value: _selectedProduit, hint: const Text('Sélectionner un produit fini (PF)'), decoration: _deco('Produit fini'), items: _produitsFinis .map((a) => DropdownMenuItem( value: a, child: Text('${a.reference} — ${a.designation}'), )) .toList(), onChanged: _onProduitSelected, validator: (v) => v == null ? 'Obligatoire' : null, ), ), const SizedBox(height: 12), // BOM affiché après sélection if (_loadingBom) const Center(child: Padding( padding: EdgeInsets.all(12), child: CircularProgressIndicator())), if (_bom.isNotEmpty) ...[ _Card( title: 'Nomenclature (BOM)', child: Column( children: _bom.map((b) { final qteNecessaire = qte * b.quantiteParUnite; final stock = b.composant?.stockActuel ?? 0; final ok = stock >= qteNecessaire; return Padding( padding: const EdgeInsets.symmetric(vertical: 5), child: Row( children: [ Icon(ok ? Icons.check_circle_outline : Icons.warning_amber_outlined, size: 16, color: ok ? Colors.green : Colors.orange), const SizedBox(width: 8), Expanded( child: Text(b.composant?.designation ?? '—', style: const TextStyle(fontSize: 13)), ), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( '${(b.quantiteParUnite * (qte > 0 ? qte : 1)).toStringAsFixed(2)} ${b.uniteMesure ?? ''}', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: ok ? const Color(0xFF374151) : Colors.orange), ), Text('Stock: ${stock.toStringAsFixed(2)}', style: TextStyle(fontSize: 11, color: Colors.grey[500])), ], ), ], ), ); }).toList(), ), ), const SizedBox(height: 12), ], if (_selectedProduit != null && !_loadingBom && _bom.isEmpty) Container( padding: const EdgeInsets.all(12), margin: const EdgeInsets.only(bottom: 12), decoration: BoxDecoration( color: Colors.orange[50], borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.orange[200]!), ), child: Row( children: [ Icon(Icons.warning_amber, color: Colors.orange[700], size: 18), const SizedBox(width: 8), const Expanded( child: Text('Aucune nomenclature BOM définie pour ce produit.', style: TextStyle(fontSize: 13)), ), ], ), ), _Card( title: 'Quantité & Date', child: Column( children: [ TextFormField( controller: _qteCtrl, keyboardType: TextInputType.number, decoration: _deco('Quantité à produire') .copyWith(hintText: 'Ex: 1000'), onChanged: (_) => setState(() {}), validator: (v) { if (v == null || v.isEmpty) return 'Obligatoire'; if ((double.tryParse(v) ?? 0) <= 0) return 'Doit être > 0'; return null; }, ), const SizedBox(height: 12), GestureDetector( onTap: () async { final d = await showDatePicker( context: context, initialDate: DateTime.now().add(const Duration(days: 1)), firstDate: DateTime.now(), lastDate: DateTime.now().add(const Duration(days: 365)), ); if (d != null) setState(() => _datePlanifiee = 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( _datePlanifiee != null ? DateFormat('dd/MM/yyyy').format(_datePlanifiee!) : 'Date planifiée *', style: TextStyle( color: _datePlanifiee != null ? Colors.black87 : Colors.grey[600]), ), ], ), ), ), ], ), ), const SizedBox(height: 24), SizedBox( height: 52, child: ElevatedButton( onPressed: _saving ? null : _save, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF6366F1), 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('Planifier l\'OF', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600)), ), ), const SizedBox(height: 32), ], ), ), ); } } class _Card extends StatelessWidget { final String title; final Widget child; const _Card({required this.title, required this.child}); @override Widget build(BuildContext context) => 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, ], ), ); } 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), );