mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-22 02:51:12 -05:00
Reconstruct fetchItems function
This commit is contained in:
@@ -120,8 +120,284 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
timestamp: new Date().toISOString()
|
timestamp: new Date().toISOString()
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: Function body was accidentally removed - needs restoration
|
try {
|
||||||
console.error('fetchItems function body is missing!');
|
// Set loading states
|
||||||
|
if (!silent) {
|
||||||
|
setLoading(true);
|
||||||
|
} else {
|
||||||
|
setIsRefreshing(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build base query for content submissions
|
||||||
|
let submissionsQuery = supabase
|
||||||
|
.from('content_submissions')
|
||||||
|
.select(`
|
||||||
|
id,
|
||||||
|
submission_type,
|
||||||
|
status,
|
||||||
|
content,
|
||||||
|
created_at,
|
||||||
|
user_id,
|
||||||
|
reviewed_at,
|
||||||
|
reviewer_id,
|
||||||
|
reviewer_notes,
|
||||||
|
escalated,
|
||||||
|
priority,
|
||||||
|
assigned_to,
|
||||||
|
locked_until
|
||||||
|
`)
|
||||||
|
.order('priority', { ascending: false })
|
||||||
|
.order('created_at', { ascending: true });
|
||||||
|
|
||||||
|
// Apply status filter
|
||||||
|
if (statusFilter === 'all') {
|
||||||
|
submissionsQuery = submissionsQuery.in('status', ['pending', 'approved', 'rejected', 'partially_approved']);
|
||||||
|
} else if (statusFilter === 'pending') {
|
||||||
|
submissionsQuery = submissionsQuery.in('status', ['pending', 'partially_approved']);
|
||||||
|
} else {
|
||||||
|
submissionsQuery = submissionsQuery.eq('status', statusFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply entity type filter
|
||||||
|
if (entityFilter === 'photos') {
|
||||||
|
submissionsQuery = submissionsQuery.eq('submission_type', 'photo');
|
||||||
|
} else if (entityFilter === 'submissions') {
|
||||||
|
submissionsQuery = submissionsQuery.neq('submission_type', 'photo');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: submissions, error: submissionsError } = await submissionsQuery;
|
||||||
|
|
||||||
|
if (submissionsError) throw submissionsError;
|
||||||
|
|
||||||
|
// Get user IDs and fetch user profiles
|
||||||
|
const userIds = submissions?.map(s => s.user_id).filter(Boolean) || [];
|
||||||
|
const reviewerIds = submissions?.map(s => s.reviewer_id).filter((id): id is string => !!id) || [];
|
||||||
|
const allUserIds = [...new Set([...userIds, ...reviewerIds])];
|
||||||
|
|
||||||
|
let userProfiles: any[] = [];
|
||||||
|
if (allUserIds.length > 0) {
|
||||||
|
const { data: profiles } = await supabase
|
||||||
|
.from('profiles')
|
||||||
|
.select('user_id, username, display_name, avatar_url')
|
||||||
|
.in('user_id', allUserIds);
|
||||||
|
|
||||||
|
userProfiles = profiles || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const userProfileMap = new Map(userProfiles.map(p => [p.user_id, p]));
|
||||||
|
|
||||||
|
// Collect entity IDs for bulk fetching
|
||||||
|
const rideIds = new Set<string>();
|
||||||
|
const parkIds = new Set<string>();
|
||||||
|
const companyIds = new Set<string>();
|
||||||
|
const rideModelIds = new Set<string>();
|
||||||
|
|
||||||
|
submissions?.forEach(submission => {
|
||||||
|
const content = submission.content as any;
|
||||||
|
if (content && typeof content === 'object') {
|
||||||
|
if (content.ride_id) rideIds.add(content.ride_id);
|
||||||
|
if (content.park_id) parkIds.add(content.park_id);
|
||||||
|
if (content.company_id) companyIds.add(content.company_id);
|
||||||
|
if (content.entity_id) {
|
||||||
|
if (submission.submission_type === 'ride') rideIds.add(content.entity_id);
|
||||||
|
if (submission.submission_type === 'park') parkIds.add(content.entity_id);
|
||||||
|
if (['manufacturer', 'operator', 'designer', 'property_owner'].includes(submission.submission_type)) {
|
||||||
|
companyIds.add(content.entity_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (content.manufacturer_id) companyIds.add(content.manufacturer_id);
|
||||||
|
if (content.designer_id) companyIds.add(content.designer_id);
|
||||||
|
if (content.operator_id) companyIds.add(content.operator_id);
|
||||||
|
if (content.property_owner_id) companyIds.add(content.property_owner_id);
|
||||||
|
if (content.ride_model_id) rideModelIds.add(content.ride_model_id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetch entities only if we don't have them cached or if they're new
|
||||||
|
const fetchPromises: Promise<{ type: string; data: any[] }>[] = [];
|
||||||
|
|
||||||
|
if (rideIds.size > 0) {
|
||||||
|
const uncachedRideIds = Array.from(rideIds).filter(id => !entityCache.rides.has(id));
|
||||||
|
if (uncachedRideIds.length > 0) {
|
||||||
|
fetchPromises.push(
|
||||||
|
Promise.resolve(
|
||||||
|
supabase
|
||||||
|
.from('rides')
|
||||||
|
.select('id, name, park_id')
|
||||||
|
.in('id', uncachedRideIds)
|
||||||
|
).then(({ data }) => ({ type: 'rides', data: data || [] }))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parkIds.size > 0) {
|
||||||
|
const uncachedParkIds = Array.from(parkIds).filter(id => !entityCache.parks.has(id));
|
||||||
|
if (uncachedParkIds.length > 0) {
|
||||||
|
fetchPromises.push(
|
||||||
|
Promise.resolve(
|
||||||
|
supabase
|
||||||
|
.from('parks')
|
||||||
|
.select('id, name')
|
||||||
|
.in('id', uncachedParkIds)
|
||||||
|
).then(({ data }) => ({ type: 'parks', data: data || [] }))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (companyIds.size > 0) {
|
||||||
|
const uncachedCompanyIds = Array.from(companyIds).filter(id => !entityCache.companies.has(id));
|
||||||
|
if (uncachedCompanyIds.length > 0) {
|
||||||
|
fetchPromises.push(
|
||||||
|
Promise.resolve(
|
||||||
|
supabase
|
||||||
|
.from('companies')
|
||||||
|
.select('id, name')
|
||||||
|
.in('id', uncachedCompanyIds)
|
||||||
|
).then(({ data }) => ({ type: 'companies', data: data || [] }))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch all uncached entities
|
||||||
|
const entityResults = await Promise.all(fetchPromises);
|
||||||
|
|
||||||
|
// Update entity cache
|
||||||
|
entityResults.forEach(result => {
|
||||||
|
if (result.type === 'rides') {
|
||||||
|
result.data.forEach((ride: any) => {
|
||||||
|
entityCache.rides.set(ride.id, ride);
|
||||||
|
if (ride.park_id) parkIds.add(ride.park_id);
|
||||||
|
});
|
||||||
|
} else if (result.type === 'parks') {
|
||||||
|
result.data.forEach((park: any) => {
|
||||||
|
entityCache.parks.set(park.id, park);
|
||||||
|
});
|
||||||
|
} else if (result.type === 'companies') {
|
||||||
|
result.data.forEach((company: any) => {
|
||||||
|
entityCache.companies.set(company.id, company);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Helper function to create memo key
|
||||||
|
const createMemoKey = (submission: any): string => {
|
||||||
|
return JSON.stringify({
|
||||||
|
id: submission.id,
|
||||||
|
status: submission.status,
|
||||||
|
content: submission.content,
|
||||||
|
reviewed_at: submission.reviewed_at,
|
||||||
|
reviewer_notes: submission.reviewer_notes,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Map submissions to moderation items with memoization
|
||||||
|
const moderationItems: ModerationItem[] = submissions?.map(submission => {
|
||||||
|
const memoKey = createMemoKey(submission);
|
||||||
|
const existingMemo = submissionMemo.get(submission.id);
|
||||||
|
|
||||||
|
// Check if we can reuse the memoized item
|
||||||
|
if (existingMemo && createMemoKey(existingMemo) === memoKey) {
|
||||||
|
return existingMemo as ModerationItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve entity name
|
||||||
|
const content = submission.content as any;
|
||||||
|
let entityName = content?.name || 'Unknown';
|
||||||
|
let parkName: string | undefined;
|
||||||
|
|
||||||
|
if (submission.submission_type === 'ride' && content?.entity_id) {
|
||||||
|
const ride = entityCache.rides.get(content.entity_id);
|
||||||
|
if (ride) {
|
||||||
|
entityName = ride.name;
|
||||||
|
if (ride.park_id) {
|
||||||
|
const park = entityCache.parks.get(ride.park_id);
|
||||||
|
if (park) parkName = park.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (submission.submission_type === 'park' && content?.entity_id) {
|
||||||
|
const park = entityCache.parks.get(content.entity_id);
|
||||||
|
if (park) entityName = park.name;
|
||||||
|
} else if (['manufacturer', 'operator', 'designer', 'property_owner'].includes(submission.submission_type) && content?.entity_id) {
|
||||||
|
const company = entityCache.companies.get(content.entity_id);
|
||||||
|
if (company) entityName = company.name;
|
||||||
|
} else if (content?.ride_id) {
|
||||||
|
const ride = entityCache.rides.get(content.ride_id);
|
||||||
|
if (ride) {
|
||||||
|
entityName = ride.name;
|
||||||
|
if (ride.park_id) {
|
||||||
|
const park = entityCache.parks.get(ride.park_id);
|
||||||
|
if (park) parkName = park.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (content?.park_id) {
|
||||||
|
const park = entityCache.parks.get(content.park_id);
|
||||||
|
if (park) parkName = park.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
const userProfile = userProfileMap.get(submission.user_id);
|
||||||
|
const reviewerProfile = submission.reviewer_id ? userProfileMap.get(submission.reviewer_id) : undefined;
|
||||||
|
|
||||||
|
const item: ModerationItem = {
|
||||||
|
id: submission.id,
|
||||||
|
type: 'content_submission',
|
||||||
|
content: submission.content,
|
||||||
|
created_at: submission.created_at,
|
||||||
|
user_id: submission.user_id,
|
||||||
|
status: submission.status,
|
||||||
|
submission_type: submission.submission_type,
|
||||||
|
user_profile: userProfile ? {
|
||||||
|
username: userProfile.username,
|
||||||
|
display_name: userProfile.display_name,
|
||||||
|
avatar_url: userProfile.avatar_url,
|
||||||
|
} : undefined,
|
||||||
|
entity_name: entityName,
|
||||||
|
park_name: parkName,
|
||||||
|
reviewed_at: submission.reviewed_at,
|
||||||
|
reviewed_by: submission.reviewer_id,
|
||||||
|
reviewer_notes: submission.reviewer_notes,
|
||||||
|
reviewer_profile: reviewerProfile,
|
||||||
|
};
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}) || [];
|
||||||
|
|
||||||
|
// Update memoization cache
|
||||||
|
const newMemoMap = new Map<string, ModerationItem>();
|
||||||
|
moderationItems.forEach(item => {
|
||||||
|
newMemoMap.set(item.id, item);
|
||||||
|
});
|
||||||
|
setSubmissionMemo(newMemoMap);
|
||||||
|
|
||||||
|
// Apply smart merge for state updates
|
||||||
|
const mergeResult = smartMergeArray(items, moderationItems, {
|
||||||
|
compareFields: ['status', 'reviewed_at', 'reviewer_notes'],
|
||||||
|
preserveOrder: silent && preserveInteraction,
|
||||||
|
addToTop: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!silent || mergeResult.hasChanges) {
|
||||||
|
setItems(mergeResult.items);
|
||||||
|
|
||||||
|
// Track new items for toast notification
|
||||||
|
if (silent && mergeResult.changes.added.length > 0) {
|
||||||
|
setNewItemsCount(prev => prev + mergeResult.changes.added.length);
|
||||||
|
} else if (!silent) {
|
||||||
|
setNewItemsCount(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Error fetching moderation items:', error);
|
||||||
|
toast({
|
||||||
|
title: 'Error',
|
||||||
|
description: error.message || 'Failed to fetch moderation queue',
|
||||||
|
variant: 'destructive',
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
setIsRefreshing(false);
|
||||||
|
setIsInitialLoad(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Expose refresh method via ref
|
// Expose refresh method via ref
|
||||||
|
|||||||
Reference in New Issue
Block a user