diff --git a/lib/screens/garage_screen.dart b/lib/screens/garage_screen.dart index 0a0e52e..df9d5d0 100644 --- a/lib/screens/garage_screen.dart +++ b/lib/screens/garage_screen.dart @@ -476,11 +476,12 @@ class GarageScreenState extends State { ); if (confirmed != true || targetId == null) return; + final targetCollectionId = targetId!; if (isOwner) { await supabase .from('hotwheels') - .update({'collection_id': targetId}) + .update({'collection_id': targetCollectionId}) .inFilter('id', _selectedIds.toList()); } else { final userId = supabase.auth.currentUser?.id; @@ -492,22 +493,54 @@ class GarageScreenState extends State { .where((car) => _selectedIds.contains(car['id'] as int)) .toList(growable: false); + final hwIds = sourceCars + .map((car) => car['hw_id'] as String) + .toSet() + .toList(growable: false); + + final existing = await supabase + .from('hotwheels') + .select('hw_id') + .eq('collection_id', targetCollectionId) + .inFilter('hw_id', hwIds); + final existingHwIds = (existing as List) + .map((row) => row['hw_id'] as String) + .toSet(); + final insertRows = sourceCars.map((car) { final notes = car['notes'] as String?; final imagePath = car['user_image_url'] as String?; return { 'hw_id': car['hw_id'] as String, 'user_id': userId, - 'collection_id': targetId, + 'collection_id': targetCollectionId, if (notes != null && notes.trim().isNotEmpty) 'notes': notes, if (imagePath != null && imagePath.isNotEmpty) 'user_image_url': imagePath, }; - }).toList(growable: false); + }).where((row) => !existingHwIds.contains(row['hw_id'] as String)).toList( + growable: false, + ); + + final skippedDuplicates = sourceCars.length - insertRows.length; + + if (insertRows.isEmpty) { + showGlobalSnackBar( + 'All selected cars are already in the target collection.', + isError: true, + ); + return; + } if (insertRows.isNotEmpty) { await supabase.from('hotwheels').insert(insertRows); } + + if (skippedDuplicates > 0) { + showGlobalSnackBar( + '$skippedDuplicates car(s) skipped because they already exist in target collection.', + ); + } } if (!mounted) return; @@ -1069,6 +1102,21 @@ class GarageScreenState extends State { throw Exception('You must be signed in to copy cars.'); } + final existing = await supabase + .from('hotwheels') + .select('id') + .eq('collection_id', targetId) + .eq('hw_id', car['hw_id']) + .maybeSingle(); + + if (existing != null) { + showGlobalSnackBar( + '${car['hw_id']} is already in the target collection.', + isError: true, + ); + return; + } + final notes = car['notes'] as String?; final imagePath = car['user_image_url'] as String?; await supabase.from('hotwheels').insert({ diff --git a/lib/services/collection_service.dart b/lib/services/collection_service.dart index f2fee28..023a52d 100644 --- a/lib/services/collection_service.dart +++ b/lib/services/collection_service.dart @@ -114,14 +114,29 @@ class CollectionService { final itemCounts = await getCollectionItemCounts(collectionIdList); final memberCounts = {}; - final members = await supabase - .from('collection_members') - .select('collection_id') - .inFilter('collection_id', collectionIdList); + await Future.wait(collectionIdList.map((collectionId) async { + try { + final rows = await supabase.rpc('get_collection_members', params: { + 'p_collection_id': collectionId, + }); + memberCounts[collectionId] = (rows as List).length; + } catch (_) { + } + })); - for (final member in members) { - final collectionId = member['collection_id'] as String; - memberCounts[collectionId] = (memberCounts[collectionId] ?? 0) + 1; + final unresolvedIds = collectionIdList + .where((id) => !memberCounts.containsKey(id)) + .toList(growable: false); + if (unresolvedIds.isNotEmpty) { + final members = await supabase + .from('collection_members') + .select('id, collection_id') + .inFilter('collection_id', unresolvedIds); + + for (final member in members) { + final collectionId = member['collection_id'] as String; + memberCounts[collectionId] = (memberCounts[collectionId] ?? 0) + 1; + } } final collections = [];