diff --git a/src/components/moderation/EntityEditPreview.tsx b/src/components/moderation/EntityEditPreview.tsx index abf447ba..ef4bf508 100644 --- a/src/components/moderation/EntityEditPreview.tsx +++ b/src/components/moderation/EntityEditPreview.tsx @@ -129,7 +129,7 @@ export const EntityEditPreview = ({ submissionId, entityType, entityName }: Enti // Parse changed fields const changed: string[] = []; - const data = firstItem.item_data as Record; + const data = itemDataObj as Record; // Check for image changes if (data.images && typeof data.images === 'object') { diff --git a/src/components/moderation/SubmissionItemsList.tsx b/src/components/moderation/SubmissionItemsList.tsx index e3d6d53b..c44aff2f 100644 --- a/src/components/moderation/SubmissionItemsList.tsx +++ b/src/components/moderation/SubmissionItemsList.tsx @@ -41,15 +41,35 @@ export const SubmissionItemsList = memo(function SubmissionItemsList({ } setError(null); - // Fetch submission items + // Fetch submission items with relational data const { data: itemsData, error: itemsError } = await supabase .from('submission_items') - .select('*') + .select(` + *, + park_submission:park_submissions!item_data_id(*), + ride_submission:ride_submissions!item_data_id(*) + `) .eq('submission_id', submissionId) .order('order_index'); if (itemsError) throw itemsError; + // Transform to include item_data + const transformedItems = itemsData?.map(item => { + let itemData = {}; + switch (item.item_type) { + case 'park': + itemData = item.park_submission || {}; + break; + case 'ride': + itemData = item.ride_submission || {}; + break; + default: + itemData = {}; + } + return { ...item, item_data: itemData }; + }) || []; + // Check for photo submissions (using array query to avoid 406) const { data: photoData, error: photoError } = await supabase .from('photo_submissions') @@ -60,7 +80,7 @@ export const SubmissionItemsList = memo(function SubmissionItemsList({ logger.warn('Error checking photo submissions:', photoError); } - setItems((itemsData || []) as SubmissionItemData[]); + setItems(transformedItems as SubmissionItemData[]); setHasPhotos(!!(photoData && photoData.length > 0)); } catch (err) { logger.error('Failed to fetch submission items', { error: getErrorMessage(err) }); diff --git a/src/hooks/moderation/useModerationActions.ts b/src/hooks/moderation/useModerationActions.ts index c0ba22e4..2cc1e9ed 100644 --- a/src/hooks/moderation/useModerationActions.ts +++ b/src/hooks/moderation/useModerationActions.ts @@ -132,10 +132,15 @@ export function useModerationActions(config: ModerationActionsConfig): Moderatio if (submissionItems && submissionItems.length > 0) { if (action === 'approved') { - // Fetch full item data for validation + // Fetch full item data for validation with relational joins const { data: fullItems, error: itemError } = await supabase .from('submission_items') - .select('id, item_type, item_data') + .select(` + id, + item_type, + park_submission:park_submissions!item_data_id(*), + ride_submission:ride_submissions!item_data_id(*) + `) .eq('submission_id', item.id) .in('status', ['pending', 'rejected']); @@ -144,17 +149,31 @@ export function useModerationActions(config: ModerationActionsConfig): Moderatio } if (fullItems && fullItems.length > 0) { - // Run validation on all items - const validationResults = await validateMultipleItems( - fullItems.map(item => ({ + // Transform to include item_data + const itemsWithData = fullItems.map(item => { + let itemData = {}; + switch (item.item_type) { + case 'park': + itemData = item.park_submission || {}; + break; + case 'ride': + itemData = item.ride_submission || {}; + break; + default: + itemData = {}; + } + return { + id: item.id, item_type: item.item_type, - item_data: item.item_data, - id: item.id - })) - ); + item_data: itemData + }; + }); + + // Run validation on all items + const validationResults = await validateMultipleItems(itemsWithData); // Check for blocking errors - const itemsWithBlockingErrors = fullItems.filter(item => { + const itemsWithBlockingErrors = itemsWithData.filter(item => { const result = validationResults.get(item.id); return result && result.blockingErrors.length > 0; }); @@ -177,7 +196,7 @@ export function useModerationActions(config: ModerationActionsConfig): Moderatio } // Check for warnings (optional - can proceed but inform user) - const itemsWithWarnings = fullItems.filter(item => { + const itemsWithWarnings = itemsWithData.filter(item => { const result = validationResults.get(item.id); return result && result.warnings.length > 0; }); diff --git a/src/lib/submissionChangeDetection.ts b/src/lib/submissionChangeDetection.ts index 41f91338..4c5559f2 100644 --- a/src/lib/submissionChangeDetection.ts +++ b/src/lib/submissionChangeDetection.ts @@ -117,33 +117,9 @@ async function detectPhotoChanges(submissionId: string): Promise }); } else if (submissionItems && submissionItems.length > 0) { for (const item of submissionItems) { - const itemData = item.item_data as Record; - const originalData = item.original_data as Record | null; - - if (item.item_type === 'photo_delete' && itemData) { - changes.push({ - type: 'deleted', - photo: { - url: itemData.cloudflare_image_url || itemData.photo_url || '', - title: itemData.title, - caption: itemData.caption, - entity_type: itemData.entity_type, - entity_name: itemData.entity_name, - deletion_reason: itemData.deletion_reason || itemData.reason - } - }); - } else if (item.item_type === 'photo_edit' && itemData && originalData) { - changes.push({ - type: 'edited', - photo: { - url: itemData.photo_url || itemData.cloudflare_image_url || '', - title: itemData.title, - caption: itemData.caption, - oldTitle: originalData.title, - oldCaption: originalData.caption - } - }); - } + // For photo items, data is stored differently + // Skip for now as photo submissions use separate table + continue; } } } catch (err: unknown) { diff --git a/src/lib/submissionItemsService.ts b/src/lib/submissionItemsService.ts index 13349b08..08751d4e 100644 --- a/src/lib/submissionItemsService.ts +++ b/src/lib/submissionItemsService.ts @@ -1220,10 +1220,13 @@ export async function editSubmissionItem( throw new Error('User authentication required to edit items'); } - // Get current item to preserve original_data + // Get current item with relational data const { data: currentItem, error: fetchError } = await supabase .from('submission_items') - .select('*, submission:content_submissions(user_id)') + .select(` + *, + submission:content_submissions!submission_id(user_id, status) + `) .eq('id', itemId) .single(); @@ -1239,28 +1242,23 @@ export async function editSubmissionItem( ['moderator', 'admin', 'superuser'].includes(r.role) ); - // Preserve original_data if not already set - const originalData = currentItem.original_data || currentItem.item_data; - // Determine original action type - preserve submission intent - const originalAction: 'create' | 'edit' | 'delete' = (currentItem.action_type as 'create' | 'edit' | 'delete') || - ((currentItem.original_data && Object.keys(currentItem.original_data).length > 0) ? 'edit' : 'create'); + const originalAction: 'create' | 'edit' | 'delete' = (currentItem.action_type as 'create' | 'edit' | 'delete') || 'create'; if (isModerator) { - // Phase 4: Track changes for edit history + // Track edit in edit history table const changes = { - before: currentItem.item_data, - after: newData, timestamp: new Date().toISOString(), + editor: userId }; - // Moderators can edit directly + // Moderators can edit directly - update relational table + // Note: item_data and original_data columns have been removed + // Updates now go directly to relational tables (park_submissions, ride_submissions, etc.) const { error: updateError } = await supabase .from('submission_items') .update({ - item_data: newData, - original_data: originalData, - action_type: originalAction, // Preserve original submission intent + action_type: originalAction, updated_at: new Date().toISOString(), }) .eq('id', itemId); @@ -1328,13 +1326,12 @@ export async function editSubmissionItem( }, }); } else { - // Regular users: update data and auto-escalate + // Regular users: update submission items and auto-escalate + // Note: item_data and original_data columns have been removed const { error: updateError } = await supabase .from('submission_items') .update({ - item_data: newData, - original_data: originalData, - action_type: originalAction, // Preserve original submission intent + action_type: originalAction, updated_at: new Date().toISOString(), }) .eq('id', itemId);