refactor: streamline alias editing and improve category path handling in admin panel
Test Suite / backend-pr-quick (push) Has been skipped
Test Suite / quick-import-pr-quick (push) Has been skipped
Test Suite / backend-full (push) Failing after 21s
Test Suite / flutter-quality (push) Successful in 57s

This commit is contained in:
Nils-Johan Gynther
2026-05-12 21:53:19 +02:00
parent a4d16cdbae
commit 621ced0e43
2 changed files with 101 additions and 51 deletions
@@ -26,7 +26,6 @@ class _AdminAliasesPanelState extends ConsumerState<AdminAliasesPanel> {
final TextEditingController _aliasController = TextEditingController(); final TextEditingController _aliasController = TextEditingController();
int? _selectedProductId; int? _selectedProductId;
int? _editingAliasId;
@override @override
void initState() { void initState() {
@@ -81,31 +80,21 @@ class _AdminAliasesPanelState extends ConsumerState<AdminAliasesPanel> {
setState(() => _isSaving = true); setState(() => _isSaving = true);
try { try {
final repo = ref.read(adminRepositoryProvider); final repo = ref.read(adminRepositoryProvider);
final isEditing = _editingAliasId != null;
if (isEditing) {
await repo.updateReceiptAlias(
_editingAliasId!,
receiptName: rawAlias,
productId: productId,
);
} else {
await repo.upsertReceiptAlias( await repo.upsertReceiptAlias(
receiptName: rawAlias, receiptName: rawAlias,
productId: productId, productId: productId,
isGlobal: true, isGlobal: true,
); );
}
if (!mounted) return; if (!mounted) return;
_aliasController.clear(); _aliasController.clear();
setState(() { setState(() {
_selectedProductId = null; _selectedProductId = null;
_editingAliasId = null;
}); });
await _load(); await _load();
if (!mounted) return; if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(isEditing ? 'Alias uppdaterat.' : 'Alias sparad.')), const SnackBar(content: Text('Alias sparad.')),
); );
} catch (e) { } catch (e) {
if (!mounted) return; if (!mounted) return;
@@ -117,27 +106,95 @@ class _AdminAliasesPanelState extends ConsumerState<AdminAliasesPanel> {
} }
} }
void _startEditAlias(ReceiptAlias alias) { Future<void> _editAlias(ReceiptAlias alias) async {
if (!alias.isGlobal) { String aliasName = alias.receiptName;
int selectedProductId = alias.productId;
final nameController = TextEditingController(text: alias.receiptName);
final result = await showDialog<bool>(
context: context,
builder: (dialogContext) => StatefulBuilder(
builder: (dialogContext, setDialogState) => AlertDialog(
title: const Text('Redigera alias'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: nameController,
decoration: const InputDecoration(
labelText: 'Kvittonamn (alias)',
border: OutlineInputBorder(),
),
onChanged: (value) => aliasName = value,
),
const SizedBox(height: 12),
DropdownButtonFormField<int>(
initialValue: selectedProductId,
decoration: const InputDecoration(
labelText: 'Produkt',
border: OutlineInputBorder(),
),
items: _products
.map(
(product) => DropdownMenuItem<int>(
value: product.id,
child: Text(product.displayName),
),
)
.toList(),
onChanged: (value) {
if (value == null) return;
setDialogState(() => selectedProductId = value);
},
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(dialogContext, false),
child: const Text('Avbryt'),
),
FilledButton(
onPressed: () => Navigator.pop(dialogContext, true),
child: const Text('Spara'),
),
],
),
),
);
nameController.dispose();
if (result != true || !mounted) return;
final trimmedAlias = aliasName.trim();
if (trimmedAlias.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Privata alias redigeras av respektive användare.')), const SnackBar(content: Text('Aliasnamn kan inte vara tomt.')),
); );
return; return;
} }
setState(() { setState(() => _isSaving = true);
_editingAliasId = alias.id; try {
_aliasController.text = alias.receiptName; await ref.read(adminRepositoryProvider).updateReceiptAlias(
_selectedProductId = alias.productId; alias.id,
}); receiptName: trimmedAlias,
productId: selectedProductId,
);
if (!mounted) return;
await _load();
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Alias uppdaterat.')),
);
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
buildCopyableErrorSnackBar(context, mapErrorToUserMessage(e, context)),
);
} finally {
if (mounted) setState(() => _isSaving = false);
} }
void _cancelEditAlias() {
setState(() {
_editingAliasId = null;
_aliasController.clear();
_selectedProductId = null;
});
} }
Future<void> _removeAlias(ReceiptAlias alias) async { Future<void> _removeAlias(ReceiptAlias alias) async {
@@ -207,7 +264,7 @@ class _AdminAliasesPanelState extends ConsumerState<AdminAliasesPanel> {
Widget buildAliasCard(ReceiptAlias alias) { Widget buildAliasCard(ReceiptAlias alias) {
final product = productById[alias.productId]; final product = productById[alias.productId];
final categoryPath = product?.categoryPath ?? 'okänd'; final categoryPath = product?.categoryPath?.trim();
return Card( return Card(
child: ListTile( child: ListTile(
@@ -215,6 +272,7 @@ class _AdminAliasesPanelState extends ConsumerState<AdminAliasesPanel> {
title: Row( title: Row(
children: [ children: [
Expanded(child: Text(alias.receiptName, style: const TextStyle(fontWeight: FontWeight.w500))), Expanded(child: Text(alias.receiptName, style: const TextStyle(fontWeight: FontWeight.w500))),
if (categoryPath != null && categoryPath.isNotEmpty)
buildCategoryPathChip(categoryPath), buildCategoryPathChip(categoryPath),
], ],
), ),
@@ -244,11 +302,9 @@ class _AdminAliasesPanelState extends ConsumerState<AdminAliasesPanel> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
IconButton( IconButton(
onPressed: () => _startEditAlias(alias), onPressed: _isSaving ? null : () => _editAlias(alias),
icon: const Icon(Icons.edit_outlined), icon: const Icon(Icons.edit_outlined),
tooltip: alias.isGlobal tooltip: 'Redigera alias',
? 'Redigera alias'
: 'Privata alias redigeras av användaren',
), ),
IconButton( IconButton(
onPressed: () => _removeAlias(alias), onPressed: () => _removeAlias(alias),
@@ -326,13 +382,6 @@ class _AdminAliasesPanelState extends ConsumerState<AdminAliasesPanel> {
), ),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
if (_editingAliasId != null) ...[
OutlinedButton(
onPressed: _isSaving ? null : _cancelEditAlias,
child: const Text('Avbryt'),
),
const SizedBox(width: 8),
],
FilledButton.icon( FilledButton.icon(
onPressed: _isSaving ? null : _upsertAlias, onPressed: _isSaving ? null : _upsertAlias,
icon: _isSaving icon: _isSaving
@@ -341,8 +390,8 @@ class _AdminAliasesPanelState extends ConsumerState<AdminAliasesPanel> {
height: 14, height: 14,
child: CircularProgressIndicator(strokeWidth: 2), child: CircularProgressIndicator(strokeWidth: 2),
) )
: Icon(_editingAliasId != null ? Icons.edit_outlined : Icons.save_outlined), : const Icon(Icons.save_outlined),
label: Text(_editingAliasId != null ? 'Uppdatera' : 'Spara'), label: const Text('Spara'),
), ),
], ],
), ),
@@ -76,9 +76,10 @@ List<ProductOption> toProductOptions(List<AdminProduct> products) {
} }
Widget buildCategoryPathChip(String? categoryPath, {double maxWidth = 220}) { Widget buildCategoryPathChip(String? categoryPath, {double maxWidth = 220}) {
final value = (categoryPath == null || categoryPath.trim().isEmpty) if (categoryPath == null || categoryPath.trim().isEmpty) {
? 'okänd' return const SizedBox.shrink();
: categoryPath.trim(); }
final value = categoryPath.trim();
return Tooltip( return Tooltip(
message: value, message: value,
child: Chip( child: Chip(