import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../core/api/api_error_mapper.dart'; import '../data/admin_repository.dart'; import '../domain/admin_product.dart'; import '../domain/receipt_alias.dart'; class AdminAliasesPanel extends ConsumerStatefulWidget { final bool embedded; const AdminAliasesPanel({super.key, this.embedded = false}); @override ConsumerState createState() => _AdminAliasesPanelState(); } class _AdminAliasesPanelState extends ConsumerState { bool _isLoading = true; bool _isSaving = false; String? _error; String _search = ''; List _aliases = []; List _products = []; final TextEditingController _aliasController = TextEditingController(); int? _selectedProductId; @override void initState() { super.initState(); _load(); } @override void dispose() { _aliasController.dispose(); super.dispose(); } Future _load() async { setState(() { _isLoading = true; _error = null; }); try { final results = await Future.wait([ ref.read(adminRepositoryProvider).listReceiptAliases(), ref.read(adminRepositoryProvider).listProducts(), ]); if (!mounted) return; setState(() { _aliases = (results[0] as List) ..sort((a, b) => a.receiptName.compareTo(b.receiptName)); _products = (results[1] as List) ..sort( (a, b) => a.displayName.toLowerCase().compareTo(b.displayName.toLowerCase()), ); }); } catch (e) { if (!mounted) return; setState(() => _error = mapErrorToUserMessage(e, context)); } finally { if (mounted) setState(() => _isLoading = false); } } Future _upsertAlias() async { final rawAlias = _aliasController.text.trim().toLowerCase(); final productId = _selectedProductId; if (rawAlias.isEmpty || productId == null) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Ange alias och välj produkt.')), ); return; } setState(() => _isSaving = true); try { await ref.read(adminRepositoryProvider).upsertReceiptAlias( receiptName: rawAlias, productId: productId, isGlobal: true, ); if (!mounted) return; _aliasController.clear(); setState(() => _selectedProductId = null); await _load(); if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Alias sparad.')), ); } catch (e) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(mapErrorToUserMessage(e, context))), ); } finally { if (mounted) setState(() => _isSaving = false); } } Future _removeAlias(ReceiptAlias alias) async { final confirmed = await showDialog( context: context, builder: (dialogContext) => AlertDialog( title: const Text('Ta bort alias'), content: Text('Ta bort alias "${alias.receiptName}"?'), actions: [ TextButton( onPressed: () => Navigator.pop(dialogContext, false), child: const Text('Avbryt'), ), FilledButton( onPressed: () => Navigator.pop(dialogContext, true), child: const Text('Ta bort'), ), ], ), ); if (confirmed != true || !mounted) return; try { await ref.read(adminRepositoryProvider).removeReceiptAlias(alias.id); if (!mounted) return; await _load(); if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Alias borttaget.')), ); } catch (e) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(mapErrorToUserMessage(e, context))), ); } } @override Widget build(BuildContext context) { final theme = Theme.of(context); if (_isLoading) { return const Center(child: CircularProgressIndicator()); } if (_error != null) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Text(_error!, style: TextStyle(color: theme.colorScheme.error)), const SizedBox(height: 16), FilledButton(onPressed: _load, child: const Text('Försök igen')), ], ), ); } final filteredAliases = _aliases.where((alias) { final query = _search.trim().toLowerCase(); if (query.isEmpty) return true; return alias.receiptName.contains(query) || alias.displayProductName.toLowerCase().contains(query); }).toList(); final content = Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Globala alias används som fallback i kvittoimporten. När samma kvittonamn upprepas kan rätt produkt matchas direkt.', style: theme.textTheme.bodyMedium, ), const SizedBox(height: 12), Row( children: [ Expanded( child: TextField( controller: _aliasController, decoration: const InputDecoration( labelText: 'Kvittonamn (alias)', border: OutlineInputBorder(), ), ), ), const SizedBox(width: 8), Expanded( child: DropdownButtonFormField( value: _selectedProductId, decoration: const InputDecoration( labelText: 'Produkt', border: OutlineInputBorder(), ), items: _products .map( (product) => DropdownMenuItem( value: product.id, child: Text(product.displayName), ), ) .toList(), onChanged: _isSaving ? null : (value) => setState(() => _selectedProductId = value), ), ), const SizedBox(width: 8), FilledButton.icon( onPressed: _isSaving ? null : _upsertAlias, icon: _isSaving ? const SizedBox( width: 14, height: 14, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.save_outlined), label: const Text('Spara'), ), ], ), const SizedBox(height: 12), TextField( decoration: const InputDecoration( labelText: 'Sök alias', prefixIcon: Icon(Icons.search), border: OutlineInputBorder(), ), onChanged: (value) => setState(() => _search = value), ), const SizedBox(height: 12), if (filteredAliases.isEmpty) const Text('Inga alias hittades.') else ...filteredAliases.map( (alias) => Card( child: ListTile( title: Text(alias.receiptName), subtitle: Text('Produkt: ${alias.displayProductName}'), trailing: IconButton( onPressed: () => _removeAlias(alias), icon: const Icon(Icons.delete_outline), tooltip: 'Ta bort alias', ), ), ), ), ], ); if (!widget.embedded) { return ListView( padding: const EdgeInsets.all(16), children: [content], ); } return content; } }