import { useEffect, useMemo, useRef, useState } from "react"; import type React from "react"; import { Activity, BarChart3, Download, FileClock, FileJson, Github, HelpCircle, Heart, Search, Plus, RotateCcw, Save, Send, ShieldAlert, Trash2, Upload, X, UserRound } from "lucide-react"; import { accommodationLabels, categoryTooltipTexts, delayedSymptomLabels, limitationLabels, negativeScale, positiveScale, recoveryLabels, statusLabels, taskCompletionLabels, taskTypeLabels, tooltipTexts, worseLabels } from "./labels"; import { deleteEvaluatorNote, deleteEvaluatorProfile, ensureDemoEvaluatorProfiles, exportEvaluatorBackup, importEvaluatorBackup, importExportPayload, listEvaluatorProfiles, renameEvaluatorProfile, upsertEvaluatorNote } from "./evaluatorStorage"; import { calculatePeriodWorkAbilityAssessment, calculateReportScore, getEntryTime, getReportScore, getScoreBand, normalizeReportMetric, scoreBandDisclaimer } from "./scoring"; import { verificationLabel, verificationTooltip } from "./integrity"; import { I18nProvider, translateScoreBand, translateScoreBandDescription, translateVerificationStatus, useI18n } from "./i18n"; import { confirmSecureShareImport, createSecureShare, decryptSecureSharePayload, fetchSecureShare, parseShareRequestFromLocation } from "./secureShare"; import { createFollowup, createReport, deleteAllReports, deleteReport, assertValidExportRange, buildExportFilename, ensureDemoData, exportReports, getLatestWorkReport, getLastWorkReport, importReportsFromJson, isEditableWithin24Hours, isDemoModeEnabled, listReports, previewImportReports, setDemoModeEnabled, updateFollowup, updateReport } from "./storage"; import type { ExportRange } from "./storage"; import type { Accommodation, DelayedFollowup, DelayedSymptom, EvaluatorProfile, FollowupDraft, ImportSummary, Limitation, RecoveryStatus, Report, ReportDraft, ReportStatus, ScaleValue, StoredEntry, TaskCompletion, TaskType, WorseThanBefore } from "./types"; type View = "registrer" | "oppfolging" | "oversikt" | "personvern"; type PresetRange = "7d" | "1m" | "3m" | "6m" | "1y" | "3y" | "5y" | "all" | "custom"; type LineVisibility = "normal" | "dim" | "hidden"; type TrendMetric = { key: keyof Report | "total_score_percent"; label: string; short: string; color: string }; const PERIOD_KEY = "arbeidsevne-egenvurdering:trend-period"; const APP_VERSION = "1.2.1"; const taskTypeOptions = Object.keys(taskTypeLabels) as TaskType[]; const limitationOptions = Object.keys(limitationLabels) as Limitation[]; const accommodationOptions = Object.keys(accommodationLabels) as Accommodation[]; const delayedSymptomOptions = Object.keys(delayedSymptomLabels) as DelayedSymptom[]; const visibleStatusOptions: ReportStatus[] = ["kan_fortsette", "trenger_pause", "trenger_enklere_oppgave", "bør_stoppe"]; const trendMetrics: TrendMetric[] = [ { key: "total_score_percent", label: "Totalscore", short: "T", color: "#235b5e" }, { key: "work_ability", label: "Arbeidsevne", short: "A", color: "#307f78" }, { key: "energy_level", label: "Energi", short: "E", color: "#b2642a" }, { key: "mental_clarity", label: "Klarhet", short: "K", color: "#4f5c9c" }, { key: "symptom_burden", label: "Symptomer", short: "S", color: "#b23b4a" }, { key: "effort_strain", label: "Belastning", short: "B", color: "#6b5a3a" } ]; function App() { return ( ); } function AppRoutes() { const { t } = useI18n(); if (window.location.pathname.startsWith("/s")) { return ; } if (window.location.pathname.startsWith("/evaluator") || window.location.pathname.startsWith("/veileder")) { return ; } const [demoMode, setDemoMode] = useState(() => isDemoModeEnabled()); const [view, setView] = useState("registrer"); const [entries, setEntries] = useState(() => listReports(isDemoModeEnabled())); const [selectedEntryId, setSelectedEntryId] = useState(null); const [deleteRequest, setDeleteRequest] = useState<{ id: string; name: string } | null>(null); const [editEntry, setEditEntry] = useState(null); const refresh = () => { const next = listReports(demoMode); setEntries(next); }; const selectedEntry = selectedEntryId ? entries.find((entry) => entry.id === selectedEntryId) : undefined; const changeDemoMode = (enabled: boolean) => { setDemoModeEnabled(enabled); if (enabled) ensureDemoData(); setDemoMode(enabled); setSelectedEntryId(null); setDeleteRequest(null); setEditEntry(null); setEntries(listReports(enabled)); }; useEffect(() => { setEntries(listReports(demoMode)); }, [demoMode]); return (

{t("app.eyebrow")}

{t("app.title")}

{t("app.evaluator")}
{demoMode && (
)} {view === "registrer" && ( setEditEntry(null)} onSaved={() => { setEditEntry(null); refresh(); }} /> )} {view === "oppfolging" && ( setEditEntry(null)} onSaved={() => { setEditEntry(null); refresh(); }} /> )} {view === "oversikt" && ( setSelectedEntryId(null)} onRequestEdit={(entry) => { setEditEntry(entry); setView(entry.type === "work_report" ? "registrer" : "oppfolging"); }} onRequestDelete={(entry) => setDeleteRequest({ id: entry.id, name: entry.type === "work_report" ? entry.workplace || t("danger.reportName") : t("danger.followupName") })} /> )} {view === "personvern" && }
setDeleteRequest(null)} onConfirm={() => { if (!deleteRequest) return; deleteReport(deleteRequest.id, demoMode); if (selectedEntryId === deleteRequest.id) setSelectedEntryId(null); setDeleteRequest(null); refresh(); }} />
); } function NavButton({ icon, label, active, onClick }: { icon: React.ReactNode; label: string; active: boolean; onClick: () => void; }) { return ( ); } function AppFooter({ demoMode, onDemoModeChange, showDemoToggle = true }: { demoMode: boolean; onDemoModeChange: (enabled: boolean) => void; showDemoToggle?: boolean; }) { const { language, setLanguage, t } = useI18n(); const baseYear = 2026; const currentYear = new Date().getFullYear(); const yearLabel = currentYear > baseYear ? `${baseYear}-${currentYear}` : `${baseYear}`; return (
|
© {yearLabel} {t("footer.developedBy")}
{showDemoToggle && ( )}
); } function ConfirmDeleteModal({ open, itemName, onCancel, onConfirm }: { open: boolean; itemName: string; onCancel: () => void; onConfirm: () => void; }) { const { t } = useI18n(); const [secondsLeft, setSecondsLeft] = useState(1); useEffect(() => { if (!open) return; setSecondsLeft(1); const timer = window.setTimeout(() => setSecondsLeft(0), 1000); return () => window.clearTimeout(timer); }, [open, itemName]); useEffect(() => { if (!open) return; const onKeyDown = (event: KeyboardEvent) => { if (event.key === "Escape") onCancel(); if (event.key === "Enter" && secondsLeft > 0) event.preventDefault(); }; window.addEventListener("keydown", onKeyDown); return () => window.removeEventListener("keydown", onKeyDown); }, [onCancel, open, secondsLeft]); if (!open) return null; return (
event.stopPropagation()}>

{t("deleteModal.title")}

{t("deleteModal.body", { itemName })}

); } function createInitialReportDraft(demoMode = false): ReportDraft { const last = getLastWorkReport(demoMode); return { workplace: last?.workplace ?? "", work_start_time: last?.work_start_time ?? "", work_end_time: last?.work_end_time ?? "", task_types: [], main_limitations: [], helpful_accommodations: [], note: "" }; } function createReportDraftFromReport(report: Report): ReportDraft { return { workplace: report.workplace ?? "", work_start_time: report.work_start_time ?? "", work_end_time: report.work_end_time ?? "", work_ability: report.work_ability, energy_level: report.energy_level, mental_clarity: report.mental_clarity, symptom_burden: report.symptom_burden, effort_strain: report.effort_strain, status: report.status, physical_energy_detail: report.physical_energy, mental_energy_detail: report.mental_energy, perceived_productivity: report.perceived_productivity, task_completion: report.task_completion, task_types: report.task_types, main_limitations: report.main_limitations, helpful_accommodations: report.helpful_accommodations, note: report.note ?? "" }; } function ReportForm({ demoMode, editReport, onCancelEdit, onSaved }: { demoMode: boolean; editReport?: Report | null; onCancelEdit?: () => void; onSaved: () => void; }) { const { labels, t } = useI18n(); const editing = Boolean(editReport); const editAllowed = !editReport || isEditableWithin24Hours(editReport); const [draft, setDraft] = useState(() => editReport ? createReportDraftFromReport(editReport) : createInitialReportDraft(demoMode)); const [attempted, setAttempted] = useState(false); const [savedText, setSavedText] = useState(""); const reportFields = requiredReportFields.map((field) => ({ ...field, label: reportFieldLabel(field.key, t) })); const missing = reportFields.filter((field) => !draft[field.key]); const canSubmit = missing.length === 0; useEffect(() => { setDraft(editReport ? createReportDraftFromReport(editReport) : createInitialReportDraft(demoMode)); setAttempted(false); setSavedText(""); }, [demoMode, editReport?.id]); const setScale = (key: keyof ReportDraft, value: ScaleValue) => { setDraft((current) => ({ ...current, [key]: value })); }; const toggle = (key: keyof ReportDraft, value: T) => { setDraft((current) => { const values = current[key] as T[]; return { ...current, [key]: values.includes(value) ? values.filter((item) => item !== value) : [...values, value] }; }); }; const submit = () => { setAttempted(true); if (!canSubmit || !editAllowed) return; if (editReport) updateReport(editReport.id, draft, demoMode); else createReport(draft, demoMode); setDraft(createInitialReportDraft(demoMode)); setAttempted(false); setSavedText(editReport ? t("edit.saved") : t("register.saved")); onSaved(); window.setTimeout(() => setSavedText(""), 2400); }; return (
{editing && !editAllowed &&

{t("edit.expired")}

}

{t("register.workSession")}

{reportFields.map((field) => ( setScale(field.key, value)} /> ))}

{t("register.continueQuestion")}

{t("register.chooseBestNow")}

{visibleStatusOptions.map((status) => ( ))}
{t("register.optionalDetails")}
setScale("physical_energy_detail", value)} /> setScale("mental_energy_detail", value)} /> setScale("perceived_productivity", value)} /> setDraft((current) => ({ ...current, task_completion: value }))} /> toggle("task_types", value)} /> toggle("main_limitations", value)} /> toggle("helpful_accommodations", value)} />