From 76ec48bfefa1af963961c3e59d80e5a46ad03b41 Mon Sep 17 00:00:00 2001 From: Franz Rolfsvaag Date: Thu, 28 May 2026 11:13:35 +0200 Subject: [PATCH] Release v1.1.1 demo advisor mode --- package-lock.json | 4 +- package.json | 2 +- public/service-worker.js | 2 +- src/App.tsx | 118 ++++++++++++++++----------- src/demoData.ts | 153 ++++++++++++++++++++++++++++++++++- src/evaluatorStorage.test.ts | 8 ++ src/evaluatorStorage.ts | 77 +++++++++++------- src/i18n.tsx | 24 +++--- src/styles.css | 65 ++++----------- 9 files changed, 314 insertions(+), 139 deletions(-) diff --git a/package-lock.json b/package-lock.json index b233376..a34d0fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "arbeidspuls", - "version": "1.1.0", + "version": "1.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "arbeidspuls", - "version": "1.1.0", + "version": "1.1.1", "dependencies": { "lucide-react": "^0.475.0", "react": "^19.0.0", diff --git a/package.json b/package.json index e7ab4f1..27b50f9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "arbeidspuls", - "version": "1.1.0", + "version": "1.1.1", "private": true, "type": "module", "scripts": { diff --git a/public/service-worker.js b/public/service-worker.js index 92c7e58..4125b7d 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -1,4 +1,4 @@ -const CACHE_NAME = "arbeidspuls-v5"; +const CACHE_NAME = "arbeidspuls-v6"; const ASSETS = ["/", "/index.html", "/manifest.webmanifest", "/icon.svg"]; self.addEventListener("install", (event) => { diff --git a/src/App.tsx b/src/App.tsx index 9064dcb..cf16ac7 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -37,6 +37,7 @@ import { import { deleteEvaluatorNote, deleteEvaluatorProfile, + ensureDemoEvaluatorProfiles, exportEvaluatorBackup, importEvaluatorBackup, importExportPayload, @@ -153,20 +154,14 @@ function AppRoutes() { return (
- {demoMode && changeDemoMode(false)} />}

{t("app.eyebrow")}

{t("app.title")}

-
- - - {t("app.evaluator")} - -
+ + {t("app.evaluator")} +
); @@ -1687,8 +1671,9 @@ function PrivacyPanel({ demoMode, entries, onDeleted }: { demoMode: boolean; ent function EvaluatorApp() { const { labels, t } = useI18n(); - const [profiles, setProfiles] = useState(() => listEvaluatorProfiles()); - const [selectedId, setSelectedId] = useState(() => listEvaluatorProfiles()[0]?.id ?? null); + const [demoMode, setDemoMode] = useState(() => isDemoModeEnabled()); + const [profiles, setProfiles] = useState(() => listEvaluatorProfiles(isDemoModeEnabled())); + const [selectedId, setSelectedId] = useState(() => listEvaluatorProfiles(isDemoModeEnabled())[0]?.id ?? null); const [displayName, setDisplayName] = useState(""); const [targetId, setTargetId] = useState(""); const [message, setMessage] = useState(""); @@ -1705,10 +1690,27 @@ function EvaluatorApp() { if (selected) setPersonNote(getPersonNote(selected)?.text ?? ""); }, [selected?.id]); - const refresh = (nextSelectedId?: string) => { - const next = listEvaluatorProfiles(); + useEffect(() => { + const next = listEvaluatorProfiles(demoMode); setProfiles(next); - if (nextSelectedId) setSelectedId(nextSelectedId); + setSelectedId((current) => (current && next.some((profile) => profile.id === current) ? current : next[0]?.id ?? null)); + setSelectedEntryId(null); + }, [demoMode]); + + const refresh = (nextSelectedId?: string) => { + const next = listEvaluatorProfiles(demoMode); + setProfiles(next); + setSelectedId((current) => nextSelectedId ?? (current && next.some((profile) => profile.id === current) ? current : next[0]?.id ?? null)); + }; + + const changeDemoMode = (enabled: boolean) => { + setDemoModeEnabled(enabled); + if (enabled) { + ensureDemoData(); + ensureDemoEvaluatorProfiles(); + } + setDemoMode(enabled); + setMessage(enabled ? t("demo.notice") : ""); }; const importFile = async (event: React.ChangeEvent) => { @@ -1716,7 +1718,7 @@ function EvaluatorApp() { if (!file) return; try { - const result = importExportPayload(await file.text(), displayName, targetId || undefined); + const result = importExportPayload(await file.text(), displayName, targetId || undefined, demoMode); const flagged = result.verification.modified + result.verification.missing + result.verification.unsupported + result.verification.error; const actionText = result.target_resolution === "matched_existing_person" @@ -1739,8 +1741,10 @@ function EvaluatorApp() { const file = event.target.files?.[0]; if (!file) return; try { - importEvaluatorBackup(await file.text()); - setMessage(t("evaluator.importedBackup")); + const text = await file.text(); + const demoEntries = countEvaluatorBackupDemoEntries(text); + importEvaluatorBackup(text, demoMode); + setMessage(`${t("evaluator.importedBackup")}${demoEntries > 0 ? ` ADVARSEL: Backupen inneholder ${demoEntries} demodata-oppføringer.` : ""}`); refresh(); } catch (error) { setMessage(error instanceof Error ? error.message : "Backup kunne ikke importeres."); @@ -1750,32 +1754,32 @@ function EvaluatorApp() { }; const downloadBackup = () => { - const blob = new Blob([exportEvaluatorBackup()], { type: "application/json" }); + const blob = new Blob([exportEvaluatorBackup(demoMode)], { type: "application/json" }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; - link.download = `arbeidsevne-veilederdata-${new Date().toISOString().slice(0, 10)}.json`; + link.download = `${demoMode ? "arbeidspuls-demo" : "arbeidspuls"}-veilederdata-${new Date().toISOString().slice(0, 10)}.json`; link.click(); URL.revokeObjectURL(url); }; const saveName = () => { if (!selected) return; - renameEvaluatorProfile(selected.id, selected.display_name); + renameEvaluatorProfile(selected.id, selected.display_name, demoMode); refresh(selected.id); }; const savePersonNote = () => { if (!selected) return; - upsertEvaluatorNote(selected.id, personNote, { note_type: "person" }); + upsertEvaluatorNote(selected.id, personNote, { note_type: "person" }, demoMode); setPersonNote(""); refresh(selected.id); }; const deleteSelectedPerson = () => { if (!deletePersonRequest) return; - deleteEvaluatorProfile(deletePersonRequest.id); - const remaining = listEvaluatorProfiles(); + deleteEvaluatorProfile(deletePersonRequest.id, demoMode); + const remaining = listEvaluatorProfiles(demoMode); setProfiles(remaining); setSelectedId(remaining[0]?.id ?? null); setSelectedEntryId(null); @@ -1801,7 +1805,7 @@ function EvaluatorApp() { const followupsByParent = useMemo(() => (selected ? groupFollowups(selected.reports) : new Map()), [selected]); return ( -
+

{t("evaluator.eyebrow")}

@@ -1812,6 +1816,13 @@ function EvaluatorApp() {
+ {demoMode && ( +
+
+ )} +
@@ -1968,6 +1979,7 @@ function EvaluatorApp() { followups={selectedEntry.type === "work_report" ? followupsByParent.get(selectedEntry.id) ?? [] : []} notes={selected.evaluator_notes} profileId={selected.id} + demoMode={demoMode} onSaved={() => refresh(selected.id)} onClose={() => setSelectedEntryId(null)} /> @@ -1982,7 +1994,7 @@ function EvaluatorApp() { )}
- + total + (profile.contains_demo_data ? 1 : 0) + profile.reports.filter((entry) => entry.demo || entry.id.startsWith("demo-arbeidspuls")).length, + 0 + ); + return parsed.data_mode === "demo" && count === 0 ? 1 : count; + } catch { + return 0; + } +} + function ReviewReportCard({ report, followups, @@ -2130,6 +2156,7 @@ function ReviewDetailPane({ followups, notes, profileId, + demoMode, onSaved, onClose }: { @@ -2137,6 +2164,7 @@ function ReviewDetailPane({ followups: DelayedFollowup[]; notes: EvaluatorProfile["evaluator_notes"]; profileId: string; + demoMode: boolean; onSaved: () => void; onClose: () => void; }) { @@ -2151,22 +2179,22 @@ function ReviewDetailPane({ const save = () => { if (entry.type === "work_report") { - upsertEvaluatorNote(profileId, noteText, { note_type: "report", related_report_id: entry.id }); + upsertEvaluatorNote(profileId, noteText, { note_type: "report", related_report_id: entry.id }, demoMode); } else { upsertEvaluatorNote(profileId, noteText, { note_type: "followup", related_followup_id: entry.id, parent_report_id: findParentId(entry) - }); + }, demoMode); } onSaved(); }; const remove = () => { if (entry.type === "work_report") { - deleteEvaluatorNote(profileId, { note_type: "report", related_report_id: entry.id }); + deleteEvaluatorNote(profileId, { note_type: "report", related_report_id: entry.id }, demoMode); } else { - deleteEvaluatorNote(profileId, { note_type: "followup", related_followup_id: entry.id }); + deleteEvaluatorNote(profileId, { note_type: "followup", related_followup_id: entry.id }, demoMode); } setNoteText(""); onSaved(); diff --git a/src/demoData.ts b/src/demoData.ts index a2f2269..a374be5 100644 --- a/src/demoData.ts +++ b/src/demoData.ts @@ -1,4 +1,4 @@ -import type { StoredEntry } from "./types"; +import type { EvaluatorProfile, StoredEntry } from "./types"; export const demoEntries: StoredEntry[] = [ { @@ -182,3 +182,154 @@ export const demoEntries: StoredEntry[] = [ } } ]; + +const sharedDemoProfiles = { + created_at: "2026-05-28T08:00:00.000Z", + updated_at: "2026-05-28T08:00:00.000Z", + contains_demo_data: true, + evaluator_notes: [] +}; + +export const demoEvaluatorProfiles: EvaluatorProfile[] = [ + { + ...sharedDemoProfiles, + id: "demo-advisor-profile-01", + display_name: "Demo: Nora Hjemmekontor", + source_fingerprint: "demo-nora", + reports: demoEntries + }, + { + ...sharedDemoProfiles, + id: "demo-advisor-profile-02", + display_name: "Demo: Amir Kontor", + source_fingerprint: "demo-amir", + reports: [ + { + id: "demo-arbeidspuls-amir-001", + created_at: "2026-05-18T07:20:00.000Z", + report_date: "2026-05-18", + report_time: "09:20:00", + type: "work_report", + workplace: "Kontor", + work_start_time: "08:30", + work_end_time: "11:30", + work_ability: 3, + energy_level: 3, + mental_clarity: 4, + symptom_burden: 2, + effort_strain: 3, + status: "kan_fortsette", + physical_energy: 3, + mental_energy: 4, + perceived_productivity: 3, + task_completion: "delvis", + task_types: ["sitting", "computer_work", "time_pressure"], + main_limitations: ["fatigue"], + helpful_accommodations: ["extra_breaks"], + note: "Testdata: moderat dag med noen pauser.", + total_score_percent: 68, + demo: true + }, + { + id: "demo-arbeidspuls-amir-002", + created_at: "2026-05-23T10:45:00.000Z", + report_date: "2026-05-23", + report_time: "12:45:00", + type: "work_report", + workplace: "Kontor", + work_start_time: "10:00", + work_end_time: "12:30", + work_ability: 2, + energy_level: 2, + mental_clarity: 2, + symptom_burden: 4, + effort_strain: 5, + status: "trenger_pause", + physical_energy: 2, + mental_energy: 2, + perceived_productivity: 2, + task_completion: "litt", + task_types: ["sitting", "talking_social", "noise"], + main_limitations: ["fatigue", "sensory_overload", "brain_fog"], + helpful_accommodations: ["quiet_room", "shorter_task"], + note: "Testdata: støy og sosial belastning ga tydelig fall.", + total_score_percent: 24, + demo: true + }, + { + id: "demo-arbeidspuls-amir-followup-002", + created_at: "2026-05-24T08:30:00.000Z", + report_date: "2026-05-24", + report_time: "10:30:00", + type: "delayed_followup", + parent_report_id: "demo-arbeidspuls-amir-002", + related_report_id: "demo-arbeidspuls-amir-002", + worse_than_before: "moderat", + delayed_symptoms: ["fatigue", "brain_fog", "headache"], + recovery_status: "to_tre_dager", + note: "Testdata: oppfølging etter krevende kontordag.", + demo: true + } + ] + }, + { + ...sharedDemoProfiles, + id: "demo-advisor-profile-03", + display_name: "Demo: Liv Butikk", + source_fingerprint: "demo-liv", + reports: [ + { + id: "demo-arbeidspuls-liv-001", + created_at: "2026-05-19T12:15:00.000Z", + report_date: "2026-05-19", + report_time: "14:15:00", + type: "work_report", + workplace: "Butikk", + work_start_time: "12:00", + work_end_time: "14:00", + work_ability: 3, + energy_level: 3, + mental_clarity: 3, + symptom_burden: 3, + effort_strain: 3, + status: "trenger_enklere_oppgave", + physical_energy: 3, + mental_energy: 3, + perceived_productivity: 3, + task_completion: "delvis", + task_types: ["standing", "walking", "talking_social"], + main_limitations: ["pain", "fatigue"], + helpful_accommodations: ["sitting_down", "help_from_others"], + note: "Testdata: fysisk aktivitet krevde tilrettelegging.", + total_score_percent: 50, + demo: true + }, + { + id: "demo-arbeidspuls-liv-002", + created_at: "2026-05-26T07:50:00.000Z", + report_date: "2026-05-26", + report_time: "09:50:00", + type: "work_report", + workplace: "Butikk", + work_start_time: "09:00", + work_end_time: "09:45", + work_ability: 4, + energy_level: 4, + mental_clarity: 4, + symptom_burden: 2, + effort_strain: 2, + status: "kan_fortsette", + physical_energy: 4, + mental_energy: 4, + perceived_productivity: 4, + task_completion: "som_forventet", + task_types: ["standing", "walking"], + main_limitations: [], + helpful_accommodations: ["shorter_task", "sitting_down"], + note: "Testdata: kortere oppgave fungerte bedre.", + total_score_percent: 78, + demo: true + } + ] + } +]; diff --git a/src/evaluatorStorage.test.ts b/src/evaluatorStorage.test.ts index dad14c8..d75014e 100644 --- a/src/evaluatorStorage.test.ts +++ b/src/evaluatorStorage.test.ts @@ -123,4 +123,12 @@ describe("evaluator-import", () => { expect(result.profile.contains_demo_data).toBe(true); expect(listEvaluatorProfiles()[0].contains_demo_data).toBe(true); }); + + it("holder veilederens demobrukere separat fra normale brukere", () => { + const demoProfiles = listEvaluatorProfiles(true); + + expect(demoProfiles.length).toBeGreaterThanOrEqual(3); + expect(demoProfiles.every((profile) => profile.contains_demo_data)).toBe(true); + expect(listEvaluatorProfiles()).toHaveLength(0); + }); }); diff --git a/src/evaluatorStorage.ts b/src/evaluatorStorage.ts index 9aaa11e..f04add0 100644 --- a/src/evaluatorStorage.ts +++ b/src/evaluatorStorage.ts @@ -1,8 +1,11 @@ import { calculateReportScore, getEntryTime } from "./scoring"; import { summarizeVerification, verifyEntryIntegrity } from "./integrity"; +import { demoEvaluatorProfiles } from "./demoData"; import type { EvaluatorNote, EvaluatorProfile, ExportPayload, ImportTargetResolution, Report, StoredEntry } from "./types"; const EVALUATOR_KEY = "arbeidsevne-egenvurdering:evaluator-profiles"; +const DEMO_EVALUATOR_KEY = "arbeidspuls:demo-evaluator-profiles"; +const DEMO_EVALUATOR_SEEDED_KEY = "arbeidspuls:demo-evaluator-seeded"; export type ImportResult = { profile: EvaluatorProfile; @@ -15,8 +18,22 @@ export type ImportResult = { matched_report_ids: string[]; }; -function readProfiles(): EvaluatorProfile[] { - const raw = localStorage.getItem(EVALUATOR_KEY); +function evaluatorKey(demoMode = false) { + return demoMode ? DEMO_EVALUATOR_KEY : EVALUATOR_KEY; +} + +export function ensureDemoEvaluatorProfiles() { + const existing = readProfiles(true); + if (existing.length > 0) return existing; + if (localStorage.getItem(DEMO_EVALUATOR_SEEDED_KEY) === "true") return []; + const seeded = demoEvaluatorProfiles.map(normalizeProfile); + writeProfiles(seeded, true); + localStorage.setItem(DEMO_EVALUATOR_SEEDED_KEY, "true"); + return seeded; +} + +function readProfiles(demoMode = false): EvaluatorProfile[] { + const raw = localStorage.getItem(evaluatorKey(demoMode)); if (!raw) return []; try { @@ -27,32 +44,33 @@ function readProfiles(): EvaluatorProfile[] { } } -function writeProfiles(profiles: EvaluatorProfile[]) { - localStorage.setItem(EVALUATOR_KEY, JSON.stringify(profiles)); +function writeProfiles(profiles: EvaluatorProfile[], demoMode = false) { + localStorage.setItem(evaluatorKey(demoMode), JSON.stringify(profiles)); } -export function listEvaluatorProfiles() { - return readProfiles().map(normalizeProfile).sort((a, b) => b.updated_at.localeCompare(a.updated_at)); +export function listEvaluatorProfiles(demoMode = false) { + if (demoMode) ensureDemoEvaluatorProfiles(); + return readProfiles(demoMode).map(normalizeProfile).sort((a, b) => b.updated_at.localeCompare(a.updated_at)); } -export function updateEvaluatorProfile(profile: EvaluatorProfile) { - const profiles = readProfiles(); +export function updateEvaluatorProfile(profile: EvaluatorProfile, demoMode = false) { + const profiles = readProfiles(demoMode); const next = profiles.some((item) => item.id === profile.id) ? profiles.map((item) => (item.id === profile.id ? profile : item)) : [profile, ...profiles]; - writeProfiles(next); + writeProfiles(next, demoMode); } -export function renameEvaluatorProfile(profileId: string, displayName: string) { +export function renameEvaluatorProfile(profileId: string, displayName: string, demoMode = false) { const now = new Date().toISOString(); - const profiles = readProfiles().map((profile) => + const profiles = readProfiles(demoMode).map((profile) => profile.id === profileId ? { ...profile, display_name: displayName.trim() || profile.display_name, updated_at: now } : profile ); - writeProfiles(profiles); + writeProfiles(profiles, demoMode); } -export function deleteEvaluatorProfile(profileId: string) { - writeProfiles(readProfiles().filter((profile) => profile.id !== profileId)); +export function deleteEvaluatorProfile(profileId: string, demoMode = false) { + writeProfiles(readProfiles(demoMode).filter((profile) => profile.id !== profileId), demoMode); } export function addEvaluatorNote(profileId: string, text: string, relatedReportId?: string) { @@ -62,22 +80,24 @@ export function addEvaluatorNote(profileId: string, text: string, relatedReportI export function upsertEvaluatorNote( profileId: string, text: string, - noteTarget: Pick + noteTarget: Pick, + demoMode = false ) { const now = new Date().toISOString(); const trimmed = text.trim(); - const profiles = readProfiles().map((profile) => + const profiles = readProfiles(demoMode).map((profile) => profile.id === profileId ? upsertNoteInProfile(normalizeProfile(profile), trimmed, noteTarget, now) : profile ); - writeProfiles(profiles); + writeProfiles(profiles, demoMode); } export function deleteEvaluatorNote( profileId: string, - noteTarget: Pick + noteTarget: Pick, + demoMode = false ) { const now = new Date().toISOString(); - const profiles = readProfiles().map((profile) => + const profiles = readProfiles(demoMode).map((profile) => profile.id === profileId ? { ...normalizeProfile(profile), @@ -86,14 +106,14 @@ export function deleteEvaluatorNote( } : profile ); - writeProfiles(profiles); + writeProfiles(profiles, demoMode); } -export function importExportPayload(jsonText: string, displayName: string, targetProfileId?: string): ImportResult { +export function importExportPayload(jsonText: string, displayName: string, targetProfileId?: string, demoMode = false): ImportResult { const parsed = JSON.parse(jsonText); const reports = extractReports(parsed).map(verifyEntryIntegrity); const demoEntriesDetected = countDemoEntries(reports, parsed); - const profiles = readProfiles().map(normalizeProfile); + const profiles = readProfiles(demoMode).map(normalizeProfile); const fingerprint = makeFingerprint(reports); const target = resolveImportTarget(reports, profiles, targetProfileId); if (target.action === "conflict_requires_manual_resolution") { @@ -141,7 +161,7 @@ export function importExportPayload(jsonText: string, displayName: string, targe evaluator_notes: [] }; - updateEvaluatorProfile(profile); + updateEvaluatorProfile(profile, demoMode); return { profile, added, @@ -154,25 +174,26 @@ export function importExportPayload(jsonText: string, displayName: string, targe }; } -export function exportEvaluatorBackup() { +export function exportEvaluatorBackup(demoMode = false) { return JSON.stringify( { backup_type: "arbeidsevne-veilederdata", schema_version: 1, exported_at: new Date().toISOString(), - profiles: listEvaluatorProfiles() + data_mode: demoMode ? "demo" : "normal", + profiles: listEvaluatorProfiles(demoMode) }, null, 2 ); } -export function importEvaluatorBackup(jsonText: string) { +export function importEvaluatorBackup(jsonText: string, demoMode = false) { const parsed = JSON.parse(jsonText); if (parsed?.backup_type !== "arbeidsevne-veilederdata" || !Array.isArray(parsed.profiles)) { throw new Error("Filen er ikke en gyldig veilederbackup."); } - const existing = readProfiles(); + const existing = readProfiles(demoMode); const merged = [...existing]; for (const profile of parsed.profiles as EvaluatorProfile[]) { const index = merged.findIndex((item) => item.id === profile.id); @@ -186,7 +207,7 @@ export function importEvaluatorBackup(jsonText: string) { merged.push(normalizeProfile(profile)); } } - writeProfiles(merged); + writeProfiles(merged, demoMode); } export function extractReports(payload: ExportPayload | StoredEntry[] | { reports?: StoredEntry[] }) { diff --git a/src/i18n.tsx b/src/i18n.tsx index a1f23fe..d46276a 100644 --- a/src/i18n.tsx +++ b/src/i18n.tsx @@ -334,7 +334,7 @@ const en: TranslationTree = { notCalculated: "Not calculated", unknownWorkplace: "Unknown workplace" }, - app: { eyebrow: "Local self-assessment", title: "Arbeidspuls", evaluator: "Guide", navLabel: "Main navigation" }, + app: { eyebrow: "Local self-assessment", title: "Arbeidspuls", evaluator: "Advisor", navLabel: "Main navigation" }, nav: { register: "Register", followup: "Follow-up", overview: "Overview", privacy: "Privacy" }, demo: { enter: "Demo", @@ -442,9 +442,9 @@ const en: TranslationTree = { backupTitle: "Backup and device changes", backupBody: "Because the data is stored locally, it may be lost if you change device, delete browser data, use another browser or reset the device. Export a JSON file regularly if you want to keep the reports.", - sharingTitle: "Sharing with a guide", + sharingTitle: "Sharing with an advisor", sharingBody: - "If a guide or another person will review the reports, export a JSON file and share it with them, for example by email. The recipient can import the JSON file in the guide view.", + "If an advisor or another person will review the reports, export a JSON file and share it with them, for example by email. The recipient can import the JSON file in the advisor view.", sensitiveTitle: "Sensitive information", sensitiveBody: "Reports may contain health and function-related information. Share the JSON file in a way suitable for sensitive information, and only with people who should have access.", @@ -452,8 +452,8 @@ const en: TranslationTree = { deleteAllName: "all local reports" }, evaluator: { - eyebrow: "Guide", - title: "Guide review", + eyebrow: "Advisor", + title: "Advisor review", backToApp: "Back to app", importJson: "Import JSON", importHelp: "Choose an export from the app. Data is stored locally in this browser.", @@ -462,14 +462,14 @@ const en: TranslationTree = { connectExisting: "Connect to existing person", autoMatch: "Create new or use automatic match", chooseJson: "Choose JSON file", - exportBackup: "Export guide data", - importBackup: "Import guide backup", - importedBackup: "Guide backup has been imported.", + exportBackup: "Export advisor data", + importBackup: "Import advisor backup", + importedBackup: "Advisor backup has been imported.", demoDataWarning: "This import contains demo data. Do not use it as real user or health information.", importStart: "Import a JSON file to start reviewing.", saveName: "Save name", deletePerson: "Delete user and reports", - deletePersonTitle: "Deletes this user, imported reports, follow-ups and guide notes after confirmation.", + deletePersonTitle: "Deletes this user, imported reports, follow-ups and advisor notes after confirmation.", reportCount: "{count} reports", personNote: "General note for user", personNoteHelp: "This note applies to the person as a whole, not one specific report.", @@ -486,15 +486,15 @@ const en: TranslationTree = { closeDetails: "Close details", reportNote: "Report note", followupNote: "Follow-up note", - noteHelp: "The guide note is stored locally and does not change imported report data.", + noteHelp: "The advisor note is stored locally and does not change imported report data.", deleteNote: "Delete note", validationError: "Validation error", comment: "Comment", - evaluatorNote: "Guide note", + evaluatorNote: "Advisor note", followupNoteBadge: "Follow-up note", hasFollowup: "Has follow-up", hasComment: "Has user comment", - hasEvaluatorNote: "Has guide note", + hasEvaluatorNote: "Has advisor note", scoreValues: "Score values" }, details: { diff --git a/src/styles.css b/src/styles.css index 0151119..cdee224 100644 --- a/src/styles.css +++ b/src/styles.css @@ -51,14 +51,6 @@ button { padding: 10px 2px 14px; } -.topbar-actions { - align-items: center; - display: flex; - flex-wrap: wrap; - gap: 0.5rem; - justify-content: flex-end; -} - .topbar h1, .field-head h2, .filters h2, @@ -688,47 +680,6 @@ textarea { white-space: nowrap; } -.text-link.active { - background: #235b5e; - border-color: #235b5e; - color: #fff; -} - -.demo-banner { - align-items: center; - background: #1f4f51; - border: 1px solid rgba(255, 255, 255, 0.4); - border-radius: 999px; - box-shadow: 0 12px 30px rgba(31, 79, 81, 0.22); - color: #fff; - display: inline-flex; - font-size: 0.78rem; - font-weight: 950; - gap: 0.5rem; - letter-spacing: 0; - padding: 0.42rem 0.46rem 0.42rem 0.74rem; - position: fixed; - right: max(12px, env(safe-area-inset-right)); - top: max(12px, env(safe-area-inset-top)); - z-index: 40; -} - -.demo-banner button { - align-items: center; - background: rgba(255, 255, 255, 0.16); - border: 1px solid rgba(255, 255, 255, 0.42); - border-radius: 999px; - color: #fff; - cursor: pointer; - display: inline-flex; - font: inherit; - height: 1.35rem; - justify-content: center; - line-height: 1; - padding: 0; - width: 1.35rem; -} - .demo-meta-pill { background: #fbecd7; color: #7d4118; @@ -1633,7 +1584,10 @@ textarea { border: 1px solid #ccd7d4; border-radius: 999px; color: #235b5e; + cursor: pointer; display: inline-flex; + font: inherit; + font-weight: 850; gap: 0.4rem; justify-content: center; padding: 0.44rem 0.7rem; @@ -1653,6 +1607,19 @@ textarea { transform: translateY(-1px); } +.footer-link.active { + background: #235b5e; + border-color: #235b5e; + color: #fff; +} + +.footer-link.active:hover, +.footer-link.active:focus-visible { + background: #1f4f51; + border-color: #1f4f51; + color: #fff; +} + .app-footer svg { width: 14px; height: 14px;