feat: enhance receipt import functionality with category selection and PDF opening support
This commit is contained in:
@@ -13,6 +13,20 @@ import '../../features/admin/domain/admin_category_node.dart';
|
||||
class CategoryThenProductPicker {
|
||||
CategoryThenProductPicker._();
|
||||
|
||||
static List<String>? _findPath(
|
||||
List<AdminCategoryNode> nodes,
|
||||
int id,
|
||||
List<String> parents,
|
||||
) {
|
||||
for (final node in nodes) {
|
||||
final path = [...parents, node.name];
|
||||
if (node.id == id) return path;
|
||||
final found = _findPath(node.children, id, path);
|
||||
if (found != null) return found;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Samlar alla ID:n för [node] och alla dess ättlingar rekursivt.
|
||||
static Set<int> _collectIds(AdminCategoryNode node) {
|
||||
final ids = <int>{node.id};
|
||||
@@ -74,7 +88,7 @@ class CategoryThenProductPicker {
|
||||
}
|
||||
|
||||
// Samla alla kategori-IDs i den valda grenen (inkl. ättlingar)
|
||||
final categoryIds = _collectIds(selectedCategory!);
|
||||
final categoryIds = _collectIds(selectedCategory);
|
||||
|
||||
// Filtrera produkter på dessa kategorier
|
||||
final filtered = products
|
||||
@@ -93,12 +107,39 @@ class CategoryThenProductPicker {
|
||||
context,
|
||||
products: useList,
|
||||
value: currentProductId,
|
||||
label: 'Produkt i "${selectedCategory!.name}"',
|
||||
label: 'Produkt i "${selectedCategory.name}"',
|
||||
categoryFilter: null, // redan förfiltrerat
|
||||
initialQuery: initialQuery,
|
||||
onCreate: onCreateBound,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<({int id, String name, String path})?> showCategorySheet(
|
||||
BuildContext context, {
|
||||
required List<AdminCategoryNode> categoryTree,
|
||||
int? preselectedCategoryId,
|
||||
}) async {
|
||||
if (!context.mounted) return null;
|
||||
final selectedCategory = await showModalBottomSheet<AdminCategoryNode>(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
useSafeArea: true,
|
||||
builder: (ctx) => _CategoryPickerSheet(
|
||||
tree: categoryTree,
|
||||
preselectedId: preselectedCategoryId,
|
||||
onSelected: (node) => Navigator.pop(ctx, node),
|
||||
),
|
||||
);
|
||||
if (selectedCategory == null) return null;
|
||||
final path = _findPath(categoryTree, selectedCategory.id, const [])
|
||||
?.join(' > ') ??
|
||||
selectedCategory.name;
|
||||
return (
|
||||
id: selectedCategory.id,
|
||||
name: selectedCategory.name,
|
||||
path: path,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Kategoriträdets bottenark ────────────────────────────────────────────────
|
||||
@@ -293,7 +334,7 @@ class _CategoryTileState extends State<_CategoryTile> {
|
||||
dense: true,
|
||||
selected: isPreselected,
|
||||
selectedColor: theme.colorScheme.primary,
|
||||
selectedTileColor: theme.colorScheme.primaryContainer.withOpacity(0.3),
|
||||
selectedTileColor: theme.colorScheme.primaryContainer.withValues(alpha: 0.3),
|
||||
leading: Icon(
|
||||
Icons.label_outline,
|
||||
size: 16,
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'pdf_opener_stub.dart'
|
||||
if (dart.library.js_interop) 'pdf_opener_web.dart' as impl;
|
||||
|
||||
Future<bool> openPdfBytes(Uint8List bytes) => impl.openPdfBytes(bytes);
|
||||
@@ -0,0 +1,3 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
Future<bool> openPdfBytes(Uint8List bytes) async => false;
|
||||
@@ -0,0 +1,19 @@
|
||||
import 'dart:js_interop';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:web/web.dart' as web;
|
||||
|
||||
Future<bool> openPdfBytes(Uint8List bytes) async {
|
||||
final blob = web.Blob(
|
||||
[bytes.toJS].toJS,
|
||||
web.BlobPropertyBag(type: 'application/pdf'),
|
||||
);
|
||||
final url = web.URL.createObjectURL(blob);
|
||||
final openedWindow = web.window.open(url, '_blank', 'noopener,noreferrer');
|
||||
if (openedWindow == null) {
|
||||
web.URL.revokeObjectURL(url);
|
||||
return false;
|
||||
}
|
||||
web.URL.revokeObjectURL(url);
|
||||
return true;
|
||||
}
|
||||
Reference in New Issue
Block a user