fix: use exact collection stats beyond row limits
This commit is contained in:
parent
69362e7a96
commit
34597b9909
2 changed files with 87 additions and 28 deletions
|
|
@ -139,26 +139,12 @@ class GarageScreenState extends State<GarageScreen> {
|
||||||
|
|
||||||
Future<void> _loadCollectionStats() async {
|
Future<void> _loadCollectionStats() async {
|
||||||
try {
|
try {
|
||||||
final weekAgoIso = DateTime.now()
|
final stats = await CollectionService.getCollectionStats(widget.collectionId);
|
||||||
.subtract(const Duration(days: 7))
|
|
||||||
.toUtc()
|
|
||||||
.toIso8601String();
|
|
||||||
|
|
||||||
final totalResult = await supabase
|
|
||||||
.from('hotwheels')
|
|
||||||
.select('id')
|
|
||||||
.eq('collection_id', widget.collectionId);
|
|
||||||
|
|
||||||
final recentResult = await supabase
|
|
||||||
.from('hotwheels')
|
|
||||||
.select('id')
|
|
||||||
.eq('collection_id', widget.collectionId)
|
|
||||||
.gte('created_at', weekAgoIso);
|
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
_totalCarsCount = totalResult.length;
|
_totalCarsCount = stats.total;
|
||||||
_recentCarsCount = recentResult.length;
|
_recentCarsCount = stats.recent;
|
||||||
});
|
});
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
|
||||||
|
|
@ -103,17 +103,7 @@ class CollectionService {
|
||||||
|
|
||||||
final collectionIdList = collectionIds.toList();
|
final collectionIdList = collectionIds.toList();
|
||||||
|
|
||||||
// Fetch all items for these collections in a single query and count them in memory.
|
final itemCounts = await getCollectionItemCounts(collectionIdList);
|
||||||
final items = await supabase
|
|
||||||
.from('hotwheels')
|
|
||||||
.select('id, collection_id')
|
|
||||||
.inFilter('collection_id', collectionIdList);
|
|
||||||
|
|
||||||
final itemCounts = <String, int>{};
|
|
||||||
for (final item in items) {
|
|
||||||
final collectionId = item['collection_id'] as String;
|
|
||||||
itemCounts[collectionId] = (itemCounts[collectionId] ?? 0) + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
final memberCounts = <String, int>{};
|
final memberCounts = <String, int>{};
|
||||||
await Future.wait(collectionIdList.map((collectionId) async {
|
await Future.wait(collectionIdList.map((collectionId) async {
|
||||||
|
|
@ -171,6 +161,89 @@ class CollectionService {
|
||||||
return collections;
|
return collections;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<Map<String, int>> getCollectionItemCounts(
|
||||||
|
List<String> collectionIds) async {
|
||||||
|
if (collectionIds.isEmpty) return <String, int>{};
|
||||||
|
|
||||||
|
final itemCounts = <String, int>{};
|
||||||
|
try {
|
||||||
|
final rows = await supabase.rpc('get_collection_counts', params: {
|
||||||
|
'p_collection_ids': collectionIds,
|
||||||
|
});
|
||||||
|
|
||||||
|
for (final row in rows as List) {
|
||||||
|
final collectionId = row['collection_id'] as String?;
|
||||||
|
if (collectionId == null) continue;
|
||||||
|
itemCounts[collectionId] = (row['total_count'] as num?)?.toInt() ?? 0;
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
final unresolvedIds = collectionIds
|
||||||
|
.where((collectionId) => !itemCounts.containsKey(collectionId))
|
||||||
|
.toList(growable: false);
|
||||||
|
|
||||||
|
if (unresolvedIds.isNotEmpty) {
|
||||||
|
await Future.wait(unresolvedIds.map((collectionId) async {
|
||||||
|
itemCounts[collectionId] = await _countCarsPaginated(collectionId);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return itemCounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<({int total, int recent})> getCollectionStats(
|
||||||
|
String collectionId) async {
|
||||||
|
try {
|
||||||
|
final rows = await supabase.rpc('get_collection_counts', params: {
|
||||||
|
'p_collection_ids': [collectionId],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (rows is List && rows.isNotEmpty) {
|
||||||
|
final first = rows.first as Map<String, dynamic>;
|
||||||
|
return (
|
||||||
|
total: (first['total_count'] as num?)?.toInt() ?? 0,
|
||||||
|
recent: (first['recent_count'] as num?)?.toInt() ?? 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
final weekAgoIso = DateTime.now()
|
||||||
|
.subtract(const Duration(days: 7))
|
||||||
|
.toUtc()
|
||||||
|
.toIso8601String();
|
||||||
|
final total = await _countCarsPaginated(collectionId);
|
||||||
|
final recent = await _countCarsPaginated(collectionId, sinceIso: weekAgoIso);
|
||||||
|
return (total: total, recent: recent);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<int> _countCarsPaginated(String collectionId,
|
||||||
|
{String? sinceIso}) async {
|
||||||
|
const pageSize = 1000;
|
||||||
|
var from = 0;
|
||||||
|
var total = 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
dynamic query = supabase
|
||||||
|
.from('hotwheels')
|
||||||
|
.select('id')
|
||||||
|
.eq('collection_id', collectionId)
|
||||||
|
.range(from, from + pageSize - 1);
|
||||||
|
|
||||||
|
if (sinceIso != null) {
|
||||||
|
query = query.gte('created_at', sinceIso);
|
||||||
|
}
|
||||||
|
|
||||||
|
final rows = await query;
|
||||||
|
final count = (rows as List).length;
|
||||||
|
total += count;
|
||||||
|
|
||||||
|
if (count < pageSize) break;
|
||||||
|
from += pageSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a new collection. The caller is automatically added as owner.
|
/// Create a new collection. The caller is automatically added as owner.
|
||||||
static Future<Collection> create({
|
static Future<Collection> create({
|
||||||
required String name,
|
required String name,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue