diff --git a/flutter/lib/features/pantry/data/pantry_providers.dart b/flutter/lib/features/pantry/data/pantry_providers.dart index edef313a..a75d6bf8 100644 --- a/flutter/lib/features/pantry/data/pantry_providers.dart +++ b/flutter/lib/features/pantry/data/pantry_providers.dart @@ -4,7 +4,6 @@ import '../../../core/api/api_providers.dart'; import '../../../core/api/guarded_api_call.dart'; import '../../auth/data/auth_providers.dart'; import '../domain/pantry_item.dart'; -import '../domain/pantry_product.dart'; import 'pantry_repository.dart'; final pantryRepositoryProvider = Provider((ref) { @@ -18,11 +17,3 @@ final pantryProvider = FutureProvider>((ref) async { () => ref.read(pantryRepositoryProvider).fetchPantry(token: token), ); }); - -final pantryProductsProvider = FutureProvider>((ref) async { - final token = await ref.watch(authStateProvider.future); - return guardedApiCall( - ref, - () => ref.read(pantryRepositoryProvider).fetchProducts(token: token), - ); -}); \ No newline at end of file diff --git a/flutter/lib/features/pantry/data/pantry_repository.dart b/flutter/lib/features/pantry/data/pantry_repository.dart index 29da414c..9bd03a3d 100644 --- a/flutter/lib/features/pantry/data/pantry_repository.dart +++ b/flutter/lib/features/pantry/data/pantry_repository.dart @@ -2,7 +2,6 @@ import 'package:logging/logging.dart'; import '../../../core/api/api_client.dart'; import '../../../core/api/api_paths.dart'; import '../domain/pantry_item.dart'; -import '../domain/pantry_product.dart'; final _logger = Logger('PantryRepository'); @@ -25,20 +24,6 @@ class PantryRepository { } } - Future> fetchProducts({String? token}) async { - try { - final data = await _api.getJson(ProductApiPaths.mine, token: token); - final list = data as List; - _logger.info('Fetched ${list.length} products'); - return list - .map((e) => PantryProduct.fromJson(e as Map)) - .toList(); - } catch (error) { - _logger.severe('Failed to fetch products: $error'); - rethrow; - } - } - Future createPantryItem( int productId, { String? token, diff --git a/flutter/lib/features/pantry/domain/pantry_item.dart b/flutter/lib/features/pantry/domain/pantry_item.dart index 45f528d2..d8b9e630 100644 --- a/flutter/lib/features/pantry/domain/pantry_item.dart +++ b/flutter/lib/features/pantry/domain/pantry_item.dart @@ -5,6 +5,7 @@ class PantryItem { final String? canonicalName; final String? category; final int? categoryId; + final String? categoryPath; final String? location; const PantryItem({ @@ -14,6 +15,7 @@ class PantryItem { this.canonicalName, this.category, this.categoryId, + this.categoryPath, this.location, }); @@ -24,6 +26,17 @@ class PantryItem { return productName; } + String? get l1CategoryOrNull { + final path = categoryPath?.trim(); + if (path != null && path.isNotEmpty) { + return path.split('>').first.trim(); + } + if (category != null && category!.trim().isNotEmpty) { + return category!.trim(); + } + return null; + } + factory PantryItem.fromJson(Map json) { final product = json['product'] as Map? ?? {}; return PantryItem( @@ -33,7 +46,25 @@ class PantryItem { canonicalName: product['canonicalName']?.toString(), category: product['category']?.toString(), categoryId: (product['categoryId'] as num?)?.toInt(), + categoryPath: _buildCategoryPath(product['categoryRef']), location: json['location']?.toString(), ); } + + static String? _buildCategoryPath(dynamic rawCategoryRef) { + if (rawCategoryRef is! Map) return null; + + final names = []; + dynamic current = rawCategoryRef; + while (current is Map) { + final name = current['name']?.toString().trim(); + if (name != null && name.isNotEmpty) { + names.add(name); + } + current = current['parent']; + } + + if (names.isEmpty) return null; + return names.reversed.join(' > '); + } } \ No newline at end of file diff --git a/flutter/lib/features/pantry/domain/pantry_product.dart b/flutter/lib/features/pantry/domain/pantry_product.dart deleted file mode 100644 index 4b13e074..00000000 --- a/flutter/lib/features/pantry/domain/pantry_product.dart +++ /dev/null @@ -1,55 +0,0 @@ -class PantryProduct { - final int id; - final String name; - final String? canonicalName; - final String? category; - final int? categoryId; - final String? categoryPath; - - const PantryProduct({ - required this.id, - required this.name, - this.canonicalName, - this.category, - this.categoryId, - this.categoryPath, - }); - - String get displayName { - if (canonicalName != null && canonicalName!.trim().isNotEmpty) { - return canonicalName!; - } - return name; - } - - factory PantryProduct.fromJson(Map json) { - final categoryRef = json['categoryRef']; - final path = _buildCategoryPath(categoryRef); - - return PantryProduct( - id: (json['id'] as num).toInt(), - name: (json['name'] ?? '').toString(), - canonicalName: json['canonicalName']?.toString(), - category: json['category']?.toString(), - categoryId: (json['categoryId'] as num?)?.toInt(), - categoryPath: path, - ); - } - - static String? _buildCategoryPath(dynamic rawCategoryRef) { - if (rawCategoryRef is! Map) return null; - - final names = []; - dynamic current = rawCategoryRef; - while (current is Map) { - final name = current['name']?.toString().trim(); - if (name != null && name.isNotEmpty) { - names.insert(0, name); - } - current = current['parent']; - } - - if (names.isEmpty) return null; - return names.join(' > '); - } -} \ No newline at end of file diff --git a/flutter/lib/features/pantry/presentation/pantry_screen.dart b/flutter/lib/features/pantry/presentation/pantry_screen.dart index 2f68f540..17eca142 100644 --- a/flutter/lib/features/pantry/presentation/pantry_screen.dart +++ b/flutter/lib/features/pantry/presentation/pantry_screen.dart @@ -11,7 +11,6 @@ import '../../auth/data/auth_providers.dart'; import '../../inventory/data/inventory_providers.dart'; import '../data/pantry_providers.dart'; import '../domain/pantry_item.dart'; -import '../domain/pantry_product.dart'; final _logger = Logger('PantryScreen'); @@ -214,35 +213,26 @@ class _PantryScreenState extends ConsumerState { } } - String _resolveL1Category(PantryItem item, Map productById) { - final path = productById[item.productId]?.categoryPath?.trim(); - if (path != null && path.isNotEmpty) { - return path.split('>').first.trim(); - } - if (item.category != null && item.category!.trim().isNotEmpty) { - return item.category!.trim(); - } - return context.l10n.pantryOtherCategory; + String _resolveL1Category(PantryItem item) { + return item.l1CategoryOrNull ?? context.l10n.pantryOtherCategory; } @override Widget build(BuildContext context) { final pantryAsync = ref.watch(pantryProvider); - final productsAsync = ref.watch(pantryProductsProvider); - if (pantryAsync.isLoading || productsAsync.isLoading) { + if (pantryAsync.isLoading) { return LoadingStateView(label: context.l10n.pantryLoading); } - if (pantryAsync.hasError || productsAsync.hasError) { - final error = pantryAsync.error ?? productsAsync.error; + if (pantryAsync.hasError) { + final error = pantryAsync.error; _logger.severe('Error loading pantry or products: $error'); return buildCopyableErrorPanel( context: context, message: mapErrorToUserMessage(error ?? 'Okänt fel', context), onRetry: () { ref.invalidate(pantryProvider); - ref.invalidate(pantryProductsProvider); }, title: 'Kunde inte läsa baslagret', ); @@ -250,15 +240,17 @@ class _PantryScreenState extends ConsumerState { final pantryItems = pantryAsync.maybeWhen(data: (d) => d, orElse: () => null) ?? const []; - final products = - productsAsync.maybeWhen(data: (d) => d, orElse: () => null) ?? const []; - final productById = {for (final product in products) product.id: product}; final filteredItems = pantryItems.where((item) { if (_locationFilter.isEmpty) return true; return (item.location ?? '').trim() == _locationFilter; }).toList(); + final l1LowerByItemId = { + for (final item in filteredItems) + item.id: _resolveL1Category(item).toLowerCase(), + }; + filteredItems.sort((a, b) { if (_sort == 'nameDesc') { return b.displayName.toLowerCase().compareTo(a.displayName.toLowerCase()); @@ -271,9 +263,7 @@ class _PantryScreenState extends ConsumerState { return a.displayName.toLowerCase().compareTo(b.displayName.toLowerCase()); } if (_sort == 'l1CategoryAsc') { - final byL1 = _resolveL1Category(a, productById).toLowerCase().compareTo( - _resolveL1Category(b, productById).toLowerCase(), - ); + final byL1 = (l1LowerByItemId[a.id] ?? '').compareTo(l1LowerByItemId[b.id] ?? ''); if (byL1 != 0) return byL1; return a.displayName.toLowerCase().compareTo(b.displayName.toLowerCase()); } @@ -379,7 +369,7 @@ class _PantryScreenState extends ConsumerState { if (index == 0) return filterSection; if (index == 1) return headerSection; final item = filteredItems[index - 2]; - final l1Category = _resolveL1Category(item, productById); + final l1Category = _resolveL1Category(item); return ListTile( title: Text(item.displayName),