import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../core/api/api_error_mapper.dart'; import '../../../core/ui/category_then_product_picker.dart'; import '../../../core/ui/product_picker_field.dart'; import '../data/admin_repository.dart'; import '../domain/admin_category_node.dart'; import '../domain/admin_inventory_item.dart'; import '../domain/admin_product.dart'; import '../domain/user_admin.dart'; enum _InventorySort { newest, nameAsc, nameDesc, quantityAsc, quantityDesc, } class AdminInventoryPanel extends ConsumerStatefulWidget { final bool embedded; const AdminInventoryPanel({super.key, this.embedded = false}); @override ConsumerState createState() => _AdminInventoryPanelState(); } class _AdminInventoryPanelState extends ConsumerState { bool _isLoading = true; String? _error; String _search = ''; int? _selectedUserId; _InventorySort _sort = _InventorySort.newest; List _items = []; List _products = []; List _categories = []; List _users = []; @override void initState() { super.initState(); _load(); } Future _load() async { setState(() { _isLoading = true; _error = null; }); try { final results = await Future.wait([ ref.read(adminRepositoryProvider).listAdminInventory( userId: _selectedUserId, sort: _sortParam, ), ref.read(adminRepositoryProvider).listGlobalProducts(), ref.read(adminRepositoryProvider).listCategoryTree(), ref.read(adminRepositoryProvider).listUsers(), ]); if (!mounted) return; setState(() { _items = results[0] as List; _products = results[1] as List; _categories = results[2] as List; _users = results[3] as List; }); } catch (e) { if (!mounted) return; setState(() => _error = mapErrorToUserMessage(e, context)); } finally { if (mounted) setState(() => _isLoading = false); } } String get _sortParam => switch (_sort) { _InventorySort.newest => '', _InventorySort.nameAsc => 'nameAsc', _InventorySort.nameDesc => 'nameDesc', _InventorySort.quantityAsc => 'quantityAsc', _InventorySort.quantityDesc => 'quantityDesc', }; String _sortLabel(_InventorySort sort) => switch (sort) { _InventorySort.newest => 'Nyast', _InventorySort.nameAsc => 'Namn A-Ö', _InventorySort.nameDesc => 'Namn Ö-A', _InventorySort.quantityAsc => 'Mängd stigande', _InventorySort.quantityDesc => 'Mängd fallande', }; List get _filtered { final q = _search.trim().toLowerCase(); if (q.isEmpty) return _items; return _items.where((item) { return item.displayName.toLowerCase().contains(q) || item.username.toLowerCase().contains(q) || item.userEmail.toLowerCase().contains(q) || (item.location ?? '').toLowerCase().contains(q) || (item.categoryPath ?? '').toLowerCase().contains(q); }).toList(); } Future _addItem() async { final values = await _showInventoryFormDialog(initialOwnerUserId: _selectedUserId); if (values == null) return; try { await ref.read(adminRepositoryProvider).createAdminInventory( userId: values.ownerUserId, productId: values.productId, quantity: values.quantity, unit: values.unit, location: values.location, brand: values.brand, receiptName: values.receiptName, suitableFor: values.suitableFor, comment: values.comment, ); if (!mounted) return; await _load(); if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Inventory-post skapad.')), ); } catch (e) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( buildCopyableErrorSnackBar(context, mapErrorToUserMessage(e, context)), ); } } Future _editItem(AdminInventoryItem item) async { final values = await _showInventoryFormDialog(initial: item); if (values == null) return; try { await ref.read(adminRepositoryProvider).updateAdminInventory( item.id, productId: values.productId, quantity: values.quantity, unit: values.unit, location: values.location, brand: values.brand, receiptName: values.receiptName, suitableFor: values.suitableFor, comment: values.comment, ); if (!mounted) return; await _load(); if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Inventory-post uppdaterad.')), ); } catch (e) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( buildCopyableErrorSnackBar(context, mapErrorToUserMessage(e, context)), ); } } Future _deleteItem(AdminInventoryItem item) async { final confirm = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Ta bort inventory-post'), content: Text('Ta bort "${item.displayName}" för ${item.username}?'), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), child: const Text('Avbryt'), ), FilledButton( onPressed: () => Navigator.of(context).pop(true), child: const Text('Ta bort'), ), ], ), ); if (confirm != true) return; try { await ref.read(adminRepositoryProvider).removeAdminInventory(item.id); if (!mounted) return; await _load(); if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Inventory-post borttagen.')), ); } catch (e) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( buildCopyableErrorSnackBar(context, mapErrorToUserMessage(e, context)), ); } } Future _mergeItems() async { if (_items.length < 2) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Minst två inventory-poster krävs för merge.')), ); return; } int? sourceId; int? targetId; int? previewSourceId; int? previewTargetId; Future?>? previewFuture; AdminInventoryItem? byId(int? id) { if (id == null) return null; for (final item in _items) { if (item.id == id) return item; } return null; } String? mergeValidation(AdminInventoryItem? source, AdminInventoryItem? target) { if (source == null || target == null) { return 'Välj både source och target för merge.'; } if (source.id == target.id) { return 'Source och target kan inte vara samma post.'; } if (source.userId != target.userId) { return 'Merge kräver samma användare på båda posterna.'; } if (source.productId != target.productId) { return 'Merge kräver samma produkt på båda posterna.'; } if (source.unit.trim().toLowerCase() != target.unit.trim().toLowerCase()) { return 'Merge kräver samma enhet på båda posterna.'; } return null; } Future?> fetchPreview(int? sourceId, int? targetId) async { if (sourceId == null || targetId == null) return null; try { return await ref.read(adminRepositoryProvider).previewAdminInventoryMerge( sourceInventoryId: sourceId, targetInventoryId: targetId, ); } catch (_) { return null; } } Future?>? resolvePreviewFuture(int? sourceId, int? targetId) { if (sourceId == null || targetId == null) return null; if (previewFuture == null || previewSourceId != sourceId || previewTargetId != targetId) { previewSourceId = sourceId; previewTargetId = targetId; previewFuture = fetchPreview(sourceId, targetId); } return previewFuture; } final ok = await showDialog( context: context, builder: (context) { return StatefulBuilder( builder: (context, setDialogState) { final source = byId(sourceId); final target = byId(targetId); final validationMessage = mergeValidation(source, target); final canMerge = validationMessage == null; final localMergedQuantity = canMerge && source != null && target != null ? source.quantity + target.quantity : null; return AlertDialog( title: const Text('Merge inventory-poster'), content: SizedBox( width: 460, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Välj två poster för samma användare, produkt och enhet. Source tas bort och target behålls.', style: Theme.of(context).textTheme.bodyMedium, ), const SizedBox(height: 12), DropdownButtonFormField( initialValue: sourceId, items: _items .map((e) => DropdownMenuItem( value: e.id, child: Text( '${e.displayName} (${e.quantity} ${e.unit}) · ${e.username}', overflow: TextOverflow.ellipsis, ), )) .toList(), onChanged: (v) => setDialogState(() => sourceId = v), decoration: const InputDecoration(labelText: 'Source (tas bort)'), ), const SizedBox(height: 12), DropdownButtonFormField( initialValue: targetId, items: _items .map((e) => DropdownMenuItem( value: e.id, child: Text( '${e.displayName} (${e.quantity} ${e.unit}) · ${e.username}', overflow: TextOverflow.ellipsis, ), )) .toList(), onChanged: (v) => setDialogState(() => targetId = v), decoration: const InputDecoration(labelText: 'Target (behålls)'), ), const SizedBox(height: 12), if (sourceId != null && targetId != null) FutureBuilder?>( future: resolvePreviewFuture(sourceId, targetId), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Align( alignment: Alignment.centerLeft, child: Text('Hämtar server-preview...'), ); } final data = snapshot.data; if (data == null) { return const SizedBox.shrink(); } final canMergeServer = data['canMerge'] == true; final reason = data['reason']?.toString(); final outcome = data['outcome'] as Map?; final mergedQuantity = outcome?['mergedQuantity']; final mergedUnit = outcome?['mergedUnit']?.toString() ?? ''; if (!canMergeServer && reason != null && reason.isNotEmpty) { return Container( width: double.infinity, padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: Theme.of(context).colorScheme.errorContainer, borderRadius: BorderRadius.circular(8), ), child: Text( 'Server-preview: $reason', style: TextStyle( color: Theme.of(context).colorScheme.onErrorContainer, ), ), ); } if (canMergeServer && mergedQuantity != null) { return Container( width: double.infinity, padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: Theme.of(context).colorScheme.primaryContainer, borderRadius: BorderRadius.circular(8), ), child: Text( 'Server-preview: target blir $mergedQuantity $mergedUnit.', style: TextStyle( color: Theme.of(context).colorScheme.onPrimaryContainer, ), ), ); } return const SizedBox.shrink(); }, ), if (sourceId != null && targetId != null) const SizedBox(height: 12), if (validationMessage != null) Container( width: double.infinity, padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: Theme.of(context).colorScheme.errorContainer, borderRadius: BorderRadius.circular(8), ), child: Text( validationMessage, style: TextStyle( color: Theme.of(context).colorScheme.onErrorContainer, ), ), ) else if (localMergedQuantity != null) Container( width: double.infinity, padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: Theme.of(context).colorScheme.primaryContainer, borderRadius: BorderRadius.circular(8), ), child: Text( 'Lokal förhandsvisning: target blir ${localMergedQuantity.toStringAsFixed(2)} ${target?.unit ?? ''}.', style: TextStyle( color: Theme.of(context).colorScheme.onPrimaryContainer, ), ), ), ], ), ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), child: const Text('Avbryt'), ), FilledButton( onPressed: canMerge ? () => Navigator.of(context).pop(true) : null, child: const Text('Merge'), ), ], ); }, ); }, ); if (ok != true || sourceId == null || targetId == null) return; try { await ref.read(adminRepositoryProvider).mergeAdminInventory( sourceInventoryId: sourceId!, targetInventoryId: targetId!, ); if (!mounted) return; await _load(); if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Inventory merge genomförd.')), ); } catch (e) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( buildCopyableErrorSnackBar(context, mapErrorToUserMessage(e, context)), ); } } Future<_InventoryFormValues?> _showInventoryFormDialog({ AdminInventoryItem? initial, int? initialOwnerUserId, }) { return showDialog<_InventoryFormValues>( context: context, builder: (context) => _InventoryFormDialog( users: _users, products: _products, categories: _categories, initial: initial, initialOwnerUserId: initialOwnerUserId, ), ); } @override Widget build(BuildContext context) { final theme = Theme.of(context); if (_isLoading) { return const Center(child: CircularProgressIndicator()); } if (_error != null) { final message = _error!; return Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 720), child: Card( margin: const EdgeInsets.all(16), child: Padding( padding: const EdgeInsets.all(16), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Kunde inte läsa inventory-data', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600), ), const SizedBox(height: 8), SelectableText(message), const SizedBox(height: 12), Wrap( spacing: 8, runSpacing: 8, children: [ FilledButton.icon( onPressed: _load, icon: const Icon(Icons.refresh), label: const Text('Försök igen'), ), OutlinedButton.icon( onPressed: () { Clipboard.setData(ClipboardData(text: message)); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Felmeddelande kopierat.')), ); }, icon: const Icon(Icons.copy_all), label: const Text('Kopiera fel'), ), ], ), ], ), ), ), ), ); } final filtered = _filtered; return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Inventory', style: theme.textTheme.titleMedium), const SizedBox(height: 8), Text( 'Här arbetar du på användarnas inventory-poster. Du kan filtrera per användare, justera mängder, flytta poster till baslager och slå ihop duplicerade rader.', style: theme.textTheme.bodyMedium, ), const SizedBox(height: 8), const Wrap( spacing: 8, runSpacing: 8, children: [ Chip(label: Text('User-scope')), Chip(label: Text('Merge')), Chip(label: Text('Flytta till baslager')), ], ), ], ), ), ), const SizedBox(height: 12), Row( children: [ SizedBox( width: 300, child: DropdownButtonFormField( initialValue: _selectedUserId, decoration: const InputDecoration(labelText: 'Filtrera användare'), items: [ const DropdownMenuItem( value: null, child: Text('Alla användare'), ), ..._users.map( (u) => DropdownMenuItem( value: u.id, child: Text( '${u.displayName} (${u.username})', overflow: TextOverflow.ellipsis, ), ), ), ], onChanged: (value) { setState(() => _selectedUserId = value); _load(); }, ), ), const SizedBox(width: 8), SizedBox( width: 220, child: DropdownButtonFormField<_InventorySort>( initialValue: _sort, decoration: const InputDecoration(labelText: 'Sortering'), items: _InventorySort.values .map( (s) => DropdownMenuItem<_InventorySort>( value: s, child: Text(_sortLabel(s)), ), ) .toList(), onChanged: (value) { if (value == null) return; setState(() => _sort = value); _load(); }, ), ), const SizedBox(width: 8), Expanded( child: TextField( decoration: const InputDecoration( prefixIcon: Icon(Icons.search), hintText: 'Sök produkt, användare eller plats', ), onChanged: (value) => setState(() => _search = value), ), ), const SizedBox(width: 8), OutlinedButton.icon( onPressed: _load, icon: const Icon(Icons.refresh), label: const Text('Uppdatera'), ), const SizedBox(width: 8), OutlinedButton.icon( onPressed: _mergeItems, icon: const Icon(Icons.merge_type), label: const Text('Merge'), ), const SizedBox(width: 8), FilledButton.icon( onPressed: _addItem, icon: const Icon(Icons.add), label: const Text('Lägg till'), ), ], ), const SizedBox(height: 8), Text('Visar ${filtered.length} av ${_items.length} inventory-poster'), const SizedBox(height: 8), Expanded( child: Card( child: filtered.isEmpty ? Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text('Inventory', style: theme.textTheme.titleMedium), const SizedBox(height: 8), Text( 'Inga inventory-poster hittades med nuvarande filter.', style: theme.textTheme.bodyMedium, ), ], ), ) : ListView.separated( itemCount: filtered.length, separatorBuilder: (_, __) => const Divider(height: 1), itemBuilder: (context, index) { final item = filtered[index]; return ListTile( title: Text(item.displayName), subtitle: Text( '${item.quantity} ${item.unit} · ${item.username} (${item.userEmail})' '${item.location == null || item.location!.isEmpty ? '' : ' · ${item.location}'}' '${item.categoryPath == null || item.categoryPath!.isEmpty ? '' : ' · ${item.categoryPath}'}', ), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ IconButton( tooltip: 'Flytta till baslager', onPressed: () async { try { await ref.read(adminRepositoryProvider).moveAdminInventoryToPantry(item.id); if (!mounted) return; await _load(); if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Flyttade "${item.displayName}" till baslager.')), ); } catch (e) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( buildCopyableErrorSnackBar(context, mapErrorToUserMessage(e, context)), ); } }, icon: const Icon(Icons.storefront_outlined), ), IconButton( tooltip: 'Ändra', onPressed: () => _editItem(item), icon: const Icon(Icons.edit_outlined), ), IconButton( tooltip: 'Ta bort', onPressed: () => _deleteItem(item), icon: const Icon(Icons.delete_outline), ), ], ), ); }, ), ), ), ], ); } } class _InventoryFormValues { final int? ownerUserId; final int productId; final double quantity; final String unit; final String? location; final String? brand; final String? receiptName; final String? suitableFor; final String? comment; const _InventoryFormValues({ this.ownerUserId, required this.productId, required this.quantity, required this.unit, this.location, this.brand, this.receiptName, this.suitableFor, this.comment, }); } class _InventoryFormDialog extends StatefulWidget { final List users; final List products; final List categories; final AdminInventoryItem? initial; final int? initialOwnerUserId; const _InventoryFormDialog({ required this.users, required this.products, required this.categories, this.initial, this.initialOwnerUserId, }); @override State<_InventoryFormDialog> createState() => _InventoryFormDialogState(); } class _InventoryFormDialogState extends State<_InventoryFormDialog> { final _formKey = GlobalKey(); late final TextEditingController _quantityController; late final TextEditingController _unitController; late final TextEditingController _locationController; late final TextEditingController _brandController; late final TextEditingController _receiptNameController; late final TextEditingController _suitableForController; late final TextEditingController _commentController; int? _ownerUserId; int? _productId; int? _categoryId; String? _categoryPath; String? _productErrorText; @override void initState() { super.initState(); final initial = widget.initial; _ownerUserId = initial?.userId ?? widget.initialOwnerUserId; _productId = initial?.productId; final initialProduct = _productById(_productId); _categoryId = initialProduct?.categoryId; _categoryPath = initialProduct?.categoryPath; _quantityController = TextEditingController( text: initial == null ? '' : initial.quantity.toString(), ); _unitController = TextEditingController(text: initial?.unit ?? 'st'); _locationController = TextEditingController(text: initial?.location ?? ''); _brandController = TextEditingController(text: initial?.brand ?? ''); _receiptNameController = TextEditingController(text: initial?.receiptName ?? ''); _suitableForController = TextEditingController(text: initial?.suitableFor ?? ''); _commentController = TextEditingController(text: initial?.comment ?? ''); } @override void dispose() { _quantityController.dispose(); _unitController.dispose(); _locationController.dispose(); _brandController.dispose(); _receiptNameController.dispose(); _suitableForController.dispose(); _commentController.dispose(); super.dispose(); } AdminProduct? _productById(int? id) { if (id == null) return null; for (final product in widget.products) { if (product.id == id) return product; } return null; } List _productOptions() { final source = _categoryId == null ? widget.products : widget.products.where((p) => p.categoryId == _categoryId).toList(); final sorted = [...source] ..sort((a, b) => a.displayName.toLowerCase().compareTo(b.displayName.toLowerCase())); return sorted .map((p) => (id: p.id, name: p.displayName, categoryId: p.categoryId)) .toList(); } Future _pickCategory() async { final selected = await CategoryThenProductPicker.showCategorySheet( context, categoryTree: widget.categories, preselectedCategoryId: _categoryId, ); if (selected == null || !mounted) return; setState(() { _categoryId = selected.id; _categoryPath = selected.path; if (_productId != null) { final current = _productById(_productId); if (current?.categoryId != _categoryId) { _productId = null; } } }); } @override Widget build(BuildContext context) { return AlertDialog( title: Text(widget.initial == null ? 'Lägg till inventory-post' : 'Ändra inventory-post'), content: SizedBox( width: 480, child: Form( key: _formKey, child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( widget.initial == null ? 'Skapa en ny inventory-rad för en användare. Välj produkt, mängd, enhet och valfria metadata.' : 'Ändra den valda inventory-raden. Produkt, mängd, enhet och metadata kan justeras utan att byta ägare.', style: Theme.of(context).textTheme.bodyMedium, ), const SizedBox(height: 12), if (widget.initial == null) ...[ DropdownButtonFormField( initialValue: _ownerUserId, items: widget.users .map((u) => DropdownMenuItem( value: u.id, child: Text( '${u.displayName} (${u.username})', overflow: TextOverflow.ellipsis, ), )) .toList(), onChanged: (value) => setState(() => _ownerUserId = value), decoration: const InputDecoration(labelText: 'Ägare (användare)'), validator: (value) => value == null ? 'Välj användare' : null, ), const SizedBox(height: 12), ] else ...[ Align( alignment: Alignment.centerLeft, child: Text( 'Ägare: ${widget.initial!.username} (${widget.initial!.userEmail})', style: Theme.of(context).textTheme.bodyMedium, ), ), const SizedBox(height: 12), ], GestureDetector( onTap: _pickCategory, child: InputDecorator( decoration: const InputDecoration( labelText: 'Kategori', border: OutlineInputBorder(), ), child: Text( _categoryPath == null || _categoryPath!.trim().isEmpty ? 'Tryck för att välja kategori' : _categoryPath!, ), ), ), const SizedBox(height: 12), Row( children: [ OutlinedButton.icon( onPressed: _pickCategory, icon: const Icon(Icons.category_outlined), label: const Text('Välj kategori'), ), const SizedBox(width: 8), OutlinedButton.icon( onPressed: () { setState(() { _categoryId = null; _categoryPath = null; }); }, icon: const Icon(Icons.clear), label: const Text('Rensa kategori'), ), ], ), const SizedBox(height: 12), ProductPickerField( products: _productOptions(), value: _productId, label: 'Produkt', errorText: _productErrorText, onChanged: (value) { setState(() { _productId = value; _productErrorText = null; }); }, ), const SizedBox(height: 12), TextFormField( controller: _quantityController, keyboardType: const TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration(labelText: 'Mängd'), validator: (value) { final parsed = double.tryParse((value ?? '').replaceAll(',', '.')); if (parsed == null || parsed < 0) return 'Ange en giltig mängd'; return null; }, ), const SizedBox(height: 12), TextFormField( controller: _unitController, decoration: const InputDecoration(labelText: 'Enhet'), validator: (value) => (value == null || value.trim().isEmpty) ? 'Ange enhet' : null, ), const SizedBox(height: 12), TextFormField( controller: _locationController, decoration: const InputDecoration(labelText: 'Plats (valfritt)'), ), const SizedBox(height: 12), TextFormField( controller: _brandController, decoration: const InputDecoration(labelText: 'Varumärke (valfritt)'), ), const SizedBox(height: 12), TextFormField( controller: _receiptNameController, decoration: const InputDecoration(labelText: 'Kvittonamn (valfritt)'), ), const SizedBox(height: 12), TextFormField( controller: _suitableForController, decoration: const InputDecoration(labelText: 'Passar till (valfritt)'), ), const SizedBox(height: 12), TextFormField( controller: _commentController, decoration: const InputDecoration(labelText: 'Kommentar (valfritt)'), maxLines: 3, ), ], ), ), ), ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Avbryt'), ), FilledButton( onPressed: () { if (!_formKey.currentState!.validate()) return; if (_productId == null) { setState(() => _productErrorText = 'Välj en produkt'); return; } final quantity = double.parse(_quantityController.text.trim().replaceAll(',', '.')); Navigator.of(context).pop( _InventoryFormValues( ownerUserId: _ownerUserId, productId: _productId!, quantity: quantity, unit: _unitController.text.trim(), location: _locationController.text.trim().isEmpty ? null : _locationController.text.trim(), brand: _brandController.text.trim().isEmpty ? null : _brandController.text.trim(), receiptName: _receiptNameController.text.trim().isEmpty ? null : _receiptNameController.text.trim(), suitableFor: _suitableForController.text.trim().isEmpty ? null : _suitableForController.text.trim(), comment: _commentController.text.trim().isEmpty ? null : _commentController.text.trim(), ), ); }, child: const Text('Spara'), ), ], ); } }