Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions app/src/components/BodyPanel.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { BodySVG, useIsMobile } from "../lib/bodymap.jsx";

// Renders a front+back body map pair: side-by-side on desktop, toggled on mobile.
// Manages its own mobile view state so parents don't need to.
export default function BodyPanel({ primary, secondary, muscleMap, marginBottom = 16 }) {
export default function BodyPanel({ primary, secondary, muscleMap, marginBottom = 16, onHover, hovered }) {
const isMobile = useIsMobile();
const [mobileView, setMobileView] = useState("front");

Expand All @@ -20,7 +20,7 @@ export default function BodyPanel({ primary, secondary, muscleMap, marginBottom
))}
</div>
<div style={{ maxWidth: 240, margin: "0 auto", background: "var(--cds-layer-01)", border: "1px solid var(--cds-border-subtle-01)", padding: "10px 6px" }}>
<BodySVG view={mobileView} primary={primary} secondary={secondary} muscleMap={muscleMap} />
<BodySVG view={mobileView} primary={primary} secondary={secondary} muscleMap={muscleMap} onHover={onHover} hovered={hovered} />
</div>
</div>
);
Expand All @@ -30,7 +30,7 @@ export default function BodyPanel({ primary, secondary, muscleMap, marginBottom
<div style={{ display: "flex", gap: 12, marginBottom }}>
{["front", "back"].map(view => (
<div key={view} style={{ flex: 1, background: "var(--cds-layer-01)", border: "1px solid var(--cds-border-subtle-01)", padding: "10px 6px" }}>
<BodySVG view={view} primary={primary} secondary={secondary} muscleMap={muscleMap} />
<BodySVG view={view} primary={primary} secondary={secondary} muscleMap={muscleMap} onHover={onHover} hovered={hovered} />
</div>
))}
</div>
Expand Down
35 changes: 34 additions & 1 deletion app/src/components/History.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ export default function History({ onShowHome, onShowLogger, onShowHistory, onSho
const [analyzeError, setAnalyzeError] = useState(null);
const [libraryExercises, setLibraryExercises] = useState([]);
const [newExerciseIds, setNewExerciseIds] = useState(new Set());
const [hoveredMuscle, setHoveredMuscle] = useState(null);
const fileRef = useRef();

useEffect(() => {
Expand Down Expand Up @@ -193,7 +194,8 @@ export default function History({ onShowHome, onShowLogger, onShowHistory, onSho
const toggleExpand = (id) => {
setExpandedIds(prev => {
const next = new Set(prev);
next.has(id) ? next.delete(id) : next.add(id);
if (next.has(id)) { next.delete(id); setHoveredMuscle(null); }
else next.add(id);
return next;
});
};
Expand Down Expand Up @@ -227,6 +229,7 @@ export default function History({ onShowHome, onShowLogger, onShowHistory, onSho
setSelectedDate(new Date(dateStr + "T12:00:00"));
setEditMode(false);
setSelectedSession(null);
setHoveredMuscle(null);
loadSession(dateStr);
};

Expand Down Expand Up @@ -498,8 +501,38 @@ export default function History({ onShowHome, onShowLogger, onShowHistory, onSho
primary={sessionMuscles.primary}
secondary={sessionMuscles.secondary}
muscleMap={sessionMuscleMap}
onHover={setHoveredMuscle}
hovered={hoveredMuscle}
marginBottom={0}
/>

<div style={{ height: 68, marginBottom: 16, overflow: "hidden" }}>
{hoveredMuscle ? (
<div style={{ borderLeft: "3px solid var(--cds-interactive)", background: "var(--cds-layer-01)", padding: "10px 14px" }}>
<div style={{ fontSize: 10, fontFamily: "var(--cds-font-mono)", color: "var(--cds-text-secondary)", letterSpacing: "0.12em", textTransform: "uppercase", marginBottom: 6 }}>
{MUSCLES[hoveredMuscle]?.label}
</div>
<div style={{ display: "flex", gap: 24, alignItems: "baseline", overflow: "hidden" }}>
<div style={{ flexShrink: 0 }}>
<span style={{ fontSize: 28, fontWeight: 300, fontFamily: "var(--cds-font-sans)", color: "var(--cds-text-primary)" }}>
{(sessionMuscleMap[hoveredMuscle] || []).length}
</span>
<span style={{ fontFamily: "var(--cds-font-mono)", fontSize: 10, color: "var(--cds-text-secondary)", marginLeft: 6, letterSpacing: "0.1em", textTransform: "uppercase" }}>
{(sessionMuscleMap[hoveredMuscle] || []).length === 1 ? "ØVELSE" : "ØVELSER"}
</span>
</div>
<span style={{ fontFamily: "var(--cds-font-mono)", fontSize: 10, color: "var(--cds-text-secondary)", letterSpacing: "0.08em", overflow: "hidden", whiteSpace: "nowrap", textOverflow: "ellipsis", minWidth: 0 }}>
{(sessionMuscleMap[hoveredMuscle] || []).join(" · ")}
</span>
</div>
</div>
) : (
<div style={{ fontSize: 11, color: "var(--cds-text-secondary)", fontFamily: "var(--cds-font-mono)", padding: "10px 0", letterSpacing: "0.08em" }}>
Hold musepeker over kroppen for detaljer
</div>
)}
</div>

<div style={{ display: "flex", gap: 8, marginBottom: 16, flexWrap: "wrap" }}>
<Tag type="green" size="sm">Primær ({sessionMuscles.primary.length})</Tag>
<Tag type="blue" size="sm">Sekundær ({sessionMuscles.secondary.length})</Tag>
Expand Down
20 changes: 5 additions & 15 deletions app/src/components/Home.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { format, parseISO, startOfISOWeek, addDays } from "date-fns";
import { nb } from "date-fns/locale";
import { InlineLoading } from "@carbon/react";
import { Camera } from "@carbon/icons-react";
import { BodySVG, MUSCLES } from "../lib/bodymap.jsx";
import { BodySVG } from "../lib/bodymap.jsx";
import { fetchLastSession, fetchThisWeekSessions } from "../lib/db";
import { extractMuscles } from "../lib/utils";
import PageShell, { SectionLabel, PageHeading } from "./PageShell";
Expand Down Expand Up @@ -76,12 +76,6 @@ export default function Home({
const muscles = lastSession ? extractMuscles(lastSession) : null;
const isToday = lastSession?.session_date === format(today, "yyyy-MM-dd");

const muscleMap = muscles
? Object.fromEntries(
[...muscles.primary, ...muscles.secondary].map(id => [id, [MUSCLES[id]?.label ?? id]])
)
: {};

const weekStart = startOfISOWeek(today);
const weekDays = DAY_LABELS.map((label, i) => {
const date = format(addDays(weekStart, i), "yyyy-MM-dd");
Expand Down Expand Up @@ -171,13 +165,13 @@ export default function Home({

{/* Body figures + exercise list side by side */}
<div style={{ display: "flex", gap: 16, padding: "12px 16px 0", alignItems: "flex-start" }}>
{/* Body figures — hover shows muscle name */}
{/* Body figures */}
<div style={{ display: "flex", gap: 4, flexShrink: 0 }}>
<div style={{ width: 72 }}>
<BodySVG primary={muscles.primary} secondary={muscles.secondary} view="front" muscleMap={muscleMap} />
<BodySVG primary={muscles.primary} secondary={muscles.secondary} view="front" />
</div>
<div style={{ width: 72 }}>
<BodySVG primary={muscles.primary} secondary={muscles.secondary} view="back" muscleMap={muscleMap} />
<BodySVG primary={muscles.primary} secondary={muscles.secondary} view="back" />
</div>
</div>

Expand Down Expand Up @@ -261,11 +255,7 @@ export default function Home({
maxWidth: 200,
}}>
{tooltip.names.map((name, i) => (
<div key={i} style={{
fontFamily: "var(--cds-font-mono)", fontSize: 12,
color: "var(--cds-text-primary)",
padding: i > 0 ? "4px 0 0" : 0,
}}>
<div key={i} style={{ fontFamily: "var(--cds-font-mono)", fontSize: 12, color: "var(--cds-text-primary)", padding: i > 0 ? "4px 0 0" : 0 }}>
{name}
</div>
))}
Expand Down
4 changes: 2 additions & 2 deletions app/src/components/Report.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -322,9 +322,9 @@ export default function Report({ onShowHome, onShowLogger, onShowHistory, onShow
))}
</div>

<div style={{ minHeight: 68, marginBottom: 12 }}>
<div style={{ height: 68, marginBottom: 12, overflow: "hidden" }}>
{hoveredMuscle ? (
<div style={{ borderLeft: "3px solid #0f62fe", background: "var(--cds-layer-01)", padding: "10px 14px" }}>
<div style={{ borderLeft: "3px solid var(--cds-interactive)", background: "var(--cds-layer-01)", padding: "10px 14px" }}>
<div style={{ fontSize: 10, fontFamily: "var(--cds-font-mono)", color: "var(--cds-text-secondary)", letterSpacing: "0.12em", textTransform: "uppercase", marginBottom: 6 }}>
{MUSCLES[hoveredMuscle]?.label}
</div>
Expand Down
13 changes: 9 additions & 4 deletions app/src/lib/bodymap.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from "react";

export const EX_DB = [

Check warning on line 3 in app/src/lib/bodymap.jsx

View workflow job for this annotation

GitHub Actions / Lint, test and audit

Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components
{ kw: ["benkpress","bench press","chest press","push up","pushup","armhevinger","brystpress","flies","fly","pec deck"], p: ["chest"], s: ["shoulders_front","triceps"] },
{ kw: ["skulderpress","shoulder press","overhead press","ohp","militærpress","military press","arnold"], p: ["shoulders_front","shoulders_side"], s: ["triceps","traps"] },
{ kw: ["sidehev","lateral raise","lateral"], p: ["shoulders_side"], s: [] },
Expand All @@ -25,7 +25,7 @@
{ kw: ["shrug","skuldertrekk","upright row"], p: ["traps"], s: ["shoulders_side"] },
];

export const MUSCLES = {

Check warning on line 28 in app/src/lib/bodymap.jsx

View workflow job for this annotation

GitHub Actions / Lint, test and audit

Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components
chest: { label: "Bryst", view: "front" },
shoulders_front: { label: "Fremre skuldre", view: "front" },
shoulders_side: { label: "Laterale skuldre", view: "front" },
Expand All @@ -46,7 +46,7 @@
};

// shape.d = SVG path string; otherwise ellipse (cx/cy/rx/ry)
export const SHAPES = {

Check warning on line 49 in app/src/lib/bodymap.jsx

View workflow job for this annotation

GitHub Actions / Lint, test and audit

Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components
chest: [{ cx:62, cy:80, rx:18, ry:13 }, { cx:98, cy:80, rx:18, ry:13 }],
shoulders_front: [{ cx:42, cy:60, rx:10, ry:8 }, { cx:118, cy:60, rx:10, ry:8 }],
shoulders_side: [{ cx:23, cy:68, rx:9, ry:8 }, { cx:137, cy:68, rx:9, ry:8 }],
Expand Down Expand Up @@ -83,7 +83,7 @@
export const SEC_HOVER = "none";
export const SEC_STROKE = "none";

export function useIsMobile(breakpoint = 500) {

Check warning on line 86 in app/src/lib/bodymap.jsx

View workflow job for this annotation

GitHub Actions / Lint, test and audit

Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components
const [mobile, setMobile] = React.useState(() => window.innerWidth < breakpoint);
React.useEffect(() => {
const fn = () => setMobile(window.innerWidth < breakpoint);
Expand All @@ -93,7 +93,7 @@
return mobile;
}

export function calcMuscles(exercises) {

Check warning on line 96 in app/src/lib/bodymap.jsx

View workflow job for this annotation

GitHub Actions / Lint, test and audit

Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components
const p = new Set(), s = new Set();
exercises.forEach(ex => {
if (ex.primary?.length || ex.secondary?.length) {
Expand Down Expand Up @@ -252,23 +252,28 @@
);
}

export function BodySVG({ view, primary, secondary, muscleMap = {} }) {
export function BodySVG({ view, primary, secondary, muscleMap = {}, onHover, hovered }) {
const pSet = new Set(primary);
const sSet = new Set(secondary);
const [tooltip, setTooltip] = React.useState(null);
const wrapRef = React.useRef();

const handleEnter = (id, e) => {
if (onHover) { onHover(id); return; }
const rect = wrapRef.current?.getBoundingClientRect();
if (!rect) return;
setTooltip({ id, x: e.clientX - rect.left, y: e.clientY - rect.top });
};
const handleMove = (id, e) => {
if (onHover) return;
const rect = wrapRef.current?.getBoundingClientRect();
if (!rect) return;
setTooltip({ id, x: e.clientX - rect.left, y: e.clientY - rect.top });
};
const handleLeave = () => setTooltip(null);
const handleLeave = () => {
if (onHover) { onHover(null); return; }
setTooltip(null);
};

return (
<div ref={wrapRef} style={{ position: "relative", width: "100%" }}>
Expand All @@ -295,7 +300,7 @@
const isPrimary = pSet.has(id);
const isSec = sSet.has(id);
if (!isPrimary && !isSec) return null;
const isHovered = tooltip?.id === id;
const isHovered = onHover ? id === hovered : tooltip?.id === id;
const fill = isPrimary
? (isHovered ? PRIMARY_HOVER : PRIMARY_FILL)
: `url(#sec-stripe-${view})`;
Expand All @@ -321,7 +326,7 @@
</text>
</svg>

{tooltip && muscleMap[tooltip.id]?.length > 0 && (
{!onHover && tooltip && muscleMap[tooltip.id]?.length > 0 && (
<div style={{
position: "absolute",
left: Math.min(tooltip.x + 10, (wrapRef.current?.offsetWidth || 200) - 140),
Expand Down
Loading