@burdenoff/microfe-mybodyshield 2026.830.1 → 2026.1026.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/MyBodyShieldRoutes.d.ts.map +1 -1
- package/dist/mybodyshield/MyBodyShieldRoutes.js +35 -24
- package/dist/mybodyshield/MyBodyShieldRoutes.js.map +1 -1
- package/dist/mybodyshield/components/FilterTabs.js +1 -1
- package/dist/mybodyshield/components/FilterTabs.js.map +1 -1
- package/dist/mybodyshield/hooks/useMyBodyShieldApi.d.ts +4 -1
- package/dist/mybodyshield/hooks/useMyBodyShieldApi.d.ts.map +1 -1
- package/dist/mybodyshield/hooks/useMyBodyShieldApi.js +85 -60
- package/dist/mybodyshield/hooks/useMyBodyShieldApi.js.map +1 -1
- package/dist/mybodyshield/pages/insights/InsightsPage.js +1 -1
- package/dist/mybodyshield/pages/insights/InsightsPage.js.map +1 -1
- package/dist/mybodyshield/pages/recovery/MindfulnessPage.d.ts +2 -0
- package/dist/mybodyshield/pages/recovery/MindfulnessPage.d.ts.map +1 -0
- package/dist/mybodyshield/pages/recovery/MindfulnessPage.js +181 -0
- package/dist/mybodyshield/pages/recovery/MindfulnessPage.js.map +1 -0
- package/dist/mybodyshield/pages/recovery/RecoveryPage.d.ts.map +1 -1
- package/dist/mybodyshield/pages/recovery/RecoveryPage.js +210 -201
- package/dist/mybodyshield/pages/recovery/RecoveryPage.js.map +1 -1
- package/dist/mybodyshield/types/index.d.ts +31 -0
- package/dist/mybodyshield/types/index.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -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 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 MybDeviceConnection,\n MybDeviceProvider,\n MybWearableMetricType,\n MybTodayGuidance,\n MybFood,\n MybInsights,\n MybAiSettings,\n MybAiOperationLogConnection,\n MybUpdateAiSettingsInput,\n MybAiProcessingConsent,\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 workoutExercises: (id: string) => ['mybodyshield', 'workout-exercises', id] as const,\n sessions: ['mybodyshield', 'sessions'] as const,\n session: (id: string) => ['mybodyshield', 'session', id] as const,\n exerciseBestSet: (name: string) => ['mybodyshield', 'exercise-best-set', name] 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 deviceConnections: ['mybodyshield', 'device-connections'] as const,\n guidance: ['mybodyshield', 'guidance'] as const,\n insights: (range: string) => ['mybodyshield', 'insights', range] as const,\n foodSearch: (query: string) => ['mybodyshield', 'food-search', query] as const,\n foodByBarcode: (barcode: string) => ['mybodyshield', 'food-barcode', barcode] as const,\n aiSettings: ['mybodyshield', 'ai-settings'] as const,\n aiOperationLogs: ['mybodyshield', 'ai-operation-logs'] as const,\n aiConsent: ['mybodyshield', 'ai-consent'] 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 barcode 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 DEVICE_CONNECTION_FIELDS = `\n id workspaceId memberId provider status externalAccountId lastSyncedAt scopes createdAt updatedAt\n`;\n\nconst GUIDANCE_FIELDS = `\n memberId date readinessScore aiSummary cards { signal action reason }\n`;\n\nconst INSIGHTS_FIELDS = `\n dateRangeStart dateRangeEnd\n summary { consistencyStreakDays totalSessions totalVolumeKg averageReadiness macroAdherencePercent }\n daily { date sessions volumeKg readiness sleepHours calories proteinG carbsG fatG weight }\n goals { ${GOAL_FIELDS} }\n`;\n\nconst AI_SETTINGS_FIELDS = `\n id workspaceId memberId provider hasProviderKey modelName dailyAllowance dailyAllowanceUsed allowanceResetAt createdAt updatedAt\n`;\n\nconst AI_OPERATION_LOG_FIELDS = `\n id workspaceId memberId operationType provider modelName inputTokens outputTokens status errorMessage createdAt\n`;\n\nconst AI_PROCESSING_CONSENT_FIELDS = `\n id workspaceId actorId enabled acceptedAt withdrawnAt createdAt updatedAt\n`;\n\nconst FOOD_FIELDS = `\n barcode name brand servingG calories proteinG carbsG fatG imageUrl\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'> & {\n workouts: { items: MybWorkoutWithExerciseConnection[] };\n })\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\n/**\n * Planned exercises for a single workout, in `orderIndex` order — the\n * guided-workout stepper (BOFF-4096) walks a live session's `workoutId`\n * through these to drive the \"next exercise\" flow.\n */\nexport const useMybWorkoutExercises = (workoutId: string | undefined) => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useQuery({\n queryKey: KEY.workoutExercises(workoutId ?? ''),\n enabled: Boolean(workoutId),\n queryFn: async () => {\n const result = await mybQuery<{ mybWorkoutExercises: { items: MybExercise[] } }>(\n `query MybWorkoutExercises($workoutId: ID!) {\n mybWorkoutExercises(workoutId: $workoutId, pagination: { limit: 100 }) {\n items { ${EXERCISE_FIELDS} }\n }\n }`,\n { workoutId },\n apiGatewayUrl\n );\n return result.mybWorkoutExercises.items.slice().sort((a, b) => a.orderIndex - b.orderIndex);\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\n/**\n * The calling member's own best-ever logged set for `exerciseName` (backend:\n * `mybExerciseBestSet`, self-scoped — no memberId argument). Drives live\n * auto-PR detection in the log-set dialog (BOFF-4096): `null` means the\n * member has never logged this exercise before, so there is no record to\n * beat yet.\n */\nexport const useMybExerciseBestSet = (exerciseName: string) => {\n const { apiGatewayUrl } = useMyBodyShield();\n const trimmed = exerciseName.trim();\n return useQuery({\n queryKey: KEY.exerciseBestSet(trimmed),\n enabled: trimmed.length > 0,\n queryFn: async () => {\n const result = await mybQuery<{ mybExerciseBestSet: MybSessionSet | null }>(\n `query MybExerciseBestSet($exerciseName: String!) {\n mybExerciseBestSet(exerciseName: $exerciseName) { ${SET_FIELDS} }\n }`,\n { exerciseName: trimmed },\n apiGatewayUrl\n );\n return result.mybExerciseBestSet;\n },\n // Best-set history changes rarely mid-session; a short stale window\n // avoids refiring on every keystroke while the exercise name settles.\n staleTime: 15_000,\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 // A just-logged set may itself now be the best-ever for this exercise —\n // refresh so the next auto-PR comparison (this session or a later one)\n // sees it (BOFF-4096).\n qc.invalidateQueries({ queryKey: KEY.exerciseBestSet(data.exerciseName) });\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\n// ============================================================================\n// Wearable device integrations (BOFF-4172)\n// ============================================================================\n\nexport const useMybDeviceConnections = (memberId?: string) => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useQuery({\n queryKey: [...KEY.deviceConnections, memberId ?? 'me'],\n queryFn: async () => {\n const result = await mybQuery<{ mybListDeviceConnections: MybDeviceConnection[] }>(\n `query MybListDeviceConnections($memberId: ID) {\n mybListDeviceConnections(memberId: $memberId) { ${DEVICE_CONNECTION_FIELDS} }\n }`,\n { memberId },\n apiGatewayUrl\n );\n return result.mybListDeviceConnections;\n },\n });\n};\n\nexport const useMybConnectDevice = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: {\n provider: MybDeviceProvider;\n externalAccountId?: string;\n scopes?: string[];\n }) => {\n const result = await mybQuery<{ mybConnectDevice: MybDeviceConnection }>(\n `mutation MybConnectDevice($input: MybConnectDeviceInput!) {\n mybConnectDevice(input: $input) { ${DEVICE_CONNECTION_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.mybConnectDevice;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.deviceConnections }),\n });\n};\n\nexport const useMybDisconnectDevice = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await mybQuery<{ mybDisconnectDevice: boolean }>(\n `mutation MybDisconnectDevice($id: ID!) { mybDisconnectDevice(id: $id) }`,\n { id },\n apiGatewayUrl\n );\n return result.mybDisconnectDevice;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.deviceConnections }),\n });\n};\n\nexport interface MybSyncReadingInput {\n metricType: MybWearableMetricType;\n value: number;\n unit?: string;\n recordedAt: string;\n}\n\n/**\n * Sync a batch of readings a native health bridge (Apple HealthKit / Google\n * Health Connect) already pulled on-device. The bridge itself is a Capacitor\n * plugin concern that lives in the app shell (`mybodyshield-app`), not this\n * MFE; this hook is the transport primitive it calls once it has readings to\n * push (BOFF-4172).\n */\nexport const useMybSyncDeviceReadings = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (vars: { connectionId: string; readings: MybSyncReadingInput[] }) => {\n const result = await mybQuery<{ mybSyncDeviceReadings: unknown[] }>(\n `mutation MybSyncDeviceReadings($connectionId: ID!, $readings: [MybSyncReadingInput!]!) {\n mybSyncDeviceReadings(connectionId: $connectionId, readings: $readings) { id }\n }`,\n vars,\n apiGatewayUrl\n );\n return result.mybSyncDeviceReadings;\n },\n onSuccess: () => {\n qc.invalidateQueries({ queryKey: KEY.deviceConnections });\n qc.invalidateQueries({ queryKey: KEY.recovery });\n qc.invalidateQueries({ queryKey: KEY.bodyMetrics });\n },\n });\n};\n\n// ============================================================================\n// Adaptive guidance (BOFF-4174)\n// ============================================================================\n\nexport const useMybTodayGuidance = (memberId?: string) => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useQuery({\n queryKey: [...KEY.guidance, memberId ?? 'me'],\n queryFn: async () => {\n const result = await mybQuery<{ mybTodayGuidance: MybTodayGuidance }>(\n `query MybTodayGuidance($memberId: ID) {\n mybTodayGuidance(memberId: $memberId) { ${GUIDANCE_FIELDS} }\n }`,\n { memberId },\n apiGatewayUrl\n );\n return result.mybTodayGuidance;\n },\n // Guidance is derived from the day's logged data; a short stale window\n // avoids refetching on every focus while still catching a fresh log.\n staleTime: 60_000,\n });\n};\n\nexport interface InsightsRange {\n dateRangeStart?: string;\n dateRangeEnd?: string;\n}\n\nexport const useMybInsights = (range: InsightsRange = {}) => {\n const { apiGatewayUrl } = useMyBodyShield();\n const rangeKey = `${range.dateRangeStart ?? ''}|${range.dateRangeEnd ?? ''}`;\n return useQuery({\n queryKey: KEY.insights(rangeKey),\n queryFn: async () => {\n const result = await mybQuery<{ mybInsights: MybInsights }>(\n `query MybInsights($dateRangeStart: DateTime, $dateRangeEnd: DateTime) {\n mybInsights(dateRangeStart: $dateRangeStart, dateRangeEnd: $dateRangeEnd) { ${INSIGHTS_FIELDS} }\n }`,\n { dateRangeStart: range.dateRangeStart, dateRangeEnd: range.dateRangeEnd },\n apiGatewayUrl\n );\n return result.mybInsights;\n },\n });\n};\n\n// ============================================================================\n// AI controls (BOFF-5949)\n// ============================================================================\n\nexport const useMybAiSettings = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useQuery({\n queryKey: KEY.aiSettings,\n queryFn: async () => {\n const result = await mybQuery<{ mybAiSettings: MybAiSettings }>(\n `query MybAiSettings { mybAiSettings { ${AI_SETTINGS_FIELDS} } }`,\n {},\n apiGatewayUrl\n );\n return result.mybAiSettings;\n },\n });\n};\n\nexport const useMybUpdateAiSettings = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: MybUpdateAiSettingsInput) => {\n const result = await mybQuery<{ mybUpdateAiSettings: MybAiSettings }>(\n `mutation MybUpdateAiSettings($input: MybUpdateAiSettingsInput!) {\n mybUpdateAiSettings(input: $input) { ${AI_SETTINGS_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.mybUpdateAiSettings;\n },\n onSuccess: () => {\n qc.invalidateQueries({ queryKey: KEY.aiSettings });\n },\n });\n};\n\nexport const useMybAiOperationLogs = (pagination?: PaginationInput) => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useQuery({\n queryKey: [...KEY.aiOperationLogs, pagination ?? DEFAULT_PAGE],\n queryFn: async () => {\n const result = await mybQuery<{ mybListAiOperationLogs: MybAiOperationLogConnection }>(\n `query MybListAiOperationLogs($pagination: PaginationInput) {\n mybListAiOperationLogs(pagination: $pagination) {\n items { ${AI_OPERATION_LOG_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n { pagination: pagination ?? DEFAULT_PAGE },\n apiGatewayUrl\n );\n return result.mybListAiOperationLogs;\n },\n });\n};\n\n// ============================================================================\n// AI processing consent (BOFF-3838)\n// ============================================================================\n\nexport const useMybAiProcessingConsent = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useQuery({\n queryKey: KEY.aiConsent,\n queryFn: async () => {\n const result = await mybQuery<{ mybMyAiProcessingConsent: MybAiProcessingConsent }>(\n `query MybMyAiProcessingConsent { mybMyAiProcessingConsent { ${AI_PROCESSING_CONSENT_FIELDS} } }`,\n {},\n apiGatewayUrl\n );\n return result.mybMyAiProcessingConsent;\n },\n });\n};\n\nexport const useMybSetAiProcessingConsent = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (enabled: boolean) => {\n const result = await mybQuery<{ mybSetAiProcessingConsent: MybAiProcessingConsent }>(\n `mutation MybSetAiProcessingConsent($enabled: Boolean!) {\n mybSetAiProcessingConsent(enabled: $enabled) { ${AI_PROCESSING_CONSENT_FIELDS} }\n }`,\n { enabled },\n apiGatewayUrl\n );\n return result.mybSetAiProcessingConsent;\n },\n onSuccess: () => {\n qc.invalidateQueries({ queryKey: KEY.aiConsent });\n },\n });\n};\n\n// ============================================================================\n// Food database — Open Food Facts (BOFF-4173)\n// ============================================================================\n\nexport const useMybSearchFoods = (query: string, limit = 10) => {\n const { apiGatewayUrl } = useMyBodyShield();\n const trimmed = query.trim();\n return useQuery({\n queryKey: KEY.foodSearch(trimmed),\n enabled: trimmed.length >= 2,\n queryFn: async () => {\n const result = await mybQuery<{ mybSearchFoods: MybFood[] }>(\n `query MybSearchFoods($query: String!, $limit: Int) {\n mybSearchFoods(query: $query, limit: $limit) { ${FOOD_FIELDS} }\n }`,\n { query: trimmed, limit },\n apiGatewayUrl\n );\n return result.mybSearchFoods;\n },\n staleTime: 30_000,\n });\n};\n\nexport const useMybFoodByBarcode = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useMutation({\n mutationFn: async (barcode: string) => {\n const result = await mybQuery<{ mybFoodByBarcode: MybFood | null }>(\n `query MybFoodByBarcode($barcode: String!) {\n mybFoodByBarcode(barcode: $barcode) { ${FOOD_FIELDS} }\n }`,\n { barcode },\n apiGatewayUrl\n );\n return result.mybFoodByBarcode;\n },\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 MybDeviceConnection,\n MybDeviceProvider,\n MybWearableMetricType,\n MybTodayGuidance,\n MybFood,\n MybInsights,\n MybAiSettings,\n MybAiOperationLogConnection,\n MybUpdateAiSettingsInput,\n MybAiProcessingConsent,\n MybMindfulnessExercise,\n MybMindfulnessSession,\n MybCompleteMindfulnessSessionInput,\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 workoutExercises: (id: string) => ['mybodyshield', 'workout-exercises', id] as const,\n sessions: ['mybodyshield', 'sessions'] as const,\n session: (id: string) => ['mybodyshield', 'session', id] as const,\n exerciseBestSet: (name: string) => ['mybodyshield', 'exercise-best-set', name] 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 deviceConnections: ['mybodyshield', 'device-connections'] as const,\n guidance: ['mybodyshield', 'guidance'] as const,\n insights: (range: string) => ['mybodyshield', 'insights', range] as const,\n foodSearch: (query: string) => ['mybodyshield', 'food-search', query] as const,\n foodByBarcode: (barcode: string) => ['mybodyshield', 'food-barcode', barcode] as const,\n aiSettings: ['mybodyshield', 'ai-settings'] as const,\n aiOperationLogs: ['mybodyshield', 'ai-operation-logs'] as const,\n aiConsent: ['mybodyshield', 'ai-consent'] as const,\n mindfulnessExercises: ['mybodyshield', 'mindfulness-exercises'] as const,\n mindfulnessSessions: ['mybodyshield', 'mindfulness-sessions'] 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 barcode 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 DEVICE_CONNECTION_FIELDS = `\n id workspaceId memberId provider status externalAccountId lastSyncedAt scopes createdAt updatedAt\n`;\n\nconst GUIDANCE_FIELDS = `\n memberId date readinessScore aiSummary cards { signal action reason }\n`;\n\nconst INSIGHTS_FIELDS = `\n dateRangeStart dateRangeEnd\n summary { consistencyStreakDays totalSessions totalVolumeKg averageReadiness macroAdherencePercent }\n daily { date sessions volumeKg readiness sleepHours calories proteinG carbsG fatG weight }\n goals { ${GOAL_FIELDS} }\n`;\n\nconst AI_SETTINGS_FIELDS = `\n id workspaceId memberId provider hasProviderKey modelName dailyAllowance dailyAllowanceUsed allowanceResetAt createdAt updatedAt\n`;\n\nconst AI_OPERATION_LOG_FIELDS = `\n id workspaceId memberId operationType provider modelName inputTokens outputTokens status errorMessage createdAt\n`;\n\nconst AI_PROCESSING_CONSENT_FIELDS = `\n id workspaceId actorId enabled acceptedAt withdrawnAt createdAt updatedAt\n`;\n\nconst FOOD_FIELDS = `\n barcode name brand servingG calories proteinG carbsG fatG imageUrl\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'> & {\n workouts: { items: MybWorkoutWithExerciseConnection[] };\n })\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\n/**\n * Planned exercises for a single workout, in `orderIndex` order — the\n * guided-workout stepper (BOFF-4096) walks a live session's `workoutId`\n * through these to drive the \"next exercise\" flow.\n */\nexport const useMybWorkoutExercises = (workoutId: string | undefined) => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useQuery({\n queryKey: KEY.workoutExercises(workoutId ?? ''),\n enabled: Boolean(workoutId),\n queryFn: async () => {\n const result = await mybQuery<{ mybWorkoutExercises: { items: MybExercise[] } }>(\n `query MybWorkoutExercises($workoutId: ID!) {\n mybWorkoutExercises(workoutId: $workoutId, pagination: { limit: 100 }) {\n items { ${EXERCISE_FIELDS} }\n }\n }`,\n { workoutId },\n apiGatewayUrl\n );\n return result.mybWorkoutExercises.items.slice().sort((a, b) => a.orderIndex - b.orderIndex);\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\n/**\n * The calling member's own best-ever logged set for `exerciseName` (backend:\n * `mybExerciseBestSet`, self-scoped — no memberId argument). Drives live\n * auto-PR detection in the log-set dialog (BOFF-4096): `null` means the\n * member has never logged this exercise before, so there is no record to\n * beat yet.\n */\nexport const useMybExerciseBestSet = (exerciseName: string) => {\n const { apiGatewayUrl } = useMyBodyShield();\n const trimmed = exerciseName.trim();\n return useQuery({\n queryKey: KEY.exerciseBestSet(trimmed),\n enabled: trimmed.length > 0,\n queryFn: async () => {\n const result = await mybQuery<{ mybExerciseBestSet: MybSessionSet | null }>(\n `query MybExerciseBestSet($exerciseName: String!) {\n mybExerciseBestSet(exerciseName: $exerciseName) { ${SET_FIELDS} }\n }`,\n { exerciseName: trimmed },\n apiGatewayUrl\n );\n return result.mybExerciseBestSet;\n },\n // Best-set history changes rarely mid-session; a short stale window\n // avoids refiring on every keystroke while the exercise name settles.\n staleTime: 15_000,\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 // A just-logged set may itself now be the best-ever for this exercise —\n // refresh so the next auto-PR comparison (this session or a later one)\n // sees it (BOFF-4096).\n qc.invalidateQueries({ queryKey: KEY.exerciseBestSet(data.exerciseName) });\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\nconst MINDFULNESS_EXERCISE_FIELDS = `\n id slug title description kind durationSeconds inhaleSeconds holdSeconds\n exhaleSeconds cycles contentSource\n`;\n\nconst MINDFULNESS_SESSION_FIELDS = `\n id workspaceId memberId startedAt completedAt durationSeconds cyclesCompleted createdAt\n exercise { ${MINDFULNESS_EXERCISE_FIELDS} }\n`;\n\nexport const useMybMindfulnessExercises = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useQuery({\n queryKey: KEY.mindfulnessExercises,\n queryFn: async () => {\n const result = await mybQuery<{ mybListMindfulnessExercises: MybMindfulnessExercise[] }>(\n `query MybListMindfulnessExercises { mybListMindfulnessExercises { ${MINDFULNESS_EXERCISE_FIELDS} } }`,\n {},\n apiGatewayUrl\n );\n return result.mybListMindfulnessExercises;\n },\n });\n};\n\nexport const useMybMindfulnessSessions = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useQuery({\n queryKey: KEY.mindfulnessSessions,\n queryFn: async () => {\n const result = await mybQuery<{\n mybListMindfulnessSessions: MybConnection<MybMindfulnessSession>;\n }>(\n `query MybListMindfulnessSessions { mybListMindfulnessSessions { items { ${MINDFULNESS_SESSION_FIELDS} } pageInfo { cursor hasMore } } }`,\n {},\n apiGatewayUrl\n );\n return result.mybListMindfulnessSessions;\n },\n });\n};\n\nexport const useMybCompleteMindfulnessSession = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: MybCompleteMindfulnessSessionInput) => {\n const result = await mybQuery<{ mybCompleteMindfulnessSession: MybMindfulnessSession }>(\n `mutation MybCompleteMindfulnessSession($input: MybCompleteMindfulnessSessionInput!) {\n mybCompleteMindfulnessSession(input: $input) { ${MINDFULNESS_SESSION_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.mybCompleteMindfulnessSession;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.mindfulnessSessions }),\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\n// ============================================================================\n// Wearable device integrations (BOFF-4172)\n// ============================================================================\n\nexport const useMybDeviceConnections = (memberId?: string) => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useQuery({\n queryKey: [...KEY.deviceConnections, memberId ?? 'me'],\n queryFn: async () => {\n const result = await mybQuery<{ mybListDeviceConnections: MybDeviceConnection[] }>(\n `query MybListDeviceConnections($memberId: ID) {\n mybListDeviceConnections(memberId: $memberId) { ${DEVICE_CONNECTION_FIELDS} }\n }`,\n { memberId },\n apiGatewayUrl\n );\n return result.mybListDeviceConnections;\n },\n });\n};\n\nexport const useMybConnectDevice = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: {\n provider: MybDeviceProvider;\n externalAccountId?: string;\n scopes?: string[];\n }) => {\n const result = await mybQuery<{ mybConnectDevice: MybDeviceConnection }>(\n `mutation MybConnectDevice($input: MybConnectDeviceInput!) {\n mybConnectDevice(input: $input) { ${DEVICE_CONNECTION_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.mybConnectDevice;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.deviceConnections }),\n });\n};\n\nexport const useMybDisconnectDevice = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await mybQuery<{ mybDisconnectDevice: boolean }>(\n `mutation MybDisconnectDevice($id: ID!) { mybDisconnectDevice(id: $id) }`,\n { id },\n apiGatewayUrl\n );\n return result.mybDisconnectDevice;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.deviceConnections }),\n });\n};\n\nexport interface MybSyncReadingInput {\n metricType: MybWearableMetricType;\n value: number;\n unit?: string;\n recordedAt: string;\n}\n\n/**\n * Sync a batch of readings a native health bridge (Apple HealthKit / Google\n * Health Connect) already pulled on-device. The bridge itself is a Capacitor\n * plugin concern that lives in the app shell (`mybodyshield-app`), not this\n * MFE; this hook is the transport primitive it calls once it has readings to\n * push (BOFF-4172).\n */\nexport const useMybSyncDeviceReadings = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (vars: { connectionId: string; readings: MybSyncReadingInput[] }) => {\n const result = await mybQuery<{ mybSyncDeviceReadings: unknown[] }>(\n `mutation MybSyncDeviceReadings($connectionId: ID!, $readings: [MybSyncReadingInput!]!) {\n mybSyncDeviceReadings(connectionId: $connectionId, readings: $readings) { id }\n }`,\n vars,\n apiGatewayUrl\n );\n return result.mybSyncDeviceReadings;\n },\n onSuccess: () => {\n qc.invalidateQueries({ queryKey: KEY.deviceConnections });\n qc.invalidateQueries({ queryKey: KEY.recovery });\n qc.invalidateQueries({ queryKey: KEY.bodyMetrics });\n },\n });\n};\n\n// ============================================================================\n// Adaptive guidance (BOFF-4174)\n// ============================================================================\n\nexport const useMybTodayGuidance = (memberId?: string) => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useQuery({\n queryKey: [...KEY.guidance, memberId ?? 'me'],\n queryFn: async () => {\n const result = await mybQuery<{ mybTodayGuidance: MybTodayGuidance }>(\n `query MybTodayGuidance($memberId: ID) {\n mybTodayGuidance(memberId: $memberId) { ${GUIDANCE_FIELDS} }\n }`,\n { memberId },\n apiGatewayUrl\n );\n return result.mybTodayGuidance;\n },\n // Guidance is derived from the day's logged data; a short stale window\n // avoids refetching on every focus while still catching a fresh log.\n staleTime: 60_000,\n });\n};\n\nexport interface InsightsRange {\n dateRangeStart?: string;\n dateRangeEnd?: string;\n}\n\nexport const useMybInsights = (range: InsightsRange = {}) => {\n const { apiGatewayUrl } = useMyBodyShield();\n const rangeKey = `${range.dateRangeStart ?? ''}|${range.dateRangeEnd ?? ''}`;\n return useQuery({\n queryKey: KEY.insights(rangeKey),\n queryFn: async () => {\n const result = await mybQuery<{ mybInsights: MybInsights }>(\n `query MybInsights($dateRangeStart: DateTime, $dateRangeEnd: DateTime) {\n mybInsights(dateRangeStart: $dateRangeStart, dateRangeEnd: $dateRangeEnd) { ${INSIGHTS_FIELDS} }\n }`,\n { dateRangeStart: range.dateRangeStart, dateRangeEnd: range.dateRangeEnd },\n apiGatewayUrl\n );\n return result.mybInsights;\n },\n });\n};\n\n// ============================================================================\n// AI controls (BOFF-5949)\n// ============================================================================\n\nexport const useMybAiSettings = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useQuery({\n queryKey: KEY.aiSettings,\n queryFn: async () => {\n const result = await mybQuery<{ mybAiSettings: MybAiSettings }>(\n `query MybAiSettings { mybAiSettings { ${AI_SETTINGS_FIELDS} } }`,\n {},\n apiGatewayUrl\n );\n return result.mybAiSettings;\n },\n });\n};\n\nexport const useMybUpdateAiSettings = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: MybUpdateAiSettingsInput) => {\n const result = await mybQuery<{ mybUpdateAiSettings: MybAiSettings }>(\n `mutation MybUpdateAiSettings($input: MybUpdateAiSettingsInput!) {\n mybUpdateAiSettings(input: $input) { ${AI_SETTINGS_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.mybUpdateAiSettings;\n },\n onSuccess: () => {\n qc.invalidateQueries({ queryKey: KEY.aiSettings });\n },\n });\n};\n\nexport const useMybAiOperationLogs = (pagination?: PaginationInput) => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useQuery({\n queryKey: [...KEY.aiOperationLogs, pagination ?? DEFAULT_PAGE],\n queryFn: async () => {\n const result = await mybQuery<{ mybListAiOperationLogs: MybAiOperationLogConnection }>(\n `query MybListAiOperationLogs($pagination: PaginationInput) {\n mybListAiOperationLogs(pagination: $pagination) {\n items { ${AI_OPERATION_LOG_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n { pagination: pagination ?? DEFAULT_PAGE },\n apiGatewayUrl\n );\n return result.mybListAiOperationLogs;\n },\n });\n};\n\n// ============================================================================\n// AI processing consent (BOFF-3838)\n// ============================================================================\n\nexport const useMybAiProcessingConsent = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useQuery({\n queryKey: KEY.aiConsent,\n queryFn: async () => {\n const result = await mybQuery<{ mybMyAiProcessingConsent: MybAiProcessingConsent }>(\n `query MybMyAiProcessingConsent { mybMyAiProcessingConsent { ${AI_PROCESSING_CONSENT_FIELDS} } }`,\n {},\n apiGatewayUrl\n );\n return result.mybMyAiProcessingConsent;\n },\n });\n};\n\nexport const useMybSetAiProcessingConsent = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (enabled: boolean) => {\n const result = await mybQuery<{ mybSetAiProcessingConsent: MybAiProcessingConsent }>(\n `mutation MybSetAiProcessingConsent($enabled: Boolean!) {\n mybSetAiProcessingConsent(enabled: $enabled) { ${AI_PROCESSING_CONSENT_FIELDS} }\n }`,\n { enabled },\n apiGatewayUrl\n );\n return result.mybSetAiProcessingConsent;\n },\n onSuccess: () => {\n qc.invalidateQueries({ queryKey: KEY.aiConsent });\n },\n });\n};\n\n// ============================================================================\n// Food database — Open Food Facts (BOFF-4173)\n// ============================================================================\n\nexport const useMybSearchFoods = (query: string, limit = 10) => {\n const { apiGatewayUrl } = useMyBodyShield();\n const trimmed = query.trim();\n return useQuery({\n queryKey: KEY.foodSearch(trimmed),\n enabled: trimmed.length >= 2,\n queryFn: async () => {\n const result = await mybQuery<{ mybSearchFoods: MybFood[] }>(\n `query MybSearchFoods($query: String!, $limit: Int) {\n mybSearchFoods(query: $query, limit: $limit) { ${FOOD_FIELDS} }\n }`,\n { query: trimmed, limit },\n apiGatewayUrl\n );\n return result.mybSearchFoods;\n },\n staleTime: 30_000,\n });\n};\n\nexport const useMybFoodByBarcode = () => {\n const { apiGatewayUrl } = useMyBodyShield();\n return useMutation({\n mutationFn: async (barcode: string) => {\n const result = await mybQuery<{ mybFoodByBarcode: MybFood | null }>(\n `query MybFoodByBarcode($barcode: String!) {\n mybFoodByBarcode(barcode: $barcode) { ${FOOD_FIELDS} }\n }`,\n { barcode },\n apiGatewayUrl\n );\n return result.mybFoodByBarcode;\n },\n });\n};\n"],"mappings":";;;;;AAwFA,IAAM,UAAqC;CACzC,IAAM,IAAI;AAGV,CAFA,EAAE,6BAA6B,OAAO,EACtC,EAAE,gCAAgC,OAAO,EACzC,EAAE,4BAA4B,OAAO;GAGjC,IAAW,OACf,GACA,GACA,MACmB;CACnB,IAAM,IAAW,MAAM,EAAoB;EACzC,SAAS;EACT;EACA;EACA;EACA,gBAAgB;EACjB,CAAC;AAEF,KAAI,EAAS,QAAQ,OACnB,OAAU,MAAM,EAAS,OAAO,IAAI,WAAW,yBAAyB;AAE1E,KAAI,CAAC,EAAS,KACZ,OAAU,MAAM,wCAAwC;AAK1D,QAHI,iBAAiB,KAAK,EAAM,IAC9B,GAAwB,EAEnB,EAAS;GAIZ,IAAM;CACV,UAAU,CAAC,gBAAgB,WAAW;CACtC,UAAU,MAAe;EAAC;EAAgB;EAAW;EAAG;CACxD,kBAAkB,MAAe;EAAC;EAAgB;EAAoB;EAAG;CACzE,mBAAmB,MAAe;EAAC;EAAgB;EAAqB;EAAG;CAC3E,UAAU,CAAC,gBAAgB,WAAW;CACtC,UAAU,MAAe;EAAC;EAAgB;EAAW;EAAG;CACxD,kBAAkB,MAAiB;EAAC;EAAgB;EAAqB;EAAK;CAC9E,OAAO,CAAC,gBAAgB,QAAQ;CAChC,OAAO,MAAe;EAAC;EAAgB;EAAQ;EAAG;CAClD,OAAO,CAAC,gBAAgB,QAAQ;CAChC,gBAAgB,MAAe;EAAC;EAAgB;EAAkB;EAAG;CACrE,YAAY,CAAC,gBAAgB,cAAc;CAC3C,aAAa,CAAC,gBAAgB,eAAe;CAC7C,aAAa,CAAC,gBAAgB,eAAe;CAC7C,UAAU,CAAC,gBAAgB,WAAW;CACtC,UAAU,CAAC,gBAAgB,WAAW;CACtC,mBAAmB,CAAC,gBAAgB,qBAAqB;CACzD,UAAU,CAAC,gBAAgB,WAAW;CACtC,WAAW,MAAkB;EAAC;EAAgB;EAAY;EAAM;CAChE,aAAa,MAAkB;EAAC;EAAgB;EAAe;EAAM;CACrE,gBAAgB,MAAoB;EAAC;EAAgB;EAAgB;EAAQ;CAC7E,YAAY,CAAC,gBAAgB,cAAc;CAC3C,iBAAiB,CAAC,gBAAgB,oBAAoB;CACtD,WAAW,CAAC,gBAAgB,aAAa;CACzC,sBAAsB,CAAC,gBAAgB,wBAAwB;CAC/D,qBAAqB,CAAC,gBAAgB,uBAAuB;CAC9D,EAEK,IAAgC,EAAE,OAAO,IAAI,EAM7C,IAAiB,sJAKjB,IAAkB,iJAKlB,IAAiB,2HAKjB,IAAa,mIAKb,IAAiB,6JAKjB,IAAc,wJAKd,IAAc,gJAKd,IAAc,iKAKd,IAAgB,mGAIhB,IAAkB,4FAIlB,IAAkB,mJASlB,KAAkB,+EAIlB,KAAkB;;;;YAIZ,EAAY;GAGlB,IAAqB,0IAIrB,IAA0B,yHAI1B,IAA+B,mFAI/B,IAAc,4EAId,KAAgB,8FAahB,KAAsB,MAC1B,EAAM,KAAK,EAAE,cAAW,GAAG,SAAe;CAAE,GAAG;CAAS,WAAW,EAAU;CAAO,EAAE,EAY3E,KAAkB,IAA0B,EAAE,KAAK;CAC9D,IAAM,EAAE,qBAAkB,GAAiB;AAC3C,QAAO,EAAS;EACd,UAAU,CAAC,GAAG,EAAI,UAAU,EAAQ;EACpC,SAAS,aACQ,MAAM,EACnB;;sBAEc,EAAe;;;YAI7B;GACE,QAAQ,EAAQ;GAChB,UAAU,EAAQ;GAClB,YAAY,EAAQ,cAAc;GACnC,EACD,EACD,EACa;EAEjB,CAAC;GAGS,KAAiB,MAA2B;CACvD,IAAM,EAAE,qBAAkB,GAAiB;AAC3C,QAAO,EAAS;EACd,UAAU,EAAI,QAAQ,KAAM,GAAG;EAC/B,SAAS,EAAQ;EACjB,SAAS,YAAY;GACnB,IAAM,IAAS,MAAM,EAOnB;;cAEM,EAAe;;;kBAGX,EAAe;sCACK,EAAgB;;;;YAK9C,EAAE,OAAI,EACN,EACD;AACD,OAAI,CAAC,EAAO,WAAY,QAAO;GAC/B,IAAM,EAAE,aAAU,GAAG,MAAY,EAAO;AACxC,UAAO;IAAE,GAAG;IAAS,UAAU,EAAmB,EAAS,MAAM;IAAE;;EAEtE,CAAC;GAGS,KAAyB,MAAkC;CACtE,IAAM,EAAE,qBAAkB,GAAiB;AAC3C,QAAO,EAAS;EACd,UAAU,EAAI,gBAAgB,KAAa,GAAG;EAC9C,SAAS,EAAQ;EACjB,SAAS,YAeA,GAdQ,MAAM,EAGnB;;;gBAGQ,EAAe;oCACK,EAAgB;;;YAI5C,EAAE,cAAW,EACb,EACD,EACgC,mBAAmB,MAAM;EAE7D,CAAC;GAQS,KAA0B,MAAkC;CACvE,IAAM,EAAE,qBAAkB,GAAiB;AAC3C,QAAO,EAAS;EACd,UAAU,EAAI,iBAAiB,KAAa,GAAG;EAC/C,SAAS,EAAQ;EACjB,SAAS,aACQ,MAAM,EACnB;;sBAEc,EAAgB;;YAG9B,EAAE,cAAW,EACb,EACD,EACa,oBAAoB,MAAM,OAAO,CAAC,MAAM,GAAG,MAAM,EAAE,aAAa,EAAE,WAAW;EAE9F,CAAC;GAGS,UAA4B;CACvC,IAAM,EAAE,qBAAkB,GAAiB,EACrC,IAAK,GAAgB;AAC3B,QAAO,EAAY;EACjB,YAAY,OAAO,OACF,MAAM,EACnB;8CACsC,EAAe;YAErD,EAAE,UAAO,EACT,EACD,EACa;EAEhB,iBAAiB,EAAG,kBAAkB,EAAE,UAAU,EAAI,UAAU,CAAC;EAClE,CAAC;GAGS,UAA4B;CACvC,IAAM,EAAE,qBAAkB,GAAiB,EACrC,IAAK,GAAgB;AAC3B,QAAO,EAAY;EACjB,YAAY,OAAO,OACF,MAAM,EACnB;uDAC+C,EAAe;YAE9D,GACA,EACD,EACa;EAEhB,YAAY,MAAS;AAEnB,GADA,EAAG,kBAAkB,EAAE,UAAU,EAAI,UAAU,CAAC,EAChD,EAAG,kBAAkB,EAAE,UAAU,EAAI,QAAQ,EAAK,GAAG,EAAE,CAAC;;EAE3D,CAAC;GAGS,UAAgC;CAC3C,IAAM,EAAE,qBAAkB,GAAiB,EACrC,IAAK,GAAgB;AAC3B,QAAO,EAAY;EACjB,YAAY,OAAO,OACF,MAAM,EACnB;6DACqD,EAAe;YAEpE,GACA,EACD,EACa;EAEhB,YAAY,MAAS;AAEnB,GADA,EAAG,kBAAkB,EAAE,UAAU,EAAI,UAAU,CAAC,EAChD,EAAG,kBAAkB,EAAE,UAAU,EAAI,QAAQ,EAAK,GAAG,EAAE,CAAC;;EAE3D,CAAC;GAGS,UAA6B;CACxC,IAAM,EAAE,qBAAkB,GAAiB,EACrC,IAAK,GAAgB;AAC3B,QAAO,EAAY;EACjB,YAAY,OAAO,OACF,MAAM,EACnB,uEACA,EAAE,OAAI,EACN,EACD,EACa;EAEhB,iBAAiB,EAAG,kBAAkB,EAAE,UAAU,EAAI,UAAU,CAAC;EAClE,CAAC;GAGS,UAAyB;CACpC,IAAM,EAAE,qBAAkB,GAAiB,EACrC,IAAK,GAAgB;AAC3B,QAAO,EAAY;EACjB,YAAY,OAAO,OACF,MAAM,EACnB;2CACmC,EAAe;YAElD,EAAE,UAAO,EACT,EACD,EACa;EAEhB,YAAY,MAAS;AAEnB,GADA,EAAG,kBAAkB,EAAE,UAAU,EAAI,QAAQ,EAAK,UAAU,EAAE,CAAC,EAC/D,EAAG,kBAAkB,EAAE,UAAU,EAAI,gBAAgB,EAAK,UAAU,EAAE,CAAC;;EAE1E,CAAC;GAGS,UAA0B;CACrC,IAAM,EAAE,qBAAkB,GAAiB,EACrC,IAAK,GAAgB;AAC3B,QAAO,EAAY;EACjB,YAAY,OAAO,OACF,MAAM,EACnB,mIAGA,EAAE,UAAO,EACT,EACD,EACa;EAEhB,iBAAiB;AAIf,GADA,EAAG,kBAAkB,EAAE,UAAU,CAAC,gBAAgB,UAAU,EAAE,CAAC,EAC/D,EAAG,kBAAkB,EAAE,UAAU,CAAC,gBAAgB,mBAAmB,EAAE,CAAC;;EAE3E,CAAC;GAaS,KAAkB,IAA0B,EAAE,KAAK;CAC9D,IAAM,EAAE,qBAAkB,GAAiB;AAC3C,QAAO,EAAS;EACd,UAAU,CAAC,GAAG,EAAI,UAAU,EAAQ;EACpC,SAAS,aACQ,MAAM,EACnB;;sBAEc,EAAe;;;YAI7B;GACE,QAAQ,EAAQ;GAChB,WAAW,EAAQ;GACnB,YAAY,EAAQ,cAAc;GACnC,EACD,EACD,EACa;EAEjB,CAAC;GAGS,KAAiB,MAA2B;CACvD,IAAM,EAAE,qBAAkB,GAAiB;AAC3C,QAAO,EAAS;EACd,UAAU,EAAI,QAAQ,KAAM,GAAG;EAC/B,SAAS,EAAQ;EACjB,SAAS,YAAY;GACnB,IAAM,IAAS,MAAM,EAGnB;;cAEM,EAAe;6BACA,EAAW;;YAGhC,EAAE,OAAI,EACN,EACD;AACD,OAAI,CAAC,EAAO,WAAY,QAAO;GAC/B,IAAM,EAAE,SAAM,GAAG,MAAY,EAAO;AACpC,UAAO;IAAE,GAAG;IAAS,MAAM,EAAK;IAAO;;EAE1C,CAAC;GAUS,KAAyB,MAAyB;CAC7D,IAAM,EAAE,qBAAkB,GAAiB,EACrC,IAAU,EAAa,MAAM;AACnC,QAAO,EAAS;EACd,UAAU,EAAI,gBAAgB,EAAQ;EACtC,SAAS,EAAQ,SAAS;EAC1B,SAAS,aACQ,MAAM,EACnB;8DACsD,EAAW;YAEjE,EAAE,cAAc,GAAS,EACzB,EACD,EACa;EAIhB,WAAW;EACZ,CAAC;GAGS,UAA2B;CACtC,IAAM,EAAE,qBAAkB,GAAiB,EACrC,IAAK,GAAgB;AAC3B,QAAO,EAAY;EACjB,YAAY,OAAO,OACF,MAAM,EACnB;6CACqC,EAAe;YAEpD,EAAE,UAAO,EACT,EACD,EACa;EAEhB,iBAAiB,EAAG,kBAAkB,EAAE,UAAU,EAAI,UAAU,CAAC;EAClE,CAAC;GAGS,UAAqB;CAChC,IAAM,EAAE,qBAAkB,GAAiB,EACrC,IAAK,GAAgB;AAC3B,QAAO,EAAY;EACjB,YAAY,OAAO,OACF,MAAM,EACnB;uCAC+B,EAAW;YAE1C,EAAE,UAAO,EACT,EACD,EACa;EAEhB,YAAY,MAAS;AAMnB,GALA,EAAG,kBAAkB,EAAE,UAAU,EAAI,QAAQ,EAAK,UAAU,EAAE,CAAC,EAC/D,EAAG,kBAAkB,EAAE,UAAU,EAAI,UAAU,CAAC,EAIhD,EAAG,kBAAkB,EAAE,UAAU,EAAI,gBAAgB,EAAK,aAAa,EAAE,CAAC;;EAE7E,CAAC;GAGS,UAA8B;CACzC,IAAM,EAAE,qBAAkB,GAAiB,EACrC,IAAK,GAAgB;AAC3B,QAAO,EAAY;EACjB,YAAY,OAAO,OACF,MAAM,EACnB;yDACiD,EAAe;YAEhE,GACA,EACD,EACa;EAEhB,YAAY,MAAS;AAEnB,GADA,EAAG,kBAAkB,EAAE,UAAU,EAAI,QAAQ,EAAK,GAAG,EAAE,CAAC,EACxD,EAAG,kBAAkB,EAAE,UAAU,EAAI,UAAU,CAAC;;EAEnD,CAAC;GAaS,KAAe,IAAuB,EAAE,KAAK;CACxD,IAAM,EAAE,qBAAkB,GAAiB;AAC3C,QAAO,EAAS;EACd,UAAU,CAAC,GAAG,EAAI,OAAO,EAAQ;EACjC,SAAS,aACQ,MAAM,EACnB;;sBAEc,EAAY;;;YAI1B;GACE,QAAQ,EAAQ;GAChB,UAAU,EAAQ;GAClB,YAAY,EAAQ,cAAc;GACnC,EACD,EACD,EACa;EAEjB,CAAC;GAGS,UAAyB;CACpC,IAAM,EAAE,qBAAkB,GAAiB,EACrC,IAAK,GAAgB;AAC3B,QAAO,EAAY;EACjB,YAAY,OAAO,OACF,MAAM,EACnB;2CACmC,EAAY;YAE/C,EAAE,UAAO,EACT,EACD,EACa;EAEhB,iBAAiB,EAAG,kBAAkB,EAAE,UAAU,EAAI,OAAO,CAAC;EAC/D,CAAC;GAGS,UAAyB;CACpC,IAAM,EAAE,qBAAkB,GAAiB,EACrC,IAAK,GAAgB;AAC3B,QAAO,EAAY;EACjB,YAAY,OAAO,OACF,MAAM,EACnB;oDAC4C,EAAY;YAExD,GACA,EACD,EACa;EAEhB,iBAAiB,EAAG,kBAAkB,EAAE,UAAU,EAAI,OAAO,CAAC;EAC/D,CAAC;GAGS,UAA0B;CACrC,IAAM,EAAE,qBAAkB,GAAiB,EACrC,IAAK,GAAgB;AAC3B,QAAO,EAAY;EACjB,YAAY,OAAO,OACF,MAAM,EACnB;sCAC8B,EAAY;YAE1C,EAAE,OAAI,EACN,EACD,EACa;EAEhB,iBAAiB,EAAG,kBAAkB,EAAE,UAAU,EAAI,OAAO,CAAC;EAC/D,CAAC;GAOS,KAA6B,MAAsB;CAC9D,IAAM,EAAE,qBAAkB,GAAiB;AAC3C,QAAO,EAAS;EACd,UAAU,CAAC,GAAG,EAAI,YAAY,KAAY,KAAK;EAC/C,SAAS,aACQ,MAAM,EACnB;0DACkD,EAAY;YAE9D,EAAE,aAAU,EACZ,EACD,EACa;EAEjB,CAAC;GA8BS,KAAe,IAAuB,EAAE,KAAK;CACxD,IAAM,EAAE,qBAAkB,GAAiB;AAC3C,QAAO,EAAS;EACd,UAAU,CAAC,GAAG,EAAI,OAAO,EAAQ;EACjC,SAAS,aACQ,MAAM,EACnB;;sBAEc,EAAY;;;YAI1B;GACE,UAAU,EAAQ;GAClB,QAAQ,EAAQ;GAChB,YAAY,EAAQ,cAAc;GACnC,EACD,EACD,EACa;EAEjB,CAAC;GAGS,KAAqB,GAAe,MAAsB;CACrE,IAAM,EAAE,qBAAkB,GAAiB;AAC3C,QAAO,EAAS;EACd,UAAU;GAAC,GAAG,EAAI;GAAa,KAAQ;GAAS,KAAY;GAAK;EACjE,SAAS,aACQ,MAAM,EACnB;+DACuD,GAAc;YAErE;GAAE;GAAM;GAAU,EAClB,EACD,EACa;EAEjB,CAAC;GAGS,WAAkC;CAC7C,IAAM,EAAE,qBAAkB,GAAiB,EACrC,IAAK,GAAgB;AAC3B,QAAO,EAAY;EACjB,YAAY,OAAO,OACF,MAAM,EACnB;oDAC4C,EAAY;YAExD,EAAE,UAAO,EACT,EACD,EACa;EAEhB,iBAAiB;AACf,KAAG,kBAAkB,EAAE,UAAU,EAAI,YAAY,CAAC;;EAErD,CAAC;GAGS,UAAkC;CAC7C,IAAM,EAAE,qBAAkB,GAAiB,EACrC,IAAK,GAAgB;AAC3B,QAAO,EAAY;EACjB,YAAY,OAAO,OACF,MAAM,EACnB;6DACqD,EAAY;YAEjE,GACA,EACD,EACa;EAEhB,YAAY,MAAS;AAEnB,GADA,EAAG,kBAAkB,EAAE,UAAU,EAAI,YAAY,CAAC,EAClD,EAAG,kBAAkB,EAAE,UAAU,EAAI,cAAc,EAAK,GAAG,EAAE,CAAC;;EAEjE,CAAC;GAGS,UAAoC;CAC/C,IAAM,EAAE,qBAAkB,GAAiB,EACrC,IAAK,GAAgB;AAC3B,QAAO,EAAY;EACjB,YAAY,OAAO,OACF,MAAM,EACnB;gDACwC,EAAY;YAEpD,EAAE,OAAI,EACN,EACD,EACa;EAEhB,YAAY,MAAS;AAEnB,GADA,EAAG,kBAAkB,EAAE,UAAU,EAAI,YAAY,CAAC,EAClD,EAAG,kBAAkB,EAAE,UAAU,EAAI,cAAc,EAAK,GAAG,EAAE,CAAC;;EAEjE,CAAC;GAGS,UAAsB;CACjC,IAAM,EAAE,qBAAkB,GAAiB,EACrC,IAAK,GAAgB;AAC3B,QAAO,EAAY;EACjB,YAAY,OAAO,OACF,MAAM,EACnB;wCACgC,EAAY;YAE5C,EAAE,UAAO,EACT,EACD,EACa;EAEhB,YAAY,MAAS;AAGnB,GAFA,EAAG,kBAAkB,EAAE,UAAU,EAAI,OAAO,CAAC,EAC7C,EAAG,kBAAkB,EAAE,UAAU,EAAI,aAAa,CAAC,EAC/C,EAAK,UAAQ,EAAG,kBAAkB,EAAE,UAAU,EAAI,cAAc,EAAK,OAAO,EAAE,CAAC;;EAEtF,CAAC;GAGS,WAAyB;CACpC,IAAM,EAAE,qBAAkB,GAAiB,EACrC,IAAK,GAAgB;AAC3B,QAAO,EAAY;EACjB,YAAY,OAAO,OACF,MAAM,EACnB,+DACA,EAAE,OAAI,EACN,EACD,EACa;EAEhB,iBAAiB;AAEf,GADA,EAAG,kBAAkB,EAAE,UAAU,EAAI,OAAO,CAAC,EAC7C,EAAG,kBAAkB,EAAE,UAAU,EAAI,aAAa,CAAC;;EAEtD,CAAC;GAaS,MAAqB,IAA6B,EAAE,KAAK;CACpE,IAAM,EAAE,qBAAkB,GAAiB;AAC3C,QAAO,EAAS;EACd,UAAU,CAAC,GAAG,EAAI,aAAa,EAAQ;EACvC,SAAS,aACQ,MAAM,EACnB;;sBAEc,EAAc;;;YAI5B;GACE,MAAM,EAAQ;GACd,UAAU,EAAQ;GAClB,YAAY,EAAQ,cAAc;GACnC,EACD,EACD,EACa;EAEjB,CAAC;GAGS,WAA+B;CAC1C,IAAM,EAAE,qBAAkB,GAAiB,EACrC,IAAK,GAAgB;AAC3B,QAAO,EAAY;EACjB,YAAY,OAAO,OACF,MAAM,EACnB;iDACyC,EAAc;YAEvD,EAAE,UAAO,EACT,EACD,EACa;EAEhB,iBAAiB,EAAG,kBAAkB,EAAE,UAAU,EAAI,aAAa,CAAC;EACrE,CAAC;GAOS,MAAsB,GAA4B,MAAsB;CACnF,IAAM,EAAE,qBAAkB,GAAiB;AAC3C,QAAO,EAAS;EACd,UAAU;GAAC,GAAG,EAAI;GAAU,KAAU;GAAO,KAAY;GAAK;EAC9D,SAAS,aACQ,MAAM,EACnB;;sBAEc,EAAgB;;;YAI9B;GAAE;GAAQ;GAAU,EACpB,EACD,EACa;EAEjB,CAAC;GAGS,WAA0B;CACrC,IAAM,EAAE,qBAAkB,GAAiB,EACrC,IAAK,GAAgB;AAC3B,QAAO,EAAY;EACjB,YAAY,OAAO,OACF,MAAM,EACnB;4CACoC,EAAgB;YAEpD,EAAE,UAAO,EACT,EACD,EACa;EAEhB,iBAAiB,EAAG,kBAAkB,EAAE,UAAU,EAAI,UAAU,CAAC;EAClE,CAAC;GAGE,IAA8B,wHAK9B,IAA6B;;eAEpB,EAA4B;GAG9B,WAAmC;CAC9C,IAAM,EAAE,qBAAkB,GAAiB;AAC3C,QAAO,EAAS;EACd,UAAU,EAAI;EACd,SAAS,aACQ,MAAM,EACnB,qEAAqE,EAA4B,OACjG,EAAE,EACF,EACD,EACa;EAEjB,CAAC;GAGS,WAAkC;CAC7C,IAAM,EAAE,qBAAkB,GAAiB;AAC3C,QAAO,EAAS;EACd,UAAU,EAAI;EACd,SAAS,aACQ,MAAM,EAGnB,2EAA2E,EAA2B,qCACtG,EAAE,EACF,EACD,EACa;EAEjB,CAAC;GAGS,WAAyC;CACpD,IAAM,EAAE,qBAAkB,GAAiB,EACrC,IAAK,GAAgB;AAC3B,QAAO,EAAY;EACjB,YAAY,OAAO,OACF,MAAM,EACnB;2DACmD,EAA2B;YAE9E,EAAE,UAAO,EACT,EACD,EACa;EAEhB,iBAAiB,EAAG,kBAAkB,EAAE,UAAU,EAAI,qBAAqB,CAAC;EAC7E,CAAC;GAOS,MAAkB,GAA4B,MAAsB;CAC/E,IAAM,EAAE,qBAAkB,GAAiB;AAC3C,QAAO,EAAS;EACd,UAAU;GAAC,GAAG,EAAI;GAAU,KAAU;GAAO,KAAY;GAAK;EAC9D,SAAS,aACQ,MAAM,EACnB;;sBAEc,EAAgB;;;YAI9B;GAAE;GAAQ;GAAU,EACpB,EACD,EACa;EAEjB,CAAC;GAGS,WAA6B;CACxC,IAAM,EAAE,qBAAkB,GAAiB,EACrC,IAAK,GAAgB;AAC3B,QAAO,EAAY;EACjB,YAAY,OAAO,OACF,MAAM,EACnB;+CACuC,EAAgB;YAEvD,EAAE,UAAO,EACT,EACD,EACa;EAEhB,iBAAiB,EAAG,kBAAkB,EAAE,UAAU,EAAI,UAAU,CAAC;EAClE,CAAC;GAGS,UAAiC;CAC5C,IAAM,EAAE,qBAAkB,GAAiB,EACrC,IAAK,GAAgB;AAC3B,QAAO,EAAY;EACjB,YAAY,OAAO,OACF,MAAM,EACnB;8DACsD,EAAgB;YAEtE,GACA,EACD,EACa;EAEhB,iBAAiB,EAAG,kBAAkB,EAAE,UAAU,EAAI,UAAU,CAAC;EAClE,CAAC;GAsGS,MAAuB,MAAsB;CACxD,IAAM,EAAE,qBAAkB,GAAiB;AAC3C,QAAO,EAAS;EACd,UAAU,CAAC,GAAG,EAAI,UAAU,KAAY,KAAK;EAC7C,SAAS,aACQ,MAAM,EACnB;oDAC4C,GAAgB;YAE5D,EAAE,aAAU,EACZ,EACD,EACa;EAIhB,WAAW;EACZ,CAAC;GAQS,MAAkB,IAAuB,EAAE,KAAK;CAC3D,IAAM,EAAE,qBAAkB,GAAiB,EACrC,IAAW,GAAG,EAAM,kBAAkB,GAAG,GAAG,EAAM,gBAAgB;AACxE,QAAO,EAAS;EACd,UAAU,EAAI,SAAS,EAAS;EAChC,SAAS,aACQ,MAAM,EACnB;wFACgF,GAAgB;YAEhG;GAAE,gBAAgB,EAAM;GAAgB,cAAc,EAAM;GAAc,EAC1E,EACD,EACa;EAEjB,CAAC;GAOS,WAAyB;CACpC,IAAM,EAAE,qBAAkB,GAAiB;AAC3C,QAAO,EAAS;EACd,UAAU,EAAI;EACd,SAAS,aACQ,MAAM,EACnB,yCAAyC,EAAmB,OAC5D,EAAE,EACF,EACD,EACa;EAEjB,CAAC;GAGS,WAA+B;CAC1C,IAAM,EAAE,qBAAkB,GAAiB,EACrC,IAAK,GAAgB;AAC3B,QAAO,EAAY;EACjB,YAAY,OAAO,OACF,MAAM,EACnB;iDACyC,EAAmB;YAE5D,EAAE,UAAO,EACT,EACD,EACa;EAEhB,iBAAiB;AACf,KAAG,kBAAkB,EAAE,UAAU,EAAI,YAAY,CAAC;;EAErD,CAAC;GAGS,MAAyB,MAAiC;CACrE,IAAM,EAAE,qBAAkB,GAAiB;AAC3C,QAAO,EAAS;EACd,UAAU,CAAC,GAAG,EAAI,iBAAiB,KAAc,EAAa;EAC9D,SAAS,aACQ,MAAM,EACnB;;sBAEc,EAAwB;;;YAItC,EAAE,YAAY,KAAc,GAAc,EAC1C,EACD,EACa;EAEjB,CAAC;GAOS,WAAkC;CAC7C,IAAM,EAAE,qBAAkB,GAAiB;AAC3C,QAAO,EAAS;EACd,UAAU,EAAI;EACd,SAAS,aACQ,MAAM,EACnB,+DAA+D,EAA6B,OAC5F,EAAE,EACF,EACD,EACa;EAEjB,CAAC;GAGS,WAAqC;CAChD,IAAM,EAAE,qBAAkB,GAAiB,EACrC,IAAK,GAAgB;AAC3B,QAAO,EAAY;EACjB,YAAY,OAAO,OACF,MAAM,EACnB;2DACmD,EAA6B;YAEhF,EAAE,YAAS,EACX,EACD,EACa;EAEhB,iBAAiB;AACf,KAAG,kBAAkB,EAAE,UAAU,EAAI,WAAW,CAAC;;EAEpD,CAAC;GAOS,MAAqB,GAAe,IAAQ,OAAO;CAC9D,IAAM,EAAE,qBAAkB,GAAiB,EACrC,IAAU,EAAM,MAAM;AAC5B,QAAO,EAAS;EACd,UAAU,EAAI,WAAW,EAAQ;EACjC,SAAS,EAAQ,UAAU;EAC3B,SAAS,aACQ,MAAM,EACnB;2DACmD,EAAY;YAE/D;GAAE,OAAO;GAAS;GAAO,EACzB,EACD,EACa;EAEhB,WAAW;EACZ,CAAC;GAGS,WAA4B;CACvC,IAAM,EAAE,qBAAkB,GAAiB;AAC3C,QAAO,EAAY,EACjB,YAAY,OAAO,OACF,MAAM,EACnB;kDAC0C,EAAY;YAEtD,EAAE,YAAS,EACX,EACD,EACa,kBAEjB,CAAC"}
|
|
@@ -277,7 +277,7 @@ function K() {
|
|
|
277
277
|
children: z.map((e) => /* @__PURE__ */ F("button", {
|
|
278
278
|
type: "button",
|
|
279
279
|
onClick: () => H(e.days),
|
|
280
|
-
className: `rounded-lg px-3 py-1.5 text-sm font-medium transition-colors ${V === e.days ? "bg-accent text-
|
|
280
|
+
className: `rounded-lg px-3 py-1.5 text-sm font-medium transition-colors ${V === e.days ? "bg-accent text-action-primary-text" : "bg-bg-sunken text-text-secondary hover:bg-bg-elevated"}`,
|
|
281
281
|
children: C(`mybodyshield.pages.insights.${e.labelKey}`, e.fallback)
|
|
282
282
|
}, e.days))
|
|
283
283
|
}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InsightsPage.js","names":[],"sources":["../../../../src/mybodyshield/pages/insights/InsightsPage.tsx"],"sourcesContent":["/**\n * Insights — unified analytics dashboard for MyBodyShield.\n * @module mybodyshield/pages/insights\n *\n * BOFF-4056: surfaces a 30-day rollup of training volume, sessions,\n * readiness, sleep, nutrition, body weight, and active goals using the\n * `mybInsights` federated query. The backend already aggregates and\n * anonymizes the data; this page only renders what the member is allowed\n * to see.\n */\n\nimport { useEffect, useMemo, useState } from 'react';\nimport {\n BarChart3,\n Calendar,\n Dumbbell,\n Flame,\n HeartPulse,\n Moon,\n Scale,\n Target,\n TrendingUp,\n Utensils,\n} from 'lucide-react';\nimport { Button, GlassCard } from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n MetricsCard,\n LoadingView,\n ErrorView,\n Section,\n DataTable,\n type Column,\n} from '../../components';\nimport { useMybInsights, useMyBodyShieldAnalytics, useTr } from '../../hooks';\nimport { useMyBodyShield } from '../../context';\nimport type { MybInsightsDay, MybInsightsSummary, MybGoal } from '../../types';\nimport {\n formatDate,\n formatNumber,\n formatWeight,\n formatKcal,\n formatGrams,\n formatPercent,\n formatEnumLabel,\n} from '../../utils/formatters';\nimport {\n VolumeChart,\n ReadinessSleepChart,\n MacroChart,\n CalorieChart,\n CorrelationView,\n} from './InsightsCharts';\n\nconst RANGE_OPTIONS = [\n { labelKey: 'last7Days', fallback: 'Last 7 days', days: 7 },\n { labelKey: 'last30Days', fallback: 'Last 30 days', days: 30 },\n { labelKey: 'last90Days', fallback: 'Last 90 days', days: 90 },\n] as const;\n\ntype RangeDays = (typeof RANGE_OPTIONS)[number]['days'];\n\nfunction toISODate(d: Date): string {\n return d.toISOString().split('T')[0];\n}\n\n/**\n * True when this day carries any logged nutrition. `MybInsightsDay` totals are\n * sums over the day's meals, so a day with no meals is all-zero — but a meal\n * logged with zero macros is indistinguishable from that, which is why the\n * label below says \"macros logged\" rather than \"meals logged\".\n */\nfunction hasMacros(day: MybInsightsDay): boolean {\n return day.calories > 0 || day.proteinG > 0 || day.carbsG > 0 || day.fatG > 0;\n}\n\n/** True when the member logged a readiness or a sleep entry that day. */\nfunction hasRecovery(day: MybInsightsDay): boolean {\n return day.readiness != null || day.sleepHours != null;\n}\n\n/** True when the day carries anything at all — used for \"days with data\". */\nfunction hasAnyData(day: MybInsightsDay): boolean {\n return day.sessions > 0 || hasRecovery(day) || hasMacros(day) || day.weight != null;\n}\n\n/**\n * \"What these numbers are built on\" — the evidence behind the charts above.\n *\n * Every figure here is read straight out of the `mybInsights` payload this page\n * already holds. `daily` is zero-filled to one row per calendar day in the\n * range by the service, so a day count is exact; raw entry counts (how many\n * meals, how many measurements) are NOT in the payload and are deliberately not\n * guessed at. Each row deep-links to the page that owns the underlying log.\n */\nfunction InsightsSources({\n daily,\n summary,\n onOpen,\n}: {\n daily: MybInsightsDay[];\n summary: MybInsightsSummary;\n onOpen: (route: string) => void;\n}) {\n const t = useTr();\n const totalDays = daily.length;\n const sources = [\n {\n route: 'train',\n icon: <Dumbbell className=\"h-4 w-4\" />,\n label: t('mybodyshield.pages.insights.training', 'Training'),\n value: summary.totalSessions,\n unit:\n summary.totalSessions === 1\n ? t('mybodyshield.pages.insights.trainingUnitSingular', 'completed session')\n : t('mybodyshield.pages.insights.trainingUnitPlural', 'completed sessions'),\n detail: t('mybodyshield.pages.insights.trainingDetail', 'on {{active}} of {{total}} days', {\n active: formatNumber(daily.filter((d) => d.sessions > 0).length),\n total: formatNumber(totalDays),\n }),\n },\n {\n route: 'recovery',\n icon: <HeartPulse className=\"h-4 w-4\" />,\n label: t('mybodyshield.pages.insights.recovery', 'Recovery'),\n value: daily.filter(hasRecovery).length,\n unit: t('mybodyshield.pages.insights.recoveryUnit', 'days logged'),\n detail: t('mybodyshield.pages.insights.recoveryDetail', 'readiness or sleep entries'),\n },\n {\n route: 'nutrition',\n icon: <Utensils className=\"h-4 w-4\" />,\n label: t('mybodyshield.pages.insights.nutrition', 'Nutrition'),\n value: daily.filter(hasMacros).length,\n unit: t('mybodyshield.pages.insights.nutritionUnit', 'days logged'),\n detail: t('mybodyshield.pages.insights.nutritionDetail', 'days with macros logged'),\n },\n {\n route: 'progress',\n icon: <Scale className=\"h-4 w-4\" />,\n label: t('mybodyshield.pages.insights.bodyWeight', 'Body weight'),\n value: daily.filter((d) => d.weight != null).length,\n unit: t('mybodyshield.pages.insights.bodyWeightUnit', 'days logged'),\n detail: t('mybodyshield.pages.insights.bodyWeightDetail', 'weight readings only'),\n },\n ];\n\n return (\n <Section\n title={t('mybodyshield.pages.insights.sourcesTitle', 'What these numbers are built on')}\n description={t(\n 'mybodyshield.pages.insights.sourcesDescription',\n 'The logs feeding the charts above. Gaps here are why a trend can look flat.'\n )}\n >\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4\">\n {sources.map((source) => (\n <GlassCard key={source.route} treatment=\"plain\" className=\"flex flex-col gap-3 p-5\">\n <div className=\"flex items-center justify-between gap-2\">\n <div className=\"flex min-w-0 items-center gap-2 text-text-secondary\">\n {source.icon}\n <span className=\"truncate text-sm font-medium text-text-primary\">\n {source.label}\n </span>\n </div>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => onOpen(source.route)}>\n {t('mybodyshield.pages.insights.viewButton', 'View')}\n </Button>\n </div>\n <div>\n <p className=\"text-2xl font-bold tabular-nums text-text-primary\">\n {formatNumber(source.value)}\n </p>\n <p className=\"text-xs text-text-secondary\">{source.unit}</p>\n <p className=\"mt-1 text-xs text-text-tertiary\">{source.detail}</p>\n </div>\n </GlassCard>\n ))}\n </div>\n </Section>\n );\n}\n\nfunction useInsightsRange(days: RangeDays) {\n return useMemo(() => {\n const end = new Date();\n const start = new Date();\n start.setDate(end.getDate() - days + 1);\n return {\n dateRangeStart: toISODate(start),\n dateRangeEnd: toISODate(end),\n };\n }, [days]);\n}\n\nexport function InsightsPage() {\n const t = useTr();\n const { workspaceId, navigateTo } = useMyBodyShield();\n const [days, setDays] = useState<RangeDays>(30);\n const range = useInsightsRange(days);\n const insights = useMybInsights(range);\n const analytics = useMyBodyShieldAnalytics();\n\n // Emit a page-view event once on mount.\n useEffect(() => {\n analytics.emitPageView('insights', { workspaceId });\n }, [analytics, workspaceId]);\n\n if (insights.isLoading) {\n return (\n <PageLayout title={t('mybodyshield.pages.insights.title', 'Insights')}>\n <LoadingView label={t('mybodyshield.pages.insights.loading', 'Loading insights…')} />\n </PageLayout>\n );\n }\n\n if (insights.isError) {\n return (\n <PageLayout title={t('mybodyshield.pages.insights.title', 'Insights')}>\n <ErrorView\n title={t('mybodyshield.pages.insights.errorTitle', 'Could not load insights')}\n message={\n insights.error instanceof Error\n ? insights.error.message\n : t('mybodyshield.common.unknownError', 'Unknown error')\n }\n onRetry={() => insights.refetch()}\n />\n </PageLayout>\n );\n }\n\n const data = insights.data;\n if (!data) {\n return (\n <PageLayout title={t('mybodyshield.pages.insights.title', 'Insights')}>\n <ErrorView\n title={t('mybodyshield.pages.insights.errorTitle', 'Could not load insights')}\n message={t('mybodyshield.pages.insights.error', 'No insight data was returned.')}\n onRetry={() => insights.refetch()}\n />\n </PageLayout>\n );\n }\n\n const { summary, daily, goals } = data;\n\n // `daily` carries one row per calendar day in the range, zero-filled by the\n // service for days with nothing logged — so `daily.length` is the range\n // length, not a measure of tracking. Count the days that actually carry data.\n const daysWithData = daily.filter(hasAnyData).length;\n\n const dayColumns: Column<MybInsightsDay>[] = [\n {\n key: 'date',\n header: t('mybodyshield.pages.insights.dateColumn', 'Date'),\n render: (r) => <span className=\"text-text-primary\">{formatDate(r.date)}</span>,\n },\n {\n key: 'sessions',\n header: t('mybodyshield.pages.insights.sessionsColumn', 'Sessions'),\n align: 'right',\n render: (r) => <span className=\"tabular-nums text-text-secondary\">{r.sessions}</span>,\n },\n {\n key: 'volumeKg',\n header: t('mybodyshield.pages.insights.volumeColumn', 'Volume'),\n align: 'right',\n render: (r) => (\n <span className=\"tabular-nums text-text-secondary\">{formatWeight(r.volumeKg)}</span>\n ),\n },\n {\n key: 'readiness',\n header: t('mybodyshield.pages.insights.readinessColumn', 'Readiness'),\n align: 'right',\n render: (r) => (\n <span className=\"tabular-nums text-text-secondary\">\n {r.readiness != null ? formatNumber(r.readiness) : '—'}\n </span>\n ),\n },\n {\n key: 'sleepHours',\n header: t('mybodyshield.pages.insights.sleepColumn', 'Sleep'),\n align: 'right',\n render: (r) => (\n <span className=\"tabular-nums text-text-secondary\">\n {r.sleepHours != null ? `${formatNumber(r.sleepHours)} h` : '—'}\n </span>\n ),\n },\n {\n key: 'calories',\n header: t('mybodyshield.pages.insights.caloriesColumn', 'Calories'),\n align: 'right',\n render: (r) => (\n <span className=\"tabular-nums text-text-secondary\">{formatKcal(r.calories)}</span>\n ),\n },\n {\n key: 'proteinG',\n header: t('mybodyshield.pages.insights.proteinColumn', 'Protein'),\n align: 'right',\n render: (r) => (\n <span className=\"tabular-nums text-text-secondary\">{formatGrams(r.proteinG)}</span>\n ),\n },\n {\n key: 'weight',\n header: t('mybodyshield.pages.insights.weightColumn', 'Weight'),\n align: 'right',\n render: (r) => (\n <span className=\"tabular-nums text-text-secondary\">\n {r.weight != null ? formatWeight(r.weight) : '—'}\n </span>\n ),\n },\n ];\n\n const goalColumns: Column<MybGoal>[] = [\n {\n key: 'title',\n header: t('mybodyshield.pages.insights.goalColumn', 'Goal'),\n render: (r) => (\n <div>\n <span className=\"block text-sm text-text-primary\">{r.title}</span>\n <span className=\"text-xs text-text-tertiary\">{formatEnumLabel(r.type)}</span>\n </div>\n ),\n },\n {\n key: 'status',\n header: t('mybodyshield.pages.insights.statusColumn', 'Status'),\n render: (r) => (\n <span\n className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${\n r.status === 'ACHIEVED'\n ? 'bg-status-success-bg text-status-success-text'\n : 'bg-status-warning-bg text-status-warning-text'\n }`}\n >\n {formatEnumLabel(r.status)}\n </span>\n ),\n },\n {\n key: 'progress',\n header: t('mybodyshield.pages.insights.progressColumn', 'Progress'),\n align: 'right',\n render: (r) => (\n <span className=\"tabular-nums text-text-secondary\">\n {formatNumber(r.currentValue)} / {formatNumber(r.targetValue)} {r.unit}\n </span>\n ),\n },\n ];\n\n return (\n <PageLayout\n title={t('mybodyshield.pages.insights.title', 'Insights')}\n description={t('mybodyshield.pages.insights.description', '{{start}} – {{end}}', {\n start: formatDate(data.dateRangeStart),\n end: formatDate(data.dateRangeEnd),\n })}\n >\n {/* Range selector */}\n <div className=\"flex flex-wrap items-center gap-2\">\n {RANGE_OPTIONS.map((opt) => (\n <button\n key={opt.days}\n type=\"button\"\n onClick={() => setDays(opt.days)}\n className={`rounded-lg px-3 py-1.5 text-sm font-medium transition-colors ${\n days === opt.days\n ? 'bg-accent text-white'\n : 'bg-bg-sunken text-text-secondary hover:bg-bg-elevated'\n }`}\n >\n {t(`mybodyshield.pages.insights.${opt.labelKey}`, opt.fallback)}\n </button>\n ))}\n </div>\n\n {/* Summary KPIs */}\n <Section title={t('mybodyshield.pages.insights.summary', 'Summary')}>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n <MetricsCard\n icon={<Flame className=\"h-5 w-5\" />}\n value={summary.consistencyStreakDays}\n label={t('mybodyshield.pages.insights.consistencyStreak', 'Consistency streak')}\n />\n <MetricsCard\n icon={<Dumbbell className=\"h-5 w-5\" />}\n value={summary.totalSessions}\n label={t('mybodyshield.pages.insights.totalSessions', 'Total sessions')}\n />\n <MetricsCard\n icon={<TrendingUp className=\"h-5 w-5\" />}\n value={formatWeight(summary.totalVolumeKg)}\n label={t('mybodyshield.pages.insights.totalVolume', 'Total volume')}\n />\n <MetricsCard\n icon={<HeartPulse className=\"h-5 w-5\" />}\n value={formatNumber(summary.averageReadiness)}\n label={t('mybodyshield.pages.insights.averageReadiness', 'Average readiness')}\n />\n <MetricsCard\n icon={<Utensils className=\"h-5 w-5\" />}\n value={formatPercent(summary.macroAdherencePercent)}\n label={t('mybodyshield.pages.insights.macroAdherence', 'Macro adherence')}\n />\n <MetricsCard\n icon={<Calendar className=\"h-5 w-5\" />}\n value={daysWithData}\n label={t('mybodyshield.pages.insights.daysWithData', 'Days with data')}\n footer={\n <p className=\"text-xs text-text-tertiary\">\n {t('mybodyshield.pages.insights.ofDaysInRange', 'of {{count}} days in this range', {\n count: daily.length,\n })}\n </p>\n }\n />\n </div>\n </Section>\n\n {/* Evidence behind the numbers */}\n <InsightsSources daily={daily} summary={summary} onOpen={navigateTo} />\n\n {/* Trends */}\n <Section\n title={t('mybodyshield.pages.insights.trends', 'Trends')}\n description={t(\n 'mybodyshield.pages.insights.trendsDescription',\n 'How training, recovery and nutrition move together.'\n )}\n >\n <div className=\"grid grid-cols-1 gap-6 xl:grid-cols-2\">\n <VolumeChart daily={daily} />\n <ReadinessSleepChart daily={daily} />\n <MacroChart daily={daily} />\n <CalorieChart daily={daily} />\n </div>\n </Section>\n\n {/* Correlations */}\n <CorrelationView daily={daily} />\n\n {/* Daily breakdown */}\n <Section\n title={t('mybodyshield.pages.insights.dailyBreakdown', 'Daily breakdown')}\n description={t(\n 'mybodyshield.pages.insights.dailyBreakdownDescription',\n 'Per-day training, recovery and nutrition.'\n )}\n >\n <GlassCard treatment=\"plain\" className=\"overflow-hidden p-0\">\n <DataTable\n columns={dayColumns}\n rows={daily}\n rowKey={(r) => r.date}\n defaultSort={{ key: 'date', dir: 'desc' }}\n />\n </GlassCard>\n </Section>\n\n {/* Active goals */}\n <Section\n title={t('mybodyshield.pages.insights.activeGoals', 'Active goals')}\n description={t(\n 'mybodyshield.pages.insights.activeGoalsDescription',\n 'Goals open during this period.'\n )}\n >\n {goals.length === 0 ? (\n <GlassCard treatment=\"plain\" className=\"p-8 text-center\">\n <Target className=\"mx-auto h-8 w-8 text-text-tertiary\" />\n <p className=\"mt-3 text-sm font-medium text-text-primary\">\n {t('mybodyshield.pages.insights.noActiveGoals', 'No active goals')}\n </p>\n <p className=\"mt-1 text-xs text-text-tertiary\">\n {t(\n 'mybodyshield.pages.insights.noActiveGoalsDescription',\n 'Create a goal to see progress here.'\n )}\n </p>\n </GlassCard>\n ) : (\n <GlassCard treatment=\"plain\" className=\"overflow-hidden p-0\">\n <DataTable\n columns={goalColumns}\n rows={goals}\n rowKey={(r) => r.id}\n defaultSort={{ key: 'status', dir: 'asc' }}\n />\n </GlassCard>\n )}\n </Section>\n\n {/* Legend */}\n <div className=\"flex flex-wrap gap-4 text-xs text-text-tertiary\">\n <span className=\"inline-flex items-center gap-1.5\">\n <Dumbbell className=\"h-3.5 w-3.5\" />\n {t(\n 'mybodyshield.pages.insights.legendTraining',\n 'Training data from completed sessions.'\n )}\n </span>\n <span className=\"inline-flex items-center gap-1.5\">\n <Moon className=\"h-3.5 w-3.5\" />\n {t('mybodyshield.pages.insights.legendRecovery', 'Recovery data from recovery logs.')}\n </span>\n <span className=\"inline-flex items-center gap-1.5\">\n <Scale className=\"h-3.5 w-3.5\" />\n {t('mybodyshield.pages.insights.legendBody', 'Body metrics from progress logs.')}\n </span>\n <span className=\"inline-flex items-center gap-1.5\">\n <BarChart3 className=\"h-3.5 w-3.5\" />\n {t('mybodyshield.pages.insights.legendNutrition', 'Nutrition data from logged meals.')}\n </span>\n </div>\n\n <p className=\"mt-4 border-t border-border-seam pt-4 text-xs leading-relaxed text-text-tertiary\">\n {t(\n 'mybodyshield.pages.insights.medicalDisclaimer',\n 'General fitness information, not medical advice. MyBodyShield is not a medical device and does not diagnose, treat, cure or prevent any condition — talk to a qualified healthcare professional before changing how you train or eat.'\n )}\n </p>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"InsightsPage.js","names":[],"sources":["../../../../src/mybodyshield/pages/insights/InsightsPage.tsx"],"sourcesContent":["/**\n * Insights — unified analytics dashboard for MyBodyShield.\n * @module mybodyshield/pages/insights\n *\n * BOFF-4056: surfaces a 30-day rollup of training volume, sessions,\n * readiness, sleep, nutrition, body weight, and active goals using the\n * `mybInsights` federated query. The backend already aggregates and\n * anonymizes the data; this page only renders what the member is allowed\n * to see.\n */\n\nimport { useEffect, useMemo, useState } from 'react';\nimport {\n BarChart3,\n Calendar,\n Dumbbell,\n Flame,\n HeartPulse,\n Moon,\n Scale,\n Target,\n TrendingUp,\n Utensils,\n} from 'lucide-react';\nimport { Button, GlassCard } from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n MetricsCard,\n LoadingView,\n ErrorView,\n Section,\n DataTable,\n type Column,\n} from '../../components';\nimport { useMybInsights, useMyBodyShieldAnalytics, useTr } from '../../hooks';\nimport { useMyBodyShield } from '../../context';\nimport type { MybInsightsDay, MybInsightsSummary, MybGoal } from '../../types';\nimport {\n formatDate,\n formatNumber,\n formatWeight,\n formatKcal,\n formatGrams,\n formatPercent,\n formatEnumLabel,\n} from '../../utils/formatters';\nimport {\n VolumeChart,\n ReadinessSleepChart,\n MacroChart,\n CalorieChart,\n CorrelationView,\n} from './InsightsCharts';\n\nconst RANGE_OPTIONS = [\n { labelKey: 'last7Days', fallback: 'Last 7 days', days: 7 },\n { labelKey: 'last30Days', fallback: 'Last 30 days', days: 30 },\n { labelKey: 'last90Days', fallback: 'Last 90 days', days: 90 },\n] as const;\n\ntype RangeDays = (typeof RANGE_OPTIONS)[number]['days'];\n\nfunction toISODate(d: Date): string {\n return d.toISOString().split('T')[0];\n}\n\n/**\n * True when this day carries any logged nutrition. `MybInsightsDay` totals are\n * sums over the day's meals, so a day with no meals is all-zero — but a meal\n * logged with zero macros is indistinguishable from that, which is why the\n * label below says \"macros logged\" rather than \"meals logged\".\n */\nfunction hasMacros(day: MybInsightsDay): boolean {\n return day.calories > 0 || day.proteinG > 0 || day.carbsG > 0 || day.fatG > 0;\n}\n\n/** True when the member logged a readiness or a sleep entry that day. */\nfunction hasRecovery(day: MybInsightsDay): boolean {\n return day.readiness != null || day.sleepHours != null;\n}\n\n/** True when the day carries anything at all — used for \"days with data\". */\nfunction hasAnyData(day: MybInsightsDay): boolean {\n return day.sessions > 0 || hasRecovery(day) || hasMacros(day) || day.weight != null;\n}\n\n/**\n * \"What these numbers are built on\" — the evidence behind the charts above.\n *\n * Every figure here is read straight out of the `mybInsights` payload this page\n * already holds. `daily` is zero-filled to one row per calendar day in the\n * range by the service, so a day count is exact; raw entry counts (how many\n * meals, how many measurements) are NOT in the payload and are deliberately not\n * guessed at. Each row deep-links to the page that owns the underlying log.\n */\nfunction InsightsSources({\n daily,\n summary,\n onOpen,\n}: {\n daily: MybInsightsDay[];\n summary: MybInsightsSummary;\n onOpen: (route: string) => void;\n}) {\n const t = useTr();\n const totalDays = daily.length;\n const sources = [\n {\n route: 'train',\n icon: <Dumbbell className=\"h-4 w-4\" />,\n label: t('mybodyshield.pages.insights.training', 'Training'),\n value: summary.totalSessions,\n unit:\n summary.totalSessions === 1\n ? t('mybodyshield.pages.insights.trainingUnitSingular', 'completed session')\n : t('mybodyshield.pages.insights.trainingUnitPlural', 'completed sessions'),\n detail: t('mybodyshield.pages.insights.trainingDetail', 'on {{active}} of {{total}} days', {\n active: formatNumber(daily.filter((d) => d.sessions > 0).length),\n total: formatNumber(totalDays),\n }),\n },\n {\n route: 'recovery',\n icon: <HeartPulse className=\"h-4 w-4\" />,\n label: t('mybodyshield.pages.insights.recovery', 'Recovery'),\n value: daily.filter(hasRecovery).length,\n unit: t('mybodyshield.pages.insights.recoveryUnit', 'days logged'),\n detail: t('mybodyshield.pages.insights.recoveryDetail', 'readiness or sleep entries'),\n },\n {\n route: 'nutrition',\n icon: <Utensils className=\"h-4 w-4\" />,\n label: t('mybodyshield.pages.insights.nutrition', 'Nutrition'),\n value: daily.filter(hasMacros).length,\n unit: t('mybodyshield.pages.insights.nutritionUnit', 'days logged'),\n detail: t('mybodyshield.pages.insights.nutritionDetail', 'days with macros logged'),\n },\n {\n route: 'progress',\n icon: <Scale className=\"h-4 w-4\" />,\n label: t('mybodyshield.pages.insights.bodyWeight', 'Body weight'),\n value: daily.filter((d) => d.weight != null).length,\n unit: t('mybodyshield.pages.insights.bodyWeightUnit', 'days logged'),\n detail: t('mybodyshield.pages.insights.bodyWeightDetail', 'weight readings only'),\n },\n ];\n\n return (\n <Section\n title={t('mybodyshield.pages.insights.sourcesTitle', 'What these numbers are built on')}\n description={t(\n 'mybodyshield.pages.insights.sourcesDescription',\n 'The logs feeding the charts above. Gaps here are why a trend can look flat.'\n )}\n >\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4\">\n {sources.map((source) => (\n <GlassCard key={source.route} treatment=\"plain\" className=\"flex flex-col gap-3 p-5\">\n <div className=\"flex items-center justify-between gap-2\">\n <div className=\"flex min-w-0 items-center gap-2 text-text-secondary\">\n {source.icon}\n <span className=\"truncate text-sm font-medium text-text-primary\">\n {source.label}\n </span>\n </div>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => onOpen(source.route)}>\n {t('mybodyshield.pages.insights.viewButton', 'View')}\n </Button>\n </div>\n <div>\n <p className=\"text-2xl font-bold tabular-nums text-text-primary\">\n {formatNumber(source.value)}\n </p>\n <p className=\"text-xs text-text-secondary\">{source.unit}</p>\n <p className=\"mt-1 text-xs text-text-tertiary\">{source.detail}</p>\n </div>\n </GlassCard>\n ))}\n </div>\n </Section>\n );\n}\n\nfunction useInsightsRange(days: RangeDays) {\n return useMemo(() => {\n const end = new Date();\n const start = new Date();\n start.setDate(end.getDate() - days + 1);\n return {\n dateRangeStart: toISODate(start),\n dateRangeEnd: toISODate(end),\n };\n }, [days]);\n}\n\nexport function InsightsPage() {\n const t = useTr();\n const { workspaceId, navigateTo } = useMyBodyShield();\n const [days, setDays] = useState<RangeDays>(30);\n const range = useInsightsRange(days);\n const insights = useMybInsights(range);\n const analytics = useMyBodyShieldAnalytics();\n\n // Emit a page-view event once on mount.\n useEffect(() => {\n analytics.emitPageView('insights', { workspaceId });\n }, [analytics, workspaceId]);\n\n if (insights.isLoading) {\n return (\n <PageLayout title={t('mybodyshield.pages.insights.title', 'Insights')}>\n <LoadingView label={t('mybodyshield.pages.insights.loading', 'Loading insights…')} />\n </PageLayout>\n );\n }\n\n if (insights.isError) {\n return (\n <PageLayout title={t('mybodyshield.pages.insights.title', 'Insights')}>\n <ErrorView\n title={t('mybodyshield.pages.insights.errorTitle', 'Could not load insights')}\n message={\n insights.error instanceof Error\n ? insights.error.message\n : t('mybodyshield.common.unknownError', 'Unknown error')\n }\n onRetry={() => insights.refetch()}\n />\n </PageLayout>\n );\n }\n\n const data = insights.data;\n if (!data) {\n return (\n <PageLayout title={t('mybodyshield.pages.insights.title', 'Insights')}>\n <ErrorView\n title={t('mybodyshield.pages.insights.errorTitle', 'Could not load insights')}\n message={t('mybodyshield.pages.insights.error', 'No insight data was returned.')}\n onRetry={() => insights.refetch()}\n />\n </PageLayout>\n );\n }\n\n const { summary, daily, goals } = data;\n\n // `daily` carries one row per calendar day in the range, zero-filled by the\n // service for days with nothing logged — so `daily.length` is the range\n // length, not a measure of tracking. Count the days that actually carry data.\n const daysWithData = daily.filter(hasAnyData).length;\n\n const dayColumns: Column<MybInsightsDay>[] = [\n {\n key: 'date',\n header: t('mybodyshield.pages.insights.dateColumn', 'Date'),\n render: (r) => <span className=\"text-text-primary\">{formatDate(r.date)}</span>,\n },\n {\n key: 'sessions',\n header: t('mybodyshield.pages.insights.sessionsColumn', 'Sessions'),\n align: 'right',\n render: (r) => <span className=\"tabular-nums text-text-secondary\">{r.sessions}</span>,\n },\n {\n key: 'volumeKg',\n header: t('mybodyshield.pages.insights.volumeColumn', 'Volume'),\n align: 'right',\n render: (r) => (\n <span className=\"tabular-nums text-text-secondary\">{formatWeight(r.volumeKg)}</span>\n ),\n },\n {\n key: 'readiness',\n header: t('mybodyshield.pages.insights.readinessColumn', 'Readiness'),\n align: 'right',\n render: (r) => (\n <span className=\"tabular-nums text-text-secondary\">\n {r.readiness != null ? formatNumber(r.readiness) : '—'}\n </span>\n ),\n },\n {\n key: 'sleepHours',\n header: t('mybodyshield.pages.insights.sleepColumn', 'Sleep'),\n align: 'right',\n render: (r) => (\n <span className=\"tabular-nums text-text-secondary\">\n {r.sleepHours != null ? `${formatNumber(r.sleepHours)} h` : '—'}\n </span>\n ),\n },\n {\n key: 'calories',\n header: t('mybodyshield.pages.insights.caloriesColumn', 'Calories'),\n align: 'right',\n render: (r) => (\n <span className=\"tabular-nums text-text-secondary\">{formatKcal(r.calories)}</span>\n ),\n },\n {\n key: 'proteinG',\n header: t('mybodyshield.pages.insights.proteinColumn', 'Protein'),\n align: 'right',\n render: (r) => (\n <span className=\"tabular-nums text-text-secondary\">{formatGrams(r.proteinG)}</span>\n ),\n },\n {\n key: 'weight',\n header: t('mybodyshield.pages.insights.weightColumn', 'Weight'),\n align: 'right',\n render: (r) => (\n <span className=\"tabular-nums text-text-secondary\">\n {r.weight != null ? formatWeight(r.weight) : '—'}\n </span>\n ),\n },\n ];\n\n const goalColumns: Column<MybGoal>[] = [\n {\n key: 'title',\n header: t('mybodyshield.pages.insights.goalColumn', 'Goal'),\n render: (r) => (\n <div>\n <span className=\"block text-sm text-text-primary\">{r.title}</span>\n <span className=\"text-xs text-text-tertiary\">{formatEnumLabel(r.type)}</span>\n </div>\n ),\n },\n {\n key: 'status',\n header: t('mybodyshield.pages.insights.statusColumn', 'Status'),\n render: (r) => (\n <span\n className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${\n r.status === 'ACHIEVED'\n ? 'bg-status-success-bg text-status-success-text'\n : 'bg-status-warning-bg text-status-warning-text'\n }`}\n >\n {formatEnumLabel(r.status)}\n </span>\n ),\n },\n {\n key: 'progress',\n header: t('mybodyshield.pages.insights.progressColumn', 'Progress'),\n align: 'right',\n render: (r) => (\n <span className=\"tabular-nums text-text-secondary\">\n {formatNumber(r.currentValue)} / {formatNumber(r.targetValue)} {r.unit}\n </span>\n ),\n },\n ];\n\n return (\n <PageLayout\n title={t('mybodyshield.pages.insights.title', 'Insights')}\n description={t('mybodyshield.pages.insights.description', '{{start}} – {{end}}', {\n start: formatDate(data.dateRangeStart),\n end: formatDate(data.dateRangeEnd),\n })}\n >\n {/* Range selector */}\n <div className=\"flex flex-wrap items-center gap-2\">\n {RANGE_OPTIONS.map((opt) => (\n <button\n key={opt.days}\n type=\"button\"\n onClick={() => setDays(opt.days)}\n className={`rounded-lg px-3 py-1.5 text-sm font-medium transition-colors ${\n days === opt.days\n ? 'bg-accent text-action-primary-text'\n : 'bg-bg-sunken text-text-secondary hover:bg-bg-elevated'\n }`}\n >\n {t(`mybodyshield.pages.insights.${opt.labelKey}`, opt.fallback)}\n </button>\n ))}\n </div>\n\n {/* Summary KPIs */}\n <Section title={t('mybodyshield.pages.insights.summary', 'Summary')}>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n <MetricsCard\n icon={<Flame className=\"h-5 w-5\" />}\n value={summary.consistencyStreakDays}\n label={t('mybodyshield.pages.insights.consistencyStreak', 'Consistency streak')}\n />\n <MetricsCard\n icon={<Dumbbell className=\"h-5 w-5\" />}\n value={summary.totalSessions}\n label={t('mybodyshield.pages.insights.totalSessions', 'Total sessions')}\n />\n <MetricsCard\n icon={<TrendingUp className=\"h-5 w-5\" />}\n value={formatWeight(summary.totalVolumeKg)}\n label={t('mybodyshield.pages.insights.totalVolume', 'Total volume')}\n />\n <MetricsCard\n icon={<HeartPulse className=\"h-5 w-5\" />}\n value={formatNumber(summary.averageReadiness)}\n label={t('mybodyshield.pages.insights.averageReadiness', 'Average readiness')}\n />\n <MetricsCard\n icon={<Utensils className=\"h-5 w-5\" />}\n value={formatPercent(summary.macroAdherencePercent)}\n label={t('mybodyshield.pages.insights.macroAdherence', 'Macro adherence')}\n />\n <MetricsCard\n icon={<Calendar className=\"h-5 w-5\" />}\n value={daysWithData}\n label={t('mybodyshield.pages.insights.daysWithData', 'Days with data')}\n footer={\n <p className=\"text-xs text-text-tertiary\">\n {t('mybodyshield.pages.insights.ofDaysInRange', 'of {{count}} days in this range', {\n count: daily.length,\n })}\n </p>\n }\n />\n </div>\n </Section>\n\n {/* Evidence behind the numbers */}\n <InsightsSources daily={daily} summary={summary} onOpen={navigateTo} />\n\n {/* Trends */}\n <Section\n title={t('mybodyshield.pages.insights.trends', 'Trends')}\n description={t(\n 'mybodyshield.pages.insights.trendsDescription',\n 'How training, recovery and nutrition move together.'\n )}\n >\n <div className=\"grid grid-cols-1 gap-6 xl:grid-cols-2\">\n <VolumeChart daily={daily} />\n <ReadinessSleepChart daily={daily} />\n <MacroChart daily={daily} />\n <CalorieChart daily={daily} />\n </div>\n </Section>\n\n {/* Correlations */}\n <CorrelationView daily={daily} />\n\n {/* Daily breakdown */}\n <Section\n title={t('mybodyshield.pages.insights.dailyBreakdown', 'Daily breakdown')}\n description={t(\n 'mybodyshield.pages.insights.dailyBreakdownDescription',\n 'Per-day training, recovery and nutrition.'\n )}\n >\n <GlassCard treatment=\"plain\" className=\"overflow-hidden p-0\">\n <DataTable\n columns={dayColumns}\n rows={daily}\n rowKey={(r) => r.date}\n defaultSort={{ key: 'date', dir: 'desc' }}\n />\n </GlassCard>\n </Section>\n\n {/* Active goals */}\n <Section\n title={t('mybodyshield.pages.insights.activeGoals', 'Active goals')}\n description={t(\n 'mybodyshield.pages.insights.activeGoalsDescription',\n 'Goals open during this period.'\n )}\n >\n {goals.length === 0 ? (\n <GlassCard treatment=\"plain\" className=\"p-8 text-center\">\n <Target className=\"mx-auto h-8 w-8 text-text-tertiary\" />\n <p className=\"mt-3 text-sm font-medium text-text-primary\">\n {t('mybodyshield.pages.insights.noActiveGoals', 'No active goals')}\n </p>\n <p className=\"mt-1 text-xs text-text-tertiary\">\n {t(\n 'mybodyshield.pages.insights.noActiveGoalsDescription',\n 'Create a goal to see progress here.'\n )}\n </p>\n </GlassCard>\n ) : (\n <GlassCard treatment=\"plain\" className=\"overflow-hidden p-0\">\n <DataTable\n columns={goalColumns}\n rows={goals}\n rowKey={(r) => r.id}\n defaultSort={{ key: 'status', dir: 'asc' }}\n />\n </GlassCard>\n )}\n </Section>\n\n {/* Legend */}\n <div className=\"flex flex-wrap gap-4 text-xs text-text-tertiary\">\n <span className=\"inline-flex items-center gap-1.5\">\n <Dumbbell className=\"h-3.5 w-3.5\" />\n {t(\n 'mybodyshield.pages.insights.legendTraining',\n 'Training data from completed sessions.'\n )}\n </span>\n <span className=\"inline-flex items-center gap-1.5\">\n <Moon className=\"h-3.5 w-3.5\" />\n {t('mybodyshield.pages.insights.legendRecovery', 'Recovery data from recovery logs.')}\n </span>\n <span className=\"inline-flex items-center gap-1.5\">\n <Scale className=\"h-3.5 w-3.5\" />\n {t('mybodyshield.pages.insights.legendBody', 'Body metrics from progress logs.')}\n </span>\n <span className=\"inline-flex items-center gap-1.5\">\n <BarChart3 className=\"h-3.5 w-3.5\" />\n {t('mybodyshield.pages.insights.legendNutrition', 'Nutrition data from logged meals.')}\n </span>\n </div>\n\n <p className=\"mt-4 border-t border-border-seam pt-4 text-xs leading-relaxed text-text-tertiary\">\n {t(\n 'mybodyshield.pages.insights.medicalDisclaimer',\n 'General fitness information, not medical advice. MyBodyShield is not a medical device and does not diagnose, treat, cure or prevent any condition — talk to a qualified healthcare professional before changing how you train or eat.'\n )}\n </p>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MindfulnessPage.d.ts","sourceRoot":"","sources":["../../../../src/mybodyshield/pages/recovery/MindfulnessPage.tsx"],"names":[],"mappings":"AA6BA,wBAAgB,eAAe,gCAmL9B"}
|