diff --git a/flutter/lib/features/admin/data/admin_repository.dart b/flutter/lib/features/admin/data/admin_repository.dart index 0f7fe1c6..e7340fd9 100644 --- a/flutter/lib/features/admin/data/admin_repository.dart +++ b/flutter/lib/features/admin/data/admin_repository.dart @@ -42,7 +42,7 @@ class AdminRepository { return _parseList(data, fromJson); } - /// POST-anrop som returnerar ett typad objekt med [fromJson]. + /// POST-anrop som returnerar ett typat objekt med [fromJson]. Future _post( String path, { required Map? body, @@ -57,7 +57,7 @@ class AdminRepository { return parse(data); } - /// PATCH-anrop som returnerar ett typad objekt med [fromJson]. + /// PATCH-anrop som returnerar ett typat objekt med [fromJson]. Future _patch( String path, { required Map body, @@ -98,6 +98,31 @@ class AdminRepository { ); } + Future> _getMap( + String path, { + bool requiresAuth = true, + }) async { + final token = requiresAuth ? await _token() : null; + final data = await guardedApiCall( + _ref, + () => _apiClient.getJson(path, token: token), + ); + return Map.from(data as Map); + } + + Future> _postMap( + String path, { + Map? body, + bool requiresAuth = true, + }) async { + final token = requiresAuth ? await _token() : null; + final data = await guardedApiCall( + _ref, + () => _apiClient.postJson(path, body: body, token: token), + ); + return Map.from(data as Map); + } + /// Tolerant listparsning — accepterar ren lista eller wrapper ({items, data}). static List _parseList( dynamic data, @@ -164,17 +189,16 @@ class AdminRepository { /// Returns `{ temporaryPassword, to, subject, body }`. Future> resetPassword(int userId) => - _post>( - UserApiPaths.resetPassword(userId), - body: null, - parse: (d) => d as Map, - ); + _postMap(UserApiPaths.resetPassword(userId), body: null); // ── Produkter ────────────────────────────────────────────────────────────── Future> listProducts() => _getList(ProductApiPaths.list, AdminProduct.fromJson, requiresAuth: false); + @Deprecated('Use listProducts(). Kept for temporary compatibility.') + Future> listGlobalProducts() => listProducts(); + Future> listPrivateProducts() => _getList(ProductApiPaths.privateList, PendingProduct.fromJson); @@ -204,8 +228,6 @@ class AdminRepository { _postVoid(ProductApiPaths.restore(productId)); // ── Product canonical name updates ──────────────────────────────────────── - // Admin can update any product; users can only update their own private products - Future updateCanonicalName(int productId, String canonicalName) => _patchVoid( ProductApiPaths.canonicalName(productId), @@ -219,8 +241,6 @@ class AdminRepository { ); // ── Product merging ──────────────────────────────────────────────────────── - // Admin can merge any products; users can only merge their own private products - Future mergeProductsPrivate({ required int sourceProductId, required int targetProductId, @@ -232,23 +252,28 @@ class AdminRepository { /// Skapar en ny aktiv produkt (kräver admin). Returnerar `{id, name, categoryId?}`. Future> createProduct(String name, {int? categoryId}) => - _post>( + _postMap( ProductApiPaths.list, body: { 'name': name.trim(), if (categoryId != null) 'categoryId': categoryId, }, - parse: (d) => d as Map, ); + int _parseUpdatedCount(dynamic data) { + if (data is! Map) { + debugPrint('[AdminRepository] bulkSetCategory unexpected response type: ${data.runtimeType}'); + return 0; + } + final map = Map.from(data); + return (map['updated'] as num?)?.toInt() ?? 0; + } + Future bulkSetCategory(List ids, {required int? categoryId}) => _post( ProductApiPaths.bulkUpdate, body: {'ids': ids, 'categoryId': categoryId}, - parse: (d) { - final map = Map.from(d as Map); - return (map['updated'] as num?)?.toInt() ?? 0; - }, + parse: _parseUpdatedCount, ); Future mergeProducts({ @@ -263,17 +288,8 @@ class AdminRepository { Future> previewMerge({ required int sourceProductId, required int targetProductId, - }) async { - final token = await _token(); - final data = await guardedApiCall( - _ref, - () => _apiClient.getJson( - ProductApiPaths.mergePreview(sourceProductId, targetProductId), - token: token, - ), - ); - return Map.from(data as Map); - } + }) => + _getMap(ProductApiPaths.mergePreview(sourceProductId, targetProductId)); Future> aiCategorizeBulk({ List? productIds, @@ -403,28 +419,28 @@ class AdminRepository { Future removeAdminInventory(int inventoryId) => _deleteVoid(AdminInventoryApiPaths.remove(inventoryId)); - Future moveAdminInventoryToPantry(int inventoryId) => + Future moveAdminInventoryToPantry(int inventoryId) => _postVoid(AdminInventoryApiPaths.moveToPantry(inventoryId)); - // ── Admin pantry ────────────────────────────────────────────────────────── + // ── Admin pantry ────────────────────────────────────────────────────────── - Future> listAdminPantry({int? userId}) { - final params = {}; - if (userId != null) params['userId'] = '$userId'; - final path = params.isEmpty - ? PantryApiPaths.adminList - : '${PantryApiPaths.adminList}?${params.entries.map((e) => '${Uri.encodeQueryComponent(e.key)}=${Uri.encodeQueryComponent(e.value)}').join('&')}'; - return _getList(path, AdminPantryItem.fromJson); - } + Future> listAdminPantry({int? userId}) { + final params = {}; + if (userId != null) params['userId'] = '$userId'; + final path = params.isEmpty + ? PantryApiPaths.adminList + : '${PantryApiPaths.adminList}?${params.entries.map((e) => '${Uri.encodeQueryComponent(e.key)}=${Uri.encodeQueryComponent(e.value)}').join('&')}'; + return _getList(path, AdminPantryItem.fromJson); + } - Future removeAdminPantryItem(int pantryItemId) => - _deleteVoid(PantryApiPaths.adminRemove(pantryItemId)); + Future removeAdminPantryItem(int pantryItemId) => + _deleteVoid(PantryApiPaths.adminRemove(pantryItemId)); - Future moveAdminPantryToInventory( - int pantryItemId, - Map body, - ) => - _postVoid(PantryApiPaths.moveToInventoryAdmin(pantryItemId), body); + Future moveAdminPantryToInventory( + int pantryItemId, + Map body, + ) => + _postVoid(PantryApiPaths.moveToInventoryAdmin(pantryItemId), body); Future mergeAdminInventory({ required int sourceInventoryId, @@ -438,15 +454,8 @@ class AdminRepository { Future> previewAdminInventoryMerge({ required int sourceInventoryId, required int targetInventoryId, - }) async { - final token = await _token(); - final data = await guardedApiCall( - _ref, - () => _apiClient.getJson( + }) => + _getMap( AdminInventoryApiPaths.mergePreview(sourceInventoryId, targetInventoryId), - token: token, - ), - ); - return Map.from(data as Map); - } + ); }