8.9 KiB
8.9 KiB
Implementeringsplan: "Se kvitto"-Modal för Kvittoimporten
Mål: MVP-vägen för split-view UX – lägg till modal som visar OCR-text från parsade kvittoraderna. Scope: 2-3 timmar Status: Planering
1. Ändringar i receipt_import_tab.dart
1.1 Lägg till knapp "Se kvitto" i header-raden (rad ~745-752)
Plats: Höger om "Välj alla/Avmarkera alla"-knappen
// Innan: Row med bara "Välj alla"-knapp
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('${items.length} rader — tryck för att redigera', style: theme.textTheme.titleSmall),
TextButton(...), // "Välj alla/Avmarkera alla"
],
)
// Efter: Lägg till "Se kvitto"-knapp
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('${items.length} rader — tryck för att redigera', style: theme.textTheme.titleSmall),
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(...), // Befintlig "Välj alla"
child: Text(...),
),
],
),
],
)
1.2 Implementera _showReceiptPreview-metod
Lägg till denna metod i _ReceiptImportTabState:
Future<void> _showReceiptPreview(BuildContext context, List<ParsedReceiptItem> items) async {
if (!context.mounted) return;
await showDialog(
context: context,
builder: (ctx) => _ReceiptPreviewDialog(items: items),
);
}
2. Ny widget: _ReceiptPreviewDialog
Lägg till denna widget i samma fil (receipt_import_tab.dart), efter _ReceiptImportResultRow-klassen:
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, // Responsiv bredd på desktop
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(' ');
}
}
3. Implementeringssteg (steg-för-steg)
- Läs receipt_import_tab.dart och identifiera raden med "Välj alla/Avmarkera alla"-knappen
- Refaktorera Row: Lägg "Se kvitto"-knapp bredvid befintliga knapp
- Lägg till
_showReceiptPreview()-metod i_ReceiptImportTabState - Implementera
_ReceiptPreviewDialog-widget på slutet av filen - Testa:
- Ladda ett kvitto
- Klicka "Se kvitto"-knappen
- Verifiera att texten är lesbar och formaterad
- Testa responsive bredd (dialog behöver minska på mobil)
4. Responsiv förbättring (optional)
Om dialogen behöver anpassas för mobil:
// I _ReceiptPreviewDialog.build():
final isWide = MediaQuery.of(context).size.width > 600;
return Dialog(
insetPadding: const EdgeInsets.all(16),
child: SizedBox(
width: isWide ? 600 : double.maxFinite, // Full bredd på mobil
// ...
),
);
5. Långsiktiga förbättringar (Phase 2)
Se next_steps_flutter.md för split-view roadmap:
- Horisontell split-view på desktop
- Scroll-synkronisering
- Tab-fallback på mobil
- AI-guiding labels
Ärendemal
Titel: "Se kvitto"-modal för kvittoimporten
Branch: feat/receipt-preview-modal
Labels: enhancement, import-ux, phase-1-mvp
Estimate: 2-3h