Approve database migration

This commit is contained in:
gpt-engineer-app[bot]
2025-10-16 15:38:28 +00:00
parent bd44597f9a
commit 0d9926a5ae
9 changed files with 1027 additions and 116 deletions

View File

@@ -9,7 +9,9 @@ import { getErrorMessage } from '@/lib/errorHandler';
import { AddRideCreditDialog } from './AddRideCreditDialog';
import { RideCreditCard } from './RideCreditCard';
import { SortableRideCreditCard } from './SortableRideCreditCard';
import { RideCreditFilters } from './RideCreditFilters';
import { UserRideCredit } from '@/types/database';
import { useRideCreditFilters } from '@/hooks/useRideCreditFilters';
import {
DndContext,
DragEndEvent,
@@ -33,10 +35,18 @@ export function RideCreditsManager({ userId }: RideCreditsManagerProps) {
const [loading, setLoading] = useState(true);
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid');
const [sortBy, setSortBy] = useState<'date' | 'count' | 'name' | 'custom'>('custom');
const [filterCategory, setFilterCategory] = useState<string>('all');
const [isAddDialogOpen, setIsAddDialogOpen] = useState(false);
const [isEditMode, setIsEditMode] = useState(false);
// Use the filter hook
const {
filters,
updateFilter,
clearFilters,
filteredCredits,
activeFilterCount,
} = useRideCreditFilters(credits);
const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: {
@@ -47,25 +57,13 @@ export function RideCreditsManager({ userId }: RideCreditsManagerProps) {
useEffect(() => {
fetchCredits();
}, [userId, sortBy, filterCategory]);
}, [userId, sortBy]);
const fetchCredits = async () => {
try {
setLoading(true);
// First, get ride IDs that match the category filter (if any)
let rideIdsToFilter: string[] | null = null;
if (filterCategory !== 'all') {
const { data: filteredRides, error: rideError } = await supabase
.from('rides')
.select('id')
.eq('category', filterCategory);
if (rideError) throw rideError;
rideIdsToFilter = filteredRides.map(r => r.id);
}
// Build main query
// Build main query with enhanced data
let query = supabase
.from('user_ride_credits')
.select(`
@@ -75,36 +73,46 @@ export function RideCreditsManager({ userId }: RideCreditsManagerProps) {
name,
slug,
category,
status,
coaster_type,
seating_type,
intensity_level,
max_speed_kmh,
max_height_meters,
length_meters,
inversions,
card_image_url,
parks:park_id (
id,
name,
slug
slug,
park_type,
locations:location_id (
country,
state_province,
city
)
),
manufacturer:manufacturer_id (
id,
name
),
designer:designer_id (
id,
name
)
)
`)
.eq('user_id', userId);
// Apply ride ID filter if category filter is active
if (rideIdsToFilter && rideIdsToFilter.length > 0) {
query = query.in('ride_id', rideIdsToFilter);
} else if (rideIdsToFilter && rideIdsToFilter.length === 0) {
// No rides match the category - return empty
setCredits([]);
setLoading(false);
return;
}
// Apply sorting - default to sort_order when available
if (sortBy === 'date') {
query = query.order('first_ride_date', { ascending: false, nullsFirst: false });
} else if (sortBy === 'count') {
query = query.order('ride_count', { ascending: false });
} else if (sortBy === 'name') {
// Note: Can't order by joined table - we'll sort client-side
query = query.order('created_at', { ascending: false });
} else {
// Default to custom sort order
query = query.order('sort_order', { ascending: true, nullsFirst: false });
}
@@ -206,6 +214,9 @@ export function RideCreditsManager({ userId }: RideCreditsManagerProps) {
coasters: credits.filter(c => c.rides?.category === 'roller_coaster').length
};
// Use filtered credits for display
const displayCredits = filteredCredits;
if (loading) {
return (
<div className="space-y-4">
@@ -250,6 +261,21 @@ export function RideCreditsManager({ userId }: RideCreditsManagerProps) {
</Card>
</div>
{/* Filters */}
<Card>
<CardContent className="pt-6">
<RideCreditFilters
filters={filters}
onFilterChange={updateFilter}
onClearFilters={clearFilters}
credits={credits}
activeFilterCount={activeFilterCount}
resultCount={displayCredits.length}
totalCount={credits.length}
/>
</CardContent>
</Card>
{/* Controls */}
<div className="flex flex-wrap gap-4 items-center justify-between">
<div className="flex gap-2">
@@ -268,19 +294,6 @@ export function RideCreditsManager({ userId }: RideCreditsManagerProps) {
</div>
<div className="flex gap-2">
<Select value={filterCategory} onValueChange={setFilterCategory}>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Filter by type" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Rides</SelectItem>
<SelectItem value="roller_coaster">Roller Coasters</SelectItem>
<SelectItem value="flat_ride">Flat Rides</SelectItem>
<SelectItem value="water_ride">Water Rides</SelectItem>
<SelectItem value="dark_ride">Dark Rides</SelectItem>
</SelectContent>
</Select>
<Select value={sortBy} onValueChange={(value) => setSortBy(value as typeof sortBy)}>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Sort by" />
@@ -313,18 +326,32 @@ export function RideCreditsManager({ userId }: RideCreditsManagerProps) {
</div>
{/* Credits Display */}
{credits.length === 0 ? (
{displayCredits.length === 0 ? (
<Card>
<CardContent className="py-12">
<div className="text-center">
<h3 className="text-xl font-semibold mb-2">No Ride Credits Yet</h3>
<p className="text-muted-foreground mb-4">
Start tracking your ride experiences
</p>
<Button onClick={() => setIsAddDialogOpen(true)}>
<Plus className="w-4 h-4 mr-2" />
Add Your First Credit
</Button>
{credits.length === 0 ? (
<>
<h3 className="text-xl font-semibold mb-2">No Ride Credits Yet</h3>
<p className="text-muted-foreground mb-4">
Start tracking your ride experiences
</p>
<Button onClick={() => setIsAddDialogOpen(true)}>
<Plus className="w-4 h-4 mr-2" />
Add Your First Credit
</Button>
</>
) : (
<>
<h3 className="text-xl font-semibold mb-2">No Results Found</h3>
<p className="text-muted-foreground mb-4">
Try adjusting your filters
</p>
<Button onClick={clearFilters} variant="outline">
Clear Filters
</Button>
</>
)}
</div>
</CardContent>
</Card>
@@ -335,19 +362,19 @@ export function RideCreditsManager({ userId }: RideCreditsManagerProps) {
onDragEnd={handleDragEnd}
>
<SortableContext
items={credits.map(c => c.id)}
items={displayCredits.map(c => c.id)}
strategy={verticalListSortingStrategy}
>
<div className={viewMode === 'grid'
? 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4'
: 'space-y-4'
}>
{credits.map((credit, index) => (
{displayCredits.map((credit, index) => (
<SortableRideCreditCard
key={credit.id}
credit={credit}
position={index + 1}
maxPosition={credits.length}
maxPosition={displayCredits.length}
viewMode={viewMode}
onUpdate={handleCreditUpdated}
onDelete={() => handleCreditDeleted(credit.id)}
@@ -362,7 +389,7 @@ export function RideCreditsManager({ userId }: RideCreditsManagerProps) {
? 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4'
: 'space-y-4'
}>
{credits.map((credit, index) => (
{displayCredits.map((credit, index) => (
<RideCreditCard
key={credit.id}
credit={credit}