"use client"; import { useRef, useState, type ChangeEvent } from 'react'; import { useRouter } from 'next/navigation'; import Link from 'next/link'; import { adminFetch } from "@/config/adminFetch"; import AdminMobileMenu from "@/components/AdminMobileMenu"; // KOMPONENT 1: MultiSelect for samarbeidende klubber const MultiSelect = ({ label, options, selected, onChange }: { label: string, options: any[], selected: string[], onChange: (s: string[]) => void }) => { const toggle = (val: string) => { if (selected.includes(val)) onChange(selected.filter(x => x !== val)); else onChange([...selected, val]); }; return (
{options.map(opt => ( ))}
); }; // KOMPONENT 2: Viser flate JSON-objekter (som fasiliteter) som rader med Nøkkel og Verdi const KeyValueEditor = ({ label, value, onChange }: { label: string, value: any, onChange: (v: any) => void }) => { const entries = Object.entries(value || {}); const updateKey = (oldKey: string, newKey: string, val: any) => { const newObj: any = {}; for (const [k, v] of entries) { if (k === oldKey) { if (newKey.trim()) newObj[newKey] = val; } else { newObj[k] = v; } } onChange(newObj); }; const updateVal = (key: string, val: string) => { onChange({ ...value, [key]: val }); }; const removeKey = (key: string) => { const newObj = { ...value }; delete newObj[key]; onChange(newObj); }; const addRow = () => { const tempKey = `ny_rad_${Date.now()}`; onChange({ ...value, [tempKey]: "" }); }; return (
{entries.map(([k, v]) => (
updateKey(k, e.target.value, v)} /> updateVal(k, e.target.value)} />
))}
); }; // KOMPONENT 3: Viser Arrays med objekter (som Greenfee-lister) som små pene kort const ListObjectEditor = ({ label, value, templateKeys, onChange }: { label: string, value: any[], templateKeys: string[], onChange: (v: any[]) => void }) => { const items = Array.isArray(value) ? value : []; const updateField = (index: number, key: string, val: string | number) => { const newItems = [...items]; const parsedVal = (!isNaN(Number(val)) && val !== "") ? Number(val) : val; newItems[index] = { ...newItems[index], [key]: parsedVal }; onChange(newItems); }; const addRow = () => { const newItem: any = {}; templateKeys.forEach(k => newItem[k] = ""); onChange([...items, newItem]); }; const removeRow = (index: number) => { const newItems = items.filter((_, i) => i !== index); onChange(newItems); }; return (
{items.map((item, idx) => (
{templateKeys.map(key => (
updateField(idx, key, e.target.value)} />
))}
))}
); }; // KOMPONENT 4: DEN NYE SCOREKORT-BYGGEREN const ScorecardBuilder = ({ course, onChange }: { course: any, onChange: (c: any) => void }) => { const ALL_KEYS = ['lengst', 'lang', 'mellomlang', 'mellomkort', 'kort', 'kortest']; const [holes, setHoles] = useState(() => { const h = course.holes || []; if (h.length === 0) { return Array.from({length: 18}, (_, i) => ({ hole_number: i+1, par: '', hcp_index: '', lengths: {} })); } return h.sort((a: any, b: any) => a.hole_number - b.hole_number); }); const [activeKeys, setActiveKeys] = useState(() => { const keys = new Set(); holes.forEach(h => { if (h.lengths) Object.keys(h.lengths).forEach(k => keys.add(k)); }); return ALL_KEYS.filter(k => keys.has(k)); }); const [tees, setTees] = useState(() => { const herrer = course.tee_boxes?.herrer || []; const damer = course.tee_boxes?.damer || []; const initialTees = { herrer: {} as any, damer: {} as any }; activeKeys.forEach((key, idx) => { initialTees.herrer[key] = herrer[idx] || { navn_utslag: '', baneverdi: '', slopeverdi: '' }; initialTees.damer[key] = damer[idx] || { navn_utslag_damer: '', baneverdi_damer: '', slopeverdi_damer: '' }; }); return initialTees; }); const syncToParent = (newHoles: any[], newKeys: string[], newTees: any) => { const updatedTeeBoxes = { herrer: newKeys.map(k => newTees.herrer[k] || {}), damer: newKeys.map(k => newTees.damer[k] || {}) }; onChange({ ...course, holes: newHoles, tee_boxes: updatedTeeBoxes }); }; const toggleKey = (key: string) => { const newKeys = activeKeys.includes(key) ? activeKeys.filter(k => k !== key) : ALL_KEYS.filter(k => activeKeys.includes(k) || k === key); setActiveKeys(newKeys); const newTees = { ...tees }; if (!newTees.herrer[key]) newTees.herrer[key] = { navn_utslag: '', baneverdi: '', slopeverdi: '' }; if (!newTees.damer[key]) newTees.damer[key] = { navn_utslag_damer: '', baneverdi_damer: '', slopeverdi_damer: '' }; setTees(newTees); syncToParent(holes, newKeys, newTees); }; const updateTee = (gender: 'herrer'|'damer', key: string, field: string, value: string) => { const newTees = { ...tees }; newTees[gender][key] = { ...newTees[gender][key], [field]: value }; setTees(newTees); syncToParent(holes, activeKeys, newTees); }; const updateHole = (index: number, field: string, value: string, lengthKey: string | null = null) => { const newHoles = [...holes]; if (lengthKey) { newHoles[index].lengths = { ...newHoles[index].lengths, [lengthKey]: value === '' ? '' : Number(value) }; } else { newHoles[index][field] = value === '' ? '' : Number(value); } setHoles(newHoles); syncToParent(newHoles, activeKeys, tees); }; const addHole = () => { const newHoles = [...holes, { hole_number: holes.length + 1, par: '', hcp_index: '', lengths: {} }]; setHoles(newHoles); syncToParent(newHoles, activeKeys, tees); }; const removeLastHole = () => { const newHoles = holes.slice(0, -1); setHoles(newHoles); syncToParent(newHoles, activeKeys, tees); }; return (
Aktive Utslagskolonner: {ALL_KEYS.map(k => ( ))}

Herrer

{activeKeys.map(k => (

{k}

updateTee('herrer', k, 'navn_utslag', e.target.value)} /> updateTee('herrer', k, 'baneverdi', e.target.value)} /> updateTee('herrer', k, 'slopeverdi', e.target.value)} />
))}

Damer

{activeKeys.map(k => (

{k}

updateTee('damer', k, 'navn_utslag_damer', e.target.value)} /> updateTee('damer', k, 'baneverdi_damer', e.target.value)} /> updateTee('damer', k, 'slopeverdi_damer', e.target.value)} />
))}

Hull for hull

Hvert hull er et eget kort med par, hcp og lengder per aktiv utslagskolonne.

{holes.length} hull
{holes.map((h, idx) => (

Hull {h.hole_number}

{activeKeys.length} utslag
updateHole(idx, 'par', e.target.value)} />
updateHole(idx, 'hcp_index', e.target.value)} />
{activeKeys.map(k => (
updateHole(idx, 'lengths', e.target.value, k)} />
))}
))}
{activeKeys.map(k => )} {/* Herrer */} {activeKeys.map(k => ( ))} {/* Damer */} {activeKeys.map(k => ( ))} {holes.map((h, idx) => ( {activeKeys.map(k => ( ))} ))}
Hull Par HCP{k}
Herrer (Navn / CR / Slope)
updateTee('herrer', k, 'navn_utslag', e.target.value)} />
updateTee('herrer', k, 'baneverdi', e.target.value)} /> updateTee('herrer', k, 'slopeverdi', e.target.value)} />
Damer (Navn / CR / Slope)
updateTee('damer', k, 'navn_utslag_damer', e.target.value)} />
updateTee('damer', k, 'baneverdi_damer', e.target.value)} /> updateTee('damer', k, 'slopeverdi_damer', e.target.value)} />
{h.hole_number} updateHole(idx, 'par', e.target.value)} /> updateHole(idx, 'hcp_index', e.target.value)} /> updateHole(idx, 'lengths', e.target.value, k)} />
); }; const normalizeStringList = (value: any): string[] => { if (Array.isArray(value)) { return Array.from(new Set(value.map((entry) => String(entry || "").trim()).filter(Boolean))); } if (typeof value === 'string') { try { return normalizeStringList(JSON.parse(value)); } catch { return []; } } return []; }; const getMediaFieldLabel = (field: string) => { if (field === 'image_url') return 'hovedbildet'; if (field === 'logo_url') return 'logoen'; return 'bildet'; }; export default function EditFacilityClient({ initialData, allFacilities }: { initialData: any, allFacilities: any[] }) { const router = useRouter(); const [formData, setFormData] = useState(initialData); const [activeTab, setActiveTab] = useState('generelt'); const [saving, setSaving] = useState(false); const [mediaFeedback, setMediaFeedback] = useState(""); const [uploadingTarget, setUploadingTarget] = useState(null); const mainImageInputRef = useRef(null); const logoImageInputRef = useRef(null); const galleryInputRef = useRef(null); // Trekk ut unike arkitekter fra alle anlegg const uniqueArchitects = Array.from(new Set(allFacilities.map(f => f.architect).filter(Boolean))).sort(); // Sørg for at cooperating_clubs er et array const [coopClubs, setCoopClubs] = useState( Array.isArray(initialData.cooperating_clubs) ? initialData.cooperating_clubs : (typeof initialData.cooperating_clubs === 'string' ? JSON.parse(initialData.cooperating_clubs) : []) ); const handleChange = (field: string, value: any) => { setFormData((prev: any) => ({ ...prev, [field]: value })); }; const galleryImages = normalizeStringList(formData.gallery); const setGalleryImages = (images: string[]) => { handleChange('gallery', Array.from(new Set(images.map((entry) => String(entry || "").trim()).filter(Boolean)))); }; const updateGalleryImage = (index: number, value: string) => { const nextGallery = [...galleryImages]; nextGallery[index] = value; setGalleryImages(nextGallery); }; const removeGalleryImage = (index: number) => { setGalleryImages(galleryImages.filter((_, currentIndex) => currentIndex !== index)); }; const moveGalleryImage = (index: number, direction: -1 | 1) => { const nextIndex = index + direction; if (nextIndex < 0 || nextIndex >= galleryImages.length) return; const nextGallery = [...galleryImages]; const [item] = nextGallery.splice(index, 1); nextGallery.splice(nextIndex, 0, item); setGalleryImages(nextGallery); }; const uploadFacilityImage = async (file: File) => { const payload = new FormData(); payload.append("file", file); payload.append("folder", "facilities"); const response = await adminFetch("/api/admin/uploads/images", { method: "POST", body: payload, credentials: "include", }); if (!response.ok) { const error = await response .json() .catch(() => ({ detail: "Kunne ikke laste opp bildet." })); throw new Error(error.detail || "Kunne ikke laste opp bildet."); } const result = (await response.json()) as { url?: string }; if (!result.url) { throw new Error("Uploaden returnerte ingen bildeadresse."); } return result.url; }; const handleSingleImageUpload = async (field: 'image_url' | 'logo_url', event: ChangeEvent) => { const file = event.target.files?.[0]; event.target.value = ""; if (!file) return; setUploadingTarget(field); setMediaFeedback(""); try { const url = await uploadFacilityImage(file); handleChange(field, url); setMediaFeedback(`Lastet opp ${getMediaFieldLabel(field)}.`); } catch (error) { setMediaFeedback(error instanceof Error ? error.message : "Kunne ikke laste opp bildet."); } finally { setUploadingTarget(null); } }; const handleGalleryUpload = async (event: ChangeEvent) => { const files = Array.from(event.target.files || []); event.target.value = ""; if (files.length === 0) return; setUploadingTarget('gallery'); setMediaFeedback(""); try { const uploadedUrls = await Promise.all(files.map((file) => uploadFacilityImage(file))); setGalleryImages([...galleryImages, ...uploadedUrls]); setMediaFeedback(`Lastet opp ${uploadedUrls.length} galleribilde${uploadedUrls.length === 1 ? "" : "r"}.`); } catch (error) { setMediaFeedback(error instanceof Error ? error.message : "Kunne ikke laste opp galleribilder."); } finally { setUploadingTarget(null); } }; const handleSave = async () => { setSaving(true); try { const res = await adminFetch(`/api/admin/facilities/${initialData.id}/full`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(formData) }); if (res.ok) { alert("Lagret suksessfullt!"); router.refresh(); } else { alert("Noe gikk galt under lagring."); } } catch (e) { alert("Nettverksfeil."); } setSaving(false); }; const tabs = [ { id: 'generelt', label: 'Generelt' }, { id: 'lokasjon', label: 'Lokasjon & Kontakt' }, { id: 'linker', label: 'Lenker & Media' }, { id: 'okonomi', label: 'Økonomi, medlemskap og fasiliteter' }, { id: 'baner', label: 'Baner & Scorekort' } ]; // Hjelpefunksjon for å hente ut verdi (spesielt formatert for dato) const getValue = (field: string, type: string) => { let val = formData[field] || ""; if (type === 'date' && val) { val = val.split('T')[0]; } return val; }; return (
← Tilbake til oversikten

Rediger:{" "} {initialData.name}

{/* SIDEBAR MENY */}
{tabs.map(tab => ( ))}
{/* SKJEMA OMRÅDE */}
{activeTab === 'generelt' && (
handleChange('name', e.target.value)} />