feat(shopping-list): add shopping list feature with flyer integration
Test Suite / backend-pr-quick (push) Has been skipped
Test Suite / quick-import-pr-quick (push) Has been skipped
Test Suite / backend-full (push) Successful in 5m8s
Test Suite / flutter-quality (push) Failing after 1m41s

This commit introduces a comprehensive shopping list feature with the following key changes:

Backend:
- Added ShoppingListItem model with relations to User, Product, and Category
- Added new fields to FlyerSession for source file metadata
- Added categoryId field to FlyerItem model
- Implemented session source file retrieval endpoint
- Added endpoint for updating flyer session items with category assignment
- Added endpoint for planning flyer selections to shopping list
- Implemented backfillCategoriesMine for AI-assisted category assignment
- Added ShoppingListModule and integrated with FlyerSelectionModule

Frontend:
- Added ShoppingListScreen and navigation route
- Implemented API paths and client methods for shopping list operations
- Added category tree loading for shopping list item creation
- Integrated shopping list functionality in flyer import tab
- Added category backfill trigger in inventory screen
- Updated FlyerImportItem model with categoryId support
- Added methods for updating flyer session items and retrieving source files

Database:
- Added new Prisma migration for flyer source metadata and shopping list items
- Updated schema with new relations and indexes

The shopping list feature allows users to:
1. Plan flyer selections directly to their shopping list
2. View and manage their shopping list items
3. Update flyer session items with proper categorization
4. Retrieve original flyer source files
5. Automatically backfill categories for uncategorized products
This commit is contained in:
Nils-Johan Gynther
2026-05-20 09:07:30 +02:00
parent 996f0d774b
commit a1a2c33427
37 changed files with 1843 additions and 102 deletions
@@ -2,11 +2,14 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../../core/api/api_error_mapper.dart';
import '../../../core/l10n/l10n.dart';
import '../../../core/api/api_error_mapper.dart';
import '../../../core/api/api_paths.dart';
import '../../../core/api/api_providers.dart';
import '../../../core/l10n/l10n.dart';
import '../../../core/ui/async_state_views.dart';
import '../../../core/utils/display_labels.dart';
import '../../auth/data/auth_providers.dart';
import '../../auth/data/auth_providers.dart';
import '../../pantry/data/pantry_providers.dart';
import '../domain/inventory_item.dart';
import '../data/inventory_providers.dart';
import 'swipeable_inventory_tile.dart';
@@ -18,9 +21,30 @@ class InventoryScreen extends ConsumerStatefulWidget {
ConsumerState<InventoryScreen> createState() => _InventoryScreenState();
}
class _InventoryScreenState extends ConsumerState<InventoryScreen> {
class _InventoryScreenState extends ConsumerState<InventoryScreen> {
final Set<int> _selectedIds = <int>{};
static const _sortByDisplayedCategory = 'l1CategoryAsc';
static const _sortByDisplayedCategory = 'l1CategoryAsc';
bool _backfillTriggered = false;
@override
void initState() {
super.initState();
_triggerCategoryBackfill();
}
Future<void> _triggerCategoryBackfill() async {
if (_backfillTriggered) return;
_backfillTriggered = true;
try {
final token = await ref.read(authStateProvider.future);
final api = ref.read(apiClientProvider);
await api.postJson(ProductApiPaths.backfillMineCategories, token: token);
ref.invalidate(inventoryProvider);
ref.invalidate(pantryProvider);
} catch (_) {
// Ignorera fel här för att inte blockera vyn.
}
}
static const _locationOptions = <String>['', 'Kyl', 'Frys', 'Skafferi'];