From 9b1964d634cae20e9e969be2e200d1c670119f43 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Tue, 4 Nov 2025 01:48:11 +0000 Subject: [PATCH] Implement Phase 2 display enhancements --- .../moderation/SubmissionItemsList.tsx | 24 +- .../displays/RichCompanyDisplay.tsx | 26 +- .../moderation/displays/RichParkDisplay.tsx | 38 +- .../moderation/displays/RichRideDisplay.tsx | 473 ++++++++++++++++-- .../displays/RichRideModelDisplay.tsx | 156 ++++++ 5 files changed, 658 insertions(+), 59 deletions(-) create mode 100644 src/components/moderation/displays/RichRideModelDisplay.tsx diff --git a/src/components/moderation/SubmissionItemsList.tsx b/src/components/moderation/SubmissionItemsList.tsx index 50e237be..25eb3d31 100644 --- a/src/components/moderation/SubmissionItemsList.tsx +++ b/src/components/moderation/SubmissionItemsList.tsx @@ -5,13 +5,14 @@ import { PhotoSubmissionDisplay } from './PhotoSubmissionDisplay'; import { RichParkDisplay } from './displays/RichParkDisplay'; import { RichRideDisplay } from './displays/RichRideDisplay'; import { RichCompanyDisplay } from './displays/RichCompanyDisplay'; +import { RichRideModelDisplay } from './displays/RichRideModelDisplay'; import { Skeleton } from '@/components/ui/skeleton'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { Badge } from '@/components/ui/badge'; import { AlertCircle, Loader2 } from 'lucide-react'; import { format } from 'date-fns'; import type { SubmissionItemData } from '@/types/submissions'; -import type { ParkSubmissionData, RideSubmissionData, CompanySubmissionData } from '@/types/submission-data'; +import type { ParkSubmissionData, RideSubmissionData, CompanySubmissionData, RideModelSubmissionData } from '@/types/submission-data'; import { logger } from '@/lib/logger'; import { getErrorMessage } from '@/lib/errorHandler'; import { ModerationErrorBoundary } from '@/components/error/ModerationErrorBoundary'; @@ -109,6 +110,15 @@ export const SubmissionItemsList = memo(function SubmissionItemsList({ entityData = data as any; break; } + case 'ride_model': { + const { data } = await supabase + .from('ride_model_submissions') + .select('*') + .eq('id', item.item_data_id) + .maybeSingle(); + entityData = data as any; + break; + } default: entityData = null; } @@ -267,6 +277,18 @@ export const SubmissionItemsList = memo(function SubmissionItemsList({ ); } + if (item.item_type === 'ride_model' && entityData) { + return ( + <> + {itemMetadata} + + + ); + } + // Fallback to SubmissionChangesDisplay return ( <> diff --git a/src/components/moderation/displays/RichCompanyDisplay.tsx b/src/components/moderation/displays/RichCompanyDisplay.tsx index 70631043..0301a746 100644 --- a/src/components/moderation/displays/RichCompanyDisplay.tsx +++ b/src/components/moderation/displays/RichCompanyDisplay.tsx @@ -142,11 +142,21 @@ export function RichCompanyDisplay({ data, actionType, showAllFields = true }: R )} {/* Images Preview */} - {(data.banner_image_url || data.card_image_url) && ( + {(data.logo_url || data.banner_image_url || data.card_image_url) && (
Images
+ {data.logo_url && ( +
+ Logo +
Logo
+
+ )} {data.banner_image_url && (
Banner -
Banner
+
+ Banner + {data.banner_image_id && ( + ID: {data.banner_image_id.slice(0, 8)}... + )} +
)} {data.card_image_url && ( @@ -164,7 +179,12 @@ export function RichCompanyDisplay({ data, actionType, showAllFields = true }: R alt="Card" className="w-full h-24 object-cover rounded border" /> -
Card
+
+ Card + {data.card_image_id && ( + ID: {data.card_image_id.slice(0, 8)}... + )} +
)}
diff --git a/src/components/moderation/displays/RichParkDisplay.tsx b/src/components/moderation/displays/RichParkDisplay.tsx index 6bc87e23..c572d19a 100644 --- a/src/components/moderation/displays/RichParkDisplay.tsx +++ b/src/components/moderation/displays/RichParkDisplay.tsx @@ -111,6 +111,30 @@ export function RichParkDisplay({ data, actionType, showAllFields = true }: Rich {/* Key Information Grid */}
+ {/* Contact Information */} + {(data.phone || data.email) && ( +
+
+ + Contact +
+
+ {data.phone && ( +
+ Phone:{' '} + {data.phone} +
+ )} + {data.email && ( +
+ Email:{' '} + {data.email} +
+ )} +
+
+ )} + {/* Dates */} {(data.opening_date || data.closing_date) && (
@@ -231,7 +255,12 @@ export function RichParkDisplay({ data, actionType, showAllFields = true }: Rich alt="Banner" className="w-full h-24 object-cover rounded border" /> -
Banner
+
+ Banner + {data.banner_image_id && ( + ID: {data.banner_image_id.slice(0, 8)}... + )} +
)} {data.card_image_url && ( @@ -241,7 +270,12 @@ export function RichParkDisplay({ data, actionType, showAllFields = true }: Rich alt="Card" className="w-full h-24 object-cover rounded border" /> -
Card
+
+ Card + {data.card_image_id && ( + ID: {data.card_image_id.slice(0, 8)}... + )} +
)} diff --git a/src/components/moderation/displays/RichRideDisplay.tsx b/src/components/moderation/displays/RichRideDisplay.tsx index b9b8e45e..81886189 100644 --- a/src/components/moderation/displays/RichRideDisplay.tsx +++ b/src/components/moderation/displays/RichRideDisplay.tsx @@ -1,6 +1,8 @@ -import { Train, Gauge, Ruler, Zap, Calendar, Building, User, ExternalLink, AlertCircle, TrendingUp } from 'lucide-react'; +import { Train, Gauge, Ruler, Zap, Calendar, Building, User, ExternalLink, AlertCircle, TrendingUp, Droplets, Sparkles, RotateCw, Baby, Navigation } from 'lucide-react'; import { Badge } from '@/components/ui/badge'; import { Separator } from '@/components/ui/separator'; +import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; +import { ChevronDown, ChevronRight } from 'lucide-react'; import type { RideSubmissionData } from '@/types/submission-data'; import { useEffect, useState } from 'react'; import { supabase } from '@/lib/supabaseClient'; @@ -16,10 +18,11 @@ export function RichRideDisplay({ data, actionType, showAllFields = true }: Rich const [manufacturer, setManufacturer] = useState(null); const [designer, setDesigner] = useState(null); const [model, setModel] = useState(null); + const [showCategorySpecific, setShowCategorySpecific] = useState(false); + const [showTechnical, setShowTechnical] = useState(false); useEffect(() => { const fetchRelatedData = async () => { - // Fetch park if (data.park_id) { const { data: parkData } = await supabase .from('parks') @@ -29,7 +32,6 @@ export function RichRideDisplay({ data, actionType, showAllFields = true }: Rich setPark(parkData?.name || null); } - // Fetch manufacturer if (data.manufacturer_id) { const { data: mfgData } = await supabase .from('companies') @@ -39,7 +41,6 @@ export function RichRideDisplay({ data, actionType, showAllFields = true }: Rich setManufacturer(mfgData?.name || null); } - // Fetch designer if (data.designer_id) { const { data: designerData } = await supabase .from('companies') @@ -49,7 +50,6 @@ export function RichRideDisplay({ data, actionType, showAllFields = true }: Rich setDesigner(designerData?.name || null); } - // Fetch model if (data.ride_model_id) { const { data: modelData } = await supabase .from('ride_models') @@ -73,19 +73,15 @@ export function RichRideDisplay({ data, actionType, showAllFields = true }: Rich } }; - // 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; + // Determine which category-specific section to show + const category = data.category?.toLowerCase(); + const hasWaterFields = category === 'water_ride' && (data.water_depth_cm || data.splash_height_meters || data.wetness_level || data.flume_type || data.boat_capacity); + const hasDarkRideFields = category === 'dark_ride' && (data.theme_name || data.story_description || data.show_duration_seconds || data.animatronics_count || data.projection_type || data.ride_system || data.scenes_count); + const hasFlatRideFields = category === 'flat_ride' && (data.rotation_type || data.motion_pattern || data.platform_count || data.swing_angle_degrees || data.rotation_speed_rpm || data.arm_length_meters || data.max_height_reached_meters); + const hasKiddieFields = category === 'kiddie_ride' && (data.min_age || data.max_age || data.educational_theme || data.character_theme); + const hasTransportFields = category === 'transport_ride' && (data.transport_type || data.route_length_meters || data.stations_count || data.vehicle_capacity || data.vehicles_count || data.round_trip_duration_seconds); + + const hasTechnicalFields = data.track_material || data.support_material || data.propulsion_method || data.coaster_type || data.seating_type || data.intensity_level; return (
@@ -124,46 +120,46 @@ export function RichRideDisplay({ data, actionType, showAllFields = true }: Rich
- {/* Statistics Grid */} - {(heightDisplay || speedDisplay || lengthDisplay || dropDisplay || data.duration_seconds || data.inversions) && ( + {/* Primary Statistics Grid */} + {(data.max_height_meters || data.max_speed_kmh || data.length_meters || data.drop_height_meters || data.duration_seconds || data.inversions !== null) && (
- {heightDisplay && ( + {data.max_height_meters && (
Height
-
{heightDisplay}
+
{data.max_height_meters.toFixed(1)} m
)} - {speedDisplay && ( + {data.max_speed_kmh && (
Speed
-
{speedDisplay}
+
{data.max_speed_kmh.toFixed(1)} km/h
)} - {lengthDisplay && ( + {data.length_meters && (
Length
-
{lengthDisplay}
+
{data.length_meters.toFixed(1)} m
)} - {dropDisplay && ( + {data.drop_height_meters && (
Drop
-
{dropDisplay}
+
{data.drop_height_meters.toFixed(1)} m
)} @@ -189,7 +185,379 @@ export function RichRideDisplay({ data, actionType, showAllFields = true }: Rich
)} - {/* Companies */} + {/* Requirements & Capacity */} + {(data.height_requirement || data.age_requirement || data.capacity_per_hour || data.max_g_force) && ( +
+
Requirements & Capacity
+
+ {data.height_requirement && ( +
+ Height Requirement + {data.height_requirement} cm +
+ )} + {data.age_requirement && ( +
+ Min Age + {data.age_requirement}+ +
+ )} + {data.capacity_per_hour && ( +
+ Capacity/Hour + {data.capacity_per_hour} +
+ )} + {data.max_g_force && ( +
+ Max G-Force + {data.max_g_force}g +
+ )} +
+
+ )} + + {/* Technical Details (Collapsible) */} + {hasTechnicalFields && ( + + + {showTechnical ? : } + + Technical Specifications + + + +
+ {data.track_material && data.track_material.length > 0 && ( +
+ Track Material +
+ {data.track_material.map((material, i) => ( + + {material.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())} + + ))} +
+
+ )} + + {data.support_material && data.support_material.length > 0 && ( +
+ Support Material +
+ {data.support_material.map((material, i) => ( + + {material.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())} + + ))} +
+
+ )} + + {data.propulsion_method && data.propulsion_method.length > 0 && ( +
+ Propulsion Method +
+ {data.propulsion_method.map((method, i) => ( + + {method.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())} + + ))} +
+
+ )} + +
+ {data.coaster_type && ( +
+ Coaster Type + {data.coaster_type.replace(/_/g, ' ')} +
+ )} + {data.seating_type && ( +
+ Seating Type + {data.seating_type.replace(/_/g, ' ')} +
+ )} + {data.intensity_level && ( +
+ Intensity Level + {data.intensity_level.replace(/_/g, ' ').toUpperCase()} +
+ )} +
+
+
+
+ )} + + {/* Water Ride Fields */} + {hasWaterFields && ( + + + {showCategorySpecific ? : } + + Water Ride Specifications + + + +
+
+ {data.water_depth_cm && ( +
+ Water Depth + {data.water_depth_cm} cm +
+ )} + {data.splash_height_meters && ( +
+ Splash Height + {data.splash_height_meters.toFixed(1)} m +
+ )} + {data.wetness_level && ( +
+ Wetness Level + {data.wetness_level.toUpperCase()} +
+ )} + {data.flume_type && ( +
+ Flume Type + {data.flume_type.replace(/_/g, ' ')} +
+ )} + {data.boat_capacity && ( +
+ Boat Capacity + {data.boat_capacity} riders +
+ )} +
+
+
+
+ )} + + {/* Dark Ride Fields */} + {hasDarkRideFields && ( + + + {showCategorySpecific ? : } + + Dark Ride Details + + + +
+ {data.theme_name && ( +
+ Theme Name + {data.theme_name} +
+ )} + {data.story_description && ( +
+ Story Description +

{data.story_description}

+
+ )} +
+ {data.show_duration_seconds && ( +
+ Show Duration + {Math.floor(data.show_duration_seconds / 60)}:{(data.show_duration_seconds % 60).toString().padStart(2, '0')} +
+ )} + {data.animatronics_count && ( +
+ Animatronics + {data.animatronics_count} +
+ )} + {data.scenes_count && ( +
+ Scenes + {data.scenes_count} +
+ )} + {data.projection_type && ( +
+ Projection Type + {data.projection_type.replace(/_/g, ' ')} +
+ )} + {data.ride_system && ( +
+ Ride System + {data.ride_system.replace(/_/g, ' ')} +
+ )} +
+
+
+
+ )} + + {/* Flat Ride Fields */} + {hasFlatRideFields && ( + + + {showCategorySpecific ? : } + + Flat Ride Specifications + + + +
+
+ {data.rotation_type && ( +
+ Rotation Type + {data.rotation_type.replace(/_/g, ' ')} +
+ )} + {data.motion_pattern && ( +
+ Motion Pattern + {data.motion_pattern.replace(/_/g, ' ')} +
+ )} + {data.platform_count && ( +
+ Platforms + {data.platform_count} +
+ )} + {data.swing_angle_degrees && ( +
+ Swing Angle + {data.swing_angle_degrees}° +
+ )} + {data.rotation_speed_rpm && ( +
+ Rotation Speed + {data.rotation_speed_rpm} RPM +
+ )} + {data.arm_length_meters && ( +
+ Arm Length + {data.arm_length_meters.toFixed(1)} m +
+ )} + {data.max_height_reached_meters && ( +
+ Max Height Reached + {data.max_height_reached_meters.toFixed(1)} m +
+ )} +
+
+
+
+ )} + + {/* Kiddie Ride Fields */} + {hasKiddieFields && ( + + + {showCategorySpecific ? : } + + Kiddie Ride Details + + + +
+
+ {data.min_age && ( +
+ Min Age + {data.min_age} +
+ )} + {data.max_age && ( +
+ Max Age + {data.max_age} +
+ )} + {data.educational_theme && ( +
+ Educational Theme + {data.educational_theme} +
+ )} + {data.character_theme && ( +
+ Character Theme + {data.character_theme} +
+ )} +
+
+
+
+ )} + + {/* Transport Ride Fields */} + {hasTransportFields && ( + + + {showCategorySpecific ? : } + + Transport Ride Specifications + + + +
+
+ {data.transport_type && ( +
+ Transport Type + {data.transport_type.replace(/_/g, ' ')} +
+ )} + {data.route_length_meters && ( +
+ Route Length + {data.route_length_meters.toFixed(0)} m +
+ )} + {data.stations_count && ( +
+ Stations + {data.stations_count} +
+ )} + {data.vehicle_capacity && ( +
+ Vehicle Capacity + {data.vehicle_capacity} +
+ )} + {data.vehicles_count && ( +
+ Total Vehicles + {data.vehicles_count} +
+ )} + {data.round_trip_duration_seconds && ( +
+ Round Trip + {Math.floor(data.round_trip_duration_seconds / 60)}:{(data.round_trip_duration_seconds % 60).toString().padStart(2, '0')} +
+ )} +
+
+
+
+ )} + + {/* Companies & Model */} {(manufacturer || designer || model) && (
@@ -259,27 +627,6 @@ export function RichRideDisplay({ data, actionType, showAllFields = true }: Rich
)} - {/* 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 && (
Images
@@ -319,7 +666,12 @@ export function RichRideDisplay({ data, actionType, showAllFields = true }: Rich alt="Banner" className="w-full h-32 object-cover rounded border" /> -
Banner
+
+ Banner + {data.banner_image_id && ( + ID: {data.banner_image_id.slice(0, 8)}... + )} +
)} {data.card_image_url && ( @@ -329,7 +681,22 @@ export function RichRideDisplay({ data, actionType, showAllFields = true }: Rich alt="Card" className="w-full h-32 object-cover rounded border" /> -
Card
+
+ Card + {data.card_image_id && ( + ID: {data.card_image_id.slice(0, 8)}... + )} +
+ + )} + {data.image_url && !data.banner_image_url && !data.card_image_url && ( +
+ Ride +
Legacy Image
)} diff --git a/src/components/moderation/displays/RichRideModelDisplay.tsx b/src/components/moderation/displays/RichRideModelDisplay.tsx new file mode 100644 index 00000000..278323d8 --- /dev/null +++ b/src/components/moderation/displays/RichRideModelDisplay.tsx @@ -0,0 +1,156 @@ +import { Package, Building, Calendar, ExternalLink, AlertCircle } from 'lucide-react'; +import { Badge } from '@/components/ui/badge'; +import { Separator } from '@/components/ui/separator'; +import type { RideModelSubmissionData } from '@/types/submission-data'; +import { useEffect, useState } from 'react'; +import { supabase } from '@/lib/supabaseClient'; + +interface RichRideModelDisplayProps { + data: RideModelSubmissionData; + actionType: 'create' | 'edit' | 'delete'; + showAllFields?: boolean; +} + +export function RichRideModelDisplay({ data, actionType, showAllFields = true }: RichRideModelDisplayProps) { + const [manufacturer, setManufacturer] = useState(null); + + useEffect(() => { + const fetchManufacturer = async () => { + if (data.manufacturer_id) { + const { data: mfgData } = await supabase + .from('companies') + .select('name') + .eq('id', data.manufacturer_id) + .single(); + setManufacturer(mfgData?.name || null); + } + }; + + fetchManufacturer(); + }, [data.manufacturer_id]); + + return ( +
+ {/* Header Section */} +
+
+ +
+
+

{data.name}

+ {manufacturer && ( +
by {manufacturer}
+ )} +
+ + {data.category?.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())} + + {data.ride_type && ( + + {data.ride_type.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())} + + )} + {actionType === 'create' && ( + New Model + )} + {actionType === 'edit' && ( + Edit + )} + {actionType === 'delete' && ( + Delete + )} +
+
+
+ + {/* Manufacturer */} + {manufacturer && ( +
+
+ + Manufacturer +
+
+ {manufacturer} +
+
+ )} + + {/* Description */} + {data.description && ( +
+
Description
+
+ {data.description} +
+
+ )} + + {/* 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.banner_image_id && ( + ID: {data.banner_image_id.slice(0, 8)}... + )} +
+
+ )} + {data.card_image_url && ( +
+ Card +
+ Card + {data.card_image_id && ( + ID: {data.card_image_id.slice(0, 8)}... + )} +
+
+ )} +
+
+ )} +
+ ); +}