MVP Build

This commit is contained in:
Eric Gullickson
2025-08-09 12:47:15 -05:00
parent 2e8816df7f
commit 8f5117a4e2
92 changed files with 5910 additions and 0 deletions

View File

@@ -0,0 +1,115 @@
/**
* @ai-summary Vehicle form component for create/edit
*/
import React from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { Button } from '../../../shared-minimal/components/Button';
import { CreateVehicleRequest } from '../types/vehicles.types';
const vehicleSchema = z.object({
vin: z.string().length(17, 'VIN must be exactly 17 characters'),
nickname: z.string().optional(),
color: z.string().optional(),
licensePlate: z.string().optional(),
odometerReading: z.number().min(0).optional(),
});
interface VehicleFormProps {
onSubmit: (data: CreateVehicleRequest) => void;
onCancel: () => void;
initialData?: Partial<CreateVehicleRequest>;
loading?: boolean;
}
export const VehicleForm: React.FC<VehicleFormProps> = ({
onSubmit,
onCancel,
initialData,
loading,
}) => {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<CreateVehicleRequest>({
resolver: zodResolver(vehicleSchema),
defaultValues: initialData,
});
return (
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
VIN <span className="text-red-500">*</span>
</label>
<input
{...register('vin')}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500"
placeholder="Enter 17-character VIN"
/>
{errors.vin && (
<p className="mt-1 text-sm text-red-600">{errors.vin.message}</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Nickname
</label>
<input
{...register('nickname')}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500"
placeholder="e.g., Family Car"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Color
</label>
<input
{...register('color')}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500"
placeholder="e.g., Blue"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
License Plate
</label>
<input
{...register('licensePlate')}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500"
placeholder="e.g., ABC-123"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Current Odometer Reading
</label>
<input
{...register('odometerReading', { valueAsNumber: true })}
type="number"
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500"
placeholder="e.g., 50000"
/>
</div>
<div className="flex gap-3 justify-end pt-4">
<Button variant="secondary" onClick={onCancel} type="button">
Cancel
</Button>
<Button type="submit" loading={loading}>
{initialData ? 'Update Vehicle' : 'Add Vehicle'}
</Button>
</div>
</form>
);
};