"use client"; /** * TEE OFF ADMIN DASHBOARD v4.0 - KONTROLLPANEL */ import { useEffect, useMemo, useRef, useState } from 'react'; import { API_URL } from "@/config/constants"; import { adminFetch } from "@/config/adminFetch"; import ScrapeMethodSelect from "@/components/ScrapeMethodSelect"; import Link from 'next/link'; type AdminTab = 'banestatus' | 'medlemskap' | 'greenfee' | 'vtg'; type ScrapeJobStatus = 'pending' | 'running' | 'completed' | 'failed'; type ScrapeJobEvent = { timestamp?: string; facility_id?: number | null; facility_name?: string; outcome?: 'success' | 'warning' | 'error' | 'info' | string; message?: string; processed?: number; total?: number; }; type ScrapeJob = { id: number; job_type: AdminTab; status: ScrapeJobStatus; facility_ids: number[]; total_facilities: number; error_message?: string | null; error_code?: string | null; retryable?: boolean; attempt_count?: number; max_attempts?: number; next_retry_at?: string | null; last_error_at?: string | null; progress_total?: number; progress_completed?: number; progress_ok?: number; progress_failed?: number; progress_skipped?: number; current_facility_id?: number | null; current_facility_name?: string | null; recent_events?: ScrapeJobEvent[]; result_summary?: Record; created_at?: string | null; started_at?: string | null; finished_at?: string | null; }; type QueueFeedback = { tone: 'success' | 'warning' | 'info' | 'error'; title: string; message: string; }; type TwoFactorSetupResponse = { issuer: string; account_name: string; otp_secret: string; provisioning_uri: string; qr_svg: string; }; const JOB_LABELS: Record = { banestatus: 'Banestatus', medlemskap: 'Medlemskap', greenfee: 'Greenfee', vtg: 'VTG', }; const JOB_STATUS_LABELS: Record = { pending: 'I kø', running: 'Kjører', completed: 'Fullført', failed: 'Feilet', }; const JOB_ERROR_LABELS: Record = { json_parse: 'JSON-tolkning', configuration: 'Konfigurasjon', timeout: 'Tidsavbrudd', browser: 'Nettleser / Playwright', network: 'Nettverk', database: 'Database', validation: 'Validering', unknown: 'Ukjent feil', worker_stale: 'Worker mistet heartbeat', }; const JOB_EVENT_TONE_CLASSES: Record = { success: 'bg-[#edf6e3] text-[#11280f] border-[#d8e8c8]', warning: 'bg-amber-50 text-amber-800 border-amber-200', error: 'bg-red-50 text-red-700 border-red-200', info: 'bg-slate-50 text-slate-700 border-slate-200', }; const InlineEdit = ({ facilityId, field, initialValue, onSave }: { facilityId: number, field: string, initialValue: string, onSave: (id: number, field: string, val: string) => void }) => { const [isEditing, setIsEditing] = useState(false); const [value, setValue] = useState(initialValue || ''); const handleSave = () => { setIsEditing(false); if (value !== initialValue) { onSave(facilityId, field, value); } }; if (isEditing) { return (