From 53afcc98a9818947c5f9eecfa7af13a583bbc993 Mon Sep 17 00:00:00 2001 From: Nils-Johan Gynther Date: Sat, 25 Apr 2026 07:47:35 +0200 Subject: [PATCH] feat: enhance product picker and improve error handling in inventory screen --- flutter/lib/core/ui/product_picker_field.dart | 24 ++++++++++++++----- .../presentation/create_inventory_screen.dart | 16 ++++++++++--- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/flutter/lib/core/ui/product_picker_field.dart b/flutter/lib/core/ui/product_picker_field.dart index a0b7396e..5216c196 100644 --- a/flutter/lib/core/ui/product_picker_field.dart +++ b/flutter/lib/core/ui/product_picker_field.dart @@ -11,6 +11,8 @@ typedef ProductOption = ({int id, String name}); /// Replaces a long [DropdownButtonFormField] when the product list is large. /// Works both inside and outside a [Form]. class ProductPickerField extends StatelessWidget { + static const _clearSelectionToken = '__clear_selection__'; + final List products; /// Currently selected product id, or null if nothing is selected. @@ -51,7 +53,7 @@ class ProductPickerField extends StatelessWidget { orElse: () => null, ); - final interactive = enabled && !isLoading; + final interactive = enabled && !isLoading && products.isNotEmpty; return MouseRegion( cursor: interactive ? SystemMouseCursors.click : MouseCursor.defer, @@ -73,7 +75,11 @@ class ProductPickerField extends StatelessWidget { ), ) : selected == null - ? const Icon(Icons.search) + ? Text( + products.isEmpty + ? 'Inga produkter tillgängliga' + : 'Tryck för att välja produkt', + ) : Text(selected.name), ), ), @@ -81,7 +87,7 @@ class ProductPickerField extends StatelessWidget { } Future _openPicker(BuildContext context) async { - final selectedId = await showModalBottomSheet( + final result = await showModalBottomSheet( context: context, isScrollControlled: true, useSafeArea: true, @@ -114,7 +120,8 @@ class ProductPickerField extends StatelessWidget { ), ), TextButton.icon( - onPressed: () => Navigator.pop(context, null), + onPressed: () => + Navigator.pop(context, _clearSelectionToken), icon: const Icon(Icons.clear), label: const Text('Rensa'), ), @@ -169,10 +176,15 @@ class ProductPickerField extends StatelessWidget { ); if (!context.mounted) return; - if (selectedId == null) { + if (result == null) { + return; + } + if (result == _clearSelectionToken) { onChanged?.call(null); return; } - onChanged?.call(selectedId); + if (result is int) { + onChanged?.call(result); + } } } diff --git a/flutter/lib/features/inventory/presentation/create_inventory_screen.dart b/flutter/lib/features/inventory/presentation/create_inventory_screen.dart index 375c4f63..ed4fd475 100644 --- a/flutter/lib/features/inventory/presentation/create_inventory_screen.dart +++ b/flutter/lib/features/inventory/presentation/create_inventory_screen.dart @@ -57,16 +57,26 @@ class _CreateInventoryScreenState final token = await ref.read(authStateProvider.future); final api = ref.read(apiClientProvider); final data = await api.getJson(ProductApiPaths.list, token: token); + final list = data is List + ? data + : (data is Map && data['items'] is List) + ? data['items'] as List + : const []; if (mounted) { setState(() { - _products = (data as List) + _products = list .map((e) => e as Map) .toList(); _loadingProducts = false; }); } - } catch (_) { + } catch (e) { if (mounted) setState(() => _loadingProducts = false); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(mapErrorToUserMessage(e, context))), + ); + } } } @@ -147,7 +157,7 @@ class _CreateInventoryScreenState final productOptions = sortedProducts .map( (p) => ( - id: p['id'] as int, + id: (p['id'] as num).toInt(), name: (p['canonicalName'] ?? p['name'] ?? '').toString(), ), )