feat: enhance error dialogs with delete functionality and improve documentation
Test Suite / backend-pr-quick (24.15.0) (push) Has been skipped
Test Suite / quick-import-pr-quick (24.15.0) (push) Has been skipped
Test Suite / backend-full (24.15.0) (push) Failing after 26s
Test Suite / flutter-quality (push) Failing after 4s

This commit is contained in:
Nils-Johan Gynther
2026-05-12 21:11:54 +02:00
parent 0784c1a032
commit fb6b371fb7
4 changed files with 136 additions and 24 deletions
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
/// Visar en global dialogruta med ett felmeddelande och en kopieringsknapp.
/// Visar en global dialogruta med ett felmeddelande och knappar för kopiera och ta bort.
void showGlobalErrorDialog(BuildContext context, String errorMessage) {
showDialog(
context: context,
@@ -17,7 +17,7 @@ void showGlobalErrorDialog(BuildContext context, String errorMessage) {
),
actions: <Widget>[
TextButton(
child: const Text('Stäng'),
child: const Text('Ta bort'),
onPressed: () {
Navigator.of(context).pop();
},
@@ -26,6 +26,7 @@ void showGlobalErrorDialog(BuildContext context, String errorMessage) {
child: const Text('Kopiera'),
onPressed: () {
Clipboard.setData(ClipboardData(text: errorMessage));
Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Felmeddelande kopierat!')),
);
@@ -3,7 +3,7 @@ import 'package:flutter/services.dart';
import '../../../core/l10n/l10n.dart';
/// Visar en dialogruta med ett felmeddelande och en kopieringsknapp.
/// Visar en dialogruta med ett felmeddelande och knappar för kopiera och ta bort.
void showErrorDialog(BuildContext context, String errorMessage) {
showDialog(
context: context,
@@ -28,6 +28,7 @@ void showErrorDialog(BuildContext context, String errorMessage) {
child: Text(context.l10n.errorDialogCopy),
onPressed: () {
Clipboard.setData(ClipboardData(text: errorMessage));
Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.errorDialogCopied)),
);
@@ -460,6 +460,44 @@ class _ReceiptImportTabState extends ConsumerState<ReceiptImportTab> {
);
}
void _deleteItem(int index) {
final items = _items;
if (items == null || index < 0 || index >= items.length) return;
// Ta bort raden från items
final remainingItems = <ParsedReceiptItem>[...items];
remainingItems.removeAt(index);
// Re-index edits och selected i en enda pass
final remainingEdits = <int, ItemEdit>{};
final remainingSelected = <int, bool>{};
var newIndex = 0;
for (var oldIndex = 0; oldIndex < items.length; oldIndex++) {
if (oldIndex != index) {
if (_edits.containsKey(oldIndex)) {
remainingEdits[newIndex] = _edits[oldIndex]!;
}
if (_selected[oldIndex] == true) {
remainingSelected[newIndex] = true;
}
newIndex++;
}
}
final notifier = ref.read(receiptImportSessionProvider.notifier);
if (remainingItems.isEmpty) {
notifier.clear();
} else {
notifier.setImportedResult(
items: remainingItems,
edits: remainingEdits,
selected: remainingSelected,
);
}
setState(() {});
}
Future<void> _addSelected() async {
final items = _items;
if (items == null) return;
@@ -572,9 +610,36 @@ class _ReceiptImportTabState extends ConsumerState<ReceiptImportTab> {
SnackBar(content: Text(parts.join(', ') + '.')),
);
// Avmarkera sparade rader och uppdatera inventariet
// Ta bort de tillagda raderna från listan
final addedIndexSet = toAdd.toSet();
final remainingItems = <ParsedReceiptItem>[];
final remainingEdits = <int, ItemEdit>{};
final remainingSelected = <int, bool>{};
var newIndex = 0;
for (var oldIndex = 0; oldIndex < items.length; oldIndex++) {
if (!addedIndexSet.contains(oldIndex)) {
remainingItems.add(items[oldIndex]);
if (_edits.containsKey(oldIndex)) {
remainingEdits[newIndex] = _edits[oldIndex]!;
}
if (_selected[oldIndex] == true) {
remainingSelected[newIndex] = true;
}
newIndex++;
}
}
final notifier = ref.read(receiptImportSessionProvider.notifier);
notifier.setSelectedForIndexes(toAdd, false);
if (remainingItems.isEmpty) {
notifier.clear();
} else {
notifier.setImportedResult(
items: remainingItems,
edits: remainingEdits,
selected: remainingSelected,
);
}
setState(() {});
await _loadInventory();
} catch (e) {
@@ -779,6 +844,7 @@ class _ReceiptImportTabState extends ConsumerState<ReceiptImportTab> {
i,
initialEntryMode: ImportProductEntryMode.create,
),
onDeleteRequested: () => _deleteItem(i),
matchedViaBadgeBuilder: _buildMatchedViaBadge,
);
},
@@ -811,6 +877,7 @@ class _ReceiptImportResultRow extends ConsumerWidget {
final VoidCallback onEditRequested;
final VoidCallback onSelectExistingRequested;
final VoidCallback onCreateRequested;
final VoidCallback onDeleteRequested;
final Widget Function(ParsedReceiptItem item, ThemeData theme)
matchedViaBadgeBuilder;
@@ -824,6 +891,7 @@ class _ReceiptImportResultRow extends ConsumerWidget {
required this.onEditRequested,
required this.onSelectExistingRequested,
required this.onCreateRequested,
required this.onDeleteRequested,
required this.matchedViaBadgeBuilder,
});
@@ -1019,14 +1087,29 @@ class _ReceiptImportResultRow extends ConsumerWidget {
],
],
),
trailing: Icon(
hasProduct
? Icons.check_circle
: (isSuggested ? Icons.help_outline : Icons.error_outline),
color: hasProduct
? Colors.green
: (isSuggested ? Colors.orange : theme.colorScheme.tertiary),
size: 20,
trailing: SizedBox(
width: 80,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
hasProduct
? Icons.check_circle
: (isSuggested ? Icons.help_outline : Icons.error_outline),
color: hasProduct
? Colors.green
: (isSuggested ? Colors.orange : theme.colorScheme.tertiary),
size: 20,
),
IconButton(
icon: Icon(Icons.delete_outline, size: 18, color: theme.colorScheme.error),
onPressed: onDeleteRequested,
tooltip: 'Ta bort rad',
constraints: const BoxConstraints(minWidth: 40, minHeight: 40),
padding: const EdgeInsets.all(4),
),
],
),
),
onTap: onEditRequested,
),