diff --git a/src/components/moderation/SubmissionItemsList.tsx b/src/components/moderation/SubmissionItemsList.tsx index f58eeb14..c16de32d 100644 --- a/src/components/moderation/SubmissionItemsList.tsx +++ b/src/components/moderation/SubmissionItemsList.tsx @@ -2,10 +2,14 @@ import { useState, useEffect, memo } from 'react'; import { supabase } from '@/lib/supabaseClient'; import { SubmissionChangesDisplay } from './SubmissionChangesDisplay'; import { PhotoSubmissionDisplay } from './PhotoSubmissionDisplay'; +import { RichParkDisplay } from './displays/RichParkDisplay'; +import { RichRideDisplay } from './displays/RichRideDisplay'; +import { RichCompanyDisplay } from './displays/RichCompanyDisplay'; import { Skeleton } from '@/components/ui/skeleton'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { AlertCircle, Loader2 } from 'lucide-react'; import type { SubmissionItemData } from '@/types/submissions'; +import type { ParkSubmissionData, RideSubmissionData, CompanySubmissionData } from '@/types/submission-data'; import { logger } from '@/lib/logger'; import { getErrorMessage } from '@/lib/errorHandler'; import { ModerationErrorBoundary } from '@/components/error/ModerationErrorBoundary'; @@ -170,9 +174,66 @@ export const SubmissionItemsList = memo(function SubmissionItemsList({ ); } + // Render item with appropriate display component + const renderItem = (item: SubmissionItemData) => { + // SubmissionItemData from submissions.ts has item_data property + const entityData = item.item_data; + const actionType = item.action_type || 'create'; + + // Use summary view for compact display + if (view === 'summary') { + return ( + + ); + } + + // Use rich displays for detailed view + if (item.item_type === 'park' && entityData) { + return ( + + ); + } + + if (item.item_type === 'ride' && entityData) { + return ( + + ); + } + + if ((['manufacturer', 'operator', 'designer', 'property_owner'] as const).some(type => type === item.item_type) && entityData) { + return ( + + ); + } + + // Fallback to SubmissionChangesDisplay + return ( + + ); + }; + return ( -
+
{refreshing && (
@@ -183,12 +244,7 @@ export const SubmissionItemsList = memo(function SubmissionItemsList({ {/* Show regular submission items */} {items.map((item) => (
- + {renderItem(item)}
))} diff --git a/src/components/moderation/displays/RichCompanyDisplay.tsx b/src/components/moderation/displays/RichCompanyDisplay.tsx new file mode 100644 index 00000000..70631043 --- /dev/null +++ b/src/components/moderation/displays/RichCompanyDisplay.tsx @@ -0,0 +1,175 @@ +import { Building, MapPin, Calendar, Globe, ExternalLink, AlertCircle } from 'lucide-react'; +import { Badge } from '@/components/ui/badge'; +import { Separator } from '@/components/ui/separator'; +import type { CompanySubmissionData } from '@/types/submission-data'; + +interface RichCompanyDisplayProps { + data: CompanySubmissionData; + actionType: 'create' | 'edit' | 'delete'; + showAllFields?: boolean; +} + +export function RichCompanyDisplay({ data, actionType, showAllFields = true }: RichCompanyDisplayProps) { + const getCompanyTypeColor = (type: string) => { + switch (type.toLowerCase()) { + case 'manufacturer': return 'bg-blue-500'; + case 'operator': return 'bg-green-500'; + case 'designer': return 'bg-purple-500'; + case 'property_owner': return 'bg-orange-500'; + default: return 'bg-gray-500'; + } + }; + + return ( +
+ {/* Header Section */} +
+
+ +
+
+

{data.name}

+
+ + {data.company_type?.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())} + + {data.person_type && ( + + {data.person_type.replace(/\b\w/g, l => l.toUpperCase())} + + )} + {actionType === 'create' && ( + New Company + )} + {actionType === 'edit' && ( + Edit + )} + {actionType === 'delete' && ( + Delete + )} +
+
+
+ + {/* Key Information */} +
+ {/* Founded Date */} + {(data.founded_date || data.founded_year) && ( +
+
+ + Founded +
+
+ {data.founded_date ? ( + <> + {new Date(data.founded_date).toLocaleDateString()} + {data.founded_date_precision && data.founded_date_precision !== 'day' && ( + ({data.founded_date_precision}) + )} + + ) : ( + {data.founded_year} + )} +
+
+ )} + + {/* Location */} + {data.headquarters_location && ( +
+
+ + Headquarters +
+
+ {data.headquarters_location} +
+
+ )} +
+ + {/* Description */} + {data.description && ( +
+
Description
+
+ {data.description} +
+
+ )} + + {/* Website & Source */} + {(data.website_url || data.source_url) && ( +
+ {data.website_url && ( + + + Official Website + + + )} + {data.source_url && ( + + Source + + + )} +
+ )} + + {/* Submission Notes */} + {data.submission_notes && ( +
+
+ + Submitter Notes +
+
+ {data.submission_notes} +
+
+ )} + + {/* Images Preview */} + {(data.banner_image_url || data.card_image_url) && ( +
+ +
Images
+
+ {data.banner_image_url && ( +
+ Banner +
Banner
+
+ )} + {data.card_image_url && ( +
+ Card +
Card
+
+ )} +
+
+ )} +
+ ); +} diff --git a/src/components/moderation/displays/RichParkDisplay.tsx b/src/components/moderation/displays/RichParkDisplay.tsx new file mode 100644 index 00000000..6bc87e23 --- /dev/null +++ b/src/components/moderation/displays/RichParkDisplay.tsx @@ -0,0 +1,252 @@ +import { Building2, MapPin, Calendar, Globe, ExternalLink, Users, AlertCircle } from 'lucide-react'; +import { Badge } from '@/components/ui/badge'; +import { Separator } from '@/components/ui/separator'; +import type { ParkSubmissionData } from '@/types/submission-data'; +import { useEffect, useState } from 'react'; +import { supabase } from '@/lib/supabaseClient'; + +interface RichParkDisplayProps { + data: ParkSubmissionData; + actionType: 'create' | 'edit' | 'delete'; + showAllFields?: boolean; +} + +export function RichParkDisplay({ data, actionType, showAllFields = true }: RichParkDisplayProps) { + const [location, setLocation] = useState(null); + const [operator, setOperator] = useState(null); + const [propertyOwner, setPropertyOwner] = useState(null); + + useEffect(() => { + const fetchRelatedData = async () => { + // Fetch location + if (data.location_id) { + const { data: locationData } = await supabase + .from('locations') + .select('*') + .eq('id', data.location_id) + .single(); + setLocation(locationData); + } + + // Fetch operator + if (data.operator_id) { + const { data: operatorData } = await supabase + .from('companies') + .select('name') + .eq('id', data.operator_id) + .single(); + setOperator(operatorData?.name || null); + } + + // Fetch property owner + if (data.property_owner_id) { + const { data: ownerData } = await supabase + .from('companies') + .select('name') + .eq('id', data.property_owner_id) + .single(); + setPropertyOwner(ownerData?.name || null); + } + }; + + fetchRelatedData(); + }, [data.location_id, data.operator_id, data.property_owner_id]); + + const getStatusColor = (status: string) => { + switch (status.toLowerCase()) { + case 'operating': return 'bg-green-500'; + case 'closed': return 'bg-red-500'; + case 'under_construction': return 'bg-blue-500'; + case 'planned': return 'bg-purple-500'; + default: return 'bg-gray-500'; + } + }; + + return ( +
+ {/* Header Section */} +
+
+ +
+
+

{data.name}

+
+ + {data.park_type?.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())} + + + {data.status?.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())} + + {actionType === 'create' && ( + New Park + )} + {actionType === 'edit' && ( + Edit + )} + {actionType === 'delete' && ( + Delete + )} +
+
+
+ + {/* Location Section */} + {location && ( +
+
+ + Location +
+
+ {location.city &&
City: {location.city}
} + {location.state_province &&
State/Province: {location.state_province}
} + {location.country &&
Country: {location.country}
} + {location.formatted_address && ( +
{location.formatted_address}
+ )} +
+
+ )} + + {/* Key Information Grid */} +
+ {/* Dates */} + {(data.opening_date || data.closing_date) && ( +
+
+ + Dates +
+
+ {data.opening_date && ( +
+ Opened:{' '} + {new Date(data.opening_date).toLocaleDateString()} + {data.opening_date_precision && data.opening_date_precision !== 'day' && ( + ({data.opening_date_precision}) + )} +
+ )} + {data.closing_date && ( +
+ Closed:{' '} + {new Date(data.closing_date).toLocaleDateString()} + {data.closing_date_precision && data.closing_date_precision !== 'day' && ( + ({data.closing_date_precision}) + )} +
+ )} +
+
+ )} + + {/* Companies */} + {(operator || propertyOwner) && ( +
+
+ + Companies +
+
+ {operator && ( +
+ Operator:{' '} + {operator} +
+ )} + {propertyOwner && ( +
+ Owner:{' '} + {propertyOwner} +
+ )} +
+
+ )} +
+ + {/* Description */} + {data.description && ( +
+
Description
+
+ {data.description} +
+
+ )} + + {/* Website & Source */} + {(data.website_url || data.source_url) && ( +
+ {data.website_url && ( + + + Official Website + + + )} + {data.source_url && ( + + Source + + + )} +
+ )} + + {/* Submission Notes */} + {data.submission_notes && ( +
+
+ + Submitter Notes +
+
+ {data.submission_notes} +
+
+ )} + + {/* Images Preview */} + {(data.banner_image_url || data.card_image_url) && ( +
+ +
Images
+
+ {data.banner_image_url && ( +
+ Banner +
Banner
+
+ )} + {data.card_image_url && ( +
+ Card +
Card
+
+ )} +
+
+ )} +
+ ); +} diff --git a/src/components/moderation/displays/RichRideDisplay.tsx b/src/components/moderation/displays/RichRideDisplay.tsx new file mode 100644 index 00000000..b9b8e45e --- /dev/null +++ b/src/components/moderation/displays/RichRideDisplay.tsx @@ -0,0 +1,340 @@ +import { Train, Gauge, Ruler, Zap, Calendar, Building, User, ExternalLink, AlertCircle, TrendingUp } from 'lucide-react'; +import { Badge } from '@/components/ui/badge'; +import { Separator } from '@/components/ui/separator'; +import type { RideSubmissionData } from '@/types/submission-data'; +import { useEffect, useState } from 'react'; +import { supabase } from '@/lib/supabaseClient'; + +interface RichRideDisplayProps { + data: RideSubmissionData; + actionType: 'create' | 'edit' | 'delete'; + showAllFields?: boolean; +} + +export function RichRideDisplay({ data, actionType, showAllFields = true }: RichRideDisplayProps) { + const [park, setPark] = useState(null); + const [manufacturer, setManufacturer] = useState(null); + const [designer, setDesigner] = useState(null); + const [model, setModel] = useState(null); + + useEffect(() => { + const fetchRelatedData = async () => { + // Fetch park + if (data.park_id) { + const { data: parkData } = await supabase + .from('parks') + .select('name') + .eq('id', data.park_id) + .single(); + setPark(parkData?.name || null); + } + + // Fetch manufacturer + if (data.manufacturer_id) { + const { data: mfgData } = await supabase + .from('companies') + .select('name') + .eq('id', data.manufacturer_id) + .single(); + setManufacturer(mfgData?.name || null); + } + + // Fetch designer + if (data.designer_id) { + const { data: designerData } = await supabase + .from('companies') + .select('name') + .eq('id', data.designer_id) + .single(); + setDesigner(designerData?.name || null); + } + + // Fetch model + if (data.ride_model_id) { + const { data: modelData } = await supabase + .from('ride_models') + .select('name') + .eq('id', data.ride_model_id) + .single(); + setModel(modelData?.name || null); + } + }; + + fetchRelatedData(); + }, [data.park_id, data.manufacturer_id, data.designer_id, data.ride_model_id]); + + const getStatusColor = (status: string) => { + switch (status.toLowerCase()) { + case 'operating': return 'bg-green-500'; + case 'closed': return 'bg-red-500'; + case 'under_construction': return 'bg-blue-500'; + case 'sbno': return 'bg-orange-500'; + default: return 'bg-gray-500'; + } + }; + + // Format metrics for display + const heightDisplay = data.max_height_meters + ? `${data.max_height_meters.toFixed(1)} m` + : null; + const speedDisplay = data.max_speed_kmh + ? `${data.max_speed_kmh.toFixed(1)} km/h` + : null; + const lengthDisplay = data.length_meters + ? `${data.length_meters.toFixed(1)} m` + : null; + const dropDisplay = data.drop_height_meters + ? `${data.drop_height_meters.toFixed(1)} m` + : null; + + return ( +
+ {/* Header Section */} +
+
+ +
+
+

{data.name}

+ {park && ( +
at {park}
+ )} +
+ + {data.category?.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())} + + {data.ride_sub_type && ( + + {data.ride_sub_type.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())} + + )} + + {data.status?.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())} + + {actionType === 'create' && ( + New Ride + )} + {actionType === 'edit' && ( + Edit + )} + {actionType === 'delete' && ( + Delete + )} +
+
+
+ + {/* Statistics Grid */} + {(heightDisplay || speedDisplay || lengthDisplay || dropDisplay || data.duration_seconds || data.inversions) && ( +
+ {heightDisplay && ( +
+
+ + Height +
+
{heightDisplay}
+
+ )} + + {speedDisplay && ( +
+
+ + Speed +
+
{speedDisplay}
+
+ )} + + {lengthDisplay && ( +
+
+ + Length +
+
{lengthDisplay}
+
+ )} + + {dropDisplay && ( +
+
+ + Drop +
+
{dropDisplay}
+
+ )} + + {data.duration_seconds && ( +
+
+ + Duration +
+
{Math.floor(data.duration_seconds / 60)}:{(data.duration_seconds % 60).toString().padStart(2, '0')}
+
+ )} + + {data.inversions !== null && data.inversions !== undefined && ( +
+
+ + Inversions +
+
{data.inversions}
+
+ )} +
+ )} + + {/* Companies */} + {(manufacturer || designer || model) && ( +
+
+ + Companies & Model +
+
+ {manufacturer && ( +
+ Manufacturer:{' '} + {manufacturer} +
+ )} + {designer && ( +
+ Designer:{' '} + {designer} +
+ )} + {model && ( +
+ Model:{' '} + {model} +
+ )} +
+
+ )} + + {/* Dates */} + {(data.opening_date || data.closing_date) && ( +
+
+ + Dates +
+
+ {data.opening_date && ( +
+ Opened:{' '} + {new Date(data.opening_date).toLocaleDateString()} + {data.opening_date_precision && data.opening_date_precision !== 'day' && ( + ({data.opening_date_precision}) + )} +
+ )} + {data.closing_date && ( +
+ Closed:{' '} + {new Date(data.closing_date).toLocaleDateString()} + {data.closing_date_precision && data.closing_date_precision !== 'day' && ( + ({data.closing_date_precision}) + )} +
+ )} +
+
+ )} + + {/* Description */} + {data.description && ( +
+
Description
+
+ {data.description} +
+
+ )} + + {/* Additional Details */} + {(data.capacity_per_hour || data.max_g_force || data.height_requirement || data.age_requirement) && ( +
+
Additional Details
+
+ {data.capacity_per_hour && ( +
Capacity/Hour: {data.capacity_per_hour}
+ )} + {data.max_g_force && ( +
Max G-Force: {data.max_g_force}g
+ )} + {data.height_requirement && ( +
Height Req: {data.height_requirement} cm
+ )} + {data.age_requirement && ( +
Age Req: {data.age_requirement}+
+ )} +
+
+ )} + + {/* Source */} + {data.source_url && ( + + Source + + + )} + + {/* Submission Notes */} + {data.submission_notes && ( +
+
+ + Submitter Notes +
+
+ {data.submission_notes} +
+
+ )} + + {/* Images Preview */} + {(data.banner_image_url || data.card_image_url) && ( +
+ +
Images
+
+ {data.banner_image_url && ( +
+ Banner +
Banner
+
+ )} + {data.card_image_url && ( +
+ Card +
Card
+
+ )} +
+
+ )} +
+ ); +}