feat: enhance admin product management with AI categorization, product status updates, and email editing for users

This commit is contained in:
Nils-Johan Gynther
2026-04-25 08:46:54 +02:00
parent a02950c97a
commit 6abe69e12d
6 changed files with 781 additions and 65 deletions
@@ -3,6 +3,7 @@ import '../../../core/api/api_client.dart';
import '../../../core/api/api_paths.dart';
import '../../../core/api/guarded_api_call.dart';
import '../../auth/data/auth_providers.dart';
import '../domain/admin_ai_categorize_result.dart';
import '../domain/admin_category_node.dart';
import '../domain/admin_product.dart';
import '../domain/ai_model_info.dart';
@@ -48,6 +49,18 @@ class AdminRepository {
return UserAdmin.fromJson(data);
}
Future<void> updateEmail(int userId, String email) async {
final token = await _token();
await guardedApiCall(
_ref,
() => _apiClient.patchJson(
UserApiPaths.updateEmail(userId),
body: {'email': email},
token: token,
),
);
}
Future<UserAdmin> createUser({
required String username,
required String email,
@@ -119,9 +132,21 @@ class AdminRepository {
}
Future<List<AdminProduct>> listProducts() async {
final token = await _token();
final data = await guardedApiCall(
_ref,
() => _apiClient.getJson(ProductApiPaths.list),
() => _apiClient.getJson(ProductApiPaths.list, token: token),
);
return (data as List<dynamic>)
.map((e) => AdminProduct.fromJson(e as Map<String, dynamic>))
.toList();
}
Future<List<AdminProduct>> listDeletedProducts() async {
final token = await _token();
final data = await guardedApiCall(
_ref,
() => _apiClient.getJson(ProductApiPaths.deleted, token: token),
);
return (data as List<dynamic>)
.map((e) => AdminProduct.fromJson(e as Map<String, dynamic>))
@@ -129,9 +154,10 @@ class AdminRepository {
}
Future<List<AdminCategoryNode>> listCategoryTree() async {
final token = await _token();
final data = await guardedApiCall(
_ref,
() => _apiClient.getJson(CategoryApiPaths.tree),
() => _apiClient.getJson(CategoryApiPaths.tree, token: token),
);
return (data as List<dynamic>)
.map((e) => AdminCategoryNode.fromJson(e as Map<String, dynamic>))
@@ -149,4 +175,70 @@ class AdminRepository {
),
);
}
Future<void> setProductCategory(int productId, {required int? categoryId}) async {
final token = await _token();
await guardedApiCall(
_ref,
() => _apiClient.patchJson(
ProductApiPaths.update(productId),
body: {'categoryId': categoryId},
token: token,
),
);
}
Future<void> removeProduct(int productId) async {
final token = await _token();
await guardedApiCall(
_ref,
() => _apiClient.deleteJson(ProductApiPaths.remove(productId), token: token),
);
}
Future<void> restoreProduct(int productId) async {
final token = await _token();
await guardedApiCall(
_ref,
() => _apiClient.postJson(ProductApiPaths.restore(productId), token: token),
);
}
Future<void> mergeProducts({
required int sourceProductId,
required int targetProductId,
}) async {
final token = await _token();
await guardedApiCall(
_ref,
() => _apiClient.postJson(
ProductApiPaths.merge,
body: {
'sourceProductId': sourceProductId,
'targetProductId': targetProductId,
},
token: token,
),
);
}
Future<List<AdminAiCategorizeResult>> aiCategorizeBulk({
List<int>? productIds,
}) async {
final token = await _token();
final data = await guardedApiCall(
_ref,
() => _apiClient.postJson(
ProductApiPaths.aiCategorizeBulk,
body: productIds == null || productIds.isEmpty
? null
: {'productIds': productIds},
token: token,
),
);
return (data as List<dynamic>)
.map((e) => AdminAiCategorizeResult.fromJson(e as Map<String, dynamic>))
.where((e) => e.productId > 0 && e.categoryId > 0)
.toList();
}
}