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
+38 -11
View File
@@ -24,18 +24,29 @@ Ge en detaljerad rapport enligt följande struktur:
---
### **1. Allmän kodkvalitet**
- **Optimeringar**:
- Finns det ineffektiva algoritmer (t.ex. O(n²) istället för O(n))?
- Kan loopar, databaserfrågor (Prisma) eller API-anrop optimeras (t.ex. med caching, batch-behandling)?
- Finns det onödig kod (död kod, duplicerad logik)?
- Kan minne eller CPU-användning reduceras (t.ex. undvika djupa kopior, använda streams)?
- **Läsbarhet/underhållbarhet**:
- **Läsbarhet/underhållbarhet** (kan blockera om allvarligt):
- Finns det bristande namngivning (variabler, funktioner, klasser)?
- Saknas kommentarer för komplex logik?
- Kan modulariseringen förbättras (t.ex. splitta stora funktioner/klasser)?
- Följs TypeScript-bäst-praxis (t.ex. starka typer, interfaces, SOLID-principer)?
---
### **1b. Performance-optimeringar** (INFORMATIONAL)
Dessa rapporteras men blockerar inte commit. Kan adresseras i senare iteration:
- **Algoritm-effektivitet**:
- Finns det O(n²) eller värre algoritmer som kan vara O(n)?
- Finns onödig kod (död kod, duplicerad logik)?
- **Resurser**:
- Kan minne eller CPU-användning reduceras (t.ex. undvika djupa kopior, använda streams)?
- Kan loopar eller databaserfrågor (Prisma) optimeras (t.ex. med caching, batch-behandling)?
- Finns N+1-frågor eller ineffektiva `include/select`-mönster?
**Severity**: `Low` eller `Medium` beroende på påverkan. Blockerar aldrig commit.
---
### **2. Säkerhetsanalys**
- **Sårbarheter**:
@@ -82,13 +93,27 @@ Ge en detaljerad rapport enligt följande struktur:
- **Uppskattad tid** för att implementera förslagen.
- **Rekommenderade verktyg** för automatiserade kontroller (t.ex. `ESLint`, `Prisma Lint`, `OWASP Dependency-Check`).
---
### **Klassificering av fynd (Severity)**
**BLOCKING** (hindrar commit):
- `Critical`: Säkerhetshål, scope-brister (IDOR), SQL-injection, XSS, eller data-loss risk.
- `High`: Allvarlig korrektness-fel, felaktig autentisering/auktorisation, eller felaktig felhantering som påverkar produktion.
**INFORMATIONAL** (rapporteras, men blockerar inte):
- `Medium`: Code-quality, läsbarhet, testluckor, eller mindre performance-optimeringar.
- `Low`: Stilfrågor, dokumentation, eller nice-to-have refactor.
**Regel**: Gate-beslut = `PASS` om inga `Critical` eller `High` finns. `BLOCK` annars.
---
### **Regler för analysen**
- Var **specifik**: Ge **kod-exempel** för varje förslag.
- Var **praktisk**: Fokusera på **realistiska förbättringar** som kan implementeras nu.
- Var **kritisk**: Peka ut **allvarliga risker** (t.ex. säkerhetshål) först.
- Använd **severity** per fynd: `Critical`, `High`, `Medium`, `Low`.
- För varje fynd: ange fil, kort riskbeskrivning, varför det är ett problem, och konkret åtgärd.
- Använd **severity** per fynd enligt klassificering ovan: `Critical`, `High`, `Medium`, `Low`.
- För varje fynd: ange fil, kort riskbeskrivning, varför det är ett problem, severity, och konkret åtgärd.
- **Separa fynd efter severity**: Listet först alla `Critical`/`High` (blocking), sedan `Medium`/`Low` (informational).
- Om inga allvarliga risker hittas: skriv det explicit och lyft kvarvarande risker/testluckor.
- Ignorera filer som inte är relevanta (t.ex. node_modules, .git, binärfiler).
- Prioritera körbarhet: föreslagna åtgärder ska kunna göras i denna kodbas utan större arkitekturprojekt.
@@ -100,9 +125,11 @@ Ge en detaljerad rapport enligt följande struktur:
### **Outputformat (obligatoriskt)**
1. `Scope`
2. `Gate-beslut` (`PASS` eller `BLOCK`)
3. `1. Allmän kodkvalitet`
4. `2. Säkerhetsanalys`
5. `3. Sammanfattning`
3. `1. Allmän kodkvalitet` (blocking issues)
4. `1b. Performance-optimeringar` (informational)
5. `2. Säkerhetsanalys` (blocking issues)
6. `2b. Backend-specifik kontroll` (blocking + informational)
7. `3. Sammanfattning` (topprioriteringar, tidskattning)
Om inga relevanta filer hittas:
- Skriv `Inget att analysera` och varför (t.ex. tom staged + tom working tree).
@@ -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,
),