import { useState, useEffect } from 'react'; import { Badge } from '@/components/ui/badge'; import { Card, CardContent } from '@/components/ui/card'; import { supabase } from '@/integrations/supabase/client'; import { Image as ImageIcon } from 'lucide-react'; import { PhotoModal } from './PhotoModal'; import { handleError } from '@/lib/errorHandler'; interface EntityEditPreviewProps { submissionId: string; entityType: string; entityName?: string; } /** * Deep equality check for detecting changes in nested objects/arrays */ const deepEqual = (a: any, b: any): boolean => { // Handle null/undefined cases if (a === b) return true; if (a == null || b == null) return false; if (typeof a !== typeof b) return false; // Handle primitives and functions if (typeof a !== 'object') return a === b; // Handle arrays if (Array.isArray(a) && Array.isArray(b)) { if (a.length !== b.length) return false; return a.every((item, index) => deepEqual(item, b[index])); } // One is array, other is not if (Array.isArray(a) !== Array.isArray(b)) return false; // Handle objects const keysA = Object.keys(a); const keysB = Object.keys(b); if (keysA.length !== keysB.length) return false; return keysA.every(key => deepEqual(a[key], b[key])); }; interface ImageAssignments { uploaded: Array<{ url: string; cloudflare_id: string; }>; banner_assignment: number | null; card_assignment: number | null; } interface SubmissionItemData { id: string; item_data: Record; original_data?: Record; } export const EntityEditPreview = ({ submissionId, entityType, entityName }: EntityEditPreviewProps) => { const [loading, setLoading] = useState(true); const [itemData, setItemData] = useState | null>(null); const [originalData, setOriginalData] = useState | null>(null); const [changedFields, setChangedFields] = useState([]); const [bannerImageUrl, setBannerImageUrl] = useState(null); const [cardImageUrl, setCardImageUrl] = useState(null); const [isModalOpen, setIsModalOpen] = useState(false); const [selectedImageIndex, setSelectedImageIndex] = useState(0); const [isPhotoOperation, setIsPhotoOperation] = useState(false); useEffect(() => { fetchSubmissionItems(); }, [submissionId]); const fetchSubmissionItems = async () => { try { setLoading(true); const { data: items, error } = await supabase .from('submission_items') .select('*') .eq('submission_id', submissionId) .order('order_index', { ascending: true }); if (error) throw error; if (items && items.length > 0) { const firstItem = items[0]; setItemData(firstItem.item_data as Record); setOriginalData(firstItem.original_data as Record | null); // Check for photo edit/delete operations if (firstItem.item_type === 'photo_edit' || firstItem.item_type === 'photo_delete') { setIsPhotoOperation(true); if (firstItem.item_type === 'photo_edit') { setChangedFields(['caption']); } return; } // Parse changed fields const changed: string[] = []; const data = firstItem.item_data as Record; // Check for image changes if (data.images && typeof data.images === 'object') { const images = data.images as { uploaded?: Array<{ url: string; cloudflare_id: string }>; banner_assignment?: number | null; card_assignment?: number | null; }; // Safety check: verify uploaded array exists and is valid if (!images.uploaded || !Array.isArray(images.uploaded)) { // Invalid images data structure, skip image processing return; } // Extract banner image if (images.banner_assignment !== null && images.banner_assignment !== undefined) { // Safety check: verify index is within bounds if (images.banner_assignment >= 0 && images.banner_assignment < images.uploaded.length) { const bannerImg = images.uploaded[images.banner_assignment]; // Validate nested image data if (bannerImg && bannerImg.url) { setBannerImageUrl(bannerImg.url); changed.push('banner_image'); } } } // Extract card image if (images.card_assignment !== null && images.card_assignment !== undefined) { // Safety check: verify index is within bounds if (images.card_assignment >= 0 && images.card_assignment < images.uploaded.length) { const cardImg = images.uploaded[images.card_assignment]; // Validate nested image data if (cardImg && cardImg.url) { setCardImageUrl(cardImg.url); changed.push('card_image'); } } } } // Check for other field changes by comparing with original_data if (firstItem.original_data) { const originalData = firstItem.original_data as Record; const excludeFields = ['images', 'updated_at', 'created_at']; Object.keys(data).forEach(key => { if (!excludeFields.includes(key)) { // Use deep equality check for objects and arrays const isEqual = deepEqual(data[key], originalData[key]); if (!isEqual) { changed.push(key); } } }); } setChangedFields(changed); } } catch (error: unknown) { handleError(error, { action: 'Load Submission Preview', metadata: { submissionId, entityType } }); } finally { setLoading(false); } }; if (loading) { return (
Loading preview...
); } if (!itemData) { return (
No preview available
); } // Handle photo edit/delete operations if (isPhotoOperation) { const isEdit = changedFields.includes('caption'); return (
Photo {isEdit ? 'Edit' : 'Delete'}
{itemData?.cloudflare_image_url && typeof itemData.cloudflare_image_url === 'string' && ( Photo to be modified )} {isEdit && (
Old caption: {(originalData?.caption as string) || No caption}
New caption: {(itemData?.new_caption as string) || No caption}
)} {!isEdit && itemData?.reason && typeof itemData.reason === 'string' && (
Reason: {itemData.reason}
)}
Click "Review Items" for full details
); } // Build photos array for modal const photos = []; if (bannerImageUrl) { photos.push({ id: 'banner', url: `${bannerImageUrl}`, caption: 'New Banner Image' }); } if (cardImageUrl) { photos.push({ id: 'card', url: `${cardImageUrl}`, caption: 'New Card Image' }); } return (
{entityType} Edit
{entityName && (
{entityName}
)} {changedFields.length > 0 && (
Changed fields: {changedFields.map(field => field.replace(/_/g, ' ')).join(', ')}
)} {(bannerImageUrl || cardImageUrl) && (
Image Changes:
{bannerImageUrl && ( { setSelectedImageIndex(0); setIsModalOpen(true); }}> New banner
Banner
)} {cardImageUrl && ( { setSelectedImageIndex(bannerImageUrl ? 1 : 0); setIsModalOpen(true); }}> New card
Card
)}
)}
Click "Review Items" for full details
setIsModalOpen(false)} />
); };