feat: Implement caching for selectable products and enhance product filtering in admin panels
Test Suite / test (24.15.0) (push) Has been cancelled
Test Suite / test (24.15.0) (push) Has been cancelled
This commit is contained in:
@@ -21,9 +21,18 @@ final adminRepositoryProvider = Provider<AdminRepository>((ref) {
|
||||
class AdminRepository {
|
||||
final ApiClient _apiClient;
|
||||
final Ref _ref;
|
||||
List<AdminProduct>? _selectableProductsCache;
|
||||
DateTime? _selectableProductsCacheAt;
|
||||
|
||||
static const Duration _selectableProductsCacheTtl = Duration(seconds: 45);
|
||||
|
||||
AdminRepository(this._apiClient, this._ref);
|
||||
|
||||
void _invalidateSelectableProductsCache() {
|
||||
_selectableProductsCache = null;
|
||||
_selectableProductsCacheAt = null;
|
||||
}
|
||||
|
||||
// ── Interna helpers ────────────────────────────────────────────────────────
|
||||
|
||||
Future<String?> _token() => _ref.read(authStateProvider.future);
|
||||
@@ -202,6 +211,50 @@ class AdminRepository {
|
||||
Future<List<PendingProduct>> listPrivateProducts() =>
|
||||
_getList(ProductApiPaths.privateList, PendingProduct.fromJson);
|
||||
|
||||
Future<List<AdminProduct>> listSelectableProductsForAdmin({
|
||||
bool forceRefresh = false,
|
||||
}) async {
|
||||
final now = DateTime.now();
|
||||
final cached = _selectableProductsCache;
|
||||
final cacheAt = _selectableProductsCacheAt;
|
||||
if (!forceRefresh && cached != null && cacheAt != null) {
|
||||
if (now.difference(cacheAt) <= _selectableProductsCacheTtl) {
|
||||
return List<AdminProduct>.from(cached);
|
||||
}
|
||||
}
|
||||
|
||||
final results = await Future.wait<dynamic>([
|
||||
listProducts(),
|
||||
listPrivateProducts(),
|
||||
]);
|
||||
final globalProducts = results[0] as List<AdminProduct>;
|
||||
final privateProducts = results[1] as List<PendingProduct>;
|
||||
|
||||
final merged = <int, AdminProduct>{
|
||||
for (final product in globalProducts) product.id: product,
|
||||
};
|
||||
|
||||
for (final product in privateProducts) {
|
||||
merged[product.id] = AdminProduct(
|
||||
id: product.id,
|
||||
name: product.name,
|
||||
canonicalName: product.canonicalName,
|
||||
ownerId: product.ownerId,
|
||||
categoryId: product.categoryId,
|
||||
categoryPath: product.categoryPath,
|
||||
status: 'private',
|
||||
);
|
||||
}
|
||||
|
||||
final list = merged.values.toList();
|
||||
list.sort(
|
||||
(a, b) => a.displayName.toLowerCase().compareTo(b.displayName.toLowerCase()),
|
||||
);
|
||||
_selectableProductsCache = List<AdminProduct>.from(list);
|
||||
_selectableProductsCacheAt = now;
|
||||
return list;
|
||||
}
|
||||
|
||||
Future<List<AdminProduct>> listDeletedProducts() =>
|
||||
_getList(ProductApiPaths.deleted, AdminProduct.fromJson);
|
||||
|
||||
@@ -209,36 +262,51 @@ class AdminRepository {
|
||||
_getList(ProductApiPaths.pending, PendingProduct.fromJson);
|
||||
|
||||
Future<void> setProductStatus(int productId, String status) =>
|
||||
_patchVoid(ProductApiPaths.setStatus(productId), {'status': status});
|
||||
_patchVoid(ProductApiPaths.setStatus(productId), {'status': status}).then((_) {
|
||||
_invalidateSelectableProductsCache();
|
||||
});
|
||||
|
||||
Future<AdminProduct> promotePrivateProduct(int productId) =>
|
||||
_post<AdminProduct>(
|
||||
ProductApiPaths.promotePrivate(productId),
|
||||
body: null,
|
||||
parse: (d) => AdminProduct.fromJson(Map<String, dynamic>.from(d as Map)),
|
||||
);
|
||||
).then((value) {
|
||||
_invalidateSelectableProductsCache();
|
||||
return value;
|
||||
});
|
||||
|
||||
Future<void> setProductCategory(int productId, {required int? categoryId}) =>
|
||||
_patchVoid(ProductApiPaths.update(productId), {'categoryId': categoryId});
|
||||
_patchVoid(ProductApiPaths.update(productId), {'categoryId': categoryId}).then((_) {
|
||||
_invalidateSelectableProductsCache();
|
||||
});
|
||||
|
||||
Future<void> removeProduct(int productId) =>
|
||||
_deleteVoid(ProductApiPaths.remove(productId));
|
||||
_deleteVoid(ProductApiPaths.remove(productId)).then((_) {
|
||||
_invalidateSelectableProductsCache();
|
||||
});
|
||||
|
||||
Future<void> restoreProduct(int productId) =>
|
||||
_postVoid(ProductApiPaths.restore(productId));
|
||||
_postVoid(ProductApiPaths.restore(productId)).then((_) {
|
||||
_invalidateSelectableProductsCache();
|
||||
});
|
||||
|
||||
// ── Product canonical name updates ────────────────────────────────────────
|
||||
Future<void> updateCanonicalName(int productId, String canonicalName) =>
|
||||
_patchVoid(
|
||||
ProductApiPaths.canonicalName(productId),
|
||||
{'canonicalName': canonicalName.trim()},
|
||||
);
|
||||
).then((_) {
|
||||
_invalidateSelectableProductsCache();
|
||||
});
|
||||
|
||||
Future<void> updateCanonicalNamePrivate(int productId, String canonicalName) =>
|
||||
_patchVoid(
|
||||
ProductApiPaths.canonicalNamePrivate(productId),
|
||||
{'canonicalName': canonicalName.trim()},
|
||||
);
|
||||
).then((_) {
|
||||
_invalidateSelectableProductsCache();
|
||||
});
|
||||
|
||||
// ── Product merging ────────────────────────────────────────────────────────
|
||||
Future<void> mergeProductsPrivate({
|
||||
@@ -248,6 +316,8 @@ class AdminRepository {
|
||||
_postVoid(ProductApiPaths.mergePrivate, {
|
||||
'sourceProductId': sourceProductId,
|
||||
'targetProductId': targetProductId,
|
||||
}).then((_) {
|
||||
_invalidateSelectableProductsCache();
|
||||
});
|
||||
|
||||
/// Skapar en ny aktiv produkt (kräver admin). Returnerar `{id, name, categoryId?}`.
|
||||
@@ -258,7 +328,10 @@ class AdminRepository {
|
||||
'name': name.trim(),
|
||||
if (categoryId != null) 'categoryId': categoryId,
|
||||
},
|
||||
);
|
||||
).then((value) {
|
||||
_invalidateSelectableProductsCache();
|
||||
return value;
|
||||
});
|
||||
|
||||
int _parseUpdatedCount(dynamic data) {
|
||||
if (data is! Map) {
|
||||
@@ -274,7 +347,10 @@ class AdminRepository {
|
||||
ProductApiPaths.bulkUpdate,
|
||||
body: {'ids': ids, 'categoryId': categoryId},
|
||||
parse: _parseUpdatedCount,
|
||||
);
|
||||
).then((value) {
|
||||
_invalidateSelectableProductsCache();
|
||||
return value;
|
||||
});
|
||||
|
||||
Future<void> mergeProducts({
|
||||
required int sourceProductId,
|
||||
@@ -283,6 +359,8 @@ class AdminRepository {
|
||||
_postVoid(ProductApiPaths.merge, {
|
||||
'sourceProductId': sourceProductId,
|
||||
'targetProductId': targetProductId,
|
||||
}).then((_) {
|
||||
_invalidateSelectableProductsCache();
|
||||
});
|
||||
|
||||
Future<Map<String, dynamic>> previewMerge({
|
||||
|
||||
Reference in New Issue
Block a user