"use client"; /** * TEE OFF ADMIN DASHBOARD v1.9 - RESPONSIVT MED AI-HVISKER, KILL SWITCH, FILTER & FULL EDIT * --------------------------------------------------------------------------- * PLASSERING: frontend/src/app/admin/page.tsx * FUNKSJON: Live-oppdatering, massevalg, redigering, meny og smart-filtrering. * --------------------------------------------------------------------------- */ import { useState, useEffect, useMemo } from 'react'; import { API_URL } from "@/config/constants"; import ScrapeMethodSelect from "@/components/ScrapeMethodSelect"; import Link from 'next/link'; export default function AdminDashboard() { const [facilities, setFacilities] = useState([]); const [loading, setLoading] = useState(true); const [selectedFacilities, setSelectedFacilities] = useState([]); const [isScraping, setIsScraping] = useState(false); const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false); const [editingFacility, setEditingFacility] = useState(null); // NYTT: State for å holde styr på hvilket filter som er aktivt const [statusFilter, setStatusFilter] = useState('alle'); const [editForm, setEditForm] = useState({ scrape_status_url: '', scrape_status_selector: '', scrape_method: '', ai_instruction: '', courses: [] as any[] }); const [isSaving, setIsSaving] = useState(false); const fetchFacilities = () => { fetch(`${API_URL}/facilities`) .then(res => res.json()) .then(data => { setFacilities(Array.isArray(data) ? data : []); setLoading(false); }) .catch(() => setLoading(false)); }; useEffect(() => { fetchFacilities(); }, []); useEffect(() => { let interval: NodeJS.Timeout; if (isScraping) { interval = setInterval(() => { fetchFacilities(); }, 10000); } return () => clearInterval(interval); }, [isScraping]); // Filtreringslogikken som kjører automatisk når facilities eller filteret endres const filteredFacilities = useMemo(() => { if (statusFilter === 'alle') return facilities; return facilities.map(facility => { if (!facility.course_statuses) return facility; // Filtrer banene innad i hvert anlegg const filteredCourses = facility.course_statuses.filter((cs: any) => { const s = cs.status || 'ukjent'; if (statusFilter === 'aapne') { return s === 'aapen'; } if (statusFilter === 'ikke_stengt') { return ['aapen', 'aapen_med_vintergreener', 'aapner_snart'].includes(s); } if (statusFilter === 'stengt') { return s === 'stengt' || s === 'nedlagt'; } if (statusFilter === 'ukjent_feil') { return s === 'ukjent' || s === 'NOT_FOUND'; } return true; }); // Returner anlegget kun med de banene som matcher return { ...facility, course_statuses: filteredCourses }; }).filter(facility => facility.course_statuses && facility.course_statuses.length > 0); }, [facilities, statusFilter]); // "Velg alle" gjelder kun de anleggene som er synlige i filteret const handleSelectAll = (e: React.ChangeEvent) => { if (e.target.checked) { setSelectedFacilities(filteredFacilities.map(f => f.id)); } else { setSelectedFacilities([]); } }; const handleSelectOne = (id: number, checked: boolean) => { if (checked) { setSelectedFacilities([...selectedFacilities, id]); } else { setSelectedFacilities(selectedFacilities.filter(facilityId => facilityId !== id)); } }; const handleRunScrapers = async () => { if (isScraping) { setIsScraping(false); return; } setIsScraping(true); try { const response = await fetch(`${API_URL}/admin/run-scraper`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ facility_ids: selectedFacilities }) }); if (!response.ok) throw new Error("Kunne ikke starte skraping"); const timeoutMs = Math.max(selectedFacilities.length * 40 * 1000, 60000); setSelectedFacilities([]); setTimeout(() => setIsScraping(false), timeoutMs); } catch (error) { console.error(error); alert("Feil ved start av skraperen."); setIsScraping(false); } }; const openEditModal = (facility: any) => { setEditingFacility(facility); setEditForm({ scrape_status_url: facility.scrape_status_url || '', scrape_status_selector: facility.scrape_status_selector || '', scrape_method: facility.scrape_method || 'css_selector', ai_instruction: facility.ai_instruction || '', courses: facility.course_statuses ? facility.course_statuses.map((c: any) => ({id: c.id, name: c.name, status: c.status})) : [] }); }; const handleSaveEdit = async () => { setIsSaving(true); try { const response = await fetch(`${API_URL}/admin/facilities/${editingFacility.id}/scrape-settings`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(editForm) }); if (!response.ok) throw new Error("Feil ved lagring"); setEditingFacility(null); fetchFacilities(); } catch (error) { alert("Kunne ikke lagre endringene."); console.error(error); } finally { setIsSaving(false); } }; if (loading) return
LASTER DASHBORD...
; return (
{/* REDIGER-MODAL FOR SKRAPING */} {editingFacility && (

Skrape-innstillinger

{editingFacility.name}

setEditForm({...editForm, scrape_status_url: e.target.value})} className="w-full border-2 border-gray-100 rounded-xl p-3 text-sm focus:border-[#8bc34a] focus:outline-none transition-colors" placeholder="f.eks. https://golfklubb.no/banestatus" />
{editForm.scrape_method === 'llm_parse' && (