diff --git a/src/components/admin/RideForm.tsx b/src/components/admin/RideForm.tsx index 6e234f84..52b16a18 100644 --- a/src/components/admin/RideForm.tsx +++ b/src/components/admin/RideForm.tsx @@ -618,6 +618,26 @@ export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }: + +
+ + +
diff --git a/src/components/profile/RideCreditFilters.tsx b/src/components/profile/RideCreditFilters.tsx index 8f3a0966..b904fe9e 100644 --- a/src/components/profile/RideCreditFilters.tsx +++ b/src/components/profile/RideCreditFilters.tsx @@ -42,6 +42,10 @@ export function RideCreditFilters({ const cities = new Set(); const parks = new Map(); const manufacturers = new Map(); + const coasterTypes = new Set(); + const seatingTypes = new Set(); + const intensityLevels = new Set(); + const trackMaterials = new Set(); credits.forEach(credit => { const location = credit.rides?.parks?.locations; @@ -56,6 +60,22 @@ export function RideCreditFilters({ if (credit.rides?.manufacturer?.id && credit.rides?.manufacturer?.name) { manufacturers.set(credit.rides.manufacturer.id, credit.rides.manufacturer.name); } + + if (credit.rides?.coaster_type) { + coasterTypes.add(credit.rides.coaster_type); + } + + if (credit.rides?.seating_type) { + seatingTypes.add(credit.rides.seating_type); + } + + if (credit.rides?.intensity_level) { + intensityLevels.add(credit.rides.intensity_level); + } + + if (credit.rides?.track_material) { + trackMaterials.add(credit.rides.track_material); + } }); return { @@ -64,6 +84,10 @@ export function RideCreditFilters({ cities: Array.from(cities).sort(), parks: Array.from(parks.entries()).map(([id, name]) => ({ id, name })).sort((a, b) => a.name.localeCompare(b.name)), manufacturers: Array.from(manufacturers.entries()).map(([id, name]) => ({ id, name })).sort((a, b) => a.name.localeCompare(b.name)), + coasterTypes: Array.from(coasterTypes).sort(), + seatingTypes: Array.from(seatingTypes).sort(), + intensityLevels: Array.from(intensityLevels).sort(), + trackMaterials: Array.from(trackMaterials).sort(), }; }, [credits]); @@ -295,6 +319,86 @@ export function RideCreditFilters({
)} + {filterOptions.coasterTypes.length > 0 && ( +
+ + ({ + label: type.charAt(0).toUpperCase() + type.slice(1), + value: type + }))} + value={filters.coasterTypes || []} + onValueChange={(values) => + onFilterChange('coasterTypes', values.length > 0 ? values : undefined) + } + placeholder="Select coaster types..." + searchPlaceholder="Search types..." + emptyText="No coaster types found" + className={compact ? "h-9 text-sm" : ""} + /> +
+ )} + + {filterOptions.seatingTypes.length > 0 && ( +
+ + ({ + label: type.replace('_', ' ').replace(/\b\w/g, l => l.toUpperCase()), + value: type + }))} + value={filters.seatingTypes || []} + onValueChange={(values) => + onFilterChange('seatingTypes', values.length > 0 ? values : undefined) + } + placeholder="Select seating types..." + searchPlaceholder="Search types..." + emptyText="No seating types found" + className={compact ? "h-9 text-sm" : ""} + /> +
+ )} + + {filterOptions.intensityLevels.length > 0 && ( +
+ + ({ + label: level.charAt(0).toUpperCase() + level.slice(1), + value: level + }))} + value={filters.intensityLevels || []} + onValueChange={(values) => + onFilterChange('intensityLevels', values.length > 0 ? values : undefined) + } + placeholder="Select intensity levels..." + searchPlaceholder="Search intensity..." + emptyText="No intensity levels found" + className={compact ? "h-9 text-sm" : ""} + /> +
+ )} + + {filterOptions.trackMaterials.length > 0 && ( +
+ + ({ + label: tm.charAt(0).toUpperCase() + tm.slice(1), + value: tm + }))} + value={filters.trackMaterial || []} + onValueChange={(values) => + onFilterChange('trackMaterial', values.length > 0 ? values : undefined) + } + placeholder="Select track materials..." + searchPlaceholder="Search materials..." + emptyText="No track materials found" + className={compact ? "h-9 text-sm" : ""} + /> +
+ )} +
diff --git a/src/hooks/useRideCreditFilters.ts b/src/hooks/useRideCreditFilters.ts index 9a7f93e3..f72f9278 100644 --- a/src/hooks/useRideCreditFilters.ts +++ b/src/hooks/useRideCreditFilters.ts @@ -160,6 +160,38 @@ export function useRideCreditFilters(credits: UserRideCredit[]) { }); } + // Coaster types filter + if (filters.coasterTypes && filters.coasterTypes.length > 0) { + result = result.filter(credit => + credit.rides?.coaster_type && + filters.coasterTypes!.includes(credit.rides.coaster_type) + ); + } + + // Seating types filter + if (filters.seatingTypes && filters.seatingTypes.length > 0) { + result = result.filter(credit => + credit.rides?.seating_type && + filters.seatingTypes!.includes(credit.rides.seating_type) + ); + } + + // Intensity levels filter + if (filters.intensityLevels && filters.intensityLevels.length > 0) { + result = result.filter(credit => + credit.rides?.intensity_level && + filters.intensityLevels!.includes(credit.rides.intensity_level) + ); + } + + // Track material filter + if (filters.trackMaterial && filters.trackMaterial.length > 0) { + result = result.filter(credit => + credit.rides?.track_material && + filters.trackMaterial!.includes(credit.rides.track_material) + ); + } + // User rating if (filters.hasRating === true) { result = result.filter(credit => credit.personal_rating !== null); diff --git a/src/integrations/supabase/types.ts b/src/integrations/supabase/types.ts index 2859019b..9d702edc 100644 --- a/src/integrations/supabase/types.ts +++ b/src/integrations/supabase/types.ts @@ -2487,6 +2487,7 @@ export type Database = { slug: string status: string submission_id: string + track_material: string | null updated_at: string } Insert: { @@ -2525,6 +2526,7 @@ export type Database = { slug: string status?: string submission_id: string + track_material?: string | null updated_at?: string } Update: { @@ -2563,6 +2565,7 @@ export type Database = { slug?: string status?: string submission_id?: string + track_material?: string | null updated_at?: string } Relationships: [ @@ -2656,6 +2659,7 @@ export type Database = { slug: string status: string submission_id: string | null + track_material: string | null version_id: string version_number: number } @@ -2695,6 +2699,7 @@ export type Database = { slug: string status: string submission_id?: string | null + track_material?: string | null version_id?: string version_number: number } @@ -2734,6 +2739,7 @@ export type Database = { slug?: string status?: string submission_id?: string | null + track_material?: string | null version_id?: string version_number?: number } @@ -2827,6 +2833,7 @@ export type Database = { seating_type: string | null slug: string status: string + track_material: string | null updated_at: string view_count_30d: number | null view_count_7d: number | null @@ -2869,6 +2876,7 @@ export type Database = { seating_type?: string | null slug: string status?: string + track_material?: string | null updated_at?: string view_count_30d?: number | null view_count_7d?: number | null @@ -2911,6 +2919,7 @@ export type Database = { seating_type?: string | null slug?: string status?: string + track_material?: string | null updated_at?: string view_count_30d?: number | null view_count_7d?: number | null diff --git a/src/lib/entityValidationSchemas.ts b/src/lib/entityValidationSchemas.ts index 798d47fd..1c365afe 100644 --- a/src/lib/entityValidationSchemas.ts +++ b/src/lib/entityValidationSchemas.ts @@ -125,6 +125,7 @@ export const rideValidationSchema = z.object({ coaster_type: z.string().optional(), seating_type: z.string().optional(), intensity_level: z.string().optional(), + track_material: z.enum(['wood', 'steel', 'hybrid', 'aluminum', 'other']).optional(), banner_image_id: z.string().optional(), banner_image_url: z.string().optional(), card_image_id: z.string().optional(), diff --git a/src/types/database.ts b/src/types/database.ts index ff4151ac..16521043 100644 --- a/src/types/database.ts +++ b/src/types/database.ts @@ -166,6 +166,7 @@ export interface Ride { coaster_type?: string; seating_type?: string; intensity_level?: string; + track_material?: string; drop_height_meters?: number; max_g_force?: number; } diff --git a/supabase/functions/process-selective-approval/index.ts b/supabase/functions/process-selective-approval/index.ts index 6e075bca..265025a6 100644 --- a/supabase/functions/process-selective-approval/index.ts +++ b/supabase/functions/process-selective-approval/index.ts @@ -20,7 +20,7 @@ const RIDE_FIELDS = [ 'capacity_per_hour', 'duration_seconds', 'max_speed_kmh', 'max_height_meters', 'length_meters', 'inversions', 'ride_sub_type', 'coaster_type', 'seating_type', 'intensity_level', - 'drop_height_meters', 'max_g_force', 'image_url', + 'track_material', 'drop_height_meters', 'max_g_force', 'image_url', 'banner_image_url', 'banner_image_id', 'card_image_url', 'card_image_id' ]; diff --git a/supabase/migrations/20251016195134_e13e1b78-9574-49e5-aac8-6bc25831d022.sql b/supabase/migrations/20251016195134_e13e1b78-9574-49e5-aac8-6bc25831d022.sql new file mode 100644 index 00000000..b7f303fc --- /dev/null +++ b/supabase/migrations/20251016195134_e13e1b78-9574-49e5-aac8-6bc25831d022.sql @@ -0,0 +1,17 @@ +-- Add track_material column to rides table +ALTER TABLE rides +ADD COLUMN track_material text; + +COMMENT ON COLUMN rides.track_material IS 'Material used for the ride track (wood, steel, hybrid, aluminum, etc.)'; + +-- Add track_material to ride_versions table for version tracking +ALTER TABLE ride_versions +ADD COLUMN track_material text; + +-- Add track_material to ride_submissions table +ALTER TABLE ride_submissions +ADD COLUMN track_material text; + +-- Create an index for filtering +CREATE INDEX idx_rides_track_material ON rides(track_material) +WHERE track_material IS NOT NULL; \ No newline at end of file