feat: Implement comprehensive plan

This commit is contained in:
gpt-engineer-app[bot]
2025-09-29 20:31:28 +00:00
parent 07b036bb7d
commit f586b31954
6 changed files with 990 additions and 19 deletions

View File

@@ -7,12 +7,18 @@ import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
import { Label } from '@/components/ui/label';
import { Badge } from '@/components/ui/badge';
import { PhotoUpload } from '@/components/upload/PhotoUpload';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { DatePicker } from '@/components/ui/date-picker';
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Combobox } from '@/components/ui/combobox';
import { toast } from '@/hooks/use-toast';
import { Zap, Save, X } from 'lucide-react';
import { Plus, Zap, Save, X } from 'lucide-react';
import { useUnitPreferences } from '@/hooks/useUnitPreferences';
import { useManufacturers, useRideModels } from '@/hooks/useAutocompleteData';
import { ManufacturerForm } from './ManufacturerForm';
import { RideModelForm } from './RideModelForm';
import {
convertSpeed,
convertDistance,
@@ -50,7 +56,10 @@ const rideSchema = z.object({
max_g_force: z.number().optional(),
former_names: z.string().optional(),
coaster_stats: z.string().optional(),
technical_specs: z.string().optional()
technical_specs: z.string().optional(),
// Manufacturer and model
manufacturer_id: z.string().uuid().optional(),
ride_model_id: z.string().uuid().optional()
});
type RideFormData = z.infer<typeof rideSchema>;
@@ -111,6 +120,20 @@ export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }:
const { preferences } = useUnitPreferences();
const measurementSystem = preferences.measurement_system;
// Manufacturer and model state
const [selectedManufacturerId, setSelectedManufacturerId] = useState<string>(
initialData?.manufacturer_id || ''
);
const [selectedManufacturerName, setSelectedManufacturerName] = useState<string>('');
const [tempNewManufacturer, setTempNewManufacturer] = useState<any>(null);
const [tempNewRideModel, setTempNewRideModel] = useState<any>(null);
const [isManufacturerModalOpen, setIsManufacturerModalOpen] = useState(false);
const [isModelModalOpen, setIsModelModalOpen] = useState(false);
// Fetch data
const { manufacturers, loading: manufacturersLoading } = useManufacturers();
const { rideModels, loading: modelsLoading } = useRideModels(selectedManufacturerId);
const {
register,
handleSubmit,
@@ -154,7 +177,9 @@ export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }:
max_g_force: initialData?.max_g_force || undefined,
former_names: initialData?.former_names || '',
coaster_stats: initialData?.coaster_stats || '',
technical_specs: initialData?.technical_specs || ''
technical_specs: initialData?.technical_specs || '',
manufacturer_id: initialData?.manufacturer_id || undefined,
ride_model_id: initialData?.ride_model_id || undefined
}
});
@@ -199,13 +224,36 @@ export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }:
image_url: rideImage || undefined
};
await onSubmit(metricData);
// Build composite submission if new entities were created
const submissionContent: any = {
ride: metricData,
};
// Add new manufacturer if created
if (tempNewManufacturer) {
submissionContent.new_manufacturer = tempNewManufacturer;
submissionContent.ride.manufacturer_id = null; // Clear since using new
}
// Add new ride model if created
if (tempNewRideModel) {
submissionContent.new_ride_model = tempNewRideModel;
submissionContent.ride.ride_model_id = null; // Clear since using new
}
// Pass composite data to parent
await onSubmit({
...metricData,
_compositeSubmission: submissionContent
} as any);
toast({
title: isEditing ? "Ride Updated" : "Ride Created",
title: isEditing ? "Ride Updated" : "Submission Sent",
description: isEditing
? "The ride information has been updated successfully."
: "The new ride has been created successfully."
: tempNewManufacturer
? "Ride, manufacturer, and model submitted for review"
: "Ride submitted for review"
});
} catch (error: any) {
toast({
@@ -320,6 +368,144 @@ export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }:
</div>
</div>
{/* Manufacturer & Model Selection */}
<div className="space-y-4">
<h3 className="text-lg font-semibold">Manufacturer & Model</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Manufacturer Column */}
<div className="space-y-2">
<Label>Manufacturer</Label>
{tempNewManufacturer ? (
// Show temp manufacturer badge
<div className="flex items-center gap-2 p-3 border rounded-md bg-blue-50 dark:bg-blue-950">
<Badge variant="secondary">New</Badge>
<span className="font-medium">{tempNewManufacturer.name}</span>
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => {
setTempNewManufacturer(null);
setTempNewRideModel(null); // Clear model too
}}
>
<X className="w-4 h-4" />
</Button>
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => setIsManufacturerModalOpen(true)}
>
Edit
</Button>
</div>
) : (
// Show combobox for existing manufacturers
<Combobox
options={manufacturers}
value={watch('manufacturer_id')}
onValueChange={(value) => {
setValue('manufacturer_id', value);
setSelectedManufacturerId(value);
// Find and set manufacturer name
const mfr = manufacturers.find(m => m.value === value);
setSelectedManufacturerName(mfr?.label || '');
// Clear model when manufacturer changes
setValue('ride_model_id', undefined);
setTempNewRideModel(null);
}}
placeholder="Select manufacturer"
searchPlaceholder="Search manufacturers..."
emptyText="No manufacturers found"
loading={manufacturersLoading}
/>
)}
{/* Create New Manufacturer Button */}
{!tempNewManufacturer && (
<Button
type="button"
variant="outline"
size="sm"
className="w-full"
onClick={() => setIsManufacturerModalOpen(true)}
>
<Plus className="w-4 h-4 mr-2" />
Create New Manufacturer
</Button>
)}
</div>
{/* Ride Model Column - Conditional */}
{(selectedManufacturerId || tempNewManufacturer) && (
<div className="space-y-2">
<Label>Ride Model (Optional)</Label>
{tempNewRideModel ? (
// Show temp model badge
<div className="flex items-center gap-2 p-3 border rounded-md bg-purple-50 dark:bg-purple-950">
<Badge variant="secondary">New</Badge>
<span className="font-medium">{tempNewRideModel.name}</span>
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => setTempNewRideModel(null)}
>
<X className="w-4 h-4" />
</Button>
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => setIsModelModalOpen(true)}
>
Edit
</Button>
</div>
) : (
// Show combobox for existing models
<>
<Combobox
options={rideModels}
value={watch('ride_model_id')}
onValueChange={(value) => setValue('ride_model_id', value)}
placeholder="Select model"
searchPlaceholder="Search models..."
emptyText={tempNewManufacturer
? "Create the manufacturer first to add models"
: "No models found for this manufacturer"}
loading={modelsLoading}
disabled={!!tempNewManufacturer}
/>
{/* Create New Model Button */}
<Button
type="button"
variant="outline"
size="sm"
className="w-full"
onClick={() => setIsModelModalOpen(true)}
>
<Plus className="w-4 h-4 mr-2" />
Create New Model
</Button>
</>
)}
<p className="text-xs text-muted-foreground">
{tempNewManufacturer
? "New models will be created after manufacturer approval"
: "Select a specific model or leave blank"}
</p>
</div>
)}
</div>
</div>
{/* Dates */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-2">
@@ -565,6 +751,61 @@ export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }:
)}
</div>
</form>
{/* Manufacturer Modal */}
<Dialog open={isManufacturerModalOpen} onOpenChange={setIsManufacturerModalOpen}>
<DialogContent className="max-w-3xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>
{tempNewManufacturer ? 'Edit New Manufacturer' : 'Create New Manufacturer'}
</DialogTitle>
<DialogDescription>
This manufacturer will be submitted for moderation along with the ride.
</DialogDescription>
</DialogHeader>
<ManufacturerForm
initialData={tempNewManufacturer}
onSubmit={(data) => {
setTempNewManufacturer(data);
setSelectedManufacturerName(data.name);
setIsManufacturerModalOpen(false);
// Clear existing manufacturer selection
setValue('manufacturer_id', undefined);
setSelectedManufacturerId('');
// Clear any existing model
setValue('ride_model_id', undefined);
setTempNewRideModel(null);
}}
onCancel={() => setIsManufacturerModalOpen(false)}
/>
</DialogContent>
</Dialog>
{/* Ride Model Modal */}
<Dialog open={isModelModalOpen} onOpenChange={setIsModelModalOpen}>
<DialogContent className="max-w-3xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>
{tempNewRideModel ? 'Edit New Ride Model' : 'Create New Ride Model'}
</DialogTitle>
<DialogDescription>
Creating a model for: <strong>{selectedManufacturerName || tempNewManufacturer?.name}</strong>
</DialogDescription>
</DialogHeader>
<RideModelForm
manufacturerName={selectedManufacturerName || tempNewManufacturer?.name}
manufacturerId={selectedManufacturerId}
initialData={tempNewRideModel}
onSubmit={(data) => {
setTempNewRideModel(data);
setIsModelModalOpen(false);
// Clear existing model selection
setValue('ride_model_id', undefined);
}}
onCancel={() => setIsModelModalOpen(false)}
/>
</DialogContent>
</Dialog>
</CardContent>
</Card>
);