Skip to content

Commit 66254fb

Browse files
Merge pull request #52 from pineapplestrikesback/claude/fix-time-filter-views-FoC4F
2 parents 812bd4a + 9753a1d commit 66254fb

10 files changed

Lines changed: 149 additions & 35 deletions

src/db/hooks/useVolumeStats.ts

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*/
44

55
import { useMemo } from 'react';
6-
import { useWorkouts } from './useWorkouts';
6+
import { useWorkouts, type ViewMode } from './useWorkouts';
77
import { useProfile } from './useProfiles';
88
import { useExerciseMappings } from './useExerciseMappings';
99
import { calculateMuscleVolume, aggregateToFunctionalGroups } from '@core/volume-calculator';
@@ -110,19 +110,27 @@ function convertSets(dbSets: WorkoutSet[]): WorkoutSet[] {
110110
return dbSets;
111111
}
112112

113+
export type { ViewMode };
114+
113115
/**
114116
* Get volume statistics at the ScientificMuscle level
117+
* @param profileId - Profile ID
118+
* @param daysBackOrMode - Number of days back (legacy) or ViewMode ('last7days' | 'calendarWeek')
115119
*/
116120
export function useScientificMuscleVolume(
117121
profileId: string | null,
118-
daysBack: number = 7
122+
daysBackOrMode: number | ViewMode = 7
119123
): {
120124
stats: VolumeStatItem[];
121125
totalVolume: number;
122126
isLoading: boolean;
123127
error: Error | null;
124128
} {
125-
const { workouts, isLoading: workoutsLoading, error } = useWorkouts(profileId, daysBack);
129+
// Convert to options format for useWorkouts
130+
const workoutsArg = typeof daysBackOrMode === 'number'
131+
? daysBackOrMode
132+
: { mode: daysBackOrMode };
133+
const { workouts, isLoading: workoutsLoading, error } = useWorkouts(profileId, workoutsArg);
126134
const { profile } = useProfile(profileId);
127135
const { mappings: userMappings, isLoading: mappingsLoading } = useExerciseMappings(profileId);
128136

@@ -161,18 +169,24 @@ export function useScientificMuscleVolume(
161169

162170
/**
163171
* Get volume statistics at the FunctionalGroup level
172+
* @param profileId - Profile ID
173+
* @param daysBackOrMode - Number of days back (legacy) or ViewMode ('last7days' | 'calendarWeek')
164174
*/
165175
export function useFunctionalGroupVolume(
166176
profileId: string | null,
167-
daysBack: number = 7
177+
daysBackOrMode: number | ViewMode = 7
168178
): {
169179
stats: VolumeStatItem[];
170180
totalVolume: number;
171181
totalGoal: number;
172182
isLoading: boolean;
173183
error: Error | null;
174184
} {
175-
const { workouts, isLoading: workoutsLoading, error } = useWorkouts(profileId, daysBack);
185+
// Convert to options format for useWorkouts
186+
const workoutsArg = typeof daysBackOrMode === 'number'
187+
? daysBackOrMode
188+
: { mode: daysBackOrMode };
189+
const { workouts, isLoading: workoutsLoading, error } = useWorkouts(profileId, workoutsArg);
176190
const { profile } = useProfile(profileId);
177191
const { mappings: userMappings, isLoading: mappingsLoading } = useExerciseMappings(profileId);
178192

@@ -232,17 +246,20 @@ export function useFunctionalGroupVolume(
232246

233247
/**
234248
* Get the breakdown of scientific muscles within a functional group
249+
* @param profileId - Profile ID
250+
* @param functionalGroup - Functional group to get breakdown for
251+
* @param daysBackOrMode - Number of days back (legacy) or ViewMode ('last7days' | 'calendarWeek')
235252
*/
236253
export function useFunctionalGroupBreakdown(
237254
profileId: string | null,
238255
functionalGroup: FunctionalGroup,
239-
daysBack: number = 7
256+
daysBackOrMode: number | ViewMode = 7
240257
): {
241258
breakdown: VolumeStatItem[];
242259
isLoading: boolean;
243260
error: Error | null;
244261
} {
245-
const { stats, isLoading, error } = useScientificMuscleVolume(profileId, daysBack);
262+
const { stats, isLoading, error } = useScientificMuscleVolume(profileId, daysBackOrMode);
246263
const { profile } = useProfile(profileId);
247264

248265
const breakdown = useMemo(() => {

src/db/hooks/useWorkouts.ts

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { db, generateId, type Workout, type WorkoutSet } from '../schema';
77

88
const WORKOUTS_KEY = ['workouts'];
99

10+
export type ViewMode = 'last7days' | 'calendarWeek';
11+
1012
/**
1113
* Get the start of today in the user's local timezone
1214
*/
@@ -15,25 +17,76 @@ function getStartOfToday(): Date {
1517
return new Date(now.getFullYear(), now.getMonth(), now.getDate());
1618
}
1719

20+
/**
21+
* Get the end of today in the user's local timezone (23:59:59.999)
22+
*/
23+
function getEndOfToday(): Date {
24+
const now = new Date();
25+
return new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999);
26+
}
27+
28+
/**
29+
* Get the start of the current calendar week (Monday)
30+
*/
31+
function getStartOfWeek(): Date {
32+
const now = new Date();
33+
const day = now.getDay();
34+
const diff = day === 0 ? -6 : 1 - day; // Adjust when Sunday (0)
35+
const monday = new Date(now);
36+
monday.setDate(now.getDate() + diff);
37+
return new Date(monday.getFullYear(), monday.getMonth(), monday.getDate());
38+
}
39+
40+
export interface UseWorkoutsOptions {
41+
mode?: ViewMode;
42+
}
43+
1844
/**
1945
* Get workouts for a profile within a date range
46+
* @param profileId - Profile ID
47+
* @param daysBackOrOptions - Number of days back (legacy) or options object with mode
2048
*/
2149
export function useWorkouts(
2250
profileId: string | null,
23-
daysBack: number = 7
51+
daysBackOrOptions: number | UseWorkoutsOptions = 7
2452
): {
2553
workouts: Workout[];
2654
isLoading: boolean;
2755
error: Error | null;
2856
} {
57+
// Support both legacy daysBack number and new options object
58+
const options: UseWorkoutsOptions = typeof daysBackOrOptions === 'number'
59+
? { mode: 'last7days' }
60+
: daysBackOrOptions;
61+
const mode = options.mode ?? 'last7days';
62+
63+
// For legacy daysBack support, extract the number
64+
const daysBack = typeof daysBackOrOptions === 'number' ? daysBackOrOptions : 7;
65+
2966
const { data, isLoading, error } = useQuery({
30-
queryKey: [...WORKOUTS_KEY, profileId, daysBack],
67+
queryKey: [...WORKOUTS_KEY, profileId, mode, daysBack],
3168
queryFn: async () => {
3269
if (!profileId) return [];
3370

34-
const endDate = new Date();
35-
const startDate = new Date(getStartOfToday());
36-
startDate.setDate(startDate.getDate() - daysBack + 1);
71+
let startDate: Date;
72+
let endDate: Date;
73+
74+
if (typeof daysBackOrOptions === 'number') {
75+
// Legacy behavior: rolling daysBack window
76+
endDate = new Date();
77+
startDate = new Date(getStartOfToday());
78+
startDate.setDate(startDate.getDate() - daysBackOrOptions + 1);
79+
} else if (mode === 'last7days') {
80+
endDate = getEndOfToday();
81+
startDate = new Date(getStartOfToday());
82+
startDate.setDate(startDate.getDate() - 6); // 6 days back + today = 7 days
83+
} else {
84+
// calendarWeek mode
85+
startDate = getStartOfWeek();
86+
endDate = new Date(startDate);
87+
endDate.setDate(endDate.getDate() + 6); // Monday + 6 days = Sunday
88+
endDate.setHours(23, 59, 59, 999);
89+
}
3790

3891
return db.workouts
3992
.where('profileId')

src/ui/components/MuscleHeatmap.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*/
66

77
import React, { useMemo, useId } from 'react';
8-
import { useScientificMuscleVolume } from '@db/hooks/useVolumeStats';
8+
import { useScientificMuscleVolume, type ViewMode } from '@db/hooks/useVolumeStats';
99
import type { ScientificMuscle } from '@core/taxonomy';
1010
import { getVolumeColor, getNoTargetColor } from '@core/color-scale';
1111
import Model from 'react-body-highlighter';
@@ -14,6 +14,7 @@ import type { IExerciseData, Muscle } from 'react-body-highlighter';
1414
interface MuscleHeatmapProps {
1515
profileId: string | null;
1616
daysBack?: number;
17+
viewMode?: ViewMode;
1718
}
1819

1920
/**
@@ -138,8 +139,10 @@ function getFrequencyLevel(percentage: number): number {
138139
* @param daysBack - Number of days to aggregate stats over (default: 7)
139140
* @returns A React element containing the split anterior/posterior muscle heatmap
140141
*/
141-
export function MuscleHeatmap({ profileId, daysBack = 7 }: MuscleHeatmapProps): React.ReactElement {
142-
const { stats, isLoading, error } = useScientificMuscleVolume(profileId, daysBack);
142+
export function MuscleHeatmap({ profileId, daysBack = 7, viewMode }: MuscleHeatmapProps): React.ReactElement {
143+
// Use viewMode if provided, otherwise fall back to daysBack
144+
const volumeArg = viewMode ?? daysBack;
145+
const { stats, isLoading, error } = useScientificMuscleVolume(profileId, volumeArg);
143146

144147
// Map stats to muscle-level data
145148
const muscleStats = useMemo((): MuscleStats[] => {

src/ui/components/TotalVolumeCard.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,18 @@
33
* Shows total weekly volume with progress bar
44
*/
55

6-
import { useFunctionalGroupVolume } from '@db/hooks/useVolumeStats';
6+
import { useFunctionalGroupVolume, type ViewMode } from '@db/hooks/useVolumeStats';
77
import { useCurrentProfile } from '../context/ProfileContext';
88

9-
export function TotalVolumeCard(): React.ReactElement {
9+
interface TotalVolumeCardProps {
10+
viewMode?: ViewMode;
11+
}
12+
13+
export function TotalVolumeCard({ viewMode = 'last7days' }: TotalVolumeCardProps): React.ReactElement {
1014
const { currentProfile } = useCurrentProfile();
1115
const { totalVolume, totalGoal, isLoading } = useFunctionalGroupVolume(
12-
currentProfile?.id ?? null
16+
currentProfile?.id ?? null,
17+
viewMode
1318
);
1419

1520
if (isLoading) {

src/ui/components/WeeklyActivityChart.tsx

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ import { BarChart, Bar, XAxis, YAxis, ResponsiveContainer, Cell, Tooltip } from
88
import { useDailyStats, type DailyActivity } from '../../db/hooks/useDailyStats';
99
import { useCurrentProfile } from '../context/ProfileContext';
1010

11+
type ViewMode = 'last7days' | 'calendarWeek';
12+
1113
interface WeeklyActivityChartProps {
1214
profileId?: string;
15+
viewMode?: ViewMode;
16+
onViewModeChange?: (mode: ViewMode) => void;
1317
}
1418

15-
type ViewMode = 'last7days' | 'calendarWeek';
16-
1719
interface CustomTooltipProps {
1820
active?: boolean;
1921
payload?: Array<{ payload: DailyActivity }>;
@@ -153,11 +155,23 @@ function DayDetailPanel({ day, onClose }: DayDetailPanelProps): React.ReactEleme
153155
*/
154156
export function WeeklyActivityChart({
155157
profileId: propProfileId,
158+
viewMode: controlledViewMode,
159+
onViewModeChange,
156160
}: WeeklyActivityChartProps): React.ReactElement {
157161
const { currentProfile } = useCurrentProfile();
158162
const profileId = propProfileId ?? currentProfile?.id ?? null;
159163

160-
const [viewMode, setViewMode] = useState<ViewMode>('last7days');
164+
// Support both controlled and uncontrolled modes
165+
const [internalViewMode, setInternalViewMode] = useState<ViewMode>('last7days');
166+
const viewMode = controlledViewMode ?? internalViewMode;
167+
const setViewMode = (mode: ViewMode): void => {
168+
if (onViewModeChange) {
169+
onViewModeChange(mode);
170+
} else {
171+
setInternalViewMode(mode);
172+
}
173+
};
174+
161175
const [selectedDayIndex, setSelectedDayIndex] = useState<number | null>(null);
162176

163177
const { days, isLoading, error } = useDailyStats(profileId, { mode: viewMode });

src/ui/components/mobile/MobileCarousel.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ import { useState, useEffect, useCallback } from 'react';
1212
import useEmblaCarousel from 'embla-carousel-react';
1313
import { MobileHeatmap } from '@ui/components/mobile/MobileHeatmap';
1414
import { MobileMuscleList } from '@ui/components/mobile/MobileMuscleList';
15+
import type { ViewMode } from '@db/hooks/useWorkouts';
1516

1617
interface MobileCarouselProps {
1718
profileId: string | null;
1819
daysBack?: number;
20+
viewMode?: ViewMode;
1921
}
2022

2123
/**
@@ -26,7 +28,8 @@ interface MobileCarouselProps {
2628
*/
2729
export function MobileCarousel({
2830
profileId,
29-
daysBack = 7
31+
daysBack = 7,
32+
viewMode,
3033
}: MobileCarouselProps): React.ReactElement {
3134
// Initialize Embla with critical options
3235
const [emblaRef, emblaApi] = useEmblaCarousel({
@@ -71,13 +74,14 @@ export function MobileCarousel({
7174
<MobileHeatmap
7275
profileId={profileId}
7376
daysBack={daysBack}
77+
viewMode={viewMode}
7478
isActive={selectedIndex === 0}
7579
/>
7680
</div>
7781

7882
{/* Slide 2: Muscle List */}
7983
<div className="flex-[0_0_100%] min-w-0 px-4">
80-
<MobileMuscleList profileId={profileId} daysBack={daysBack} />
84+
<MobileMuscleList profileId={profileId} daysBack={daysBack} viewMode={viewMode} />
8185
</div>
8286
</div>
8387
</div>

src/ui/components/mobile/MobileHeatmap.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import { useMemo, useId, useState, useCallback, useEffect } from 'react';
1212
import Model from 'react-body-highlighter';
1313
import type { IExerciseData, Muscle } from 'react-body-highlighter';
14-
import { useScientificMuscleVolume } from '@db/hooks';
14+
import { useScientificMuscleVolume, type ViewMode } from '@db/hooks';
1515
import { useEffectiveMuscleGroupConfig } from '@db/hooks/useMuscleGroups';
1616
import type { ScientificMuscle } from '@core/taxonomy';
1717
import { getVolumeColor, getNoTargetColor } from '@core/color-scale';
@@ -21,6 +21,7 @@ import { MuscleDetailModal } from './MuscleDetailModal';
2121
interface MobileHeatmapProps {
2222
profileId: string | null;
2323
daysBack?: number;
24+
viewMode?: ViewMode;
2425
isActive?: boolean;
2526
}
2627

@@ -134,9 +135,12 @@ interface MuscleStats {
134135
export function MobileHeatmap({
135136
profileId,
136137
daysBack = 7,
138+
viewMode,
137139
isActive = true,
138140
}: MobileHeatmapProps): React.ReactElement {
139-
const { stats, isLoading, error } = useScientificMuscleVolume(profileId, daysBack);
141+
// Use viewMode if provided, otherwise fall back to daysBack
142+
const volumeArg = viewMode ?? daysBack;
143+
const { stats, isLoading, error } = useScientificMuscleVolume(profileId, volumeArg);
140144
const { config } = useEffectiveMuscleGroupConfig(profileId);
141145
const [view, setView] = useSessionState<'front' | 'back'>(
142146
'scientificmuscle_heatmap_view',
@@ -341,6 +345,7 @@ export function MobileHeatmap({
341345
relatedMuscles={selectedRegion ? REGION_TO_MUSCLES[selectedRegion].related : undefined}
342346
profileId={profileId}
343347
daysBack={daysBack}
348+
viewMode={viewMode}
344349
/>
345350
</div>
346351
);

src/ui/components/mobile/MobileMuscleList.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,15 @@
1515
*/
1616

1717
import { useState, useMemo, useEffect } from 'react';
18-
import { useScientificMuscleVolume, type VolumeStatItem } from '@db/hooks/useVolumeStats';
18+
import { useScientificMuscleVolume, type VolumeStatItem, type ViewMode } from '@db/hooks/useVolumeStats';
1919
import { useEffectiveMuscleGroupConfig } from '@db/hooks/useMuscleGroups';
2020
import { getVolumeColor } from '@core/color-scale';
2121
import type { ScientificMuscle } from '@core/taxonomy';
2222

2323
interface MobileMuscleListProps {
2424
profileId: string | null;
2525
daysBack?: number;
26+
viewMode?: ViewMode;
2627
}
2728

2829
/**
@@ -40,9 +41,12 @@ function formatVolume(volume: number): string {
4041
export function MobileMuscleList({
4142
profileId,
4243
daysBack = 7,
44+
viewMode,
4345
}: MobileMuscleListProps): React.ReactElement {
4446
// Fetch volume data for all muscles
45-
const { stats, isLoading: volumeLoading, error } = useScientificMuscleVolume(profileId, daysBack);
47+
// Use viewMode if provided, otherwise fall back to daysBack
48+
const volumeArg = viewMode ?? daysBack;
49+
const { stats, isLoading: volumeLoading, error } = useScientificMuscleVolume(profileId, volumeArg);
4650

4751
// Fetch custom group configuration
4852
const { config, isLoading: configLoading } = useEffectiveMuscleGroupConfig(profileId);

0 commit comments

Comments
 (0)