feat: add "See receipt" button and preview modal in receipt import flow
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:
@@ -449,6 +449,14 @@ class _ReceiptImportTabState extends ConsumerState<ReceiptImportTab> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _showReceiptPreview(BuildContext context, List<ParsedReceiptItem> items) async {
|
||||
if (!context.mounted) return;
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => _ReceiptPreviewDialog(items: items),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _addSelected() async {
|
||||
final items = _items;
|
||||
if (items == null) return;
|
||||
@@ -742,12 +750,22 @@ class _ReceiptImportTabState extends ConsumerState<ReceiptImportTab> {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text('${items.length} rader — tryck för att redigera', style: theme.textTheme.titleSmall),
|
||||
TextButton(
|
||||
onPressed: () => setState(() {
|
||||
final notifier = ref.read(receiptImportSessionProvider.notifier);
|
||||
notifier.setSelectedForAll(items.length, _selectedCount < items.length);
|
||||
}),
|
||||
child: Text(_selectedCount < items.length ? 'Välj alla' : 'Avmarkera alla'),
|
||||
Row(
|
||||
children: [
|
||||
TextButton.icon(
|
||||
onPressed: items.isEmpty ? null : () => _showReceiptPreview(context, items),
|
||||
icon: const Icon(Icons.description_outlined),
|
||||
label: const Text('Se kvitto'),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
TextButton(
|
||||
onPressed: () => setState(() {
|
||||
final notifier = ref.read(receiptImportSessionProvider.notifier);
|
||||
notifier.setSelectedForAll(items.length, _selectedCount < items.length);
|
||||
}),
|
||||
child: Text(_selectedCount < items.length ? 'Välj alla' : 'Avmarkera alla'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -1031,3 +1049,103 @@ class _ReceiptImportResultRow extends ConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _ReceiptPreviewDialog extends StatelessWidget {
|
||||
final List<ParsedReceiptItem> items;
|
||||
|
||||
const _ReceiptPreviewDialog({required this.items});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return AlertDialog(
|
||||
title: const Text('Kvittotexten i sin helhet'),
|
||||
content: SizedBox(
|
||||
width: 600,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Här visas all OCR-parsad text från kvittot. En rad per artikel:',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.surfaceContainerLowest,
|
||||
border: Border.all(color: theme.colorScheme.outlineVariant),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: SelectableText.rich(
|
||||
TextSpan(
|
||||
children: items.isEmpty
|
||||
? [TextSpan(text: '(Inga rader)', style: theme.textTheme.bodySmall)]
|
||||
: items
|
||||
.asMap()
|
||||
.entries
|
||||
.map((entry) {
|
||||
final item = entry.value;
|
||||
final lineNumber = entry.key + 1;
|
||||
final lineText = _formatReceiptLine(item);
|
||||
return TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: '$lineNumber. ',
|
||||
style: theme.textTheme.labelSmall?.copyWith(
|
||||
color: theme.colorScheme.outlineVariant,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: lineText,
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
fontFamily: 'monospace',
|
||||
),
|
||||
),
|
||||
const TextSpan(text: '\n'),
|
||||
],
|
||||
);
|
||||
})
|
||||
.toList(),
|
||||
),
|
||||
style: theme.textTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('Stäng'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
String _formatReceiptLine(ParsedReceiptItem item) {
|
||||
final parts = <String>[];
|
||||
|
||||
if (item.quantity != null) {
|
||||
parts.add('${item.quantity}');
|
||||
}
|
||||
|
||||
if (item.unit != null) {
|
||||
parts.add(item.unit!);
|
||||
}
|
||||
|
||||
parts.add(item.rawName);
|
||||
|
||||
if (item.price != null) {
|
||||
parts.add('— ${item.price} kr');
|
||||
}
|
||||
|
||||
return parts.join(' ');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user