feat: add initial query support to ProductPickerField and enhance ParsedReceiptItem with categorySuggestionPath

This commit is contained in:
Nils-Johan Gynther
2026-05-01 01:50:18 +02:00
parent 997d62ade8
commit f4fea7b92c
3 changed files with 110 additions and 58 deletions
@@ -14,6 +14,7 @@ class ParsedReceiptItem {
final String? suggestedProductName;
// AI-kategorisuggestion (premium)
final String? categorySuggestionName;
final String? categorySuggestionPath;
ParsedReceiptItem({
required this.rawName,
@@ -27,19 +28,24 @@ class ParsedReceiptItem {
this.suggestedProductId,
this.suggestedProductName,
this.categorySuggestionName,
this.categorySuggestionPath,
});
factory ParsedReceiptItem.fromJson(Map<String, dynamic> json) => ParsedReceiptItem(
rawName: json['rawName'] as String? ?? '',
quantity: (json['quantity'] as num?)?.toDouble(),
unit: json['unit'] as String?,
price: (json['price'] as num?)?.toDouble(),
brand: json['brand'] as String?,
origin: json['origin'] as String?,
matchedProductId: (json['matchedProductId'] as num?)?.toInt(),
matchedProductName: json['matchedProductName'] as String?,
suggestedProductId: (json['suggestedProductId'] as num?)?.toInt(),
suggestedProductName: json['suggestedProductName'] as String?,
categorySuggestionName: (json['categorySuggestion'] as Map<String, dynamic>?)?['categoryName'] as String?,
);
factory ParsedReceiptItem.fromJson(Map<String, dynamic> json) {
final cat = json['categorySuggestion'] as Map<String, dynamic>?;
return ParsedReceiptItem(
rawName: json['rawName'] as String? ?? '',
quantity: (json['quantity'] as num?)?.toDouble(),
unit: json['unit'] as String?,
price: (json['price'] as num?)?.toDouble(),
brand: json['brand'] as String?,
origin: json['origin'] as String?,
matchedProductId: (json['matchedProductId'] as num?)?.toInt(),
matchedProductName: json['matchedProductName'] as String?,
suggestedProductId: (json['suggestedProductId'] as num?)?.toInt(),
suggestedProductName: json['suggestedProductName'] as String?,
categorySuggestionName: cat?['categoryName'] as String?,
categorySuggestionPath: cat?['path'] as String?,
);
}
}
@@ -82,32 +82,70 @@ class _EditDialogState extends State<_EditDialog> {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final aiCategory = widget.item.categorySuggestionName;
final item = widget.item;
final aiCategory = item.categorySuggestionName;
final aiPath = item.categorySuggestionPath;
// Visa hela sökvägen om det finns, annars bara kategorinamnet
final aiLabel = aiPath != null && aiPath.isNotEmpty ? aiPath : aiCategory;
// Hjälpfunktion: acceptera AI-förslaget
void acceptAiSuggestion() {
final sugId = item.suggestedProductId;
if (sugId != null) {
// Välj den föreslagna produkten direkt
setState(() {
_productId = sugId;
_productName = item.suggestedProductName;
});
} else if (aiCategory != null) {
// Öppna pickern med kategorinamnet sökt
ProductPickerField.showSheet(
context,
products: widget.products,
value: _productId,
label: 'Produkt',
initialQuery: aiCategory,
).then((id) {
if (id != null && mounted) {
setState(() {
_productId = id;
_productName = widget.products
.cast<ProductOption?>()
.firstWhere((p) => p?.id == id, orElse: () => null)
?.name;
});
}
});
}
}
return AlertDialog(
title: Text(widget.item.rawName, maxLines: 2, overflow: TextOverflow.ellipsis),
title: Text(item.rawName, maxLines: 2, overflow: TextOverflow.ellipsis),
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// AI-kategorisuggestion
if (aiCategory != null) ...[
// AI-kategorisuggestion — klickbar
if (aiLabel != null) ...[
Wrap(
children: [
Chip(
avatar: const Icon(Icons.auto_awesome, size: 14),
label: Text('AI: $aiCategory',
style: theme.textTheme.labelSmall),
ActionChip(
avatar: Icon(Icons.auto_awesome, size: 14, color: Colors.green.shade700),
label: Text('AI: $aiLabel', style: theme.textTheme.labelSmall),
backgroundColor: Colors.green.shade50,
side: BorderSide(color: Colors.green.shade300),
visualDensity: VisualDensity.compact,
tooltip: item.suggestedProductId != null
? 'Välj "${item.suggestedProductName}" automatiskt'
: 'Sök produkter i kategorin "$aiCategory"',
onPressed: acceptAiSuggestion,
),
],
),
const SizedBox(height: 8),
],
// Destination
// Destination
SegmentedButton<_Destination>(
segments: const [
ButtonSegment(