"use client" import { useState } from "react" import { useForm, Controller } from "react-hook-form" import { zodResolver } from "@hookform/resolvers/zod" import * as z from "zod" import { Calculator, Loader2, Thermometer, Layers, PaintBucket } from "lucide-react" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Textarea } from "@/components/ui/textarea" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Switch } from "@/components/ui/switch" import { Slider } from "@/components/ui/slider" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { Progress } from "@/components/ui/progress" import { CONSTRAINTS, FLOORING_TYPES, type FlooringType } from "@/lib/constants" import { validateDanishPostalCode, getDistance } from "@/lib/distance" import { calculatePrice, type CalculationDetails } from "@/lib/calculations" const formSchema = z.object({ name: z.string().refine((val) => { const parts = val.trim().split(/\s+/) return parts.length >= 2 && parts[0].length >= 3 && parts[1].length >= 3 }, "Indtast fornavn og efternavn (mindst 3 tegn hver)"), email: z.string().email("Ugyldig email"), phone: z.string().regex(/^\d{8}$/, "Telefonnummer skal være 8 cifre"), postalCode: z .string() .length(4, "Postnummer skal være 4 cifre") .refine(validateDanishPostalCode, "Ugyldigt dansk postnummer"), address: z.string().optional(), area: z.coerce .number() .min(CONSTRAINTS.MIN_AREA, `Minimum areal er ${CONSTRAINTS.MIN_AREA} m²`) .max(CONSTRAINTS.MAX_AREA, `Maximum areal er ${CONSTRAINTS.MAX_AREA} m²`), height: z.coerce .number() .min(CONSTRAINTS.MIN_HEIGHT, `Minimum højde er ${CONSTRAINTS.MIN_HEIGHT} cm`) .max(CONSTRAINTS.MAX_HEIGHT, `Maximum højde er ${CONSTRAINTS.MAX_HEIGHT} cm`), remarks: z.string().optional(), includeInsulation: z.boolean(), includeFloorHeating: z.boolean(), includeCompound: z.boolean(), flooringType: z.string(), }) type FormData = z.infer interface CalculatorFormProps { onCalculation: ( result: CalculationDetails, formData?: FormData, distanceSource?: "openrouteservice" | "table" ) => void showDetails?: boolean } interface CalculationProgress { step: string progress: number } export function CalculatorForm({ onCalculation, showDetails = false }: CalculatorFormProps) { const [isCalculating, setIsCalculating] = useState(false) const [calculationProgress, setCalculationProgress] = useState(null) const [result, setResult] = useState(null) const [distanceSource, setDistanceSource] = useState<"openrouteservice" | "table" | null>(null) const { register, handleSubmit, formState: { errors }, watch, control, } = useForm({ resolver: zodResolver(formSchema), defaultValues: { name: "", email: "", phone: "", postalCode: "", address: "", area: 50, height: 15, remarks: "", includeInsulation: true, includeFloorHeating: true, includeCompound: true, flooringType: "STANDARD", }, }) const watchedIncludeCompound = watch("includeCompound") const onSubmit = async (data: FormData) => { setIsCalculating(true) setDistanceSource(null) setCalculationProgress({ step: "Finder din adresse...", progress: 20 }) try { let distance: number let source: "openrouteservice" | "table" = "table" try { setCalculationProgress({ step: "Beregner afstand...", progress: 40 }) const params = new URLSearchParams({ postalCode: data.postalCode, ...(data.address && { address: data.address }), }) const distanceResponse = await fetch(`/api/distance?${params}`) const distanceData = await distanceResponse.json() distance = distanceData.distance source = distanceData.source } catch { distance = getDistance(data.postalCode) source = "table" } setDistanceSource(source) setCalculationProgress({ step: "Beregner pris...", progress: 70 }) await new Promise((resolve) => setTimeout(resolve, 200)) const calculationResult = calculatePrice({ area: data.area, height: data.height, postalCode: data.postalCode, distance, includeInsulation: data.includeInsulation, includeFloorHeating: data.includeFloorHeating, includeCompound: data.includeCompound, flooringType: data.flooringType as FlooringType, }) setCalculationProgress({ step: "Færdig!", progress: 100 }) await new Promise((resolve) => setTimeout(resolve, 300)) setResult(calculationResult) onCalculation(calculationResult, data, source) } finally { setIsCalculating(false) setCalculationProgress(null) } } return (
Prisberegner
Få et hurtigt overslag på din nye gulvløsning
{/* Contact Section */}

Kontaktoplysninger

{errors.name &&

{errors.name.message}

}
{errors.email &&

{errors.email.message}

}
{errors.phone &&

{errors.phone.message}

}
{errors.postalCode && (

{errors.postalCode.message}

)}
{/* Floor Dimensions Section */}

Gulvmål

{/* Area Slider */}
( field.onChange(Number(e.target.value))} className="h-8 w-16 border-0 bg-transparent p-0 text-right text-lg font-semibold" min={CONSTRAINTS.MIN_AREA} max={CONSTRAINTS.MAX_AREA} /> )} />
( field.onChange(value)} className="py-2" /> )} />
{CONSTRAINTS.MIN_AREA} m² {CONSTRAINTS.MAX_AREA} m²
{errors.area &&

{errors.area.message}

}
{/* Height Slider */}
( field.onChange(Number(e.target.value))} className="h-8 w-16 border-0 bg-transparent p-0 text-right text-lg font-semibold" min={CONSTRAINTS.MIN_HEIGHT} max={CONSTRAINTS.MAX_HEIGHT} /> )} /> cm
( field.onChange(value)} className="py-2" /> )} />
{CONSTRAINTS.MIN_HEIGHT} cm {CONSTRAINTS.MAX_HEIGHT} cm
{errors.height && (

{errors.height.message}

)}
{/* Components Section */}

Vælg komponenter

{/* Insulation Toggle */} ( )} /> {/* Floor Heating Toggle */} ( )} /> {/* Compound Toggle */} ( )} />
{/* Flooring Type Section */} {watchedIncludeCompound && (

Gulvbelægning

(
{Object.entries(FLOORING_TYPES).map(([key, type]) => ( ))}
)} />
)} {/* Remarks Section */}

Bemærkninger (valgfrit)