2026-03-07 09:46:33 +01:00
|
|
|
"use client";
|
|
|
|
|
import { useState } from 'react';
|
|
|
|
|
import { useRouter } from 'next/navigation';
|
|
|
|
|
import Link from 'next/link';
|
2026-04-11 09:54:54 +02:00
|
|
|
import { adminFetch } from "@/config/adminFetch";
|
|
|
|
|
import AdminMobileMenu from "@/components/AdminMobileMenu";
|
2026-03-07 09:46:33 +01:00
|
|
|
|
2026-03-08 10:26:56 +01:00
|
|
|
// 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 (
|
|
|
|
|
<div className="flex flex-col gap-2 mb-8 col-span-1 md:col-span-2">
|
|
|
|
|
<label className="text-xs font-black uppercase tracking-widest text-gray-600">{label}</label>
|
|
|
|
|
<div className="p-4 rounded-2xl border-2 border-gray-300 bg-white shadow-sm max-h-64 overflow-y-auto grid grid-cols-1 md:grid-cols-2 gap-2">
|
|
|
|
|
{options.map(opt => (
|
|
|
|
|
<label key={opt.slug} className="flex items-center gap-3 p-2 hover:bg-gray-50 rounded-lg cursor-pointer border border-transparent hover:border-gray-200 transition-all">
|
|
|
|
|
<input type="checkbox" checked={selected.includes(opt.slug)} onChange={() => toggle(opt.slug)} className="w-5 h-5 accent-[#8bc34a]" />
|
|
|
|
|
<span className="text-sm font-bold text-gray-700">{opt.name}</span>
|
|
|
|
|
</label>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// KOMPONENT 2: Viser flate JSON-objekter (som fasiliteter) som rader med Nøkkel og Verdi
|
2026-03-07 09:46:33 +01:00
|
|
|
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 (
|
2026-03-08 10:26:56 +01:00
|
|
|
<div className="flex flex-col gap-4 mb-8 bg-gray-100 p-6 md:p-8 rounded-[2rem] border border-gray-200 shadow-sm">
|
|
|
|
|
<label className="text-sm font-black uppercase tracking-widest text-[#11280f]">{label}</label>
|
|
|
|
|
<div className="space-y-3">
|
2026-03-07 09:46:33 +01:00
|
|
|
{entries.map(([k, v]) => (
|
2026-04-11 09:54:54 +02:00
|
|
|
<div key={k} className="grid gap-3 md:grid-cols-[minmax(0,0.35fr)_minmax(0,1fr)_auto] md:items-center">
|
2026-03-07 09:46:33 +01:00
|
|
|
<input
|
2026-04-11 09:54:54 +02:00
|
|
|
className="w-full p-4 rounded-xl border-2 border-gray-300 text-sm font-bold text-black bg-white focus:border-[#8bc34a] outline-none shadow-sm"
|
2026-03-07 09:46:33 +01:00
|
|
|
placeholder="Nøkkel (f.eks proshop)"
|
|
|
|
|
defaultValue={k.startsWith('ny_rad_') ? '' : k}
|
|
|
|
|
onBlur={e => updateKey(k, e.target.value, v)}
|
|
|
|
|
/>
|
|
|
|
|
<input
|
2026-03-08 10:26:56 +01:00
|
|
|
className="w-full p-4 rounded-xl border-2 border-gray-300 text-base font-medium text-black bg-white focus:border-[#8bc34a] outline-none shadow-sm"
|
2026-03-07 09:46:33 +01:00
|
|
|
placeholder="Verdi (f.eks Ja, eller et navn)"
|
|
|
|
|
value={String(v)}
|
|
|
|
|
onChange={e => updateVal(k, e.target.value)}
|
|
|
|
|
/>
|
2026-04-11 09:54:54 +02:00
|
|
|
<button onClick={() => removeKey(k)} className="w-full rounded-xl border border-red-200 bg-red-100 p-4 text-left text-lg font-black text-red-700 transition-colors hover:bg-red-200 hover:text-red-900 md:w-auto md:text-center">✕</button>
|
2026-03-07 09:46:33 +01:00
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
2026-03-08 10:26:56 +01:00
|
|
|
<button onClick={addRow} className="mt-2 text-left text-sm font-black text-[#8bc34a] hover:text-[#11280f] transition-colors bg-white px-6 py-3 rounded-xl border-2 border-[#8bc34a] self-start">+ Legg til ny rad</button>
|
2026-03-07 09:46:33 +01:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2026-03-08 10:26:56 +01:00
|
|
|
// KOMPONENT 3: Viser Arrays med objekter (som Greenfee-lister) som små pene kort
|
2026-03-07 09:46:33 +01:00
|
|
|
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 (
|
2026-03-08 10:26:56 +01:00
|
|
|
<div className="flex flex-col gap-4 mb-8 bg-gray-100 p-6 md:p-8 rounded-[2rem] border border-gray-200 shadow-sm">
|
|
|
|
|
<label className="text-sm font-black uppercase tracking-widest text-[#11280f]">{label}</label>
|
|
|
|
|
<div className="space-y-6">
|
2026-03-07 09:46:33 +01:00
|
|
|
{items.map((item, idx) => (
|
2026-03-08 10:26:56 +01:00
|
|
|
<div key={idx} className="flex flex-col bg-white p-6 rounded-2xl border-2 border-gray-300 shadow-sm relative group hover:border-[#8bc34a] transition-colors">
|
2026-04-10 09:52:34 +02:00
|
|
|
<button onClick={() => removeRow(idx)} className="absolute top-4 right-4 w-8 h-8 flex items-center justify-center bg-red-100 text-red-700 hover:bg-red-200 hover:text-red-900 rounded-full text-sm font-black transition-colors border border-red-200 z-10">✕</button>
|
2026-03-08 10:26:56 +01:00
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 pr-10">
|
2026-03-07 09:46:33 +01:00
|
|
|
{templateKeys.map(key => (
|
2026-03-08 10:26:56 +01:00
|
|
|
<div key={key} className="flex flex-col gap-2">
|
|
|
|
|
<label className="text-xs uppercase font-black text-gray-600 tracking-wider">{key.replace(/_/g, ' ')}</label>
|
2026-03-07 09:46:33 +01:00
|
|
|
<input
|
2026-03-08 10:26:56 +01:00
|
|
|
className="p-3 rounded-lg border-2 border-gray-300 text-base font-bold text-black bg-gray-50 focus:bg-white focus:border-[#8bc34a] outline-none transition-colors"
|
2026-03-07 09:46:33 +01:00
|
|
|
value={item[key] || ""}
|
|
|
|
|
onChange={e => updateField(idx, key, e.target.value)}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
2026-03-08 10:26:56 +01:00
|
|
|
<button onClick={addRow} className="mt-2 text-left text-sm font-black text-[#8bc34a] hover:text-[#11280f] transition-colors bg-white px-6 py-3 rounded-xl border-2 border-[#8bc34a] self-start">+ Legg til nytt element</button>
|
2026-03-07 09:46:33 +01:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2026-03-08 10:26:56 +01:00
|
|
|
// 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<any[]>(() => {
|
|
|
|
|
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<string[]>(() => {
|
|
|
|
|
const keys = new Set<string>();
|
|
|
|
|
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<any>(() => {
|
|
|
|
|
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);
|
2026-03-07 09:46:33 +01:00
|
|
|
|
2026-03-08 10:26:56 +01:00
|
|
|
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 (
|
|
|
|
|
<div className="flex flex-col gap-4 mt-6">
|
|
|
|
|
<div className="flex flex-wrap gap-4 items-center bg-gray-100 p-4 rounded-xl border-2 border-gray-200">
|
|
|
|
|
<span className="text-xs font-black uppercase tracking-widest text-gray-600">Aktive Utslagskolonner:</span>
|
|
|
|
|
{ALL_KEYS.map(k => (
|
|
|
|
|
<label key={k} className="flex items-center gap-2 text-sm font-bold cursor-pointer text-black">
|
|
|
|
|
<input
|
|
|
|
|
type="checkbox"
|
|
|
|
|
checked={activeKeys.includes(k)}
|
|
|
|
|
onChange={() => toggleKey(k)}
|
|
|
|
|
className="w-5 h-5 accent-[#8bc34a]"
|
|
|
|
|
/>
|
|
|
|
|
{k.toUpperCase()}
|
|
|
|
|
</label>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-04-11 09:54:54 +02:00
|
|
|
<div className="grid gap-6 lg:grid-cols-2">
|
|
|
|
|
<section className="rounded-[1.75rem] border-2 border-blue-100 bg-blue-50/60 p-5 shadow-sm">
|
|
|
|
|
<p className="text-xs font-black uppercase tracking-widest text-blue-900">Herrer</p>
|
|
|
|
|
<div className="mt-4 space-y-3">
|
|
|
|
|
{activeKeys.map(k => (
|
|
|
|
|
<div key={k} className="rounded-2xl bg-white p-4 shadow-sm">
|
|
|
|
|
<p className="text-[10px] font-black uppercase tracking-[0.18em] text-gray-400">{k}</p>
|
|
|
|
|
<div className="mt-3 grid gap-2 sm:grid-cols-3">
|
|
|
|
|
<input placeholder="Eks: Gul" className="w-full rounded-xl border border-blue-200 bg-white p-3 text-sm font-bold text-black outline-none focus:border-blue-500" value={tees.herrer[k]?.navn_utslag || ''} onChange={e => updateTee('herrer', k, 'navn_utslag', e.target.value)} />
|
|
|
|
|
<input placeholder="CR" className="w-full rounded-xl border border-blue-200 bg-white p-3 text-sm text-center text-black outline-none focus:border-blue-500" value={tees.herrer[k]?.baneverdi || ''} onChange={e => updateTee('herrer', k, 'baneverdi', e.target.value)} />
|
|
|
|
|
<input placeholder="Slope" className="w-full rounded-xl border border-blue-200 bg-white p-3 text-sm text-center text-black outline-none focus:border-blue-500" value={tees.herrer[k]?.slopeverdi || ''} onChange={e => updateTee('herrer', k, 'slopeverdi', e.target.value)} />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<section className="rounded-[1.75rem] border-2 border-red-100 bg-red-50/60 p-5 shadow-sm">
|
|
|
|
|
<p className="text-xs font-black uppercase tracking-widest text-red-900">Damer</p>
|
|
|
|
|
<div className="mt-4 space-y-3">
|
|
|
|
|
{activeKeys.map(k => (
|
|
|
|
|
<div key={k} className="rounded-2xl bg-white p-4 shadow-sm">
|
|
|
|
|
<p className="text-[10px] font-black uppercase tracking-[0.18em] text-gray-400">{k}</p>
|
|
|
|
|
<div className="mt-3 grid gap-2 sm:grid-cols-3">
|
|
|
|
|
<input placeholder="Eks: Rod" className="w-full rounded-xl border border-red-200 bg-white p-3 text-sm font-bold text-black outline-none focus:border-red-500" value={tees.damer[k]?.navn_utslag_damer || ''} onChange={e => updateTee('damer', k, 'navn_utslag_damer', e.target.value)} />
|
|
|
|
|
<input placeholder="CR" className="w-full rounded-xl border border-red-200 bg-white p-3 text-sm text-center text-black outline-none focus:border-red-500" value={tees.damer[k]?.baneverdi_damer || ''} onChange={e => updateTee('damer', k, 'baneverdi_damer', e.target.value)} />
|
|
|
|
|
<input placeholder="Slope" className="w-full rounded-xl border border-red-200 bg-white p-3 text-sm text-center text-black outline-none focus:border-red-500" value={tees.damer[k]?.slopeverdi_damer || ''} onChange={e => updateTee('damer', k, 'slopeverdi_damer', e.target.value)} />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<section className="rounded-[1.75rem] border-2 border-gray-200 bg-white p-5 shadow-sm">
|
|
|
|
|
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
|
|
|
|
<div>
|
|
|
|
|
<p className="text-xs font-black uppercase tracking-widest text-gray-400">Hull for hull</p>
|
|
|
|
|
<p className="mt-1 text-sm text-gray-500">Hvert hull er et eget kort med par, hcp og lengder per aktiv utslagskolonne.</p>
|
|
|
|
|
</div>
|
|
|
|
|
<span className="rounded-xl bg-[#f1f7ed] px-3 py-2 text-[10px] font-black uppercase tracking-widest text-[#11280f]">{holes.length} hull</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="mt-5 grid gap-4 lg:grid-cols-2">
|
|
|
|
|
{holes.map((h, idx) => (
|
|
|
|
|
<div key={idx} className="rounded-[1.5rem] border border-gray-200 bg-[#fbfdf8] p-4 shadow-sm">
|
|
|
|
|
<div className="flex items-center justify-between gap-3">
|
|
|
|
|
<p className="text-lg font-black text-[#11280f]">Hull {h.hole_number}</p>
|
|
|
|
|
<span className="rounded-xl bg-white px-3 py-1 text-[10px] font-black uppercase tracking-widest text-gray-400 shadow-sm">{activeKeys.length} utslag</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="mt-4 grid gap-3 sm:grid-cols-2">
|
|
|
|
|
<div>
|
|
|
|
|
<label className="text-[10px] font-black uppercase tracking-[0.18em] text-gray-400">Par</label>
|
|
|
|
|
<input type="number" className="mt-2 w-full rounded-xl border-2 border-gray-200 bg-white p-3 text-center font-bold text-black outline-none focus:border-[#8bc34a]" value={h.par || ''} onChange={e => updateHole(idx, 'par', e.target.value)} />
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label className="text-[10px] font-black uppercase tracking-[0.18em] text-gray-400">HCP</label>
|
|
|
|
|
<input type="number" className="mt-2 w-full rounded-xl border-2 border-gray-200 bg-white p-3 text-center font-bold text-black outline-none focus:border-[#8bc34a]" value={h.hcp_index || ''} onChange={e => updateHole(idx, 'hcp_index', e.target.value)} />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="mt-4 grid gap-3 sm:grid-cols-2 xl:grid-cols-3">
|
|
|
|
|
{activeKeys.map(k => (
|
|
|
|
|
<div key={k} className="rounded-2xl bg-white p-3 shadow-sm">
|
|
|
|
|
<label className="text-[10px] font-black uppercase tracking-[0.18em] text-gray-400">{k}</label>
|
|
|
|
|
<input type="number" placeholder="Lengde" className="mt-2 w-full rounded-xl border-2 border-gray-200 bg-white p-3 text-center font-mono font-bold text-black outline-none focus:border-[#8bc34a]" value={h.lengths?.[k] || ''} onChange={e => updateHole(idx, 'lengths', e.target.value, k)} />
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<div className="hidden overflow-x-auto rounded-2xl border-2 border-gray-300 shadow-sm bg-white pb-2">
|
2026-03-08 10:26:56 +01:00
|
|
|
<table className="w-full text-center text-sm min-w-[800px] border-collapse">
|
|
|
|
|
<thead>
|
|
|
|
|
<tr className="bg-gray-100 text-gray-700 text-xs font-black uppercase tracking-widest border-b-2 border-gray-300">
|
|
|
|
|
<th className="p-3 border-r border-gray-200">Hull</th>
|
|
|
|
|
<th className="p-3 border-r border-gray-200">Par</th>
|
|
|
|
|
<th className="p-3 border-r border-gray-300">HCP</th>
|
|
|
|
|
{activeKeys.map(k => <th key={k} className="p-3 border-r border-gray-300 w-32">{k}</th>)}
|
|
|
|
|
</tr>
|
|
|
|
|
{/* Herrer */}
|
|
|
|
|
<tr className="bg-blue-50 border-b border-gray-300">
|
|
|
|
|
<th colSpan={3} className="p-3 text-right text-[10px] font-black text-blue-900 uppercase tracking-widest border-r border-gray-300">
|
|
|
|
|
Herrer (Navn / CR / Slope)
|
|
|
|
|
</th>
|
|
|
|
|
{activeKeys.map(k => (
|
|
|
|
|
<td key={k} className="p-2 border-r border-gray-300 align-top">
|
|
|
|
|
<div className="flex flex-col gap-1">
|
|
|
|
|
<input placeholder="Eks: Gul" className="w-full p-2 text-xs font-bold text-center border border-blue-200 rounded outline-none focus:border-blue-500 bg-white text-black" value={tees.herrer[k]?.navn_utslag || ''} onChange={e => updateTee('herrer', k, 'navn_utslag', e.target.value)} />
|
|
|
|
|
<div className="flex gap-1">
|
|
|
|
|
<input placeholder="CR" className="w-1/2 p-2 text-xs text-center border border-blue-200 rounded outline-none focus:border-blue-500 bg-white text-black" value={tees.herrer[k]?.baneverdi || ''} onChange={e => updateTee('herrer', k, 'baneverdi', e.target.value)} />
|
|
|
|
|
<input placeholder="Slope" className="w-1/2 p-2 text-xs text-center border border-blue-200 rounded outline-none focus:border-blue-500 bg-white text-black" value={tees.herrer[k]?.slopeverdi || ''} onChange={e => updateTee('herrer', k, 'slopeverdi', e.target.value)} />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</td>
|
|
|
|
|
))}
|
|
|
|
|
</tr>
|
|
|
|
|
{/* Damer */}
|
|
|
|
|
<tr className="bg-red-50 border-b-4 border-gray-400">
|
|
|
|
|
<th colSpan={3} className="p-3 text-right text-[10px] font-black text-red-900 uppercase tracking-widest border-r border-gray-300">
|
|
|
|
|
Damer (Navn / CR / Slope)
|
|
|
|
|
</th>
|
|
|
|
|
{activeKeys.map(k => (
|
|
|
|
|
<td key={k} className="p-2 border-r border-gray-300 align-top">
|
|
|
|
|
<div className="flex flex-col gap-1">
|
|
|
|
|
<input placeholder="Eks: Rød" className="w-full p-2 text-xs font-bold text-center border border-red-200 rounded outline-none focus:border-red-500 bg-white text-black" value={tees.damer[k]?.navn_utslag_damer || ''} onChange={e => updateTee('damer', k, 'navn_utslag_damer', e.target.value)} />
|
|
|
|
|
<div className="flex gap-1">
|
|
|
|
|
<input placeholder="CR" className="w-1/2 p-2 text-xs text-center border border-red-200 rounded outline-none focus:border-red-500 bg-white text-black" value={tees.damer[k]?.baneverdi_damer || ''} onChange={e => updateTee('damer', k, 'baneverdi_damer', e.target.value)} />
|
|
|
|
|
<input placeholder="Slope" className="w-1/2 p-2 text-xs text-center border border-red-200 rounded outline-none focus:border-red-500 bg-white text-black" value={tees.damer[k]?.slopeverdi_damer || ''} onChange={e => updateTee('damer', k, 'slopeverdi_damer', e.target.value)} />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</td>
|
|
|
|
|
))}
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody>
|
|
|
|
|
{holes.map((h, idx) => (
|
|
|
|
|
<tr key={idx} className="border-b border-gray-200 hover:bg-gray-50">
|
|
|
|
|
<td className="p-2 font-black text-lg text-gray-800 border-r border-gray-200">{h.hole_number}</td>
|
|
|
|
|
<td className="p-2 border-r border-gray-200"><input type="number" className="w-full p-3 text-center border-2 border-gray-200 rounded-xl font-bold text-black outline-none focus:border-[#8bc34a] bg-white" value={h.par || ''} onChange={e => updateHole(idx, 'par', e.target.value)} /></td>
|
|
|
|
|
<td className="p-2 border-r border-gray-300"><input type="number" className="w-full p-3 text-center border-2 border-gray-200 rounded-xl font-bold text-black outline-none focus:border-[#8bc34a] bg-white" value={h.hcp_index || ''} onChange={e => updateHole(idx, 'hcp_index', e.target.value)} /></td>
|
|
|
|
|
{activeKeys.map(k => (
|
|
|
|
|
<td key={k} className="p-2 border-r border-gray-300 bg-gray-50/50">
|
|
|
|
|
<input type="number" placeholder="Lengde" className="w-full p-3 text-center border-2 border-gray-200 rounded-xl font-mono font-bold text-black outline-none focus:border-[#8bc34a] bg-white" value={h.lengths?.[k] || ''} onChange={e => updateHole(idx, 'lengths', e.target.value, k)} />
|
|
|
|
|
</td>
|
|
|
|
|
))}
|
|
|
|
|
</tr>
|
|
|
|
|
))}
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-04-11 09:54:54 +02:00
|
|
|
<div className="flex flex-col gap-4 px-2 sm:flex-row">
|
|
|
|
|
<button onClick={addHole} className="text-sm font-black text-[#8bc34a] hover:text-[#11280f] px-4 py-3 border-2 border-[#8bc34a] rounded-xl">+ Legg til hull</button>
|
|
|
|
|
<button onClick={removeLastHole} className="text-sm font-black text-red-500 hover:text-red-700 px-4 py-3 border-2 border-red-500 rounded-xl">- Slett siste hull</button>
|
2026-03-08 10:26:56 +01:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export default function EditFacilityClient({ initialData, allFacilities }: { initialData: any, allFacilities: any[] }) {
|
2026-03-07 09:46:33 +01:00
|
|
|
const router = useRouter();
|
|
|
|
|
const [formData, setFormData] = useState(initialData);
|
|
|
|
|
const [activeTab, setActiveTab] = useState('generelt');
|
|
|
|
|
const [saving, setSaving] = useState(false);
|
|
|
|
|
|
2026-03-08 10:26:56 +01:00
|
|
|
// 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<string[]>(
|
|
|
|
|
Array.isArray(initialData.cooperating_clubs) ? initialData.cooperating_clubs :
|
|
|
|
|
(typeof initialData.cooperating_clubs === 'string' ? JSON.parse(initialData.cooperating_clubs) : [])
|
|
|
|
|
);
|
|
|
|
|
|
2026-03-07 09:46:33 +01:00
|
|
|
const handleChange = (field: string, value: any) => {
|
|
|
|
|
setFormData((prev: any) => ({ ...prev, [field]: value }));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleSave = async () => {
|
|
|
|
|
setSaving(true);
|
|
|
|
|
try {
|
2026-04-11 09:54:54 +02:00
|
|
|
const res = await adminFetch(`/api/admin/facilities/${initialData.id}/full`, {
|
2026-03-07 09:46:33 +01:00
|
|
|
method: 'PUT',
|
|
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
body: JSON.stringify(formData)
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (res.ok) {
|
2026-03-08 10:26:56 +01:00
|
|
|
alert("Lagret suksessfullt!");
|
2026-03-07 09:46:33 +01:00
|
|
|
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' },
|
2026-03-08 10:26:56 +01:00
|
|
|
{ id: 'baner', label: 'Baner & Scorekort' }
|
2026-03-07 09:46:33 +01:00
|
|
|
];
|
|
|
|
|
|
2026-03-12 13:39:10 +01:00
|
|
|
// 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];
|
2026-03-08 10:26:56 +01:00
|
|
|
}
|
2026-03-12 13:39:10 +01:00
|
|
|
return val;
|
2026-03-08 10:26:56 +01:00
|
|
|
};
|
2026-03-07 09:46:33 +01:00
|
|
|
|
|
|
|
|
return (
|
2026-03-08 10:26:56 +01:00
|
|
|
<div className="max-w-[1400px] mx-auto p-4 md:p-8 relative z-40 bg-white min-h-screen">
|
2026-04-11 09:54:54 +02:00
|
|
|
<AdminMobileMenu />
|
2026-03-08 10:26:56 +01:00
|
|
|
<div className="flex flex-col md:flex-row justify-between items-start md:items-center mb-10 pb-6 border-b border-gray-200 gap-6">
|
2026-03-07 09:46:33 +01:00
|
|
|
<div>
|
2026-03-08 10:26:56 +01:00
|
|
|
<Link href="/admin" className="text-sm font-bold text-gray-500 hover:text-[#8bc34a] mb-2 block">← Tilbake til oversikten</Link>
|
2026-03-07 09:46:33 +01:00
|
|
|
<h1 className="text-4xl font-black text-[#11280f]">Rediger: <span className="text-[#8bc34a]">{initialData.name}</span></h1>
|
|
|
|
|
</div>
|
|
|
|
|
<button
|
|
|
|
|
onClick={handleSave}
|
|
|
|
|
disabled={saving}
|
2026-03-08 10:26:56 +01:00
|
|
|
className="bg-[#11280f] text-white px-8 py-4 rounded-full font-black uppercase tracking-widest hover:bg-[#8bc34a] transition-colors shadow-xl disabled:opacity-50 w-full md:w-auto"
|
2026-03-07 09:46:33 +01:00
|
|
|
>
|
|
|
|
|
{saving ? "Lagrer..." : "Lagre endringer"}
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="flex flex-col md:flex-row gap-10">
|
2026-03-08 10:26:56 +01:00
|
|
|
{/* SIDEBAR MENY */}
|
|
|
|
|
<div className="w-full md:w-1/4 flex flex-col gap-3">
|
2026-03-07 09:46:33 +01:00
|
|
|
{tabs.map(tab => (
|
|
|
|
|
<button
|
|
|
|
|
key={tab.id}
|
|
|
|
|
onClick={() => setActiveTab(tab.id)}
|
2026-03-08 10:26:56 +01:00
|
|
|
className={`p-4 rounded-2xl text-left font-black uppercase text-sm tracking-widest transition-all ${activeTab === tab.id ? 'bg-[#8bc34a] text-white shadow-lg translate-x-2' : 'bg-gray-100 text-gray-600 hover:bg-gray-200'}`}
|
2026-03-07 09:46:33 +01:00
|
|
|
>
|
|
|
|
|
{tab.label}
|
|
|
|
|
</button>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-03-08 10:26:56 +01:00
|
|
|
{/* SKJEMA OMRÅDE */}
|
|
|
|
|
<div className="w-full md:w-3/4">
|
2026-03-07 09:46:33 +01:00
|
|
|
{activeTab === 'generelt' && (
|
2026-03-08 10:26:56 +01:00
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-8">
|
2026-03-12 13:39:10 +01:00
|
|
|
<div className="col-span-1 md:col-span-2 flex flex-col gap-2 mb-8">
|
|
|
|
|
<label className="text-xs font-black uppercase tracking-widest text-gray-600">Anleggsnavn</label>
|
|
|
|
|
<input className="p-4 rounded-2xl border-2 border-gray-300 bg-white text-black text-base font-bold shadow-sm focus:border-[#8bc34a] outline-none" value={getValue('name', 'text')} onChange={e => handleChange('name', e.target.value)} />
|
|
|
|
|
</div>
|
2026-03-08 10:26:56 +01:00
|
|
|
|
2026-03-12 13:39:10 +01:00
|
|
|
<div className="col-span-1 md:col-span-2 flex flex-col gap-2 mb-8">
|
|
|
|
|
<label className="text-xs font-black uppercase tracking-widest text-gray-600">Viktig beskjed (Kursiv intro-tekst)</label>
|
|
|
|
|
<textarea className="p-4 rounded-2xl border-2 border-gray-300 bg-white text-black text-base shadow-sm focus:border-[#8bc34a] outline-none" rows={4} value={getValue('footnote', 'textarea')} onChange={e => handleChange('footnote', e.target.value)} />
|
|
|
|
|
</div>
|
2026-03-08 10:26:56 +01:00
|
|
|
|
2026-03-12 13:39:10 +01:00
|
|
|
<div className="col-span-1 md:col-span-2 flex flex-col gap-2 mb-8">
|
|
|
|
|
<label className="text-xs font-black uppercase tracking-widest text-gray-600">Hovedbeskrivelse</label>
|
|
|
|
|
<textarea className="p-4 rounded-2xl border-2 border-gray-300 bg-white text-black text-base shadow-sm focus:border-[#8bc34a] outline-none" rows={4} value={getValue('description', 'textarea')} onChange={e => handleChange('description', e.target.value)} />
|
|
|
|
|
</div>
|
2026-03-08 10:26:56 +01:00
|
|
|
|
2026-03-12 13:39:10 +01:00
|
|
|
<div className="flex flex-col gap-2 mb-8">
|
|
|
|
|
<label className="text-xs font-black uppercase tracking-widest text-gray-600">Banetype (f.eks Park/Skog)</label>
|
|
|
|
|
<input className="p-4 rounded-2xl border-2 border-gray-300 bg-white text-black text-base font-bold shadow-sm focus:border-[#8bc34a] outline-none" value={getValue('banetype', 'text')} onChange={e => handleChange('banetype', e.target.value)} />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="flex flex-col gap-2 mb-8">
|
|
|
|
|
<label className="text-xs font-black uppercase tracking-widest text-gray-600">Sesong (f.eks April-Oktober)</label>
|
|
|
|
|
<input className="p-4 rounded-2xl border-2 border-gray-300 bg-white text-black text-base font-bold shadow-sm focus:border-[#8bc34a] outline-none" value={getValue('season', 'text')} onChange={e => handleChange('season', e.target.value)} />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="flex flex-col gap-2 mb-8">
|
|
|
|
|
<label className="text-xs font-black uppercase tracking-widest text-gray-600">Byggeår</label>
|
|
|
|
|
<input type="number" className="p-4 rounded-2xl border-2 border-gray-300 bg-white text-black text-base font-bold shadow-sm focus:border-[#8bc34a] outline-none" value={getValue('established_year', 'number')} onChange={e => handleChange('established_year', Number(e.target.value))} />
|
|
|
|
|
</div>
|
2026-03-08 10:26:56 +01:00
|
|
|
|
|
|
|
|
<div className="flex flex-col gap-2 mb-8">
|
|
|
|
|
<label className="text-xs font-black uppercase tracking-widest text-gray-600">Arkitekt</label>
|
|
|
|
|
<input
|
|
|
|
|
list="architect-list"
|
|
|
|
|
className="p-4 rounded-2xl border-2 border-gray-300 bg-white text-black text-base font-bold shadow-sm focus:border-[#8bc34a] outline-none transition-all"
|
2026-03-12 13:39:10 +01:00
|
|
|
value={getValue('architect', 'text')}
|
2026-03-08 10:26:56 +01:00
|
|
|
onChange={e => handleChange('architect', e.target.value)}
|
|
|
|
|
placeholder="Velg eller skriv inn ny..."
|
|
|
|
|
/>
|
|
|
|
|
<datalist id="architect-list">
|
|
|
|
|
<option value="Ukjent" />
|
|
|
|
|
{uniqueArchitects.map((arch: any) => <option key={arch} value={arch} />)}
|
|
|
|
|
</datalist>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-03-12 13:39:10 +01:00
|
|
|
<div className="flex flex-col gap-2 mb-8">
|
|
|
|
|
<label className="text-xs font-black uppercase tracking-widest text-gray-600">Totallengde (meter)</label>
|
|
|
|
|
<input type="number" className="p-4 rounded-2xl border-2 border-gray-300 bg-white text-black text-base font-bold shadow-sm focus:border-[#8bc34a] outline-none" value={getValue('length_meters', 'number')} onChange={e => handleChange('length_meters', Number(e.target.value))} />
|
|
|
|
|
</div>
|
2026-03-08 10:26:56 +01:00
|
|
|
|
|
|
|
|
<MultiSelect
|
|
|
|
|
label="Samarbeidende Klubber (Gjestespill etc.)"
|
|
|
|
|
options={allFacilities.filter(f => f.id !== initialData.id)}
|
|
|
|
|
selected={coopClubs}
|
|
|
|
|
onChange={(val) => {
|
|
|
|
|
setCoopClubs(val);
|
|
|
|
|
handleChange('cooperating_clubs', val);
|
|
|
|
|
}}
|
|
|
|
|
/>
|
2026-03-07 09:46:33 +01:00
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{activeTab === 'lokasjon' && (
|
2026-03-08 10:26:56 +01:00
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-8">
|
2026-03-12 13:39:10 +01:00
|
|
|
<div className="col-span-1 md:col-span-2 flex flex-col gap-2 mb-8">
|
|
|
|
|
<label className="text-xs font-black uppercase tracking-widest text-gray-600">Gateadresse</label>
|
|
|
|
|
<input className="p-4 rounded-2xl border-2 border-gray-300 bg-white text-black text-base font-bold shadow-sm focus:border-[#8bc34a] outline-none" value={getValue('address', 'text')} onChange={e => handleChange('address', e.target.value)} />
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex flex-col gap-2 mb-8"><label className="text-xs font-black uppercase tracking-widest text-gray-600">Postnummer</label><input className="p-4 rounded-2xl border-2 border-gray-300 bg-white text-black text-base font-bold shadow-sm focus:border-[#8bc34a] outline-none" value={getValue('zipcode', 'text')} onChange={e => handleChange('zipcode', e.target.value)} /></div>
|
|
|
|
|
<div className="flex flex-col gap-2 mb-8"><label className="text-xs font-black uppercase tracking-widest text-gray-600">Poststed / By</label><input className="p-4 rounded-2xl border-2 border-gray-300 bg-white text-black text-base font-bold shadow-sm focus:border-[#8bc34a] outline-none" value={getValue('city', 'text')} onChange={e => handleChange('city', e.target.value)} /></div>
|
|
|
|
|
<div className="flex flex-col gap-2 mb-8"><label className="text-xs font-black uppercase tracking-widest text-gray-600">Fylke</label><input className="p-4 rounded-2xl border-2 border-gray-300 bg-white text-black text-base font-bold shadow-sm focus:border-[#8bc34a] outline-none" value={getValue('county', 'text')} onChange={e => handleChange('county', e.target.value)} /></div>
|
|
|
|
|
<div className="flex flex-col gap-2 mb-8"><label className="text-xs font-black uppercase tracking-widest text-gray-600">Telefon</label><input className="p-4 rounded-2xl border-2 border-gray-300 bg-white text-black text-base font-bold shadow-sm focus:border-[#8bc34a] outline-none" value={getValue('phone', 'text')} onChange={e => handleChange('phone', e.target.value)} /></div>
|
|
|
|
|
<div className="flex flex-col gap-2 mb-8"><label className="text-xs font-black uppercase tracking-widest text-gray-600">E-post</label><input className="p-4 rounded-2xl border-2 border-gray-300 bg-white text-black text-base font-bold shadow-sm focus:border-[#8bc34a] outline-none" value={getValue('email', 'text')} onChange={e => handleChange('email', e.target.value)} /></div>
|
|
|
|
|
<div className="flex flex-col gap-2 mb-8"><label className="text-xs font-black uppercase tracking-widest text-gray-600">Breddegrad (Latitude)</label><input type="number" className="p-4 rounded-2xl border-2 border-gray-300 bg-white text-black text-base font-bold shadow-sm focus:border-[#8bc34a] outline-none" value={getValue('lat', 'number')} onChange={e => handleChange('lat', Number(e.target.value))} /></div>
|
|
|
|
|
<div className="flex flex-col gap-2 mb-8"><label className="text-xs font-black uppercase tracking-widest text-gray-600">Lengdegrad (Longitude)</label><input type="number" className="p-4 rounded-2xl border-2 border-gray-300 bg-white text-black text-base font-bold shadow-sm focus:border-[#8bc34a] outline-none" value={getValue('lng', 'number')} onChange={e => handleChange('lng', Number(e.target.value))} /></div>
|
2026-03-07 09:46:33 +01:00
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{activeTab === 'linker' && (
|
|
|
|
|
<div className="flex flex-col">
|
2026-03-12 13:39:10 +01:00
|
|
|
<div className="flex flex-col gap-2 mb-8"><label className="text-xs font-black uppercase tracking-widest text-gray-600">Nettside URL</label><input className="p-4 rounded-2xl border-2 border-gray-300 bg-white text-black text-base font-bold shadow-sm focus:border-[#8bc34a] outline-none" value={getValue('website_url', 'text')} onChange={e => handleChange('website_url', e.target.value)} /></div>
|
|
|
|
|
<div className="flex flex-col gap-2 mb-8"><label className="text-xs font-black uppercase tracking-widest text-gray-600">Golfbox Booking URL</label><input className="p-4 rounded-2xl border-2 border-gray-300 bg-white text-black text-base font-bold shadow-sm focus:border-[#8bc34a] outline-none" value={getValue('golfbox_booking_url', 'text')} onChange={e => handleChange('golfbox_booking_url', e.target.value)} /></div>
|
|
|
|
|
<div className="flex flex-col gap-2 mb-8"><label className="text-xs font-black uppercase tracking-widest text-gray-600">Golfbox Turnering URL</label><input className="p-4 rounded-2xl border-2 border-gray-300 bg-white text-black text-base font-bold shadow-sm focus:border-[#8bc34a] outline-none" value={getValue('golfbox_tournament_url', 'text')} onChange={e => handleChange('golfbox_tournament_url', e.target.value)} /></div>
|
|
|
|
|
<div className="flex flex-col gap-2 mb-8"><label className="text-xs font-black uppercase tracking-widest text-gray-600">Baneguide URL</label><input className="p-4 rounded-2xl border-2 border-gray-300 bg-white text-black text-base font-bold shadow-sm focus:border-[#8bc34a] outline-none" value={getValue('baneguide_url', 'text')} onChange={e => handleChange('baneguide_url', e.target.value)} /></div>
|
|
|
|
|
<div className="flex flex-col gap-2 mb-8"><label className="text-xs font-black uppercase tracking-widest text-gray-600">Flyfoto URL</label><input className="p-4 rounded-2xl border-2 border-gray-300 bg-white text-black text-base font-bold shadow-sm focus:border-[#8bc34a] outline-none" value={getValue('flyfoto_url', 'text')} onChange={e => handleChange('flyfoto_url', e.target.value)} /></div>
|
|
|
|
|
<div className="flex flex-col gap-2 mb-8"><label className="text-xs font-black uppercase tracking-widest text-gray-600">Vær URL (YR)</label><input className="p-4 rounded-2xl border-2 border-gray-300 bg-white text-black text-base font-bold shadow-sm focus:border-[#8bc34a] outline-none" value={getValue('weather_url', 'text')} onChange={e => handleChange('weather_url', e.target.value)} /></div>
|
|
|
|
|
<div className="flex flex-col gap-2 mb-8"><label className="text-xs font-black uppercase tracking-widest text-gray-600">Webkamera URL</label><input className="p-4 rounded-2xl border-2 border-gray-300 bg-white text-black text-base font-bold shadow-sm focus:border-[#8bc34a] outline-none" value={getValue('webcam_url', 'text')} onChange={e => handleChange('webcam_url', e.target.value)} /></div>
|
|
|
|
|
<div className="flex flex-col gap-2 mb-8"><label className="text-xs font-black uppercase tracking-widest text-gray-600">Video URL (YouTube/Vimeo)</label><input className="p-4 rounded-2xl border-2 border-gray-300 bg-white text-black text-base font-bold shadow-sm focus:border-[#8bc34a] outline-none" value={getValue('video_url', 'text')} onChange={e => handleChange('video_url', e.target.value)} /></div>
|
2026-03-08 10:26:56 +01:00
|
|
|
|
|
|
|
|
<ListObjectEditor
|
|
|
|
|
label="Sosiale Medier (Legg inn f.eks facebook, instagram, linkedin)"
|
|
|
|
|
value={formData.social_links}
|
|
|
|
|
templateKeys={['platform', 'url']}
|
|
|
|
|
onChange={(v) => handleChange('social_links', v)}
|
|
|
|
|
/>
|
2026-03-07 09:46:33 +01:00
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{activeTab === 'okonomi' && (
|
|
|
|
|
<div className="flex flex-col">
|
2026-04-10 09:52:34 +02:00
|
|
|
{/* MEDLEMSKAP */}
|
2026-03-08 10:26:56 +01:00
|
|
|
<div className="bg-gray-100 p-6 rounded-2xl mb-8 border border-gray-200">
|
|
|
|
|
<h3 className="font-black uppercase tracking-widest text-gray-800 mb-6 pb-2 border-b-2 border-gray-200">Medlemskap</h3>
|
2026-04-10 09:52:34 +02:00
|
|
|
<div className="flex flex-col gap-2 mb-8">
|
|
|
|
|
<label className="text-xs font-black uppercase tracking-widest text-gray-600">Sist Oppdatert (Dato)</label>
|
|
|
|
|
<input type="date" className="p-4 rounded-2xl border-2 border-gray-300 bg-white text-black text-base font-bold shadow-sm focus:border-[#8bc34a] outline-none w-max" value={getValue('membership_updated_at', 'date')} onChange={e => handleChange('membership_updated_at', e.target.value)} />
|
|
|
|
|
</div>
|
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-8">
|
|
|
|
|
<div className="flex flex-col gap-2 mb-8"><label className="text-xs font-black uppercase tracking-widest text-gray-600">Navn på standard medlemskap</label><input className="p-4 rounded-2xl border-2 border-gray-300 bg-white text-black text-base font-bold shadow-sm focus:border-[#8bc34a] outline-none" value={getValue('navn_standard_medlemskap', 'text')} onChange={e => handleChange('navn_standard_medlemskap', e.target.value)} /></div>
|
|
|
|
|
<div className="flex flex-col gap-2 mb-8"><label className="text-xs font-black uppercase tracking-widest text-gray-600">Pris standard (kun tall)</label><input type="number" className="p-4 rounded-2xl border-2 border-gray-300 bg-white text-black text-base font-bold shadow-sm focus:border-[#8bc34a] outline-none" value={getValue('standard_medlemskap', 'number')} onChange={e => handleChange('standard_medlemskap', Number(e.target.value))} /></div>
|
|
|
|
|
<div className="flex flex-col gap-2 mb-8 col-span-1 md:col-span-2"><label className="text-xs font-black uppercase tracking-widest text-gray-600">Kommentar standard</label><textarea className="p-4 rounded-2xl border-2 border-gray-300 bg-white text-black text-base shadow-sm focus:border-[#8bc34a] outline-none" rows={2} value={getValue('standard_medlemskap_kommentarer', 'textarea')} onChange={e => handleChange('standard_medlemskap_kommentarer', e.target.value)} /></div>
|
|
|
|
|
<div className="flex flex-col gap-2 mb-8"><label className="text-xs font-black uppercase tracking-widest text-gray-600">Navn på rimeligste</label><input className="p-4 rounded-2xl border-2 border-gray-300 bg-white text-black text-base font-bold shadow-sm focus:border-[#8bc34a] outline-none" value={getValue('navn_rimeligste_alternativ', 'text')} onChange={e => handleChange('navn_rimeligste_alternativ', e.target.value)} /></div>
|
|
|
|
|
<div className="flex flex-col gap-2 mb-8"><label className="text-xs font-black uppercase tracking-widest text-gray-600">Pris rimeligste (kun tall)</label><input type="number" className="p-4 rounded-2xl border-2 border-gray-300 bg-white text-black text-base font-bold shadow-sm focus:border-[#8bc34a] outline-none" value={getValue('rimeligste_alternativ', 'number')} onChange={e => handleChange('rimeligste_alternativ', Number(e.target.value))} /></div>
|
|
|
|
|
<div className="flex flex-col gap-2 mb-8 col-span-1 md:col-span-2"><label className="text-xs font-black uppercase tracking-widest text-gray-600">Lenke til medlemskapsside</label><input className="p-4 rounded-2xl border-2 border-gray-300 bg-white text-black text-base font-bold shadow-sm focus:border-[#8bc34a] outline-none" value={getValue('medlemskap_url', 'text')} onChange={e => handleChange('medlemskap_url', e.target.value)} /></div>
|
|
|
|
|
</div>
|
2026-03-08 10:26:56 +01:00
|
|
|
</div>
|
|
|
|
|
|
2026-04-10 09:52:34 +02:00
|
|
|
{/* GREENFEE */}
|
|
|
|
|
<div className="bg-gray-100 p-6 rounded-2xl border border-gray-200 mb-8">
|
|
|
|
|
<h3 className="font-black uppercase tracking-widest text-gray-800 mb-6 pb-2 border-b-2 border-gray-200">Greenfee / Gjestespill</h3>
|
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-8">
|
|
|
|
|
<div className="flex flex-col gap-2 mb-8"><label className="text-xs font-black uppercase tracking-widest text-gray-600">Lenke til Greenfee-side</label><input className="p-4 rounded-2xl border-2 border-gray-300 bg-white text-black text-base font-bold shadow-sm focus:border-[#8bc34a] outline-none" value={getValue('greenfee_url', 'text')} onChange={e => handleChange('greenfee_url', e.target.value)} /></div>
|
|
|
|
|
<div className="flex flex-col gap-2 mb-8"><label className="text-xs font-black uppercase tracking-widest text-gray-600">Krav til Gjestespill (f.eks Klubbhandicap)</label><input className="p-4 rounded-2xl border-2 border-gray-300 bg-white text-black text-base font-bold shadow-sm focus:border-[#8bc34a] outline-none" value={getValue('guest_requirements', 'text')} onChange={e => handleChange('guest_requirements', e.target.value)} /></div>
|
|
|
|
|
</div>
|
|
|
|
|
<ListObjectEditor
|
|
|
|
|
label="Greenfee Priser (Legg til rader for Voksen/Junior etc)"
|
|
|
|
|
value={formData.greenfee}
|
|
|
|
|
templateKeys={['banenavn', 'priskategori', 'pris_voksne', 'pris_junior']}
|
|
|
|
|
onChange={(v) => handleChange('greenfee', v)}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* VEIEN TIL GOLF (VTG) */}
|
|
|
|
|
<div className="bg-[#8bc34a]/10 p-6 rounded-2xl border border-[#8bc34a]/30 mb-8">
|
|
|
|
|
<h3 className="font-black uppercase tracking-widest text-[#11280f] mb-6 pb-2 border-b-2 border-[#8bc34a]/20">Veien til Golf (VTG)</h3>
|
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-8">
|
|
|
|
|
<div className="flex flex-col gap-2 mb-8"><label className="text-xs font-black uppercase tracking-widest text-gray-600">Pris VTG kurs (kun tall)</label><input type="number" className="p-4 rounded-2xl border-2 border-gray-300 bg-white text-black text-base font-bold shadow-sm focus:border-[#8bc34a] outline-none" value={getValue('vtg_pris', 'number')} onChange={e => handleChange('vtg_pris', Number(e.target.value))} /></div>
|
|
|
|
|
<div className="flex flex-col gap-2 mb-8"><label className="text-xs font-black uppercase tracking-widest text-gray-600">Lenke til VTG påmelding</label><input className="p-4 rounded-2xl border-2 border-gray-300 bg-white text-black text-base font-bold shadow-sm focus:border-[#8bc34a] outline-none" value={getValue('vtg_lenke', 'text')} onChange={e => handleChange('vtg_lenke', e.target.value)} /></div>
|
|
|
|
|
<div className="flex flex-col gap-2 mb-8 col-span-1 md:col-span-2"><label className="text-xs font-black uppercase tracking-widest text-gray-600">Beskrivelse / Hva er inkludert</label><textarea className="p-4 rounded-2xl border-2 border-gray-300 bg-white text-black text-base shadow-sm focus:border-[#8bc34a] outline-none" rows={3} value={getValue('vtg_beskrivelse', 'textarea')} onChange={e => handleChange('vtg_beskrivelse', e.target.value)} /></div>
|
|
|
|
|
</div>
|
|
|
|
|
<ListObjectEditor
|
|
|
|
|
label="Kursdatoer"
|
|
|
|
|
value={formData.vtg_datoer}
|
|
|
|
|
templateKeys={['dato', 'status']}
|
|
|
|
|
onChange={(v) => handleChange('vtg_datoer', v)}
|
|
|
|
|
/>
|
2026-03-08 10:26:56 +01:00
|
|
|
</div>
|
2026-03-07 09:46:33 +01:00
|
|
|
|
2026-03-08 10:26:56 +01:00
|
|
|
<div className="mt-8 border-t-2 border-gray-200 pt-8">
|
|
|
|
|
<KeyValueEditor label="Fasiliteter (Proshop, Kafé etc.)" value={formData.amenities} onChange={(v) => handleChange('amenities', v)} />
|
|
|
|
|
<KeyValueEditor label="Norsk Seniorgolf (NSG)" value={formData.nsg_data} onChange={(v) => handleChange('nsg_data', v)} />
|
|
|
|
|
<KeyValueEditor label="Golfamore Info" value={formData.golfamore_data} onChange={(v) => handleChange('golfamore_data', v)} />
|
|
|
|
|
|
2026-04-10 09:52:34 +02:00
|
|
|
{/* HER ER GOLFPAKKENE SOM JEG MISTET I FORRIGE RUNDE */}
|
2026-03-08 10:26:56 +01:00
|
|
|
<ListObjectEditor
|
|
|
|
|
label="Golfpakker"
|
|
|
|
|
value={formData.golfpakker}
|
|
|
|
|
templateKeys={['navn', 'pris', 'beskrivelse']}
|
|
|
|
|
onChange={(v) => handleChange('golfpakker', v)}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
2026-03-07 09:46:33 +01:00
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
2026-03-08 10:26:56 +01:00
|
|
|
{activeTab === 'baner' && (
|
|
|
|
|
<div className="flex flex-col gap-8">
|
|
|
|
|
<div className="bg-[#f1f7ed] p-6 rounded-2xl border-2 border-[#7ca982] mb-4">
|
|
|
|
|
<h3 className="font-black text-[#11280f] text-lg uppercase tracking-widest mb-2">Baner og Scorekort</h3>
|
|
|
|
|
<p className="text-sm text-gray-800 font-medium">Bruk det interaktive skjemaet under for å redigere lengder, par og utslag.</p>
|
2026-03-07 09:46:33 +01:00
|
|
|
</div>
|
|
|
|
|
|
2026-03-08 10:26:56 +01:00
|
|
|
{formData.courses?.map((course: any, cIdx: number) => (
|
|
|
|
|
<div key={course.id || cIdx} className="bg-gray-100 p-8 rounded-[2rem] border-2 border-gray-200 shadow-sm mb-8">
|
|
|
|
|
<div className="flex flex-col md:flex-row justify-between items-start md:items-center mb-8 gap-4 border-b-2 border-gray-200 pb-4">
|
|
|
|
|
<h4 className="text-2xl font-black text-black">{course.name}</h4>
|
|
|
|
|
<span className={`px-4 py-2 rounded-xl text-xs font-black uppercase tracking-widest ${course.is_main_course ? 'bg-[#8bc34a] text-white shadow-md' : 'bg-gray-300 text-gray-700'}`}>
|
|
|
|
|
{course.is_main_course ? 'Hovedbane' : 'Sekundærbane'}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-8">
|
|
|
|
|
<div className="flex flex-col gap-2 mb-6">
|
|
|
|
|
<label className="text-xs font-black uppercase tracking-widest text-gray-600">Banenavn</label>
|
|
|
|
|
<input className="p-4 rounded-2xl border-2 border-gray-300 focus:border-[#8bc34a] outline-none font-bold text-black bg-white text-base shadow-sm" value={course.name || ""} onChange={e => {
|
|
|
|
|
const newCourses = [...formData.courses];
|
|
|
|
|
newCourses[cIdx] = {...course, name: e.target.value};
|
|
|
|
|
handleChange('courses', newCourses);
|
|
|
|
|
}} />
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex flex-col gap-2 mb-6">
|
|
|
|
|
<label className="text-xs font-black uppercase tracking-widest text-gray-600">Status</label>
|
|
|
|
|
<select className="p-4 rounded-2xl border-2 border-gray-300 focus:border-[#8bc34a] outline-none font-bold text-black bg-white text-base shadow-sm" value={course.status || "ukjent"} onChange={e => {
|
|
|
|
|
const newCourses = [...formData.courses];
|
|
|
|
|
newCourses[cIdx] = {...course, status: e.target.value};
|
|
|
|
|
handleChange('courses', newCourses);
|
|
|
|
|
}}>
|
|
|
|
|
<option value="aapen">🟢 Åpen</option>
|
|
|
|
|
<option value="aapen_med_vintergreener">🟡 Vintergreener</option>
|
|
|
|
|
<option value="aapner_snart">🟡 Åpner Snart</option>
|
|
|
|
|
<option value="stengt">🔴 Stengt</option>
|
|
|
|
|
<option value="nedlagt">⚫ Nedlagt</option>
|
|
|
|
|
<option value="ukjent">⚪ Ukjent</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex flex-col gap-2 mb-6">
|
|
|
|
|
<label className="text-xs font-black uppercase tracking-widest text-gray-600">Total Par (Bane)</label>
|
|
|
|
|
<input type="number" className="p-4 rounded-2xl border-2 border-gray-300 focus:border-[#8bc34a] outline-none font-bold text-black bg-white text-base shadow-sm" value={course.par || ""} onChange={e => {
|
|
|
|
|
const newCourses = [...formData.courses];
|
|
|
|
|
newCourses[cIdx] = {...course, par: Number(e.target.value)};
|
|
|
|
|
handleChange('courses', newCourses);
|
|
|
|
|
}} />
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex flex-col gap-2 mb-6">
|
|
|
|
|
<label className="text-xs font-black uppercase tracking-widest text-gray-600">Utløpsdato Slope</label>
|
|
|
|
|
<input type="date" className="p-4 rounded-2xl border-2 border-gray-300 focus:border-[#8bc34a] outline-none font-bold text-black bg-white text-base shadow-sm" value={course.slope_valid_until ? course.slope_valid_until.split('T')[0] : ""} onChange={e => {
|
|
|
|
|
const newCourses = [...formData.courses];
|
|
|
|
|
newCourses[cIdx] = {...course, slope_valid_until: e.target.value};
|
|
|
|
|
handleChange('courses', newCourses);
|
|
|
|
|
}} />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* DET NYE SCOREKORTET INKLUDERES HER */}
|
|
|
|
|
<ScorecardBuilder
|
|
|
|
|
course={course}
|
|
|
|
|
onChange={(updatedCourse) => {
|
|
|
|
|
const newCourses = [...formData.courses];
|
|
|
|
|
newCourses[cIdx] = updatedCourse;
|
|
|
|
|
handleChange('courses', newCourses);
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
2026-03-07 09:46:33 +01:00
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
2026-04-11 09:54:54 +02:00
|
|
|
}
|