mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 14:32:03 +00:00
Render the day around a workout instead of linking away from it
The "That day" card took the workout, dropped it, and offered three chips to Pulse, Sleep and Mood. None of those pages accepts a date, so every chip landed on an all-time trend surface with the day nowhere in sight. Replace it with the day itself: the intraday pulse chart pinned to the session's day, the night the person woke from into it, and that day's mood entries. All three reads are addressed by the server-resolved dayKey, so nothing here decides which day a workout belongs to. The two outbound links that remain are undated and say what they lead to, Sleep trends and Mood history. Sleep and the intraday chart mount only when their module is on, so a switched-off module cannot paint as a failed read, and a query that has not answered yet shows a skeleton rather than claiming the night was empty. The detail page also drops the description it borrowed from the list.
This commit is contained in:
1 parent
0094445f07
commit
76b407b6de
15 files changed
+492
-111
No files matched your search
@@ -0,0 +1,71 @@
|
||||
"use client";
|
||||
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { apiGet } from "@/lib/api/api-fetch";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
|
||||
/**
|
||||
* Every mood entry a person logged on ONE local day, from
|
||||
* `GET /api/mood-entries?from=<day>&to=<day>`.
|
||||
*
|
||||
* The route filters on the mood `date` column, which already IS the local
|
||||
* day key, so no timezone maths happens here. Entries come back newest
|
||||
* first (`sortBy=moodLoggedAt`, `sortDir=desc`, the route's defaults).
|
||||
*/
|
||||
export interface MoodDayEntry {
|
||||
id: string;
|
||||
date: string;
|
||||
/** The mood enum, e.g. `GUT`. Rendered through `MOOD_LABEL_KEYS`. */
|
||||
mood: string;
|
||||
score: number;
|
||||
note: string | null;
|
||||
source: string;
|
||||
moodLoggedAt: string;
|
||||
}
|
||||
|
||||
interface MoodDayResponse {
|
||||
entries: MoodDayEntry[];
|
||||
meta: { total: number };
|
||||
}
|
||||
|
||||
export interface UseMoodDayResult {
|
||||
/** `undefined` until the route has answered; `[]` means the day really
|
||||
* carried no entry. */
|
||||
entries: MoodDayEntry[] | undefined;
|
||||
isError: boolean;
|
||||
refetch: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads one day's mood entries.
|
||||
*
|
||||
* `enabled` is the caller's gate — pass `false` when the user is signed
|
||||
* out, and nothing is fetched. Mood has no module gate, so there is
|
||||
* nothing else to check.
|
||||
*/
|
||||
export function useMoodDay(
|
||||
date: string | undefined,
|
||||
enabled: boolean,
|
||||
): UseMoodDayResult {
|
||||
const { data, isError, refetch } = useQuery({
|
||||
queryKey: queryKeys.moodEntriesDay(date ?? ""),
|
||||
queryFn: () => {
|
||||
const params = new URLSearchParams({
|
||||
from: date as string,
|
||||
to: date as string,
|
||||
limit: "50",
|
||||
});
|
||||
return apiGet<MoodDayResponse>(`/api/mood-entries?${params}`);
|
||||
},
|
||||
enabled: enabled && Boolean(date),
|
||||
staleTime: 5 * 60_000,
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
|
||||
return {
|
||||
entries: data?.entries,
|
||||
isError,
|
||||
refetch: () => void refetch(),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
"use client";
|
||||
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { apiGet } from "@/lib/api/api-fetch";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
|
||||
/**
|
||||
* One reconstructed night from `GET /api/sleep/night?date=YYYY-MM-DD`.
|
||||
*
|
||||
* The date is the WAKE day: the night a person woke from into that day.
|
||||
* The server owns the reconstruction (stage folding, cross-source pick,
|
||||
* WHOOP timeline synthesis) and the day keying, so this hook only carries
|
||||
* the resolved figures across.
|
||||
*
|
||||
* The route is gated on the `sleep` module and answers 403 when it is off.
|
||||
* Callers gate the mount on `useModuleEnabled("sleep")` so a switched-off
|
||||
* module never paints as a failed read.
|
||||
*/
|
||||
export interface SleepNightSegment {
|
||||
stage: string;
|
||||
start: string;
|
||||
end: string;
|
||||
minutes: number;
|
||||
}
|
||||
|
||||
export interface SleepNightSession {
|
||||
/** `YYYY-MM-DD` wake-day key. */
|
||||
night: string;
|
||||
source: string | null;
|
||||
start: string;
|
||||
end: string;
|
||||
asleepMinutes: number;
|
||||
inBedMinutes: number | null;
|
||||
awakeMinutes: number | null;
|
||||
/** Mid-sleep awakenings. `0` is a real answer, not an absence. */
|
||||
awakenings: number;
|
||||
reconstructed: boolean;
|
||||
stages: Record<string, number>;
|
||||
segments: SleepNightSegment[];
|
||||
}
|
||||
|
||||
export interface SleepNightPayload {
|
||||
night: string | null;
|
||||
/** `null` when nothing scorable was recorded for that night. */
|
||||
main: SleepNightSession | null;
|
||||
naps: SleepNightSession[];
|
||||
}
|
||||
|
||||
export interface UseSleepNightResult {
|
||||
/** `undefined` until the route has answered — never confuse it with a
|
||||
* night that carried nothing. */
|
||||
data: SleepNightPayload | undefined;
|
||||
isError: boolean;
|
||||
refetch: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the night a person woke from into `date`.
|
||||
*
|
||||
* `enabled` is the caller's gate — pass `false` when the user is signed
|
||||
* out or the sleep module is disabled, and nothing is fetched.
|
||||
*/
|
||||
export function useSleepNight(
|
||||
date: string | undefined,
|
||||
enabled: boolean,
|
||||
): UseSleepNightResult {
|
||||
const { data, isError, refetch } = useQuery({
|
||||
queryKey: queryKeys.sleepNight(date),
|
||||
queryFn: () =>
|
||||
apiGet<SleepNightPayload>(
|
||||
`/api/sleep/night?date=${encodeURIComponent(date as string)}`,
|
||||
),
|
||||
enabled: enabled && Boolean(date),
|
||||
staleTime: 5 * 60_000,
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
|
||||
return { data, isError, refetch: () => void refetch() };
|
||||
}
|
||||
Reference in new issue
Block a user