import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:intl/intl.dart'; import 'package:ristoris/core/credit_score_color.dart'; import '../../../database/daos/sconti_dao.dart'; import '../../../providers/auth_provider.dart'; import '../../widgets/cod_desc_dropdown_item.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'; import 'selezione_cliente_page.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; bool _usaListinoCliente = true; 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); _tabController.addListener(() { if (_tabController.index == 1 && _searchResults.isEmpty && _artCtrl.text.isEmpty) { _searchArticles(''); } }); 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 { setState(() => _searching = true); final dao = ref.read(articoliDaoProvider); final order = ref.read(orderProvider).order; final dataOrdine = order?.dataOrdine ?? DateTime.now().toIso8601String(); List results; if (_usaListinoCliente && _cliente != null) { results = await dao.getArticoliPerListinoCliente(_cliente!.idCliente, dataOrdine); } else { results = await dao.getArticoliPerListinoAttivo(dataOrdine); } if (q.isNotEmpty) { final lower = q.toLowerCase(); results = results.where((a) { final cod = (a.codArticolo ?? '').toLowerCase(); final desc = (a.descrizione ?? '').toLowerCase(); final ean = (a.ean ?? '').toLowerCase(); return cod.contains(lower) || desc.contains(lower) || ean.contains(lower); }).toList(); } setState(() { _searchResults = results; _searching = false; }); } void _showArticlePicker(Articolo art) async { final agente = ref.read(authProvider).agente; final canEditPrice = agente?.modificaPrezzi == 1; final order = ref.read(orderProvider).order; final dataOrdine = order?.dataOrdine ?? DateTime.now().toIso8601String(); final listini = await ref .read(listiniDaoProvider) .getListiniPerArticolo(art.idArticolo, dataOrdine); final listiniDao = ref.read(listiniDaoProvider); final scontiDao = ref.read(scontiDaoProvider); if (!context.mounted) return; showModalBottomSheet( context: context, isScrollControlled: true, builder: (_) => _ArticlePickerSheet( articolo: art, listini: listini, listiniDao: listiniDao, scontiDao: scontiDao, dataOrdine: dataOrdine, canEditPrezzo: canEditPrice, idListinoDefault: _selectedListino, onConfirm: ( colli, quantita, prezzoLordo, prezzoNetto, listinoId, { double sconto1 = 0, double sconto2 = 0, double sconto3 = 0, double sconto4 = 0, double sconto5 = 0, }) { _addArticleWithDetails( art: art, idListino: listinoId, colli: colli, quantita: quantita, prezzoLordo: prezzoLordo, prezzoNetto: prezzoNetto, sconto1: sconto1, sconto2: sconto2, sconto3: sconto3, sconto4: sconto4, sconto5: sconto5, ); }, ), ); } Future _addArticleWithDetails({ required Articolo art, required int idListino, required double colli, required double quantita, required double prezzoLordo, required double prezzoNetto, double? sconto1 = 0, double? sconto2 = 0, double? sconto3 = 0, double? sconto4 = 0, double? sconto5 = 0, }) async { final orderState = ref.read(orderProvider); var idOrdine = orderState.order?.idOrdine; if (idOrdine == null || idOrdine == 0) { await ref.read(orderProvider.notifier).saveOrder(); idOrdine = ref.read(orderProvider).order?.idOrdine; if (idOrdine == null || idOrdine == 0) return; } await ref .read(orderProvider.notifier) .addLine( RigaOrdine( idRigaOrdine: 0, idOrdine: idOrdine, idArticolo: art.idArticolo, idListino: idListino, quantita: quantita, prezzoLordo: prezzoLordo, prezzoNetto: prezzoNetto, sconto1: sconto1, sconto2: sconto2, sconto3: sconto3, sconto4: sconto4, sconto5: sconto5, totale: prezzoNetto * quantita, promozione: 0, rigaPromozione: 0, colli: colli, ), ); _artCtrl.clear(); setState(() => _searchResults = []); } Future _selezionaCliente() async { final agente = ref.read(authProvider).agente; if (agente == null) return; final cliente = await Navigator.push( context, MaterialPageRoute( builder: (_) => SelezioneClientePage(idAgente: agente.idAgente), ), ); if (cliente != null) _onClienteSelezionato(cliente); } void _onClienteSelezionato(Cliente cliente) async { ref.read(orderProvider.notifier).setIdCliente(cliente.idCliente); final filialiDao = ref.read(filialiDaoProvider); final filiali = await filialiDao.getDestinationsForCustomer( cliente.idCliente, ); setState(() { _cliente = cliente; _selectedFiliale = null; _selectedPagamento = cliente.idPagamento; _selectedListino = cliente.idListino; _filiali = filiali; _noteClienteCtrl.text = cliente.noteOrdine ?? ''; }); } 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: SafeArea( child: 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) { final cs = theme.colorScheme; final bgColor = creditScoreBg(_cliente?.creditScoreColor); return SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Card( elevation: 0, color: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), side: BorderSide(color: cs.outlineVariant), ), child: Padding( padding: const EdgeInsets.all(12), child: Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Cliente', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: cs.onSurfaceVariant, ), ), const SizedBox(height: 4), if (_cliente != null) Row( children: [ Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4, ), decoration: BoxDecoration( color: bgColor ?? cs.surfaceVariant, borderRadius: BorderRadius.circular(4), ), child: Text( _cliente!.codCliente ?? '', style: TextStyle( fontSize: 12, height: 16 / 12, fontWeight: FontWeight.w500, color: bgColor != null ? Colors.white : cs.onSurfaceVariant, ), ), ), const SizedBox(width: 8), Expanded( child: Text( _cliente!.ragioneSociale ?? '', style: theme.textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w600, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), ], ) else Text( 'Nessun cliente selezionato', style: theme.textTheme.bodyMedium?.copyWith( color: cs.onSurfaceVariant, ), ), ], ), ), IconButton( icon: Icon(Icons.search, color: cs.primary), onPressed: _selezionaCliente, ), ], ), ), ), const SizedBox(height: 16), DropdownButtonFormField( value: _selectedFiliale, decoration: const InputDecoration(labelText: 'Filiale'), items: _filiali .map( (f) => DropdownMenuItem( value: f.idFiliale, child: CodDescDropdownItem( codice: f.codFiliale, descrizione: f.descrizione, ), ), ) .toList(), onChanged: (v) => setState(() => _selectedFiliale = v), ), const SizedBox(height: 16), DropdownButtonFormField( initialValue: _selectedListino, decoration: const InputDecoration(labelText: 'Listino'), items: _listini .map( (l) => DropdownMenuItem( value: l.idListino, child: CodDescDropdownItem( codice: l.codListino, descrizione: l.descrizione, ), ), ) .toList(), onChanged: (v) => setState(() => _selectedListino = v), ), const SizedBox(height: 16), DropdownButtonFormField( initialValue: _selectedPagamento, decoration: const InputDecoration(labelText: 'Pagamento'), items: _pagamenti .map( (p) => DropdownMenuItem( value: p.idPagamento, child: CodDescDropdownItem( codice: p.codPagamento, descrizione: 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: Row( children: [ Expanded( 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, ), ), const SizedBox(width: 8), FilterChip( label: const Text('Listino cliente'), selected: _usaListinoCliente, onSelected: (v) { setState(() => _usaListinoCliente = v); _searchArticles(_artCtrl.text); }, ), ], ), ), 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 ScontiDao scontiDao; final String dataOrdine; final bool canEditPrezzo; final int? idListinoDefault; final void Function( double colli, double quantita, double prezzoLordo, double prezzoNetto, int listinoId, { double sconto1, double sconto2, double sconto3, double sconto4, double sconto5, }) onConfirm; const _ArticlePickerSheet({ required this.articolo, required this.listini, required this.listiniDao, required this.scontiDao, required this.dataOrdine, 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(); final _sconto1Ctrl = TextEditingController(); final _sconto2Ctrl = TextEditingController(); final _sconto3Ctrl = TextEditingController(); final _sconto4Ctrl = TextEditingController(); final _sconto5Ctrl = TextEditingController(); int? _selectedListino; double _prezzo = 0; double _prezzoNetto = 0; double _sconto1 = 0; double _sconto2 = 0; double _sconto3 = 0; double _sconto4 = 0; double _sconto5 = 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(); _sconto1Ctrl.dispose(); _sconto2Ctrl.dispose(); _sconto3Ctrl.dispose(); _sconto4Ctrl.dispose(); _sconto5Ctrl.dispose(); super.dispose(); } Future _loadPrezzo(int idListino) async { final lp = await widget.listiniDao.findListinoArticolo( idListino, widget.articolo.idArticolo, ); if (lp != null) { if (lp.prezzo != null) { setState(() => _prezzo = lp.prezzo!); _prezzoCtrl.text = _prezzo.toStringAsFixed(2); } if (lp.idSconto != null) { final sconto = await widget.scontiDao.findById(lp.idSconto!); setState(() { _sconto1 = sconto?.sconto1 ?? 0; _sconto2 = sconto?.sconto2 ?? 0; _sconto3 = sconto?.sconto3 ?? 0; _sconto4 = sconto?.sconto4 ?? 0; _sconto5 = sconto?.sconto5 ?? 0; }); _sconto1Ctrl.text = _sconto1 > 0 ? _sconto1.toStringAsFixed(1) : ''; _sconto2Ctrl.text = _sconto2 > 0 ? _sconto2.toStringAsFixed(1) : ''; _sconto3Ctrl.text = _sconto3 > 0 ? _sconto3.toStringAsFixed(1) : ''; _sconto4Ctrl.text = _sconto4 > 0 ? _sconto4.toStringAsFixed(1) : ''; _sconto5Ctrl.text = _sconto5 > 0 ? _sconto5.toStringAsFixed(1) : ''; } else { _resetSconti(); } _calcolaPrezzoNetto(); } } void _resetSconti() { setState(() { _sconto1 = 0; _sconto2 = 0; _sconto3 = 0; _sconto4 = 0; _sconto5 = 0; }); _sconto1Ctrl.clear(); _sconto2Ctrl.clear(); _sconto3Ctrl.clear(); _sconto4Ctrl.clear(); _sconto5Ctrl.clear(); } void _calcolaPrezzoNetto() { double n = _prezzo; n *= (100 - _sconto1) / 100; n *= (100 - _sconto2) / 100; n *= (100 - _sconto3) / 100; n *= (100 - _sconto4) / 100; n *= (100 - _sconto5) / 100; setState(() => _prezzoNetto = n); } void _onPrezzoChanged(String v) { setState(() => _prezzo = double.tryParse(v) ?? 0); _calcolaPrezzoNetto(); } void _onScontoChanged(int index, String v) { final val = double.tryParse(v) ?? 0; setState(() { switch (index) { case 1: _sconto1 = val; case 2: _sconto2 = val; case 3: _sconto3 = val; case 4: _sconto4 = val; case 5: _sconto5 = val; } }); _calcolaPrezzoNetto(); } 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 prezzoLordo = double.tryParse(_prezzoCtrl.text) ?? 0; final listinoId = _selectedListino ?? 0; widget.onConfirm( colli, qta, prezzoLordo, _prezzoNetto, listinoId, sconto1: _sconto1, sconto2: _sconto2, sconto3: _sconto3, sconto4: _sconto4, sconto5: _sconto5, ); 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: CodDescDropdownItem( codice: l.codListino, descrizione: l.descrizione, ), ), ) .toList(), onChanged: (v) { setState(() => _selectedListino = v); if (v != null) _loadPrezzo(v); }, ), const SizedBox(height: 12), TextField( controller: _prezzoCtrl, decoration: const InputDecoration(labelText: 'Prezzo lordo'), keyboardType: const TextInputType.numberWithOptions( decimal: true, ), enabled: widget.canEditPrezzo, onChanged: _onPrezzoChanged, ), const SizedBox(height: 12), Row( children: [ Expanded( child: TextField( controller: _sconto1Ctrl, decoration: const InputDecoration(labelText: 'Sconto 1 %'), keyboardType: const TextInputType.numberWithOptions( decimal: true, ), onChanged: (v) => _onScontoChanged(1, v), ), ), const SizedBox(width: 12), Expanded( child: TextField( controller: _sconto2Ctrl, decoration: const InputDecoration(labelText: 'Sconto 2 %'), keyboardType: const TextInputType.numberWithOptions( decimal: true, ), onChanged: (v) => _onScontoChanged(2, v), ), ), ], ), const SizedBox(height: 12), Row( children: [ Expanded( child: TextField( controller: _sconto3Ctrl, decoration: const InputDecoration(labelText: 'Sconto 3 %'), keyboardType: const TextInputType.numberWithOptions( decimal: true, ), onChanged: (v) => _onScontoChanged(3, v), ), ), const SizedBox(width: 12), Expanded( child: TextField( controller: _sconto4Ctrl, decoration: const InputDecoration(labelText: 'Sconto 4 %'), keyboardType: const TextInputType.numberWithOptions( decimal: true, ), onChanged: (v) => _onScontoChanged(4, v), ), ), ], ), const SizedBox(height: 12), SizedBox( width: 140, child: TextField( controller: _sconto5Ctrl, decoration: const InputDecoration(labelText: 'Sconto 5 %'), keyboardType: const TextInputType.numberWithOptions( decimal: true, ), onChanged: (v) => _onScontoChanged(5, v), ), ), const SizedBox(height: 16), Row( children: [ Text('Prezzo netto: ', style: theme.textTheme.bodyLarge), Text( _prezzoNetto.toStringAsFixed(2), style: theme.textTheme.bodyLarge?.copyWith( fontWeight: FontWeight.bold, ), ), ], ), 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: 20), SizedBox( width: double.infinity, child: ElevatedButton( onPressed: _confirm, child: const Text('Aggiungi'), ), ), const SizedBox(height: 8), ], ), ), ); } }