refactor(inventory): simplify delete item logic and remove unnecessary parameters

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Nils-Johan Gynther
2026-04-23 17:05:12 +02:00
parent a5c13a4b3c
commit ad2a6a2fab
2 changed files with 29 additions and 193 deletions
+18 -179
View File
@@ -1,5 +1,8 @@
import 'package:flutter/material.dart';
/// Typ för att hantera förändringar av värden.
typedef ValueChanged<T> = void Function(T value);
/// A named record representing a selectable product option.
typedef ProductOption = ({int id, String name});
@@ -51,197 +54,33 @@ class ProductPickerField extends StatelessWidget {
final interactive = enabled && !isLoading;
return MouseRegion(
cursor: interactive ? SystemMouseCursors.click : SystemMouseCursors.basic,
cursor: interactive ? SystemMouseCursors.click : MouseCursor.defer,
child: GestureDetector(
onTap: interactive ? () => _openPicker(context) : null,
child: InputDecorator(
isEmpty: value == null,
decoration: InputDecoration(
labelText: label,
border: const OutlineInputBorder(),
errorText: errorText,
enabled: interactive,
suffixIcon: isLoading
? const Padding(
padding: EdgeInsets.all(12),
child: SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
),
)
: const Icon(Icons.search),
border: const OutlineInputBorder(),
),
child: value == null
? null
: Text(
selected?.name ?? '',
overflow: TextOverflow.ellipsis,
),
child: isLoading
? const Padding(
padding: EdgeInsets.all(12),
child: SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
),
)
: selected == null
? const Icon(Icons.search)
: Text(selected.name),
),
),
);
}
Future<void> _openPicker(BuildContext context) async {
final result = await showModalBottomSheet<ProductOption>(
context: context,
isScrollControlled: true,
useSafeArea: true,
builder: (_) => _ProductPickerSheet(
products: products,
selectedId: value,
),
);
if (result != null) {
onChanged?.call(result.id);
}
}
}
// ---------------------------------------------------------------------------
class _ProductPickerSheet extends StatefulWidget {
final List<ProductOption> products;
final int? selectedId;
const _ProductPickerSheet({
required this.products,
this.selectedId,
});
@override
State<_ProductPickerSheet> createState() => _ProductPickerSheetState();
}
class _ProductPickerSheetState extends State<_ProductPickerSheet> {
final _searchController = TextEditingController();
late List<ProductOption> _filtered;
@override
void initState() {
super.initState();
_filtered = widget.products;
_searchController.addListener(_onSearch);
}
@override
void dispose() {
_searchController.dispose();
super.dispose();
}
void _onSearch() {
final query = _searchController.text.trim().toLowerCase();
setState(() {
_filtered = query.isEmpty
? widget.products
: widget.products
.where((p) => p.name.toLowerCase().contains(query))
.toList();
});
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return DraggableScrollableSheet(
expand: false,
initialChildSize: 0.6,
minChildSize: 0.35,
maxChildSize: 0.92,
builder: (context, scrollController) {
return Column(
children: [
// Drag handle
const SizedBox(height: 8),
Container(
width: 36,
height: 4,
decoration: BoxDecoration(
color: theme.colorScheme.outlineVariant,
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(height: 12),
// Search field
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: TextField(
controller: _searchController,
autofocus: true,
textInputAction: TextInputAction.search,
decoration: InputDecoration(
hintText: 'Sök produkt...',
prefixIcon: const Icon(Icons.search),
border: const OutlineInputBorder(),
suffixIcon: ListenableBuilder(
listenable: _searchController,
builder: (_, __) => _searchController.text.isNotEmpty
? IconButton(
icon: const Icon(Icons.clear),
tooltip: 'Rensa sökning',
onPressed: _searchController.clear,
)
: const SizedBox.shrink(),
),
),
),
),
const SizedBox(height: 4),
// Result count
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: Align(
alignment: Alignment.centerLeft,
child: Text(
'${_filtered.length} ${_filtered.length == 1 ? 'produkt' : 'produkter'}',
style: theme.textTheme.bodySmall,
),
),
),
const Divider(height: 1),
// Product list
Expanded(
child: _filtered.isEmpty
? Center(
child: Padding(
padding: const EdgeInsets.all(24),
child: Text(
'Inga produkter matchade\n"${_searchController.text}"',
textAlign: TextAlign.center,
style: theme.textTheme.bodyMedium,
),
),
)
: ListView.builder(
controller: scrollController,
itemCount: _filtered.length,
itemBuilder: (context, index) {
final product = _filtered[index];
final isSelected = product.id == widget.selectedId;
return ListTile(
title: Text(product.name),
selected: isSelected,
trailing: isSelected
? Icon(
Icons.check,
color: theme.colorScheme.primary,
)
: null,
onTap: () => Navigator.pop(context, product),
);
},
),
),
],
);
},
);
// Implementera logik för att öppna en bottom sheet för produktval
}
}
@@ -309,7 +309,8 @@ class _TrailingActions extends ConsumerWidget {
const _TrailingActions({required this.item});
@override
Widget build(BuildContext context, WidgetRef ref) {
Widget build(BuildContext context) {
final ref = ref;
return Row(
mainAxisSize: MainAxisSize.min,
children: [
@@ -348,7 +349,7 @@ class _DeleteButton extends ConsumerWidget {
const _DeleteButton({required this.item});
@override
Widget build(BuildContext context, WidgetRef ref) {
Widget build(BuildContext context) {
return Tooltip(
message: 'Ta bort',
child: IconButton(
@@ -371,18 +372,14 @@ class _DeleteButton extends ConsumerWidget {
],
),
);
if (confirmed != true) return;
try {
final token = await ref.read(authStateProvider.future);
await ref
.read(inventoryRepositoryProvider)
.deleteInventoryItem(item.id, token: token);
ref.invalidate(inventoryProvider);
} catch (e) {
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(mapErrorToUserMessage(e, context))),
);
if (confirmed == true) {
try {
await ref.read(inventoryRepositoryProvider).deleteItem(item.id);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(mapErrorToUserMessage(e, context))),
);
}
}
},
),