mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 08:51:13 -05:00
feat: Add park selection to RideForm
This commit is contained in:
@@ -23,10 +23,10 @@ import { SlugField } from '@/components/ui/slug-field';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { toast } from '@/hooks/use-toast';
|
||||
import { handleError } from '@/lib/errorHandler';
|
||||
import { Plus, Zap, Save, X, Building2 } from 'lucide-react';
|
||||
import { Plus, Zap, Save, X, Building2, AlertCircle } from 'lucide-react';
|
||||
import { toDateOnly, parseDateOnly, toDateWithPrecision } from '@/lib/dateUtils';
|
||||
import { useUnitPreferences } from '@/hooks/useUnitPreferences';
|
||||
import { useManufacturers, useRideModels } from '@/hooks/useAutocompleteData';
|
||||
import { useManufacturers, useRideModels, useParks } from '@/hooks/useAutocompleteData';
|
||||
import { useUserRole } from '@/hooks/useUserRole';
|
||||
import { ManufacturerForm } from './ManufacturerForm';
|
||||
import { RideModelForm } from './RideModelForm';
|
||||
@@ -208,12 +208,14 @@ export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }:
|
||||
// Fetch data
|
||||
const { manufacturers, loading: manufacturersLoading } = useManufacturers();
|
||||
const { rideModels, loading: modelsLoading } = useRideModels(selectedManufacturerId);
|
||||
const { parks, loading: parksLoading } = useParks();
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
watch,
|
||||
trigger,
|
||||
formState: { errors }
|
||||
} = useForm<RideFormData>({
|
||||
resolver: zodResolver(entitySchemas.ride),
|
||||
@@ -256,11 +258,13 @@ export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }:
|
||||
ride_model_id: initialData?.ride_model_id || undefined,
|
||||
source_url: initialData?.source_url || '',
|
||||
submission_notes: initialData?.submission_notes || '',
|
||||
images: { uploaded: [] }
|
||||
images: { uploaded: [] },
|
||||
park_id: initialData?.park_id || undefined
|
||||
}
|
||||
});
|
||||
|
||||
const selectedCategory = watch('category');
|
||||
const isParkPreselected = !!initialData?.park_id; // Coming from park detail page
|
||||
|
||||
|
||||
const handleFormSubmit = async (data: RideFormData) => {
|
||||
@@ -419,6 +423,96 @@ export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }:
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Park Selection */}
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg font-semibold">Park Information</h3>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="flex items-center gap-1">
|
||||
Park
|
||||
<span className="text-destructive">*</span>
|
||||
</Label>
|
||||
|
||||
{tempNewPark ? (
|
||||
// Show temp park badge
|
||||
<div className="flex items-center gap-2 p-3 border rounded-md bg-green-50 dark:bg-green-950">
|
||||
<Badge variant="secondary">New</Badge>
|
||||
<span className="font-medium">{tempNewPark.name}</span>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setTempNewPark(null);
|
||||
}}
|
||||
disabled={isParkPreselected}
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setIsParkModalOpen(true)}
|
||||
disabled={isParkPreselected}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
// Show combobox for existing parks
|
||||
<Combobox
|
||||
options={parks}
|
||||
value={watch('park_id') || undefined}
|
||||
onValueChange={(value) => {
|
||||
setValue('park_id', value);
|
||||
trigger('park_id');
|
||||
}}
|
||||
placeholder={isParkPreselected ? "Park pre-selected" : "Select a park"}
|
||||
searchPlaceholder="Search parks..."
|
||||
emptyText="No parks found"
|
||||
loading={parksLoading}
|
||||
disabled={isParkPreselected}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Validation error display */}
|
||||
{errors.park_id && (
|
||||
<p className="text-sm text-destructive flex items-center gap-1">
|
||||
<AlertCircle className="w-4 h-4" />
|
||||
{errors.park_id.message}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Create New Park Button */}
|
||||
{!tempNewPark && !isParkPreselected && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="w-full"
|
||||
onClick={() => setIsParkModalOpen(true)}
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Create New Park
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{/* Help text */}
|
||||
{isParkPreselected ? (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Park is pre-selected from the park detail page and cannot be changed.
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{tempNewPark
|
||||
? "New park will be created when submission is approved"
|
||||
: "Select the park where this ride is located"}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Category and Status */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div className="space-y-2">
|
||||
|
||||
@@ -301,4 +301,46 @@ export function usePropertyOwners() {
|
||||
}, []);
|
||||
|
||||
return { propertyOwners, loading };
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to fetch all parks for autocomplete
|
||||
* Returns parks as combobox options
|
||||
*/
|
||||
export function useParks() {
|
||||
const [parks, setParks] = useState<ComboboxOption[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchParks() {
|
||||
setLoading(true);
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('parks')
|
||||
.select('id, name, slug')
|
||||
.order('name');
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
setParks(
|
||||
(data || []).map(park => ({
|
||||
label: park.name,
|
||||
value: park.id
|
||||
}))
|
||||
);
|
||||
} catch (error: unknown) {
|
||||
handleNonCriticalError(error, { action: 'Fetch parks' });
|
||||
toast.error('Failed to load parks', {
|
||||
description: 'Please refresh the page and try again.',
|
||||
});
|
||||
setParks([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
fetchParks();
|
||||
}, []);
|
||||
|
||||
return { parks, loading };
|
||||
}
|
||||
Reference in New Issue
Block a user