"use client" import { ArrowLeft, Building2, ChevronRight, Flag, MapPin, PencilLine, Plus, Search } from "lucide-react" import { useEffect, useMemo, useRef, useState } from "react" import { fetchFacilityCourses, fetchOwnCourse, nearbyOfficialFacilities, searchOfficialFacilities, searchOwnCourses, } from "@/lib/ny-runde/api" import type { Course, Facility } from "@/lib/ny-runde/types" import { CreateCourseForm } from "./create-course-form" import { ChoiceCard, Field, NativeSelect, Panel, Pill, TextInput, useDebouncedValue } from "./primitives" import { useWizard } from "./wizard-context" export function Step1CourseTime() { const { state, patch, exit } = useWizard() const sub = state.s1Sub function pickCourse(course: Course, meta: { source: "teeoff"; facilitySlug: string; teeoffCourseId: number } | { source: "custom"; personalCourseId: string }) { if (state.templateMode) { patch({ templateSeed: course, templateMode: false, s1Sub: "own-create" }) return } const compatible = state.ownGender ? course.tees.filter((t) => t.genders.includes(state.ownGender!)) : course.tees const tee = compatible[0] patch({ course, courseMeta: meta, courseSource: meta.source === "teeoff" ? "official" : "own", teeId: tee?.id, s1Sub: "fields", // Eierens EGET spillerkort (steg 3) har sitt eget teeId-felt, atskilt // fra state.teeId over -- uten denne synkroniseringen viser kortet // "Ikke valgt" selv om et utslag faktisk er valgt (kun kosmetisk, selve // innsendingen leser state.teeId direkte for eieren -- men samme // synkronisering som den opprinnelige new-round.tsx sin chooseCourse() // gjorde, så oppførselen matcher eksakt). players: state.players.map((p) => (p.kind === "owner" ? { ...p, teeId: tee?.id } : p)), }) } switch (sub) { case "source": return ( patch({ s1Sub: "official-search" })} onOwn={() => patch({ s1Sub: "own-search" })} onBack={exit} /> ) case "template-source": return ( patch({ templateMode: true, s1Sub: "official-search" })} onOwn={() => patch({ templateMode: true, s1Sub: "own-search" })} onBack={() => patch({ s1Sub: "own-create" })} /> ) case "official-search": return ( patch({ s1Sub: state.templateMode ? "template-source" : "source" })} onPickFacility={(f) => patch({ selectedFacilitySlug: f.slug, s1Sub: "official-courses" })} /> ) case "official-courses": return ( patch({ s1Sub: "official-search" })} onPick={(teeoffCourseId, course) => pickCourse(course, { source: "teeoff", facilitySlug: state.selectedFacilitySlug!, teeoffCourseId }) } /> ) case "own-search": return ( patch({ s1Sub: state.templateMode ? "template-source" : "source" })} onCreate={() => patch({ s1Sub: "own-create", templateSeed: undefined, ownCreateOrigin: "own-search" })} onPick={(course) => pickCourse(course, { source: "custom", personalCourseId: course.id })} /> ) case "own-create": return ( patch({ s1Sub: "own-search", templateSeed: undefined })} onTemplate={() => patch({ s1Sub: "template-source", ownCreateOrigin: "own-create" })} onCreate={(course) => pickCourse(course, { source: "custom", personalCourseId: course.id })} seed={state.templateSeed} /> ) case "fields": return default: return null } } /* ------------------------------------------------------------------ */ function SubHeader({ title, onBack, subtitle }: { title: string; onBack: () => void; subtitle?: string }) { return (

{title}

{subtitle ?

{subtitle}

: null}
) } /* ------------------------------------------------------------------ */ /* source / template-source */ function SourceScreen({ templateMode, onOfficial, onOwn, onBack, }: { templateMode: boolean onOfficial: () => void onOwn: () => void onBack: () => void }) { return (
} title="Offisiell bane" description="Søk opp en registrert golfbane fra databasen." /> } title="Egen bane" description="Bruk eller lag din egen manuelt registrerte bane." />
) } /* ------------------------------------------------------------------ */ /* official-search (med geolokasjon "nærmest deg") */ function OfficialSearch({ templateMode, onBack, onPickFacility, }: { templateMode: boolean onBack: () => void onPickFacility: (f: Facility) => void }) { const [query, setQuery] = useState("") const debounced = useDebouncedValue(query, 250) const [results, setResults] = useState(null) const [nearest, setNearest] = useState<(Facility & { distanceKm: number })[]>([]) const requested = useRef(false) useEffect(() => { if (requested.current) return requested.current = true if (typeof navigator === "undefined" || !navigator.geolocation) return navigator.geolocation.getCurrentPosition( (pos) => { void nearbyOfficialFacilities(pos.coords.latitude, pos.coords.longitude).then(setNearest) }, () => { /* stille ved avslag/feil */ }, { timeout: 8000 }, ) }, []) useEffect(() => { const trimmed = debounced.trim() if (trimmed === "") { setResults(null) return } let cancelled = false void searchOfficialFacilities(trimmed).then((r) => { if (!cancelled) setResults(r) }) return () => { cancelled = true } }, [debounced]) return (
{results === null ? ( nearest.length > 0 ? ( <>

    {nearest.map((f) => ( onPickFacility(f)} distance={f.distanceKm * 1000 < 1000 ? `${Math.round(f.distanceKm * 1000)} m` : `${Math.round(f.distanceKm)} km`} /> ))}
) : (

Begynn å skrive for å søke etter en bane.

) ) : results.length > 0 ? (
    {results.map((f) => ( onPickFacility(f)} /> ))}
) : (

Ingen anlegg matcher «{debounced}».

)}
) } function FacilityRow({ facility, onClick, distance }: { facility: Facility; onClick: () => void; distance?: string }) { return (
  • ) } /* ------------------------------------------------------------------ */ /* official-courses */ function OfficialCourses({ slug, templateMode, onBack, onPick, }: { slug?: string templateMode: boolean onBack: () => void onPick: (teeoffCourseId: number, course: Course) => void }) { const [courses, setCourses] = useState<{ teeoffCourseId: number; course: Course }[] | null>(null) const [loadError, setLoadError] = useState(false) useEffect(() => { if (!slug) return let cancelled = false setCourses(null) setLoadError(false) fetchFacilityCourses(slug) .then((r) => { if (!cancelled) setCourses(r) }) .catch(() => { if (!cancelled) setLoadError(true) }) return () => { cancelled = true } }, [slug]) return (
    {loadError ? (

    Klarte ikke å hente baner fra teeoff akkurat nå. Prøv igjen om litt.

    ) : courses === null ? (

    Laster baner…

    ) : (
      {courses.map(({ teeoffCourseId, course }) => (
    • onPick(teeoffCourseId, course)} />
    • ))}
    )}
    ) } function CourseRow({ course, templateMode, onPick }: { course: Course; templateMode: boolean; onPick: () => void }) { return ( ) } /* ------------------------------------------------------------------ */ /* own-search */ function OwnSearch({ templateMode, onBack, onCreate, onPick, }: { templateMode: boolean onBack: () => void onCreate: () => void onPick: (c: Course) => void }) { const [query, setQuery] = useState("") const debounced = useDebouncedValue(query, 250) const [results, setResults] = useState<{ id: string; name: string }[] | null>(null) const inputRef = useRef(null) useEffect(() => { inputRef.current?.focus() }, []) useEffect(() => { const trimmed = debounced.trim() if (trimmed === "") { setResults(null) return } let cancelled = false void searchOwnCourses(trimmed).then((r) => { if (!cancelled) setResults(r) }) return () => { cancelled = true } }, [debounced]) async function pick(id: string) { const course = await fetchOwnCourse(id) if (course) onPick(course) } return (
    {!templateMode ? ( ) : null}
    {results === null ? (

    Søk etter en egen bane{templateMode ? "" : ", eller opprett en ny"}.

    ) : results.length > 0 ? (
      {results.map((c) => (
    • ))}
    ) : (

    Fant ingen egne baner som matcher «{debounced}».

    )}
    ) } /* ------------------------------------------------------------------ */ /* own-create */ function OwnCreate({ seed, onBack, onTemplate, onCreate, }: { seed?: Course onBack: () => void onTemplate: () => void onCreate: (c: Course) => void }) { return (
    {!seed ? ( ) : (
    Forhåndsutfylt fra «{seed.name}»
    )}
    ) } /* ------------------------------------------------------------------ */ /* fields */ function Fields() { const { state, patch } = useWizard() const course = state.course if (!course) return null const compatibleTees = state.ownGender ? course.tees.filter((t) => t.genders.includes(state.ownGender!)) : course.tees const noCompatibleTee = compatibleTees.length === 0 return (

    Bane & tid

    Bekreft detaljene for runden.

    {course.name}

    {course.tees.length} utslag

    {state.courseSource === "own" ? "Egen bane" : "Offisiell"}
    patch({ roundName: e.target.value.slice(0, 200) })} placeholder={course.name} maxLength={200} /> {noCompatibleTee ? (
    Ingen kompatible utslag.
    ) : ( patch({ teeId: e.target.value })}> {compatibleTees.map((t) => ( ))} )}
    patch({ date: e.target.value })} /> patch({ teeTime: e.target.value })} />
    patch({ startHole: Number.parseInt(e.target.value, 10) })} > {Array.from({ length: 18 }, (_, i) => i + 1).map((h) => ( ))}
    ) }