feat: implement dropdowns for unit and location selection in inventory forms; add product sorting functionality
This commit is contained in:
@@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../../core/api/api_error_mapper.dart';
|
||||
import '../../../core/api/api_providers.dart';
|
||||
import '../../../core/forms/form_options.dart';
|
||||
import '../../auth/data/auth_providers.dart';
|
||||
import '../data/inventory_providers.dart';
|
||||
|
||||
@@ -135,6 +136,13 @@ class _CreateInventoryScreenState
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final sortedProducts = [..._products]
|
||||
..sort((a, b) {
|
||||
final aName = (a['canonicalName'] ?? a['name'] ?? '').toString();
|
||||
final bName = (b['canonicalName'] ?? b['name'] ?? '').toString();
|
||||
return aName.toLowerCase().compareTo(bName.toLowerCase());
|
||||
});
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Lagg till inventariepost')),
|
||||
body: Form(
|
||||
@@ -142,41 +150,39 @@ class _CreateInventoryScreenState
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
Autocomplete<Map<String, dynamic>>(
|
||||
optionsBuilder: (textEditingValue) {
|
||||
if (textEditingValue.text.isEmpty) return const [];
|
||||
final q = textEditingValue.text.toLowerCase();
|
||||
return _products
|
||||
.where((p) =>
|
||||
(p['name'] as String).toLowerCase().contains(q))
|
||||
.take(10);
|
||||
},
|
||||
displayStringForOption: (option) => option['name'] as String,
|
||||
onSelected: (option) {
|
||||
setState(() => _selectedProductId = option['id'] as int);
|
||||
},
|
||||
fieldViewBuilder:
|
||||
(context, controller, focusNode, onFieldSubmitted) {
|
||||
return TextFormField(
|
||||
controller: controller,
|
||||
focusNode: focusNode,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Produkt *',
|
||||
border: const OutlineInputBorder(),
|
||||
suffixIcon: _loadingProducts
|
||||
? const Padding(
|
||||
padding: EdgeInsets.all(12),
|
||||
child: SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
enabled: !_saving,
|
||||
);
|
||||
},
|
||||
DropdownButtonFormField<int>(
|
||||
value: _selectedProductId,
|
||||
isExpanded: true,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Produkt *',
|
||||
border: const OutlineInputBorder(),
|
||||
suffixIcon: _loadingProducts
|
||||
? const Padding(
|
||||
padding: EdgeInsets.all(12),
|
||||
child: SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
items: sortedProducts
|
||||
.map(
|
||||
(product) => DropdownMenuItem<int>(
|
||||
value: product['id'] as int,
|
||||
child: Text(
|
||||
((product['canonicalName'] ?? product['name']) as Object)
|
||||
.toString(),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
onChanged: (_loadingProducts || _saving)
|
||||
? null
|
||||
: (value) => setState(() => _selectedProductId = value),
|
||||
validator: (value) => value == null ? 'Valj produkt' : null,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
@@ -205,27 +211,56 @@ class _CreateInventoryScreenState
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: _unitController,
|
||||
child: DropdownButtonFormField<String>(
|
||||
value: _unitController.text.trim().isEmpty
|
||||
? null
|
||||
: _unitController.text.trim(),
|
||||
isExpanded: true,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Enhet *',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
enabled: !_saving,
|
||||
validator: (v) =>
|
||||
(v == null || v.trim().isEmpty) ? 'Ange enhet' : null,
|
||||
items: unitOptions
|
||||
.map(
|
||||
(option) => DropdownMenuItem<String>(
|
||||
value: option.value,
|
||||
child: Text(option.label, overflow: TextOverflow.ellipsis),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
onChanged: _saving
|
||||
? null
|
||||
: (value) =>
|
||||
setState(() => _unitController.text = value ?? ''),
|
||||
validator: (value) =>
|
||||
(value == null || value.trim().isEmpty) ? 'Ange enhet' : null,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextFormField(
|
||||
controller: _locationController,
|
||||
DropdownButtonFormField<String>(
|
||||
value: _locationController.text.trim().isEmpty
|
||||
? null
|
||||
: _locationController.text.trim(),
|
||||
isExpanded: true,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Plats (valfri)',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
enabled: !_saving,
|
||||
items: inventoryLocationOptions
|
||||
.map(
|
||||
(location) => DropdownMenuItem<String>(
|
||||
value: location,
|
||||
child: Text(location),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
onChanged: _saving
|
||||
? null
|
||||
: (value) => setState(() {
|
||||
_locationController.text = value ?? '';
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextFormField(
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../../core/api/api_error_mapper.dart';
|
||||
import '../../../core/forms/form_options.dart';
|
||||
import '../../../core/ui/async_state_views.dart';
|
||||
import '../../auth/data/auth_providers.dart';
|
||||
import '../data/inventory_providers.dart';
|
||||
@@ -173,13 +174,27 @@ class _InventoryEditScreenState extends ConsumerState<InventoryEditScreen> {
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: _unitController,
|
||||
child: DropdownButtonFormField<String>(
|
||||
value: _unitController.text.trim().isEmpty
|
||||
? null
|
||||
: _unitController.text.trim(),
|
||||
isExpanded: true,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Enhet *',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
enabled: !_saving,
|
||||
items: unitOptions
|
||||
.map(
|
||||
(option) => DropdownMenuItem<String>(
|
||||
value: option.value,
|
||||
child: Text(option.label, overflow: TextOverflow.ellipsis),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
onChanged: _saving
|
||||
? null
|
||||
: (value) =>
|
||||
setState(() => _unitController.text = value ?? ''),
|
||||
validator: (v) => (v == null || v.trim().isEmpty)
|
||||
? 'Ange enhet'
|
||||
: null,
|
||||
@@ -188,13 +203,27 @@ class _InventoryEditScreenState extends ConsumerState<InventoryEditScreen> {
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextFormField(
|
||||
controller: _locationController,
|
||||
DropdownButtonFormField<String>(
|
||||
value: _locationController.text.trim().isEmpty
|
||||
? null
|
||||
: _locationController.text.trim(),
|
||||
isExpanded: true,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Plats',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
enabled: !_saving,
|
||||
items: inventoryLocationOptions
|
||||
.map(
|
||||
(location) => DropdownMenuItem<String>(
|
||||
value: location,
|
||||
child: Text(location),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
onChanged: _saving
|
||||
? null
|
||||
: (value) =>
|
||||
setState(() => _locationController.text = value ?? ''),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextFormField(
|
||||
|
||||
Reference in New Issue
Block a user