selezione_articolo_page.dart 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745
  1. import 'package:flutter/material.dart';
  2. import 'package:flutter_riverpod/flutter_riverpod.dart';
  3. import 'package:intl/intl.dart';
  4. import 'package:ristoris/database/daos/articoli_dao.dart';
  5. import '../../../database/daos/sconti_dao.dart';
  6. import '../../../providers/auth_provider.dart';
  7. import '../../widgets/cod_desc_dropdown_item.dart';
  8. import '../../../providers/database_provider.dart';
  9. import '../../../database/database.dart';
  10. import '../../widgets/article_image.dart';
  11. enum ArticleFilterMode { tutti, offertaCliente, acquistatiInPassato }
  12. class ArticleSelectionResult {
  13. final Articolo articolo;
  14. final int idListino;
  15. final double colli;
  16. final double quantita;
  17. final double prezzoLordo;
  18. final double prezzoNetto;
  19. final double sconto1;
  20. final double sconto2;
  21. final double sconto3;
  22. final double sconto4;
  23. final double sconto5;
  24. const ArticleSelectionResult({
  25. required this.articolo,
  26. required this.idListino,
  27. required this.colli,
  28. required this.quantita,
  29. required this.prezzoLordo,
  30. required this.prezzoNetto,
  31. this.sconto1 = 0,
  32. this.sconto2 = 0,
  33. this.sconto3 = 0,
  34. this.sconto4 = 0,
  35. this.sconto5 = 0,
  36. });
  37. }
  38. class SelezioneArticoloPage extends ConsumerStatefulWidget {
  39. final int idCliente;
  40. final String dataOrdine;
  41. final int? idListinoDefault;
  42. const SelezioneArticoloPage({
  43. super.key,
  44. required this.idCliente,
  45. required this.dataOrdine,
  46. this.idListinoDefault,
  47. });
  48. @override
  49. ConsumerState<SelezioneArticoloPage> createState() =>
  50. _SelezioneArticoloPageState();
  51. }
  52. class _SelezioneArticoloPageState extends ConsumerState<SelezioneArticoloPage> {
  53. final _artCtrl = TextEditingController();
  54. List<ArticoliConPromoData> _searchResults = [];
  55. bool _searching = false;
  56. ArticleFilterMode _filterMode = ArticleFilterMode.offertaCliente;
  57. @override
  58. void initState() {
  59. super.initState();
  60. _searchArticles('');
  61. }
  62. @override
  63. void dispose() {
  64. _artCtrl.dispose();
  65. super.dispose();
  66. }
  67. Future<void> _searchArticles(String q) async {
  68. setState(() => _searching = true);
  69. final articoliDao = ref.read(articoliDaoProvider);
  70. final clientiDao = ref.read(clientiDaoProvider);
  71. List<ArticoliConPromoData> results;
  72. switch (_filterMode) {
  73. case ArticleFilterMode.tutti:
  74. results =
  75. await articoliDao.getActiveItemsWithPromo(q, widget.idCliente);
  76. case ArticleFilterMode.offertaCliente:
  77. results = await clientiDao.getFavoriteItems(
  78. widget.idCliente,
  79. widget.dataOrdine,
  80. q,
  81. );
  82. case ArticleFilterMode.acquistatiInPassato:
  83. final dataOrdineParsed = DateTime.parse(widget.dataOrdine);
  84. final dataDa180 = dataOrdineParsed.subtract(const Duration(days: 180));
  85. final dataDa180Str = DateFormat('yyyy-MM-dd').format(dataDa180);
  86. results = await clientiDao.getAlreadySoldItems(
  87. 0,
  88. widget.idCliente,
  89. dataDa180Str,
  90. q,
  91. );
  92. }
  93. setState(() {
  94. _searchResults = results;
  95. _searching = false;
  96. });
  97. }
  98. void _showArticlePicker(int idArticolo) async {
  99. final art = await ref.read(articoliDaoProvider).getById(idArticolo);
  100. if (art == null || !context.mounted) return;
  101. final agente = ref.read(authProvider).agente;
  102. final canEditPrice = agente?.modificaPrezzi == 1;
  103. final articoliDao = ref.read(articoliDaoProvider);
  104. final listini = await articoliDao.getListiniArticoloValidi(
  105. art.idArticolo, widget.idCliente, widget.dataOrdine);
  106. final listiniCliente = await articoliDao.getListiniClienteValidi(
  107. widget.idCliente, art.idArticolo, widget.dataOrdine);
  108. final listinoCliente =
  109. listiniCliente.isNotEmpty ? listiniCliente.first : null;
  110. final scontiDao = ref.read(scontiDaoProvider);
  111. if (!context.mounted) return;
  112. final sheetResult = await showModalBottomSheet<ArticleSelectionResult>(
  113. context: context,
  114. isScrollControlled: true,
  115. builder: (_) => _ArticlePickerSheet(
  116. articolo: art,
  117. listini: listini,
  118. listinoCliente: listinoCliente,
  119. articoliDao: articoliDao,
  120. scontiDao: scontiDao,
  121. dataOrdine: widget.dataOrdine,
  122. canEditPrezzo: canEditPrice,
  123. idListinoDefault: widget.idListinoDefault,
  124. onConfirm: (
  125. colli,
  126. quantita,
  127. prezzoLordo,
  128. prezzoNetto,
  129. listinoId, {
  130. double sconto1 = 0,
  131. double sconto2 = 0,
  132. double sconto3 = 0,
  133. double sconto4 = 0,
  134. double sconto5 = 0,
  135. }) {
  136. Navigator.pop(
  137. context,
  138. ArticleSelectionResult(
  139. articolo: art,
  140. idListino: listinoId,
  141. colli: colli,
  142. quantita: quantita,
  143. prezzoLordo: prezzoLordo,
  144. prezzoNetto: prezzoNetto,
  145. sconto1: sconto1,
  146. sconto2: sconto2,
  147. sconto3: sconto3,
  148. sconto4: sconto4,
  149. sconto5: sconto5,
  150. ),
  151. );
  152. },
  153. ),
  154. );
  155. if (sheetResult != null && context.mounted) {
  156. Navigator.pop(context, sheetResult);
  157. }
  158. }
  159. @override
  160. Widget build(BuildContext context) {
  161. final theme = Theme.of(context);
  162. final currencyFormat = NumberFormat.currency(locale: 'it', symbol: '€');
  163. final dateFormat = DateFormat('dd/MM/yyyy');
  164. return Scaffold(
  165. appBar: AppBar(
  166. title: const Text('Selezione articolo'),
  167. ),
  168. body: SafeArea(
  169. child: Column(
  170. children: [
  171. Padding(
  172. padding: const EdgeInsets.fromLTRB(12, 12, 12, 0),
  173. child: TextField(
  174. controller: _artCtrl,
  175. decoration: InputDecoration(
  176. hintText: 'Cerca articolo...',
  177. prefixIcon: const Icon(Icons.search),
  178. suffixIcon: _searching
  179. ? const SizedBox(
  180. width: 20,
  181. height: 20,
  182. child: CircularProgressIndicator(strokeWidth: 2),
  183. )
  184. : null,
  185. ),
  186. onChanged: _searchArticles,
  187. ),
  188. ),
  189. Padding(
  190. padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
  191. child: SegmentedButton<ArticleFilterMode>(
  192. segments: const [
  193. ButtonSegment(
  194. value: ArticleFilterMode.offertaCliente,
  195. label: Text('Offerta cliente'),
  196. ),
  197. ButtonSegment(
  198. value: ArticleFilterMode.tutti,
  199. label: Text('Tutti'),
  200. ),
  201. ButtonSegment(
  202. value: ArticleFilterMode.acquistatiInPassato,
  203. label: Text('Acquistati'),
  204. ),
  205. ],
  206. selected: {_filterMode},
  207. onSelectionChanged: (selected) {
  208. setState(() => _filterMode = selected.first);
  209. _searchArticles(_artCtrl.text);
  210. },
  211. ),
  212. ),
  213. Expanded(
  214. child: _searchResults.isEmpty
  215. ? Center(
  216. child: Text(
  217. _searching ? 'Ricerca...' : 'Nessun articolo trovato',
  218. ),
  219. )
  220. : ListView.builder(
  221. itemCount: _searchResults.length,
  222. itemBuilder: (_, i) {
  223. final item = _searchResults[i];
  224. final hasLastPurchase = item.ultimoPrezzo != null &&
  225. item.lastDateSold != null;
  226. return Column(
  227. mainAxisSize: MainAxisSize.min,
  228. children: [
  229. ListTile(
  230. leading: ArticleImage(
  231. idArticolo: item.idArticolo,
  232. codArticolo: item.codArticolo,
  233. ),
  234. title: Text(
  235. '${item.codArticolo} ${item.descrizione}'),
  236. // subtitle: Text(item.codArticolo ?? ''),
  237. trailing: Row(
  238. mainAxisSize: MainAxisSize.min,
  239. children: [
  240. if (item.inPromo == 1)
  241. const Icon(
  242. Icons.star,
  243. color: Colors.amber,
  244. size: 20,
  245. ),
  246. TextButton(
  247. onPressed: () =>
  248. _showArticlePicker(item.idArticolo),
  249. child: const Text('Aggiungi'),
  250. ),
  251. ],
  252. ),
  253. ),
  254. if (hasLastPurchase)
  255. Padding(
  256. padding: const EdgeInsets.only(
  257. left: 72,
  258. right: 16,
  259. bottom: 8,
  260. ),
  261. child: Row(
  262. children: [
  263. Text(
  264. 'Ultimo acquisto: ',
  265. style: theme.textTheme.bodySmall,
  266. ),
  267. Text(
  268. currencyFormat.format(item.ultimoPrezzo),
  269. style: theme.textTheme.bodySmall
  270. ?.copyWith(
  271. fontWeight: FontWeight.bold),
  272. ),
  273. const SizedBox(width: 8),
  274. Text(
  275. dateFormat.format(
  276. DateTime.parse(item.lastDateSold!),
  277. ),
  278. style: theme.textTheme.bodySmall,
  279. ),
  280. ],
  281. ),
  282. ),
  283. ],
  284. );
  285. },
  286. ),
  287. ),
  288. ],
  289. ),
  290. ),
  291. );
  292. }
  293. }
  294. const int _kListinoClienteSentinel = -1;
  295. class _ArticlePickerSheet extends StatefulWidget {
  296. final Articolo articolo;
  297. final List<Listino> listini;
  298. final ListinoCliente? listinoCliente;
  299. final ArticoliDao articoliDao;
  300. final ScontiDao scontiDao;
  301. final String dataOrdine;
  302. final bool canEditPrezzo;
  303. final int? idListinoDefault;
  304. final void Function(
  305. double colli,
  306. double quantita,
  307. double prezzoLordo,
  308. double prezzoNetto,
  309. int listinoId, {
  310. double sconto1,
  311. double sconto2,
  312. double sconto3,
  313. double sconto4,
  314. double sconto5,
  315. }) onConfirm;
  316. const _ArticlePickerSheet({
  317. required this.articolo,
  318. required this.listini,
  319. this.listinoCliente,
  320. required this.articoliDao,
  321. required this.scontiDao,
  322. required this.dataOrdine,
  323. required this.canEditPrezzo,
  324. this.idListinoDefault,
  325. required this.onConfirm,
  326. });
  327. @override
  328. State<_ArticlePickerSheet> createState() => _ArticlePickerSheetState();
  329. }
  330. class _ArticlePickerSheetState extends State<_ArticlePickerSheet> {
  331. final _colliCtrl = TextEditingController();
  332. final _qtaCtrl = TextEditingController();
  333. final _prezzoCtrl = TextEditingController();
  334. final _sconto1Ctrl = TextEditingController();
  335. final _sconto2Ctrl = TextEditingController();
  336. final _sconto3Ctrl = TextEditingController();
  337. final _sconto4Ctrl = TextEditingController();
  338. final _sconto5Ctrl = TextEditingController();
  339. int? _selectedListino;
  340. double _prezzo = 0;
  341. double _prezzoNetto = 0;
  342. double _sconto1 = 0;
  343. double _sconto2 = 0;
  344. double _sconto3 = 0;
  345. double _sconto4 = 0;
  346. double _sconto5 = 0;
  347. bool _updatingColli = false;
  348. bool _updatingQta = false;
  349. @override
  350. void initState() {
  351. super.initState();
  352. if (widget.listinoCliente != null) {
  353. _selectedListino = _kListinoClienteSentinel;
  354. } else {
  355. _selectedListino = widget.idListinoDefault;
  356. if (_selectedListino != null &&
  357. !widget.listini.any((l) => l.idListino == _selectedListino)) {
  358. _selectedListino = null;
  359. }
  360. }
  361. if (_selectedListino != null) {
  362. _loadPrezzo(_selectedListino!, widget.articolo.idArticolo);
  363. }
  364. }
  365. @override
  366. void dispose() {
  367. _colliCtrl.dispose();
  368. _qtaCtrl.dispose();
  369. _prezzoCtrl.dispose();
  370. _sconto1Ctrl.dispose();
  371. _sconto2Ctrl.dispose();
  372. _sconto3Ctrl.dispose();
  373. _sconto4Ctrl.dispose();
  374. _sconto5Ctrl.dispose();
  375. super.dispose();
  376. }
  377. void _loadPrezzo(int idListino, int idaArticolo) async {
  378. if (idListino == _kListinoClienteSentinel &&
  379. widget.listinoCliente != null) {
  380. final lc = widget.listinoCliente!;
  381. if (lc.prezzo != null) {
  382. setState(() => _prezzo = lc.prezzo!);
  383. _prezzoCtrl.text = _prezzo.toStringAsFixed(2);
  384. }
  385. if (lc.idSconto != null) {
  386. _loadSconti(lc.idSconto!);
  387. } else {
  388. _resetSconti();
  389. }
  390. _calcolaPrezzoNetto();
  391. return;
  392. }
  393. final lp = await widget.articoliDao
  394. .getListPrice(idListino, idaArticolo, widget.dataOrdine);
  395. /*
  396. final lp = widget.listiniArticolo.cast<ListinoArticolo?>().firstWhere(
  397. (la) => la!.idListino == idListino,
  398. orElse: () => null,
  399. );
  400. */
  401. if (lp != null) {
  402. if (lp.prezzo != null) {
  403. setState(() => _prezzo = lp.prezzo!);
  404. _prezzoCtrl.text = _prezzo.toStringAsFixed(2);
  405. }
  406. if (lp.idSconto != null) {
  407. _loadSconti(lp.idSconto!);
  408. } else {
  409. _resetSconti();
  410. }
  411. _calcolaPrezzoNetto();
  412. }
  413. }
  414. Future<void> _loadSconti(int idSconto) async {
  415. final sconto = await widget.scontiDao.findById(idSconto);
  416. setState(() {
  417. _sconto1 = sconto?.sconto1 ?? 0;
  418. _sconto2 = sconto?.sconto2 ?? 0;
  419. _sconto3 = sconto?.sconto3 ?? 0;
  420. _sconto4 = sconto?.sconto4 ?? 0;
  421. _sconto5 = sconto?.sconto5 ?? 0;
  422. });
  423. _sconto1Ctrl.text = _sconto1 > 0 ? _sconto1.toStringAsFixed(1) : '';
  424. _sconto2Ctrl.text = _sconto2 > 0 ? _sconto2.toStringAsFixed(1) : '';
  425. _sconto3Ctrl.text = _sconto3 > 0 ? _sconto3.toStringAsFixed(1) : '';
  426. _sconto4Ctrl.text = _sconto4 > 0 ? _sconto4.toStringAsFixed(1) : '';
  427. _sconto5Ctrl.text = _sconto5 > 0 ? _sconto5.toStringAsFixed(1) : '';
  428. }
  429. void _resetSconti() {
  430. setState(() {
  431. _sconto1 = 0;
  432. _sconto2 = 0;
  433. _sconto3 = 0;
  434. _sconto4 = 0;
  435. _sconto5 = 0;
  436. });
  437. _sconto1Ctrl.clear();
  438. _sconto2Ctrl.clear();
  439. _sconto3Ctrl.clear();
  440. _sconto4Ctrl.clear();
  441. _sconto5Ctrl.clear();
  442. }
  443. void _calcolaPrezzoNetto() {
  444. double n = _prezzo;
  445. n *= (100 - _sconto1) / 100;
  446. n *= (100 - _sconto2) / 100;
  447. n *= (100 - _sconto3) / 100;
  448. n *= (100 - _sconto4) / 100;
  449. n *= (100 - _sconto5) / 100;
  450. setState(() => _prezzoNetto = n);
  451. }
  452. void _onPrezzoChanged(String v) {
  453. setState(() => _prezzo = double.tryParse(v) ?? 0);
  454. _calcolaPrezzoNetto();
  455. }
  456. void _onScontoChanged(int index, String v) {
  457. final val = double.tryParse(v) ?? 0;
  458. setState(() {
  459. switch (index) {
  460. case 1:
  461. _sconto1 = val;
  462. case 2:
  463. _sconto2 = val;
  464. case 3:
  465. _sconto3 = val;
  466. case 4:
  467. _sconto4 = val;
  468. case 5:
  469. _sconto5 = val;
  470. }
  471. });
  472. _calcolaPrezzoNetto();
  473. }
  474. void _onColliChanged(String v) {
  475. if (_updatingColli) return;
  476. _updatingQta = true;
  477. final colli = double.tryParse(v) ?? 0;
  478. final pezzi = widget.articolo.pezziConfezione ?? 0;
  479. if (colli > 0 && pezzi > 0) {
  480. _qtaCtrl.text = (colli * pezzi).toStringAsFixed(1);
  481. } else if (colli == 0) {
  482. _qtaCtrl.text = '';
  483. }
  484. _updatingQta = false;
  485. }
  486. void _onQtaChanged(String v) {
  487. if (_updatingQta) return;
  488. _updatingColli = true;
  489. final qta = double.tryParse(v) ?? 0;
  490. if (qta > 0) {
  491. _colliCtrl.text = '0';
  492. } else if (qta == 0) {
  493. _colliCtrl.text = '';
  494. }
  495. _updatingColli = false;
  496. }
  497. void _confirm() {
  498. final colli = double.tryParse(_colliCtrl.text) ?? 0;
  499. final qta = double.tryParse(_qtaCtrl.text) ?? 0;
  500. final prezzoLordo = double.tryParse(_prezzoCtrl.text) ?? 0;
  501. final listinoId = _selectedListino == _kListinoClienteSentinel
  502. ? (widget.listinoCliente?.idListino ?? 0)
  503. : (_selectedListino ?? 0);
  504. widget.onConfirm(
  505. colli,
  506. qta,
  507. prezzoLordo,
  508. _prezzoNetto,
  509. listinoId,
  510. sconto1: _sconto1,
  511. sconto2: _sconto2,
  512. sconto3: _sconto3,
  513. sconto4: _sconto4,
  514. sconto5: _sconto5,
  515. );
  516. }
  517. @override
  518. Widget build(BuildContext context) {
  519. final theme = Theme.of(context);
  520. return Padding(
  521. padding: EdgeInsets.only(
  522. bottom: MediaQuery.of(context).viewInsets.bottom,
  523. left: 16,
  524. right: 16,
  525. top: 16,
  526. ),
  527. child: SingleChildScrollView(
  528. child: Column(
  529. mainAxisSize: MainAxisSize.min,
  530. crossAxisAlignment: CrossAxisAlignment.start,
  531. children: [
  532. Text(
  533. widget.articolo.descrizione ?? '',
  534. style: theme.textTheme.titleMedium,
  535. ),
  536. const SizedBox(height: 4),
  537. Text(
  538. widget.articolo.codArticolo ?? '',
  539. style: theme.textTheme.bodySmall,
  540. ),
  541. if (widget.articolo.pezziConfezione != null)
  542. Text(
  543. 'Pezzi/confezione: ${widget.articolo.pezziConfezione}',
  544. style: theme.textTheme.bodySmall,
  545. ),
  546. const SizedBox(height: 16),
  547. DropdownButtonFormField<int>(
  548. initialValue: _selectedListino,
  549. decoration: const InputDecoration(labelText: 'Listino'),
  550. isExpanded: true,
  551. items: [
  552. if (widget.listinoCliente != null)
  553. DropdownMenuItem(
  554. value: _kListinoClienteSentinel,
  555. child: const Text(
  556. 'Listino Cliente',
  557. style: TextStyle(
  558. fontWeight: FontWeight.bold,
  559. color: Colors.blue,
  560. ),
  561. ),
  562. ),
  563. ...widget.listini.map((l) {
  564. return DropdownMenuItem(
  565. value: l.idListino,
  566. child: CodDescDropdownItem(
  567. codice: l.codListino,
  568. descrizione: l.descrizione,
  569. ),
  570. );
  571. }),
  572. ],
  573. onChanged: (v) {
  574. setState(() => _selectedListino = v);
  575. if (v != null) _loadPrezzo(v, widget.articolo.idArticolo);
  576. },
  577. ),
  578. const SizedBox(height: 12),
  579. TextField(
  580. controller: _prezzoCtrl,
  581. decoration: const InputDecoration(labelText: 'Prezzo lordo'),
  582. keyboardType: const TextInputType.numberWithOptions(
  583. decimal: true,
  584. ),
  585. enabled: widget.canEditPrezzo,
  586. onChanged: _onPrezzoChanged,
  587. ),
  588. const SizedBox(height: 12),
  589. Row(
  590. children: [
  591. Expanded(
  592. child: TextField(
  593. controller: _sconto1Ctrl,
  594. decoration: const InputDecoration(labelText: 'Sconto 1 %'),
  595. keyboardType: const TextInputType.numberWithOptions(
  596. decimal: true,
  597. ),
  598. onChanged: (v) => _onScontoChanged(1, v),
  599. ),
  600. ),
  601. const SizedBox(width: 12),
  602. Expanded(
  603. child: TextField(
  604. controller: _sconto2Ctrl,
  605. decoration: const InputDecoration(labelText: 'Sconto 2 %'),
  606. keyboardType: const TextInputType.numberWithOptions(
  607. decimal: true,
  608. ),
  609. onChanged: (v) => _onScontoChanged(2, v),
  610. ),
  611. ),
  612. ],
  613. ),
  614. const SizedBox(height: 12),
  615. Row(
  616. children: [
  617. Expanded(
  618. child: TextField(
  619. controller: _sconto3Ctrl,
  620. decoration: const InputDecoration(labelText: 'Sconto 3 %'),
  621. keyboardType: const TextInputType.numberWithOptions(
  622. decimal: true,
  623. ),
  624. onChanged: (v) => _onScontoChanged(3, v),
  625. ),
  626. ),
  627. const SizedBox(width: 12),
  628. Expanded(
  629. child: TextField(
  630. controller: _sconto4Ctrl,
  631. decoration: const InputDecoration(labelText: 'Sconto 4 %'),
  632. keyboardType: const TextInputType.numberWithOptions(
  633. decimal: true,
  634. ),
  635. onChanged: (v) => _onScontoChanged(4, v),
  636. ),
  637. ),
  638. ],
  639. ),
  640. const SizedBox(height: 12),
  641. SizedBox(
  642. width: 140,
  643. child: TextField(
  644. controller: _sconto5Ctrl,
  645. decoration: const InputDecoration(labelText: 'Sconto 5 %'),
  646. keyboardType: const TextInputType.numberWithOptions(
  647. decimal: true,
  648. ),
  649. onChanged: (v) => _onScontoChanged(5, v),
  650. ),
  651. ),
  652. const SizedBox(height: 16),
  653. Row(
  654. children: [
  655. Text('Prezzo netto: ', style: theme.textTheme.bodyLarge),
  656. Text(
  657. _prezzoNetto.toStringAsFixed(2),
  658. style: theme.textTheme.bodyLarge?.copyWith(
  659. fontWeight: FontWeight.bold,
  660. ),
  661. ),
  662. ],
  663. ),
  664. const SizedBox(height: 12),
  665. Row(
  666. children: [
  667. Expanded(
  668. child: TextField(
  669. controller: _colliCtrl,
  670. decoration: const InputDecoration(labelText: 'Colli'),
  671. keyboardType: TextInputType.number,
  672. onChanged: _onColliChanged,
  673. ),
  674. ),
  675. const SizedBox(width: 12),
  676. Expanded(
  677. child: TextField(
  678. controller: _qtaCtrl,
  679. decoration: const InputDecoration(labelText: 'Quantità'),
  680. keyboardType: TextInputType.number,
  681. onChanged: _onQtaChanged,
  682. ),
  683. ),
  684. ],
  685. ),
  686. const SizedBox(height: 20),
  687. SizedBox(
  688. width: double.infinity,
  689. child: ElevatedButton(
  690. onPressed: _confirm,
  691. child: const Text('Aggiungi'),
  692. ),
  693. ),
  694. const SizedBox(height: 8),
  695. ],
  696. ),
  697. ),
  698. );
  699. }
  700. }