From cf0f049ea6e62bfbe5c3e8fc0e081612d050290e Mon Sep 17 00:00:00 2001 From: Erol Haagenrud Date: Mon, 20 Apr 2026 09:05:33 +0200 Subject: [PATCH] =?UTF-8?q?F=C3=B8r=20implementering=20av=20flere=20omtale?= =?UTF-8?q?r=20i=20badger=20i=20kart?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...-04-20 09.00.46 teeoff.no d8d72ce0bea9.jpg | Bin 0 -> 23897 bytes backend/main.py | 1 + .../rediger/[slug]/EditFacilityClient.tsx | 341 +++++++++++++++++- .../src/app/api/admin/uploads/images/route.ts | 8 +- 4 files changed, 348 insertions(+), 2 deletions(-) create mode 100644 2026-04-20 09.00.46 teeoff.no d8d72ce0bea9.jpg diff --git a/2026-04-20 09.00.46 teeoff.no d8d72ce0bea9.jpg b/2026-04-20 09.00.46 teeoff.no d8d72ce0bea9.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2d40e7a0d41c73c687bfe683bef79b5e11947a9c GIT binary patch literal 23897 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 literal 0 HcmV?d00001 diff --git a/backend/main.py b/backend/main.py index d2f8504..b462aaf 100644 --- a/backend/main.py +++ b/backend/main.py @@ -2776,6 +2776,7 @@ async def update_facility_full(facility_id: int, request: Request): 'address', 'zipcode', 'city', 'county', 'lat', 'lng', 'email', 'phone', 'website_url', 'golfbox_booking_url', 'golfbox_tournament_url', 'weather_url', 'webcam_url', 'video_url', 'baneguide_url', 'flyfoto_url', + 'image_url', 'logo_url', 'front_image_url', 'gallery', 'amenities', 'greenfee', 'golfpakker', 'rabattert_greenfee', 'nsg_url', 'nsg_data', 'golfamore', 'golfamore_url', 'golfamore_data', 'navn_standard_medlemskap', 'standard_medlemskap', 'standard_medlemskap_kommentarer', diff --git a/frontend/src/app/admin/rediger/[slug]/EditFacilityClient.tsx b/frontend/src/app/admin/rediger/[slug]/EditFacilityClient.tsx index b459cc9..549c3ae 100644 --- a/frontend/src/app/admin/rediger/[slug]/EditFacilityClient.tsx +++ b/frontend/src/app/admin/rediger/[slug]/EditFacilityClient.tsx @@ -1,5 +1,5 @@ "use client"; -import { useState } from 'react'; +import { useRef, useState, type ChangeEvent } from 'react'; import { useRouter } from 'next/navigation'; import Link from 'next/link'; import { adminFetch } from "@/config/adminFetch"; @@ -377,12 +377,38 @@ const ScorecardBuilder = ({ course, onChange }: { course: any, onChange: (c: any ); }; +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(); @@ -397,6 +423,98 @@ export default function EditFacilityClient({ initialData, allFacilities }: { ini 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 { @@ -556,6 +674,227 @@ export default function EditFacilityClient({ initialData, allFacilities }: { ini {activeTab === 'linker' && (
+ handleSingleImageUpload('image_url', event)} + /> + handleSingleImageUpload('logo_url', event)} + /> + + +
+
+
+

Anleggsbilder

+

+ Last opp AVIF-optimaliserte bilder direkte fra admin. Du kan også fjerne koblingen til et bilde uten å slette selve filen fra serveren. +

+
+ {mediaFeedback ? ( +
+ {mediaFeedback} +
+ ) : null} +
+ +
+
+
+
+

Hovedbilde

+

Brukes som hovedbilde på baneprofilen og som fallback i galleri.

+
+
+ + +
+
+
+
+ {getValue('image_url', 'text') ? ( + {`${initialData.name} + ) : ( +
+ Ingen hovedbilde valgt +
+ )} +
+
+
+ + handleChange('image_url', e.target.value)} + /> +
+
+ +
+
+
+

Logo

+

Vises i baneprofilen når klubben har egen logo.

+
+
+ + +
+
+
+
+ {getValue('logo_url', 'text') ? ( + {`${initialData.name} + ) : ( +
+ Ingen logo valgt +
+ )} +
+
+
+ + handleChange('logo_url', e.target.value)} + /> +
+
+
+ +
+
+
+

Galleri

+

Bildene roteres i toppen av baneprofilen. Du kan endre rekkefølge, fjerne dem eller bruke et galleribilde som hovedbilde.

+
+ +
+ + {galleryImages.length === 0 ? ( +
+ Ingen galleribilder lagt til ennå. +
+ ) : ( +
+ {galleryImages.map((url, index) => ( +
+
+
+
+ {`${initialData.name} +
+
+
+
+
+

Galleribilde {index + 1}

+ {getValue('image_url', 'text') === url ? ( +

Brukes også som hovedbilde

+ ) : null} +
+
+ + + + +
+
+ + +
+
+
+ ))} +
+ )} +
+
+
handleChange('website_url', e.target.value)} />
handleChange('golfbox_booking_url', e.target.value)} />
handleChange('golfbox_tournament_url', e.target.value)} />
diff --git a/frontend/src/app/api/admin/uploads/images/route.ts b/frontend/src/app/api/admin/uploads/images/route.ts index 41c5817..7475e46 100644 --- a/frontend/src/app/api/admin/uploads/images/route.ts +++ b/frontend/src/app/api/admin/uploads/images/route.ts @@ -17,6 +17,11 @@ const ALLOWED_MIME_TYPES = new Set([ export const runtime = "nodejs"; +function resolveUploadFolder(value: FormDataEntryValue | null) { + const normalized = String(value || "articles").trim().toLowerCase(); + return normalized === "facilities" ? "facilities" : "articles"; +} + function sanitizeFilenameStem(filename: string) { const stem = path.parse(filename).name; const normalized = stem @@ -38,6 +43,7 @@ export async function POST(request: Request) { try { const formData = await request.formData(); const file = formData.get("file"); + const uploadFolder = resolveUploadFolder(formData.get("folder")); if (!(file instanceof File)) { return NextResponse.json({ detail: "Fant ingen bildefil i requesten." }, { status: 400 }); @@ -68,7 +74,7 @@ export async function POST(request: Request) { const dateSegment = new Date().toISOString().slice(0, 10); const safeName = sanitizeFilenameStem(file.name); const filename = `${safeName}-${randomUUID()}.avif`; - const relativeDirectory = path.join("uploads", "articles", dateSegment); + const relativeDirectory = path.join("uploads", uploadFolder, dateSegment); const absoluteDirectory = path.join(process.cwd(), "public", relativeDirectory); const absolutePath = path.join(absoluteDirectory, filename);