feat: enhance error dialogs with delete functionality and improve documentation
This commit is contained in:
+38
-11
@@ -24,18 +24,29 @@ Ge en detaljerad rapport enligt följande struktur:
|
|||||||
|
|
||||||
---
|
---
|
||||||
### **1. Allmän kodkvalitet**
|
### **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)?
|
- Finns det bristande namngivning (variabler, funktioner, klasser)?
|
||||||
- Saknas kommentarer för komplex logik?
|
- Saknas kommentarer för komplex logik?
|
||||||
- Kan modulariseringen förbättras (t.ex. splitta stora funktioner/klasser)?
|
- Kan modulariseringen förbättras (t.ex. splitta stora funktioner/klasser)?
|
||||||
- Följs TypeScript-bäst-praxis (t.ex. starka typer, interfaces, SOLID-principer)?
|
- 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**
|
### **2. Säkerhetsanalys**
|
||||||
- **Sårbarheter**:
|
- **Sårbarheter**:
|
||||||
@@ -82,13 +93,27 @@ Ge en detaljerad rapport enligt följande struktur:
|
|||||||
- **Uppskattad tid** för att implementera förslagen.
|
- **Uppskattad tid** för att implementera förslagen.
|
||||||
- **Rekommenderade verktyg** för automatiserade kontroller (t.ex. `ESLint`, `Prisma Lint`, `OWASP Dependency-Check`).
|
- **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**
|
### **Regler för analysen**
|
||||||
- Var **specifik**: Ge **kod-exempel** för varje förslag.
|
- Var **specifik**: Ge **kod-exempel** för varje förslag.
|
||||||
- Var **praktisk**: Fokusera på **realistiska förbättringar** som kan implementeras nu.
|
- 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.
|
- Var **kritisk**: Peka ut **allvarliga risker** (t.ex. säkerhetshål) först.
|
||||||
- Använd **severity** per fynd: `Critical`, `High`, `Medium`, `Low`.
|
- 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, och konkret åtgärd.
|
- 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.
|
- 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).
|
- 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.
|
- 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)**
|
### **Outputformat (obligatoriskt)**
|
||||||
1. `Scope`
|
1. `Scope`
|
||||||
2. `Gate-beslut` (`PASS` eller `BLOCK`)
|
2. `Gate-beslut` (`PASS` eller `BLOCK`)
|
||||||
3. `1. Allmän kodkvalitet`
|
3. `1. Allmän kodkvalitet` (blocking issues)
|
||||||
4. `2. Säkerhetsanalys`
|
4. `1b. Performance-optimeringar` (informational)
|
||||||
5. `3. Sammanfattning`
|
5. `2. Säkerhetsanalys` (blocking issues)
|
||||||
|
6. `2b. Backend-specifik kontroll` (blocking + informational)
|
||||||
|
7. `3. Sammanfattning` (topprioriteringar, tidskattning)
|
||||||
|
|
||||||
Om inga relevanta filer hittas:
|
Om inga relevanta filer hittas:
|
||||||
- Skriv `Inget att analysera` och varför (t.ex. tom staged + tom working tree).
|
- 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/material.dart';
|
||||||
import 'package:flutter/services.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) {
|
void showGlobalErrorDialog(BuildContext context, String errorMessage) {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -17,7 +17,7 @@ void showGlobalErrorDialog(BuildContext context, String errorMessage) {
|
|||||||
),
|
),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
TextButton(
|
TextButton(
|
||||||
child: const Text('Stäng'),
|
child: const Text('Ta bort'),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
@@ -26,6 +26,7 @@ void showGlobalErrorDialog(BuildContext context, String errorMessage) {
|
|||||||
child: const Text('Kopiera'),
|
child: const Text('Kopiera'),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Clipboard.setData(ClipboardData(text: errorMessage));
|
Clipboard.setData(ClipboardData(text: errorMessage));
|
||||||
|
Navigator.of(context).pop();
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(content: Text('Felmeddelande kopierat!')),
|
const SnackBar(content: Text('Felmeddelande kopierat!')),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import 'package:flutter/services.dart';
|
|||||||
|
|
||||||
import '../../../core/l10n/l10n.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) {
|
void showErrorDialog(BuildContext context, String errorMessage) {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -28,6 +28,7 @@ void showErrorDialog(BuildContext context, String errorMessage) {
|
|||||||
child: Text(context.l10n.errorDialogCopy),
|
child: Text(context.l10n.errorDialogCopy),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Clipboard.setData(ClipboardData(text: errorMessage));
|
Clipboard.setData(ClipboardData(text: errorMessage));
|
||||||
|
Navigator.of(context).pop();
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(context.l10n.errorDialogCopied)),
|
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 {
|
Future<void> _addSelected() async {
|
||||||
final items = _items;
|
final items = _items;
|
||||||
if (items == null) return;
|
if (items == null) return;
|
||||||
@@ -572,9 +610,36 @@ class _ReceiptImportTabState extends ConsumerState<ReceiptImportTab> {
|
|||||||
SnackBar(content: Text(parts.join(', ') + '.')),
|
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);
|
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(() {});
|
setState(() {});
|
||||||
await _loadInventory();
|
await _loadInventory();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -779,6 +844,7 @@ class _ReceiptImportTabState extends ConsumerState<ReceiptImportTab> {
|
|||||||
i,
|
i,
|
||||||
initialEntryMode: ImportProductEntryMode.create,
|
initialEntryMode: ImportProductEntryMode.create,
|
||||||
),
|
),
|
||||||
|
onDeleteRequested: () => _deleteItem(i),
|
||||||
matchedViaBadgeBuilder: _buildMatchedViaBadge,
|
matchedViaBadgeBuilder: _buildMatchedViaBadge,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -811,6 +877,7 @@ class _ReceiptImportResultRow extends ConsumerWidget {
|
|||||||
final VoidCallback onEditRequested;
|
final VoidCallback onEditRequested;
|
||||||
final VoidCallback onSelectExistingRequested;
|
final VoidCallback onSelectExistingRequested;
|
||||||
final VoidCallback onCreateRequested;
|
final VoidCallback onCreateRequested;
|
||||||
|
final VoidCallback onDeleteRequested;
|
||||||
final Widget Function(ParsedReceiptItem item, ThemeData theme)
|
final Widget Function(ParsedReceiptItem item, ThemeData theme)
|
||||||
matchedViaBadgeBuilder;
|
matchedViaBadgeBuilder;
|
||||||
|
|
||||||
@@ -824,6 +891,7 @@ class _ReceiptImportResultRow extends ConsumerWidget {
|
|||||||
required this.onEditRequested,
|
required this.onEditRequested,
|
||||||
required this.onSelectExistingRequested,
|
required this.onSelectExistingRequested,
|
||||||
required this.onCreateRequested,
|
required this.onCreateRequested,
|
||||||
|
required this.onDeleteRequested,
|
||||||
required this.matchedViaBadgeBuilder,
|
required this.matchedViaBadgeBuilder,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1019,14 +1087,29 @@ class _ReceiptImportResultRow extends ConsumerWidget {
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
trailing: Icon(
|
trailing: SizedBox(
|
||||||
hasProduct
|
width: 80,
|
||||||
? Icons.check_circle
|
child: Row(
|
||||||
: (isSuggested ? Icons.help_outline : Icons.error_outline),
|
mainAxisSize: MainAxisSize.min,
|
||||||
color: hasProduct
|
children: [
|
||||||
? Colors.green
|
Icon(
|
||||||
: (isSuggested ? Colors.orange : theme.colorScheme.tertiary),
|
hasProduct
|
||||||
size: 20,
|
? 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,
|
onTap: onEditRequested,
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user