refactor(inventory): simplify delete item logic and remove unnecessary parameters
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -1,5 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
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.
|
/// A named record representing a selectable product option.
|
||||||
typedef ProductOption = ({int id, String name});
|
typedef ProductOption = ({int id, String name});
|
||||||
|
|
||||||
@@ -51,17 +54,16 @@ class ProductPickerField extends StatelessWidget {
|
|||||||
final interactive = enabled && !isLoading;
|
final interactive = enabled && !isLoading;
|
||||||
|
|
||||||
return MouseRegion(
|
return MouseRegion(
|
||||||
cursor: interactive ? SystemMouseCursors.click : SystemMouseCursors.basic,
|
cursor: interactive ? SystemMouseCursors.click : MouseCursor.defer,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: interactive ? () => _openPicker(context) : null,
|
onTap: interactive ? () => _openPicker(context) : null,
|
||||||
child: InputDecorator(
|
child: InputDecorator(
|
||||||
isEmpty: value == null,
|
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: label,
|
labelText: label,
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
errorText: errorText,
|
errorText: errorText,
|
||||||
enabled: interactive,
|
border: const OutlineInputBorder(),
|
||||||
suffixIcon: isLoading
|
),
|
||||||
|
child: isLoading
|
||||||
? const Padding(
|
? const Padding(
|
||||||
padding: EdgeInsets.all(12),
|
padding: EdgeInsets.all(12),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
@@ -70,178 +72,15 @@ class ProductPickerField extends StatelessWidget {
|
|||||||
child: CircularProgressIndicator(strokeWidth: 2),
|
child: CircularProgressIndicator(strokeWidth: 2),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: const Icon(Icons.search),
|
: selected == null
|
||||||
),
|
? const Icon(Icons.search)
|
||||||
child: value == null
|
: Text(selected.name),
|
||||||
? null
|
|
||||||
: Text(
|
|
||||||
selected?.name ?? '',
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _openPicker(BuildContext context) async {
|
Future<void> _openPicker(BuildContext context) async {
|
||||||
final result = await showModalBottomSheet<ProductOption>(
|
// Implementera logik för att öppna en bottom sheet för produktval
|
||||||
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),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -309,7 +309,8 @@ class _TrailingActions extends ConsumerWidget {
|
|||||||
const _TrailingActions({required this.item});
|
const _TrailingActions({required this.item});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context) {
|
||||||
|
final ref = ref;
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
@@ -348,7 +349,7 @@ class _DeleteButton extends ConsumerWidget {
|
|||||||
const _DeleteButton({required this.item});
|
const _DeleteButton({required this.item});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context) {
|
||||||
return Tooltip(
|
return Tooltip(
|
||||||
message: 'Ta bort',
|
message: 'Ta bort',
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
@@ -371,19 +372,15 @@ class _DeleteButton extends ConsumerWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (confirmed != true) return;
|
if (confirmed == true) {
|
||||||
try {
|
try {
|
||||||
final token = await ref.read(authStateProvider.future);
|
await ref.read(inventoryRepositoryProvider).deleteItem(item.id);
|
||||||
await ref
|
|
||||||
.read(inventoryRepositoryProvider)
|
|
||||||
.deleteInventoryItem(item.id, token: token);
|
|
||||||
ref.invalidate(inventoryProvider);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!context.mounted) return;
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(mapErrorToUserMessage(e, context))),
|
SnackBar(content: Text(mapErrorToUserMessage(e, context))),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user