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