diff --git a/src/components/moderation/SubmissionItemsList.tsx b/src/components/moderation/SubmissionItemsList.tsx index 06efaaa2..25d56356 100644 --- a/src/components/moderation/SubmissionItemsList.tsx +++ b/src/components/moderation/SubmissionItemsList.tsx @@ -48,125 +48,21 @@ export const SubmissionItemsList = memo(function SubmissionItemsList({ } setError(null); - // Fetch submission items with entity data - // Note: entity_data is pre-loaded from the view, but we need to fetch from raw table - // when this component is used standalone + // Use database function to fetch submission items with entity data in one query + // This eliminates N+1 query problem and properly handles RLS/AAL2 checks const { data: itemsData, error: itemsError } = await supabase - .from('submission_items') - .select('*') - .eq('submission_id', submissionId) - .order('order_index'); + .rpc('get_submission_items_with_entities', { + p_submission_id: submissionId + }); if (itemsError) throw itemsError; - // Fetch entity data for each item based on item_type and typed FK columns - const transformedItems = await Promise.all( - (itemsData || []).map(async (item) => { - // Determine which FK column to use based on item_type - let itemDataId: string | null = null; - switch (item.item_type) { - case 'park': - itemDataId = item.park_submission_id; - break; - case 'ride': - itemDataId = item.ride_submission_id; - break; - case 'photo': - case 'photo_edit': - case 'photo_delete': - itemDataId = item.photo_submission_id; - break; - case 'manufacturer': - case 'operator': - case 'designer': - case 'property_owner': - itemDataId = item.company_submission_id; - break; - case 'ride_model': - itemDataId = item.ride_model_submission_id; - break; - case 'milestone': - case 'timeline_event': - itemDataId = item.timeline_event_submission_id; - break; - } - - if (!itemDataId) { - return { ...item, item_data: {}, entity_data: null }; - } - - try { - let entityData = null; - - // Fetch from appropriate table based on item_type - switch (item.item_type) { - case 'park': { - const { data } = await supabase - .from('park_submissions') - .select('*') - .eq('id', itemDataId) - .maybeSingle(); - entityData = data as any; - break; - } - case 'ride': { - const { data } = await supabase - .from('ride_submissions') - .select('*') - .eq('id', itemDataId) - .maybeSingle(); - entityData = data as any; - break; - } - case 'manufacturer': - case 'operator': - case 'designer': - case 'property_owner': { - const { data } = await supabase - .from('company_submissions') - .select('*') - .eq('id', itemDataId) - .maybeSingle(); - entityData = data as any; - break; - } - case 'photo': { - const { data } = await supabase - .from('photo_submissions') - .select('*') - .eq('id', itemDataId) - .maybeSingle(); - entityData = data as any; - break; - } - case 'ride_model': { - const { data } = await supabase - .from('ride_model_submissions') - .select('*') - .eq('id', itemDataId) - .maybeSingle(); - entityData = data as any; - break; - } - default: - entityData = null; - } - - return { - ...item, - item_data: entityData || {}, - entity_data: entityData - }; - } catch (err) { - logger.warn('Failed to fetch entity data for item', { - itemId: item.id, - itemType: item.item_type, - error: getErrorMessage(err) - }); - return { ...item, item_data: {}, entity_data: null }; - } - }) - ); + // Transform to expected format + const transformedItems = (itemsData || []).map((item: any) => ({ + ...item, + item_data: item.entity_data || {}, + entity_data: item.entity_data + })); // Check for photo submissions (using array query to avoid 406) const { data: photoData, error: photoError } = await supabase diff --git a/src/components/moderation/displays/RichCompanyDisplay.tsx b/src/components/moderation/displays/RichCompanyDisplay.tsx index 0301a746..2b96f324 100644 --- a/src/components/moderation/displays/RichCompanyDisplay.tsx +++ b/src/components/moderation/displays/RichCompanyDisplay.tsx @@ -10,7 +10,8 @@ interface RichCompanyDisplayProps { } export function RichCompanyDisplay({ data, actionType, showAllFields = true }: RichCompanyDisplayProps) { - const getCompanyTypeColor = (type: string) => { + const getCompanyTypeColor = (type: string | undefined) => { + if (!type) return 'bg-gray-500'; switch (type.toLowerCase()) { case 'manufacturer': return 'bg-blue-500'; case 'operator': return 'bg-green-500'; @@ -31,7 +32,7 @@ export function RichCompanyDisplay({ data, actionType, showAllFields = true }: R