@burdenoff/microfe-mybodyshield 2026.716.1 → 2026.720.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/mybodyshield/hooks/useMyBodyShieldApi.d.ts +39 -4
- package/dist/mybodyshield/hooks/useMyBodyShieldApi.d.ts.map +1 -1
- package/dist/mybodyshield/hooks/useMyBodyShieldApi.js +69 -42
- package/dist/mybodyshield/hooks/useMyBodyShieldApi.js.map +1 -1
- package/dist/mybodyshield/pages/DashboardPage.js +1 -1
- package/dist/mybodyshield/pages/DashboardPage.js.map +1 -1
- package/dist/mybodyshield/pages/coaching/CoachingPage.js +1 -1
- package/dist/mybodyshield/pages/coaching/CoachingPage.js.map +1 -1
- package/dist/mybodyshield/pages/recovery/RecoveryPage.js +1 -1
- package/dist/mybodyshield/pages/recovery/RecoveryPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -6,7 +6,26 @@ export interface ProgramFilters {
|
|
|
6
6
|
pagination?: PaginationInput;
|
|
7
7
|
}
|
|
8
8
|
export declare const useMybPrograms: (filters?: ProgramFilters) => import('@tanstack/react-query').UseQueryResult<NoInfer<MybConnection<MybProgram>>, Error>;
|
|
9
|
-
export declare const useMybProgram: (id: string | undefined) => import('@tanstack/react-query').UseQueryResult<NoInfer<
|
|
9
|
+
export declare const useMybProgram: (id: string | undefined) => import('@tanstack/react-query').UseQueryResult<NoInfer<{
|
|
10
|
+
workouts: MybWorkout[];
|
|
11
|
+
status: MybProgramStatus;
|
|
12
|
+
memberId: string;
|
|
13
|
+
name: string;
|
|
14
|
+
description?: string | null | undefined;
|
|
15
|
+
coachId?: string | null | undefined;
|
|
16
|
+
goalType: import('../types').MybGoalType;
|
|
17
|
+
level: import('../types').MybFitnessLevel;
|
|
18
|
+
phase: import('../types').MybProgramPhase;
|
|
19
|
+
weeks: number;
|
|
20
|
+
daysPerWeek: number;
|
|
21
|
+
startDate?: string | null | undefined;
|
|
22
|
+
endDate?: string | null | undefined;
|
|
23
|
+
tags: string[];
|
|
24
|
+
workspaceId: string;
|
|
25
|
+
id: string;
|
|
26
|
+
createdAt: string;
|
|
27
|
+
updatedAt: string;
|
|
28
|
+
} | null>, Error>;
|
|
10
29
|
export declare const useMybProgramWorkouts: (programId: string | undefined) => import('@tanstack/react-query').UseQueryResult<NoInfer<MybWorkout[]>, Error>;
|
|
11
30
|
export declare const useMybCreateProgram: () => import('@tanstack/react-query').UseMutationResult<MybProgram, Error, MybCreateProgramInput, unknown>;
|
|
12
31
|
export declare const useMybUpdateProgram: () => import('@tanstack/react-query').UseMutationResult<MybProgram, Error, {
|
|
@@ -29,7 +48,23 @@ export interface SessionFilters {
|
|
|
29
48
|
pagination?: PaginationInput;
|
|
30
49
|
}
|
|
31
50
|
export declare const useMybSessions: (filters?: SessionFilters) => import('@tanstack/react-query').UseQueryResult<NoInfer<MybConnection<MybWorkoutSession>>, Error>;
|
|
32
|
-
export declare const useMybSession: (id: string | undefined) => import('@tanstack/react-query').UseQueryResult<NoInfer<
|
|
51
|
+
export declare const useMybSession: (id: string | undefined) => import('@tanstack/react-query').UseQueryResult<NoInfer<{
|
|
52
|
+
sets: MybSessionSet[];
|
|
53
|
+
status: MybSessionStatus;
|
|
54
|
+
memberId: string;
|
|
55
|
+
workspaceId: string;
|
|
56
|
+
id: string;
|
|
57
|
+
programId?: string | null | undefined;
|
|
58
|
+
createdAt: string;
|
|
59
|
+
updatedAt: string;
|
|
60
|
+
workoutId?: string | null | undefined;
|
|
61
|
+
scheduledFor?: string | null | undefined;
|
|
62
|
+
startedAt?: string | null | undefined;
|
|
63
|
+
completedAt?: string | null | undefined;
|
|
64
|
+
caloriesBurned: number;
|
|
65
|
+
perceivedExertion?: number | null | undefined;
|
|
66
|
+
notes?: string | null | undefined;
|
|
67
|
+
} | null>, Error>;
|
|
33
68
|
export declare const useMybStartSession: () => import('@tanstack/react-query').UseMutationResult<MybWorkoutSession, Error, MybStartSessionInput, unknown>;
|
|
34
69
|
export declare const useMybLogSet: () => import('@tanstack/react-query').UseMutationResult<MybSessionSet, Error, MybLogSetInput, unknown>;
|
|
35
70
|
export declare const useMybCompleteSession: () => import('@tanstack/react-query').UseMutationResult<MybWorkoutSession, Error, {
|
|
@@ -72,9 +107,9 @@ export interface BodyMetricFilters {
|
|
|
72
107
|
}
|
|
73
108
|
export declare const useMybBodyMetrics: (filters?: BodyMetricFilters) => import('@tanstack/react-query').UseQueryResult<NoInfer<MybConnection<MybBodyMetric>>, Error>;
|
|
74
109
|
export declare const useMybRecordBodyMetric: () => import('@tanstack/react-query').UseMutationResult<MybBodyMetric, Error, MybRecordMetricInput, unknown>;
|
|
75
|
-
export declare const useMybRecoveryLogs: (metric?: MybRecoveryMetric, memberId?: string) => import('@tanstack/react-query').UseQueryResult<NoInfer<MybRecoveryLog
|
|
110
|
+
export declare const useMybRecoveryLogs: (metric?: MybRecoveryMetric, memberId?: string) => import('@tanstack/react-query').UseQueryResult<NoInfer<MybConnection<MybRecoveryLog>>, Error>;
|
|
76
111
|
export declare const useMybLogRecovery: () => import('@tanstack/react-query').UseMutationResult<MybRecoveryLog, Error, MybLogRecoveryInput, unknown>;
|
|
77
|
-
export declare const useMybCoaching: (status?: MybCoachingStatus, memberId?: string) => import('@tanstack/react-query').UseQueryResult<NoInfer<MybCoachingRelationship
|
|
112
|
+
export declare const useMybCoaching: (status?: MybCoachingStatus, memberId?: string) => import('@tanstack/react-query').UseQueryResult<NoInfer<MybConnection<MybCoachingRelationship>>, Error>;
|
|
78
113
|
export declare const useMybCreateCoaching: () => import('@tanstack/react-query').UseMutationResult<MybCoachingRelationship, Error, MybCreateCoachingInput, unknown>;
|
|
79
114
|
export declare const useMybTransitionCoaching: () => import('@tanstack/react-query').UseMutationResult<MybCoachingRelationship, Error, {
|
|
80
115
|
id: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useMyBodyShieldApi.d.ts","sourceRoot":"","sources":["../../../src/mybodyshield/hooks/useMyBodyShieldApi.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,OAAO,KAAK,EACV,aAAa,EACb,UAAU,EACV,gBAAgB,EAChB,UAAU,
|
|
1
|
+
{"version":3,"file":"useMyBodyShieldApi.d.ts","sourceRoot":"","sources":["../../../src/mybodyshield/hooks/useMyBodyShieldApi.ts"],"names":[],"mappings":"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"}
|
|
@@ -52,7 +52,10 @@ var a = () => {
|
|
|
52
52
|
bodyMetrics: ["mybodyshield", "body-metrics"],
|
|
53
53
|
recovery: ["mybodyshield", "recovery"],
|
|
54
54
|
coaching: ["mybodyshield", "coaching"]
|
|
55
|
-
}, c = { limit: 50 }, l = "\n id workspaceId memberId coachId name description goalType level phase status\n weeks daysPerWeek startDate endDate tags createdAt updatedAt\n", u = "\n id workspaceId workoutId name instructions muscleGroup sets reps targetWeight\n restSeconds orderIndex demo { id } createdAt updatedAt\n", d = "\n id workspaceId programId name description type dayOfWeek weekNumber orderIndex\n durationMin createdAt updatedAt\n", f = "\n id workspaceId sessionId exerciseName setNumber reps weight distanceM durationSec\n isPersonalRecord createdAt updatedAt\n", p = "\n id workspaceId memberId programId workoutId status scheduledFor startedAt completedAt\n caloriesBurned perceivedExertion notes createdAt updatedAt\n", m = "\n id workspaceId memberId title description type status targetValue currentValue\n baselineValue unit targetDate achievedAt createdAt updatedAt\n", h = "\n id workspaceId memberId planId name mealType calories proteinG carbsG fatG servings\n isFavorite loggedAt createdAt updatedAt\n", g = "\n id workspaceId memberId coachId name goalType calorieTarget proteinTarget carbsTarget\n fatTarget startDate endDate isActive notes createdAt updatedAt\n", _ = "\n id workspaceId memberId type value unit measuredAt notes photo { id } createdAt updatedAt\n", v = "\n id workspaceId memberId metric score sleepHours notes loggedAt createdAt updatedAt\n", y = "\n id workspaceId coachId memberId status specialty shareWorkouts shareNutrition\n shareMetrics startedAt endedAt notes createdAt updatedAt\n", b = "\n date calories proteinG carbsG fatG calorieTarget proteinTarget carbsTarget fatTarget\n", x = (
|
|
55
|
+
}, c = { limit: 50 }, l = "\n id workspaceId memberId coachId name description goalType level phase status\n weeks daysPerWeek startDate endDate tags createdAt updatedAt\n", u = "\n id workspaceId workoutId name instructions muscleGroup sets reps targetWeight\n restSeconds orderIndex demo { id } createdAt updatedAt\n", d = "\n id workspaceId programId name description type dayOfWeek weekNumber orderIndex\n durationMin createdAt updatedAt\n", f = "\n id workspaceId sessionId exerciseName setNumber reps weight distanceM durationSec\n isPersonalRecord createdAt updatedAt\n", p = "\n id workspaceId memberId programId workoutId status scheduledFor startedAt completedAt\n caloriesBurned perceivedExertion notes createdAt updatedAt\n", m = "\n id workspaceId memberId title description type status targetValue currentValue\n baselineValue unit targetDate achievedAt createdAt updatedAt\n", h = "\n id workspaceId memberId planId name mealType calories proteinG carbsG fatG servings\n isFavorite loggedAt createdAt updatedAt\n", g = "\n id workspaceId memberId coachId name goalType calorieTarget proteinTarget carbsTarget\n fatTarget startDate endDate isActive notes createdAt updatedAt\n", _ = "\n id workspaceId memberId type value unit measuredAt notes photo { id } createdAt updatedAt\n", v = "\n id workspaceId memberId metric score sleepHours notes loggedAt createdAt updatedAt\n", y = "\n id workspaceId coachId memberId status specialty shareWorkouts shareNutrition\n shareMetrics startedAt endedAt notes createdAt updatedAt\n", b = "\n date calories proteinG carbsG fatG calorieTarget proteinTarget carbsTarget fatTarget\n", x = (e) => e.map(({ exercises: e, ...t }) => ({
|
|
56
|
+
...t,
|
|
57
|
+
exercises: e.items
|
|
58
|
+
})), S = (t = {}) => {
|
|
56
59
|
let { apiGatewayUrl: r } = e();
|
|
57
60
|
return n({
|
|
58
61
|
queryKey: [...s.programs, t],
|
|
@@ -67,22 +70,32 @@ var a = () => {
|
|
|
67
70
|
pagination: t.pagination ?? c
|
|
68
71
|
}, r)).mybListPrograms
|
|
69
72
|
});
|
|
70
|
-
},
|
|
73
|
+
}, C = (t) => {
|
|
71
74
|
let { apiGatewayUrl: r } = e();
|
|
72
75
|
return n({
|
|
73
76
|
queryKey: s.program(t ?? ""),
|
|
74
77
|
enabled: !!t,
|
|
75
|
-
queryFn: async () =>
|
|
78
|
+
queryFn: async () => {
|
|
79
|
+
let e = await o(`query MybProgram($id: ID!) {
|
|
76
80
|
mybProgram(id: $id) {
|
|
77
81
|
${l}
|
|
78
82
|
workouts {
|
|
79
|
-
|
|
80
|
-
|
|
83
|
+
items {
|
|
84
|
+
${d}
|
|
85
|
+
exercises { items { ${u} } }
|
|
86
|
+
}
|
|
81
87
|
}
|
|
82
88
|
}
|
|
83
|
-
}`, { id: t }, r)
|
|
89
|
+
}`, { id: t }, r);
|
|
90
|
+
if (!e.mybProgram) return null;
|
|
91
|
+
let { workouts: n, ...i } = e.mybProgram;
|
|
92
|
+
return {
|
|
93
|
+
...i,
|
|
94
|
+
workouts: x(n.items)
|
|
95
|
+
};
|
|
96
|
+
}
|
|
84
97
|
});
|
|
85
|
-
},
|
|
98
|
+
}, w = () => {
|
|
86
99
|
let { apiGatewayUrl: n } = e(), i = r();
|
|
87
100
|
return t({
|
|
88
101
|
mutationFn: async (e) => (await o(`mutation MybCreateProgram($input: MybCreateProgramInput!) {
|
|
@@ -90,7 +103,7 @@ var a = () => {
|
|
|
90
103
|
}`, { input: e }, n)).mybCreateProgram,
|
|
91
104
|
onSuccess: () => i.invalidateQueries({ queryKey: s.programs })
|
|
92
105
|
});
|
|
93
|
-
},
|
|
106
|
+
}, T = () => {
|
|
94
107
|
let { apiGatewayUrl: n } = e(), i = r();
|
|
95
108
|
return t({
|
|
96
109
|
mutationFn: async (e) => (await o(`mutation MybUpdateProgram($id: ID!, $input: MybUpdateProgramInput!) {
|
|
@@ -100,7 +113,7 @@ var a = () => {
|
|
|
100
113
|
i.invalidateQueries({ queryKey: s.programs }), i.invalidateQueries({ queryKey: s.program(e.id) });
|
|
101
114
|
}
|
|
102
115
|
});
|
|
103
|
-
},
|
|
116
|
+
}, E = () => {
|
|
104
117
|
let { apiGatewayUrl: n } = e(), i = r();
|
|
105
118
|
return t({
|
|
106
119
|
mutationFn: async (e) => (await o(`mutation MybTransitionProgram($id: ID!, $status: MybProgramStatus!) {
|
|
@@ -110,13 +123,13 @@ var a = () => {
|
|
|
110
123
|
i.invalidateQueries({ queryKey: s.programs }), i.invalidateQueries({ queryKey: s.program(e.id) });
|
|
111
124
|
}
|
|
112
125
|
});
|
|
113
|
-
},
|
|
126
|
+
}, D = () => {
|
|
114
127
|
let { apiGatewayUrl: n } = e(), i = r();
|
|
115
128
|
return t({
|
|
116
129
|
mutationFn: async (e) => (await o("mutation MybArchiveProgram($id: ID!) { mybArchiveProgram(id: $id) }", { id: e }, n)).mybArchiveProgram,
|
|
117
130
|
onSuccess: () => i.invalidateQueries({ queryKey: s.programs })
|
|
118
131
|
});
|
|
119
|
-
},
|
|
132
|
+
}, O = () => {
|
|
120
133
|
let { apiGatewayUrl: n } = e(), i = r();
|
|
121
134
|
return t({
|
|
122
135
|
mutationFn: async (e) => (await o(`mutation MybAddWorkout($input: MybCreateWorkoutInput!) {
|
|
@@ -126,7 +139,7 @@ var a = () => {
|
|
|
126
139
|
i.invalidateQueries({ queryKey: s.program(e.programId) }), i.invalidateQueries({ queryKey: s.programWorkouts(e.programId) });
|
|
127
140
|
}
|
|
128
141
|
});
|
|
129
|
-
},
|
|
142
|
+
}, k = () => {
|
|
130
143
|
let { apiGatewayUrl: n } = e(), i = r();
|
|
131
144
|
return t({
|
|
132
145
|
mutationFn: async (e) => (await o("mutation MybAddExercise($input: MybCreateExerciseInput!) {\n mybAddExercise(input: $input) { id workoutId }\n }", { input: e }, n)).mybAddExercise,
|
|
@@ -134,7 +147,7 @@ var a = () => {
|
|
|
134
147
|
i.invalidateQueries({ queryKey: ["mybodyshield", "program"] }), i.invalidateQueries({ queryKey: ["mybodyshield", "program-workouts"] });
|
|
135
148
|
}
|
|
136
149
|
});
|
|
137
|
-
},
|
|
150
|
+
}, A = (t = {}) => {
|
|
138
151
|
let { apiGatewayUrl: r } = e();
|
|
139
152
|
return n({
|
|
140
153
|
queryKey: [...s.sessions, t],
|
|
@@ -149,19 +162,27 @@ var a = () => {
|
|
|
149
162
|
pagination: t.pagination ?? c
|
|
150
163
|
}, r)).mybListSessions
|
|
151
164
|
});
|
|
152
|
-
},
|
|
165
|
+
}, j = (t) => {
|
|
153
166
|
let { apiGatewayUrl: r } = e();
|
|
154
167
|
return n({
|
|
155
168
|
queryKey: s.session(t ?? ""),
|
|
156
169
|
enabled: !!t,
|
|
157
|
-
queryFn: async () =>
|
|
170
|
+
queryFn: async () => {
|
|
171
|
+
let e = await o(`query MybSession($id: ID!) {
|
|
158
172
|
mybSession(id: $id) {
|
|
159
173
|
${p}
|
|
160
|
-
sets { ${f} }
|
|
174
|
+
sets { items { ${f} } }
|
|
161
175
|
}
|
|
162
|
-
}`, { id: t }, r)
|
|
176
|
+
}`, { id: t }, r);
|
|
177
|
+
if (!e.mybSession) return null;
|
|
178
|
+
let { sets: n, ...i } = e.mybSession;
|
|
179
|
+
return {
|
|
180
|
+
...i,
|
|
181
|
+
sets: n.items
|
|
182
|
+
};
|
|
183
|
+
}
|
|
163
184
|
});
|
|
164
|
-
},
|
|
185
|
+
}, M = () => {
|
|
165
186
|
let { apiGatewayUrl: n } = e(), i = r();
|
|
166
187
|
return t({
|
|
167
188
|
mutationFn: async (e) => (await o(`mutation MybStartSession($input: MybStartSessionInput!) {
|
|
@@ -169,7 +190,7 @@ var a = () => {
|
|
|
169
190
|
}`, { input: e }, n)).mybStartSession,
|
|
170
191
|
onSuccess: () => i.invalidateQueries({ queryKey: s.sessions })
|
|
171
192
|
});
|
|
172
|
-
},
|
|
193
|
+
}, N = () => {
|
|
173
194
|
let { apiGatewayUrl: n } = e(), i = r();
|
|
174
195
|
return t({
|
|
175
196
|
mutationFn: async (e) => (await o(`mutation MybLogSet($input: MybLogSetInput!) {
|
|
@@ -179,7 +200,7 @@ var a = () => {
|
|
|
179
200
|
i.invalidateQueries({ queryKey: s.session(e.sessionId) }), i.invalidateQueries({ queryKey: s.sessions });
|
|
180
201
|
}
|
|
181
202
|
});
|
|
182
|
-
},
|
|
203
|
+
}, P = () => {
|
|
183
204
|
let { apiGatewayUrl: n } = e(), i = r();
|
|
184
205
|
return t({
|
|
185
206
|
mutationFn: async (e) => (await o(`mutation MybCompleteSession($id: ID!, $input: MybCompleteSessionInput!) {
|
|
@@ -189,7 +210,7 @@ var a = () => {
|
|
|
189
210
|
i.invalidateQueries({ queryKey: s.session(e.id) }), i.invalidateQueries({ queryKey: s.sessions });
|
|
190
211
|
}
|
|
191
212
|
});
|
|
192
|
-
},
|
|
213
|
+
}, F = (t = {}) => {
|
|
193
214
|
let { apiGatewayUrl: r } = e();
|
|
194
215
|
return n({
|
|
195
216
|
queryKey: [...s.goals, t],
|
|
@@ -204,7 +225,7 @@ var a = () => {
|
|
|
204
225
|
pagination: t.pagination ?? c
|
|
205
226
|
}, r)).mybListGoals
|
|
206
227
|
});
|
|
207
|
-
},
|
|
228
|
+
}, I = () => {
|
|
208
229
|
let { apiGatewayUrl: n } = e(), i = r();
|
|
209
230
|
return t({
|
|
210
231
|
mutationFn: async (e) => (await o(`mutation MybCreateGoal($input: MybCreateGoalInput!) {
|
|
@@ -212,7 +233,7 @@ var a = () => {
|
|
|
212
233
|
}`, { input: e }, n)).mybCreateGoal,
|
|
213
234
|
onSuccess: () => i.invalidateQueries({ queryKey: s.goals })
|
|
214
235
|
});
|
|
215
|
-
},
|
|
236
|
+
}, L = () => {
|
|
216
237
|
let { apiGatewayUrl: n } = e(), i = r();
|
|
217
238
|
return t({
|
|
218
239
|
mutationFn: async (e) => (await o(`mutation MybUpdateGoal($id: ID!, $input: MybUpdateGoalInput!) {
|
|
@@ -220,7 +241,7 @@ var a = () => {
|
|
|
220
241
|
}`, e, n)).mybUpdateGoal,
|
|
221
242
|
onSuccess: () => i.invalidateQueries({ queryKey: s.goals })
|
|
222
243
|
});
|
|
223
|
-
},
|
|
244
|
+
}, R = () => {
|
|
224
245
|
let { apiGatewayUrl: n } = e(), i = r();
|
|
225
246
|
return t({
|
|
226
247
|
mutationFn: async (e) => (await o(`mutation MybAchieveGoal($id: ID!) {
|
|
@@ -228,7 +249,7 @@ var a = () => {
|
|
|
228
249
|
}`, { id: e }, n)).mybAchieveGoal,
|
|
229
250
|
onSuccess: () => i.invalidateQueries({ queryKey: s.goals })
|
|
230
251
|
});
|
|
231
|
-
},
|
|
252
|
+
}, z = (t) => {
|
|
232
253
|
let { apiGatewayUrl: r } = e();
|
|
233
254
|
return n({
|
|
234
255
|
queryKey: [...s.activePlan, t ?? "me"],
|
|
@@ -236,7 +257,7 @@ var a = () => {
|
|
|
236
257
|
mybActiveNutritionPlan(memberId: $memberId) { ${g} }
|
|
237
258
|
}`, { memberId: t }, r)).mybActiveNutritionPlan
|
|
238
259
|
});
|
|
239
|
-
},
|
|
260
|
+
}, B = (t = {}) => {
|
|
240
261
|
let { apiGatewayUrl: r } = e();
|
|
241
262
|
return n({
|
|
242
263
|
queryKey: [...s.meals, t],
|
|
@@ -251,7 +272,7 @@ var a = () => {
|
|
|
251
272
|
pagination: t.pagination ?? c
|
|
252
273
|
}, r)).mybListMeals
|
|
253
274
|
});
|
|
254
|
-
},
|
|
275
|
+
}, V = (t, r) => {
|
|
255
276
|
let { apiGatewayUrl: i } = e();
|
|
256
277
|
return n({
|
|
257
278
|
queryKey: [
|
|
@@ -266,7 +287,7 @@ var a = () => {
|
|
|
266
287
|
memberId: r
|
|
267
288
|
}, i)).mybDailyMacros
|
|
268
289
|
});
|
|
269
|
-
},
|
|
290
|
+
}, H = () => {
|
|
270
291
|
let { apiGatewayUrl: n } = e(), i = r();
|
|
271
292
|
return t({
|
|
272
293
|
mutationFn: async (e) => (await o(`mutation MybCreateNutritionPlan($input: MybCreateNutritionPlanInput!) {
|
|
@@ -276,7 +297,7 @@ var a = () => {
|
|
|
276
297
|
i.invalidateQueries({ queryKey: s.activePlan });
|
|
277
298
|
}
|
|
278
299
|
});
|
|
279
|
-
},
|
|
300
|
+
}, U = () => {
|
|
280
301
|
let { apiGatewayUrl: n } = e(), i = r();
|
|
281
302
|
return t({
|
|
282
303
|
mutationFn: async (e) => (await o(`mutation MybUpdateNutritionPlan($id: ID!, $input: MybUpdateNutritionPlanInput!) {
|
|
@@ -286,7 +307,7 @@ var a = () => {
|
|
|
286
307
|
i.invalidateQueries({ queryKey: s.activePlan }), i.invalidateQueries({ queryKey: s.nutritionPlan(e.id) });
|
|
287
308
|
}
|
|
288
309
|
});
|
|
289
|
-
},
|
|
310
|
+
}, W = () => {
|
|
290
311
|
let { apiGatewayUrl: n } = e(), i = r();
|
|
291
312
|
return t({
|
|
292
313
|
mutationFn: async (e) => (await o(`mutation MybActivateNutritionPlan($id: ID!) {
|
|
@@ -296,7 +317,7 @@ var a = () => {
|
|
|
296
317
|
i.invalidateQueries({ queryKey: s.activePlan }), i.invalidateQueries({ queryKey: s.nutritionPlan(e.id) });
|
|
297
318
|
}
|
|
298
319
|
});
|
|
299
|
-
},
|
|
320
|
+
}, G = () => {
|
|
300
321
|
let { apiGatewayUrl: n } = e(), i = r();
|
|
301
322
|
return t({
|
|
302
323
|
mutationFn: async (e) => (await o(`mutation MybLogMeal($input: MybLogMealInput!) {
|
|
@@ -306,7 +327,7 @@ var a = () => {
|
|
|
306
327
|
i.invalidateQueries({ queryKey: s.meals }), i.invalidateQueries({ queryKey: s.dailyMacros }), e.planId && i.invalidateQueries({ queryKey: s.nutritionPlan(e.planId) });
|
|
307
328
|
}
|
|
308
329
|
});
|
|
309
|
-
},
|
|
330
|
+
}, K = () => {
|
|
310
331
|
let { apiGatewayUrl: n } = e(), i = r();
|
|
311
332
|
return t({
|
|
312
333
|
mutationFn: async (e) => (await o("mutation MybDeleteMeal($id: ID!) { mybDeleteMeal(id: $id) }", { id: e }, n)).mybDeleteMeal,
|
|
@@ -314,7 +335,7 @@ var a = () => {
|
|
|
314
335
|
i.invalidateQueries({ queryKey: s.meals }), i.invalidateQueries({ queryKey: s.dailyMacros });
|
|
315
336
|
}
|
|
316
337
|
});
|
|
317
|
-
},
|
|
338
|
+
}, q = (t = {}) => {
|
|
318
339
|
let { apiGatewayUrl: r } = e();
|
|
319
340
|
return n({
|
|
320
341
|
queryKey: [...s.bodyMetrics, t],
|
|
@@ -329,7 +350,7 @@ var a = () => {
|
|
|
329
350
|
pagination: t.pagination ?? c
|
|
330
351
|
}, r)).mybListBodyMetrics
|
|
331
352
|
});
|
|
332
|
-
},
|
|
353
|
+
}, J = () => {
|
|
333
354
|
let { apiGatewayUrl: n } = e(), i = r();
|
|
334
355
|
return t({
|
|
335
356
|
mutationFn: async (e) => (await o(`mutation MybRecordBodyMetric($input: MybRecordMetricInput!) {
|
|
@@ -337,7 +358,7 @@ var a = () => {
|
|
|
337
358
|
}`, { input: e }, n)).mybRecordBodyMetric,
|
|
338
359
|
onSuccess: () => i.invalidateQueries({ queryKey: s.bodyMetrics })
|
|
339
360
|
});
|
|
340
|
-
},
|
|
361
|
+
}, Y = (t, r) => {
|
|
341
362
|
let { apiGatewayUrl: i } = e();
|
|
342
363
|
return n({
|
|
343
364
|
queryKey: [
|
|
@@ -346,13 +367,16 @@ var a = () => {
|
|
|
346
367
|
r ?? "me"
|
|
347
368
|
],
|
|
348
369
|
queryFn: async () => (await o(`query MybListRecoveryLogs($metric: MybRecoveryMetric, $memberId: ID) {
|
|
349
|
-
mybListRecoveryLogs(metric: $metric, memberId: $memberId) {
|
|
370
|
+
mybListRecoveryLogs(metric: $metric, memberId: $memberId) {
|
|
371
|
+
items { ${v} }
|
|
372
|
+
pageInfo { cursor hasMore }
|
|
373
|
+
}
|
|
350
374
|
}`, {
|
|
351
375
|
metric: t,
|
|
352
376
|
memberId: r
|
|
353
377
|
}, i)).mybListRecoveryLogs
|
|
354
378
|
});
|
|
355
|
-
},
|
|
379
|
+
}, X = () => {
|
|
356
380
|
let { apiGatewayUrl: n } = e(), i = r();
|
|
357
381
|
return t({
|
|
358
382
|
mutationFn: async (e) => (await o(`mutation MybLogRecovery($input: MybLogRecoveryInput!) {
|
|
@@ -360,7 +384,7 @@ var a = () => {
|
|
|
360
384
|
}`, { input: e }, n)).mybLogRecovery,
|
|
361
385
|
onSuccess: () => i.invalidateQueries({ queryKey: s.recovery })
|
|
362
386
|
});
|
|
363
|
-
},
|
|
387
|
+
}, Z = (t, r) => {
|
|
364
388
|
let { apiGatewayUrl: i } = e();
|
|
365
389
|
return n({
|
|
366
390
|
queryKey: [
|
|
@@ -369,13 +393,16 @@ var a = () => {
|
|
|
369
393
|
r ?? "me"
|
|
370
394
|
],
|
|
371
395
|
queryFn: async () => (await o(`query MybListCoaching($status: MybCoachingStatus, $memberId: ID) {
|
|
372
|
-
mybListCoaching(status: $status, memberId: $memberId) {
|
|
396
|
+
mybListCoaching(status: $status, memberId: $memberId) {
|
|
397
|
+
items { ${y} }
|
|
398
|
+
pageInfo { cursor hasMore }
|
|
399
|
+
}
|
|
373
400
|
}`, {
|
|
374
401
|
status: t,
|
|
375
402
|
memberId: r
|
|
376
403
|
}, i)).mybListCoaching
|
|
377
404
|
});
|
|
378
|
-
},
|
|
405
|
+
}, Q = () => {
|
|
379
406
|
let { apiGatewayUrl: n } = e(), i = r();
|
|
380
407
|
return t({
|
|
381
408
|
mutationFn: async (e) => (await o(`mutation MybCreateCoaching($input: MybCreateCoachingInput!) {
|
|
@@ -383,7 +410,7 @@ var a = () => {
|
|
|
383
410
|
}`, { input: e }, n)).mybCreateCoaching,
|
|
384
411
|
onSuccess: () => i.invalidateQueries({ queryKey: s.coaching })
|
|
385
412
|
});
|
|
386
|
-
},
|
|
413
|
+
}, $ = () => {
|
|
387
414
|
let { apiGatewayUrl: n } = e(), i = r();
|
|
388
415
|
return t({
|
|
389
416
|
mutationFn: async (e) => (await o(`mutation MybTransitionCoaching($id: ID!, $status: MybCoachingStatus!) {
|
|
@@ -393,6 +420,6 @@ var a = () => {
|
|
|
393
420
|
});
|
|
394
421
|
};
|
|
395
422
|
//#endregion
|
|
396
|
-
export {
|
|
423
|
+
export { R as useMybAchieveGoal, W as useMybActivateNutritionPlan, z as useMybActiveNutritionPlan, k as useMybAddExercise, O as useMybAddWorkout, D as useMybArchiveProgram, q as useMybBodyMetrics, Z as useMybCoaching, P as useMybCompleteSession, Q as useMybCreateCoaching, I as useMybCreateGoal, H as useMybCreateNutritionPlan, w as useMybCreateProgram, V as useMybDailyMacros, K as useMybDeleteMeal, F as useMybGoals, G as useMybLogMeal, X as useMybLogRecovery, N as useMybLogSet, B as useMybMeals, C as useMybProgram, S as useMybPrograms, J as useMybRecordBodyMetric, Y as useMybRecoveryLogs, j as useMybSession, A as useMybSessions, M as useMybStartSession, $ as useMybTransitionCoaching, E as useMybTransitionProgram, L as useMybUpdateGoal, U as useMybUpdateNutritionPlan, T as useMybUpdateProgram };
|
|
397
424
|
|
|
398
425
|
//# sourceMappingURL=useMyBodyShieldApi.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useMyBodyShieldApi.js","names":[],"sources":["../../../src/mybodyshield/hooks/useMyBodyShieldApi.ts"],"sourcesContent":["/**\n * MyBodyShield API hooks\n * @module mybodyshield/hooks\n *\n * React-Query hooks wired DIRECTLY to the real `wspace-mybodyshield-svc`\n * federated GraphQL contract (the `myb*` operations). Every query/mutation\n * below uses an inline gql string whose fields match the svc\n * `.hive-schema.graphql` exactly. No mock data.\n *\n * Transport: `graphqlFetch({ gateway: 'workspace', workspaceToken: true })` —\n * the host shell supplies the gateway base URL + a workspace-scoped token.\n */\n\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\n\nimport { useMyBodyShield } from '../context';\nimport type {\n MybConnection,\n MybProgram,\n MybProgramStatus,\n MybWorkout,\n MybWorkoutSession,\n MybSessionStatus,\n MybSessionSet,\n MybGoal,\n MybGoalStatus,\n MybMealLog,\n MybMealType,\n MybNutritionPlan,\n MybDailyMacros,\n MybBodyMetric,\n MybBodyMetricType,\n MybRecoveryLog,\n MybRecoveryMetric,\n MybCoachingRelationship,\n MybCoachingStatus,\n MybCreateProgramInput,\n MybUpdateProgramInput,\n MybCreateWorkoutInput,\n MybCreateExerciseInput,\n MybStartSessionInput,\n MybLogSetInput,\n MybCompleteSessionInput,\n MybCreateGoalInput,\n MybUpdateGoalInput,\n MybCreateNutritionPlanInput,\n MybUpdateNutritionPlanInput,\n MybLogMealInput,\n MybRecordMetricInput,\n MybLogRecoveryInput,\n MybCreateCoachingInput,\n PaginationInput,\n} from '../types';\n\n// ============================================================================\n// Transport\n// ============================================================================\n\n/**\n * fe-libs `graphqlFetch` coalesces `query` responses in an in-memory cache keyed\n * by operationName+variables for ~15s (mutations are never cached). A read\n * issued right after a write (e.g. React-Query refetch triggered by our\n * `invalidateQueries`) would otherwise return the PRE-mutation response until the\n * TTL expires — making just-created/updated rows invisible until a full reload.\n * fe-libs' documented remedy is that write-performing callers clear those caches;\n * we do exactly that after every myb mutation so every follow-up read is fresh.\n */\ntype GraphqlReadCacheGlobals = {\n __graphqlFetchResponseCache?: { clear: () => void };\n __graphqlFetchPendingResponses?: { clear: () => void };\n __boffGraphQLResponseCache?: { clear: () => void };\n};\n\nconst clearGraphqlReadCaches = (): void => {\n const g = globalThis as GraphqlReadCacheGlobals;\n g.__graphqlFetchResponseCache?.clear();\n g.__graphqlFetchPendingResponses?.clear();\n g.__boffGraphQLResponseCache?.clear();\n};\n\nconst mybQuery = async <TData>(\n query: string,\n variables: Record<string, unknown>,\n baseUrl?: string\n): Promise<TData> => {\n const response = await graphqlFetch<TData>({\n gateway: 'workspace',\n baseUrl,\n query,\n variables,\n workspaceToken: true,\n });\n\n if (response.errors?.length) {\n throw new Error(response.errors[0]?.message ?? 'GraphQL request failed');\n }\n if (!response.data) {\n throw new Error('GraphQL response did not include data');\n }\n if (/^\\s*mutation\\b/.test(query)) {\n clearGraphqlReadCaches();\n }\n return response.data;\n};\n\n// React-Query key roots (one namespace per domain entity).\nconst KEY = {\n programs: ['mybodyshield', 'programs'] as const,\n program: (id: string) => ['mybodyshield', 'program', id] as const,\n programWorkouts: (id: string) => ['mybodyshield', 'program-workouts', id] as const,\n sessions: ['mybodyshield', 'sessions'] as const,\n session: (id: string) => ['mybodyshield', 'session', id] as const,\n goals: ['mybodyshield', 'goals'] as const,\n goal: (id: string) => ['mybodyshield', 'goal', id] as const,\n meals: ['mybodyshield', 'meals'] as const,\n nutritionPlan: (id: string) => ['mybodyshield', 'nutrition-plan', id] as const,\n activePlan: ['mybodyshield', 'active-plan'] as const,\n dailyMacros: ['mybodyshield', 'daily-macros'] as const,\n bodyMetrics: ['mybodyshield', 'body-metrics'] as const,\n recovery: ['mybodyshield', 'recovery'] as const,\n coaching: ['mybodyshield', 'coaching'] as const,\n};\n\nconst DEFAULT_PAGE: PaginationInput = { limit: 50 };\n\n// ============================================================================\n// Reusable gql field selections (codegen-free strictness)\n// ============================================================================\n\nconst PROGRAM_FIELDS = `\n id workspaceId memberId coachId name description goalType level phase status\n weeks daysPerWeek startDate endDate tags createdAt updatedAt\n`;\n\nconst EXERCISE_FIELDS = `\n id workspaceId workoutId name instructions muscleGroup sets reps targetWeight\n restSeconds orderIndex demo { id } createdAt updatedAt\n`;\n\nconst WORKOUT_FIELDS = `\n id workspaceId programId name description type dayOfWeek weekNumber orderIndex\n durationMin createdAt updatedAt\n`;\n\nconst SET_FIELDS = `\n id workspaceId sessionId exerciseName setNumber reps weight distanceM durationSec\n isPersonalRecord createdAt updatedAt\n`;\n\nconst SESSION_FIELDS = `\n id workspaceId memberId programId workoutId status scheduledFor startedAt completedAt\n caloriesBurned perceivedExertion notes createdAt updatedAt\n`;\n\nconst GOAL_FIELDS = `\n id workspaceId memberId title description type status targetValue currentValue\n baselineValue unit targetDate achievedAt createdAt updatedAt\n`;\n\nconst MEAL_FIELDS = `\n id workspaceId memberId planId name mealType calories proteinG carbsG fatG servings\n isFavorite loggedAt createdAt updatedAt\n`;\n\nconst PLAN_FIELDS = `\n id workspaceId memberId coachId name goalType calorieTarget proteinTarget carbsTarget\n fatTarget startDate endDate isActive notes createdAt updatedAt\n`;\n\nconst METRIC_FIELDS = `\n id workspaceId memberId type value unit measuredAt notes photo { id } createdAt updatedAt\n`;\n\nconst RECOVERY_FIELDS = `\n id workspaceId memberId metric score sleepHours notes loggedAt createdAt updatedAt\n`;\n\nconst COACHING_FIELDS = `\n id workspaceId coachId memberId status specialty shareWorkouts shareNutrition\n shareMetrics startedAt endedAt notes createdAt updatedAt\n`;\n\nconst MACROS_FIELDS = `\n date calories proteinG carbsG fatG calorieTarget proteinTarget carbsTarget fatTarget\n`;\n\n// ============================================================================\n// Programs (Training)\n// ============================================================================\n\nexport interface ProgramFilters {\n status?: MybProgramStatus;\n memberId?: string;\n pagination?: PaginationInput;\n}\n\nexport const useMybPrograms = (filters: ProgramFilters = {}) => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useQuery({\n queryKey: [...KEY.programs, filters],\n queryFn: async () => {\n const result = await mybQuery<{ mybListPrograms: MybConnection<MybProgram> }>(\n `query MybListPrograms($status: MybProgramStatus, $memberId: ID, $pagination: PaginationInput) {\n mybListPrograms(status: $status, memberId: $memberId, pagination: $pagination) {\n items { ${PROGRAM_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n status: filters.status,\n memberId: filters.memberId,\n pagination: filters.pagination ?? DEFAULT_PAGE,\n },\n apiGatewayUrl\n );\n return result.mybListPrograms;\n },\n });\n};\n\nexport const useMybProgram = (id: string | undefined) => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useQuery({\n queryKey: KEY.program(id ?? ''),\n enabled: Boolean(id),\n queryFn: async () => {\n const result = await mybQuery<{ mybProgram: MybProgram | null }>(\n `query MybProgram($id: ID!) {\n mybProgram(id: $id) {\n ${PROGRAM_FIELDS}\n workouts {\n ${WORKOUT_FIELDS}\n exercises { ${EXERCISE_FIELDS} }\n }\n }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.mybProgram;\n },\n });\n};\n\nexport const useMybProgramWorkouts = (programId: string | undefined) => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useQuery({\n queryKey: KEY.programWorkouts(programId ?? ''),\n enabled: Boolean(programId),\n queryFn: async () => {\n const result = await mybQuery<{ mybProgramWorkouts: MybWorkout[] }>(\n `query MybProgramWorkouts($programId: ID!) {\n mybProgramWorkouts(programId: $programId) {\n ${WORKOUT_FIELDS}\n exercises { ${EXERCISE_FIELDS} }\n }\n }`,\n { programId },\n apiGatewayUrl\n );\n return result.mybProgramWorkouts;\n },\n });\n};\n\nexport const useMybCreateProgram = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: MybCreateProgramInput) => {\n const result = await mybQuery<{ mybCreateProgram: MybProgram }>(\n `mutation MybCreateProgram($input: MybCreateProgramInput!) {\n mybCreateProgram(input: $input) { ${PROGRAM_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.mybCreateProgram;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.programs }),\n });\n};\n\nexport const useMybUpdateProgram = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (vars: { id: string; input: MybUpdateProgramInput }) => {\n const result = await mybQuery<{ mybUpdateProgram: MybProgram }>(\n `mutation MybUpdateProgram($id: ID!, $input: MybUpdateProgramInput!) {\n mybUpdateProgram(id: $id, input: $input) { ${PROGRAM_FIELDS} }\n }`,\n vars,\n apiGatewayUrl\n );\n return result.mybUpdateProgram;\n },\n onSuccess: (data) => {\n qc.invalidateQueries({ queryKey: KEY.programs });\n qc.invalidateQueries({ queryKey: KEY.program(data.id) });\n },\n });\n};\n\nexport const useMybTransitionProgram = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (vars: { id: string; status: MybProgramStatus }) => {\n const result = await mybQuery<{ mybTransitionProgram: MybProgram }>(\n `mutation MybTransitionProgram($id: ID!, $status: MybProgramStatus!) {\n mybTransitionProgram(id: $id, status: $status) { ${PROGRAM_FIELDS} }\n }`,\n vars,\n apiGatewayUrl\n );\n return result.mybTransitionProgram;\n },\n onSuccess: (data) => {\n qc.invalidateQueries({ queryKey: KEY.programs });\n qc.invalidateQueries({ queryKey: KEY.program(data.id) });\n },\n });\n};\n\nexport const useMybArchiveProgram = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await mybQuery<{ mybArchiveProgram: boolean }>(\n `mutation MybArchiveProgram($id: ID!) { mybArchiveProgram(id: $id) }`,\n { id },\n apiGatewayUrl\n );\n return result.mybArchiveProgram;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.programs }),\n });\n};\n\nexport const useMybAddWorkout = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: MybCreateWorkoutInput) => {\n const result = await mybQuery<{ mybAddWorkout: MybWorkout }>(\n `mutation MybAddWorkout($input: MybCreateWorkoutInput!) {\n mybAddWorkout(input: $input) { ${WORKOUT_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.mybAddWorkout;\n },\n onSuccess: (data) => {\n qc.invalidateQueries({ queryKey: KEY.program(data.programId) });\n qc.invalidateQueries({ queryKey: KEY.programWorkouts(data.programId) });\n },\n });\n};\n\nexport const useMybAddExercise = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: MybCreateExerciseInput) => {\n const result = await mybQuery<{ mybAddExercise: { id: string; workoutId: string } }>(\n `mutation MybAddExercise($input: MybCreateExerciseInput!) {\n mybAddExercise(input: $input) { id workoutId }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.mybAddExercise;\n },\n onSuccess: () => {\n // Exercises live under a program's workouts; invalidate program + workout\n // caches so the newly-added exercise appears on the program detail tree.\n qc.invalidateQueries({ queryKey: ['mybodyshield', 'program'] });\n qc.invalidateQueries({ queryKey: ['mybodyshield', 'program-workouts'] });\n },\n });\n};\n\n// ============================================================================\n// Sessions / Sets (Training — live logging)\n// ============================================================================\n\nexport interface SessionFilters {\n status?: MybSessionStatus;\n programId?: string;\n pagination?: PaginationInput;\n}\n\nexport const useMybSessions = (filters: SessionFilters = {}) => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useQuery({\n queryKey: [...KEY.sessions, filters],\n queryFn: async () => {\n const result = await mybQuery<{ mybListSessions: MybConnection<MybWorkoutSession> }>(\n `query MybListSessions($status: MybSessionStatus, $programId: ID, $pagination: PaginationInput) {\n mybListSessions(status: $status, programId: $programId, pagination: $pagination) {\n items { ${SESSION_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n status: filters.status,\n programId: filters.programId,\n pagination: filters.pagination ?? DEFAULT_PAGE,\n },\n apiGatewayUrl\n );\n return result.mybListSessions;\n },\n });\n};\n\nexport const useMybSession = (id: string | undefined) => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useQuery({\n queryKey: KEY.session(id ?? ''),\n enabled: Boolean(id),\n queryFn: async () => {\n const result = await mybQuery<{ mybSession: MybWorkoutSession | null }>(\n `query MybSession($id: ID!) {\n mybSession(id: $id) {\n ${SESSION_FIELDS}\n sets { ${SET_FIELDS} }\n }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.mybSession;\n },\n });\n};\n\nexport const useMybStartSession = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: MybStartSessionInput) => {\n const result = await mybQuery<{ mybStartSession: MybWorkoutSession }>(\n `mutation MybStartSession($input: MybStartSessionInput!) {\n mybStartSession(input: $input) { ${SESSION_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.mybStartSession;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.sessions }),\n });\n};\n\nexport const useMybLogSet = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: MybLogSetInput) => {\n const result = await mybQuery<{ mybLogSet: MybSessionSet }>(\n `mutation MybLogSet($input: MybLogSetInput!) {\n mybLogSet(input: $input) { ${SET_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.mybLogSet;\n },\n onSuccess: (data) => {\n qc.invalidateQueries({ queryKey: KEY.session(data.sessionId) });\n qc.invalidateQueries({ queryKey: KEY.sessions });\n },\n });\n};\n\nexport const useMybCompleteSession = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (vars: { id: string; input: MybCompleteSessionInput }) => {\n const result = await mybQuery<{ mybCompleteSession: MybWorkoutSession }>(\n `mutation MybCompleteSession($id: ID!, $input: MybCompleteSessionInput!) {\n mybCompleteSession(id: $id, input: $input) { ${SESSION_FIELDS} }\n }`,\n vars,\n apiGatewayUrl\n );\n return result.mybCompleteSession;\n },\n onSuccess: (data) => {\n qc.invalidateQueries({ queryKey: KEY.session(data.id) });\n qc.invalidateQueries({ queryKey: KEY.sessions });\n },\n });\n};\n\n// ============================================================================\n// Goals (Progress)\n// ============================================================================\n\nexport interface GoalFilters {\n status?: MybGoalStatus;\n memberId?: string;\n pagination?: PaginationInput;\n}\n\nexport const useMybGoals = (filters: GoalFilters = {}) => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useQuery({\n queryKey: [...KEY.goals, filters],\n queryFn: async () => {\n const result = await mybQuery<{ mybListGoals: MybConnection<MybGoal> }>(\n `query MybListGoals($status: MybGoalStatus, $memberId: ID, $pagination: PaginationInput) {\n mybListGoals(status: $status, memberId: $memberId, pagination: $pagination) {\n items { ${GOAL_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n status: filters.status,\n memberId: filters.memberId,\n pagination: filters.pagination ?? DEFAULT_PAGE,\n },\n apiGatewayUrl\n );\n return result.mybListGoals;\n },\n });\n};\n\nexport const useMybCreateGoal = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: MybCreateGoalInput) => {\n const result = await mybQuery<{ mybCreateGoal: MybGoal }>(\n `mutation MybCreateGoal($input: MybCreateGoalInput!) {\n mybCreateGoal(input: $input) { ${GOAL_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.mybCreateGoal;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.goals }),\n });\n};\n\nexport const useMybUpdateGoal = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (vars: { id: string; input: MybUpdateGoalInput }) => {\n const result = await mybQuery<{ mybUpdateGoal: MybGoal }>(\n `mutation MybUpdateGoal($id: ID!, $input: MybUpdateGoalInput!) {\n mybUpdateGoal(id: $id, input: $input) { ${GOAL_FIELDS} }\n }`,\n vars,\n apiGatewayUrl\n );\n return result.mybUpdateGoal;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.goals }),\n });\n};\n\nexport const useMybAchieveGoal = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await mybQuery<{ mybAchieveGoal: MybGoal }>(\n `mutation MybAchieveGoal($id: ID!) {\n mybAchieveGoal(id: $id) { ${GOAL_FIELDS} }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.mybAchieveGoal;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.goals }),\n });\n};\n\n// ============================================================================\n// Nutrition (plans, meals, macros)\n// ============================================================================\n\nexport const useMybActiveNutritionPlan = (memberId?: string) => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useQuery({\n queryKey: [...KEY.activePlan, memberId ?? 'me'],\n queryFn: async () => {\n const result = await mybQuery<{ mybActiveNutritionPlan: MybNutritionPlan | null }>(\n `query MybActiveNutritionPlan($memberId: ID) {\n mybActiveNutritionPlan(memberId: $memberId) { ${PLAN_FIELDS} }\n }`,\n { memberId },\n apiGatewayUrl\n );\n return result.mybActiveNutritionPlan;\n },\n });\n};\n\nexport const useMybNutritionPlan = (id: string | undefined) => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useQuery({\n queryKey: KEY.nutritionPlan(id ?? ''),\n enabled: Boolean(id),\n queryFn: async () => {\n const result = await mybQuery<{ mybNutritionPlan: MybNutritionPlan | null }>(\n `query MybNutritionPlan($id: ID!) {\n mybNutritionPlan(id: $id) {\n ${PLAN_FIELDS}\n meals { ${MEAL_FIELDS} }\n }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.mybNutritionPlan;\n },\n });\n};\n\nexport interface MealFilters {\n mealType?: MybMealType;\n planId?: string;\n pagination?: PaginationInput;\n}\n\nexport const useMybMeals = (filters: MealFilters = {}) => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useQuery({\n queryKey: [...KEY.meals, filters],\n queryFn: async () => {\n const result = await mybQuery<{ mybListMeals: MybConnection<MybMealLog> }>(\n `query MybListMeals($mealType: MybMealType, $planId: ID, $pagination: PaginationInput) {\n mybListMeals(mealType: $mealType, planId: $planId, pagination: $pagination) {\n items { ${MEAL_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n mealType: filters.mealType,\n planId: filters.planId,\n pagination: filters.pagination ?? DEFAULT_PAGE,\n },\n apiGatewayUrl\n );\n return result.mybListMeals;\n },\n });\n};\n\nexport const useMybDailyMacros = (date?: string, memberId?: string) => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useQuery({\n queryKey: [...KEY.dailyMacros, date ?? 'today', memberId ?? 'me'],\n queryFn: async () => {\n const result = await mybQuery<{ mybDailyMacros: MybDailyMacros }>(\n `query MybDailyMacros($date: DateTime, $memberId: ID) {\n mybDailyMacros(date: $date, memberId: $memberId) { ${MACROS_FIELDS} }\n }`,\n { date, memberId },\n apiGatewayUrl\n );\n return result.mybDailyMacros;\n },\n });\n};\n\nexport const useMybCreateNutritionPlan = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: MybCreateNutritionPlanInput) => {\n const result = await mybQuery<{ mybCreateNutritionPlan: MybNutritionPlan }>(\n `mutation MybCreateNutritionPlan($input: MybCreateNutritionPlanInput!) {\n mybCreateNutritionPlan(input: $input) { ${PLAN_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.mybCreateNutritionPlan;\n },\n onSuccess: () => {\n qc.invalidateQueries({ queryKey: KEY.activePlan });\n },\n });\n};\n\nexport const useMybUpdateNutritionPlan = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (vars: { id: string; input: MybUpdateNutritionPlanInput }) => {\n const result = await mybQuery<{ mybUpdateNutritionPlan: MybNutritionPlan }>(\n `mutation MybUpdateNutritionPlan($id: ID!, $input: MybUpdateNutritionPlanInput!) {\n mybUpdateNutritionPlan(id: $id, input: $input) { ${PLAN_FIELDS} }\n }`,\n vars,\n apiGatewayUrl\n );\n return result.mybUpdateNutritionPlan;\n },\n onSuccess: (data) => {\n qc.invalidateQueries({ queryKey: KEY.activePlan });\n qc.invalidateQueries({ queryKey: KEY.nutritionPlan(data.id) });\n },\n });\n};\n\nexport const useMybActivateNutritionPlan = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await mybQuery<{ mybActivateNutritionPlan: MybNutritionPlan }>(\n `mutation MybActivateNutritionPlan($id: ID!) {\n mybActivateNutritionPlan(id: $id) { ${PLAN_FIELDS} }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.mybActivateNutritionPlan;\n },\n onSuccess: (data) => {\n qc.invalidateQueries({ queryKey: KEY.activePlan });\n qc.invalidateQueries({ queryKey: KEY.nutritionPlan(data.id) });\n },\n });\n};\n\nexport const useMybLogMeal = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: MybLogMealInput) => {\n const result = await mybQuery<{ mybLogMeal: MybMealLog }>(\n `mutation MybLogMeal($input: MybLogMealInput!) {\n mybLogMeal(input: $input) { ${MEAL_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.mybLogMeal;\n },\n onSuccess: (data) => {\n qc.invalidateQueries({ queryKey: KEY.meals });\n qc.invalidateQueries({ queryKey: KEY.dailyMacros });\n if (data.planId) qc.invalidateQueries({ queryKey: KEY.nutritionPlan(data.planId) });\n },\n });\n};\n\nexport const useMybDeleteMeal = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await mybQuery<{ mybDeleteMeal: boolean }>(\n `mutation MybDeleteMeal($id: ID!) { mybDeleteMeal(id: $id) }`,\n { id },\n apiGatewayUrl\n );\n return result.mybDeleteMeal;\n },\n onSuccess: () => {\n qc.invalidateQueries({ queryKey: KEY.meals });\n qc.invalidateQueries({ queryKey: KEY.dailyMacros });\n },\n });\n};\n\n// ============================================================================\n// Body metrics (Progress)\n// ============================================================================\n\nexport interface BodyMetricFilters {\n type?: MybBodyMetricType;\n memberId?: string;\n pagination?: PaginationInput;\n}\n\nexport const useMybBodyMetrics = (filters: BodyMetricFilters = {}) => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useQuery({\n queryKey: [...KEY.bodyMetrics, filters],\n queryFn: async () => {\n const result = await mybQuery<{ mybListBodyMetrics: MybConnection<MybBodyMetric> }>(\n `query MybListBodyMetrics($type: MybBodyMetricType, $memberId: ID, $pagination: PaginationInput) {\n mybListBodyMetrics(type: $type, memberId: $memberId, pagination: $pagination) {\n items { ${METRIC_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n type: filters.type,\n memberId: filters.memberId,\n pagination: filters.pagination ?? DEFAULT_PAGE,\n },\n apiGatewayUrl\n );\n return result.mybListBodyMetrics;\n },\n });\n};\n\nexport const useMybRecordBodyMetric = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: MybRecordMetricInput) => {\n const result = await mybQuery<{ mybRecordBodyMetric: MybBodyMetric }>(\n `mutation MybRecordBodyMetric($input: MybRecordMetricInput!) {\n mybRecordBodyMetric(input: $input) { ${METRIC_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.mybRecordBodyMetric;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.bodyMetrics }),\n });\n};\n\n// ============================================================================\n// Recovery (Wellbeing)\n// ============================================================================\n\nexport const useMybRecoveryLogs = (metric?: MybRecoveryMetric, memberId?: string) => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useQuery({\n queryKey: [...KEY.recovery, metric ?? 'all', memberId ?? 'me'],\n queryFn: async () => {\n const result = await mybQuery<{ mybListRecoveryLogs: MybRecoveryLog[] }>(\n `query MybListRecoveryLogs($metric: MybRecoveryMetric, $memberId: ID) {\n mybListRecoveryLogs(metric: $metric, memberId: $memberId) { ${RECOVERY_FIELDS} }\n }`,\n { metric, memberId },\n apiGatewayUrl\n );\n return result.mybListRecoveryLogs;\n },\n });\n};\n\nexport const useMybLogRecovery = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: MybLogRecoveryInput) => {\n const result = await mybQuery<{ mybLogRecovery: MybRecoveryLog }>(\n `mutation MybLogRecovery($input: MybLogRecoveryInput!) {\n mybLogRecovery(input: $input) { ${RECOVERY_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.mybLogRecovery;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.recovery }),\n });\n};\n\n// ============================================================================\n// Coaching (Wellbeing)\n// ============================================================================\n\nexport const useMybCoaching = (status?: MybCoachingStatus, memberId?: string) => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useQuery({\n queryKey: [...KEY.coaching, status ?? 'all', memberId ?? 'me'],\n queryFn: async () => {\n const result = await mybQuery<{ mybListCoaching: MybCoachingRelationship[] }>(\n `query MybListCoaching($status: MybCoachingStatus, $memberId: ID) {\n mybListCoaching(status: $status, memberId: $memberId) { ${COACHING_FIELDS} }\n }`,\n { status, memberId },\n apiGatewayUrl\n );\n return result.mybListCoaching;\n },\n });\n};\n\nexport const useMybCreateCoaching = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: MybCreateCoachingInput) => {\n const result = await mybQuery<{ mybCreateCoaching: MybCoachingRelationship }>(\n `mutation MybCreateCoaching($input: MybCreateCoachingInput!) {\n mybCreateCoaching(input: $input) { ${COACHING_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.mybCreateCoaching;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.coaching }),\n });\n};\n\nexport const useMybTransitionCoaching = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (vars: { id: string; status: MybCoachingStatus }) => {\n const result = await mybQuery<{ mybTransitionCoaching: MybCoachingRelationship }>(\n `mutation MybTransitionCoaching($id: ID!, $status: MybCoachingStatus!) {\n mybTransitionCoaching(id: $id, status: $status) { ${COACHING_FIELDS} }\n }`,\n vars,\n apiGatewayUrl\n );\n return result.mybTransitionCoaching;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.coaching }),\n });\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useMyBodyShieldApi.js","names":[],"sources":["../../../src/mybodyshield/hooks/useMyBodyShieldApi.ts"],"sourcesContent":["/**\n * MyBodyShield API hooks\n * @module mybodyshield/hooks\n *\n * React-Query hooks wired DIRECTLY to the real `wspace-mybodyshield-svc`\n * federated GraphQL contract (the `myb*` operations). Every query/mutation\n * below uses an inline gql string whose fields match the svc\n * `.hive-schema.graphql` exactly. No mock data.\n *\n * Transport: `graphqlFetch({ gateway: 'workspace', workspaceToken: true })` —\n * the host shell supplies the gateway base URL + a workspace-scoped token.\n */\n\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\n\nimport { useMyBodyShield } from '../context';\nimport type {\n MybConnection,\n MybProgram,\n MybProgramStatus,\n MybWorkout,\n MybExercise,\n MybWorkoutSession,\n MybSessionStatus,\n MybSessionSet,\n MybGoal,\n MybGoalStatus,\n MybMealLog,\n MybMealType,\n MybNutritionPlan,\n MybDailyMacros,\n MybBodyMetric,\n MybBodyMetricType,\n MybRecoveryLog,\n MybRecoveryMetric,\n MybCoachingRelationship,\n MybCoachingStatus,\n MybCreateProgramInput,\n MybUpdateProgramInput,\n MybCreateWorkoutInput,\n MybCreateExerciseInput,\n MybStartSessionInput,\n MybLogSetInput,\n MybCompleteSessionInput,\n MybCreateGoalInput,\n MybUpdateGoalInput,\n MybCreateNutritionPlanInput,\n MybUpdateNutritionPlanInput,\n MybLogMealInput,\n MybRecordMetricInput,\n MybLogRecoveryInput,\n MybCreateCoachingInput,\n PaginationInput,\n} from '../types';\n\n// ============================================================================\n// Transport\n// ============================================================================\n\n/**\n * fe-libs `graphqlFetch` coalesces `query` responses in an in-memory cache keyed\n * by operationName+variables for ~15s (mutations are never cached). A read\n * issued right after a write (e.g. React-Query refetch triggered by our\n * `invalidateQueries`) would otherwise return the PRE-mutation response until the\n * TTL expires — making just-created/updated rows invisible until a full reload.\n * fe-libs' documented remedy is that write-performing callers clear those caches;\n * we do exactly that after every myb mutation so every follow-up read is fresh.\n */\ntype GraphqlReadCacheGlobals = {\n __graphqlFetchResponseCache?: { clear: () => void };\n __graphqlFetchPendingResponses?: { clear: () => void };\n __boffGraphQLResponseCache?: { clear: () => void };\n};\n\nconst clearGraphqlReadCaches = (): void => {\n const g = globalThis as GraphqlReadCacheGlobals;\n g.__graphqlFetchResponseCache?.clear();\n g.__graphqlFetchPendingResponses?.clear();\n g.__boffGraphQLResponseCache?.clear();\n};\n\nconst mybQuery = async <TData>(\n query: string,\n variables: Record<string, unknown>,\n baseUrl?: string\n): Promise<TData> => {\n const response = await graphqlFetch<TData>({\n gateway: 'workspace',\n baseUrl,\n query,\n variables,\n workspaceToken: true,\n });\n\n if (response.errors?.length) {\n throw new Error(response.errors[0]?.message ?? 'GraphQL request failed');\n }\n if (!response.data) {\n throw new Error('GraphQL response did not include data');\n }\n if (/^\\s*mutation\\b/.test(query)) {\n clearGraphqlReadCaches();\n }\n return response.data;\n};\n\n// React-Query key roots (one namespace per domain entity).\nconst KEY = {\n programs: ['mybodyshield', 'programs'] as const,\n program: (id: string) => ['mybodyshield', 'program', id] as const,\n programWorkouts: (id: string) => ['mybodyshield', 'program-workouts', id] as const,\n sessions: ['mybodyshield', 'sessions'] as const,\n session: (id: string) => ['mybodyshield', 'session', id] as const,\n goals: ['mybodyshield', 'goals'] as const,\n goal: (id: string) => ['mybodyshield', 'goal', id] as const,\n meals: ['mybodyshield', 'meals'] as const,\n nutritionPlan: (id: string) => ['mybodyshield', 'nutrition-plan', id] as const,\n activePlan: ['mybodyshield', 'active-plan'] as const,\n dailyMacros: ['mybodyshield', 'daily-macros'] as const,\n bodyMetrics: ['mybodyshield', 'body-metrics'] as const,\n recovery: ['mybodyshield', 'recovery'] as const,\n coaching: ['mybodyshield', 'coaching'] as const,\n};\n\nconst DEFAULT_PAGE: PaginationInput = { limit: 50 };\n\n// ============================================================================\n// Reusable gql field selections (codegen-free strictness)\n// ============================================================================\n\nconst PROGRAM_FIELDS = `\n id workspaceId memberId coachId name description goalType level phase status\n weeks daysPerWeek startDate endDate tags createdAt updatedAt\n`;\n\nconst EXERCISE_FIELDS = `\n id workspaceId workoutId name instructions muscleGroup sets reps targetWeight\n restSeconds orderIndex demo { id } createdAt updatedAt\n`;\n\nconst WORKOUT_FIELDS = `\n id workspaceId programId name description type dayOfWeek weekNumber orderIndex\n durationMin createdAt updatedAt\n`;\n\nconst SET_FIELDS = `\n id workspaceId sessionId exerciseName setNumber reps weight distanceM durationSec\n isPersonalRecord createdAt updatedAt\n`;\n\nconst SESSION_FIELDS = `\n id workspaceId memberId programId workoutId status scheduledFor startedAt completedAt\n caloriesBurned perceivedExertion notes createdAt updatedAt\n`;\n\nconst GOAL_FIELDS = `\n id workspaceId memberId title description type status targetValue currentValue\n baselineValue unit targetDate achievedAt createdAt updatedAt\n`;\n\nconst MEAL_FIELDS = `\n id workspaceId memberId planId name mealType calories proteinG carbsG fatG servings\n isFavorite loggedAt createdAt updatedAt\n`;\n\nconst PLAN_FIELDS = `\n id workspaceId memberId coachId name goalType calorieTarget proteinTarget carbsTarget\n fatTarget startDate endDate isActive notes createdAt updatedAt\n`;\n\nconst METRIC_FIELDS = `\n id workspaceId memberId type value unit measuredAt notes photo { id } createdAt updatedAt\n`;\n\nconst RECOVERY_FIELDS = `\n id workspaceId memberId metric score sleepHours notes loggedAt createdAt updatedAt\n`;\n\nconst COACHING_FIELDS = `\n id workspaceId coachId memberId status specialty shareWorkouts shareNutrition\n shareMetrics startedAt endedAt notes createdAt updatedAt\n`;\n\nconst MACROS_FIELDS = `\n date calories proteinG carbsG fatG calorieTarget proteinTarget carbsTarget fatTarget\n`;\n\n/**\n * `MybWorkout.exercises` is a paginated `MybExerciseConnection` on the wire\n * (see `.hive-schema.graphql`); unwrap it back to the flat `MybExercise[]`\n * shape the rest of the app consumes.\n */\ntype MybWorkoutWithExerciseConnection = Omit<MybWorkout, 'exercises'> & {\n exercises: { items: MybExercise[] };\n};\n\nconst unwrapWorkoutItems = (items: MybWorkoutWithExerciseConnection[]): MybWorkout[] =>\n items.map(({ exercises, ...workout }) => ({ ...workout, exercises: exercises.items }));\n\n// ============================================================================\n// Programs (Training)\n// ============================================================================\n\nexport interface ProgramFilters {\n status?: MybProgramStatus;\n memberId?: string;\n pagination?: PaginationInput;\n}\n\nexport const useMybPrograms = (filters: ProgramFilters = {}) => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useQuery({\n queryKey: [...KEY.programs, filters],\n queryFn: async () => {\n const result = await mybQuery<{ mybListPrograms: MybConnection<MybProgram> }>(\n `query MybListPrograms($status: MybProgramStatus, $memberId: ID, $pagination: PaginationInput) {\n mybListPrograms(status: $status, memberId: $memberId, pagination: $pagination) {\n items { ${PROGRAM_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n status: filters.status,\n memberId: filters.memberId,\n pagination: filters.pagination ?? DEFAULT_PAGE,\n },\n apiGatewayUrl\n );\n return result.mybListPrograms;\n },\n });\n};\n\nexport const useMybProgram = (id: string | undefined) => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useQuery({\n queryKey: KEY.program(id ?? ''),\n enabled: Boolean(id),\n queryFn: async () => {\n const result = await mybQuery<{\n mybProgram:\n | (Omit<MybProgram, 'workouts'> & { workouts: { items: MybWorkoutWithExerciseConnection[] } })\n | null;\n }>(\n `query MybProgram($id: ID!) {\n mybProgram(id: $id) {\n ${PROGRAM_FIELDS}\n workouts {\n items {\n ${WORKOUT_FIELDS}\n exercises { items { ${EXERCISE_FIELDS} } }\n }\n }\n }\n }`,\n { id },\n apiGatewayUrl\n );\n if (!result.mybProgram) return null;\n const { workouts, ...program } = result.mybProgram;\n return { ...program, workouts: unwrapWorkoutItems(workouts.items) };\n },\n });\n};\n\nexport const useMybProgramWorkouts = (programId: string | undefined) => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useQuery({\n queryKey: KEY.programWorkouts(programId ?? ''),\n enabled: Boolean(programId),\n queryFn: async () => {\n const result = await mybQuery<{\n mybProgramWorkouts: { items: MybWorkoutWithExerciseConnection[] };\n }>(\n `query MybProgramWorkouts($programId: ID!) {\n mybProgramWorkouts(programId: $programId) {\n items {\n ${WORKOUT_FIELDS}\n exercises { items { ${EXERCISE_FIELDS} } }\n }\n }\n }`,\n { programId },\n apiGatewayUrl\n );\n return unwrapWorkoutItems(result.mybProgramWorkouts.items);\n },\n });\n};\n\nexport const useMybCreateProgram = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: MybCreateProgramInput) => {\n const result = await mybQuery<{ mybCreateProgram: MybProgram }>(\n `mutation MybCreateProgram($input: MybCreateProgramInput!) {\n mybCreateProgram(input: $input) { ${PROGRAM_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.mybCreateProgram;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.programs }),\n });\n};\n\nexport const useMybUpdateProgram = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (vars: { id: string; input: MybUpdateProgramInput }) => {\n const result = await mybQuery<{ mybUpdateProgram: MybProgram }>(\n `mutation MybUpdateProgram($id: ID!, $input: MybUpdateProgramInput!) {\n mybUpdateProgram(id: $id, input: $input) { ${PROGRAM_FIELDS} }\n }`,\n vars,\n apiGatewayUrl\n );\n return result.mybUpdateProgram;\n },\n onSuccess: (data) => {\n qc.invalidateQueries({ queryKey: KEY.programs });\n qc.invalidateQueries({ queryKey: KEY.program(data.id) });\n },\n });\n};\n\nexport const useMybTransitionProgram = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (vars: { id: string; status: MybProgramStatus }) => {\n const result = await mybQuery<{ mybTransitionProgram: MybProgram }>(\n `mutation MybTransitionProgram($id: ID!, $status: MybProgramStatus!) {\n mybTransitionProgram(id: $id, status: $status) { ${PROGRAM_FIELDS} }\n }`,\n vars,\n apiGatewayUrl\n );\n return result.mybTransitionProgram;\n },\n onSuccess: (data) => {\n qc.invalidateQueries({ queryKey: KEY.programs });\n qc.invalidateQueries({ queryKey: KEY.program(data.id) });\n },\n });\n};\n\nexport const useMybArchiveProgram = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await mybQuery<{ mybArchiveProgram: boolean }>(\n `mutation MybArchiveProgram($id: ID!) { mybArchiveProgram(id: $id) }`,\n { id },\n apiGatewayUrl\n );\n return result.mybArchiveProgram;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.programs }),\n });\n};\n\nexport const useMybAddWorkout = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: MybCreateWorkoutInput) => {\n const result = await mybQuery<{ mybAddWorkout: MybWorkout }>(\n `mutation MybAddWorkout($input: MybCreateWorkoutInput!) {\n mybAddWorkout(input: $input) { ${WORKOUT_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.mybAddWorkout;\n },\n onSuccess: (data) => {\n qc.invalidateQueries({ queryKey: KEY.program(data.programId) });\n qc.invalidateQueries({ queryKey: KEY.programWorkouts(data.programId) });\n },\n });\n};\n\nexport const useMybAddExercise = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: MybCreateExerciseInput) => {\n const result = await mybQuery<{ mybAddExercise: { id: string; workoutId: string } }>(\n `mutation MybAddExercise($input: MybCreateExerciseInput!) {\n mybAddExercise(input: $input) { id workoutId }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.mybAddExercise;\n },\n onSuccess: () => {\n // Exercises live under a program's workouts; invalidate program + workout\n // caches so the newly-added exercise appears on the program detail tree.\n qc.invalidateQueries({ queryKey: ['mybodyshield', 'program'] });\n qc.invalidateQueries({ queryKey: ['mybodyshield', 'program-workouts'] });\n },\n });\n};\n\n// ============================================================================\n// Sessions / Sets (Training — live logging)\n// ============================================================================\n\nexport interface SessionFilters {\n status?: MybSessionStatus;\n programId?: string;\n pagination?: PaginationInput;\n}\n\nexport const useMybSessions = (filters: SessionFilters = {}) => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useQuery({\n queryKey: [...KEY.sessions, filters],\n queryFn: async () => {\n const result = await mybQuery<{ mybListSessions: MybConnection<MybWorkoutSession> }>(\n `query MybListSessions($status: MybSessionStatus, $programId: ID, $pagination: PaginationInput) {\n mybListSessions(status: $status, programId: $programId, pagination: $pagination) {\n items { ${SESSION_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n status: filters.status,\n programId: filters.programId,\n pagination: filters.pagination ?? DEFAULT_PAGE,\n },\n apiGatewayUrl\n );\n return result.mybListSessions;\n },\n });\n};\n\nexport const useMybSession = (id: string | undefined) => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useQuery({\n queryKey: KEY.session(id ?? ''),\n enabled: Boolean(id),\n queryFn: async () => {\n const result = await mybQuery<{\n mybSession: (Omit<MybWorkoutSession, 'sets'> & { sets: { items: MybSessionSet[] } }) | null;\n }>(\n `query MybSession($id: ID!) {\n mybSession(id: $id) {\n ${SESSION_FIELDS}\n sets { items { ${SET_FIELDS} } }\n }\n }`,\n { id },\n apiGatewayUrl\n );\n if (!result.mybSession) return null;\n const { sets, ...session } = result.mybSession;\n return { ...session, sets: sets.items };\n },\n });\n};\n\nexport const useMybStartSession = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: MybStartSessionInput) => {\n const result = await mybQuery<{ mybStartSession: MybWorkoutSession }>(\n `mutation MybStartSession($input: MybStartSessionInput!) {\n mybStartSession(input: $input) { ${SESSION_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.mybStartSession;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.sessions }),\n });\n};\n\nexport const useMybLogSet = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: MybLogSetInput) => {\n const result = await mybQuery<{ mybLogSet: MybSessionSet }>(\n `mutation MybLogSet($input: MybLogSetInput!) {\n mybLogSet(input: $input) { ${SET_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.mybLogSet;\n },\n onSuccess: (data) => {\n qc.invalidateQueries({ queryKey: KEY.session(data.sessionId) });\n qc.invalidateQueries({ queryKey: KEY.sessions });\n },\n });\n};\n\nexport const useMybCompleteSession = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (vars: { id: string; input: MybCompleteSessionInput }) => {\n const result = await mybQuery<{ mybCompleteSession: MybWorkoutSession }>(\n `mutation MybCompleteSession($id: ID!, $input: MybCompleteSessionInput!) {\n mybCompleteSession(id: $id, input: $input) { ${SESSION_FIELDS} }\n }`,\n vars,\n apiGatewayUrl\n );\n return result.mybCompleteSession;\n },\n onSuccess: (data) => {\n qc.invalidateQueries({ queryKey: KEY.session(data.id) });\n qc.invalidateQueries({ queryKey: KEY.sessions });\n },\n });\n};\n\n// ============================================================================\n// Goals (Progress)\n// ============================================================================\n\nexport interface GoalFilters {\n status?: MybGoalStatus;\n memberId?: string;\n pagination?: PaginationInput;\n}\n\nexport const useMybGoals = (filters: GoalFilters = {}) => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useQuery({\n queryKey: [...KEY.goals, filters],\n queryFn: async () => {\n const result = await mybQuery<{ mybListGoals: MybConnection<MybGoal> }>(\n `query MybListGoals($status: MybGoalStatus, $memberId: ID, $pagination: PaginationInput) {\n mybListGoals(status: $status, memberId: $memberId, pagination: $pagination) {\n items { ${GOAL_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n status: filters.status,\n memberId: filters.memberId,\n pagination: filters.pagination ?? DEFAULT_PAGE,\n },\n apiGatewayUrl\n );\n return result.mybListGoals;\n },\n });\n};\n\nexport const useMybCreateGoal = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: MybCreateGoalInput) => {\n const result = await mybQuery<{ mybCreateGoal: MybGoal }>(\n `mutation MybCreateGoal($input: MybCreateGoalInput!) {\n mybCreateGoal(input: $input) { ${GOAL_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.mybCreateGoal;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.goals }),\n });\n};\n\nexport const useMybUpdateGoal = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (vars: { id: string; input: MybUpdateGoalInput }) => {\n const result = await mybQuery<{ mybUpdateGoal: MybGoal }>(\n `mutation MybUpdateGoal($id: ID!, $input: MybUpdateGoalInput!) {\n mybUpdateGoal(id: $id, input: $input) { ${GOAL_FIELDS} }\n }`,\n vars,\n apiGatewayUrl\n );\n return result.mybUpdateGoal;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.goals }),\n });\n};\n\nexport const useMybAchieveGoal = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await mybQuery<{ mybAchieveGoal: MybGoal }>(\n `mutation MybAchieveGoal($id: ID!) {\n mybAchieveGoal(id: $id) { ${GOAL_FIELDS} }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.mybAchieveGoal;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.goals }),\n });\n};\n\n// ============================================================================\n// Nutrition (plans, meals, macros)\n// ============================================================================\n\nexport const useMybActiveNutritionPlan = (memberId?: string) => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useQuery({\n queryKey: [...KEY.activePlan, memberId ?? 'me'],\n queryFn: async () => {\n const result = await mybQuery<{ mybActiveNutritionPlan: MybNutritionPlan | null }>(\n `query MybActiveNutritionPlan($memberId: ID) {\n mybActiveNutritionPlan(memberId: $memberId) { ${PLAN_FIELDS} }\n }`,\n { memberId },\n apiGatewayUrl\n );\n return result.mybActiveNutritionPlan;\n },\n });\n};\n\nexport const useMybNutritionPlan = (id: string | undefined) => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useQuery({\n queryKey: KEY.nutritionPlan(id ?? ''),\n enabled: Boolean(id),\n queryFn: async () => {\n const result = await mybQuery<{ mybNutritionPlan: MybNutritionPlan | null }>(\n `query MybNutritionPlan($id: ID!) {\n mybNutritionPlan(id: $id) {\n ${PLAN_FIELDS}\n meals { ${MEAL_FIELDS} }\n }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.mybNutritionPlan;\n },\n });\n};\n\nexport interface MealFilters {\n mealType?: MybMealType;\n planId?: string;\n pagination?: PaginationInput;\n}\n\nexport const useMybMeals = (filters: MealFilters = {}) => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useQuery({\n queryKey: [...KEY.meals, filters],\n queryFn: async () => {\n const result = await mybQuery<{ mybListMeals: MybConnection<MybMealLog> }>(\n `query MybListMeals($mealType: MybMealType, $planId: ID, $pagination: PaginationInput) {\n mybListMeals(mealType: $mealType, planId: $planId, pagination: $pagination) {\n items { ${MEAL_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n mealType: filters.mealType,\n planId: filters.planId,\n pagination: filters.pagination ?? DEFAULT_PAGE,\n },\n apiGatewayUrl\n );\n return result.mybListMeals;\n },\n });\n};\n\nexport const useMybDailyMacros = (date?: string, memberId?: string) => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useQuery({\n queryKey: [...KEY.dailyMacros, date ?? 'today', memberId ?? 'me'],\n queryFn: async () => {\n const result = await mybQuery<{ mybDailyMacros: MybDailyMacros }>(\n `query MybDailyMacros($date: DateTime, $memberId: ID) {\n mybDailyMacros(date: $date, memberId: $memberId) { ${MACROS_FIELDS} }\n }`,\n { date, memberId },\n apiGatewayUrl\n );\n return result.mybDailyMacros;\n },\n });\n};\n\nexport const useMybCreateNutritionPlan = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: MybCreateNutritionPlanInput) => {\n const result = await mybQuery<{ mybCreateNutritionPlan: MybNutritionPlan }>(\n `mutation MybCreateNutritionPlan($input: MybCreateNutritionPlanInput!) {\n mybCreateNutritionPlan(input: $input) { ${PLAN_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.mybCreateNutritionPlan;\n },\n onSuccess: () => {\n qc.invalidateQueries({ queryKey: KEY.activePlan });\n },\n });\n};\n\nexport const useMybUpdateNutritionPlan = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (vars: { id: string; input: MybUpdateNutritionPlanInput }) => {\n const result = await mybQuery<{ mybUpdateNutritionPlan: MybNutritionPlan }>(\n `mutation MybUpdateNutritionPlan($id: ID!, $input: MybUpdateNutritionPlanInput!) {\n mybUpdateNutritionPlan(id: $id, input: $input) { ${PLAN_FIELDS} }\n }`,\n vars,\n apiGatewayUrl\n );\n return result.mybUpdateNutritionPlan;\n },\n onSuccess: (data) => {\n qc.invalidateQueries({ queryKey: KEY.activePlan });\n qc.invalidateQueries({ queryKey: KEY.nutritionPlan(data.id) });\n },\n });\n};\n\nexport const useMybActivateNutritionPlan = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await mybQuery<{ mybActivateNutritionPlan: MybNutritionPlan }>(\n `mutation MybActivateNutritionPlan($id: ID!) {\n mybActivateNutritionPlan(id: $id) { ${PLAN_FIELDS} }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.mybActivateNutritionPlan;\n },\n onSuccess: (data) => {\n qc.invalidateQueries({ queryKey: KEY.activePlan });\n qc.invalidateQueries({ queryKey: KEY.nutritionPlan(data.id) });\n },\n });\n};\n\nexport const useMybLogMeal = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: MybLogMealInput) => {\n const result = await mybQuery<{ mybLogMeal: MybMealLog }>(\n `mutation MybLogMeal($input: MybLogMealInput!) {\n mybLogMeal(input: $input) { ${MEAL_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.mybLogMeal;\n },\n onSuccess: (data) => {\n qc.invalidateQueries({ queryKey: KEY.meals });\n qc.invalidateQueries({ queryKey: KEY.dailyMacros });\n if (data.planId) qc.invalidateQueries({ queryKey: KEY.nutritionPlan(data.planId) });\n },\n });\n};\n\nexport const useMybDeleteMeal = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await mybQuery<{ mybDeleteMeal: boolean }>(\n `mutation MybDeleteMeal($id: ID!) { mybDeleteMeal(id: $id) }`,\n { id },\n apiGatewayUrl\n );\n return result.mybDeleteMeal;\n },\n onSuccess: () => {\n qc.invalidateQueries({ queryKey: KEY.meals });\n qc.invalidateQueries({ queryKey: KEY.dailyMacros });\n },\n });\n};\n\n// ============================================================================\n// Body metrics (Progress)\n// ============================================================================\n\nexport interface BodyMetricFilters {\n type?: MybBodyMetricType;\n memberId?: string;\n pagination?: PaginationInput;\n}\n\nexport const useMybBodyMetrics = (filters: BodyMetricFilters = {}) => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useQuery({\n queryKey: [...KEY.bodyMetrics, filters],\n queryFn: async () => {\n const result = await mybQuery<{ mybListBodyMetrics: MybConnection<MybBodyMetric> }>(\n `query MybListBodyMetrics($type: MybBodyMetricType, $memberId: ID, $pagination: PaginationInput) {\n mybListBodyMetrics(type: $type, memberId: $memberId, pagination: $pagination) {\n items { ${METRIC_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n type: filters.type,\n memberId: filters.memberId,\n pagination: filters.pagination ?? DEFAULT_PAGE,\n },\n apiGatewayUrl\n );\n return result.mybListBodyMetrics;\n },\n });\n};\n\nexport const useMybRecordBodyMetric = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: MybRecordMetricInput) => {\n const result = await mybQuery<{ mybRecordBodyMetric: MybBodyMetric }>(\n `mutation MybRecordBodyMetric($input: MybRecordMetricInput!) {\n mybRecordBodyMetric(input: $input) { ${METRIC_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.mybRecordBodyMetric;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.bodyMetrics }),\n });\n};\n\n// ============================================================================\n// Recovery (Wellbeing)\n// ============================================================================\n\nexport const useMybRecoveryLogs = (metric?: MybRecoveryMetric, memberId?: string) => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useQuery({\n queryKey: [...KEY.recovery, metric ?? 'all', memberId ?? 'me'],\n queryFn: async () => {\n const result = await mybQuery<{ mybListRecoveryLogs: MybConnection<MybRecoveryLog> }>(\n `query MybListRecoveryLogs($metric: MybRecoveryMetric, $memberId: ID) {\n mybListRecoveryLogs(metric: $metric, memberId: $memberId) {\n items { ${RECOVERY_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n { metric, memberId },\n apiGatewayUrl\n );\n return result.mybListRecoveryLogs;\n },\n });\n};\n\nexport const useMybLogRecovery = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: MybLogRecoveryInput) => {\n const result = await mybQuery<{ mybLogRecovery: MybRecoveryLog }>(\n `mutation MybLogRecovery($input: MybLogRecoveryInput!) {\n mybLogRecovery(input: $input) { ${RECOVERY_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.mybLogRecovery;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.recovery }),\n });\n};\n\n// ============================================================================\n// Coaching (Wellbeing)\n// ============================================================================\n\nexport const useMybCoaching = (status?: MybCoachingStatus, memberId?: string) => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useQuery({\n queryKey: [...KEY.coaching, status ?? 'all', memberId ?? 'me'],\n queryFn: async () => {\n const result = await mybQuery<{ mybListCoaching: MybConnection<MybCoachingRelationship> }>(\n `query MybListCoaching($status: MybCoachingStatus, $memberId: ID) {\n mybListCoaching(status: $status, memberId: $memberId) {\n items { ${COACHING_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n { status, memberId },\n apiGatewayUrl\n );\n return result.mybListCoaching;\n },\n });\n};\n\nexport const useMybCreateCoaching = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: MybCreateCoachingInput) => {\n const result = await mybQuery<{ mybCreateCoaching: MybCoachingRelationship }>(\n `mutation MybCreateCoaching($input: MybCreateCoachingInput!) {\n mybCreateCoaching(input: $input) { ${COACHING_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.mybCreateCoaching;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.coaching }),\n });\n};\n\nexport const useMybTransitionCoaching = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (vars: { id: string; status: MybCoachingStatus }) => {\n const result = await mybQuery<{ mybTransitionCoaching: MybCoachingRelationship }>(\n `mutation MybTransitionCoaching($id: ID!, $status: MybCoachingStatus!) {\n mybTransitionCoaching(id: $id, status: $status) { ${COACHING_FIELDS} }\n }`,\n vars,\n apiGatewayUrl\n );\n return result.mybTransitionCoaching;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.coaching }),\n });\n};\n"],"mappings":"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"}
|
|
@@ -66,7 +66,7 @@ function j() {
|
|
|
66
66
|
}), B = ee(), V = p({
|
|
67
67
|
type: "WEIGHT",
|
|
68
68
|
pagination: { limit: 30 }
|
|
69
|
-
}), fe = re("READINESS"), pe = L.data?.items ?? [], H = R.data?.items ?? [], U = z.data?.items ?? [], W = V.data?.items ?? [], me = fe.data ?? [], he = (/* @__PURE__ */ new Date()).getTime() - 10080 * 60 * 1e3, G = H.filter((e) => {
|
|
69
|
+
}), fe = re("READINESS"), pe = L.data?.items ?? [], H = R.data?.items ?? [], U = z.data?.items ?? [], W = V.data?.items ?? [], me = fe.data?.items ?? [], he = (/* @__PURE__ */ new Date()).getTime() - 10080 * 60 * 1e3, G = H.filter((e) => {
|
|
70
70
|
let t = e.completedAt ?? e.startedAt ?? e.scheduledFor ?? e.createdAt;
|
|
71
71
|
return t ? new Date(t).getTime() >= he : !1;
|
|
72
72
|
}), ge = G.filter((e) => e.status === "COMPLETED").length, K = H.find((e) => e.status === "IN_PROGRESS"), q = H.filter((e) => e.status === "SCHEDULED" && e.scheduledFor).sort((e, t) => new Date(e.scheduledFor).getTime() - new Date(t.scheduledFor).getTime())[0], J = W[0], Y = W[1], X = J && Y ? J.value - Y.value : void 0, Z = me.slice().sort((e, t) => new Date(t.loggedAt).getTime() - new Date(e.loggedAt).getTime())[0], Q = b(() => U.length === 0 ? 0 : U.reduce((e, t) => e + y(t), 0) / U.length, [U]), $ = B.data, _e = $?.calorieTarget ? $.calories / $.calorieTarget * 100 : 0;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DashboardPage.js","names":[],"sources":["../../../src/mybodyshield/pages/DashboardPage.tsx"],"sourcesContent":["/**\n * Dashboard — MyBodyShield health & fitness overview.\n * @module mybodyshield/pages\n *\n * KPIs are aggregated client-side from the real `myb*` list endpoints:\n * active programs, this-week's sessions, goal progress, today's macros vs\n * target, latest body weight and recovery readiness. No mock data.\n */\n\nimport { useMemo } from 'react';\nimport type { ReactNode } from 'react';\nimport { motion, useReducedMotion } from 'framer-motion';\nimport {\n Dumbbell,\n Flame,\n Target,\n Salad,\n Moon,\n ArrowRight,\n CheckCircle2,\n Weight,\n CalendarCheck,\n} from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../components/PageLayout';\nimport { MetricsCard } from '../components/MetricsCard';\nimport { StatusBadge } from '../components/StatusBadge';\nimport { MiniBar } from '../components/MiniBar';\nimport { LoadingView, Section } from '../components/StateViews';\nimport { EmptyState } from '../components/EmptyState';\nimport {\n Meter,\n ProgressRing,\n MacroBar,\n MacroLegend,\n IconChip,\n GOAL_TYPE_META,\n RECOVERY_META,\n WORKOUT_TYPE_META,\n computeGoalProgress,\n} from './_shared/visuals';\nimport { useMyBodyShield } from '../context';\nimport {\n useMybPrograms,\n useMybSessions,\n useMybGoals,\n useMybDailyMacros,\n useMybBodyMetrics,\n useMybRecoveryLogs,\n} from '../hooks';\n\nimport {\n formatNumber,\n formatRelativeTime,\n formatKcal,\n formatWeight,\n formatPercent,\n formatEnumLabel,\n} from '../utils/formatters';\n\n/** Shared easing for entrance / hover motion (smooth ease-out). */\nconst EASE_OUT: [number, number, number, number] = [0.16, 1, 0.3, 1];\n\nexport function DashboardPage() {\n const { navigateTo, currentUser } = useMyBodyShield();\n const reduceMotion = useReducedMotion();\n\n /** Staggered fade/rise entrance — one-shot, skipped under reduced motion. */\n const rise = (i: number) =>\n reduceMotion\n ? {}\n : {\n initial: { opacity: 0, y: 14 },\n whileInView: { opacity: 1, y: 0 },\n viewport: { once: true, amount: 0.15 },\n transition: { duration: 0.45, delay: Math.min(i, 8) * 0.05, ease: EASE_OUT },\n };\n\n /** Gentle hover lift for cards. */\n const lift = reduceMotion\n ? {}\n : { whileHover: { y: -3, transition: { duration: 0.2, ease: EASE_OUT } } };\n\n /** Hover lift + tap feedback for interactive cards. */\n const liftTap = reduceMotion\n ? {}\n : {\n whileHover: { y: -3, transition: { duration: 0.2, ease: EASE_OUT } },\n whileTap: { scale: 0.985 },\n };\n\n const programs = useMybPrograms({ status: 'ACTIVE', pagination: { limit: 50 } });\n const sessions = useMybSessions({ pagination: { limit: 20 } });\n const goals = useMybGoals({ status: 'ACTIVE', pagination: { limit: 50 } });\n const macros = useMybDailyMacros();\n const weightMetrics = useMybBodyMetrics({ type: 'WEIGHT', pagination: { limit: 30 } });\n const readiness = useMybRecoveryLogs('READINESS');\n\n const programRows = programs.data?.items ?? [];\n const sessionRows = sessions.data?.items ?? [];\n const goalRows = goals.data?.items ?? [];\n const weightRows = weightMetrics.data?.items ?? [];\n const readinessRows = readiness.data ?? [];\n\n const now = new Date();\n const weekAgo = now.getTime() - 7 * 24 * 60 * 60 * 1000;\n const sessionsThisWeek = sessionRows.filter((s) => {\n const ref = s.completedAt ?? s.startedAt ?? s.scheduledFor ?? s.createdAt;\n return ref ? new Date(ref).getTime() >= weekAgo : false;\n });\n const completedThisWeek = sessionsThisWeek.filter((s) => s.status === 'COMPLETED').length;\n const activeSession = sessionRows.find((s) => s.status === 'IN_PROGRESS');\n const upcomingSession = sessionRows\n .filter((s) => s.status === 'SCHEDULED' && s.scheduledFor)\n .sort((a, b) => new Date(a.scheduledFor!).getTime() - new Date(b.scheduledFor!).getTime())[0];\n\n const latestWeight = weightRows[0];\n const priorWeight = weightRows[1];\n const weightDelta =\n latestWeight && priorWeight ? latestWeight.value - priorWeight.value : undefined;\n\n const latestReadiness = readinessRows\n .slice()\n .sort((a, b) => new Date(b.loggedAt).getTime() - new Date(a.loggedAt).getTime())[0];\n\n const goalProgressAvg = useMemo(() => {\n if (goalRows.length === 0) return 0;\n const sum = goalRows.reduce((acc, g) => acc + computeGoalProgress(g), 0);\n return sum / goalRows.length;\n }, [goalRows]);\n\n const macroData = macros.data;\n const caloriePct = macroData?.calorieTarget\n ? (macroData.calories / macroData.calorieTarget) * 100\n : 0;\n\n const isInitialLoading =\n programs.isLoading && sessions.isLoading && goals.isLoading && macros.isLoading;\n\n if (isInitialLoading) {\n return (\n <PageLayout title=\"MyBodyShield\" titleGradient aurora>\n <LoadingView label=\"Loading your fitness overview…\" />\n </PageLayout>\n );\n }\n\n const greetingName = currentUser?.firstName || currentUser?.name || 'there';\n\n const kpiCards: { id: string; node: ReactNode }[] = [\n {\n id: 'programs',\n node: (\n <MetricsCard\n icon={<Dumbbell className=\"h-5 w-5\" />}\n value={formatNumber(programRows.length)}\n label=\"Active programs\"\n footer={\n <p className=\"text-xs text-text-tertiary\">\n <button\n type=\"button\"\n onClick={() => navigateTo('programs')}\n className=\"font-medium text-accent hover:underline\"\n >\n Manage programs\n </button>\n </p>\n }\n />\n ),\n },\n {\n id: 'sessions',\n node: (\n <MetricsCard\n icon={<Flame className=\"h-5 w-5\" />}\n value={formatNumber(completedThisWeek)}\n label=\"Sessions this week\"\n accent={activeSession ? 'warning' : undefined}\n footer={\n activeSession ? (\n <button\n type=\"button\"\n onClick={() => navigateTo(`train/${activeSession.id}`)}\n className=\"text-xs font-medium text-status-warning-text hover:underline\"\n >\n Resume live session →\n </button>\n ) : (\n <p className=\"text-xs text-text-tertiary\">\n <span className=\"tabular-nums text-text-secondary\">{sessionsThisWeek.length}</span>{' '}\n logged\n </p>\n )\n }\n />\n ),\n },\n {\n id: 'goals',\n node: (\n <MetricsCard\n icon={<Target className=\"h-5 w-5\" />}\n value={formatNumber(goalRows.length)}\n label=\"Active goals\"\n footer={\n <div className=\"space-y-2\">\n <MiniBar value={goalProgressAvg} height={4} barClassName=\"bg-action-primary-bg\" />\n <p className=\"text-xs text-text-tertiary\">\n <span className=\"tabular-nums text-text-secondary\">\n {formatPercent(goalProgressAvg)}\n </span>{' '}\n avg progress\n </p>\n </div>\n }\n />\n ),\n },\n {\n id: 'weight',\n node: (\n <MetricsCard\n icon={<Weight className=\"h-5 w-5\" />}\n value={latestWeight ? formatWeight(latestWeight.value) : '—'}\n label=\"Latest weight\"\n footer={\n weightDelta != null ? (\n <p className=\"text-xs text-text-tertiary\">\n <span\n className={\n weightDelta <= 0\n ? 'font-medium text-status-success-text'\n : 'font-medium text-text-secondary'\n }\n >\n {weightDelta > 0 ? '+' : ''}\n {weightDelta.toFixed(1)} kg\n </span>{' '}\n vs last\n </p>\n ) : (\n <button\n type=\"button\"\n onClick={() => navigateTo('progress')}\n className=\"text-xs font-medium text-accent hover:underline\"\n >\n Log a measurement →\n </button>\n )\n }\n />\n ),\n },\n {\n id: 'recovery',\n node: (\n <MetricsCard\n icon={<Moon className=\"h-5 w-5\" />}\n value={latestReadiness ? `${latestReadiness.score}` : '—'}\n label=\"Recovery readiness\"\n accent={\n latestReadiness\n ? latestReadiness.score >= 70\n ? 'success'\n : latestReadiness.score >= 40\n ? 'warning'\n : 'error'\n : undefined\n }\n footer={\n latestReadiness ? (\n <p className=\"text-xs text-text-tertiary\">\n {formatRelativeTime(latestReadiness.loggedAt)}\n </p>\n ) : (\n <button\n type=\"button\"\n onClick={() => navigateTo('recovery')}\n className=\"text-xs font-medium text-accent hover:underline\"\n >\n Log recovery →\n </button>\n )\n }\n />\n ),\n },\n ];\n\n return (\n <PageLayout\n title=\"MyBodyShield\"\n titleGradient\n aurora\n description={`Welcome back, ${greetingName} — your training, nutrition, progress & recovery control room.`}\n >\n {/* KPI grid */}\n <div\n data-tour=\"mybodyshield-dashboard-kpi-grid\"\n className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5\"\n >\n {kpiCards.map((card, i) => (\n <motion.div\n key={card.id}\n className=\"group relative rounded-2xl transition-shadow duration-300 hover:shadow-float\"\n {...rise(i)}\n {...lift}\n >\n {/* Emerald top-edge accent — brightens on hover */}\n <span\n aria-hidden\n className=\"pointer-events-none absolute inset-x-6 top-0 z-10 h-px rounded-full bg-gradient-to-r from-transparent via-action-primary-bg to-transparent opacity-40 transition-opacity duration-300 group-hover:opacity-90\"\n />\n {card.node}\n </motion.div>\n ))}\n </div>\n\n <div className=\"mt-6 grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {/* Today's nutrition */}\n <motion.div\n data-tour=\"mybodyshield-dashboard-nutrition\"\n className=\"lg:col-span-2\"\n {...rise(0)}\n >\n <GlassCard treatment=\"glass\" className=\"h-full p-6\">\n <div className=\"mb-4 flex flex-wrap items-start justify-between gap-3\">\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">\n Today's nutrition\n </h2>\n <p className=\"mt-0.5 text-sm text-text-secondary\">\n Calories & macros logged against your active plan.\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => navigateTo('nutrition')}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-accent hover:underline\"\n >\n Nutrition <ArrowRight className=\"h-4 w-4\" />\n </button>\n </div>\n {!macroData || (macroData.calorieTarget === 0 && macroData.calories === 0) ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Salad className=\"h-10 w-10\" />}\n title=\"No nutrition data yet\"\n description=\"Activate a nutrition plan and log meals to track your daily macros here.\"\n />\n ) : (\n <div className=\"flex flex-col gap-8 sm:flex-row sm:items-center\">\n {/* Calorie ring — the nutrition hero */}\n <div className=\"relative mx-auto flex shrink-0 items-center justify-center sm:mx-0\">\n <span\n aria-hidden\n className=\"pointer-events-none absolute inset-2 rounded-full opacity-70 blur-2xl\"\n style={{\n background:\n 'radial-gradient(circle, color-mix(in srgb, var(--color-action-primary-bg) 26%, transparent), transparent 70%)',\n }}\n />\n <ProgressRing value={caloriePct} size={188} stroke={16}>\n <span className=\"text-[11px] font-medium uppercase tracking-wide text-text-tertiary\">\n Calories\n </span>\n <span className=\"text-3xl font-bold tabular-nums text-text-primary\">\n {formatNumber(macroData.calories)}\n </span>\n <span className=\"mt-0.5 text-xs text-text-tertiary\">\n / {formatNumber(macroData.calorieTarget)} kcal\n </span>\n </ProgressRing>\n </div>\n <div className=\"flex-1 space-y-4\">\n <div className=\"space-y-2\">\n <MacroBar\n proteinG={macroData.proteinG}\n carbsG={macroData.carbsG}\n fatG={macroData.fatG}\n height={12}\n />\n <MacroLegend />\n </div>\n <div className=\"grid grid-cols-3 gap-3\">\n {(\n [\n ['Protein', macroData.proteinG, macroData.proteinTarget],\n ['Carbs', macroData.carbsG, macroData.carbsTarget],\n ['Fat', macroData.fatG, macroData.fatTarget],\n ] as const\n ).map(([label, val, target]) => {\n const pct = target ? (val / target) * 100 : 0;\n return (\n <div\n key={label}\n className=\"rounded-2xl bg-bg-sunken/60 p-3 ring-1 ring-inset ring-border-seam/40 transition-shadow hover:shadow-float\"\n >\n <div className=\"flex items-center justify-between gap-1\">\n <p className=\"text-xs font-medium text-text-tertiary\">{label}</p>\n <span className=\"text-[11px] tabular-nums text-text-tertiary\">\n {formatPercent(pct)}\n </span>\n </div>\n <p className=\"mt-1 text-sm font-semibold tabular-nums text-text-primary\">\n {formatNumber(val)}\n <span className=\"text-xs font-normal text-text-tertiary\">\n {' '}\n / {formatNumber(target)} g\n </span>\n </p>\n <Meter value={pct} height={5} className=\"mt-2\" />\n </div>\n );\n })}\n </div>\n </div>\n </div>\n )}\n </GlassCard>\n </motion.div>\n\n {/* Next up */}\n <motion.div data-tour=\"mybodyshield-dashboard-next-up\" {...rise(1)}>\n <GlassCard treatment=\"glass\" className=\"h-full p-6\">\n <div className=\"mb-4 flex items-center justify-between gap-3\">\n <h2 className=\"text-base font-semibold text-text-primary\">Next up</h2>\n <button\n type=\"button\"\n onClick={() => navigateTo('train')}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-accent hover:underline\"\n >\n Train <ArrowRight className=\"h-4 w-4\" />\n </button>\n </div>\n {activeSession ? (\n <motion.button\n type=\"button\"\n onClick={() => navigateTo(`train/${activeSession.id}`)}\n className=\"w-full rounded-2xl border border-status-warning-text/40 bg-status-warning-bg-subtle p-4 text-left transition-shadow hover:shadow-float\"\n {...liftTap}\n >\n <div className=\"flex items-center gap-2\">\n <StatusBadge status={activeSession.status} />\n </div>\n <p className=\"mt-2 text-sm font-medium text-text-primary\">\n Live workout in progress\n </p>\n <p className=\"mt-0.5 text-xs text-text-tertiary\">\n Started {formatRelativeTime(activeSession.startedAt)}\n </p>\n </motion.button>\n ) : upcomingSession ? (\n <motion.button\n type=\"button\"\n onClick={() => navigateTo(`train/${upcomingSession.id}`)}\n className=\"w-full rounded-2xl bg-bg-sunken/60 p-4 text-left ring-1 ring-inset ring-border-seam/40 transition-shadow hover:shadow-float\"\n {...liftTap}\n >\n <div className=\"flex items-center gap-2\">\n <IconChip icon={<CalendarCheck className=\"h-4 w-4\" />} />\n <div>\n <p className=\"text-sm font-medium text-text-primary\">Scheduled session</p>\n <p className=\"text-xs text-text-tertiary\">\n {formatRelativeTime(upcomingSession.scheduledFor)}\n </p>\n </div>\n </div>\n </motion.button>\n ) : (\n <div className=\"flex flex-col items-center justify-center rounded-2xl border border-dashed border-border-seam py-8 text-center\">\n <span className=\"flex size-11 items-center justify-center rounded-full bg-[var(--color-accent-soft)]\">\n <Flame className=\"h-5 w-5 text-accent\" />\n </span>\n <p className=\"mt-3 text-sm font-medium text-text-primary\">No session scheduled</p>\n <button\n type=\"button\"\n onClick={() => navigateTo('train')}\n className=\"mt-1 text-xs font-medium text-accent hover:underline\"\n >\n Start a workout\n </button>\n </div>\n )}\n </GlassCard>\n </motion.div>\n </div>\n\n {/* Active goals */}\n <Section\n data-tour=\"mybodyshield-dashboard-active-goals\"\n title=\"Active goals\"\n description=\"Your current targets across training, nutrition and body composition.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('goals')}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-accent hover:underline\"\n >\n All goals <ArrowRight className=\"h-4 w-4\" />\n </button>\n }\n >\n {goalRows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Target className=\"h-10 w-10\" />}\n title=\"No active goals\"\n description=\"Set a goal to give your training and nutrition a clear target to work toward.\"\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {goalRows.slice(0, 6).map((g, i) => {\n const pct = computeGoalProgress(g);\n const meta = GOAL_TYPE_META[g.type];\n const Icon = meta.icon;\n return (\n <motion.button\n key={g.id}\n type=\"button\"\n onClick={() => navigateTo('goals')}\n className=\"text-left\"\n {...rise(i)}\n {...liftTap}\n >\n <GlassCard\n treatment=\"plain\"\n className=\"h-full p-4 transition-shadow hover:shadow-float\"\n >\n <div className=\"flex items-start gap-3\">\n <IconChip icon={<Icon className=\"h-4 w-4\" />} tint={meta.tint} />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-semibold text-text-primary\">\n {g.title}\n </p>\n <p className=\"text-xs text-text-tertiary\">{formatEnumLabel(g.type)}</p>\n </div>\n </div>\n <div className=\"mt-3 space-y-1.5\">\n <Meter value={pct} />\n <div className=\"flex items-center justify-between text-xs\">\n <span className=\"tabular-nums text-text-secondary\">\n {formatNumber(g.currentValue)}\n {g.unit ? ` ${g.unit}` : ''} / {formatNumber(g.targetValue)}\n {g.unit ? ` ${g.unit}` : ''}\n </span>\n <span className=\"tabular-nums text-text-tertiary\">\n {formatPercent(pct)}\n </span>\n </div>\n </div>\n </GlassCard>\n </motion.button>\n );\n })}\n </div>\n )}\n </Section>\n\n {/* Recent sessions */}\n <Section\n data-tour=\"mybodyshield-dashboard-recent-sessions\"\n title=\"Recent sessions\"\n description=\"Your latest logged and scheduled workouts.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('train')}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-accent hover:underline\"\n >\n All sessions <ArrowRight className=\"h-4 w-4\" />\n </button>\n }\n >\n {sessionRows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Flame className=\"h-10 w-10\" />}\n title=\"No sessions yet\"\n description=\"Start a workout session to begin logging sets and tracking your training.\"\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {sessionRows.slice(0, 6).map((s, i) => {\n const meta = WORKOUT_TYPE_META.STRENGTH;\n const Icon = s.status === 'COMPLETED' ? CheckCircle2 : meta.icon;\n return (\n <motion.button\n key={s.id}\n type=\"button\"\n onClick={() => navigateTo(`train/${s.id}`)}\n className=\"text-left\"\n {...rise(i)}\n {...liftTap}\n >\n <GlassCard treatment=\"plain\" className=\"p-4 transition-shadow hover:shadow-float\">\n <div className=\"flex items-center justify-between gap-2\">\n <IconChip icon={<Icon className=\"h-4 w-4\" />} tint={meta.tint} />\n <StatusBadge status={s.status} />\n </div>\n <p className=\"mt-3 text-sm font-medium text-text-primary\">\n {s.caloriesBurned > 0\n ? `${formatNumber(s.caloriesBurned)} kcal burned`\n : 'Workout session'}\n </p>\n <p className=\"mt-0.5 text-xs text-text-tertiary\">\n {formatRelativeTime(s.completedAt ?? s.startedAt ?? s.createdAt)}\n </p>\n </GlassCard>\n </motion.button>\n );\n })}\n </div>\n )}\n </Section>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA6DA,IAAM,IAA6C;CAAC;CAAM;CAAG;CAAK;CAAE;AAEpE,SAAgB,IAAgB;CAC9B,IAAM,EAAE,eAAY,mBAAgB,GAAiB,EAC/C,IAAe,GAAkB,EAGjC,KAAQ,MACZ,IACI,EAAE,GACF;EACE,SAAS;GAAE,SAAS;GAAG,GAAG;GAAI;EAC9B,aAAa;GAAE,SAAS;GAAG,GAAG;GAAG;EACjC,UAAU;GAAE,MAAM;GAAM,QAAQ;GAAM;EACtC,YAAY;GAAE,UAAU;GAAM,OAAO,KAAK,IAAI,GAAG,EAAE,GAAG;GAAM,MAAM;GAAU;EAC7E,EAGD,IAAO,IACT,EAAE,GACF,EAAE,YAAY;EAAE,GAAG;EAAI,YAAY;GAAE,UAAU;GAAK,MAAM;GAAU;EAAE,EAAE,EAGtE,IAAU,IACZ,EAAE,GACF;EACE,YAAY;GAAE,GAAG;GAAI,YAAY;IAAE,UAAU;IAAK,MAAM;IAAU;GAAE;EACpE,UAAU,EAAE,OAAO,MAAO;EAC3B,EAEC,IAAW,GAAe;EAAE,QAAQ;EAAU,YAAY,EAAE,OAAO,IAAI;EAAE,CAAC,EAC1E,IAAW,GAAe,EAAE,YAAY,EAAE,OAAO,IAAI,EAAE,CAAC,EACxD,IAAQ,GAAY;EAAE,QAAQ;EAAU,YAAY,EAAE,OAAO,IAAI;EAAE,CAAC,EACpE,IAAS,IAAmB,EAC5B,IAAgB,EAAkB;EAAE,MAAM;EAAU,YAAY,EAAE,OAAO,IAAI;EAAE,CAAC,EAChF,KAAY,GAAmB,YAAY,EAE3C,KAAc,EAAS,MAAM,SAAS,EAAE,EACxC,IAAc,EAAS,MAAM,SAAS,EAAE,EACxC,IAAW,EAAM,MAAM,SAAS,EAAE,EAClC,IAAa,EAAc,MAAM,SAAS,EAAE,EAC5C,KAAgB,GAAU,QAAQ,EAAE,EAGpC,sBADM,IAAI,MAAM,EACF,SAAS,GAAG,QAAc,KAAK,KAC7C,IAAmB,EAAY,QAAQ,MAAM;EACjD,IAAM,IAAM,EAAE,eAAe,EAAE,aAAa,EAAE,gBAAgB,EAAE;AAChE,SAAO,IAAM,IAAI,KAAK,EAAI,CAAC,SAAS,IAAI,KAAU;GAClD,EACI,KAAoB,EAAiB,QAAQ,MAAM,EAAE,WAAW,YAAY,CAAC,QAC7E,IAAgB,EAAY,MAAM,MAAM,EAAE,WAAW,cAAc,EACnE,IAAkB,EACrB,QAAQ,MAAM,EAAE,WAAW,eAAe,EAAE,aAAa,CACzD,MAAM,GAAG,MAAM,IAAI,KAAK,EAAE,aAAc,CAAC,SAAS,GAAG,IAAI,KAAK,EAAE,aAAc,CAAC,SAAS,CAAC,CAAC,IAEvF,IAAe,EAAW,IAC1B,IAAc,EAAW,IACzB,IACJ,KAAgB,IAAc,EAAa,QAAQ,EAAY,QAAQ,KAAA,GAEnE,IAAkB,GACrB,OAAO,CACP,MAAM,GAAG,MAAM,IAAI,KAAK,EAAE,SAAS,CAAC,SAAS,GAAG,IAAI,KAAK,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC,IAE7E,IAAkB,QAClB,EAAS,WAAW,IAAU,IACtB,EAAS,QAAQ,GAAK,MAAM,IAAM,EAAoB,EAAE,EAAE,EAAE,GAC3D,EAAS,QACrB,CAAC,EAAS,CAAC,EAER,IAAY,EAAO,MACnB,KAAa,GAAW,gBACzB,EAAU,WAAW,EAAU,gBAAiB,MACjD;AAKJ,KAFE,EAAS,aAAa,EAAS,aAAa,EAAM,aAAa,EAAO,UAGtE,QACE,kBAAC,GAAD;EAAY,OAAM;EAAe,eAAA;EAAc,QAAA;YAC7C,kBAAC,GAAD,EAAa,OAAM,kCAAmC,CAAA;EAC3C,CAAA;CAIjB,IAAM,KAAe,GAAa,aAAa,GAAa,QAAQ,SAE9D,KAA8C;EAClD;GACE,IAAI;GACJ,MACE,kBAAC,GAAD;IACE,MAAM,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;IACtC,OAAO,EAAa,GAAY,OAAO;IACvC,OAAM;IACN,QACE,kBAAC,KAAD;KAAG,WAAU;eACX,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAW,WAAW;MACrC,WAAU;gBACX;MAEQ,CAAA;KACP,CAAA;IAEN,CAAA;GAEL;EACD;GACE,IAAI;GACJ,MACE,kBAAC,GAAD;IACE,MAAM,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA;IACnC,OAAO,EAAa,GAAkB;IACtC,OAAM;IACN,QAAQ,IAAgB,YAAY,KAAA;IACpC,QACE,IACE,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAW,SAAS,EAAc,KAAK;KACtD,WAAU;eACX;KAEQ,CAAA,GAET,kBAAC,KAAD;KAAG,WAAU;eAAb;MACE,kBAAC,QAAD;OAAM,WAAU;iBAAoC,EAAiB;OAAc,CAAA;MAAC;MAAI;MAEtF;;IAGR,CAAA;GAEL;EACD;GACE,IAAI;GACJ,MACE,kBAAC,GAAD;IACE,MAAM,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA;IACpC,OAAO,EAAa,EAAS,OAAO;IACpC,OAAM;IACN,QACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MAAS,OAAO;MAAiB,QAAQ;MAAG,cAAa;MAAyB,CAAA,EAClF,kBAAC,KAAD;MAAG,WAAU;gBAAb;OACE,kBAAC,QAAD;QAAM,WAAU;kBACb,EAAc,EAAgB;QAC1B,CAAA;OAAC;OAAI;OAEV;QACA;;IAER,CAAA;GAEL;EACD;GACE,IAAI;GACJ,MACE,kBAAC,GAAD;IACE,MAAM,kBAAC,IAAD,EAAQ,WAAU,WAAY,CAAA;IACpC,OAAO,IAAe,EAAa,EAAa,MAAM,GAAG;IACzD,OAAM;IACN,QACE,KAAe,OAeb,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAW,WAAW;KACrC,WAAU;eACX;KAEQ,CAAA,GApBT,kBAAC,KAAD;KAAG,WAAU;eAAb;MACE,kBAAC,QAAD;OACE,WACE,KAAe,IACX,yCACA;iBAJR;QAOG,IAAc,IAAI,MAAM;QACxB,EAAY,QAAQ,EAAE;QAAC;QACnB;;MAAC;MAAI;MAEV;;IAWR,CAAA;GAEL;EACD;GACE,IAAI;GACJ,MACE,kBAAC,GAAD;IACE,MAAM,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA;IAClC,OAAO,IAAkB,GAAG,EAAgB,UAAU;IACtD,OAAM;IACN,QACE,IACI,EAAgB,SAAS,KACvB,YACA,EAAgB,SAAS,KACvB,YACA,UACJ,KAAA;IAEN,QACE,IACE,kBAAC,KAAD;KAAG,WAAU;eACV,EAAmB,EAAgB,SAAS;KAC3C,CAAA,GAEJ,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAW,WAAW;KACrC,WAAU;eACX;KAEQ,CAAA;IAGb,CAAA;GAEL;EACF;AAED,QACE,kBAAC,GAAD;EACE,OAAM;EACN,eAAA;EACA,QAAA;EACA,aAAa,iBAAiB,GAAa;YAJ7C;GAOE,kBAAC,OAAD;IACE,aAAU;IACV,WAAU;cAET,GAAS,KAAK,GAAM,MACnB,kBAAC,EAAO,KAAR;KAEE,WAAU;KACV,GAAI,EAAK,EAAE;KACX,GAAI;eAJN,CAOE,kBAAC,QAAD;MACE,eAAA;MACA,WAAU;MACV,CAAA,EACD,EAAK,KACK;OAXN,EAAK,GAWC,CACb;IACE,CAAA;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf,CAEE,kBAAC,EAAO,KAAR;KACE,aAAU;KACV,WAAU;KACV,GAAI,EAAK,EAAE;eAEX,kBAAC,GAAD;MAAW,WAAU;MAAQ,WAAU;gBAAvC,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;QAAI,WAAU;kBAA4C;QAErD,CAAA,EACL,kBAAC,KAAD;QAAG,WAAU;kBAAqC;QAE9C,CAAA,CACA,EAAA,CAAA,EACN,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,EAAW,YAAY;QACtC,WAAU;kBAHZ,CAIC,cACW,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA,CACrC;UACL;UACL,CAAC,KAAc,EAAU,kBAAkB,KAAK,EAAU,aAAa,IACtE,kBAAC,GAAD;OACE,cAAa;OACb,MAAM,kBAAC,IAAD,EAAO,WAAU,aAAc,CAAA;OACrC,OAAM;OACN,aAAY;OACZ,CAAA,GAEF,kBAAC,OAAD;OAAK,WAAU;iBAAf,CAEE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD;SACE,eAAA;SACA,WAAU;SACV,OAAO,EACL,YACE,iHACH;SACD,CAAA,EACF,kBAAC,IAAD;SAAc,OAAO;SAAY,MAAM;SAAK,QAAQ;mBAApD;UACE,kBAAC,QAAD;WAAM,WAAU;qBAAqE;WAE9E,CAAA;UACP,kBAAC,QAAD;WAAM,WAAU;qBACb,EAAa,EAAU,SAAS;WAC5B,CAAA;UACP,kBAAC,QAAD;WAAM,WAAU;qBAAhB;YAAoD;YAC/C,EAAa,EAAU,cAAc;YAAC;YACpC;;UACM;WACX;WACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,IAAD;UACE,UAAU,EAAU;UACpB,QAAQ,EAAU;UAClB,MAAM,EAAU;UAChB,QAAQ;UACR,CAAA,EACF,kBAAC,IAAD,EAAe,CAAA,CACX;YACN,kBAAC,OAAD;SAAK,WAAU;mBAEX;UACE;WAAC;WAAW,EAAU;WAAU,EAAU;WAAc;UACxD;WAAC;WAAS,EAAU;WAAQ,EAAU;WAAY;UAClD;WAAC;WAAO,EAAU;WAAM,EAAU;WAAU;UAC7C,CACD,KAAK,CAAC,GAAO,GAAK,OAAY;UAC9B,IAAM,IAAM,IAAU,IAAM,IAAU,MAAM;AAC5C,iBACE,kBAAC,OAAD;WAEE,WAAU;qBAFZ;YAIE,kBAAC,OAAD;aAAK,WAAU;uBAAf,CACE,kBAAC,KAAD;cAAG,WAAU;wBAA0C;cAAU,CAAA,EACjE,kBAAC,QAAD;cAAM,WAAU;wBACb,EAAc,EAAI;cACd,CAAA,CACH;;YACN,kBAAC,KAAD;aAAG,WAAU;uBAAb,CACG,EAAa,EAAI,EAClB,kBAAC,QAAD;cAAM,WAAU;wBAAhB;eACG;eAAI;eACF,EAAa,EAAO;eAAC;eACnB;gBACL;;YACJ,kBAAC,GAAD;aAAO,OAAO;aAAK,QAAQ;aAAG,WAAU;aAAS,CAAA;YAC7C;aAjBC,EAiBD;WAER;SACE,CAAA,CACF;UACF;SAEE;;KACD,CAAA,EAGb,kBAAC,EAAO,KAAR;KAAY,aAAU;KAAiC,GAAI,EAAK,EAAE;eAChE,kBAAC,GAAD;MAAW,WAAU;MAAQ,WAAU;gBAAvC,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,MAAD;QAAI,WAAU;kBAA4C;QAAY,CAAA,EACtE,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,EAAW,QAAQ;QAClC,WAAU;kBAHZ,CAIC,UACO,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA,CACjC;UACL;UACL,IACC,kBAAC,EAAO,QAAR;OACE,MAAK;OACL,eAAe,EAAW,SAAS,EAAc,KAAK;OACtD,WAAU;OACV,GAAI;iBAJN;QAME,kBAAC,OAAD;SAAK,WAAU;mBACb,kBAAC,GAAD,EAAa,QAAQ,EAAc,QAAU,CAAA;SACzC,CAAA;QACN,kBAAC,KAAD;SAAG,WAAU;mBAA6C;SAEtD,CAAA;QACJ,kBAAC,KAAD;SAAG,WAAU;mBAAb,CAAiD,YACtC,EAAmB,EAAc,UAAU,CAClD;;QACU;WACd,IACF,kBAAC,EAAO,QAAR;OACE,MAAK;OACL,eAAe,EAAW,SAAS,EAAgB,KAAK;OACxD,WAAU;OACV,GAAI;iBAEJ,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD,EAAU,MAAM,kBAAC,IAAD,EAAe,WAAU,WAAY,CAAA,EAAI,CAAA,EACzD,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;SAAG,WAAU;mBAAwC;SAAqB,CAAA,EAC1E,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAmB,EAAgB,aAAa;SAC/C,CAAA,CACA,EAAA,CAAA,CACF;;OACQ,CAAA,GAEhB,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,QAAD;SAAM,WAAU;mBACd,kBAAC,GAAD,EAAO,WAAU,uBAAwB,CAAA;SACpC,CAAA;QACP,kBAAC,KAAD;SAAG,WAAU;mBAA6C;SAAwB,CAAA;QAClF,kBAAC,UAAD;SACE,MAAK;SACL,eAAe,EAAW,QAAQ;SAClC,WAAU;mBACX;SAEQ,CAAA;QACL;SAEE;;KACD,CAAA,CACT;;GAGN,kBAAC,GAAD;IACE,aAAU;IACV,OAAM;IACN,aAAY;IACZ,QACE,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAW,QAAQ;KAClC,WAAU;eAHZ,CAIC,cACW,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA,CACrC;;cAGV,EAAS,WAAW,IACnB,kBAAC,GAAD;KACE,cAAa;KACb,MAAM,kBAAC,GAAD,EAAQ,WAAU,aAAc,CAAA;KACtC,OAAM;KACN,aAAY;KACZ,CAAA,GAEF,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAS,MAAM,GAAG,EAAE,CAAC,KAAK,GAAG,MAAM;MAClC,IAAM,IAAM,EAAoB,EAAE,EAC5B,IAAO,EAAe,EAAE,OACxB,IAAO,EAAK;AAClB,aACE,kBAAC,EAAO,QAAR;OAEE,MAAK;OACL,eAAe,EAAW,QAAQ;OAClC,WAAU;OACV,GAAI,EAAK,EAAE;OACX,GAAI;iBAEJ,kBAAC,GAAD;QACE,WAAU;QACV,WAAU;kBAFZ,CAIE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD;UAAU,MAAM,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA;UAAE,MAAM,EAAK;UAAQ,CAAA,EACjE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,KAAD;WAAG,WAAU;qBACV,EAAE;WACD,CAAA,EACJ,kBAAC,KAAD;WAAG,WAAU;qBAA8B,EAAgB,EAAE,KAAK;WAAK,CAAA,CACnE;YACF;YACN,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD,EAAO,OAAO,GAAO,CAAA,EACrB,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,QAAD;WAAM,WAAU;qBAAhB;YACG,EAAa,EAAE,aAAa;YAC5B,EAAE,OAAO,IAAI,EAAE,SAAS;YAAG;YAAI,EAAa,EAAE,YAAY;YAC1D,EAAE,OAAO,IAAI,EAAE,SAAS;YACpB;cACP,kBAAC,QAAD;WAAM,WAAU;qBACb,EAAc,EAAI;WACd,CAAA,CACH;YACF;WACI;;OACE,EAlCT,EAAE,GAkCO;OAElB;KACE,CAAA;IAEA,CAAA;GAGV,kBAAC,GAAD;IACE,aAAU;IACV,OAAM;IACN,aAAY;IACZ,QACE,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAW,QAAQ;KAClC,WAAU;eAHZ,CAIC,iBACc,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA,CACxC;;cAGV,EAAY,WAAW,IACtB,kBAAC,GAAD;KACE,cAAa;KACb,MAAM,kBAAC,GAAD,EAAO,WAAU,aAAc,CAAA;KACrC,OAAM;KACN,aAAY;KACZ,CAAA,GAEF,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAY,MAAM,GAAG,EAAE,CAAC,KAAK,GAAG,MAAM;MACrC,IAAM,IAAO,GAAkB,UACzB,IAAO,EAAE,WAAW,cAAc,IAAe,EAAK;AAC5D,aACE,kBAAC,EAAO,QAAR;OAEE,MAAK;OACL,eAAe,EAAW,SAAS,EAAE,KAAK;OAC1C,WAAU;OACV,GAAI,EAAK,EAAE;OACX,GAAI;iBAEJ,kBAAC,GAAD;QAAW,WAAU;QAAQ,WAAU;kBAAvC;SACE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,GAAD;WAAU,MAAM,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA;WAAE,MAAM,EAAK;WAAQ,CAAA,EACjE,kBAAC,GAAD,EAAa,QAAQ,EAAE,QAAU,CAAA,CAC7B;;SACN,kBAAC,KAAD;UAAG,WAAU;oBACV,EAAE,iBAAiB,IAChB,GAAG,EAAa,EAAE,eAAe,CAAC,gBAClC;UACF,CAAA;SACJ,kBAAC,KAAD;UAAG,WAAU;oBACV,EAAmB,EAAE,eAAe,EAAE,aAAa,EAAE,UAAU;UAC9D,CAAA;SACM;;OACE,EArBT,EAAE,GAqBO;OAElB;KACE,CAAA;IAEA,CAAA;GACC"}
|
|
1
|
+
{"version":3,"file":"DashboardPage.js","names":[],"sources":["../../../src/mybodyshield/pages/DashboardPage.tsx"],"sourcesContent":["/**\n * Dashboard — MyBodyShield health & fitness overview.\n * @module mybodyshield/pages\n *\n * KPIs are aggregated client-side from the real `myb*` list endpoints:\n * active programs, this-week's sessions, goal progress, today's macros vs\n * target, latest body weight and recovery readiness. No mock data.\n */\n\nimport { useMemo } from 'react';\nimport type { ReactNode } from 'react';\nimport { motion, useReducedMotion } from 'framer-motion';\nimport {\n Dumbbell,\n Flame,\n Target,\n Salad,\n Moon,\n ArrowRight,\n CheckCircle2,\n Weight,\n CalendarCheck,\n} from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../components/PageLayout';\nimport { MetricsCard } from '../components/MetricsCard';\nimport { StatusBadge } from '../components/StatusBadge';\nimport { MiniBar } from '../components/MiniBar';\nimport { LoadingView, Section } from '../components/StateViews';\nimport { EmptyState } from '../components/EmptyState';\nimport {\n Meter,\n ProgressRing,\n MacroBar,\n MacroLegend,\n IconChip,\n GOAL_TYPE_META,\n RECOVERY_META,\n WORKOUT_TYPE_META,\n computeGoalProgress,\n} from './_shared/visuals';\nimport { useMyBodyShield } from '../context';\nimport {\n useMybPrograms,\n useMybSessions,\n useMybGoals,\n useMybDailyMacros,\n useMybBodyMetrics,\n useMybRecoveryLogs,\n} from '../hooks';\n\nimport {\n formatNumber,\n formatRelativeTime,\n formatKcal,\n formatWeight,\n formatPercent,\n formatEnumLabel,\n} from '../utils/formatters';\n\n/** Shared easing for entrance / hover motion (smooth ease-out). */\nconst EASE_OUT: [number, number, number, number] = [0.16, 1, 0.3, 1];\n\nexport function DashboardPage() {\n const { navigateTo, currentUser } = useMyBodyShield();\n const reduceMotion = useReducedMotion();\n\n /** Staggered fade/rise entrance — one-shot, skipped under reduced motion. */\n const rise = (i: number) =>\n reduceMotion\n ? {}\n : {\n initial: { opacity: 0, y: 14 },\n whileInView: { opacity: 1, y: 0 },\n viewport: { once: true, amount: 0.15 },\n transition: { duration: 0.45, delay: Math.min(i, 8) * 0.05, ease: EASE_OUT },\n };\n\n /** Gentle hover lift for cards. */\n const lift = reduceMotion\n ? {}\n : { whileHover: { y: -3, transition: { duration: 0.2, ease: EASE_OUT } } };\n\n /** Hover lift + tap feedback for interactive cards. */\n const liftTap = reduceMotion\n ? {}\n : {\n whileHover: { y: -3, transition: { duration: 0.2, ease: EASE_OUT } },\n whileTap: { scale: 0.985 },\n };\n\n const programs = useMybPrograms({ status: 'ACTIVE', pagination: { limit: 50 } });\n const sessions = useMybSessions({ pagination: { limit: 20 } });\n const goals = useMybGoals({ status: 'ACTIVE', pagination: { limit: 50 } });\n const macros = useMybDailyMacros();\n const weightMetrics = useMybBodyMetrics({ type: 'WEIGHT', pagination: { limit: 30 } });\n const readiness = useMybRecoveryLogs('READINESS');\n\n const programRows = programs.data?.items ?? [];\n const sessionRows = sessions.data?.items ?? [];\n const goalRows = goals.data?.items ?? [];\n const weightRows = weightMetrics.data?.items ?? [];\n const readinessRows = readiness.data?.items ?? [];\n\n const now = new Date();\n const weekAgo = now.getTime() - 7 * 24 * 60 * 60 * 1000;\n const sessionsThisWeek = sessionRows.filter((s) => {\n const ref = s.completedAt ?? s.startedAt ?? s.scheduledFor ?? s.createdAt;\n return ref ? new Date(ref).getTime() >= weekAgo : false;\n });\n const completedThisWeek = sessionsThisWeek.filter((s) => s.status === 'COMPLETED').length;\n const activeSession = sessionRows.find((s) => s.status === 'IN_PROGRESS');\n const upcomingSession = sessionRows\n .filter((s) => s.status === 'SCHEDULED' && s.scheduledFor)\n .sort((a, b) => new Date(a.scheduledFor!).getTime() - new Date(b.scheduledFor!).getTime())[0];\n\n const latestWeight = weightRows[0];\n const priorWeight = weightRows[1];\n const weightDelta =\n latestWeight && priorWeight ? latestWeight.value - priorWeight.value : undefined;\n\n const latestReadiness = readinessRows\n .slice()\n .sort((a, b) => new Date(b.loggedAt).getTime() - new Date(a.loggedAt).getTime())[0];\n\n const goalProgressAvg = useMemo(() => {\n if (goalRows.length === 0) return 0;\n const sum = goalRows.reduce((acc, g) => acc + computeGoalProgress(g), 0);\n return sum / goalRows.length;\n }, [goalRows]);\n\n const macroData = macros.data;\n const caloriePct = macroData?.calorieTarget\n ? (macroData.calories / macroData.calorieTarget) * 100\n : 0;\n\n const isInitialLoading =\n programs.isLoading && sessions.isLoading && goals.isLoading && macros.isLoading;\n\n if (isInitialLoading) {\n return (\n <PageLayout title=\"MyBodyShield\" titleGradient aurora>\n <LoadingView label=\"Loading your fitness overview…\" />\n </PageLayout>\n );\n }\n\n const greetingName = currentUser?.firstName || currentUser?.name || 'there';\n\n const kpiCards: { id: string; node: ReactNode }[] = [\n {\n id: 'programs',\n node: (\n <MetricsCard\n icon={<Dumbbell className=\"h-5 w-5\" />}\n value={formatNumber(programRows.length)}\n label=\"Active programs\"\n footer={\n <p className=\"text-xs text-text-tertiary\">\n <button\n type=\"button\"\n onClick={() => navigateTo('programs')}\n className=\"font-medium text-accent hover:underline\"\n >\n Manage programs\n </button>\n </p>\n }\n />\n ),\n },\n {\n id: 'sessions',\n node: (\n <MetricsCard\n icon={<Flame className=\"h-5 w-5\" />}\n value={formatNumber(completedThisWeek)}\n label=\"Sessions this week\"\n accent={activeSession ? 'warning' : undefined}\n footer={\n activeSession ? (\n <button\n type=\"button\"\n onClick={() => navigateTo(`train/${activeSession.id}`)}\n className=\"text-xs font-medium text-status-warning-text hover:underline\"\n >\n Resume live session →\n </button>\n ) : (\n <p className=\"text-xs text-text-tertiary\">\n <span className=\"tabular-nums text-text-secondary\">{sessionsThisWeek.length}</span>{' '}\n logged\n </p>\n )\n }\n />\n ),\n },\n {\n id: 'goals',\n node: (\n <MetricsCard\n icon={<Target className=\"h-5 w-5\" />}\n value={formatNumber(goalRows.length)}\n label=\"Active goals\"\n footer={\n <div className=\"space-y-2\">\n <MiniBar value={goalProgressAvg} height={4} barClassName=\"bg-action-primary-bg\" />\n <p className=\"text-xs text-text-tertiary\">\n <span className=\"tabular-nums text-text-secondary\">\n {formatPercent(goalProgressAvg)}\n </span>{' '}\n avg progress\n </p>\n </div>\n }\n />\n ),\n },\n {\n id: 'weight',\n node: (\n <MetricsCard\n icon={<Weight className=\"h-5 w-5\" />}\n value={latestWeight ? formatWeight(latestWeight.value) : '—'}\n label=\"Latest weight\"\n footer={\n weightDelta != null ? (\n <p className=\"text-xs text-text-tertiary\">\n <span\n className={\n weightDelta <= 0\n ? 'font-medium text-status-success-text'\n : 'font-medium text-text-secondary'\n }\n >\n {weightDelta > 0 ? '+' : ''}\n {weightDelta.toFixed(1)} kg\n </span>{' '}\n vs last\n </p>\n ) : (\n <button\n type=\"button\"\n onClick={() => navigateTo('progress')}\n className=\"text-xs font-medium text-accent hover:underline\"\n >\n Log a measurement →\n </button>\n )\n }\n />\n ),\n },\n {\n id: 'recovery',\n node: (\n <MetricsCard\n icon={<Moon className=\"h-5 w-5\" />}\n value={latestReadiness ? `${latestReadiness.score}` : '—'}\n label=\"Recovery readiness\"\n accent={\n latestReadiness\n ? latestReadiness.score >= 70\n ? 'success'\n : latestReadiness.score >= 40\n ? 'warning'\n : 'error'\n : undefined\n }\n footer={\n latestReadiness ? (\n <p className=\"text-xs text-text-tertiary\">\n {formatRelativeTime(latestReadiness.loggedAt)}\n </p>\n ) : (\n <button\n type=\"button\"\n onClick={() => navigateTo('recovery')}\n className=\"text-xs font-medium text-accent hover:underline\"\n >\n Log recovery →\n </button>\n )\n }\n />\n ),\n },\n ];\n\n return (\n <PageLayout\n title=\"MyBodyShield\"\n titleGradient\n aurora\n description={`Welcome back, ${greetingName} — your training, nutrition, progress & recovery control room.`}\n >\n {/* KPI grid */}\n <div\n data-tour=\"mybodyshield-dashboard-kpi-grid\"\n className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5\"\n >\n {kpiCards.map((card, i) => (\n <motion.div\n key={card.id}\n className=\"group relative rounded-2xl transition-shadow duration-300 hover:shadow-float\"\n {...rise(i)}\n {...lift}\n >\n {/* Emerald top-edge accent — brightens on hover */}\n <span\n aria-hidden\n className=\"pointer-events-none absolute inset-x-6 top-0 z-10 h-px rounded-full bg-gradient-to-r from-transparent via-action-primary-bg to-transparent opacity-40 transition-opacity duration-300 group-hover:opacity-90\"\n />\n {card.node}\n </motion.div>\n ))}\n </div>\n\n <div className=\"mt-6 grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {/* Today's nutrition */}\n <motion.div\n data-tour=\"mybodyshield-dashboard-nutrition\"\n className=\"lg:col-span-2\"\n {...rise(0)}\n >\n <GlassCard treatment=\"glass\" className=\"h-full p-6\">\n <div className=\"mb-4 flex flex-wrap items-start justify-between gap-3\">\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">\n Today's nutrition\n </h2>\n <p className=\"mt-0.5 text-sm text-text-secondary\">\n Calories & macros logged against your active plan.\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => navigateTo('nutrition')}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-accent hover:underline\"\n >\n Nutrition <ArrowRight className=\"h-4 w-4\" />\n </button>\n </div>\n {!macroData || (macroData.calorieTarget === 0 && macroData.calories === 0) ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Salad className=\"h-10 w-10\" />}\n title=\"No nutrition data yet\"\n description=\"Activate a nutrition plan and log meals to track your daily macros here.\"\n />\n ) : (\n <div className=\"flex flex-col gap-8 sm:flex-row sm:items-center\">\n {/* Calorie ring — the nutrition hero */}\n <div className=\"relative mx-auto flex shrink-0 items-center justify-center sm:mx-0\">\n <span\n aria-hidden\n className=\"pointer-events-none absolute inset-2 rounded-full opacity-70 blur-2xl\"\n style={{\n background:\n 'radial-gradient(circle, color-mix(in srgb, var(--color-action-primary-bg) 26%, transparent), transparent 70%)',\n }}\n />\n <ProgressRing value={caloriePct} size={188} stroke={16}>\n <span className=\"text-[11px] font-medium uppercase tracking-wide text-text-tertiary\">\n Calories\n </span>\n <span className=\"text-3xl font-bold tabular-nums text-text-primary\">\n {formatNumber(macroData.calories)}\n </span>\n <span className=\"mt-0.5 text-xs text-text-tertiary\">\n / {formatNumber(macroData.calorieTarget)} kcal\n </span>\n </ProgressRing>\n </div>\n <div className=\"flex-1 space-y-4\">\n <div className=\"space-y-2\">\n <MacroBar\n proteinG={macroData.proteinG}\n carbsG={macroData.carbsG}\n fatG={macroData.fatG}\n height={12}\n />\n <MacroLegend />\n </div>\n <div className=\"grid grid-cols-3 gap-3\">\n {(\n [\n ['Protein', macroData.proteinG, macroData.proteinTarget],\n ['Carbs', macroData.carbsG, macroData.carbsTarget],\n ['Fat', macroData.fatG, macroData.fatTarget],\n ] as const\n ).map(([label, val, target]) => {\n const pct = target ? (val / target) * 100 : 0;\n return (\n <div\n key={label}\n className=\"rounded-2xl bg-bg-sunken/60 p-3 ring-1 ring-inset ring-border-seam/40 transition-shadow hover:shadow-float\"\n >\n <div className=\"flex items-center justify-between gap-1\">\n <p className=\"text-xs font-medium text-text-tertiary\">{label}</p>\n <span className=\"text-[11px] tabular-nums text-text-tertiary\">\n {formatPercent(pct)}\n </span>\n </div>\n <p className=\"mt-1 text-sm font-semibold tabular-nums text-text-primary\">\n {formatNumber(val)}\n <span className=\"text-xs font-normal text-text-tertiary\">\n {' '}\n / {formatNumber(target)} g\n </span>\n </p>\n <Meter value={pct} height={5} className=\"mt-2\" />\n </div>\n );\n })}\n </div>\n </div>\n </div>\n )}\n </GlassCard>\n </motion.div>\n\n {/* Next up */}\n <motion.div data-tour=\"mybodyshield-dashboard-next-up\" {...rise(1)}>\n <GlassCard treatment=\"glass\" className=\"h-full p-6\">\n <div className=\"mb-4 flex items-center justify-between gap-3\">\n <h2 className=\"text-base font-semibold text-text-primary\">Next up</h2>\n <button\n type=\"button\"\n onClick={() => navigateTo('train')}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-accent hover:underline\"\n >\n Train <ArrowRight className=\"h-4 w-4\" />\n </button>\n </div>\n {activeSession ? (\n <motion.button\n type=\"button\"\n onClick={() => navigateTo(`train/${activeSession.id}`)}\n className=\"w-full rounded-2xl border border-status-warning-text/40 bg-status-warning-bg-subtle p-4 text-left transition-shadow hover:shadow-float\"\n {...liftTap}\n >\n <div className=\"flex items-center gap-2\">\n <StatusBadge status={activeSession.status} />\n </div>\n <p className=\"mt-2 text-sm font-medium text-text-primary\">\n Live workout in progress\n </p>\n <p className=\"mt-0.5 text-xs text-text-tertiary\">\n Started {formatRelativeTime(activeSession.startedAt)}\n </p>\n </motion.button>\n ) : upcomingSession ? (\n <motion.button\n type=\"button\"\n onClick={() => navigateTo(`train/${upcomingSession.id}`)}\n className=\"w-full rounded-2xl bg-bg-sunken/60 p-4 text-left ring-1 ring-inset ring-border-seam/40 transition-shadow hover:shadow-float\"\n {...liftTap}\n >\n <div className=\"flex items-center gap-2\">\n <IconChip icon={<CalendarCheck className=\"h-4 w-4\" />} />\n <div>\n <p className=\"text-sm font-medium text-text-primary\">Scheduled session</p>\n <p className=\"text-xs text-text-tertiary\">\n {formatRelativeTime(upcomingSession.scheduledFor)}\n </p>\n </div>\n </div>\n </motion.button>\n ) : (\n <div className=\"flex flex-col items-center justify-center rounded-2xl border border-dashed border-border-seam py-8 text-center\">\n <span className=\"flex size-11 items-center justify-center rounded-full bg-[var(--color-accent-soft)]\">\n <Flame className=\"h-5 w-5 text-accent\" />\n </span>\n <p className=\"mt-3 text-sm font-medium text-text-primary\">No session scheduled</p>\n <button\n type=\"button\"\n onClick={() => navigateTo('train')}\n className=\"mt-1 text-xs font-medium text-accent hover:underline\"\n >\n Start a workout\n </button>\n </div>\n )}\n </GlassCard>\n </motion.div>\n </div>\n\n {/* Active goals */}\n <Section\n data-tour=\"mybodyshield-dashboard-active-goals\"\n title=\"Active goals\"\n description=\"Your current targets across training, nutrition and body composition.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('goals')}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-accent hover:underline\"\n >\n All goals <ArrowRight className=\"h-4 w-4\" />\n </button>\n }\n >\n {goalRows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Target className=\"h-10 w-10\" />}\n title=\"No active goals\"\n description=\"Set a goal to give your training and nutrition a clear target to work toward.\"\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {goalRows.slice(0, 6).map((g, i) => {\n const pct = computeGoalProgress(g);\n const meta = GOAL_TYPE_META[g.type];\n const Icon = meta.icon;\n return (\n <motion.button\n key={g.id}\n type=\"button\"\n onClick={() => navigateTo('goals')}\n className=\"text-left\"\n {...rise(i)}\n {...liftTap}\n >\n <GlassCard\n treatment=\"plain\"\n className=\"h-full p-4 transition-shadow hover:shadow-float\"\n >\n <div className=\"flex items-start gap-3\">\n <IconChip icon={<Icon className=\"h-4 w-4\" />} tint={meta.tint} />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-semibold text-text-primary\">\n {g.title}\n </p>\n <p className=\"text-xs text-text-tertiary\">{formatEnumLabel(g.type)}</p>\n </div>\n </div>\n <div className=\"mt-3 space-y-1.5\">\n <Meter value={pct} />\n <div className=\"flex items-center justify-between text-xs\">\n <span className=\"tabular-nums text-text-secondary\">\n {formatNumber(g.currentValue)}\n {g.unit ? ` ${g.unit}` : ''} / {formatNumber(g.targetValue)}\n {g.unit ? ` ${g.unit}` : ''}\n </span>\n <span className=\"tabular-nums text-text-tertiary\">\n {formatPercent(pct)}\n </span>\n </div>\n </div>\n </GlassCard>\n </motion.button>\n );\n })}\n </div>\n )}\n </Section>\n\n {/* Recent sessions */}\n <Section\n data-tour=\"mybodyshield-dashboard-recent-sessions\"\n title=\"Recent sessions\"\n description=\"Your latest logged and scheduled workouts.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('train')}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-accent hover:underline\"\n >\n All sessions <ArrowRight className=\"h-4 w-4\" />\n </button>\n }\n >\n {sessionRows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Flame className=\"h-10 w-10\" />}\n title=\"No sessions yet\"\n description=\"Start a workout session to begin logging sets and tracking your training.\"\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {sessionRows.slice(0, 6).map((s, i) => {\n const meta = WORKOUT_TYPE_META.STRENGTH;\n const Icon = s.status === 'COMPLETED' ? CheckCircle2 : meta.icon;\n return (\n <motion.button\n key={s.id}\n type=\"button\"\n onClick={() => navigateTo(`train/${s.id}`)}\n className=\"text-left\"\n {...rise(i)}\n {...liftTap}\n >\n <GlassCard treatment=\"plain\" className=\"p-4 transition-shadow hover:shadow-float\">\n <div className=\"flex items-center justify-between gap-2\">\n <IconChip icon={<Icon className=\"h-4 w-4\" />} tint={meta.tint} />\n <StatusBadge status={s.status} />\n </div>\n <p className=\"mt-3 text-sm font-medium text-text-primary\">\n {s.caloriesBurned > 0\n ? `${formatNumber(s.caloriesBurned)} kcal burned`\n : 'Workout session'}\n </p>\n <p className=\"mt-0.5 text-xs text-text-tertiary\">\n {formatRelativeTime(s.completedAt ?? s.startedAt ?? s.createdAt)}\n </p>\n </GlassCard>\n </motion.button>\n );\n })}\n </div>\n )}\n </Section>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -420,7 +420,7 @@ function te({ relationship: e, currentUserId: t, busy: i, onTransition: a }) {
|
|
|
420
420
|
});
|
|
421
421
|
}
|
|
422
422
|
function ne() {
|
|
423
|
-
let { currentUser: n } = e(), r = h(), [l, d] = v("ALL"), [f, p] = v(!1), [g, y] = v(null), [b, x] = v(null), S = c(), C = u(), w = S.data ?? [], E = _(() => {
|
|
423
|
+
let { currentUser: n } = e(), r = h(), [l, d] = v("ALL"), [f, p] = v(!1), [g, y] = v(null), [b, x] = v(null), S = c(), C = u(), w = S.data?.items ?? [], E = _(() => {
|
|
424
424
|
let e = {
|
|
425
425
|
PENDING: 0,
|
|
426
426
|
ACTIVE: 0,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CoachingPage.js","names":[],"sources":["../../../../src/mybodyshield/pages/coaching/CoachingPage.tsx"],"sourcesContent":["/**\n * Coaching — coach ↔ member relationships and shared data scope.\n * @module mybodyshield/pages/coaching\n *\n * Lists the member's `MybCoachingRelationship`s (a PLAIN ARRAY from\n * `mybListCoaching` — no pagination) with a status filter, per-relationship\n * sharing scope (workouts / nutrition / metrics), lifecycle transition actions\n * (accept / pause / resume / end) and a create flow. No mock data.\n */\n\nimport { useMemo, useState } from 'react';\nimport { motion, AnimatePresence, useReducedMotion } from 'framer-motion';\nimport {\n HeartHandshake,\n Plus,\n Dumbbell,\n Salad,\n LineChart,\n CalendarClock,\n CalendarX,\n Check,\n Minus,\n MessageSquare,\n ChevronDown,\n ChevronUp,\n User,\n} from 'lucide-react';\nimport {\n Button,\n Input,\n Label,\n Switch,\n GlassCard,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n StatusBadge,\n EmptyState,\n LoadingView,\n ErrorView,\n FilterTabs,\n MybDiscussion,\n UserSearchSelect,\n type FilterOption,\n type UserSelectValue,\n} from '../../components';\nimport { IconChip } from '../_shared/visuals';\nimport { useMyBodyShield } from '../../context';\nimport { useMybCoaching, useMybCreateCoaching, useMybTransitionCoaching } from '../../hooks';\nimport type {\n MybCoachingRelationship,\n MybCoachingStatus,\n MybCreateCoachingInput,\n} from '../../types';\nimport { formatDate } from '../../utils/formatters';\n\ntype StatusFilter = MybCoachingStatus | 'ALL';\n\nconst FILTER_ORDER: MybCoachingStatus[] = ['PENDING', 'ACTIVE', 'PAUSED', 'ENDED'];\n\n/** Shared entrance easing — a soft, decelerating cubic-bezier. */\nconst MOTION_EASE: [number, number, number, number] = [0.16, 1, 0.3, 1];\n\nconst STAGGER_CONTAINER = {\n hidden: {},\n show: { transition: { staggerChildren: 0.06 } },\n};\nconst RISE_ITEM = {\n hidden: { opacity: 0, y: 12 },\n show: { opacity: 1, y: 0, transition: { duration: 0.35, ease: MOTION_EASE } },\n};\n\n/** Allowed lifecycle transitions per current status (contract-defined). */\nconst TRANSITIONS: Record<\n MybCoachingStatus,\n { to: MybCoachingStatus; label: string; primary: boolean }[]\n> = {\n PENDING: [{ to: 'ACTIVE', label: 'Accept', primary: true }],\n ACTIVE: [\n { to: 'PAUSED', label: 'Pause', primary: false },\n { to: 'ENDED', label: 'End', primary: false },\n ],\n PAUSED: [\n { to: 'ACTIVE', label: 'Resume', primary: true },\n { to: 'ENDED', label: 'End', primary: false },\n ],\n ENDED: [],\n};\n\n/** Shorten an opaque actor id for compact display (full id kept as a title). */\nfunction shortId(id: string): string {\n return id.length <= 10 ? id : `${id.slice(0, 8)}…`;\n}\n\n// ============================================================================\n// Create-coaching dialog\n// ============================================================================\n\ninterface CreateCoachingForm {\n memberId: string;\n memberName: string;\n coachId: string;\n coachName: string;\n specialty: string;\n shareWorkouts: boolean;\n shareNutrition: boolean;\n shareMetrics: boolean;\n}\n\nfunction buildEmptyForm(defaultMember: UserSelectValue): CreateCoachingForm {\n return {\n memberId: defaultMember.userId,\n memberName: defaultMember.displayName,\n coachId: '',\n coachName: '',\n specialty: '',\n shareWorkouts: true,\n shareNutrition: true,\n shareMetrics: true,\n };\n}\n\nfunction ShareToggle({\n id,\n label,\n description,\n checked,\n onChange,\n}: {\n id: string;\n label: string;\n description: string;\n checked: boolean;\n onChange: (value: boolean) => void;\n}) {\n return (\n <div className=\"flex items-center justify-between gap-4 rounded-xl bg-bg-sunken/60 p-3\">\n <div className=\"min-w-0\">\n <Label htmlFor={id} className=\"cursor-pointer\">\n {label}\n </Label>\n <p className=\"text-xs text-text-tertiary\">{description}</p>\n </div>\n <Switch id={id} checked={checked} onCheckedChange={onChange} />\n </div>\n );\n}\n\nfunction CreateCoachingDialog({\n open,\n onOpenChange,\n defaultMember,\n}: {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n defaultMember: UserSelectValue;\n}) {\n const create = useMybCreateCoaching();\n const [form, setForm] = useState<CreateCoachingForm>(() => buildEmptyForm(defaultMember));\n const [error, setError] = useState<string | null>(null);\n\n const handleOpenChange = (next: boolean) => {\n if (!next) {\n setForm(buildEmptyForm(defaultMember));\n setError(null);\n }\n onOpenChange(next);\n };\n\n const submit = async () => {\n const coachId = form.coachId.trim();\n const memberId = form.memberId.trim();\n if (!coachId) {\n setError('Coach user id is required.');\n return;\n }\n if (!memberId) {\n setError('Member user id is required.');\n return;\n }\n setError(null);\n const input: MybCreateCoachingInput = {\n coachId,\n memberId,\n specialty: form.specialty.trim() || undefined,\n shareWorkouts: form.shareWorkouts,\n shareNutrition: form.shareNutrition,\n shareMetrics: form.shareMetrics,\n };\n try {\n await create.mutateAsync(input);\n setForm(buildEmptyForm(defaultMember));\n onOpenChange(false);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to create coaching relationship.');\n }\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={handleOpenChange}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>New coaching</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Link a coach and member, and choose exactly what training, nutrition and progress data\n is shared.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <form\n className=\"space-y-4\"\n onSubmit={(event) => {\n event.preventDefault();\n void submit();\n }}\n >\n {error && (\n <p className=\"rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {error}\n </p>\n )}\n <UserSearchSelect\n label=\"Coach\"\n required\n htmlId=\"myb-coaching-coach\"\n userId={form.coachId}\n displayName={form.coachName}\n onSelect={({ userId, displayName }) =>\n setForm((f) => ({ ...f, coachId: userId, coachName: displayName }))\n }\n />\n <UserSearchSelect\n label=\"Member\"\n htmlId=\"myb-coaching-member\"\n userId={form.memberId}\n displayName={form.memberName}\n onSelect={({ userId, displayName }) =>\n setForm((f) => ({ ...f, memberId: userId, memberName: displayName }))\n }\n />\n <div className=\"space-y-2\">\n <Label htmlFor=\"myb-coaching-specialty\">Specialty</Label>\n <Input\n id=\"myb-coaching-specialty\"\n value={form.specialty}\n onChange={(event) => setForm((f) => ({ ...f, specialty: event.target.value }))}\n placeholder=\"e.g., Strength & conditioning\"\n />\n </div>\n <div className=\"space-y-2\">\n <p className=\"text-sm font-medium text-text-primary\">Shared with the coach</p>\n <ShareToggle\n id=\"myb-coaching-share-workouts\"\n label=\"Workouts\"\n description=\"Programs, sessions and logged sets.\"\n checked={form.shareWorkouts}\n onChange={(value) => setForm((f) => ({ ...f, shareWorkouts: value }))}\n />\n <ShareToggle\n id=\"myb-coaching-share-nutrition\"\n label=\"Nutrition\"\n description=\"Plans, meals and daily macros.\"\n checked={form.shareNutrition}\n onChange={(value) => setForm((f) => ({ ...f, shareNutrition: value }))}\n />\n <ShareToggle\n id=\"myb-coaching-share-metrics\"\n label=\"Metrics\"\n description=\"Body metrics and recovery logs.\"\n checked={form.shareMetrics}\n onChange={(value) => setForm((f) => ({ ...f, shareMetrics: value }))}\n />\n </div>\n <ResponsiveDialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => handleOpenChange(false)}\n disabled={create.isPending}\n >\n Cancel\n </Button>\n <Button type=\"submit\" disabled={create.isPending}>\n {create.isPending ? 'Creating…' : 'Create coaching'}\n </Button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\n// ============================================================================\n// Sharing scope chips\n// ============================================================================\n\nfunction ShareChip({ label, icon, on }: { label: string; icon: typeof Dumbbell; on: boolean }) {\n const Icon = icon;\n const StatusIcon = on ? Check : Minus;\n return (\n <span\n aria-label={`${label} ${on ? 'shared' : 'not shared'}`}\n className={`inline-flex items-center gap-1.5 rounded-full py-0.5 pl-1 pr-2.5 text-xs font-medium ${\n on\n ? 'bg-status-success-bg-subtle text-status-success-text ring-1 ring-inset ring-[color-mix(in_oklab,var(--color-status-success-text)_28%,transparent)]'\n : 'bg-bg-sunken text-text-tertiary ring-1 ring-inset ring-border-seam'\n }`}\n >\n <span\n aria-hidden=\"true\"\n className={`inline-flex size-4 items-center justify-center rounded-full ${\n on\n ? 'bg-[color-mix(in_oklab,var(--color-status-success-text)_22%,transparent)]'\n : 'bg-[color-mix(in_oklab,var(--color-text-tertiary)_18%,transparent)]'\n }`}\n >\n <StatusIcon className=\"h-2.5 w-2.5\" />\n </span>\n <Icon className=\"h-3 w-3\" aria-hidden=\"true\" />\n <span>\n {label} · {on ? 'On' : 'Off'}\n </span>\n </span>\n );\n}\n\n// ============================================================================\n// Coaching card\n// ============================================================================\n\nfunction CoachingCard({\n relationship,\n currentUserId,\n busy,\n onTransition,\n}: {\n relationship: MybCoachingRelationship;\n currentUserId?: string;\n busy: boolean;\n onTransition: (id: string, status: MybCoachingStatus) => void;\n}) {\n const coachIsYou = Boolean(currentUserId) && relationship.coachId === currentUserId;\n const memberIsYou = Boolean(currentUserId) && relationship.memberId === currentUserId;\n const actions = TRANSITIONS[relationship.status];\n const canDiscuss = relationship.status === 'ACTIVE' || relationship.status === 'PAUSED';\n const [showDiscussion, setShowDiscussion] = useState(false);\n const reduce = useReducedMotion();\n\n const isActive = relationship.status === 'ACTIVE';\n const isPending = relationship.status === 'PENDING';\n\n return (\n <GlassCard\n treatment=\"glass\"\n glow={isActive}\n className={\n 'flex h-full flex-col gap-4 p-5 transition-shadow hover:shadow-float' +\n (isPending\n ? ' ring-1 ring-inset ring-[color-mix(in_oklab,var(--color-status-warning-text)_35%,transparent)]'\n : '')\n }\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex min-w-0 items-start gap-3\">\n <IconChip icon={<HeartHandshake className=\"h-4 w-4\" />} />\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-semibold text-text-primary\">\n {relationship.specialty?.trim() || 'Coaching relationship'}\n </p>\n <p className=\"text-xs text-text-tertiary\">Coach ↔ member</p>\n </div>\n </div>\n <StatusBadge status={relationship.status} />\n </div>\n\n <div className=\"grid grid-cols-2 gap-3\">\n <div className=\"flex items-center gap-2.5 rounded-lg bg-bg-sunken/60 p-2.5\">\n <span\n aria-hidden=\"true\"\n className={`flex size-8 shrink-0 items-center justify-center rounded-full ring-1 ring-inset ${\n coachIsYou\n ? 'bg-[color-mix(in_oklab,var(--color-action-primary-bg)_16%,transparent)] text-action-primary-bg ring-[color-mix(in_oklab,var(--color-action-primary-bg)_40%,transparent)]'\n : 'bg-bg-surface text-text-tertiary ring-border-seam'\n }`}\n >\n <User className=\"h-4 w-4\" />\n </span>\n <div className=\"min-w-0\">\n <p className=\"text-[11px] font-medium uppercase tracking-wide text-text-tertiary\">\n Coach\n </p>\n <p\n className=\"mt-0.5 truncate text-sm font-medium text-text-primary\"\n title={relationship.coachId}\n >\n {coachIsYou ? 'You' : `#${shortId(relationship.coachId)}`}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2.5 rounded-lg bg-bg-sunken/60 p-2.5\">\n <span\n aria-hidden=\"true\"\n className={`flex size-8 shrink-0 items-center justify-center rounded-full ring-1 ring-inset ${\n memberIsYou\n ? 'bg-[color-mix(in_oklab,var(--color-action-primary-bg)_16%,transparent)] text-action-primary-bg ring-[color-mix(in_oklab,var(--color-action-primary-bg)_40%,transparent)]'\n : 'bg-bg-surface text-text-tertiary ring-border-seam'\n }`}\n >\n <User className=\"h-4 w-4\" />\n </span>\n <div className=\"min-w-0\">\n <p className=\"text-[11px] font-medium uppercase tracking-wide text-text-tertiary\">\n Member\n </p>\n <p\n className=\"mt-0.5 truncate text-sm font-medium text-text-primary\"\n title={relationship.memberId}\n >\n {memberIsYou ? 'You' : `#${shortId(relationship.memberId)}`}\n </p>\n </div>\n </div>\n </div>\n\n <div className=\"flex flex-wrap gap-1.5\">\n <ShareChip label=\"Workouts\" icon={Dumbbell} on={relationship.shareWorkouts} />\n <ShareChip label=\"Nutrition\" icon={Salad} on={relationship.shareNutrition} />\n <ShareChip label=\"Metrics\" icon={LineChart} on={relationship.shareMetrics} />\n </div>\n\n {(relationship.startedAt || relationship.endedAt) && (\n <div className=\"flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-text-tertiary\">\n {relationship.startedAt && (\n <span className=\"inline-flex items-center gap-1\">\n <CalendarClock className=\"h-3.5 w-3.5\" />\n Started {formatDate(relationship.startedAt)}\n </span>\n )}\n {relationship.endedAt && (\n <span className=\"inline-flex items-center gap-1\">\n <CalendarX className=\"h-3.5 w-3.5\" />\n Ended {formatDate(relationship.endedAt)}\n </span>\n )}\n </div>\n )}\n\n {actions.length > 0 && (\n <div className=\"mt-auto flex flex-wrap gap-2 pt-1\">\n {actions.map((action) => (\n <Button\n key={action.to}\n size=\"sm\"\n variant={action.primary ? undefined : 'outline'}\n disabled={busy}\n onClick={() => onTransition(relationship.id, action.to)}\n >\n {action.label}\n </Button>\n ))}\n </div>\n )}\n\n {canDiscuss && (\n <div\n className={`border-t border-border-seam pt-3 ${actions.length === 0 ? 'mt-auto' : ''}`}\n >\n <button\n type=\"button\"\n onClick={() => setShowDiscussion((prev) => !prev)}\n aria-expanded={showDiscussion}\n className=\"inline-flex items-center gap-1.5 text-sm font-medium text-accent hover:underline\"\n >\n <MessageSquare className=\"h-4 w-4\" />\n {showDiscussion ? 'Hide discussion' : 'Discussion'}\n {showDiscussion ? (\n <ChevronUp className=\"h-4 w-4\" />\n ) : (\n <ChevronDown className=\"h-4 w-4\" />\n )}\n </button>\n <AnimatePresence initial={false}>\n {showDiscussion && (\n <motion.div\n key=\"discussion\"\n initial={reduce ? false : { opacity: 0, height: 0 }}\n animate={{ opacity: 1, height: 'auto' }}\n exit={reduce ? { opacity: 0 } : { opacity: 0, height: 0 }}\n transition={reduce ? { duration: 0 } : { duration: 0.25, ease: MOTION_EASE }}\n className=\"overflow-hidden\"\n >\n <div className=\"mt-3\">\n <MybDiscussion\n entityType=\"mybodyshield-coaching\"\n entityId={relationship.id}\n title=\"Coaching discussion\"\n description=\"A private thread between the coach and member.\"\n />\n </div>\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n )}\n </GlassCard>\n );\n}\n\n// ============================================================================\n// Page\n// ============================================================================\n\nexport function CoachingPage() {\n const { currentUser } = useMyBodyShield();\n const reduce = useReducedMotion();\n const [statusFilter, setStatusFilter] = useState<StatusFilter>('ALL');\n const [createOpen, setCreateOpen] = useState(false);\n const [transitioningId, setTransitioningId] = useState<string | null>(null);\n const [actionError, setActionError] = useState<string | null>(null);\n\n // `useMybCoaching` returns a PLAIN ARRAY — fetch every relationship once and\n // filter client-side so the tab counts stay accurate across filters.\n const coaching = useMybCoaching();\n const transition = useMybTransitionCoaching();\n\n const all = coaching.data ?? [];\n\n const counts = useMemo(() => {\n const map: Record<MybCoachingStatus, number> = {\n PENDING: 0,\n ACTIVE: 0,\n PAUSED: 0,\n ENDED: 0,\n };\n for (const rel of all) map[rel.status] += 1;\n return map;\n }, [all]);\n\n const statusFilters = useMemo<FilterOption<StatusFilter>[]>(\n () => [\n { value: 'ALL', label: 'All', count: all.length },\n ...FILTER_ORDER.map((status) => ({\n value: status as StatusFilter,\n label: status.charAt(0) + status.slice(1).toLowerCase(),\n count: counts[status],\n })),\n ],\n [all.length, counts]\n );\n\n const rows = useMemo(\n () => (statusFilter === 'ALL' ? all : all.filter((rel) => rel.status === statusFilter)),\n [all, statusFilter]\n );\n\n const handleTransition = async (id: string, status: MybCoachingStatus) => {\n setTransitioningId(id);\n setActionError(null);\n try {\n await transition.mutateAsync({ id, status });\n } catch (err) {\n setActionError(err instanceof Error ? err.message : 'Failed to update the relationship.');\n } finally {\n setTransitioningId(null);\n }\n };\n\n const showLoading = coaching.isLoading && !coaching.data;\n\n return (\n <PageLayout\n title=\"Coaching\"\n description=\"Coach ↔ member relationships and what's shared.\"\n actions={\n <Button onClick={() => setCreateOpen(true)}>\n <Plus className=\"mr-2 h-4 w-4\" />\n New coaching\n </Button>\n }\n >\n <div className=\"mb-6\">\n <FilterTabs options={statusFilters} value={statusFilter} onChange={setStatusFilter} />\n </div>\n\n {actionError && (\n <p className=\"mb-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {actionError}\n </p>\n )}\n\n {showLoading ? (\n <LoadingView label=\"Loading coaching relationships…\" />\n ) : coaching.isError ? (\n <ErrorView\n message=\"We couldn't load your coaching relationships.\"\n onRetry={() => void coaching.refetch()}\n />\n ) : rows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<HeartHandshake className=\"h-10 w-10\" />}\n title={\n all.length === 0 ? 'No coaching relationships' : 'No relationships match this filter'\n }\n description={\n all.length === 0\n ? 'Connect a coach and a member to start sharing training, nutrition and progress data.'\n : 'Try a different status filter, or create a new coaching relationship.'\n }\n action={\n <Button onClick={() => setCreateOpen(true)}>\n <Plus className=\"mr-2 h-4 w-4\" />\n New coaching\n </Button>\n }\n />\n ) : (\n <motion.div\n className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\"\n variants={reduce ? undefined : STAGGER_CONTAINER}\n initial={reduce ? false : 'hidden'}\n animate=\"show\"\n >\n {rows.map((relationship) => (\n <motion.div\n key={relationship.id}\n className=\"h-full\"\n variants={reduce ? undefined : RISE_ITEM}\n >\n <CoachingCard\n relationship={relationship}\n currentUserId={currentUser?.id}\n busy={transitioningId === relationship.id}\n onTransition={(id, status) => void handleTransition(id, status)}\n />\n </motion.div>\n ))}\n </motion.div>\n )}\n\n <CreateCoachingDialog\n open={createOpen}\n onOpenChange={setCreateOpen}\n defaultMember={{\n userId: currentUser?.id ?? '',\n displayName: currentUser?.name || currentUser?.email || '',\n }}\n />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CoachingPage.js","names":[],"sources":["../../../../src/mybodyshield/pages/coaching/CoachingPage.tsx"],"sourcesContent":["/**\n * Coaching — coach ↔ member relationships and shared data scope.\n * @module mybodyshield/pages/coaching\n *\n * Lists the member's `MybCoachingRelationship`s (a PLAIN ARRAY from\n * `mybListCoaching` — no pagination) with a status filter, per-relationship\n * sharing scope (workouts / nutrition / metrics), lifecycle transition actions\n * (accept / pause / resume / end) and a create flow. No mock data.\n */\n\nimport { useMemo, useState } from 'react';\nimport { motion, AnimatePresence, useReducedMotion } from 'framer-motion';\nimport {\n HeartHandshake,\n Plus,\n Dumbbell,\n Salad,\n LineChart,\n CalendarClock,\n CalendarX,\n Check,\n Minus,\n MessageSquare,\n ChevronDown,\n ChevronUp,\n User,\n} from 'lucide-react';\nimport {\n Button,\n Input,\n Label,\n Switch,\n GlassCard,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n StatusBadge,\n EmptyState,\n LoadingView,\n ErrorView,\n FilterTabs,\n MybDiscussion,\n UserSearchSelect,\n type FilterOption,\n type UserSelectValue,\n} from '../../components';\nimport { IconChip } from '../_shared/visuals';\nimport { useMyBodyShield } from '../../context';\nimport { useMybCoaching, useMybCreateCoaching, useMybTransitionCoaching } from '../../hooks';\nimport type {\n MybCoachingRelationship,\n MybCoachingStatus,\n MybCreateCoachingInput,\n} from '../../types';\nimport { formatDate } from '../../utils/formatters';\n\ntype StatusFilter = MybCoachingStatus | 'ALL';\n\nconst FILTER_ORDER: MybCoachingStatus[] = ['PENDING', 'ACTIVE', 'PAUSED', 'ENDED'];\n\n/** Shared entrance easing — a soft, decelerating cubic-bezier. */\nconst MOTION_EASE: [number, number, number, number] = [0.16, 1, 0.3, 1];\n\nconst STAGGER_CONTAINER = {\n hidden: {},\n show: { transition: { staggerChildren: 0.06 } },\n};\nconst RISE_ITEM = {\n hidden: { opacity: 0, y: 12 },\n show: { opacity: 1, y: 0, transition: { duration: 0.35, ease: MOTION_EASE } },\n};\n\n/** Allowed lifecycle transitions per current status (contract-defined). */\nconst TRANSITIONS: Record<\n MybCoachingStatus,\n { to: MybCoachingStatus; label: string; primary: boolean }[]\n> = {\n PENDING: [{ to: 'ACTIVE', label: 'Accept', primary: true }],\n ACTIVE: [\n { to: 'PAUSED', label: 'Pause', primary: false },\n { to: 'ENDED', label: 'End', primary: false },\n ],\n PAUSED: [\n { to: 'ACTIVE', label: 'Resume', primary: true },\n { to: 'ENDED', label: 'End', primary: false },\n ],\n ENDED: [],\n};\n\n/** Shorten an opaque actor id for compact display (full id kept as a title). */\nfunction shortId(id: string): string {\n return id.length <= 10 ? id : `${id.slice(0, 8)}…`;\n}\n\n// ============================================================================\n// Create-coaching dialog\n// ============================================================================\n\ninterface CreateCoachingForm {\n memberId: string;\n memberName: string;\n coachId: string;\n coachName: string;\n specialty: string;\n shareWorkouts: boolean;\n shareNutrition: boolean;\n shareMetrics: boolean;\n}\n\nfunction buildEmptyForm(defaultMember: UserSelectValue): CreateCoachingForm {\n return {\n memberId: defaultMember.userId,\n memberName: defaultMember.displayName,\n coachId: '',\n coachName: '',\n specialty: '',\n shareWorkouts: true,\n shareNutrition: true,\n shareMetrics: true,\n };\n}\n\nfunction ShareToggle({\n id,\n label,\n description,\n checked,\n onChange,\n}: {\n id: string;\n label: string;\n description: string;\n checked: boolean;\n onChange: (value: boolean) => void;\n}) {\n return (\n <div className=\"flex items-center justify-between gap-4 rounded-xl bg-bg-sunken/60 p-3\">\n <div className=\"min-w-0\">\n <Label htmlFor={id} className=\"cursor-pointer\">\n {label}\n </Label>\n <p className=\"text-xs text-text-tertiary\">{description}</p>\n </div>\n <Switch id={id} checked={checked} onCheckedChange={onChange} />\n </div>\n );\n}\n\nfunction CreateCoachingDialog({\n open,\n onOpenChange,\n defaultMember,\n}: {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n defaultMember: UserSelectValue;\n}) {\n const create = useMybCreateCoaching();\n const [form, setForm] = useState<CreateCoachingForm>(() => buildEmptyForm(defaultMember));\n const [error, setError] = useState<string | null>(null);\n\n const handleOpenChange = (next: boolean) => {\n if (!next) {\n setForm(buildEmptyForm(defaultMember));\n setError(null);\n }\n onOpenChange(next);\n };\n\n const submit = async () => {\n const coachId = form.coachId.trim();\n const memberId = form.memberId.trim();\n if (!coachId) {\n setError('Coach user id is required.');\n return;\n }\n if (!memberId) {\n setError('Member user id is required.');\n return;\n }\n setError(null);\n const input: MybCreateCoachingInput = {\n coachId,\n memberId,\n specialty: form.specialty.trim() || undefined,\n shareWorkouts: form.shareWorkouts,\n shareNutrition: form.shareNutrition,\n shareMetrics: form.shareMetrics,\n };\n try {\n await create.mutateAsync(input);\n setForm(buildEmptyForm(defaultMember));\n onOpenChange(false);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to create coaching relationship.');\n }\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={handleOpenChange}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>New coaching</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Link a coach and member, and choose exactly what training, nutrition and progress data\n is shared.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <form\n className=\"space-y-4\"\n onSubmit={(event) => {\n event.preventDefault();\n void submit();\n }}\n >\n {error && (\n <p className=\"rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {error}\n </p>\n )}\n <UserSearchSelect\n label=\"Coach\"\n required\n htmlId=\"myb-coaching-coach\"\n userId={form.coachId}\n displayName={form.coachName}\n onSelect={({ userId, displayName }) =>\n setForm((f) => ({ ...f, coachId: userId, coachName: displayName }))\n }\n />\n <UserSearchSelect\n label=\"Member\"\n htmlId=\"myb-coaching-member\"\n userId={form.memberId}\n displayName={form.memberName}\n onSelect={({ userId, displayName }) =>\n setForm((f) => ({ ...f, memberId: userId, memberName: displayName }))\n }\n />\n <div className=\"space-y-2\">\n <Label htmlFor=\"myb-coaching-specialty\">Specialty</Label>\n <Input\n id=\"myb-coaching-specialty\"\n value={form.specialty}\n onChange={(event) => setForm((f) => ({ ...f, specialty: event.target.value }))}\n placeholder=\"e.g., Strength & conditioning\"\n />\n </div>\n <div className=\"space-y-2\">\n <p className=\"text-sm font-medium text-text-primary\">Shared with the coach</p>\n <ShareToggle\n id=\"myb-coaching-share-workouts\"\n label=\"Workouts\"\n description=\"Programs, sessions and logged sets.\"\n checked={form.shareWorkouts}\n onChange={(value) => setForm((f) => ({ ...f, shareWorkouts: value }))}\n />\n <ShareToggle\n id=\"myb-coaching-share-nutrition\"\n label=\"Nutrition\"\n description=\"Plans, meals and daily macros.\"\n checked={form.shareNutrition}\n onChange={(value) => setForm((f) => ({ ...f, shareNutrition: value }))}\n />\n <ShareToggle\n id=\"myb-coaching-share-metrics\"\n label=\"Metrics\"\n description=\"Body metrics and recovery logs.\"\n checked={form.shareMetrics}\n onChange={(value) => setForm((f) => ({ ...f, shareMetrics: value }))}\n />\n </div>\n <ResponsiveDialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => handleOpenChange(false)}\n disabled={create.isPending}\n >\n Cancel\n </Button>\n <Button type=\"submit\" disabled={create.isPending}>\n {create.isPending ? 'Creating…' : 'Create coaching'}\n </Button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\n// ============================================================================\n// Sharing scope chips\n// ============================================================================\n\nfunction ShareChip({ label, icon, on }: { label: string; icon: typeof Dumbbell; on: boolean }) {\n const Icon = icon;\n const StatusIcon = on ? Check : Minus;\n return (\n <span\n aria-label={`${label} ${on ? 'shared' : 'not shared'}`}\n className={`inline-flex items-center gap-1.5 rounded-full py-0.5 pl-1 pr-2.5 text-xs font-medium ${\n on\n ? 'bg-status-success-bg-subtle text-status-success-text ring-1 ring-inset ring-[color-mix(in_oklab,var(--color-status-success-text)_28%,transparent)]'\n : 'bg-bg-sunken text-text-tertiary ring-1 ring-inset ring-border-seam'\n }`}\n >\n <span\n aria-hidden=\"true\"\n className={`inline-flex size-4 items-center justify-center rounded-full ${\n on\n ? 'bg-[color-mix(in_oklab,var(--color-status-success-text)_22%,transparent)]'\n : 'bg-[color-mix(in_oklab,var(--color-text-tertiary)_18%,transparent)]'\n }`}\n >\n <StatusIcon className=\"h-2.5 w-2.5\" />\n </span>\n <Icon className=\"h-3 w-3\" aria-hidden=\"true\" />\n <span>\n {label} · {on ? 'On' : 'Off'}\n </span>\n </span>\n );\n}\n\n// ============================================================================\n// Coaching card\n// ============================================================================\n\nfunction CoachingCard({\n relationship,\n currentUserId,\n busy,\n onTransition,\n}: {\n relationship: MybCoachingRelationship;\n currentUserId?: string;\n busy: boolean;\n onTransition: (id: string, status: MybCoachingStatus) => void;\n}) {\n const coachIsYou = Boolean(currentUserId) && relationship.coachId === currentUserId;\n const memberIsYou = Boolean(currentUserId) && relationship.memberId === currentUserId;\n const actions = TRANSITIONS[relationship.status];\n const canDiscuss = relationship.status === 'ACTIVE' || relationship.status === 'PAUSED';\n const [showDiscussion, setShowDiscussion] = useState(false);\n const reduce = useReducedMotion();\n\n const isActive = relationship.status === 'ACTIVE';\n const isPending = relationship.status === 'PENDING';\n\n return (\n <GlassCard\n treatment=\"glass\"\n glow={isActive}\n className={\n 'flex h-full flex-col gap-4 p-5 transition-shadow hover:shadow-float' +\n (isPending\n ? ' ring-1 ring-inset ring-[color-mix(in_oklab,var(--color-status-warning-text)_35%,transparent)]'\n : '')\n }\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex min-w-0 items-start gap-3\">\n <IconChip icon={<HeartHandshake className=\"h-4 w-4\" />} />\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-semibold text-text-primary\">\n {relationship.specialty?.trim() || 'Coaching relationship'}\n </p>\n <p className=\"text-xs text-text-tertiary\">Coach ↔ member</p>\n </div>\n </div>\n <StatusBadge status={relationship.status} />\n </div>\n\n <div className=\"grid grid-cols-2 gap-3\">\n <div className=\"flex items-center gap-2.5 rounded-lg bg-bg-sunken/60 p-2.5\">\n <span\n aria-hidden=\"true\"\n className={`flex size-8 shrink-0 items-center justify-center rounded-full ring-1 ring-inset ${\n coachIsYou\n ? 'bg-[color-mix(in_oklab,var(--color-action-primary-bg)_16%,transparent)] text-action-primary-bg ring-[color-mix(in_oklab,var(--color-action-primary-bg)_40%,transparent)]'\n : 'bg-bg-surface text-text-tertiary ring-border-seam'\n }`}\n >\n <User className=\"h-4 w-4\" />\n </span>\n <div className=\"min-w-0\">\n <p className=\"text-[11px] font-medium uppercase tracking-wide text-text-tertiary\">\n Coach\n </p>\n <p\n className=\"mt-0.5 truncate text-sm font-medium text-text-primary\"\n title={relationship.coachId}\n >\n {coachIsYou ? 'You' : `#${shortId(relationship.coachId)}`}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2.5 rounded-lg bg-bg-sunken/60 p-2.5\">\n <span\n aria-hidden=\"true\"\n className={`flex size-8 shrink-0 items-center justify-center rounded-full ring-1 ring-inset ${\n memberIsYou\n ? 'bg-[color-mix(in_oklab,var(--color-action-primary-bg)_16%,transparent)] text-action-primary-bg ring-[color-mix(in_oklab,var(--color-action-primary-bg)_40%,transparent)]'\n : 'bg-bg-surface text-text-tertiary ring-border-seam'\n }`}\n >\n <User className=\"h-4 w-4\" />\n </span>\n <div className=\"min-w-0\">\n <p className=\"text-[11px] font-medium uppercase tracking-wide text-text-tertiary\">\n Member\n </p>\n <p\n className=\"mt-0.5 truncate text-sm font-medium text-text-primary\"\n title={relationship.memberId}\n >\n {memberIsYou ? 'You' : `#${shortId(relationship.memberId)}`}\n </p>\n </div>\n </div>\n </div>\n\n <div className=\"flex flex-wrap gap-1.5\">\n <ShareChip label=\"Workouts\" icon={Dumbbell} on={relationship.shareWorkouts} />\n <ShareChip label=\"Nutrition\" icon={Salad} on={relationship.shareNutrition} />\n <ShareChip label=\"Metrics\" icon={LineChart} on={relationship.shareMetrics} />\n </div>\n\n {(relationship.startedAt || relationship.endedAt) && (\n <div className=\"flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-text-tertiary\">\n {relationship.startedAt && (\n <span className=\"inline-flex items-center gap-1\">\n <CalendarClock className=\"h-3.5 w-3.5\" />\n Started {formatDate(relationship.startedAt)}\n </span>\n )}\n {relationship.endedAt && (\n <span className=\"inline-flex items-center gap-1\">\n <CalendarX className=\"h-3.5 w-3.5\" />\n Ended {formatDate(relationship.endedAt)}\n </span>\n )}\n </div>\n )}\n\n {actions.length > 0 && (\n <div className=\"mt-auto flex flex-wrap gap-2 pt-1\">\n {actions.map((action) => (\n <Button\n key={action.to}\n size=\"sm\"\n variant={action.primary ? undefined : 'outline'}\n disabled={busy}\n onClick={() => onTransition(relationship.id, action.to)}\n >\n {action.label}\n </Button>\n ))}\n </div>\n )}\n\n {canDiscuss && (\n <div\n className={`border-t border-border-seam pt-3 ${actions.length === 0 ? 'mt-auto' : ''}`}\n >\n <button\n type=\"button\"\n onClick={() => setShowDiscussion((prev) => !prev)}\n aria-expanded={showDiscussion}\n className=\"inline-flex items-center gap-1.5 text-sm font-medium text-accent hover:underline\"\n >\n <MessageSquare className=\"h-4 w-4\" />\n {showDiscussion ? 'Hide discussion' : 'Discussion'}\n {showDiscussion ? (\n <ChevronUp className=\"h-4 w-4\" />\n ) : (\n <ChevronDown className=\"h-4 w-4\" />\n )}\n </button>\n <AnimatePresence initial={false}>\n {showDiscussion && (\n <motion.div\n key=\"discussion\"\n initial={reduce ? false : { opacity: 0, height: 0 }}\n animate={{ opacity: 1, height: 'auto' }}\n exit={reduce ? { opacity: 0 } : { opacity: 0, height: 0 }}\n transition={reduce ? { duration: 0 } : { duration: 0.25, ease: MOTION_EASE }}\n className=\"overflow-hidden\"\n >\n <div className=\"mt-3\">\n <MybDiscussion\n entityType=\"mybodyshield-coaching\"\n entityId={relationship.id}\n title=\"Coaching discussion\"\n description=\"A private thread between the coach and member.\"\n />\n </div>\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n )}\n </GlassCard>\n );\n}\n\n// ============================================================================\n// Page\n// ============================================================================\n\nexport function CoachingPage() {\n const { currentUser } = useMyBodyShield();\n const reduce = useReducedMotion();\n const [statusFilter, setStatusFilter] = useState<StatusFilter>('ALL');\n const [createOpen, setCreateOpen] = useState(false);\n const [transitioningId, setTransitioningId] = useState<string | null>(null);\n const [actionError, setActionError] = useState<string | null>(null);\n\n // `useMybCoaching` returns a paginated connection — fetch every relationship\n // once and filter client-side so the tab counts stay accurate across filters.\n const coaching = useMybCoaching();\n const transition = useMybTransitionCoaching();\n\n const all = coaching.data?.items ?? [];\n\n const counts = useMemo(() => {\n const map: Record<MybCoachingStatus, number> = {\n PENDING: 0,\n ACTIVE: 0,\n PAUSED: 0,\n ENDED: 0,\n };\n for (const rel of all) map[rel.status] += 1;\n return map;\n }, [all]);\n\n const statusFilters = useMemo<FilterOption<StatusFilter>[]>(\n () => [\n { value: 'ALL', label: 'All', count: all.length },\n ...FILTER_ORDER.map((status) => ({\n value: status as StatusFilter,\n label: status.charAt(0) + status.slice(1).toLowerCase(),\n count: counts[status],\n })),\n ],\n [all.length, counts]\n );\n\n const rows = useMemo(\n () => (statusFilter === 'ALL' ? all : all.filter((rel) => rel.status === statusFilter)),\n [all, statusFilter]\n );\n\n const handleTransition = async (id: string, status: MybCoachingStatus) => {\n setTransitioningId(id);\n setActionError(null);\n try {\n await transition.mutateAsync({ id, status });\n } catch (err) {\n setActionError(err instanceof Error ? err.message : 'Failed to update the relationship.');\n } finally {\n setTransitioningId(null);\n }\n };\n\n const showLoading = coaching.isLoading && !coaching.data;\n\n return (\n <PageLayout\n title=\"Coaching\"\n description=\"Coach ↔ member relationships and what's shared.\"\n actions={\n <Button onClick={() => setCreateOpen(true)}>\n <Plus className=\"mr-2 h-4 w-4\" />\n New coaching\n </Button>\n }\n >\n <div className=\"mb-6\">\n <FilterTabs options={statusFilters} value={statusFilter} onChange={setStatusFilter} />\n </div>\n\n {actionError && (\n <p className=\"mb-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {actionError}\n </p>\n )}\n\n {showLoading ? (\n <LoadingView label=\"Loading coaching relationships…\" />\n ) : coaching.isError ? (\n <ErrorView\n message=\"We couldn't load your coaching relationships.\"\n onRetry={() => void coaching.refetch()}\n />\n ) : rows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<HeartHandshake className=\"h-10 w-10\" />}\n title={\n all.length === 0 ? 'No coaching relationships' : 'No relationships match this filter'\n }\n description={\n all.length === 0\n ? 'Connect a coach and a member to start sharing training, nutrition and progress data.'\n : 'Try a different status filter, or create a new coaching relationship.'\n }\n action={\n <Button onClick={() => setCreateOpen(true)}>\n <Plus className=\"mr-2 h-4 w-4\" />\n New coaching\n </Button>\n }\n />\n ) : (\n <motion.div\n className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\"\n variants={reduce ? undefined : STAGGER_CONTAINER}\n initial={reduce ? false : 'hidden'}\n animate=\"show\"\n >\n {rows.map((relationship) => (\n <motion.div\n key={relationship.id}\n className=\"h-full\"\n variants={reduce ? undefined : RISE_ITEM}\n >\n <CoachingCard\n relationship={relationship}\n currentUserId={currentUser?.id}\n busy={transitioningId === relationship.id}\n onTransition={(id, status) => void handleTransition(id, status)}\n />\n </motion.div>\n ))}\n </motion.div>\n )}\n\n <CreateCoachingDialog\n open={createOpen}\n onOpenChange={setCreateOpen}\n defaultMember={{\n userId: currentUser?.id ?? '',\n displayName: currentUser?.name || currentUser?.email || '',\n }}\n />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -71,7 +71,7 @@ function j() {
|
|
|
71
71
|
damping: 22
|
|
72
72
|
}
|
|
73
73
|
} } : {}
|
|
74
|
-
}, V = Te.data ?? [], H = Ee.data ?? [], U = De.data ?? [], W = L.data ?? [], G = m(() => [...V].sort((e, t) => new Date(t.loggedAt).getTime() - new Date(e.loggedAt).getTime())[0], [V]), K = m(() => [...U].sort((e, t) => new Date(t.loggedAt).getTime() - new Date(e.loggedAt).getTime())[0], [U]), q = m(() => H.length === 0 ? null : H.reduce((e, t) => e + t.sleepHours, 0) / H.length, [H]), J = k === "SLEEP", Y = k === "ALL" ? "All metrics" : d[k].label, X = m(() => [...W].sort((e, t) => new Date(e.loggedAt).getTime() - new Date(t.loggedAt).getTime()).map((e) => ({
|
|
74
|
+
}, V = Te.data?.items ?? [], H = Ee.data?.items ?? [], U = De.data?.items ?? [], W = L.data?.items ?? [], G = m(() => [...V].sort((e, t) => new Date(t.loggedAt).getTime() - new Date(e.loggedAt).getTime())[0], [V]), K = m(() => [...U].sort((e, t) => new Date(t.loggedAt).getTime() - new Date(e.loggedAt).getTime())[0], [U]), q = m(() => H.length === 0 ? null : H.reduce((e, t) => e + t.sleepHours, 0) / H.length, [H]), J = k === "SLEEP", Y = k === "ALL" ? "All metrics" : d[k].label, X = m(() => [...W].sort((e, t) => new Date(e.loggedAt).getTime() - new Date(t.loggedAt).getTime()).map((e) => ({
|
|
75
75
|
label: n(e.loggedAt),
|
|
76
76
|
value: J ? e.sleepHours : e.score
|
|
77
77
|
})), [W, J]), Z = X.length > 0 ? X[X.length - 1] : void 0, Q = (e = "READINESS") => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RecoveryPage.js","names":[],"sources":["../../../../src/mybodyshield/pages/recovery/RecoveryPage.tsx"],"sourcesContent":["/**\n * Recovery — sleep, readiness, soreness, stress & mood tracking.\n * @module mybodyshield/pages/recovery\n *\n * Reads the real `mybListRecoveryLogs` array endpoint (no pagination). KPIs pull\n * the readiness / sleep / mood metrics independently so they stay stable while\n * the FilterTabs drive the charted + tabulated metric. New entries go through\n * `mybLogRecovery`. No mock data — everything is derived from the hooks.\n */\n\nimport { useMemo, useState } from 'react';\nimport { Plus, HeartPulse } from 'lucide-react';\nimport {\n ResponsiveContainer,\n AreaChart,\n Area,\n XAxis,\n YAxis,\n CartesianGrid,\n Tooltip,\n ReferenceDot,\n} from 'recharts';\nimport { motion, useReducedMotion, type MotionProps } from 'framer-motion';\nimport {\n Button,\n Input,\n Label,\n Textarea,\n Select,\n SelectTrigger,\n SelectContent,\n SelectItem,\n SelectValue,\n GlassCard,\n Spinner,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n MetricsCard,\n FilterTabs,\n DataTable,\n LoadingView,\n ErrorView,\n Section,\n EmptyState,\n type Column,\n type FilterOption,\n} from '../../components';\nimport { useMybRecoveryLogs, useMybLogRecovery } from '../../hooks';\nimport type { MybRecoveryMetric, MybRecoveryLog, MybLogRecoveryInput } from '../../types';\nimport { formatNumber, formatDate, formatRelativeTime } from '../../utils/formatters';\nimport { RECOVERY_META, IconChip } from '../_shared/visuals';\n\ntype RecoveryFilter = 'ALL' | MybRecoveryMetric;\n\ntype Accent = 'success' | 'warning' | 'error';\n\nconst RECOVERY_METRICS: MybRecoveryMetric[] = ['SLEEP', 'READINESS', 'SORENESS', 'STRESS', 'MOOD'];\n\nconst FILTER_OPTIONS: FilterOption<RecoveryFilter>[] = [\n { value: 'ALL', label: 'All' },\n ...RECOVERY_METRICS.map((m) => ({ value: m, label: RECOVERY_META[m].label })),\n];\n\n// Brand emerald accent — dominant color for the recovery area chart & endpoint.\nconst CHART_ACCENT = 'var(--color-action-primary-bg)';\n\nconst scoreAccent = (score: number): Accent =>\n score >= 70 ? 'success' : score >= 40 ? 'warning' : 'error';\n\nconst sleepAccent = (hours: number): Accent =>\n hours >= 7 ? 'success' : hours >= 6 ? 'warning' : 'error';\n\n/** Format a Date into a `datetime-local` input value (local time, minute precision). */\nfunction toDatetimeLocalValue(date: Date): string {\n const pad = (n: number): string => String(n).padStart(2, '0');\n return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(\n date.getHours()\n )}:${pad(date.getMinutes())}`;\n}\n\ninterface RecoveryForm {\n metric: MybRecoveryMetric;\n score: string;\n sleepHours: string;\n notes: string;\n loggedAt: string;\n}\n\nconst makeEmptyForm = (metric: MybRecoveryMetric = 'READINESS'): RecoveryForm => ({\n metric,\n score: '',\n sleepHours: '',\n notes: '',\n loggedAt: toDatetimeLocalValue(new Date()),\n});\n\nexport function RecoveryPage() {\n const [filter, setFilter] = useState<RecoveryFilter>('ALL');\n const [open, setOpen] = useState(false);\n const [form, setForm] = useState<RecoveryForm>(makeEmptyForm);\n const [err, setErr] = useState<string | null>(null);\n\n // KPI feeds — independent of the active filter so the header stays stable.\n const readiness = useMybRecoveryLogs('READINESS');\n const sleep = useMybRecoveryLogs('SLEEP');\n const mood = useMybRecoveryLogs('MOOD');\n\n // Charted + tabulated metric (undefined → all metrics).\n const selectedMetric = filter === 'ALL' ? undefined : filter;\n const logs = useMybRecoveryLogs(selectedMetric);\n\n const logRecovery = useMybLogRecovery();\n\n const reduced = useReducedMotion();\n const rise = (delay: number, hover = true): MotionProps =>\n reduced\n ? { initial: false }\n : {\n initial: { opacity: 0, y: 14 },\n animate: { opacity: 1, y: 0 },\n transition: { duration: 0.45, delay, ease: 'easeOut' },\n ...(hover\n ? {\n whileHover: {\n y: -3,\n transition: { type: 'spring', stiffness: 300, damping: 22 },\n },\n }\n : {}),\n };\n\n const readinessRows = readiness.data ?? [];\n const sleepRows = sleep.data ?? [];\n const moodRows = mood.data ?? [];\n const logRows = logs.data ?? [];\n\n const latestReadiness = useMemo(\n () =>\n [...readinessRows].sort(\n (a, b) => new Date(b.loggedAt).getTime() - new Date(a.loggedAt).getTime()\n )[0],\n [readinessRows]\n );\n\n const latestMood = useMemo(\n () =>\n [...moodRows].sort(\n (a, b) => new Date(b.loggedAt).getTime() - new Date(a.loggedAt).getTime()\n )[0],\n [moodRows]\n );\n\n const avgSleep = useMemo(() => {\n if (sleepRows.length === 0) return null;\n const sum = sleepRows.reduce((acc, l) => acc + l.sleepHours, 0);\n return sum / sleepRows.length;\n }, [sleepRows]);\n\n const isSleepSelected = filter === 'SLEEP';\n const selectedLabel = filter === 'ALL' ? 'All metrics' : RECOVERY_META[filter].label;\n\n const chartData = useMemo(\n () =>\n [...logRows]\n .sort((a, b) => new Date(a.loggedAt).getTime() - new Date(b.loggedAt).getTime())\n .map((l) => ({\n label: formatDate(l.loggedAt),\n value: isSleepSelected ? l.sleepHours : l.score,\n })),\n [logRows, isSleepSelected]\n );\n const lastPoint = chartData.length > 0 ? chartData[chartData.length - 1] : undefined;\n\n const openLog = (metric: MybRecoveryMetric = 'READINESS') => {\n setForm(makeEmptyForm(metric));\n setErr(null);\n setOpen(true);\n };\n\n const isSleepForm = form.metric === 'SLEEP';\n\n const submit = async () => {\n setErr(null);\n if (isSleepForm) {\n if (form.sleepHours.trim() === '') {\n setErr('Sleep hours is required for sleep entries.');\n return;\n }\n const h = Number(form.sleepHours);\n if (!Number.isFinite(h) || h < 0 || h > 24) {\n setErr('Enter sleep hours between 0 and 24.');\n return;\n }\n } else {\n if (form.score.trim() === '') {\n setErr('A score is required.');\n return;\n }\n }\n if (form.score.trim() !== '') {\n const s = Number(form.score);\n if (!Number.isFinite(s) || s < 0 || s > 100) {\n setErr('Enter a score between 0 and 100.');\n return;\n }\n }\n\n const input: MybLogRecoveryInput = {\n metric: form.metric,\n score: form.score.trim() !== '' ? Number(form.score) : undefined,\n sleepHours: form.sleepHours.trim() !== '' ? Number(form.sleepHours) : undefined,\n notes: form.notes.trim() || undefined,\n loggedAt: form.loggedAt ? new Date(form.loggedAt).toISOString() : undefined,\n };\n\n try {\n await logRecovery.mutateAsync(input);\n setOpen(false);\n } catch (e) {\n setErr(e instanceof Error ? e.message : 'Failed to log recovery.');\n }\n };\n\n const columns: Column<MybRecoveryLog>[] = [\n {\n key: 'metric',\n header: 'Metric',\n sortValue: (r) => RECOVERY_META[r.metric].label,\n render: (r) => {\n const meta = RECOVERY_META[r.metric];\n const Icon = meta.icon;\n return (\n <span className=\"inline-flex items-center gap-2.5\">\n <IconChip icon={<Icon className=\"h-4 w-4\" />} tint={meta.tint} />\n <span className=\"font-medium text-text-primary\">{meta.label}</span>\n </span>\n );\n },\n },\n {\n key: 'value',\n header: 'Value',\n align: 'right',\n sortValue: (r) => (r.metric === 'SLEEP' ? r.sleepHours : r.score),\n render: (r) =>\n r.metric === 'SLEEP' ? (\n <span className=\"tabular-nums text-text-primary\">\n {formatNumber(r.sleepHours, { maximumFractionDigits: 1 })} h\n </span>\n ) : (\n <span className=\"tabular-nums text-text-primary\">{formatNumber(r.score)}</span>\n ),\n },\n {\n key: 'notes',\n header: 'Notes',\n hideOnMobile: true,\n render: (r) =>\n r.notes && r.notes.trim() ? (\n <span className=\"text-text-secondary\">{r.notes}</span>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n {\n key: 'loggedAt',\n header: 'Logged',\n align: 'right',\n sortValue: (r) => new Date(r.loggedAt).getTime(),\n render: (r) => (\n <span className=\"whitespace-nowrap text-text-tertiary\">\n {formatRelativeTime(r.loggedAt)}\n </span>\n ),\n },\n ];\n\n const ReadinessIcon = RECOVERY_META.READINESS.icon;\n const SleepIcon = RECOVERY_META.SLEEP.icon;\n const MoodIcon = RECOVERY_META.MOOD.icon;\n\n return (\n <PageLayout\n title=\"Recovery\"\n description=\"Sleep, readiness, soreness, stress & mood.\"\n actions={\n <Button className=\"gap-2\" onClick={() => openLog()}>\n <Plus className=\"h-4 w-4\" />\n Log recovery\n </Button>\n }\n >\n {/* KPI grid */}\n <motion.div {...rise(0)} className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <MetricsCard\n icon={\n <IconChip\n icon={<ReadinessIcon className=\"h-5 w-5\" />}\n tint={RECOVERY_META.READINESS.tint}\n />\n }\n value={latestReadiness ? formatNumber(latestReadiness.score) : '—'}\n label=\"Latest readiness\"\n accent={latestReadiness ? scoreAccent(latestReadiness.score) : undefined}\n footer={\n latestReadiness ? (\n <p className=\"text-xs text-text-tertiary\">\n {formatRelativeTime(latestReadiness.loggedAt)}\n </p>\n ) : (\n <button\n type=\"button\"\n onClick={() => openLog('READINESS')}\n className=\"text-xs font-medium text-accent hover:underline\"\n >\n Log readiness →\n </button>\n )\n }\n />\n <MetricsCard\n icon={\n <IconChip icon={<SleepIcon className=\"h-5 w-5\" />} tint={RECOVERY_META.SLEEP.tint} />\n }\n value={\n avgSleep != null ? `${formatNumber(avgSleep, { maximumFractionDigits: 1 })} h` : '—'\n }\n label=\"Avg sleep\"\n accent={avgSleep != null ? sleepAccent(avgSleep) : undefined}\n footer={\n sleepRows.length > 0 ? (\n <p className=\"text-xs text-text-tertiary\">\n <span className=\"tabular-nums text-text-secondary\">{sleepRows.length}</span>{' '}\n {sleepRows.length === 1 ? 'night' : 'nights'} logged\n </p>\n ) : (\n <button\n type=\"button\"\n onClick={() => openLog('SLEEP')}\n className=\"text-xs font-medium text-accent hover:underline\"\n >\n Log sleep →\n </button>\n )\n }\n />\n <MetricsCard\n icon={<IconChip icon={<MoodIcon className=\"h-5 w-5\" />} tint={RECOVERY_META.MOOD.tint} />}\n value={latestMood ? formatNumber(latestMood.score) : '—'}\n label=\"Latest mood\"\n accent={latestMood ? scoreAccent(latestMood.score) : undefined}\n footer={\n latestMood ? (\n <p className=\"text-xs text-text-tertiary\">\n {formatRelativeTime(latestMood.loggedAt)}\n </p>\n ) : (\n <button\n type=\"button\"\n onClick={() => openLog('MOOD')}\n className=\"text-xs font-medium text-accent hover:underline\"\n >\n Log mood →\n </button>\n )\n }\n />\n </motion.div>\n\n {/* Metric filter */}\n <div className=\"mt-6 flex flex-wrap items-center gap-3\">\n <FilterTabs options={FILTER_OPTIONS} value={filter} onChange={setFilter} />\n </div>\n\n {/* Trend chart */}\n <Section\n title={`${selectedLabel} trend`}\n description={isSleepSelected ? 'Sleep hours over time.' : 'Recovery score over time.'}\n >\n <motion.div {...rise(0.08)}>\n <GlassCard\n treatment=\"glass\"\n className=\"rounded-2xl p-6 transition-shadow duration-300 hover:shadow-2xl\"\n >\n {logs.isLoading ? (\n <div className=\"flex h-[280px] items-center justify-center\">\n <Spinner size=\"lg\" />\n </div>\n ) : logs.isError ? (\n <ErrorView\n message=\"We couldn't load this metric's history.\"\n onRetry={() => logs.refetch()}\n />\n ) : chartData.length === 0 ? (\n <div className=\"flex h-[280px] flex-col items-center justify-center text-center\">\n <span className=\"flex size-11 items-center justify-center rounded-full bg-[var(--color-accent-soft)]\">\n <HeartPulse className=\"h-5 w-5 text-accent\" />\n </span>\n <p className=\"mt-3 text-sm font-medium text-text-primary\">Nothing to chart yet</p>\n <p className=\"mt-1 max-w-xs text-xs text-text-tertiary\">\n Log a few {selectedLabel.toLowerCase()} entries to see your trend here.\n </p>\n </div>\n ) : (\n <ResponsiveContainer width=\"100%\" height={280}>\n <AreaChart data={chartData} margin={{ top: 12, right: 16, bottom: 4, left: 4 }}>\n <defs>\n <linearGradient id=\"myb-recovery-fill\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" stopColor={CHART_ACCENT} stopOpacity={0.38} />\n <stop offset=\"55%\" stopColor={CHART_ACCENT} stopOpacity={0.12} />\n <stop offset=\"100%\" stopColor={CHART_ACCENT} stopOpacity={0} />\n </linearGradient>\n </defs>\n <CartesianGrid\n vertical={false}\n stroke=\"var(--color-border-seam)\"\n strokeDasharray=\"4 4\"\n />\n <XAxis\n dataKey=\"label\"\n tickLine={false}\n axisLine={false}\n tickMargin={8}\n minTickGap={20}\n tick={{ fill: 'var(--color-text-tertiary)', fontSize: 12 }}\n />\n <YAxis\n width={44}\n tickLine={false}\n axisLine={false}\n tick={{ fill: 'var(--color-text-tertiary)', fontSize: 12 }}\n />\n <Tooltip\n contentStyle={{\n background: 'var(--color-bg-surface)',\n border: '1px solid var(--color-border-seam)',\n borderRadius: 12,\n color: 'var(--color-text-primary)',\n fontSize: 12,\n }}\n labelStyle={{ color: 'var(--color-text-tertiary)' }}\n cursor={{ stroke: 'var(--color-border-seam)', strokeDasharray: '4 4' }}\n />\n <Area\n type=\"monotone\"\n dataKey=\"value\"\n name={isSleepSelected ? 'Sleep hours' : 'Score'}\n stroke={CHART_ACCENT}\n strokeWidth={2.5}\n fill=\"url(#myb-recovery-fill)\"\n fillOpacity={1}\n dot={false}\n activeDot={{\n r: 4,\n strokeWidth: 2,\n stroke: 'var(--color-bg-surface)',\n fill: CHART_ACCENT,\n }}\n isAnimationActive={!reduced}\n animationDuration={900}\n />\n {lastPoint && (\n <ReferenceDot\n x={lastPoint.label}\n y={lastPoint.value}\n r={12}\n isFront\n ifOverflow=\"visible\"\n fill={CHART_ACCENT}\n fillOpacity={0.16}\n stroke=\"none\"\n />\n )}\n {lastPoint && (\n <ReferenceDot\n x={lastPoint.label}\n y={lastPoint.value}\n r={5}\n isFront\n ifOverflow=\"visible\"\n fill={CHART_ACCENT}\n stroke=\"var(--color-bg-surface)\"\n strokeWidth={2}\n />\n )}\n </AreaChart>\n </ResponsiveContainer>\n )}\n </GlassCard>\n </motion.div>\n </Section>\n\n {/* Log table */}\n <motion.div {...rise(0.16, false)}>\n <Section title=\"Recovery log\" description=\"Every recovery entry, most recent first.\">\n {logs.isLoading ? (\n <LoadingView label=\"Loading recovery logs…\" />\n ) : logs.isError ? (\n <ErrorView\n message=\"We couldn't load your recovery logs.\"\n onRetry={() => logs.refetch()}\n />\n ) : logRows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<HeartPulse className=\"h-10 w-10\" />}\n title=\"No recovery logs yet\"\n description=\"Track your sleep, readiness, soreness, stress or mood to see how your body is recovering between sessions.\"\n action={\n <Button className=\"gap-2\" onClick={() => openLog()}>\n <Plus className=\"h-4 w-4\" />\n Log recovery\n </Button>\n }\n />\n ) : (\n <DataTable\n columns={columns}\n rows={logRows}\n rowKey={(r) => r.id}\n defaultSort={{ key: 'loggedAt', dir: 'desc' }}\n />\n )}\n </Section>\n </motion.div>\n\n {/* Log recovery dialog */}\n <ResponsiveDialog open={open} onOpenChange={setOpen}>\n <ResponsiveDialogContent className=\"sm:max-w-[480px]\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Log recovery</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Capture how your body is recovering — one metric per entry.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"space-y-4 py-2\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-recovery-metric\">Metric</Label>\n <Select\n value={form.metric}\n onValueChange={(v) =>\n // Select only emits values from RECOVERY_METRICS, so `v` is a\n // MybRecoveryMetric — narrow the Radix `string` payload.\n setForm((f) => ({ ...f, metric: v as MybRecoveryMetric }))\n }\n >\n <SelectTrigger id=\"myb-recovery-metric\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {RECOVERY_METRICS.map((m) => (\n <SelectItem key={m} value={m}>\n {RECOVERY_META[m].label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {isSleepForm && (\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-recovery-sleep\">Sleep hours</Label>\n <Input\n id=\"myb-recovery-sleep\"\n type=\"number\"\n min=\"0\"\n max=\"24\"\n step=\"0.1\"\n value={form.sleepHours}\n onChange={(e) => setForm((f) => ({ ...f, sleepHours: e.target.value }))}\n placeholder=\"e.g. 7.5\"\n />\n <p className=\"text-xs text-text-tertiary\">Primary measure for sleep entries.</p>\n </div>\n )}\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-recovery-score\">Score{isSleepForm ? ' (optional)' : ''}</Label>\n <Input\n id=\"myb-recovery-score\"\n type=\"number\"\n min=\"0\"\n max=\"100\"\n step=\"1\"\n value={form.score}\n onChange={(e) => setForm((f) => ({ ...f, score: e.target.value }))}\n placeholder=\"0–100\"\n />\n <p className=\"text-xs text-text-tertiary\">\n {isSleepForm\n ? 'Optional 0–100 sleep-quality score.'\n : 'A 0–100 rating for this metric.'}\n </p>\n </div>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-recovery-at\">Logged at</Label>\n <Input\n id=\"myb-recovery-at\"\n type=\"datetime-local\"\n value={form.loggedAt}\n onChange={(e) => setForm((f) => ({ ...f, loggedAt: e.target.value }))}\n />\n </div>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-recovery-notes\">Notes</Label>\n <Textarea\n id=\"myb-recovery-notes\"\n rows={3}\n value={form.notes}\n onChange={(e) => setForm((f) => ({ ...f, notes: e.target.value }))}\n placeholder=\"How are you feeling? (optional)\"\n />\n </div>\n\n {err && <p className=\"text-sm text-status-error-text\">{err}</p>}\n </div>\n\n <ResponsiveDialogFooter>\n <Button\n variant=\"outline\"\n onClick={() => setOpen(false)}\n disabled={logRecovery.isPending}\n >\n Cancel\n </Button>\n <Button onClick={submit} disabled={logRecovery.isPending}>\n {logRecovery.isPending ? 'Saving…' : 'Log recovery'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"RecoveryPage.js","names":[],"sources":["../../../../src/mybodyshield/pages/recovery/RecoveryPage.tsx"],"sourcesContent":["/**\n * Recovery — sleep, readiness, soreness, stress & mood tracking.\n * @module mybodyshield/pages/recovery\n *\n * Reads the real `mybListRecoveryLogs` array endpoint (no pagination). KPIs pull\n * the readiness / sleep / mood metrics independently so they stay stable while\n * the FilterTabs drive the charted + tabulated metric. New entries go through\n * `mybLogRecovery`. No mock data — everything is derived from the hooks.\n */\n\nimport { useMemo, useState } from 'react';\nimport { Plus, HeartPulse } from 'lucide-react';\nimport {\n ResponsiveContainer,\n AreaChart,\n Area,\n XAxis,\n YAxis,\n CartesianGrid,\n Tooltip,\n ReferenceDot,\n} from 'recharts';\nimport { motion, useReducedMotion, type MotionProps } from 'framer-motion';\nimport {\n Button,\n Input,\n Label,\n Textarea,\n Select,\n SelectTrigger,\n SelectContent,\n SelectItem,\n SelectValue,\n GlassCard,\n Spinner,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n MetricsCard,\n FilterTabs,\n DataTable,\n LoadingView,\n ErrorView,\n Section,\n EmptyState,\n type Column,\n type FilterOption,\n} from '../../components';\nimport { useMybRecoveryLogs, useMybLogRecovery } from '../../hooks';\nimport type { MybRecoveryMetric, MybRecoveryLog, MybLogRecoveryInput } from '../../types';\nimport { formatNumber, formatDate, formatRelativeTime } from '../../utils/formatters';\nimport { RECOVERY_META, IconChip } from '../_shared/visuals';\n\ntype RecoveryFilter = 'ALL' | MybRecoveryMetric;\n\ntype Accent = 'success' | 'warning' | 'error';\n\nconst RECOVERY_METRICS: MybRecoveryMetric[] = ['SLEEP', 'READINESS', 'SORENESS', 'STRESS', 'MOOD'];\n\nconst FILTER_OPTIONS: FilterOption<RecoveryFilter>[] = [\n { value: 'ALL', label: 'All' },\n ...RECOVERY_METRICS.map((m) => ({ value: m, label: RECOVERY_META[m].label })),\n];\n\n// Brand emerald accent — dominant color for the recovery area chart & endpoint.\nconst CHART_ACCENT = 'var(--color-action-primary-bg)';\n\nconst scoreAccent = (score: number): Accent =>\n score >= 70 ? 'success' : score >= 40 ? 'warning' : 'error';\n\nconst sleepAccent = (hours: number): Accent =>\n hours >= 7 ? 'success' : hours >= 6 ? 'warning' : 'error';\n\n/** Format a Date into a `datetime-local` input value (local time, minute precision). */\nfunction toDatetimeLocalValue(date: Date): string {\n const pad = (n: number): string => String(n).padStart(2, '0');\n return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(\n date.getHours()\n )}:${pad(date.getMinutes())}`;\n}\n\ninterface RecoveryForm {\n metric: MybRecoveryMetric;\n score: string;\n sleepHours: string;\n notes: string;\n loggedAt: string;\n}\n\nconst makeEmptyForm = (metric: MybRecoveryMetric = 'READINESS'): RecoveryForm => ({\n metric,\n score: '',\n sleepHours: '',\n notes: '',\n loggedAt: toDatetimeLocalValue(new Date()),\n});\n\nexport function RecoveryPage() {\n const [filter, setFilter] = useState<RecoveryFilter>('ALL');\n const [open, setOpen] = useState(false);\n const [form, setForm] = useState<RecoveryForm>(makeEmptyForm);\n const [err, setErr] = useState<string | null>(null);\n\n // KPI feeds — independent of the active filter so the header stays stable.\n const readiness = useMybRecoveryLogs('READINESS');\n const sleep = useMybRecoveryLogs('SLEEP');\n const mood = useMybRecoveryLogs('MOOD');\n\n // Charted + tabulated metric (undefined → all metrics).\n const selectedMetric = filter === 'ALL' ? undefined : filter;\n const logs = useMybRecoveryLogs(selectedMetric);\n\n const logRecovery = useMybLogRecovery();\n\n const reduced = useReducedMotion();\n const rise = (delay: number, hover = true): MotionProps =>\n reduced\n ? { initial: false }\n : {\n initial: { opacity: 0, y: 14 },\n animate: { opacity: 1, y: 0 },\n transition: { duration: 0.45, delay, ease: 'easeOut' },\n ...(hover\n ? {\n whileHover: {\n y: -3,\n transition: { type: 'spring', stiffness: 300, damping: 22 },\n },\n }\n : {}),\n };\n\n const readinessRows = readiness.data?.items ?? [];\n const sleepRows = sleep.data?.items ?? [];\n const moodRows = mood.data?.items ?? [];\n const logRows = logs.data?.items ?? [];\n\n const latestReadiness = useMemo(\n () =>\n [...readinessRows].sort(\n (a, b) => new Date(b.loggedAt).getTime() - new Date(a.loggedAt).getTime()\n )[0],\n [readinessRows]\n );\n\n const latestMood = useMemo(\n () =>\n [...moodRows].sort(\n (a, b) => new Date(b.loggedAt).getTime() - new Date(a.loggedAt).getTime()\n )[0],\n [moodRows]\n );\n\n const avgSleep = useMemo(() => {\n if (sleepRows.length === 0) return null;\n const sum = sleepRows.reduce((acc, l) => acc + l.sleepHours, 0);\n return sum / sleepRows.length;\n }, [sleepRows]);\n\n const isSleepSelected = filter === 'SLEEP';\n const selectedLabel = filter === 'ALL' ? 'All metrics' : RECOVERY_META[filter].label;\n\n const chartData = useMemo(\n () =>\n [...logRows]\n .sort((a, b) => new Date(a.loggedAt).getTime() - new Date(b.loggedAt).getTime())\n .map((l) => ({\n label: formatDate(l.loggedAt),\n value: isSleepSelected ? l.sleepHours : l.score,\n })),\n [logRows, isSleepSelected]\n );\n const lastPoint = chartData.length > 0 ? chartData[chartData.length - 1] : undefined;\n\n const openLog = (metric: MybRecoveryMetric = 'READINESS') => {\n setForm(makeEmptyForm(metric));\n setErr(null);\n setOpen(true);\n };\n\n const isSleepForm = form.metric === 'SLEEP';\n\n const submit = async () => {\n setErr(null);\n if (isSleepForm) {\n if (form.sleepHours.trim() === '') {\n setErr('Sleep hours is required for sleep entries.');\n return;\n }\n const h = Number(form.sleepHours);\n if (!Number.isFinite(h) || h < 0 || h > 24) {\n setErr('Enter sleep hours between 0 and 24.');\n return;\n }\n } else {\n if (form.score.trim() === '') {\n setErr('A score is required.');\n return;\n }\n }\n if (form.score.trim() !== '') {\n const s = Number(form.score);\n if (!Number.isFinite(s) || s < 0 || s > 100) {\n setErr('Enter a score between 0 and 100.');\n return;\n }\n }\n\n const input: MybLogRecoveryInput = {\n metric: form.metric,\n score: form.score.trim() !== '' ? Number(form.score) : undefined,\n sleepHours: form.sleepHours.trim() !== '' ? Number(form.sleepHours) : undefined,\n notes: form.notes.trim() || undefined,\n loggedAt: form.loggedAt ? new Date(form.loggedAt).toISOString() : undefined,\n };\n\n try {\n await logRecovery.mutateAsync(input);\n setOpen(false);\n } catch (e) {\n setErr(e instanceof Error ? e.message : 'Failed to log recovery.');\n }\n };\n\n const columns: Column<MybRecoveryLog>[] = [\n {\n key: 'metric',\n header: 'Metric',\n sortValue: (r) => RECOVERY_META[r.metric].label,\n render: (r) => {\n const meta = RECOVERY_META[r.metric];\n const Icon = meta.icon;\n return (\n <span className=\"inline-flex items-center gap-2.5\">\n <IconChip icon={<Icon className=\"h-4 w-4\" />} tint={meta.tint} />\n <span className=\"font-medium text-text-primary\">{meta.label}</span>\n </span>\n );\n },\n },\n {\n key: 'value',\n header: 'Value',\n align: 'right',\n sortValue: (r) => (r.metric === 'SLEEP' ? r.sleepHours : r.score),\n render: (r) =>\n r.metric === 'SLEEP' ? (\n <span className=\"tabular-nums text-text-primary\">\n {formatNumber(r.sleepHours, { maximumFractionDigits: 1 })} h\n </span>\n ) : (\n <span className=\"tabular-nums text-text-primary\">{formatNumber(r.score)}</span>\n ),\n },\n {\n key: 'notes',\n header: 'Notes',\n hideOnMobile: true,\n render: (r) =>\n r.notes && r.notes.trim() ? (\n <span className=\"text-text-secondary\">{r.notes}</span>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n {\n key: 'loggedAt',\n header: 'Logged',\n align: 'right',\n sortValue: (r) => new Date(r.loggedAt).getTime(),\n render: (r) => (\n <span className=\"whitespace-nowrap text-text-tertiary\">\n {formatRelativeTime(r.loggedAt)}\n </span>\n ),\n },\n ];\n\n const ReadinessIcon = RECOVERY_META.READINESS.icon;\n const SleepIcon = RECOVERY_META.SLEEP.icon;\n const MoodIcon = RECOVERY_META.MOOD.icon;\n\n return (\n <PageLayout\n title=\"Recovery\"\n description=\"Sleep, readiness, soreness, stress & mood.\"\n actions={\n <Button className=\"gap-2\" onClick={() => openLog()}>\n <Plus className=\"h-4 w-4\" />\n Log recovery\n </Button>\n }\n >\n {/* KPI grid */}\n <motion.div {...rise(0)} className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <MetricsCard\n icon={\n <IconChip\n icon={<ReadinessIcon className=\"h-5 w-5\" />}\n tint={RECOVERY_META.READINESS.tint}\n />\n }\n value={latestReadiness ? formatNumber(latestReadiness.score) : '—'}\n label=\"Latest readiness\"\n accent={latestReadiness ? scoreAccent(latestReadiness.score) : undefined}\n footer={\n latestReadiness ? (\n <p className=\"text-xs text-text-tertiary\">\n {formatRelativeTime(latestReadiness.loggedAt)}\n </p>\n ) : (\n <button\n type=\"button\"\n onClick={() => openLog('READINESS')}\n className=\"text-xs font-medium text-accent hover:underline\"\n >\n Log readiness →\n </button>\n )\n }\n />\n <MetricsCard\n icon={\n <IconChip icon={<SleepIcon className=\"h-5 w-5\" />} tint={RECOVERY_META.SLEEP.tint} />\n }\n value={\n avgSleep != null ? `${formatNumber(avgSleep, { maximumFractionDigits: 1 })} h` : '—'\n }\n label=\"Avg sleep\"\n accent={avgSleep != null ? sleepAccent(avgSleep) : undefined}\n footer={\n sleepRows.length > 0 ? (\n <p className=\"text-xs text-text-tertiary\">\n <span className=\"tabular-nums text-text-secondary\">{sleepRows.length}</span>{' '}\n {sleepRows.length === 1 ? 'night' : 'nights'} logged\n </p>\n ) : (\n <button\n type=\"button\"\n onClick={() => openLog('SLEEP')}\n className=\"text-xs font-medium text-accent hover:underline\"\n >\n Log sleep →\n </button>\n )\n }\n />\n <MetricsCard\n icon={<IconChip icon={<MoodIcon className=\"h-5 w-5\" />} tint={RECOVERY_META.MOOD.tint} />}\n value={latestMood ? formatNumber(latestMood.score) : '—'}\n label=\"Latest mood\"\n accent={latestMood ? scoreAccent(latestMood.score) : undefined}\n footer={\n latestMood ? (\n <p className=\"text-xs text-text-tertiary\">\n {formatRelativeTime(latestMood.loggedAt)}\n </p>\n ) : (\n <button\n type=\"button\"\n onClick={() => openLog('MOOD')}\n className=\"text-xs font-medium text-accent hover:underline\"\n >\n Log mood →\n </button>\n )\n }\n />\n </motion.div>\n\n {/* Metric filter */}\n <div className=\"mt-6 flex flex-wrap items-center gap-3\">\n <FilterTabs options={FILTER_OPTIONS} value={filter} onChange={setFilter} />\n </div>\n\n {/* Trend chart */}\n <Section\n title={`${selectedLabel} trend`}\n description={isSleepSelected ? 'Sleep hours over time.' : 'Recovery score over time.'}\n >\n <motion.div {...rise(0.08)}>\n <GlassCard\n treatment=\"glass\"\n className=\"rounded-2xl p-6 transition-shadow duration-300 hover:shadow-2xl\"\n >\n {logs.isLoading ? (\n <div className=\"flex h-[280px] items-center justify-center\">\n <Spinner size=\"lg\" />\n </div>\n ) : logs.isError ? (\n <ErrorView\n message=\"We couldn't load this metric's history.\"\n onRetry={() => logs.refetch()}\n />\n ) : chartData.length === 0 ? (\n <div className=\"flex h-[280px] flex-col items-center justify-center text-center\">\n <span className=\"flex size-11 items-center justify-center rounded-full bg-[var(--color-accent-soft)]\">\n <HeartPulse className=\"h-5 w-5 text-accent\" />\n </span>\n <p className=\"mt-3 text-sm font-medium text-text-primary\">Nothing to chart yet</p>\n <p className=\"mt-1 max-w-xs text-xs text-text-tertiary\">\n Log a few {selectedLabel.toLowerCase()} entries to see your trend here.\n </p>\n </div>\n ) : (\n <ResponsiveContainer width=\"100%\" height={280}>\n <AreaChart data={chartData} margin={{ top: 12, right: 16, bottom: 4, left: 4 }}>\n <defs>\n <linearGradient id=\"myb-recovery-fill\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" stopColor={CHART_ACCENT} stopOpacity={0.38} />\n <stop offset=\"55%\" stopColor={CHART_ACCENT} stopOpacity={0.12} />\n <stop offset=\"100%\" stopColor={CHART_ACCENT} stopOpacity={0} />\n </linearGradient>\n </defs>\n <CartesianGrid\n vertical={false}\n stroke=\"var(--color-border-seam)\"\n strokeDasharray=\"4 4\"\n />\n <XAxis\n dataKey=\"label\"\n tickLine={false}\n axisLine={false}\n tickMargin={8}\n minTickGap={20}\n tick={{ fill: 'var(--color-text-tertiary)', fontSize: 12 }}\n />\n <YAxis\n width={44}\n tickLine={false}\n axisLine={false}\n tick={{ fill: 'var(--color-text-tertiary)', fontSize: 12 }}\n />\n <Tooltip\n contentStyle={{\n background: 'var(--color-bg-surface)',\n border: '1px solid var(--color-border-seam)',\n borderRadius: 12,\n color: 'var(--color-text-primary)',\n fontSize: 12,\n }}\n labelStyle={{ color: 'var(--color-text-tertiary)' }}\n cursor={{ stroke: 'var(--color-border-seam)', strokeDasharray: '4 4' }}\n />\n <Area\n type=\"monotone\"\n dataKey=\"value\"\n name={isSleepSelected ? 'Sleep hours' : 'Score'}\n stroke={CHART_ACCENT}\n strokeWidth={2.5}\n fill=\"url(#myb-recovery-fill)\"\n fillOpacity={1}\n dot={false}\n activeDot={{\n r: 4,\n strokeWidth: 2,\n stroke: 'var(--color-bg-surface)',\n fill: CHART_ACCENT,\n }}\n isAnimationActive={!reduced}\n animationDuration={900}\n />\n {lastPoint && (\n <ReferenceDot\n x={lastPoint.label}\n y={lastPoint.value}\n r={12}\n isFront\n ifOverflow=\"visible\"\n fill={CHART_ACCENT}\n fillOpacity={0.16}\n stroke=\"none\"\n />\n )}\n {lastPoint && (\n <ReferenceDot\n x={lastPoint.label}\n y={lastPoint.value}\n r={5}\n isFront\n ifOverflow=\"visible\"\n fill={CHART_ACCENT}\n stroke=\"var(--color-bg-surface)\"\n strokeWidth={2}\n />\n )}\n </AreaChart>\n </ResponsiveContainer>\n )}\n </GlassCard>\n </motion.div>\n </Section>\n\n {/* Log table */}\n <motion.div {...rise(0.16, false)}>\n <Section title=\"Recovery log\" description=\"Every recovery entry, most recent first.\">\n {logs.isLoading ? (\n <LoadingView label=\"Loading recovery logs…\" />\n ) : logs.isError ? (\n <ErrorView\n message=\"We couldn't load your recovery logs.\"\n onRetry={() => logs.refetch()}\n />\n ) : logRows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<HeartPulse className=\"h-10 w-10\" />}\n title=\"No recovery logs yet\"\n description=\"Track your sleep, readiness, soreness, stress or mood to see how your body is recovering between sessions.\"\n action={\n <Button className=\"gap-2\" onClick={() => openLog()}>\n <Plus className=\"h-4 w-4\" />\n Log recovery\n </Button>\n }\n />\n ) : (\n <DataTable\n columns={columns}\n rows={logRows}\n rowKey={(r) => r.id}\n defaultSort={{ key: 'loggedAt', dir: 'desc' }}\n />\n )}\n </Section>\n </motion.div>\n\n {/* Log recovery dialog */}\n <ResponsiveDialog open={open} onOpenChange={setOpen}>\n <ResponsiveDialogContent className=\"sm:max-w-[480px]\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Log recovery</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Capture how your body is recovering — one metric per entry.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"space-y-4 py-2\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-recovery-metric\">Metric</Label>\n <Select\n value={form.metric}\n onValueChange={(v) =>\n // Select only emits values from RECOVERY_METRICS, so `v` is a\n // MybRecoveryMetric — narrow the Radix `string` payload.\n setForm((f) => ({ ...f, metric: v as MybRecoveryMetric }))\n }\n >\n <SelectTrigger id=\"myb-recovery-metric\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {RECOVERY_METRICS.map((m) => (\n <SelectItem key={m} value={m}>\n {RECOVERY_META[m].label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {isSleepForm && (\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-recovery-sleep\">Sleep hours</Label>\n <Input\n id=\"myb-recovery-sleep\"\n type=\"number\"\n min=\"0\"\n max=\"24\"\n step=\"0.1\"\n value={form.sleepHours}\n onChange={(e) => setForm((f) => ({ ...f, sleepHours: e.target.value }))}\n placeholder=\"e.g. 7.5\"\n />\n <p className=\"text-xs text-text-tertiary\">Primary measure for sleep entries.</p>\n </div>\n )}\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-recovery-score\">Score{isSleepForm ? ' (optional)' : ''}</Label>\n <Input\n id=\"myb-recovery-score\"\n type=\"number\"\n min=\"0\"\n max=\"100\"\n step=\"1\"\n value={form.score}\n onChange={(e) => setForm((f) => ({ ...f, score: e.target.value }))}\n placeholder=\"0–100\"\n />\n <p className=\"text-xs text-text-tertiary\">\n {isSleepForm\n ? 'Optional 0–100 sleep-quality score.'\n : 'A 0–100 rating for this metric.'}\n </p>\n </div>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-recovery-at\">Logged at</Label>\n <Input\n id=\"myb-recovery-at\"\n type=\"datetime-local\"\n value={form.loggedAt}\n onChange={(e) => setForm((f) => ({ ...f, loggedAt: e.target.value }))}\n />\n </div>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-recovery-notes\">Notes</Label>\n <Textarea\n id=\"myb-recovery-notes\"\n rows={3}\n value={form.notes}\n onChange={(e) => setForm((f) => ({ ...f, notes: e.target.value }))}\n placeholder=\"How are you feeling? (optional)\"\n />\n </div>\n\n {err && <p className=\"text-sm text-status-error-text\">{err}</p>}\n </div>\n\n <ResponsiveDialogFooter>\n <Button\n variant=\"outline\"\n onClick={() => setOpen(false)}\n disabled={logRecovery.isPending}\n >\n Cancel\n </Button>\n <Button onClick={submit} disabled={logRecovery.isPending}>\n {logRecovery.isPending ? 'Saving…' : 'Log recovery'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED