mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 06:11:11 -05:00
Add coaster-specific fields
This commit is contained in:
@@ -20,6 +20,7 @@ import { FlexibleDateInput, type DatePrecision } from '@/components/ui/flexible-
|
|||||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||||
import { Combobox } from '@/components/ui/combobox';
|
import { Combobox } from '@/components/ui/combobox';
|
||||||
import { SlugField } from '@/components/ui/slug-field';
|
import { SlugField } from '@/components/ui/slug-field';
|
||||||
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
import { toast } from '@/hooks/use-toast';
|
import { toast } from '@/hooks/use-toast';
|
||||||
import { handleError } from '@/lib/errorHandler';
|
import { handleError } from '@/lib/errorHandler';
|
||||||
import { Plus, Zap, Save, X } from 'lucide-react';
|
import { Plus, Zap, Save, X } from 'lucide-react';
|
||||||
@@ -97,6 +98,39 @@ const intensityLevels = [
|
|||||||
'extreme'
|
'extreme'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const TRACK_MATERIALS = [
|
||||||
|
{ value: 'wood', label: 'Wood' },
|
||||||
|
{ value: 'steel', label: 'Steel' },
|
||||||
|
{ value: 'hybrid', label: 'Hybrid' },
|
||||||
|
{ value: 'aluminum', label: 'Aluminum' },
|
||||||
|
{ value: 'composite', label: 'Composite' },
|
||||||
|
{ value: 'other', label: 'Other' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const SUPPORT_MATERIALS = [
|
||||||
|
{ value: 'steel', label: 'Steel' },
|
||||||
|
{ value: 'wood', label: 'Wood' },
|
||||||
|
{ value: 'concrete', label: 'Concrete' },
|
||||||
|
{ value: 'aluminum', label: 'Aluminum' },
|
||||||
|
{ value: 'composite', label: 'Composite' },
|
||||||
|
{ value: 'other', label: 'Other' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const PROPULSION_METHODS = [
|
||||||
|
{ value: 'chain_lift', label: 'Chain Lift' },
|
||||||
|
{ value: 'cable_lift', label: 'Cable Lift' },
|
||||||
|
{ value: 'friction_wheel_lift', label: 'Friction Wheel Lift' },
|
||||||
|
{ value: 'lsm_launch', label: 'LSM Launch' },
|
||||||
|
{ value: 'lim_launch', label: 'LIM Launch' },
|
||||||
|
{ value: 'hydraulic_launch', label: 'Hydraulic Launch' },
|
||||||
|
{ value: 'compressed_air_launch', label: 'Compressed Air Launch' },
|
||||||
|
{ value: 'flywheel_launch', label: 'Flywheel Launch' },
|
||||||
|
{ value: 'gravity', label: 'Gravity' },
|
||||||
|
{ value: 'tire_drive', label: 'Tire Drive' },
|
||||||
|
{ value: 'water_propulsion', label: 'Water Propulsion' },
|
||||||
|
{ value: 'other', label: 'Other' },
|
||||||
|
];
|
||||||
|
|
||||||
// Status value mappings between display (form) and database values
|
// Status value mappings between display (form) and database values
|
||||||
const STATUS_DISPLAY_TO_DB: Record<string, string> = {
|
const STATUS_DISPLAY_TO_DB: Record<string, string> = {
|
||||||
'Operating': 'operating',
|
'Operating': 'operating',
|
||||||
@@ -645,24 +679,82 @@ export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }:
|
|||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-3">
|
||||||
<Label>Track Material</Label>
|
<Label>Track Material(s)</Label>
|
||||||
<Select
|
<p className="text-sm text-muted-foreground">Select all materials used in the track</p>
|
||||||
onValueChange={(value) => setValue('track_material', value === '' ? undefined : value as 'wood' | 'steel' | 'hybrid' | 'aluminum' | 'other')}
|
<div className="grid grid-cols-2 gap-3">
|
||||||
defaultValue={initialData?.track_material || ''}
|
{TRACK_MATERIALS.map((material) => (
|
||||||
>
|
<div key={material.value} className="flex items-center space-x-2">
|
||||||
<SelectTrigger>
|
<Checkbox
|
||||||
<SelectValue placeholder="Select track material" />
|
id={`track_${material.value}`}
|
||||||
</SelectTrigger>
|
checked={watch('track_material')?.includes(material.value) || false}
|
||||||
<SelectContent>
|
onCheckedChange={(checked) => {
|
||||||
<SelectItem value="">None</SelectItem>
|
const current = watch('track_material') || [];
|
||||||
<SelectItem value="wood">Wood</SelectItem>
|
if (checked) {
|
||||||
<SelectItem value="steel">Steel</SelectItem>
|
setValue('track_material', [...current, material.value]);
|
||||||
<SelectItem value="hybrid">Hybrid (Wood/Steel)</SelectItem>
|
} else {
|
||||||
<SelectItem value="aluminum">Aluminum</SelectItem>
|
setValue('track_material', current.filter((v) => v !== material.value));
|
||||||
<SelectItem value="other">Other</SelectItem>
|
}
|
||||||
</SelectContent>
|
}}
|
||||||
</Select>
|
/>
|
||||||
|
<Label htmlFor={`track_${material.value}`} className="font-normal cursor-pointer">
|
||||||
|
{material.label}
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
<Label>Support Material(s)</Label>
|
||||||
|
<p className="text-sm text-muted-foreground">Select all materials used in the supports</p>
|
||||||
|
<div className="grid grid-cols-2 gap-3">
|
||||||
|
{SUPPORT_MATERIALS.map((material) => (
|
||||||
|
<div key={material.value} className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id={`support_${material.value}`}
|
||||||
|
checked={watch('support_material')?.includes(material.value) || false}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
const current = watch('support_material') || [];
|
||||||
|
if (checked) {
|
||||||
|
setValue('support_material', [...current, material.value]);
|
||||||
|
} else {
|
||||||
|
setValue('support_material', current.filter((v) => v !== material.value));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Label htmlFor={`support_${material.value}`} className="font-normal cursor-pointer">
|
||||||
|
{material.label}
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
<Label>Propulsion Method(s)</Label>
|
||||||
|
<p className="text-sm text-muted-foreground">Select all propulsion methods used</p>
|
||||||
|
<div className="grid grid-cols-2 gap-3">
|
||||||
|
{PROPULSION_METHODS.map((method) => (
|
||||||
|
<div key={method.value} className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id={`propulsion_${method.value}`}
|
||||||
|
checked={watch('propulsion_method')?.includes(method.value) || false}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
const current = watch('propulsion_method') || [];
|
||||||
|
if (checked) {
|
||||||
|
setValue('propulsion_method', [...current, method.value]);
|
||||||
|
} else {
|
||||||
|
setValue('propulsion_method', current.filter((v) => v !== method.value));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Label htmlFor={`propulsion_${method.value}`} className="font-normal cursor-pointer">
|
||||||
|
{method.label}
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -3081,13 +3081,15 @@ export type Database = {
|
|||||||
opening_date: string | null
|
opening_date: string | null
|
||||||
opening_date_precision: string | null
|
opening_date_precision: string | null
|
||||||
park_id: string | null
|
park_id: string | null
|
||||||
|
propulsion_method: string[] | null
|
||||||
ride_model_id: string | null
|
ride_model_id: string | null
|
||||||
ride_sub_type: string | null
|
ride_sub_type: string | null
|
||||||
seating_type: string | null
|
seating_type: string | null
|
||||||
slug: string
|
slug: string
|
||||||
status: string
|
status: string
|
||||||
submission_id: string
|
submission_id: string
|
||||||
track_material: string | null
|
support_material: string[] | null
|
||||||
|
track_material: string[] | null
|
||||||
updated_at: string
|
updated_at: string
|
||||||
}
|
}
|
||||||
Insert: {
|
Insert: {
|
||||||
@@ -3120,13 +3122,15 @@ export type Database = {
|
|||||||
opening_date?: string | null
|
opening_date?: string | null
|
||||||
opening_date_precision?: string | null
|
opening_date_precision?: string | null
|
||||||
park_id?: string | null
|
park_id?: string | null
|
||||||
|
propulsion_method?: string[] | null
|
||||||
ride_model_id?: string | null
|
ride_model_id?: string | null
|
||||||
ride_sub_type?: string | null
|
ride_sub_type?: string | null
|
||||||
seating_type?: string | null
|
seating_type?: string | null
|
||||||
slug: string
|
slug: string
|
||||||
status?: string
|
status?: string
|
||||||
submission_id: string
|
submission_id: string
|
||||||
track_material?: string | null
|
support_material?: string[] | null
|
||||||
|
track_material?: string[] | null
|
||||||
updated_at?: string
|
updated_at?: string
|
||||||
}
|
}
|
||||||
Update: {
|
Update: {
|
||||||
@@ -3159,13 +3163,15 @@ export type Database = {
|
|||||||
opening_date?: string | null
|
opening_date?: string | null
|
||||||
opening_date_precision?: string | null
|
opening_date_precision?: string | null
|
||||||
park_id?: string | null
|
park_id?: string | null
|
||||||
|
propulsion_method?: string[] | null
|
||||||
ride_model_id?: string | null
|
ride_model_id?: string | null
|
||||||
ride_sub_type?: string | null
|
ride_sub_type?: string | null
|
||||||
seating_type?: string | null
|
seating_type?: string | null
|
||||||
slug?: string
|
slug?: string
|
||||||
status?: string
|
status?: string
|
||||||
submission_id?: string
|
submission_id?: string
|
||||||
track_material?: string | null
|
support_material?: string[] | null
|
||||||
|
track_material?: string[] | null
|
||||||
updated_at?: string
|
updated_at?: string
|
||||||
}
|
}
|
||||||
Relationships: [
|
Relationships: [
|
||||||
@@ -3304,12 +3310,14 @@ export type Database = {
|
|||||||
opening_date: string | null
|
opening_date: string | null
|
||||||
opening_date_precision: string | null
|
opening_date_precision: string | null
|
||||||
park_id: string | null
|
park_id: string | null
|
||||||
|
propulsion_method: string[] | null
|
||||||
ride_id: string
|
ride_id: string
|
||||||
ride_model_id: string | null
|
ride_model_id: string | null
|
||||||
slug: string
|
slug: string
|
||||||
status: string
|
status: string
|
||||||
submission_id: string | null
|
submission_id: string | null
|
||||||
track_material: string | null
|
support_material: string[] | null
|
||||||
|
track_material: string[] | null
|
||||||
version_id: string
|
version_id: string
|
||||||
version_number: number
|
version_number: number
|
||||||
}
|
}
|
||||||
@@ -3344,12 +3352,14 @@ export type Database = {
|
|||||||
opening_date?: string | null
|
opening_date?: string | null
|
||||||
opening_date_precision?: string | null
|
opening_date_precision?: string | null
|
||||||
park_id?: string | null
|
park_id?: string | null
|
||||||
|
propulsion_method?: string[] | null
|
||||||
ride_id: string
|
ride_id: string
|
||||||
ride_model_id?: string | null
|
ride_model_id?: string | null
|
||||||
slug: string
|
slug: string
|
||||||
status: string
|
status: string
|
||||||
submission_id?: string | null
|
submission_id?: string | null
|
||||||
track_material?: string | null
|
support_material?: string[] | null
|
||||||
|
track_material?: string[] | null
|
||||||
version_id?: string
|
version_id?: string
|
||||||
version_number: number
|
version_number: number
|
||||||
}
|
}
|
||||||
@@ -3384,12 +3394,14 @@ export type Database = {
|
|||||||
opening_date?: string | null
|
opening_date?: string | null
|
||||||
opening_date_precision?: string | null
|
opening_date_precision?: string | null
|
||||||
park_id?: string | null
|
park_id?: string | null
|
||||||
|
propulsion_method?: string[] | null
|
||||||
ride_id?: string
|
ride_id?: string
|
||||||
ride_model_id?: string | null
|
ride_model_id?: string | null
|
||||||
slug?: string
|
slug?: string
|
||||||
status?: string
|
status?: string
|
||||||
submission_id?: string | null
|
submission_id?: string | null
|
||||||
track_material?: string | null
|
support_material?: string[] | null
|
||||||
|
track_material?: string[] | null
|
||||||
version_id?: string
|
version_id?: string
|
||||||
version_number?: number
|
version_number?: number
|
||||||
}
|
}
|
||||||
@@ -3527,13 +3539,15 @@ export type Database = {
|
|||||||
opening_date: string | null
|
opening_date: string | null
|
||||||
opening_date_precision: string | null
|
opening_date_precision: string | null
|
||||||
park_id: string
|
park_id: string
|
||||||
|
propulsion_method: string[] | null
|
||||||
review_count: number | null
|
review_count: number | null
|
||||||
ride_model_id: string | null
|
ride_model_id: string | null
|
||||||
ride_sub_type: string | null
|
ride_sub_type: string | null
|
||||||
seating_type: string | null
|
seating_type: string | null
|
||||||
slug: string
|
slug: string
|
||||||
status: string
|
status: string
|
||||||
track_material: string | null
|
support_material: string[] | null
|
||||||
|
track_material: string[] | null
|
||||||
updated_at: string
|
updated_at: string
|
||||||
view_count_30d: number | null
|
view_count_30d: number | null
|
||||||
view_count_7d: number | null
|
view_count_7d: number | null
|
||||||
@@ -3570,13 +3584,15 @@ export type Database = {
|
|||||||
opening_date?: string | null
|
opening_date?: string | null
|
||||||
opening_date_precision?: string | null
|
opening_date_precision?: string | null
|
||||||
park_id: string
|
park_id: string
|
||||||
|
propulsion_method?: string[] | null
|
||||||
review_count?: number | null
|
review_count?: number | null
|
||||||
ride_model_id?: string | null
|
ride_model_id?: string | null
|
||||||
ride_sub_type?: string | null
|
ride_sub_type?: string | null
|
||||||
seating_type?: string | null
|
seating_type?: string | null
|
||||||
slug: string
|
slug: string
|
||||||
status?: string
|
status?: string
|
||||||
track_material?: string | null
|
support_material?: string[] | null
|
||||||
|
track_material?: string[] | null
|
||||||
updated_at?: string
|
updated_at?: string
|
||||||
view_count_30d?: number | null
|
view_count_30d?: number | null
|
||||||
view_count_7d?: number | null
|
view_count_7d?: number | null
|
||||||
@@ -3613,13 +3629,15 @@ export type Database = {
|
|||||||
opening_date?: string | null
|
opening_date?: string | null
|
||||||
opening_date_precision?: string | null
|
opening_date_precision?: string | null
|
||||||
park_id?: string
|
park_id?: string
|
||||||
|
propulsion_method?: string[] | null
|
||||||
review_count?: number | null
|
review_count?: number | null
|
||||||
ride_model_id?: string | null
|
ride_model_id?: string | null
|
||||||
ride_sub_type?: string | null
|
ride_sub_type?: string | null
|
||||||
seating_type?: string | null
|
seating_type?: string | null
|
||||||
slug?: string
|
slug?: string
|
||||||
status?: string
|
status?: string
|
||||||
track_material?: string | null
|
support_material?: string[] | null
|
||||||
|
track_material?: string[] | null
|
||||||
updated_at?: string
|
updated_at?: string
|
||||||
view_count_30d?: number | null
|
view_count_30d?: number | null
|
||||||
view_count_7d?: number | null
|
view_count_7d?: number | null
|
||||||
|
|||||||
@@ -76,6 +76,9 @@ export function transformRideData(submissionData: RideSubmissionData): RideInser
|
|||||||
coaster_type: submissionData.coaster_type || null,
|
coaster_type: submissionData.coaster_type || null,
|
||||||
seating_type: submissionData.seating_type || null,
|
seating_type: submissionData.seating_type || null,
|
||||||
intensity_level: submissionData.intensity_level || null,
|
intensity_level: submissionData.intensity_level || null,
|
||||||
|
track_material: submissionData.track_material || null,
|
||||||
|
support_material: submissionData.support_material || null,
|
||||||
|
propulsion_method: submissionData.propulsion_method || null,
|
||||||
banner_image_url: submissionData.banner_image_url || null,
|
banner_image_url: submissionData.banner_image_url || null,
|
||||||
banner_image_id: submissionData.banner_image_id || null,
|
banner_image_id: submissionData.banner_image_id || null,
|
||||||
card_image_url: submissionData.card_image_url || null,
|
card_image_url: submissionData.card_image_url || null,
|
||||||
|
|||||||
@@ -133,7 +133,9 @@ export const rideValidationSchema = z.object({
|
|||||||
coaster_type: z.string().optional(),
|
coaster_type: z.string().optional(),
|
||||||
seating_type: z.string().optional(),
|
seating_type: z.string().optional(),
|
||||||
intensity_level: z.string().optional(),
|
intensity_level: z.string().optional(),
|
||||||
track_material: z.enum(['wood', 'steel', 'hybrid', 'aluminum', 'other']).optional(),
|
track_material: z.array(z.string()).optional().nullable(),
|
||||||
|
support_material: z.array(z.string()).optional().nullable(),
|
||||||
|
propulsion_method: z.array(z.string()).optional().nullable(),
|
||||||
// Water ride specific fields
|
// Water ride specific fields
|
||||||
water_depth_cm: z.preprocess(
|
water_depth_cm: z.preprocess(
|
||||||
(val) => val === '' || val === null || val === undefined ? undefined : Number(val),
|
(val) => val === '' || val === null || val === undefined ? undefined : Number(val),
|
||||||
|
|||||||
@@ -182,7 +182,8 @@ export default function Rides() {
|
|||||||
|
|
||||||
// Track material filter
|
// Track material filter
|
||||||
if (filters.trackMaterials.length > 0) {
|
if (filters.trackMaterials.length > 0) {
|
||||||
if (!ride.track_material || !filters.trackMaterials.includes(ride.track_material)) {
|
if (!ride.track_material || ride.track_material.length === 0 ||
|
||||||
|
!ride.track_material.some(material => filters.trackMaterials.includes(material))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -166,7 +166,9 @@ export interface Ride {
|
|||||||
coaster_type?: string;
|
coaster_type?: string;
|
||||||
seating_type?: string;
|
seating_type?: string;
|
||||||
intensity_level?: string;
|
intensity_level?: string;
|
||||||
track_material?: string;
|
track_material?: string[];
|
||||||
|
support_material?: string[];
|
||||||
|
propulsion_method?: string[];
|
||||||
drop_height_meters?: number;
|
drop_height_meters?: number;
|
||||||
max_g_force?: number;
|
max_g_force?: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,9 @@ export interface RideSubmissionData {
|
|||||||
coaster_type?: string | null;
|
coaster_type?: string | null;
|
||||||
seating_type?: string | null;
|
seating_type?: string | null;
|
||||||
intensity_level?: string | null;
|
intensity_level?: string | null;
|
||||||
track_material?: string | null;
|
track_material?: string[] | null;
|
||||||
|
support_material?: string[] | null;
|
||||||
|
propulsion_method?: string[] | null;
|
||||||
// Water ride specific
|
// Water ride specific
|
||||||
water_depth_cm?: number | null;
|
water_depth_cm?: number | null;
|
||||||
splash_height_meters?: number | null;
|
splash_height_meters?: number | null;
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
-- Add coaster-specific multi-select fields for track material, support material, and propulsion method
|
||||||
|
|
||||||
|
-- Update rides table
|
||||||
|
ALTER TABLE rides
|
||||||
|
ALTER COLUMN track_material TYPE TEXT[] USING
|
||||||
|
CASE
|
||||||
|
WHEN track_material IS NULL THEN NULL
|
||||||
|
ELSE ARRAY[track_material]
|
||||||
|
END,
|
||||||
|
ADD COLUMN IF NOT EXISTS support_material TEXT[],
|
||||||
|
ADD COLUMN IF NOT EXISTS propulsion_method TEXT[];
|
||||||
|
|
||||||
|
-- Update ride_versions table
|
||||||
|
ALTER TABLE ride_versions
|
||||||
|
ALTER COLUMN track_material TYPE TEXT[] USING
|
||||||
|
CASE
|
||||||
|
WHEN track_material IS NULL THEN NULL
|
||||||
|
ELSE ARRAY[track_material]
|
||||||
|
END,
|
||||||
|
ADD COLUMN IF NOT EXISTS support_material TEXT[],
|
||||||
|
ADD COLUMN IF NOT EXISTS propulsion_method TEXT[];
|
||||||
|
|
||||||
|
-- Update ride_submissions table
|
||||||
|
ALTER TABLE ride_submissions
|
||||||
|
ALTER COLUMN track_material TYPE TEXT[] USING
|
||||||
|
CASE
|
||||||
|
WHEN track_material IS NULL THEN NULL
|
||||||
|
ELSE ARRAY[track_material]
|
||||||
|
END,
|
||||||
|
ADD COLUMN IF NOT EXISTS support_material TEXT[],
|
||||||
|
ADD COLUMN IF NOT EXISTS propulsion_method TEXT[];
|
||||||
|
|
||||||
|
-- Create indexes for efficient filtering
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_rides_track_material ON rides USING GIN(track_material);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_rides_support_material ON rides USING GIN(support_material);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_rides_propulsion_method ON rides USING GIN(propulsion_method);
|
||||||
|
|
||||||
|
-- Add comments for documentation
|
||||||
|
COMMENT ON COLUMN rides.track_material IS 'Array of track materials used in the ride (e.g., wood, steel, hybrid)';
|
||||||
|
COMMENT ON COLUMN rides.support_material IS 'Array of support materials used in the ride (e.g., steel, wood, concrete)';
|
||||||
|
COMMENT ON COLUMN rides.propulsion_method IS 'Array of propulsion methods used in the ride (e.g., chain_lift, lsm_launch, hydraulic_launch)';
|
||||||
Reference in New Issue
Block a user