import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:intl/intl.dart'; import '../../../providers/auth_provider.dart'; import '../../../providers/order_provider.dart'; import '../../../providers/database_provider.dart'; import '../../../database/database.dart'; import '../../../database/daos/listini_dao.dart'; import '../../widgets/article_image.dart'; class OrderEditPage extends ConsumerStatefulWidget { final int? id; final int? idCliente; const OrderEditPage({super.key, this.id, this.idCliente}); @override ConsumerState createState() => _OrderEditPageState(); } class _OrderEditPageState extends ConsumerState with SingleTickerProviderStateMixin { late TabController _tabController; final _artCtrl = TextEditingController(); final _noteClienteCtrl = TextEditingController(); final _noteOrdineCtrl = TextEditingController(); final _riferimentoCtrl = TextEditingController(); List _searchResults = []; bool _searching = false; List _filiali = []; List _listini = []; List _pagamenti = []; Cliente? _cliente; int? _selectedFiliale; int? _selectedListino; int? _selectedPagamento; bool _dataLoaded = false; @override void initState() { super.initState(); _tabController = TabController(length: 2, vsync: this); WidgetsBinding.instance.addPostFrameCallback((_) => _init()); } @override void dispose() { _tabController.dispose(); _artCtrl.dispose(); _noteClienteCtrl.dispose(); _noteOrdineCtrl.dispose(); _riferimentoCtrl.dispose(); super.dispose(); } Future _init() async { final agente = ref.read(authProvider).agente; if (agente == null) return; if (widget.id != null) { await ref.read(orderProvider.notifier).loadOrder(widget.id!); } else if (widget.idCliente != null) { await ref .read(orderProvider.notifier) .createOrder(agente.idAgente, idCliente: widget.idCliente!); } else { await ref.read(orderProvider.notifier).createOrder(agente.idAgente); } final orderState = ref.read(orderProvider); final order = orderState.order; if (order == null) return; final cli = order.idCliente; final clientiDao = ref.read(clientiDaoProvider); final filialiDao = ref.read(filialiDaoProvider); final listiniDao = ref.read(listiniDaoProvider); final pagamentiDao = ref.read(pagamentiDaoProvider); if (cli != null) { _cliente = await clientiDao.getById(cli); _filiali = await filialiDao.getDestinationsForCustomer(cli); } _listini = await listiniDao.list(); _pagamenti = await pagamentiDao.searchActive(); setState(() { _selectedFiliale = order.idFiliale; _selectedListino = order.idListino ?? _cliente?.idListino; _selectedPagamento = order.idPagamento ?? _cliente?.idPagamento; _noteClienteCtrl.text = order.noteCliente ?? _cliente?.noteOrdine ?? ''; _noteOrdineCtrl.text = order.noteOrdine ?? ''; _riferimentoCtrl.text = order.riferimento ?? ''; _dataLoaded = true; }); } Future _searchArticles(String q) async { if (q.length < 2) { setState(() { _searchResults = []; _searching = false; }); return; } setState(() => _searching = true); final dao = ref.read(articoliDaoProvider); final results = await dao.getItems(q); setState(() { _searchResults = results; _searching = false; }); } void _showArticlePicker(Articolo art) async { final agente = ref.read(authProvider).agente; final canEditPrice = agente?.modificaPrezzi == 1; final listini = await ref.read(articoliDaoProvider).getPriceCodes(art.idArticolo); final listiniDao = ref.read(listiniDaoProvider); if (!context.mounted) return; showModalBottomSheet( context: context, isScrollControlled: true, builder: (_) => _ArticlePickerSheet( articolo: art, listini: listini, listiniDao: listiniDao, canEditPrezzo: canEditPrice, idListinoDefault: _selectedListino, onConfirm: (colli, quantita, prezzo, listinoId) { _addArticleWithDetails( art: art, colli: colli, quantita: quantita, prezzo: prezzo, ); }, ), ); } Future _addArticleWithDetails({ required Articolo art, required double colli, required double quantita, required double prezzo, }) async { final orderState = ref.read(orderProvider); final idOrdine = orderState.order?.idOrdine; if (idOrdine == null || idOrdine == 0) return; await ref.read(orderProvider.notifier).addLine(RigaOrdine( idRigaOrdine: 0, idOrdine: idOrdine, idArticolo: art.idArticolo, quantita: quantita, prezzoLordo: prezzo, prezzoNetto: prezzo, totale: prezzo * quantita, promozione: 0, rigaPromozione: 0, colli: colli, )); _artCtrl.clear(); setState(() => _searchResults = []); } Future _salva() async { final notifier = ref.read(orderProvider.notifier); notifier.setIdFiliale(_selectedFiliale); notifier.setIdListino(_selectedListino); notifier.setIdPagamento(_selectedPagamento); notifier.setNoteCliente(_noteClienteCtrl.text); notifier.setNoteOrdine(_noteOrdineCtrl.text); notifier.setRiferimento(_riferimentoCtrl.text); await notifier.saveOrder(); if (context.mounted) Navigator.pop(context); } @override Widget build(BuildContext context) { final orderState = ref.watch(orderProvider); final theme = Theme.of(context); return Scaffold( appBar: AppBar( title: Text('Ordine #${orderState.order?.numeroOrdine ?? 'nuovo'}'), bottom: TabBar( controller: _tabController, tabs: const [ Tab(text: 'Testata'), Tab(text: 'Righe'), ], ), ), body: orderState.isLoading || !_dataLoaded ? const Center(child: CircularProgressIndicator()) : TabBarView( controller: _tabController, children: [ _buildHeaderTab(theme), _buildLinesTab(theme, orderState), ], ), floatingActionButton: _tabController.index == 1 && orderState.order?.idOrdine != null ? FloatingActionButton.extended( onPressed: _salva, icon: const Icon(Icons.save), label: const Text('Salva'), ) : null, ); } Widget _buildHeaderTab(ThemeData theme) { return SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ DropdownButtonFormField( value: _selectedFiliale, decoration: const InputDecoration(labelText: 'Filiale'), items: _filiali .map((f) => DropdownMenuItem( value: f.idFiliale, child: Text(f.descrizione ?? ''), )) .toList(), onChanged: (v) => setState(() => _selectedFiliale = v), ), const SizedBox(height: 16), DropdownButtonFormField( value: _selectedListino, decoration: const InputDecoration(labelText: 'Listino'), items: _listini .map((l) => DropdownMenuItem( value: l.idListino, child: Text(l.descrizione ?? ''), )) .toList(), onChanged: (v) => setState(() => _selectedListino = v), ), const SizedBox(height: 16), DropdownButtonFormField( value: _selectedPagamento, decoration: const InputDecoration(labelText: 'Pagamento'), items: _pagamenti .map((p) => DropdownMenuItem( value: p.idPagamento, child: Text(p.descrizione ?? ''), )) .toList(), onChanged: (v) => setState(() => _selectedPagamento = v), ), const SizedBox(height: 16), TextFormField( controller: _noteClienteCtrl, decoration: const InputDecoration( labelText: 'Note Cliente', alignLabelWithHint: true, ), maxLines: 3, ), const SizedBox(height: 16), TextFormField( controller: _noteOrdineCtrl, decoration: const InputDecoration( labelText: 'Note Ordine', alignLabelWithHint: true, ), maxLines: 3, ), const SizedBox(height: 16), TextFormField( controller: _riferimentoCtrl, decoration: const InputDecoration(labelText: 'Riferimento'), maxLines: 1, ), ], ), ); } Widget _buildLinesTab(ThemeData theme, OrderState orderState) { return Column( children: [ Padding( padding: const EdgeInsets.all(12), child: TextField( controller: _artCtrl, decoration: InputDecoration( hintText: 'Cerca articolo...', prefixIcon: const Icon(Icons.search), suffixIcon: _searching ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2)) : null, ), onChanged: _searchArticles, ), ), if (_searchResults.isNotEmpty) SizedBox( height: 200, child: ListView.builder( itemCount: _searchResults.length, itemBuilder: (_, i) => ListTile( leading: ArticleImage( idArticolo: _searchResults[i].idArticolo, codArticolo: _searchResults[i].codArticolo), title: Text(_searchResults[i].descrizione ?? ''), subtitle: Text(_searchResults[i].codArticolo ?? ''), trailing: TextButton( onPressed: () => _showArticlePicker(_searchResults[i]), child: const Text('Aggiungi')), ), ), ), Expanded( child: orderState.lines.isEmpty ? const Center(child: Text('Nessun articolo')) : ListView.builder( itemCount: orderState.lines.length, itemBuilder: (_, i) => _LineCard( line: orderState.lines[i], onDelete: () => ref .read(orderProvider.notifier) .removeLine(orderState.lines[i].idRigaOrdine), theme: theme, ), ), ), Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: theme.colorScheme.surfaceContainerHighest, border: Border( top: BorderSide(color: theme.dividerColor)), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('Qtà: ${orderState.totQta.toStringAsFixed(1)}'), Text( 'Totale: ${NumberFormat.currency(locale: 'it', symbol: '€').format(orderState.totale)}', style: theme.textTheme.titleMedium ?.copyWith(fontWeight: FontWeight.bold), ), ], ), ), ], ); } } class _LineCard extends StatelessWidget { final RigaOrdine line; final VoidCallback onDelete; final ThemeData theme; const _LineCard({required this.line, required this.onDelete, required this.theme}); @override Widget build(BuildContext context) { final isPromo = line.rigaPromozione == 1; return Card( margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), color: isPromo ? Colors.green.shade50 : null, child: ListTile( title: Text('Articolo #${line.idArticolo ?? ''}', style: TextStyle( fontWeight: isPromo ? FontWeight.bold : null)), subtitle: Text( 'Qtà: ${line.quantita?.toStringAsFixed(1) ?? "0"} Prezzo: ${line.prezzoNetto?.toStringAsFixed(2) ?? "0,00"}€'), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ if (isPromo) Container( padding: const EdgeInsets.symmetric( horizontal: 6, vertical: 2), decoration: BoxDecoration( color: Colors.green, borderRadius: BorderRadius.circular(8)), child: const Text('PROMO', style: TextStyle( color: Colors.white, fontSize: 10)), ), if (!isPromo) IconButton( icon: const Icon(Icons.delete_outline, size: 20), onPressed: onDelete), ], ), ), ); } } class _ArticlePickerSheet extends StatefulWidget { final Articolo articolo; final List listini; final ListiniDao listiniDao; final bool canEditPrezzo; final int? idListinoDefault; final void Function(double colli, double quantita, double prezzo, int listinoId) onConfirm; const _ArticlePickerSheet({ required this.articolo, required this.listini, required this.listiniDao, required this.canEditPrezzo, this.idListinoDefault, required this.onConfirm, }); @override State<_ArticlePickerSheet> createState() => _ArticlePickerSheetState(); } class _ArticlePickerSheetState extends State<_ArticlePickerSheet> { final _colliCtrl = TextEditingController(); final _qtaCtrl = TextEditingController(); final _prezzoCtrl = TextEditingController(); int? _selectedListino; double _prezzo = 0; bool _updatingColli = false; bool _updatingQta = false; @override void initState() { super.initState(); _selectedListino = widget.idListinoDefault; if (_selectedListino != null) { _loadPrezzo(_selectedListino!); } } @override void dispose() { _colliCtrl.dispose(); _qtaCtrl.dispose(); _prezzoCtrl.dispose(); super.dispose(); } Future _loadPrezzo(int idListino) async { final lp = await widget.listiniDao.findListinoArticolo( idListino, widget.articolo.idArticolo); if (lp?.prezzo != null) { setState(() => _prezzo = lp!.prezzo!); _prezzoCtrl.text = _prezzo.toStringAsFixed(2); } } void _onColliChanged(String v) { if (_updatingColli) return; _updatingQta = true; final colli = double.tryParse(v) ?? 0; final pezzi = widget.articolo.pezziConfezione ?? 0; if (colli > 0 && pezzi > 0) { _qtaCtrl.text = (colli * pezzi).toStringAsFixed(1); } else if (colli == 0) { _qtaCtrl.text = ''; } _updatingQta = false; } void _onQtaChanged(String v) { if (_updatingQta) return; _updatingColli = true; final qta = double.tryParse(v) ?? 0; if (qta > 0) { _colliCtrl.text = '0'; } else if (qta == 0) { _colliCtrl.text = ''; } _updatingColli = false; } void _confirm() { final colli = double.tryParse(_colliCtrl.text) ?? 0; final qta = double.tryParse(_qtaCtrl.text) ?? 0; final prezzo = double.tryParse(_prezzoCtrl.text) ?? 0; final listinoId = _selectedListino ?? 0; widget.onConfirm(colli, qta, prezzo, listinoId); Navigator.pop(context); } @override Widget build(BuildContext context) { final theme = Theme.of(context); return Padding( padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom, left: 16, right: 16, top: 16, ), child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(widget.articolo.descrizione ?? '', style: theme.textTheme.titleMedium), const SizedBox(height: 4), Text(widget.articolo.codArticolo ?? '', style: theme.textTheme.bodySmall), if (widget.articolo.pezziConfezione != null) Text('Pezzi/confezione: ${widget.articolo.pezziConfezione}', style: theme.textTheme.bodySmall), const SizedBox(height: 16), DropdownButtonFormField( value: _selectedListino, decoration: const InputDecoration(labelText: 'Listino'), isExpanded: true, items: widget.listini .map((l) => DropdownMenuItem( value: l.idListino, child: Text(l.descrizione ?? ''), )) .toList(), onChanged: (v) { setState(() => _selectedListino = v); if (v != null) _loadPrezzo(v); }, ), const SizedBox(height: 12), Row( children: [ Expanded( child: TextField( controller: _colliCtrl, decoration: const InputDecoration( labelText: 'Colli'), keyboardType: TextInputType.number, onChanged: _onColliChanged, ), ), const SizedBox(width: 12), Expanded( child: TextField( controller: _qtaCtrl, decoration: const InputDecoration( labelText: 'Quantità'), keyboardType: TextInputType.number, onChanged: _onQtaChanged, ), ), ], ), const SizedBox(height: 12), TextField( controller: _prezzoCtrl, decoration: const InputDecoration(labelText: 'Prezzo'), keyboardType: const TextInputType.numberWithOptions(decimal: true), enabled: widget.canEditPrezzo, ), const SizedBox(height: 20), SizedBox( width: double.infinity, child: ElevatedButton( onPressed: _confirm, child: const Text('Aggiungi'), ), ), const SizedBox(height: 8), ], ), ), ); } }