feat: enhance inventory management with category and location filters
Test Suite / test (24.15.0) (push) Has been cancelled
Test Suite / test (24.15.0) (push) Has been cancelled
This commit is contained in:
@@ -2,6 +2,8 @@ class InventoryItem {
|
||||
final int id;
|
||||
final int productId;
|
||||
final String productName;
|
||||
final String? productCanonicalName;
|
||||
final String? categoryPath;
|
||||
final double quantity;
|
||||
final String unit;
|
||||
final String? location;
|
||||
@@ -15,6 +17,8 @@ class InventoryItem {
|
||||
required this.id,
|
||||
required this.productId,
|
||||
required this.productName,
|
||||
this.productCanonicalName,
|
||||
this.categoryPath,
|
||||
required this.quantity,
|
||||
required this.unit,
|
||||
this.location,
|
||||
@@ -25,11 +29,27 @@ class InventoryItem {
|
||||
this.comment,
|
||||
});
|
||||
|
||||
String get displayName {
|
||||
if (productCanonicalName != null && productCanonicalName!.trim().isNotEmpty) {
|
||||
return productCanonicalName!;
|
||||
}
|
||||
return productName;
|
||||
}
|
||||
|
||||
String get l1Category {
|
||||
final path = categoryPath?.trim();
|
||||
if (path == null || path.isEmpty) return 'Ovrigt';
|
||||
return path.split('>').first.trim();
|
||||
}
|
||||
|
||||
factory InventoryItem.fromJson(Map<String, dynamic> json) {
|
||||
final product = (json['product'] as Map<String, dynamic>?) ?? const {};
|
||||
return InventoryItem(
|
||||
id: json['id'] as int,
|
||||
productId: json['productId'] as int,
|
||||
productName: (json['product'] as Map<String, dynamic>?)?['name'] as String? ?? '',
|
||||
productName: product['name'] as String? ?? '',
|
||||
productCanonicalName: product['canonicalName'] as String?,
|
||||
categoryPath: _buildCategoryPath(product['categoryRef']),
|
||||
quantity: double.tryParse(json['quantity']?.toString() ?? '0') ?? 0,
|
||||
unit: json['unit'] as String? ?? '',
|
||||
location: json['location'] as String?,
|
||||
@@ -40,4 +60,21 @@ class InventoryItem {
|
||||
comment: json['comment'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
static String? _buildCategoryPath(dynamic rawCategoryRef) {
|
||||
if (rawCategoryRef is! Map<String, dynamic>) return null;
|
||||
|
||||
final names = <String>[];
|
||||
dynamic current = rawCategoryRef;
|
||||
while (current is Map<String, dynamic>) {
|
||||
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(' > ');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,9 @@ import '../data/inventory_providers.dart';
|
||||
import '../../import/data/receipt_import_session.dart' show ImportDestination;
|
||||
|
||||
class CreateInventoryScreen extends ConsumerStatefulWidget {
|
||||
const CreateInventoryScreen({super.key});
|
||||
final String? initialDestination;
|
||||
|
||||
const CreateInventoryScreen({super.key, this.initialDestination});
|
||||
|
||||
@override
|
||||
ConsumerState<CreateInventoryScreen> createState() =>
|
||||
@@ -43,6 +45,9 @@ class _CreateInventoryScreenState
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (widget.initialDestination == 'pantry') {
|
||||
_destination = ImportDestination.pantry;
|
||||
}
|
||||
_loadProducts();
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ class InventoryScreen extends ConsumerWidget {
|
||||
(value: 'nameAsc', label: context.l10n.inventorySortNameAsc),
|
||||
(value: 'bestBeforeAsc', label: context.l10n.inventorySortBestBeforeAsc),
|
||||
(value: 'bestBeforeDesc', label: context.l10n.inventorySortBestBeforeDesc),
|
||||
(value: 'l1CategoryAsc', label: 'L1-kategori (A-O)'),
|
||||
];
|
||||
|
||||
@override
|
||||
@@ -32,6 +33,17 @@ class InventoryScreen extends ConsumerWidget {
|
||||
onRetry: () => ref.invalidate(inventoryProvider),
|
||||
),
|
||||
data: (items) {
|
||||
final visibleItems = [...items];
|
||||
if (sort == 'l1CategoryAsc') {
|
||||
visibleItems.sort((a, b) {
|
||||
final byCategory = a.l1Category.toLowerCase().compareTo(
|
||||
b.l1Category.toLowerCase(),
|
||||
);
|
||||
if (byCategory != 0) return byCategory;
|
||||
return a.displayName.toLowerCase().compareTo(b.displayName.toLowerCase());
|
||||
});
|
||||
}
|
||||
|
||||
final filterSection = Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 12, 12, 4),
|
||||
child: Column(
|
||||
@@ -83,7 +95,7 @@ class InventoryScreen extends ConsumerWidget {
|
||||
),
|
||||
);
|
||||
|
||||
if (items.isEmpty) {
|
||||
if (visibleItems.isEmpty) {
|
||||
return Stack(
|
||||
children: [
|
||||
ListView(
|
||||
@@ -109,11 +121,11 @@ class InventoryScreen extends ConsumerWidget {
|
||||
children: [
|
||||
ListView.separated(
|
||||
padding: const EdgeInsets.only(bottom: 88),
|
||||
itemCount: items.length + 1,
|
||||
itemCount: visibleItems.length + 1,
|
||||
separatorBuilder: (_, __) => const Divider(height: 1),
|
||||
itemBuilder: (context, index) {
|
||||
if (index == 0) return filterSection;
|
||||
final item = items[index - 1];
|
||||
final item = visibleItems[index - 1];
|
||||
return SwipeableInventoryTile(item: item);
|
||||
},
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user