"use client" import type { ApiGender, Course } from "@/lib/ny-runde/types" import { createOwnCourse } from "@/lib/ny-runde/api" import { cn } from "@/lib/utils" import { Plus, Trash2 } from "lucide-react" import { useMemo, useState } from "react" import { Field, NativeSelect, TextInput, Toggle, useAutoFocus } from "./primitives" interface DraftRating { cr: string slope: string par: string } interface DraftTee { id: string name: string menEnabled: boolean womenEnabled: boolean men: DraftRating women: DraftRating } interface DraftHole { par: number si: number | 0 // 0 = ikke satt } let teeCounter = 0 function newTee(): DraftTee { teeCounter += 1 return { id: `draft-tee-${teeCounter}`, name: "", menEnabled: true, womenEnabled: false, men: { cr: "", slope: "", par: "" }, women: { cr: "", slope: "", par: "" }, } } // seed.tees har KUN genders (ikke CR/Slope-tall) -- et forhåndsutfylt // utslag fra en mal kan derfor bare forhåndsutfylle navn+hvilke(t) kjønn // som er aktive, ikke selve ratingtallene (de er ikke kjent klient-side). function seedToDraft(seed?: Course): { holes: DraftHole[]; tees: DraftTee[]; name: string } { if (!seed) { return { name: "", holes: Array.from({ length: 18 }, () => ({ par: 4, si: 0 })), tees: [newTee()], } } const holes: DraftHole[] = seed.templateHoles?.length ? seed.templateHoles.map((h) => ({ par: h.par, si: h.stroke_index })) : Array.from({ length: 18 }, () => ({ par: 4, si: 0 })) const tees: DraftTee[] = seed.templateTees?.length ? seed.templateTees.map((t) => { teeCounter += 1 const men = t.ratings.find((r) => r.gender === "m") const women = t.ratings.find((r) => r.gender === "f") return { id: `draft-tee-${teeCounter}`, name: t.name, menEnabled: !!men, womenEnabled: !!women, men: men ? { cr: String(men.course_rating), slope: String(men.slope_rating), par: String(men.par) } : { cr: "", slope: "", par: "" }, women: women ? { cr: String(women.course_rating), slope: String(women.slope_rating), par: String(women.par) } : { cr: "", slope: "", par: "" }, } }) : [newTee()] return { name: `${seed.name} (kopi)`, holes, tees } } function ratingComplete(r: DraftRating) { return r.cr.trim() !== "" && r.slope.trim() !== "" && r.par.trim() !== "" } export function CreateCourseForm({ seed, onCancel, onCreate, }: { seed?: Course onCancel: () => void onCreate: (course: Course) => void }) { const initial = useMemo(() => seedToDraft(seed), [seed]) const [name, setName] = useState(initial.name) const [holes, setHoles] = useState(initial.holes) const [tees, setTees] = useState(initial.tees) const [submitting, setSubmitting] = useState(false) const [error, setError] = useState(null) const nameRef = useAutoFocus(true) const siValues = holes.map((h) => h.si) const siError = useMemo(() => { const filled = siValues.filter((v) => v >= 1 && v <= 18) const set = new Set(filled) const complete = set.size === 18 && filled.length === 18 return complete ? null : "Hver stroke-indeks (1–18) må brukes nøyaktig én gang." }, [siValues]) const teesValid = tees.every((t) => { if (!t.name.trim()) return false if (!t.menEnabled && !t.womenEnabled) return false if (t.menEnabled && !ratingComplete(t.men)) return false if (t.womenEnabled && !ratingComplete(t.women)) return false return true }) const canSubmit = name.trim() !== "" && !siError && tees.length >= 1 && teesValid && !submitting function setHole(i: number, patch: Partial) { setHoles((prev) => prev.map((h, idx) => (idx === i ? { ...h, ...patch } : h))) } function setTee(id: string, patch: Partial) { setTees((prev) => prev.map((t) => (t.id === id ? { ...t, ...patch } : t))) } function setTeeRating(id: string, gender: "men" | "women", patch: Partial) { setTees((prev) => prev.map((t) => (t.id === id ? { ...t, [gender]: { ...t[gender], ...patch } } : t))) } async function submit() { if (!canSubmit) return setSubmitting(true) setError(null) try { const toRatings = (t: DraftTee) => { const ratings: { gender: ApiGender; course_rating: number; slope_rating: number; par: number }[] = [] if (t.menEnabled) ratings.push({ gender: "m", course_rating: Number.parseFloat(t.men.cr), slope_rating: Number.parseInt(t.men.slope, 10), par: Number.parseInt(t.men.par, 10) }) if (t.womenEnabled) ratings.push({ gender: "f", course_rating: Number.parseFloat(t.women.cr), slope_rating: Number.parseInt(t.women.slope, 10), par: Number.parseInt(t.women.par, 10) }) return ratings } // Kun personlige baner (ikke offisielle teeoff-baner) kan "forkes" -- // seed.id er et syntetisk teeoff-N-id for offisielle baner, en ekte // UUID for personlige (se courseFromOfficial/fetchOwnCourse i api.ts). const forkedFromId = seed && !seed.id.startsWith("teeoff-") ? seed.id : null const course = await createOwnCourse({ name: name.trim(), holes: holes.map((h, i) => ({ hole_number: i + 1, par: h.par, stroke_index: h.si })), tees: tees.map((t) => ({ name: t.name.trim(), ratings: toRatings(t) })), forkedFromId, }) onCreate(course) } catch { setError("Klarte ikke å opprette banen. Sjekk at hull og utslag er fylt ut riktig.") setSubmitting(false) } } return (
setName(e.target.value)} placeholder="F.eks. Skogsbanen" maxLength={120} /> {error ? (

{error}

) : null} {/* Hull */}

Hull

Par og stroke-indeks for 18 hull
Hull
Par
Stroke-indeks
{holes.map((h, i) => ( setHole(i, patch)} /> ))}
{siError ? (

{siError}

) : (

Alle stroke-indekser 1–18 er satt.

)}
{/* Utslag */}

Utslag

{tees.map((t, i) => (
setTee(t.id, { name: e.target.value })} placeholder={`Utslag ${i + 1}`} invalid={t.name.trim() === ""} className="bg-[var(--nr-surface)]" />
setTee(t.id, { menEnabled: v })} rating={t.men} onChange={(patch) => setTeeRating(t.id, "men", patch)} idPrefix={`${t.id}-men`} /> setTee(t.id, { womenEnabled: v })} rating={t.women} onChange={(patch) => setTeeRating(t.id, "women", patch)} idPrefix={`${t.id}-women`} /> {!t.menEnabled && !t.womenEnabled ? (

Minst én rating (herre eller dame) må være aktiv for dette utslaget.

) : null}
))}
) } function HoleRow({ index, hole, onChange, }: { index: number hole: DraftHole onChange: (patch: Partial) => void }) { return ( <>
{index + 1}
onChange({ par: Number.parseInt(e.target.value, 10) })} className="h-9 bg-[var(--nr-surface-2)]" > {[3, 4, 5, 6].map((p) => ( ))}
onChange({ si: Number.parseInt(e.target.value, 10) })} className="h-9 bg-[var(--nr-surface-2)]" > {Array.from({ length: 18 }, (_, i) => i + 1).map((si) => ( ))}
) } function RatingBlock({ title, enabled, onToggle, rating, onChange, idPrefix, }: { title: string enabled: boolean onToggle: (v: boolean) => void rating: DraftRating onChange: (patch: Partial) => void idPrefix: string }) { return (
{enabled ? (
onChange({ cr: e.target.value })} placeholder="72.1" invalid={rating.cr.trim() === ""} className="bg-[var(--nr-surface-2)]" /> onChange({ slope: e.target.value.replace(/[^\d]/g, "") })} placeholder="128" invalid={rating.slope.trim() === ""} className="bg-[var(--nr-surface-2)]" /> onChange({ par: e.target.value.replace(/[^\d]/g, "") })} placeholder="72" invalid={rating.par.trim() === ""} className="bg-[var(--nr-surface-2)]" />
) : null}
) }