- {format(new Date(daySessions[0].session_date + "T12:00:00"), "EEEE d. MMMM yyyy", { locale: nb })}
-
-
- {[...daySessions].sort((a, b) => {
- if (!muscleFilter.length) return 0;
- const aMatch = muscleFilter.some(id => sessionMuscleIds(a).has(id));
- const bMatch = muscleFilter.some(id => sessionMuscleIds(b).has(id));
- return aMatch === bMatch ? 0 : aMatch ? -1 : 1;
- }).map((session) => {
- const isEditing = editMode && selectedSession?.id === session.id;
- const isExpanded = expandedIds.has(session.id);
- const sessionMuscles = isEditing ? editMuscles : extractMuscles(session);
- const sessionMuscleMap = isEditing ? buildMuscleMapFromExercises(editExercises) : buildMuscleMapFromSession(session);
- const exCount = (session.session_exercises || []).filter(e => e.name).length;
- const musIds = sessionMuscleIds(session);
- const isFilterMatch = muscleFilter.length > 0 && muscleFilter.some(id => musIds.has(id));
- const matchedLabels = isFilterMatch
- ? muscleFilter.filter(id => musIds.has(id)).map(id => MUSCLES[id]?.label || id)
- : [];
- const topMuscles = extractMuscles(session).primary.slice(0, 2).map(id => MUSCLES[id]?.label || id);
- const sessionTime = session.gym_calendar?.start_time
- ? new Date(session.gym_calendar.start_time).toLocaleTimeString("no-NO", { hour: "2-digit", minute: "2-digit" })
- : new Date(session.created_at).toLocaleTimeString("no-NO", { hour: "2-digit", minute: "2-digit" });
- const sessionTitle = session.gym_calendar
- ? `${sessionTime} – ${session.gym_calendar.name}`
- : `${sessionTime} – Egentrening`;
-
- return (
-
+ {loadingSession && (
+
+ )}
- {/* Gym class tag (read) or selector (edit) */}
- {isEditing ? (
- editGymSessions.length > 0 && (
- <>
-
- {editGymCalendarConflict && (
-
- )}
- >
- )
- ) : (
- session.gym_calendar && (
-
- {session.gym_calendar.name}
-
- )
- )}
-
- {/* Body map */}
-
-
-
- {hoveredMuscle ? (
-
-
- {MUSCLES[hoveredMuscle]?.label}
-
-
-
-
- {(sessionMuscleMap[hoveredMuscle] || []).length}
-
-
- {(sessionMuscleMap[hoveredMuscle] || []).length === 1 ? "ØVELSE" : "ØVELSER"}
-
-
-
- {(sessionMuscleMap[hoveredMuscle] || []).join(" · ")}
-
-
-
- ) : (
-
- Hold musepeker over kroppen for detaljer
-
- )}
-
+ {daySessions.length > 0 && (
+
+
+ {format(new Date(daySessions[0].session_date + "T12:00:00"), "EEEE d. MMMM yyyy", { locale: nb })}
+
-
-
Primær ({sessionMuscles.primary.length})
-
Sekundær ({sessionMuscles.secondary.length})
+ {[...daySessions].sort((a, b) => {
+ if (!muscleFilter.length) return 0;
+ const aMatch = muscleFilter.some(id => sessionMuscleIds(a).has(id));
+ const bMatch = muscleFilter.some(id => sessionMuscleIds(b).has(id));
+ return aMatch === bMatch ? 0 : aMatch ? -1 : 1;
+ }).map((session) => {
+ const isEditing = editMode && selectedSession?.id === session.id;
+ const isExpanded = expandedIds.has(session.id);
+ const sessionMuscles = isEditing ? editMuscles : extractMuscles(session);
+ const sessionMuscleMap = isEditing ? buildMuscleMapFromExercises(editExercises) : buildMuscleMapFromSession(session);
+ const exCount = (session.session_exercises || []).filter(e => e.name).length;
+ const musIds = sessionMuscleIds(session);
+ const isFilterMatch = muscleFilter.length > 0 && muscleFilter.some(id => musIds.has(id));
+ const matchedLabels = isFilterMatch
+ ? muscleFilter.filter(id => musIds.has(id)).map(id => MUSCLES[id]?.label || id)
+ : [];
+ const topMuscles = extractMuscles(session).primary.slice(0, 2).map(id => MUSCLES[id]?.label || id);
+ const sessionTime = session.gym_calendar?.start_time
+ ? new Date(session.gym_calendar.start_time).toLocaleTimeString("no-NO", { hour: "2-digit", minute: "2-digit" })
+ : new Date(session.created_at).toLocaleTimeString("no-NO", { hour: "2-digit", minute: "2-digit" });
+ const sessionTitle = session.gym_calendar
+ ? `${sessionTime} – ${session.gym_calendar.name}`
+ : `${sessionTime} – Egentrening`;
+
+ return (
+
0 && !isFilterMatch ? 0.45 : 1 }}>
+
- {/* Exercise list */}
-
-
- Øvelser
-
+ {isExpanded && (
+
+ {/* Gym class tag (read) or selector (edit) */}
{isEditing ? (
- <>
-
- {editExercises.map((ex) => (
- setEditExercises(p => p.map(e => e.id === ex.id ? { ...e, ...updates } : e))}
- onDelete={() => setEditExercises(p => p.filter(e => e.id !== ex.id))}
- layer="layer-02"
- validateNumbers
- libraryExercises={libraryExercises}
- isNew={newExerciseIds.has(ex.id)}
+ editGymSessions.length > 0 && (
+ <>
+
+ {editGymCalendarConflict && (
+
- ))}
-
-
- >
+ )}
+ >
+ )
) : (
- (session.session_exercises || []).map(ex => {
- const muscleLabels = (ex.muscle_activations || []).map(ma => MUSCLES[ma.muscle_id]?.label || ma.muscle_id).join(", ");
- return (
-
-
- {muscleLabels ? (
- {ex.name}
- ) : ex.name}
-
- {(ex.sets || ex.reps) && (
-
- {[ex.sets && `${ex.sets}×`, ex.reps].filter(Boolean).join("")}
-
- )}
-
- );
- })
+ session.gym_calendar && (
+
+ {session.gym_calendar.name}
+
+ )
)}
-
- {/* Muscle groups (read mode only) */}
- {!isEditing && (
-
-
- Muskelgrupper
-
- {sessionMuscles.primary.map(id => {
- const exNames = (sessionMuscleMap[id] || []).join(", ");
- return (
-
-
-
- {exNames ? (
- {MUSCLES[id]?.label || id}
- ) : MUSCLES[id]?.label || id}
-
-
Primær
+ {/* Body map */}
+
+
+
+ {hoveredMuscle ? (
+
+
+ {MUSCLES[hoveredMuscle]?.label}
- );
- })}
- {sessionMuscles.secondary.map(id => {
- const exNames = (sessionMuscleMap[id] || []).join(", ");
- return (
-
-
-
- {exNames ? (
- {MUSCLES[id]?.label || id}
- ) : MUSCLES[id]?.label || id}
+
+
+
+ {(sessionMuscleMap[hoveredMuscle] || []).length}
+
+
+ {(sessionMuscleMap[hoveredMuscle] || []).length === 1 ? "ØVELSE" : "ØVELSER"}
+
+
+
+ {(sessionMuscleMap[hoveredMuscle] || []).join(" · ")}
-
Sekundær
- );
- })}
+
+ ) : (
+
+ Hold musepeker over kroppen for detaljer
+
+ )}
- )}
- {/* Edit mode actions */}
- {isEditing && (
- <>
- {analyzeError && (
-
- )}
- {editError && (
-
+
+ Primær ({sessionMuscles.primary.length})
+ Sekundær ({sessionMuscles.secondary.length})
+
+
+ {/* Exercise list */}
+
+
+ Øvelser
+
+
+ {isEditing ? (
+ <>
+
+ {editExercises.map((ex) => (
+ setEditExercises(p => p.map(e => e.id === ex.id ? { ...e, ...updates } : e))}
+ onDelete={() => setEditExercises(p => p.filter(e => e.id !== ex.id))}
+ layer="layer-02"
+ validateNumbers
+ libraryExercises={libraryExercises}
+ isNew={newExerciseIds.has(ex.id)}
+ />
+ ))}
+
+
+ >
+ ) : (
+ (session.session_exercises || []).map(ex => {
+ const muscleLabels = (ex.muscle_activations || []).map(ma => MUSCLES[ma.muscle_id]?.label || ma.muscle_id).join(", ");
+ return (
+
+
+ {muscleLabels ? (
+ {ex.name}
+ ) : ex.name}
+
+ {(ex.sets || ex.reps) && (
+
+ {[ex.sets && `${ex.sets}×`, ex.reps].filter(Boolean).join("")}
+
+ )}
+
+ );
+ })
)}
-
{ if (e.target.files[0]) reanalyze(e.target.files[0]); e.target.value = ""; }} />
-
-
-
-
+
+
+ {/* Muscle groups (read mode only) */}
+ {!isEditing && (
+
+
+ Muskelgrupper
+
+ {sessionMuscles.primary.map(id => {
+ const exNames = (sessionMuscleMap[id] || []).join(", ");
+ return (
+
+
+
+ {exNames ? (
+ {MUSCLES[id]?.label || id}
+ ) : MUSCLES[id]?.label || id}
+
+
Primær
+
+ );
+ })}
+ {sessionMuscles.secondary.map(id => {
+ const exNames = (sessionMuscleMap[id] || []).join(", ");
+ return (
+
+
+
+ {exNames ? (
+ {MUSCLES[id]?.label || id}
+ ) : MUSCLES[id]?.label || id}
+
+
Sekundær
+
+ );
+ })}
- >
- )}
-
- {/* Read mode: edit button (hidden when any session is in edit mode) */}
- {!editMode && (
-
- )}
-
+ )}
+
+ {/* Edit mode actions */}
+ {isEditing && (
+ <>
+ {analyzeError && (
+
+ )}
+ {editError && (
+
+ )}
+
{ if (e.target.files[0]) reanalyze(e.target.files[0]); e.target.value = ""; }} />
+
+
+
+
+
+ >
+ )}
+
+ {/* Read mode: edit button (hidden when any session is in edit mode) */}
+ {!editMode && (
+
+ )}
+
)}
- );
- })}
-
- )}
+ );
+ })}
+
+ )}
- {!loading && sessions.length === 0 && (
-
- Ingen økter lagret ennå.
-
- )}
+ {!loading && sessions.length === 0 && (
+
+ Ingen økter lagret ennå.
+
+ )}
-
+
);
}
diff --git a/app/src/components/Report.jsx b/app/src/components/Report.jsx
index d229372..262e3d4 100644
--- a/app/src/components/Report.jsx
+++ b/app/src/components/Report.jsx
@@ -1,4 +1,4 @@
-import { useState, useEffect, useMemo } from "react";
+import { useState, useEffect, useRef, useMemo } from "react";
import { subDays, format } from "date-fns";
import { nb } from "date-fns/locale";
import { fetchSessionsForReport } from "../lib/db";
@@ -8,8 +8,9 @@ import { CLAUDE_MODEL_TEXT, buildPeriodRecommendPrompt } from "../lib/prompts";
import {
Tag, InlineLoading, DefinitionTooltip, Button, InlineNotification,
} from "@carbon/react";
-import { AiGenerate } from "@carbon/icons-react";
-import PageShell, { SectionLabel, PageHeading } from "./PageShell";
+import { AiGenerate, Add } from "@carbon/icons-react";
+import PageShell, { SectionLabel, AccentChip, StickyCta } from "./PageShell";
+import { useNav } from "../lib/NavContext";
const PERIODS = [
{ label: "7 dager", days: 7 },
@@ -32,17 +33,19 @@ function FilterChip({ label, active, onClick }) {