From 0103a22558c9c7bd030892f54ee9bc2026c14e77 Mon Sep 17 00:00:00 2001 From: Nils-Johan Gynther Date: Sat, 2 May 2026 19:53:11 +0200 Subject: [PATCH] fix(receipt-import): surface create-product errors and harden response parsing --- .../presentation/receipt_import_tab.dart | 91 ++++++++++++++----- 1 file changed, 70 insertions(+), 21 deletions(-) diff --git a/flutter/lib/features/import/presentation/receipt_import_tab.dart b/flutter/lib/features/import/presentation/receipt_import_tab.dart index 8291e40b..90a7fa9c 100644 --- a/flutter/lib/features/import/presentation/receipt_import_tab.dart +++ b/flutter/lib/features/import/presentation/receipt_import_tab.dart @@ -1,6 +1,7 @@ import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../../core/api/api_exception.dart'; import '../../../core/api/api_paths.dart'; import '../../../core/api/api_providers.dart'; import '../../../core/ui/category_then_product_picker.dart'; @@ -370,7 +371,16 @@ class _EditDialogState extends State<_EditDialog> { _newProductNameCtrl.text.trim(), _newCategoryId!, ); - if (newProduct == null || !mounted) return; + if (newProduct == null || !mounted) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Kunde inte skapa produkten. Försök igen.'), + ), + ); + } + return; + } if (!_localProducts.any((p) => p.id == newProduct.id)) { _localProducts = [..._localProducts, newProduct]; } @@ -379,6 +389,28 @@ class _EditDialogState extends State<_EditDialog> { _productCategoryId = _newCategoryId; _productCategoryPath = _newCategoryPath; _productCategorySource = _newCategorySource ?? CategorySelectionSource.manual; + } on ApiException catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + e.message.trim().isEmpty + ? 'Kunde inte skapa produkten. Försök igen.' + : e.message, + ), + ), + ); + } + return; + } catch (_) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Kunde inte skapa produkten. Försök igen.'), + ), + ); + } + return; } finally { if (mounted) setState(() => _isCreatingProduct = false); } @@ -997,27 +1029,44 @@ class _ReceiptImportTabState extends ConsumerState { categoryTree: _categoryTree, initialEntryMode: initialEntryMode, onCreate: (name, categoryId) async { - try { - final token = await ref.read(authStateProvider.future); - final api = ref.read(apiClientProvider); - final data = await api.postJson( - ProductApiPaths.createPrivate, - body: { - 'name': name.trim(), - 'categoryId': categoryId, - }, - token: token, - ) as Map; - final newProduct = ( - id: data['id'] as int, - name: (data['canonicalName'] ?? data['name']) as String, - categoryId: categoryId, - ); - if (mounted) setState(() => _products = [..._products, newProduct]); - return newProduct; - } catch (_) { - return null; + final token = await ref.read(authStateProvider.future); + final api = ref.read(apiClientProvider); + final raw = await api.postJson( + ProductApiPaths.createPrivate, + body: { + 'name': name.trim(), + 'categoryId': categoryId, + }, + token: token, + ); + + if (raw is! Map) { + throw Exception('Ogiltigt API-svar vid produktskapande.'); } + + final idRaw = raw['id']; + if (idRaw is! num) { + throw Exception('API-svar saknar giltigt produkt-id.'); + } + + final displayNameRaw = raw['canonicalName'] ?? raw['name']; + final displayName = displayNameRaw?.toString().trim(); + if (displayName == null || displayName.isEmpty) { + throw Exception('API-svar saknar produktnamn.'); + } + + final returnedCategoryId = raw['categoryId'] is num + ? (raw['categoryId'] as num).toInt() + : categoryId; + + final newProduct = ( + id: idRaw.toInt(), + name: displayName, + categoryId: returnedCategoryId, + ); + + if (mounted) setState(() => _products = [..._products, newProduct]); + return newProduct; }, ), );