@burdenoff/microfe-atheletebridge 2026.1024.1 → 2026.1031.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.
Files changed (27) hide show
  1. package/dist/atheletebridge/components/ai/DecisionCard.d.ts.map +1 -1
  2. package/dist/atheletebridge/components/ai/DecisionCard.js +217 -122
  3. package/dist/atheletebridge/components/ai/DecisionCard.js.map +1 -1
  4. package/dist/atheletebridge/components/ai/SubjectDecisions.d.ts +29 -0
  5. package/dist/atheletebridge/components/ai/SubjectDecisions.d.ts.map +1 -0
  6. package/dist/atheletebridge/components/ai/SubjectDecisions.js +69 -0
  7. package/dist/atheletebridge/components/ai/SubjectDecisions.js.map +1 -0
  8. package/dist/atheletebridge/components/ai/index.d.ts +2 -0
  9. package/dist/atheletebridge/components/ai/index.d.ts.map +1 -1
  10. package/dist/atheletebridge/hooks/useAthleteBridgeApi.d.ts +10 -1
  11. package/dist/atheletebridge/hooks/useAthleteBridgeApi.d.ts.map +1 -1
  12. package/dist/atheletebridge/hooks/useAthleteBridgeApi.js +24 -15
  13. package/dist/atheletebridge/hooks/useAthleteBridgeApi.js.map +1 -1
  14. package/dist/atheletebridge/pages/athletes/AthleteProfilePage.d.ts.map +1 -1
  15. package/dist/atheletebridge/pages/athletes/AthleteProfilePage.js +135 -118
  16. package/dist/atheletebridge/pages/athletes/AthleteProfilePage.js.map +1 -1
  17. package/dist/atheletebridge/pages/programs/ProgramDetailPage.d.ts.map +1 -1
  18. package/dist/atheletebridge/pages/programs/ProgramDetailPage.js +42 -34
  19. package/dist/atheletebridge/pages/programs/ProgramDetailPage.js.map +1 -1
  20. package/dist/atheletebridge/pages/teams/TeamDetailPage.d.ts.map +1 -1
  21. package/dist/atheletebridge/pages/teams/TeamDetailPage.js +65 -57
  22. package/dist/atheletebridge/pages/teams/TeamDetailPage.js.map +1 -1
  23. package/dist/atheletebridge/utils/aiDecision.d.ts +12 -0
  24. package/dist/atheletebridge/utils/aiDecision.d.ts.map +1 -1
  25. package/dist/atheletebridge/utils/aiDecision.js +11 -1
  26. package/dist/atheletebridge/utils/aiDecision.js.map +1 -1
  27. package/package.json +5 -4
@@ -1 +1 @@
1
- {"version":3,"file":"AthleteProfilePage.js","names":[],"sources":["../../../../src/atheletebridge/pages/athletes/AthleteProfilePage.tsx"],"sourcesContent":["/**\n * Athlete Profile\n * @module atheletebridge/pages/athletes\n *\n * A consolidated single-athlete view composed PURELY from real data via\n * `useAthleteProfile(teamId, memberId)` — the resolved team member (identity +\n * physical stats), their performance metrics (trend charts + improvement\n * tiles), workout history and coaching sessions (with read-only ratings).\n *\n * Honesty rules: there is no photo, age/DOB, or \"fitness score\" field on the\n * schema, so this screen renders an initials/jersey monogram (never a\n * fabricated headshot) and OMITS any composite wellness score. Every number\n * traces to a real row. Semantic tokens only; fully responsive.\n */\n\nimport { useMemo, useState } from 'react';\nimport { useParams } from 'react-router';\nimport {\n ArrowLeft,\n CalendarClock,\n Dumbbell,\n Gauge,\n LineChart as LineChartIcon,\n Ruler,\n Star,\n Users,\n Weight,\n} from 'lucide-react';\nimport {\n Badge,\n GlassCard,\n GradientText,\n Tabs,\n TabsList,\n TabsTrigger,\n TabsContent,\n ResponsiveTable,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { SessionCard } from '../../components/SessionCard';\nimport { Crest } from '../../components/Crest';\nimport { UserAvatar } from '../../components/UserAvatar';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { LineChart, StatCard, RatingStars } from '../../components/viz';\nimport { MEMBER_ROLE_LABELS, METRIC_TYPE_LABELS, SPORT_LABELS } from '../../components/domain';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport { useAthleteProfile, useTeam } from '../../hooks/useAthleteBridgeApi';\nimport { formatDate, formatDuration } from '../../utils/formatters';\nimport type { AthMetricType, AthPerformanceMetric, AthWorkoutLog } from '../../types';\n\n// ── Trend range chips ────────────────────────────────────────────────────────\n\ntype RangeKey = '1M' | '3M' | '6M' | 'YTD' | '1Y' | 'ALL';\n\nconst RANGE_ORDER: RangeKey[] = ['1M', '3M', '6M', 'YTD', '1Y', 'ALL'];\nconst RANGE_LABELS: Record<RangeKey, string> = {\n '1M': '1M',\n '3M': '3M',\n '6M': '6M',\n YTD: 'YTD',\n '1Y': '1Y',\n ALL: 'All',\n};\n\n/** Lower bound (ms epoch) for a given range, or 0 for \"all time\". */\nfunction rangeStart(range: RangeKey): number {\n const now = Date.now();\n const DAY = 86_400_000;\n switch (range) {\n case '1M':\n return now - 30 * DAY;\n case '3M':\n return now - 90 * DAY;\n case '6M':\n return now - 180 * DAY;\n case '1Y':\n return now - 365 * DAY;\n case 'YTD':\n return new Date(new Date().getFullYear(), 0, 1).getTime();\n case 'ALL':\n return 0;\n }\n}\n\nconst shortDate = (iso: string): string =>\n new Date(iso).toLocaleDateString(undefined, { month: 'short', day: 'numeric' });\n\ninterface MetricGroup {\n type: AthMetricType;\n /** Ascending by recordedAt. */\n points: AthPerformanceMetric[];\n unit: string | null;\n first: number;\n last: number;\n peak: number;\n deltaPct: number | null;\n}\n\n/** Group metrics by type (ascending in time) + derive first/last/peak/delta. */\nfunction buildMetricGroups(metrics: AthPerformanceMetric[], start: number): MetricGroup[] {\n const groups = new Map<AthMetricType, AthPerformanceMetric[]>();\n for (const m of metrics) {\n const at = new Date(m.recordedAt).getTime();\n if (Number.isNaN(at) || at < start) {\n continue;\n }\n const list = groups.get(m.type) ?? [];\n list.push(m);\n groups.set(m.type, list);\n }\n return Array.from(groups.entries())\n .map(([type, list]) => {\n const points = [...list].sort(\n (a, b) => new Date(a.recordedAt).getTime() - new Date(b.recordedAt).getTime()\n );\n const first = points[0]?.value ?? 0;\n const last = points[points.length - 1]?.value ?? 0;\n const peak = points.reduce((acc, p) => Math.max(acc, p.value), Number.NEGATIVE_INFINITY);\n const deltaPct =\n points.length >= 2 && first !== 0 ? ((last - first) / Math.abs(first)) * 100 : null;\n return { type, points, unit: points[0]?.unit ?? null, first, last, peak, deltaPct };\n })\n .sort((a, b) => b.points.length - a.points.length);\n}\n\nexport function AthleteProfilePage() {\n const { teamId, memberId } = useParams<{ teamId: string; memberId: string }>();\n const { navigateTo } = useAthleteBridge();\n\n const profile = useAthleteProfile(teamId, memberId);\n const team = useTeam(teamId);\n const [range, setRange] = useState<RangeKey>('6M');\n\n const start = useMemo(() => rangeStart(range), [range]);\n const metricGroups = useMemo(\n () => buildMetricGroups(profile.metrics, start),\n [profile.metrics, start]\n );\n const allTimeGroups = useMemo(() => buildMetricGroups(profile.metrics, 0), [profile.metrics]);\n\n const ratedSessions = useMemo(\n () => profile.sessions.filter((s) => s.rating !== null),\n [profile.sessions]\n );\n const avgRating = useMemo(\n () =>\n ratedSessions.length > 0\n ? ratedSessions.reduce((acc, s) => acc + (s.rating ?? 0), 0) / ratedSessions.length\n : null,\n [ratedSessions]\n );\n\n const upcomingSessions = useMemo(\n () =>\n profile.sessions\n .filter((s) => s.status === 'REQUESTED' || s.status === 'CONFIRMED')\n .sort((a, b) => new Date(a.scheduledAt).getTime() - new Date(b.scheduledAt).getTime()),\n [profile.sessions]\n );\n\n const recentWorkouts = useMemo(\n () =>\n [...profile.workouts].sort(\n (a, b) => new Date(b.performedAt).getTime() - new Date(a.performedAt).getTime()\n ),\n [profile.workouts]\n );\n\n const completedSessions = useMemo(\n () => profile.sessions.filter((s) => s.status === 'COMPLETED').length,\n [profile.sessions]\n );\n\n if (profile.isLoading) {\n return (\n <PageLayout title=\"Athlete\">\n <LoadingState label=\"Loading athlete profile…\" />\n </PageLayout>\n );\n }\n\n if (profile.isError || !profile.member) {\n return (\n <PageLayout title=\"Athlete\">\n <ErrorState\n title=\"Athlete not found\"\n message=\"This athlete is not on the roster, or you do not have access.\"\n onRetry={() => (teamId ? navigateTo(`/teams/${teamId}`) : navigateTo('/teams'))}\n />\n </PageLayout>\n );\n }\n\n const member = profile.member;\n const teamName = team.data?.name ?? null;\n const teamSport = team.data?.sport;\n\n const physicalStats: Array<{ icon: React.ReactNode; label: string; value: string }> = [];\n if (member.height != null) {\n physicalStats.push({\n icon: <Ruler className=\"h-4 w-4\" />,\n label: 'Height',\n value: String(member.height),\n });\n }\n if (member.weight != null) {\n physicalStats.push({\n icon: <Weight className=\"h-4 w-4\" />,\n label: 'Weight',\n value: String(member.weight),\n });\n }\n if (member.dominantSide) {\n physicalStats.push({\n icon: <Gauge className=\"h-4 w-4\" />,\n label: 'Dominant side',\n value: member.dominantSide,\n });\n }\n if (member.position) {\n physicalStats.push({\n icon: <Users className=\"h-4 w-4\" />,\n label: 'Position',\n value: member.position,\n });\n }\n if (member.joinedAt) {\n physicalStats.push({\n icon: <CalendarClock className=\"h-4 w-4\" />,\n label: 'Joined',\n value: formatDate(member.joinedAt),\n });\n }\n\n const primaryGroup = metricGroups[0] ?? null;\n\n const workoutColumns: ResponsiveTableColumn<AthWorkoutLog>[] = [\n {\n key: 'title',\n header: 'Workout',\n priority: 'primary',\n cell: (row) => <span className=\"font-medium text-text-primary\">{row.title}</span>,\n },\n {\n key: 'duration',\n header: 'Duration',\n priority: 'secondary',\n cell: (row) => (\n <span className=\"tabular-nums text-text-secondary\">\n {row.durationMin != null ? formatDuration(row.durationMin) : '—'}\n </span>\n ),\n },\n {\n key: 'load',\n header: 'Load',\n priority: 'hidden-on-mobile',\n cell: (row) => (\n <span className=\"tabular-nums text-text-secondary\">\n {row.totalLoad != null ? row.totalLoad.toLocaleString() : '—'}\n </span>\n ),\n },\n {\n key: 'rpe',\n header: 'RPE',\n priority: 'hidden-on-mobile',\n cell: (row) => (\n <span className=\"tabular-nums text-text-secondary\">{row.rpe != null ? row.rpe : '—'}</span>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n priority: 'secondary',\n cell: (row) => <StatusBadge status={row.status} />,\n },\n {\n key: 'date',\n header: 'Date',\n priority: 'secondary',\n cell: (row) => (\n <span className=\"whitespace-nowrap text-text-secondary\">{formatDate(row.performedAt)}</span>\n ),\n },\n ];\n\n return (\n <PageLayout\n title={<GradientText>{member.displayName}</GradientText>}\n description={\n teamName ? `${teamName}${teamSport ? ` · ${SPORT_LABELS[teamSport]}` : ''}` : undefined\n }\n actions={\n <button\n type=\"button\"\n onClick={() => (teamId ? navigateTo(`/teams/${teamId}`) : navigateTo('/teams'))}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back to team\n </button>\n }\n >\n {/* Identity header */}\n <GlassCard treatment=\"insight\" className=\"mb-8 p-6\">\n <div className=\"flex flex-col gap-6 md:flex-row md:items-start\">\n <UserAvatar\n name={member.displayName}\n id={member.userId}\n jersey={member.jerseyNumber}\n size=\"xl\"\n ring\n className=\"shrink-0\"\n />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <Badge variant=\"default\">{MEMBER_ROLE_LABELS[member.role]}</Badge>\n {member.position ? <Badge variant=\"secondary\">{member.position}</Badge> : null}\n {member.jerseyNumber != null ? (\n <Badge variant=\"outline\" className=\"tabular-nums\">\n #{member.jerseyNumber}\n </Badge>\n ) : null}\n {teamName ? (\n <Badge variant=\"secondary\" className=\"gap-1.5\">\n <Crest name={teamName} sport={teamSport} size={16} showSportGlyph={false} />\n {teamName}\n </Badge>\n ) : null}\n <StatusBadge status={member.status} />\n </div>\n\n {physicalStats.length > 0 ? (\n <dl className=\"mt-5 grid grid-cols-2 gap-x-6 gap-y-4 sm:grid-cols-3 lg:grid-cols-5\">\n {physicalStats.map((stat) => (\n <div key={stat.label} className=\"min-w-0\">\n <dt className=\"flex items-center gap-1.5 text-xs font-medium text-text-secondary\">\n <span className=\"text-text-muted\">{stat.icon}</span>\n {stat.label}\n </dt>\n <dd className=\"mt-1 truncate text-lg font-semibold text-text-primary\">\n {stat.value}\n </dd>\n </div>\n ))}\n </dl>\n ) : null}\n </div>\n </div>\n </GlassCard>\n\n <Tabs defaultValue=\"overview\">\n <TabsList className=\"mb-6 flex-wrap\">\n <TabsTrigger value=\"overview\">Overview</TabsTrigger>\n <TabsTrigger value=\"metrics\">Metrics</TabsTrigger>\n <TabsTrigger value=\"workouts\">Workouts</TabsTrigger>\n <TabsTrigger value=\"sessions\">Sessions</TabsTrigger>\n </TabsList>\n\n {/* ── Overview ─────────────────────────────────────────────────────── */}\n <TabsContent value=\"overview\" className=\"space-y-8\">\n {/* KPI / improvement tiles */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <GlassCard treatment=\"metric\" className=\"flex flex-col gap-2 p-5\">\n <span className=\"text-sm font-medium text-text-secondary\">Avg session rating</span>\n {avgRating != null ? (\n <>\n <RatingStars\n value={avgRating}\n max={5}\n size={20}\n showValue\n count={ratedSessions.length}\n />\n </>\n ) : (\n <span className=\"text-sm text-text-muted\">No rated sessions yet</span>\n )}\n </GlassCard>\n <StatCard\n label=\"Sessions\"\n value={profile.sessions.length}\n icon={<CalendarClock className=\"h-4 w-4\" />}\n />\n <StatCard\n label=\"Completed\"\n value={completedSessions}\n icon={<Star className=\"h-4 w-4\" />}\n />\n <StatCard\n label=\"Workouts logged\"\n value={profile.workouts.length}\n icon={<Dumbbell className=\"h-4 w-4\" />}\n />\n </div>\n\n {/* Improvement tiles from real metric series */}\n {allTimeGroups.some((g) => g.deltaPct != null) ? (\n <section>\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">Improvement</h2>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n {allTimeGroups\n .filter((g) => g.deltaPct != null)\n .slice(0, 4)\n .map((g) => (\n <StatCard\n key={g.type}\n label={`${METRIC_TYPE_LABELS[g.type]}${g.unit ? ` (${g.unit})` : ''}`}\n value={g.last.toLocaleString()}\n delta={g.deltaPct ?? undefined}\n deltaLabel=\"first → latest\"\n sparkline={g.points.map((p) => p.value)}\n />\n ))}\n </div>\n </section>\n ) : null}\n\n {/* Performance trend */}\n <TrendCard\n title=\"Performance trend\"\n groups={metricGroups}\n primary={primaryGroup}\n range={range}\n onRangeChange={setRange}\n />\n\n {/* Upcoming sessions */}\n <section>\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">\n Upcoming sessions ({upcomingSessions.length})\n </h2>\n {upcomingSessions.length === 0 ? (\n <EmptyState\n illustration=\"empty-calendar\"\n icon={<CalendarClock className=\"h-10 w-10\" />}\n title=\"No upcoming sessions\"\n description=\"Confirmed and requested coaching sessions for this athlete appear here.\"\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n {upcomingSessions.slice(0, 6).map((s) => (\n <SessionCard\n key={s.id}\n session={s}\n athleteName={member.displayName}\n onClick={() => navigateTo(`/sessions/${s.id}`)}\n />\n ))}\n </div>\n )}\n </section>\n </TabsContent>\n\n {/* ── Metrics ──────────────────────────────────────────────────────── */}\n <TabsContent value=\"metrics\" className=\"space-y-8\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Performance metrics</h2>\n <RangeChips value={range} onChange={setRange} />\n </div>\n {metricGroups.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<LineChartIcon className=\"h-10 w-10\" />}\n title=\"No metrics in this range\"\n description=\"Record metrics for this athlete — speed, strength, endurance — to chart progress over time.\"\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-2\">\n {metricGroups.map((g) => (\n <GlassCard key={g.type} treatment=\"metric\" className=\"p-5\">\n <div className=\"mb-3 flex items-start justify-between gap-3\">\n <div>\n <h3 className=\"text-sm font-semibold uppercase tracking-wide text-text-secondary\">\n {METRIC_TYPE_LABELS[g.type]}\n </h3>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums text-text-primary\">\n {g.last.toLocaleString()}\n {g.unit ? (\n <span className=\"ml-1 text-sm text-text-secondary\">{g.unit}</span>\n ) : null}\n </p>\n </div>\n {g.deltaPct != null ? (\n <Badge\n variant={g.deltaPct >= 0 ? 'default' : 'destructive'}\n className=\"tabular-nums\"\n >\n {g.deltaPct >= 0 ? '+' : ''}\n {g.deltaPct.toFixed(1)}%\n </Badge>\n ) : null}\n </div>\n <LineChart\n series={g.points.map((p) => ({\n label: shortDate(p.recordedAt),\n value: p.value,\n }))}\n seriesLabel={METRIC_TYPE_LABELS[g.type]}\n height={160}\n valueFormatter={(v) => `${v.toLocaleString()}${g.unit ? ` ${g.unit}` : ''}`}\n />\n <p className=\"mt-2 text-xs text-text-muted tabular-nums\">\n Peak {g.peak.toLocaleString()}\n {g.unit ? ` ${g.unit}` : ''} · {g.points.length} readings\n </p>\n </GlassCard>\n ))}\n </div>\n )}\n </TabsContent>\n\n {/* ── Workouts ─────────────────────────────────────────────────────── */}\n <TabsContent value=\"workouts\">\n {recentWorkouts.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Dumbbell className=\"h-10 w-10\" />}\n title=\"No workouts logged\"\n description=\"This athlete's logged training sessions will appear here.\"\n />\n ) : (\n <ResponsiveTable\n columns={workoutColumns}\n data={recentWorkouts}\n rowKey={(row) => row.id}\n />\n )}\n </TabsContent>\n\n {/* ── Sessions ─────────────────────────────────────────────────────── */}\n <TabsContent value=\"sessions\">\n {profile.sessions.length === 0 ? (\n <EmptyState\n illustration=\"empty-calendar\"\n icon={<CalendarClock className=\"h-10 w-10\" />}\n title=\"No coaching sessions\"\n description=\"Booked, completed and cancelled sessions for this athlete will appear here.\"\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n {[...profile.sessions]\n .sort(\n (a, b) => new Date(b.scheduledAt).getTime() - new Date(a.scheduledAt).getTime()\n )\n .map((s) => (\n <SessionCard\n key={s.id}\n session={s}\n athleteName={member.displayName}\n onClick={() => navigateTo(`/sessions/${s.id}`)}\n />\n ))}\n </div>\n )}\n </TabsContent>\n </Tabs>\n </PageLayout>\n );\n}\n\n// ── Range chips ──────────────────────────────────────────────────────────────\n\ninterface RangeChipsProps {\n value: RangeKey;\n onChange: (value: RangeKey) => void;\n}\n\nfunction RangeChips({ value, onChange }: RangeChipsProps) {\n return (\n <div\n role=\"group\"\n aria-label=\"Trend range\"\n className=\"inline-flex items-center gap-1 rounded-lg border border-border-seam bg-bg-surface p-1\"\n >\n {RANGE_ORDER.map((key) => {\n const active = key === value;\n return (\n <button\n key={key}\n type=\"button\"\n aria-pressed={active}\n onClick={() => onChange(key)}\n className={\n active\n ? 'rounded-md bg-action-primary-bg px-2.5 py-1 text-xs font-medium text-action-primary-text'\n : 'rounded-md px-2.5 py-1 text-xs font-medium text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary'\n }\n >\n {RANGE_LABELS[key]}\n </button>\n );\n })}\n </div>\n );\n}\n\n// ── Trend card ───────────────────────────────────────────────────────────────\n\ninterface TrendCardProps {\n title: string;\n groups: MetricGroup[];\n primary: MetricGroup | null;\n range: RangeKey;\n onRangeChange: (value: RangeKey) => void;\n}\n\nfunction TrendCard({ title, groups, primary, range, onRangeChange }: TrendCardProps) {\n // Overlay a secondary metric series (aligned by index) when available, so the\n // trend reads like the mock's dual-line without fabricating a shared scale.\n const secondary = groups.find((g) => g.type !== primary?.type) ?? null;\n\n return (\n <GlassCard treatment=\"metric\" className=\"p-5\">\n <div className=\"mb-4 flex flex-wrap items-center justify-between gap-3\">\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">{title}</h2>\n {primary ? (\n <div className=\"mt-1 flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-text-secondary\">\n <span className=\"inline-flex items-center gap-1.5\">\n <span\n className=\"h-2 w-2 rounded-full bg-[var(--color-accent-primary)]\"\n aria-hidden=\"true\"\n />\n {METRIC_TYPE_LABELS[primary.type]}\n {primary.unit ? ` (${primary.unit})` : ''}\n </span>\n {secondary ? (\n <span className=\"inline-flex items-center gap-1.5\">\n <span\n className=\"h-2 w-2 rounded-full bg-[var(--color-accent-teal)]\"\n aria-hidden=\"true\"\n />\n {METRIC_TYPE_LABELS[secondary.type]}\n {secondary.unit ? ` (${secondary.unit})` : ''}\n </span>\n ) : null}\n </div>\n ) : null}\n </div>\n <RangeChips value={range} onChange={onRangeChange} />\n </div>\n {primary ? (\n <LineChart\n series={primary.points.map((p) => ({ label: shortDate(p.recordedAt), value: p.value }))}\n compare={secondary ? secondary.points.map((p) => p.value) : undefined}\n seriesLabel={METRIC_TYPE_LABELS[primary.type]}\n compareLabel={secondary ? METRIC_TYPE_LABELS[secondary.type] : undefined}\n height={240}\n />\n ) : (\n <div className=\"flex h-40 items-center justify-center text-sm text-text-muted\">\n No metric readings in this range.\n </div>\n )}\n </GlassCard>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AthleteProfilePage.js","names":[],"sources":["../../../../src/atheletebridge/pages/athletes/AthleteProfilePage.tsx"],"sourcesContent":["/**\n * Athlete Profile\n * @module atheletebridge/pages/athletes\n *\n * A consolidated single-athlete view composed PURELY from real data via\n * `useAthleteProfile(teamId, memberId)` — the resolved team member (identity +\n * physical stats), their performance metrics (trend charts + improvement\n * tiles), workout history and coaching sessions (with read-only ratings).\n *\n * Honesty rules: there is no photo, age/DOB, or \"fitness score\" field on the\n * schema, so this screen renders an initials/jersey monogram (never a\n * fabricated headshot) and OMITS any composite wellness score. Every number\n * traces to a real row. Semantic tokens only; fully responsive.\n */\n\nimport { useMemo, useState } from 'react';\nimport { useParams } from 'react-router';\nimport {\n ArrowLeft,\n CalendarClock,\n Dumbbell,\n Gauge,\n LineChart as LineChartIcon,\n Ruler,\n Sparkles,\n Star,\n Users,\n Weight,\n} from 'lucide-react';\nimport {\n Badge,\n GlassCard,\n GradientText,\n Tabs,\n TabsList,\n TabsTrigger,\n TabsContent,\n ResponsiveTable,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { SessionCard } from '../../components/SessionCard';\nimport { Crest } from '../../components/Crest';\nimport { UserAvatar } from '../../components/UserAvatar';\nimport { EmptyState } from '../../components/EmptyState';\nimport { SubjectDecisions } from '../../components/ai/SubjectDecisions';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { LineChart, StatCard, RatingStars } from '../../components/viz';\nimport { MEMBER_ROLE_LABELS, METRIC_TYPE_LABELS, SPORT_LABELS } from '../../components/domain';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport { useAthleteProfile, useTeam } from '../../hooks/useAthleteBridgeApi';\nimport { formatDate, formatDuration } from '../../utils/formatters';\nimport type { AthMetricType, AthPerformanceMetric, AthWorkoutLog } from '../../types';\n\n// ── Trend range chips ────────────────────────────────────────────────────────\n\ntype RangeKey = '1M' | '3M' | '6M' | 'YTD' | '1Y' | 'ALL';\n\nconst RANGE_ORDER: RangeKey[] = ['1M', '3M', '6M', 'YTD', '1Y', 'ALL'];\nconst RANGE_LABELS: Record<RangeKey, string> = {\n '1M': '1M',\n '3M': '3M',\n '6M': '6M',\n YTD: 'YTD',\n '1Y': '1Y',\n ALL: 'All',\n};\n\n/** Lower bound (ms epoch) for a given range, or 0 for \"all time\". */\nfunction rangeStart(range: RangeKey): number {\n const now = Date.now();\n const DAY = 86_400_000;\n switch (range) {\n case '1M':\n return now - 30 * DAY;\n case '3M':\n return now - 90 * DAY;\n case '6M':\n return now - 180 * DAY;\n case '1Y':\n return now - 365 * DAY;\n case 'YTD':\n return new Date(new Date().getFullYear(), 0, 1).getTime();\n case 'ALL':\n return 0;\n }\n}\n\nconst shortDate = (iso: string): string =>\n new Date(iso).toLocaleDateString(undefined, { month: 'short', day: 'numeric' });\n\ninterface MetricGroup {\n type: AthMetricType;\n /** Ascending by recordedAt. */\n points: AthPerformanceMetric[];\n unit: string | null;\n first: number;\n last: number;\n peak: number;\n deltaPct: number | null;\n}\n\n/** Group metrics by type (ascending in time) + derive first/last/peak/delta. */\nfunction buildMetricGroups(metrics: AthPerformanceMetric[], start: number): MetricGroup[] {\n const groups = new Map<AthMetricType, AthPerformanceMetric[]>();\n for (const m of metrics) {\n const at = new Date(m.recordedAt).getTime();\n if (Number.isNaN(at) || at < start) {\n continue;\n }\n const list = groups.get(m.type) ?? [];\n list.push(m);\n groups.set(m.type, list);\n }\n return Array.from(groups.entries())\n .map(([type, list]) => {\n const points = [...list].sort(\n (a, b) => new Date(a.recordedAt).getTime() - new Date(b.recordedAt).getTime()\n );\n const first = points[0]?.value ?? 0;\n const last = points[points.length - 1]?.value ?? 0;\n const peak = points.reduce((acc, p) => Math.max(acc, p.value), Number.NEGATIVE_INFINITY);\n const deltaPct =\n points.length >= 2 && first !== 0 ? ((last - first) / Math.abs(first)) * 100 : null;\n return { type, points, unit: points[0]?.unit ?? null, first, last, peak, deltaPct };\n })\n .sort((a, b) => b.points.length - a.points.length);\n}\n\nexport function AthleteProfilePage() {\n const { teamId, memberId } = useParams<{ teamId: string; memberId: string }>();\n const { navigateTo } = useAthleteBridge();\n\n const profile = useAthleteProfile(teamId, memberId);\n const team = useTeam(teamId);\n const [range, setRange] = useState<RangeKey>('6M');\n\n const start = useMemo(() => rangeStart(range), [range]);\n const metricGroups = useMemo(\n () => buildMetricGroups(profile.metrics, start),\n [profile.metrics, start]\n );\n const allTimeGroups = useMemo(() => buildMetricGroups(profile.metrics, 0), [profile.metrics]);\n\n const ratedSessions = useMemo(\n () => profile.sessions.filter((s) => s.rating !== null),\n [profile.sessions]\n );\n const avgRating = useMemo(\n () =>\n ratedSessions.length > 0\n ? ratedSessions.reduce((acc, s) => acc + (s.rating ?? 0), 0) / ratedSessions.length\n : null,\n [ratedSessions]\n );\n\n const upcomingSessions = useMemo(\n () =>\n profile.sessions\n .filter((s) => s.status === 'REQUESTED' || s.status === 'CONFIRMED')\n .sort((a, b) => new Date(a.scheduledAt).getTime() - new Date(b.scheduledAt).getTime()),\n [profile.sessions]\n );\n\n const recentWorkouts = useMemo(\n () =>\n [...profile.workouts].sort(\n (a, b) => new Date(b.performedAt).getTime() - new Date(a.performedAt).getTime()\n ),\n [profile.workouts]\n );\n\n const completedSessions = useMemo(\n () => profile.sessions.filter((s) => s.status === 'COMPLETED').length,\n [profile.sessions]\n );\n\n if (profile.isLoading) {\n return (\n <PageLayout title=\"Athlete\">\n <LoadingState label=\"Loading athlete profile…\" />\n </PageLayout>\n );\n }\n\n if (profile.isError || !profile.member) {\n return (\n <PageLayout title=\"Athlete\">\n <ErrorState\n title=\"Athlete not found\"\n message=\"This athlete is not on the roster, or you do not have access.\"\n onRetry={() => (teamId ? navigateTo(`/teams/${teamId}`) : navigateTo('/teams'))}\n />\n </PageLayout>\n );\n }\n\n const member = profile.member;\n const teamName = team.data?.name ?? null;\n const teamSport = team.data?.sport;\n\n const physicalStats: Array<{ icon: React.ReactNode; label: string; value: string }> = [];\n if (member.height != null) {\n physicalStats.push({\n icon: <Ruler className=\"h-4 w-4\" />,\n label: 'Height',\n value: String(member.height),\n });\n }\n if (member.weight != null) {\n physicalStats.push({\n icon: <Weight className=\"h-4 w-4\" />,\n label: 'Weight',\n value: String(member.weight),\n });\n }\n if (member.dominantSide) {\n physicalStats.push({\n icon: <Gauge className=\"h-4 w-4\" />,\n label: 'Dominant side',\n value: member.dominantSide,\n });\n }\n if (member.position) {\n physicalStats.push({\n icon: <Users className=\"h-4 w-4\" />,\n label: 'Position',\n value: member.position,\n });\n }\n if (member.joinedAt) {\n physicalStats.push({\n icon: <CalendarClock className=\"h-4 w-4\" />,\n label: 'Joined',\n value: formatDate(member.joinedAt),\n });\n }\n\n const primaryGroup = metricGroups[0] ?? null;\n\n const workoutColumns: ResponsiveTableColumn<AthWorkoutLog>[] = [\n {\n key: 'title',\n header: 'Workout',\n priority: 'primary',\n cell: (row) => <span className=\"font-medium text-text-primary\">{row.title}</span>,\n },\n {\n key: 'duration',\n header: 'Duration',\n priority: 'secondary',\n cell: (row) => (\n <span className=\"tabular-nums text-text-secondary\">\n {row.durationMin != null ? formatDuration(row.durationMin) : '—'}\n </span>\n ),\n },\n {\n key: 'load',\n header: 'Load',\n priority: 'hidden-on-mobile',\n cell: (row) => (\n <span className=\"tabular-nums text-text-secondary\">\n {row.totalLoad != null ? row.totalLoad.toLocaleString() : '—'}\n </span>\n ),\n },\n {\n key: 'rpe',\n header: 'RPE',\n priority: 'hidden-on-mobile',\n cell: (row) => (\n <span className=\"tabular-nums text-text-secondary\">{row.rpe != null ? row.rpe : '—'}</span>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n priority: 'secondary',\n cell: (row) => <StatusBadge status={row.status} />,\n },\n {\n key: 'date',\n header: 'Date',\n priority: 'secondary',\n cell: (row) => (\n <span className=\"whitespace-nowrap text-text-secondary\">{formatDate(row.performedAt)}</span>\n ),\n },\n ];\n\n return (\n <PageLayout\n title={<GradientText>{member.displayName}</GradientText>}\n description={\n teamName ? `${teamName}${teamSport ? ` · ${SPORT_LABELS[teamSport]}` : ''}` : undefined\n }\n actions={\n <button\n type=\"button\"\n onClick={() => (teamId ? navigateTo(`/teams/${teamId}`) : navigateTo('/teams'))}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back to team\n </button>\n }\n >\n {/* Identity header */}\n <GlassCard treatment=\"insight\" className=\"mb-8 p-6\">\n <div className=\"flex flex-col gap-6 md:flex-row md:items-start\">\n <UserAvatar\n name={member.displayName}\n id={member.userId}\n jersey={member.jerseyNumber}\n size=\"xl\"\n ring\n className=\"shrink-0\"\n />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <Badge variant=\"default\">{MEMBER_ROLE_LABELS[member.role]}</Badge>\n {member.position ? <Badge variant=\"secondary\">{member.position}</Badge> : null}\n {member.jerseyNumber != null ? (\n <Badge variant=\"outline\" className=\"tabular-nums\">\n #{member.jerseyNumber}\n </Badge>\n ) : null}\n {teamName ? (\n <Badge variant=\"secondary\" className=\"gap-1.5\">\n <Crest name={teamName} sport={teamSport} size={16} showSportGlyph={false} />\n {teamName}\n </Badge>\n ) : null}\n <StatusBadge status={member.status} />\n </div>\n\n {physicalStats.length > 0 ? (\n <dl className=\"mt-5 grid grid-cols-2 gap-x-6 gap-y-4 sm:grid-cols-3 lg:grid-cols-5\">\n {physicalStats.map((stat) => (\n <div key={stat.label} className=\"min-w-0\">\n <dt className=\"flex items-center gap-1.5 text-xs font-medium text-text-secondary\">\n <span className=\"text-text-muted\">{stat.icon}</span>\n {stat.label}\n </dt>\n <dd className=\"mt-1 truncate text-lg font-semibold text-text-primary\">\n {stat.value}\n </dd>\n </div>\n ))}\n </dl>\n ) : null}\n </div>\n </div>\n </GlassCard>\n\n <Tabs defaultValue=\"overview\">\n <TabsList className=\"mb-6 flex-wrap\">\n <TabsTrigger value=\"overview\">Overview</TabsTrigger>\n <TabsTrigger value=\"metrics\">Metrics</TabsTrigger>\n <TabsTrigger value=\"workouts\">Workouts</TabsTrigger>\n <TabsTrigger value=\"sessions\">Sessions</TabsTrigger>\n <TabsTrigger value=\"ai\">AI decisions</TabsTrigger>\n </TabsList>\n\n {/* ── Overview ─────────────────────────────────────────────────────── */}\n <TabsContent value=\"overview\" className=\"space-y-8\">\n {/* KPI / improvement tiles */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <GlassCard treatment=\"metric\" className=\"flex flex-col gap-2 p-5\">\n <span className=\"text-sm font-medium text-text-secondary\">Avg session rating</span>\n {avgRating != null ? (\n <>\n <RatingStars\n value={avgRating}\n max={5}\n size={20}\n showValue\n count={ratedSessions.length}\n />\n </>\n ) : (\n <span className=\"text-sm text-text-muted\">No rated sessions yet</span>\n )}\n </GlassCard>\n <StatCard\n label=\"Sessions\"\n value={profile.sessions.length}\n icon={<CalendarClock className=\"h-4 w-4\" />}\n />\n <StatCard\n label=\"Completed\"\n value={completedSessions}\n icon={<Star className=\"h-4 w-4\" />}\n />\n <StatCard\n label=\"Workouts logged\"\n value={profile.workouts.length}\n icon={<Dumbbell className=\"h-4 w-4\" />}\n />\n </div>\n\n {/* Improvement tiles from real metric series */}\n {allTimeGroups.some((g) => g.deltaPct != null) ? (\n <section>\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">Improvement</h2>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n {allTimeGroups\n .filter((g) => g.deltaPct != null)\n .slice(0, 4)\n .map((g) => (\n <StatCard\n key={g.type}\n label={`${METRIC_TYPE_LABELS[g.type]}${g.unit ? ` (${g.unit})` : ''}`}\n value={g.last.toLocaleString()}\n delta={g.deltaPct ?? undefined}\n deltaLabel=\"first → latest\"\n sparkline={g.points.map((p) => p.value)}\n />\n ))}\n </div>\n </section>\n ) : null}\n\n {/* Performance trend */}\n <TrendCard\n title=\"Performance trend\"\n groups={metricGroups}\n primary={primaryGroup}\n range={range}\n onRangeChange={setRange}\n />\n\n {/* Upcoming sessions */}\n <section>\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">\n Upcoming sessions ({upcomingSessions.length})\n </h2>\n {upcomingSessions.length === 0 ? (\n <EmptyState\n illustration=\"empty-calendar\"\n icon={<CalendarClock className=\"h-10 w-10\" />}\n title=\"No upcoming sessions\"\n description=\"Confirmed and requested coaching sessions for this athlete appear here.\"\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n {upcomingSessions.slice(0, 6).map((s) => (\n <SessionCard\n key={s.id}\n session={s}\n athleteName={member.displayName}\n onClick={() => navigateTo(`/sessions/${s.id}`)}\n />\n ))}\n </div>\n )}\n </section>\n </TabsContent>\n\n {/* ── Metrics ──────────────────────────────────────────────────────── */}\n <TabsContent value=\"metrics\" className=\"space-y-8\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Performance metrics</h2>\n <RangeChips value={range} onChange={setRange} />\n </div>\n {metricGroups.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<LineChartIcon className=\"h-10 w-10\" />}\n title=\"No metrics in this range\"\n description=\"Record metrics for this athlete — speed, strength, endurance — to chart progress over time.\"\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-2\">\n {metricGroups.map((g) => (\n <GlassCard key={g.type} treatment=\"metric\" className=\"p-5\">\n <div className=\"mb-3 flex items-start justify-between gap-3\">\n <div>\n <h3 className=\"text-sm font-semibold uppercase tracking-wide text-text-secondary\">\n {METRIC_TYPE_LABELS[g.type]}\n </h3>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums text-text-primary\">\n {g.last.toLocaleString()}\n {g.unit ? (\n <span className=\"ml-1 text-sm text-text-secondary\">{g.unit}</span>\n ) : null}\n </p>\n </div>\n {g.deltaPct != null ? (\n <Badge\n variant={g.deltaPct >= 0 ? 'default' : 'destructive'}\n className=\"tabular-nums\"\n >\n {g.deltaPct >= 0 ? '+' : ''}\n {g.deltaPct.toFixed(1)}%\n </Badge>\n ) : null}\n </div>\n <LineChart\n series={g.points.map((p) => ({\n label: shortDate(p.recordedAt),\n value: p.value,\n }))}\n seriesLabel={METRIC_TYPE_LABELS[g.type]}\n height={160}\n valueFormatter={(v) => `${v.toLocaleString()}${g.unit ? ` ${g.unit}` : ''}`}\n />\n <p className=\"mt-2 text-xs text-text-muted tabular-nums\">\n Peak {g.peak.toLocaleString()}\n {g.unit ? ` ${g.unit}` : ''} · {g.points.length} readings\n </p>\n </GlassCard>\n ))}\n </div>\n )}\n </TabsContent>\n\n {/* ── Workouts ─────────────────────────────────────────────────────── */}\n <TabsContent value=\"workouts\">\n {recentWorkouts.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Dumbbell className=\"h-10 w-10\" />}\n title=\"No workouts logged\"\n description=\"This athlete's logged training sessions will appear here.\"\n />\n ) : (\n <ResponsiveTable\n columns={workoutColumns}\n data={recentWorkouts}\n rowKey={(row) => row.id}\n />\n )}\n </TabsContent>\n\n {/* ── Sessions ─────────────────────────────────────────────────────── */}\n <TabsContent value=\"sessions\">\n {profile.sessions.length === 0 ? (\n <EmptyState\n illustration=\"empty-calendar\"\n icon={<CalendarClock className=\"h-10 w-10\" />}\n title=\"No coaching sessions\"\n description=\"Booked, completed and cancelled sessions for this athlete will appear here.\"\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n {[...profile.sessions]\n .sort(\n (a, b) => new Date(b.scheduledAt).getTime() - new Date(a.scheduledAt).getTime()\n )\n .map((s) => (\n <SessionCard\n key={s.id}\n session={s}\n athleteName={member.displayName}\n onClick={() => navigateTo(`/sessions/${s.id}`)}\n />\n ))}\n </div>\n )}\n </TabsContent>\n\n {/* ── AI decisions (BOFF-3574) ─────────────────────────────────────── */}\n <TabsContent value=\"ai\">\n {member.userId ? (\n <SubjectDecisions subject={{ kind: 'athlete', athleteId: member.userId }} />\n ) : (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Sparkles className=\"h-10 w-10\" />}\n title=\"No AI decisions for this roster entry\"\n description=\"AI assessments are keyed to an athlete's account. Link this roster entry to a user to see the decisions made about them.\"\n />\n )}\n </TabsContent>\n </Tabs>\n </PageLayout>\n );\n}\n\n// ── Range chips ──────────────────────────────────────────────────────────────\n\ninterface RangeChipsProps {\n value: RangeKey;\n onChange: (value: RangeKey) => void;\n}\n\nfunction RangeChips({ value, onChange }: RangeChipsProps) {\n return (\n <div\n role=\"group\"\n aria-label=\"Trend range\"\n className=\"inline-flex items-center gap-1 rounded-lg border border-border-seam bg-bg-surface p-1\"\n >\n {RANGE_ORDER.map((key) => {\n const active = key === value;\n return (\n <button\n key={key}\n type=\"button\"\n aria-pressed={active}\n onClick={() => onChange(key)}\n className={\n active\n ? 'rounded-md bg-action-primary-bg px-2.5 py-1 text-xs font-medium text-action-primary-text'\n : 'rounded-md px-2.5 py-1 text-xs font-medium text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary'\n }\n >\n {RANGE_LABELS[key]}\n </button>\n );\n })}\n </div>\n );\n}\n\n// ── Trend card ───────────────────────────────────────────────────────────────\n\ninterface TrendCardProps {\n title: string;\n groups: MetricGroup[];\n primary: MetricGroup | null;\n range: RangeKey;\n onRangeChange: (value: RangeKey) => void;\n}\n\nfunction TrendCard({ title, groups, primary, range, onRangeChange }: TrendCardProps) {\n // Overlay a secondary metric series (aligned by index) when available, so the\n // trend reads like the mock's dual-line without fabricating a shared scale.\n const secondary = groups.find((g) => g.type !== primary?.type) ?? null;\n\n return (\n <GlassCard treatment=\"metric\" className=\"p-5\">\n <div className=\"mb-4 flex flex-wrap items-center justify-between gap-3\">\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">{title}</h2>\n {primary ? (\n <div className=\"mt-1 flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-text-secondary\">\n <span className=\"inline-flex items-center gap-1.5\">\n <span\n className=\"h-2 w-2 rounded-full bg-[var(--color-accent-primary)]\"\n aria-hidden=\"true\"\n />\n {METRIC_TYPE_LABELS[primary.type]}\n {primary.unit ? ` (${primary.unit})` : ''}\n </span>\n {secondary ? (\n <span className=\"inline-flex items-center gap-1.5\">\n <span\n className=\"h-2 w-2 rounded-full bg-[var(--color-accent-teal)]\"\n aria-hidden=\"true\"\n />\n {METRIC_TYPE_LABELS[secondary.type]}\n {secondary.unit ? ` (${secondary.unit})` : ''}\n </span>\n ) : null}\n </div>\n ) : null}\n </div>\n <RangeChips value={range} onChange={onRangeChange} />\n </div>\n {primary ? (\n <LineChart\n series={primary.points.map((p) => ({ label: shortDate(p.recordedAt), value: p.value }))}\n compare={secondary ? secondary.points.map((p) => p.value) : undefined}\n seriesLabel={METRIC_TYPE_LABELS[primary.type]}\n compareLabel={secondary ? METRIC_TYPE_LABELS[secondary.type] : undefined}\n height={240}\n />\n ) : (\n <div className=\"flex h-40 items-center justify-center text-sm text-text-muted\">\n No metric readings in this range.\n </div>\n )}\n </GlassCard>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ProgramDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/atheletebridge/pages/programs/ProgramDetailPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAkFH,wBAAgB,iBAAiB,gCAuchC"}
1
+ {"version":3,"file":"ProgramDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/atheletebridge/pages/programs/ProgramDetailPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAmFH,wBAAgB,iBAAiB,gCA0chC"}
@@ -4,15 +4,16 @@ import { PageLayout as n } from "../../components/PageLayout.js";
4
4
  import { StatusBadge as r } from "../../components/StatusBadge.js";
5
5
  import { PROGRAM_LEVEL_LABELS as i, SPORT_ICONS as a, SPORT_LABELS as o } from "../../components/domain.js";
6
6
  import { EmptyState as ee } from "../../components/EmptyState.js";
7
- import { ErrorState as te, LoadingState as s } from "../../components/LoadingState.js";
7
+ import { ErrorState as s, LoadingState as te } from "../../components/LoadingState.js";
8
8
  import { useAddDrill as ne, useArchiveProgram as c, useProgram as l, useRemoveDrill as u, useTransitionProgram as d, useUploadFile as f } from "../../hooks/useAthleteBridgeApi.js";
9
9
  import { FormField as p, fieldInputClass as m } from "../../components/form.js";
10
- import { DrillVideoLink as h } from "../../components/DrillVideoLink.js";
11
- import { useMemo as re, useState as g } from "react";
12
- import { Archive as _, ArrowLeft as ie, Copy as ae, Edit as oe, FileVideo as se, Loader2 as v, Plus as y, Send as ce, Trash2 as le, Undo2 as ue } from "lucide-react";
13
- import { useParams as b } from "react-router";
14
- import { Fragment as de, jsx as x, jsxs as S } from "react/jsx-runtime";
15
- import { Accordion as fe, AccordionContent as pe, AccordionItem as me, AccordionTrigger as he, Badge as C, GlassCard as w, ResponsiveDialog as T, ResponsiveDialogContent as E, ResponsiveDialogFooter as D, ResponsiveDialogHeader as O, ResponsiveDialogTitle as k } from "@burdenoff/fe-libs/ui";
10
+ import { SubjectDecisions as re } from "../../components/ai/SubjectDecisions.js";
11
+ import { DrillVideoLink as ie } from "../../components/DrillVideoLink.js";
12
+ import { useMemo as ae, useState as h } from "react";
13
+ import { Archive as g, ArrowLeft as oe, Copy as se, Edit as ce, FileVideo as le, Loader2 as _, Plus as v, Send as ue, Trash2 as de, Undo2 as y } from "lucide-react";
14
+ import { useParams as fe } from "react-router";
15
+ import { Fragment as b, jsx as x, jsxs as S } from "react/jsx-runtime";
16
+ import { Accordion as pe, AccordionContent as me, AccordionItem as he, AccordionTrigger as ge, Badge as C, GlassCard as w, ResponsiveDialog as T, ResponsiveDialogContent as E, ResponsiveDialogFooter as D, ResponsiveDialogHeader as O, ResponsiveDialogTitle as k } from "@burdenoff/fe-libs/ui";
16
17
  //#region src/atheletebridge/pages/programs/ProgramDetailPage.tsx
17
18
  var A = {
18
19
  name: "",
@@ -32,7 +33,7 @@ var A = {
32
33
  return Number.isNaN(n) ? void 0 : n;
33
34
  };
34
35
  function N() {
35
- let N = e(), { id: P } = b(), { navigateTo: F } = t(), I = l(P), L = d(), R = c(), z = ne(), B = u(P), ge = f(), [_e, V] = g(!1), [H, U] = g(A), [W, G] = g(null), [K, q] = g(!1), [J, Y] = g(null), X = re(() => {
36
+ let N = e(), { id: P } = fe(), { navigateTo: F } = t(), I = l(P), L = d(), R = c(), z = ne(), B = u(P), _e = f(), [ve, V] = h(!1), [H, U] = h(A), [W, G] = h(null), [K, q] = h(!1), [J, Y] = h(null), X = ae(() => {
36
37
  let e = I.data?.drills?.items ?? [], t = /* @__PURE__ */ new Map();
37
38
  for (let n of e) {
38
39
  let e = t.get(n.weekNumber) ?? [];
@@ -45,17 +46,17 @@ function N() {
45
46
  }, [I.data]);
46
47
  if (I.isLoading) return /* @__PURE__ */ x(n, {
47
48
  title: N("atheletebridge.pages.programDetail.title", "Program"),
48
- children: /* @__PURE__ */ x(s, { label: N("atheletebridge.pages.programDetail.loading", "Loading program…") })
49
+ children: /* @__PURE__ */ x(te, { label: N("atheletebridge.pages.programDetail.loading", "Loading program…") })
49
50
  });
50
51
  if (I.isError || !I.data) return /* @__PURE__ */ x(n, {
51
52
  title: N("atheletebridge.pages.programDetail.title", "Program"),
52
- children: /* @__PURE__ */ x(te, {
53
+ children: /* @__PURE__ */ x(s, {
53
54
  title: N("atheletebridge.pages.programDetail.notFoundTitle", "Program not found"),
54
55
  message: N("atheletebridge.pages.programDetail.notFoundMessage", "This program does not exist or you do not have access to it."),
55
56
  onRetry: () => F("/programs")
56
57
  })
57
58
  });
58
- let Z = I.data, Q = Z.drills?.items.length ?? 0, ve = a[Z.sport], ye = X.map(({ week: e }) => `week-${e}`), $ = (e) => {
59
+ let Z = I.data, Q = Z.drills?.items.length ?? 0, ye = a[Z.sport], be = X.map(({ week: e }) => `week-${e}`), $ = (e) => {
59
60
  P && L.mutate({
60
61
  id: P,
61
62
  status: e
@@ -73,7 +74,7 @@ function N() {
73
74
  onClick: () => F("/programs"),
74
75
  className: "inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken",
75
76
  children: [
76
- /* @__PURE__ */ x(ie, { className: "h-4 w-4" }),
77
+ /* @__PURE__ */ x(oe, { className: "h-4 w-4" }),
77
78
  " ",
78
79
  N("atheletebridge.pages.programDetail.back", "Back")
79
80
  ]
@@ -83,7 +84,7 @@ function N() {
83
84
  onClick: () => F(`/programs/${Z.id}/edit`),
84
85
  className: "inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken",
85
86
  children: [
86
- /* @__PURE__ */ x(oe, { className: "h-4 w-4" }),
87
+ /* @__PURE__ */ x(ce, { className: "h-4 w-4" }),
87
88
  " ",
88
89
  N("atheletebridge.pages.programDetail.edit", "Edit")
89
90
  ]
@@ -96,7 +97,7 @@ function N() {
96
97
  disabled: R.isPending,
97
98
  className: "inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:opacity-60",
98
99
  children: [
99
- R.isPending ? /* @__PURE__ */ x(v, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ x(_, { className: "h-4 w-4" }),
100
+ R.isPending ? /* @__PURE__ */ x(_, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ x(g, { className: "h-4 w-4" }),
100
101
  " ",
101
102
  N("atheletebridge.pages.programDetail.archive", "Archive")
102
103
  ]
@@ -112,7 +113,7 @@ function N() {
112
113
  className: "flex items-start gap-4",
113
114
  children: [/* @__PURE__ */ x("div", {
114
115
  className: "flex h-14 w-14 shrink-0 items-center justify-center rounded-2xl bg-[var(--color-accent-soft)] text-text-primary",
115
- children: /* @__PURE__ */ x(ve, {
116
+ children: /* @__PURE__ */ x(ye, {
116
117
  className: "h-7 w-7",
117
118
  "aria-hidden": "true"
118
119
  })
@@ -132,7 +133,7 @@ function N() {
132
133
  /* @__PURE__ */ x(r, { status: Z.status }),
133
134
  Z.isTemplate && /* @__PURE__ */ S("span", {
134
135
  className: "inline-flex items-center gap-1 text-xs text-text-secondary",
135
- children: [/* @__PURE__ */ x(ae, { className: "h-3.5 w-3.5" }), " Template"]
136
+ children: [/* @__PURE__ */ x(se, { className: "h-3.5 w-3.5" }), " Template"]
136
137
  })
137
138
  ]
138
139
  }), /* @__PURE__ */ S("p", {
@@ -173,18 +174,18 @@ function N() {
173
174
  disabled: L.isPending,
174
175
  className: "inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-60",
175
176
  children: [
176
- /* @__PURE__ */ x(ce, { className: "h-4 w-4" }),
177
+ /* @__PURE__ */ x(ue, { className: "h-4 w-4" }),
177
178
  " ",
178
179
  N("atheletebridge.pages.programDetail.publish", "Publish")
179
180
  ]
180
181
  }),
181
- Z.status === "PUBLISHED" && /* @__PURE__ */ S(de, { children: [/* @__PURE__ */ S("button", {
182
+ Z.status === "PUBLISHED" && /* @__PURE__ */ S(b, { children: [/* @__PURE__ */ S("button", {
182
183
  type: "button",
183
184
  onClick: () => $("DRAFT"),
184
185
  disabled: L.isPending,
185
186
  className: "inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:opacity-60",
186
187
  children: [
187
- /* @__PURE__ */ x(ue, { className: "h-4 w-4" }),
188
+ /* @__PURE__ */ x(y, { className: "h-4 w-4" }),
188
189
  " ",
189
190
  N("atheletebridge.pages.programDetail.pullBackToDraft", "Pull back to draft")
190
191
  ]
@@ -194,7 +195,7 @@ function N() {
194
195
  disabled: L.isPending,
195
196
  className: "inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:opacity-60",
196
197
  children: [
197
- /* @__PURE__ */ x(_, { className: "h-4 w-4" }),
198
+ /* @__PURE__ */ x(g, { className: "h-4 w-4" }),
198
199
  " ",
199
200
  N("atheletebridge.pages.programDetail.archiveAction", "Archive")
200
201
  ]
@@ -203,7 +204,7 @@ function N() {
203
204
  className: "text-sm text-text-secondary",
204
205
  children: N("atheletebridge.pages.programDetail.archived", "This program is archived.")
205
206
  }),
206
- L.isPending && /* @__PURE__ */ x(v, { className: "h-4 w-4 animate-spin text-text-secondary" })
207
+ L.isPending && /* @__PURE__ */ x(_, { className: "h-4 w-4 animate-spin text-text-secondary" })
207
208
  ]
208
209
  })
209
210
  ]
@@ -225,27 +226,27 @@ function N() {
225
226
  },
226
227
  className: "inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90",
227
228
  children: [
228
- /* @__PURE__ */ x(y, { className: "h-4 w-4" }),
229
+ /* @__PURE__ */ x(v, { className: "h-4 w-4" }),
229
230
  " ",
230
231
  N("atheletebridge.pages.programDetail.addDrill", "Add drill")
231
232
  ]
232
233
  })]
233
234
  }), Q === 0 ? /* @__PURE__ */ x(ee, {
234
235
  illustration: "empty-data",
235
- icon: /* @__PURE__ */ x(y, { className: "h-10 w-10" }),
236
+ icon: /* @__PURE__ */ x(v, { className: "h-10 w-10" }),
236
237
  title: N("atheletebridge.pages.programDetail.noDrillsTitle", "No drills yet"),
237
238
  description: N("atheletebridge.pages.programDetail.noDrillsHint", "Add drills with sets, reps and durations, organized by week.")
238
- }) : /* @__PURE__ */ x(fe, {
239
+ }) : /* @__PURE__ */ x(pe, {
239
240
  type: "multiple",
240
- defaultValue: ye,
241
+ defaultValue: be,
241
242
  className: "flex flex-col gap-3",
242
243
  children: X.map(({ week: e, drills: t }) => /* @__PURE__ */ x(w, {
243
244
  treatment: "insight",
244
245
  className: "overflow-hidden px-4 py-0",
245
- children: /* @__PURE__ */ S(me, {
246
+ children: /* @__PURE__ */ S(he, {
246
247
  value: `week-${e}`,
247
248
  className: "border-b-0",
248
- children: [/* @__PURE__ */ x(he, {
249
+ children: [/* @__PURE__ */ x(ge, {
249
250
  className: "py-4",
250
251
  children: /* @__PURE__ */ S("span", {
251
252
  className: "flex items-center gap-2.5",
@@ -261,7 +262,7 @@ function N() {
261
262
  ]
262
263
  })]
263
264
  })
264
- }), /* @__PURE__ */ x(pe, {
265
+ }), /* @__PURE__ */ x(me, {
265
266
  className: "pb-2",
266
267
  children: /* @__PURE__ */ x("div", {
267
268
  className: "divide-y divide-border-seam",
@@ -287,7 +288,7 @@ function N() {
287
288
  e.restSec !== null && /* @__PURE__ */ S(j, { children: [e.restSec, "s rest"] })
288
289
  ]
289
290
  }),
290
- e.video?.id && /* @__PURE__ */ x("div", { children: /* @__PURE__ */ x(h, { fileId: e.video.id }) })
291
+ e.video?.id && /* @__PURE__ */ x("div", { children: /* @__PURE__ */ x(ie, { fileId: e.video.id }) })
291
292
  ]
292
293
  }), /* @__PURE__ */ x("button", {
293
294
  type: "button",
@@ -295,7 +296,7 @@ function N() {
295
296
  disabled: B.isPending,
296
297
  "aria-label": `${N("common.delete", "Delete")} ${e.name}`,
297
298
  className: "rounded-lg p-2 text-text-secondary transition-colors hover:bg-status-error-bg-subtle hover:text-status-error-text disabled:opacity-60",
298
- children: /* @__PURE__ */ x(le, { className: "h-4 w-4" })
299
+ children: /* @__PURE__ */ x(de, { className: "h-4 w-4" })
299
300
  })]
300
301
  }, e.id))
301
302
  })
@@ -303,8 +304,15 @@ function N() {
303
304
  })
304
305
  }, e))
305
306
  })] }),
307
+ /* @__PURE__ */ x(re, {
308
+ className: "mt-8",
309
+ subject: {
310
+ kind: "program",
311
+ programId: Z.id
312
+ }
313
+ }),
306
314
  /* @__PURE__ */ x(T, {
307
- open: _e,
315
+ open: ve,
308
316
  onOpenChange: V,
309
317
  children: /* @__PURE__ */ S(E, { children: [/* @__PURE__ */ x(O, { children: /* @__PURE__ */ x(k, { children: N("atheletebridge.pages.programDetail.addDrill", "Add drill") }) }), /* @__PURE__ */ S("form", {
310
318
  onSubmit: async (e) => {
@@ -317,7 +325,7 @@ function N() {
317
325
  if (W) {
318
326
  q(!0);
319
327
  try {
320
- n = await ge(W);
328
+ n = await _e(W);
321
329
  } catch (e) {
322
330
  Y(e instanceof Error ? e.message : "Failed to upload the demo video."), q(!1);
323
331
  return;
@@ -463,7 +471,7 @@ function N() {
463
471
  }), W && /* @__PURE__ */ S("p", {
464
472
  className: "mt-1 inline-flex items-center gap-1 text-xs text-text-secondary",
465
473
  children: [
466
- /* @__PURE__ */ x(se, { className: "h-3.5 w-3.5" }),
474
+ /* @__PURE__ */ x(le, { className: "h-3.5 w-3.5" }),
467
475
  " ",
468
476
  W.name
469
477
  ]
@@ -484,7 +492,7 @@ function N() {
484
492
  type: "submit",
485
493
  disabled: z.isPending || K,
486
494
  className: "inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-60",
487
- children: [(z.isPending || K) && /* @__PURE__ */ x(v, { className: "h-4 w-4 animate-spin" }), K ? N("atheletebridge.pages.programDetail.uploadingVideo", "Uploading video…") : N("atheletebridge.pages.programDetail.addDrill", "Add drill")]
495
+ children: [(z.isPending || K) && /* @__PURE__ */ x(_, { className: "h-4 w-4 animate-spin" }), K ? N("atheletebridge.pages.programDetail.uploadingVideo", "Uploading video…") : N("atheletebridge.pages.programDetail.addDrill", "Add drill")]
488
496
  })] })
489
497
  ]
490
498
  })] })
@@ -1 +1 @@
1
- {"version":3,"file":"ProgramDetailPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/programs/ProgramDetailPage.tsx"],"sourcesContent":["/**\n * Program Detail\n * @module atheletebridge/pages/programs\n *\n * Reads a single program with its drills (athProgram), supports the publish\n * lifecycle (athTransitionProgram), editing/archiving, and adding/removing\n * drills (athAddDrill / athRemoveDrill).\n */\n\nimport { useMemo, useState } from 'react';\nimport { useParams } from 'react-router';\nimport {\n ArrowLeft,\n Edit,\n Archive,\n Plus,\n Trash2,\n Loader2,\n Send,\n Undo2,\n Copy,\n FileVideo,\n} from 'lucide-react';\nimport {\n GlassCard,\n Badge,\n Accordion,\n AccordionItem,\n AccordionTrigger,\n AccordionContent,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { FormField, fieldInputClass } from '../../components/form';\nimport { DrillVideoLink } from '../../components/DrillVideoLink';\nimport { SPORT_ICONS, SPORT_LABELS, PROGRAM_LEVEL_LABELS } from '../../components/domain';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport {\n useProgram,\n useTransitionProgram,\n useArchiveProgram,\n useAddDrill,\n useRemoveDrill,\n useUploadFile,\n} from '../../hooks/useAthleteBridgeApi';\nimport { useAbTranslate } from '../../i18n';\nimport type { AthDrill, AthProgramStatus } from '../../types';\n\ninterface DrillForm {\n name: string;\n description: string;\n weekNumber: string;\n sets: string;\n reps: string;\n durationSec: string;\n restSec: string;\n}\n\nconst EMPTY_DRILL: DrillForm = {\n name: '',\n description: '',\n weekNumber: '1',\n sets: '',\n reps: '',\n durationSec: '',\n restSec: '',\n};\n\n/** Small tokenized metric pill for a drill's sets/reps/work/rest. */\nconst MetricChip = ({ children }: { children: React.ReactNode }) => (\n <span className=\"inline-flex items-center rounded-md border border-border-seam bg-bg-sunken px-2 py-0.5 text-xs font-medium tabular-nums text-text-secondary\">\n {children}\n </span>\n);\n\nconst numOrUndefined = (v: string): number | undefined => {\n const t = v.trim();\n if (!t) return undefined;\n const n = Number.parseInt(t, 10);\n return Number.isNaN(n) ? undefined : n;\n};\n\nexport function ProgramDetailPage() {\n const tr = useAbTranslate();\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useAthleteBridge();\n\n const program = useProgram(id);\n const transition = useTransitionProgram();\n const archive = useArchiveProgram();\n const addDrill = useAddDrill();\n const removeDrill = useRemoveDrill(id);\n const uploadFile = useUploadFile();\n\n const [drillOpen, setDrillOpen] = useState(false);\n const [drillForm, setDrillForm] = useState<DrillForm>(EMPTY_DRILL);\n const [videoFile, setVideoFile] = useState<File | null>(null);\n const [uploadingVideo, setUploadingVideo] = useState(false);\n const [drillError, setDrillError] = useState<string | null>(null);\n\n const drillsByWeek = useMemo(() => {\n const drills = program.data?.drills?.items ?? [];\n const groups = new Map<number, AthDrill[]>();\n for (const d of drills) {\n const list = groups.get(d.weekNumber) ?? [];\n list.push(d);\n groups.set(d.weekNumber, list);\n }\n return Array.from(groups.entries())\n .sort(([a], [b]) => a - b)\n .map(([week, list]) => ({\n week,\n drills: [...list].sort((a, b) => a.orderIndex - b.orderIndex),\n }));\n }, [program.data]);\n\n if (program.isLoading) {\n return (\n <PageLayout title={tr('atheletebridge.pages.programDetail.title', 'Program')}>\n <LoadingState\n label={tr('atheletebridge.pages.programDetail.loading', 'Loading program…')}\n />\n </PageLayout>\n );\n }\n\n if (program.isError || !program.data) {\n return (\n <PageLayout title={tr('atheletebridge.pages.programDetail.title', 'Program')}>\n <ErrorState\n title={tr('atheletebridge.pages.programDetail.notFoundTitle', 'Program not found')}\n message={tr(\n 'atheletebridge.pages.programDetail.notFoundMessage',\n 'This program does not exist or you do not have access to it.'\n )}\n onRetry={() => navigateTo('/programs')}\n />\n </PageLayout>\n );\n }\n\n const p = program.data;\n const drillCount = p.drills?.items.length ?? 0;\n const SportIcon = SPORT_ICONS[p.sport];\n const openWeeks = drillsByWeek.map(({ week }) => `week-${week}`);\n\n const handleTransition = (status: AthProgramStatus) => {\n if (!id) return;\n void transition.mutate({ id, status });\n };\n\n const handleArchive = async () => {\n if (!id) return;\n await archive.mutateAsync(id);\n navigateTo('/programs');\n };\n\n const handleAddDrill = async (e: React.FormEvent) => {\n e.preventDefault();\n setDrillError(null);\n if (!id) return;\n if (!drillForm.name.trim()) {\n setDrillError('Drill name is required.');\n return;\n }\n const week = numOrUndefined(drillForm.weekNumber) ?? 1;\n\n let videoFileId: string | undefined;\n if (videoFile) {\n setUploadingVideo(true);\n try {\n videoFileId = await uploadFile(videoFile);\n } catch (err) {\n setDrillError(err instanceof Error ? err.message : 'Failed to upload the demo video.');\n setUploadingVideo(false);\n return;\n }\n setUploadingVideo(false);\n }\n\n try {\n await addDrill.mutateAsync({\n programId: id,\n name: drillForm.name.trim(),\n description: drillForm.description.trim() || undefined,\n weekNumber: week,\n orderIndex: program.data?.drills?.items.filter((d) => d.weekNumber === week).length ?? 0,\n sets: numOrUndefined(drillForm.sets),\n reps: numOrUndefined(drillForm.reps),\n durationSec: numOrUndefined(drillForm.durationSec),\n restSec: numOrUndefined(drillForm.restSec),\n videoFileId,\n });\n setDrillForm(EMPTY_DRILL);\n setVideoFile(null);\n setDrillOpen(false);\n } catch (err) {\n setDrillError(err instanceof Error ? err.message : 'Failed to add drill.');\n }\n };\n\n return (\n <PageLayout\n title={p.name}\n gradientTitle\n description={`${SPORT_LABELS[p.sport]} · ${PROGRAM_LEVEL_LABELS[p.level]} · ${p.weeks} ${\n p.weeks === 1 ? 'week' : 'weeks'\n }`}\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/programs')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" />{' '}\n {tr('atheletebridge.pages.programDetail.back', 'Back')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/programs/${p.id}/edit`)}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <Edit className=\"h-4 w-4\" /> {tr('atheletebridge.pages.programDetail.edit', 'Edit')}\n </button>\n {p.status !== 'ARCHIVED' && (\n <button\n type=\"button\"\n onClick={handleArchive}\n disabled={archive.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:opacity-60\"\n >\n {archive.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Archive className=\"h-4 w-4\" />\n )}{' '}\n {tr('atheletebridge.pages.programDetail.archive', 'Archive')}\n </button>\n )}\n </div>\n }\n >\n {/* Summary + lifecycle */}\n <GlassCard treatment=\"insight\" className=\"mb-8 p-6\">\n <div className=\"flex items-start gap-4\">\n <div className=\"flex h-14 w-14 shrink-0 items-center justify-center rounded-2xl bg-[var(--color-accent-soft)] text-text-primary\">\n <SportIcon className=\"h-7 w-7\" aria-hidden=\"true\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <Badge variant=\"secondary\">{SPORT_LABELS[p.sport]}</Badge>\n <Badge variant=\"secondary\">{PROGRAM_LEVEL_LABELS[p.level]}</Badge>\n <StatusBadge status={p.status} />\n {p.isTemplate && (\n <span className=\"inline-flex items-center gap-1 text-xs text-text-secondary\">\n <Copy className=\"h-3.5 w-3.5\" /> Template\n </span>\n )}\n </div>\n <p className=\"mt-2 text-sm text-text-secondary\">\n {p.weeks} {p.weeks === 1 ? 'week' : 'weeks'} · {drillCount}{' '}\n {drillCount === 1 ? 'drill' : 'drills'}\n </p>\n </div>\n </div>\n {p.summary && <p className=\"mt-4 text-sm text-text-secondary\">{p.summary}</p>}\n {p.tags.length > 0 && (\n <div className=\"mt-4 flex flex-wrap gap-1.5\">\n {p.tags.map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\"\n >\n {tag}\n </span>\n ))}\n </div>\n )}\n\n {/* Lifecycle transitions */}\n <div className=\"mt-5 flex flex-wrap items-center gap-2 border-t border-border-seam pt-5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('atheletebridge.pages.programDetail.lifecycle', 'Publish lifecycle:')}\n </span>\n {p.status === 'DRAFT' && (\n <button\n type=\"button\"\n onClick={() => handleTransition('PUBLISHED')}\n disabled={transition.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-60\"\n >\n <Send className=\"h-4 w-4\" />{' '}\n {tr('atheletebridge.pages.programDetail.publish', 'Publish')}\n </button>\n )}\n {p.status === 'PUBLISHED' && (\n <>\n <button\n type=\"button\"\n onClick={() => handleTransition('DRAFT')}\n disabled={transition.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:opacity-60\"\n >\n <Undo2 className=\"h-4 w-4\" />{' '}\n {tr('atheletebridge.pages.programDetail.pullBackToDraft', 'Pull back to draft')}\n </button>\n <button\n type=\"button\"\n onClick={() => handleTransition('ARCHIVED')}\n disabled={transition.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:opacity-60\"\n >\n <Archive className=\"h-4 w-4\" />{' '}\n {tr('atheletebridge.pages.programDetail.archiveAction', 'Archive')}\n </button>\n </>\n )}\n {p.status === 'ARCHIVED' && (\n <span className=\"text-sm text-text-secondary\">\n {tr('atheletebridge.pages.programDetail.archived', 'This program is archived.')}\n </span>\n )}\n {transition.isPending && <Loader2 className=\"h-4 w-4 animate-spin text-text-secondary\" />}\n </div>\n </GlassCard>\n\n {/* Drills */}\n <section>\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('atheletebridge.pages.programDetail.drillsTitle', 'Drills')} ({drillCount})\n </h2>\n <button\n type=\"button\"\n onClick={() => {\n setDrillError(null);\n setDrillOpen(true);\n }}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" />{' '}\n {tr('atheletebridge.pages.programDetail.addDrill', 'Add drill')}\n </button>\n </div>\n\n {drillCount === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Plus className=\"h-10 w-10\" />}\n title={tr('atheletebridge.pages.programDetail.noDrillsTitle', 'No drills yet')}\n description={tr(\n 'atheletebridge.pages.programDetail.noDrillsHint',\n 'Add drills with sets, reps and durations, organized by week.'\n )}\n />\n ) : (\n <Accordion type=\"multiple\" defaultValue={openWeeks} className=\"flex flex-col gap-3\">\n {drillsByWeek.map(({ week, drills }) => (\n <GlassCard key={week} treatment=\"insight\" className=\"overflow-hidden px-4 py-0\">\n <AccordionItem value={`week-${week}`} className=\"border-b-0\">\n <AccordionTrigger className=\"py-4\">\n <span className=\"flex items-center gap-2.5\">\n <span className=\"text-sm font-semibold text-text-primary\">Week {week}</span>\n <Badge variant=\"secondary\">\n {drills.length} {drills.length === 1 ? 'drill' : 'drills'}\n </Badge>\n </span>\n </AccordionTrigger>\n <AccordionContent className=\"pb-2\">\n <div className=\"divide-y divide-border-seam\">\n {drills.map((d) => (\n <div key={d.id} className=\"flex items-start gap-4 py-3\">\n <div className=\"min-w-0 flex-1\">\n <p className=\"text-sm font-medium text-text-primary\">{d.name}</p>\n {d.description && (\n <p className=\"mt-0.5 text-xs text-text-secondary\">{d.description}</p>\n )}\n <div className=\"mt-2 flex flex-wrap gap-1.5\">\n {d.sets !== null && <MetricChip>{d.sets} sets</MetricChip>}\n {d.reps !== null && <MetricChip>{d.reps} reps</MetricChip>}\n {d.durationSec !== null && (\n <MetricChip>{d.durationSec}s work</MetricChip>\n )}\n {d.restSec !== null && <MetricChip>{d.restSec}s rest</MetricChip>}\n </div>\n {d.video?.id && (\n <div>\n <DrillVideoLink fileId={d.video.id} />\n </div>\n )}\n </div>\n <button\n type=\"button\"\n onClick={() => void removeDrill.mutate(d.id)}\n disabled={removeDrill.isPending}\n aria-label={`${tr('common.delete', 'Delete')} ${d.name}`}\n className=\"rounded-lg p-2 text-text-secondary transition-colors hover:bg-status-error-bg-subtle hover:text-status-error-text disabled:opacity-60\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n ))}\n </div>\n </AccordionContent>\n </AccordionItem>\n </GlassCard>\n ))}\n </Accordion>\n )}\n </section>\n\n {/* Add drill dialog */}\n <ResponsiveDialog open={drillOpen} onOpenChange={setDrillOpen}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n {tr('atheletebridge.pages.programDetail.addDrill', 'Add drill')}\n </ResponsiveDialogTitle>\n </ResponsiveDialogHeader>\n <form onSubmit={handleAddDrill} className=\"space-y-4 px-1\">\n <FormField label=\"Name\" htmlFor=\"drillName\" required>\n <input\n id=\"drillName\"\n className={fieldInputClass}\n value={drillForm.name}\n onChange={(e) => setDrillForm((p) => ({ ...p, name: e.target.value }))}\n placeholder=\"e.g. Box jumps\"\n />\n </FormField>\n <FormField label=\"Description\" htmlFor=\"drillDesc\">\n <textarea\n id=\"drillDesc\"\n className={fieldInputClass}\n rows={2}\n value={drillForm.description}\n onChange={(e) => setDrillForm((p) => ({ ...p, description: e.target.value }))}\n placeholder=\"How to perform the drill.\"\n />\n </FormField>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <FormField label=\"Week\" htmlFor=\"drillWeek\">\n <input\n id=\"drillWeek\"\n inputMode=\"numeric\"\n className={fieldInputClass}\n value={drillForm.weekNumber}\n onChange={(e) => setDrillForm((p) => ({ ...p, weekNumber: e.target.value }))}\n />\n </FormField>\n <FormField label=\"Sets\" htmlFor=\"drillSets\">\n <input\n id=\"drillSets\"\n inputMode=\"numeric\"\n className={fieldInputClass}\n value={drillForm.sets}\n onChange={(e) => setDrillForm((p) => ({ ...p, sets: e.target.value }))}\n />\n </FormField>\n <FormField label=\"Reps\" htmlFor=\"drillReps\">\n <input\n id=\"drillReps\"\n inputMode=\"numeric\"\n className={fieldInputClass}\n value={drillForm.reps}\n onChange={(e) => setDrillForm((p) => ({ ...p, reps: e.target.value }))}\n />\n </FormField>\n </div>\n <div className=\"grid grid-cols-2 gap-4\">\n <FormField label=\"Work (sec)\" htmlFor=\"drillDuration\">\n <input\n id=\"drillDuration\"\n inputMode=\"numeric\"\n className={fieldInputClass}\n value={drillForm.durationSec}\n onChange={(e) => setDrillForm((p) => ({ ...p, durationSec: e.target.value }))}\n />\n </FormField>\n <FormField label=\"Rest (sec)\" htmlFor=\"drillRest\">\n <input\n id=\"drillRest\"\n inputMode=\"numeric\"\n className={fieldInputClass}\n value={drillForm.restSec}\n onChange={(e) => setDrillForm((p) => ({ ...p, restSec: e.target.value }))}\n />\n </FormField>\n </div>\n <FormField\n label=\"Demo video\"\n htmlFor=\"drillVideo\"\n hint=\"Optional — attach a short demo clip; it's stored in workspace files.\"\n >\n <input\n id=\"drillVideo\"\n type=\"file\"\n accept=\"video/*\"\n className={fieldInputClass}\n onChange={(e) => setVideoFile(e.target.files?.[0] ?? null)}\n />\n {videoFile && (\n <p className=\"mt-1 inline-flex items-center gap-1 text-xs text-text-secondary\">\n <FileVideo className=\"h-3.5 w-3.5\" /> {videoFile.name}\n </p>\n )}\n </FormField>\n {drillError && <p className=\"text-sm text-status-error-text\">{drillError}</p>}\n <ResponsiveDialogFooter>\n <button\n type=\"button\"\n onClick={() => {\n setDrillOpen(false);\n setVideoFile(null);\n }}\n className=\"rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n disabled={addDrill.isPending || uploadingVideo}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-60\"\n >\n {(addDrill.isPending || uploadingVideo) && (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n )}\n {uploadingVideo\n ? tr('atheletebridge.pages.programDetail.uploadingVideo', 'Uploading video…')\n : tr('atheletebridge.pages.programDetail.addDrill', 'Add drill')}\n </button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ProgramDetailPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/programs/ProgramDetailPage.tsx"],"sourcesContent":["/**\n * Program Detail\n * @module atheletebridge/pages/programs\n *\n * Reads a single program with its drills (athProgram), supports the publish\n * lifecycle (athTransitionProgram), editing/archiving, and adding/removing\n * drills (athAddDrill / athRemoveDrill).\n */\n\nimport { useMemo, useState } from 'react';\nimport { useParams } from 'react-router';\nimport { SubjectDecisions } from '../../components/ai/SubjectDecisions';\nimport {\n ArrowLeft,\n Edit,\n Archive,\n Plus,\n Trash2,\n Loader2,\n Send,\n Undo2,\n Copy,\n FileVideo,\n} from 'lucide-react';\nimport {\n GlassCard,\n Badge,\n Accordion,\n AccordionItem,\n AccordionTrigger,\n AccordionContent,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { FormField, fieldInputClass } from '../../components/form';\nimport { DrillVideoLink } from '../../components/DrillVideoLink';\nimport { SPORT_ICONS, SPORT_LABELS, PROGRAM_LEVEL_LABELS } from '../../components/domain';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport {\n useProgram,\n useTransitionProgram,\n useArchiveProgram,\n useAddDrill,\n useRemoveDrill,\n useUploadFile,\n} from '../../hooks/useAthleteBridgeApi';\nimport { useAbTranslate } from '../../i18n';\nimport type { AthDrill, AthProgramStatus } from '../../types';\n\ninterface DrillForm {\n name: string;\n description: string;\n weekNumber: string;\n sets: string;\n reps: string;\n durationSec: string;\n restSec: string;\n}\n\nconst EMPTY_DRILL: DrillForm = {\n name: '',\n description: '',\n weekNumber: '1',\n sets: '',\n reps: '',\n durationSec: '',\n restSec: '',\n};\n\n/** Small tokenized metric pill for a drill's sets/reps/work/rest. */\nconst MetricChip = ({ children }: { children: React.ReactNode }) => (\n <span className=\"inline-flex items-center rounded-md border border-border-seam bg-bg-sunken px-2 py-0.5 text-xs font-medium tabular-nums text-text-secondary\">\n {children}\n </span>\n);\n\nconst numOrUndefined = (v: string): number | undefined => {\n const t = v.trim();\n if (!t) return undefined;\n const n = Number.parseInt(t, 10);\n return Number.isNaN(n) ? undefined : n;\n};\n\nexport function ProgramDetailPage() {\n const tr = useAbTranslate();\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useAthleteBridge();\n\n const program = useProgram(id);\n const transition = useTransitionProgram();\n const archive = useArchiveProgram();\n const addDrill = useAddDrill();\n const removeDrill = useRemoveDrill(id);\n const uploadFile = useUploadFile();\n\n const [drillOpen, setDrillOpen] = useState(false);\n const [drillForm, setDrillForm] = useState<DrillForm>(EMPTY_DRILL);\n const [videoFile, setVideoFile] = useState<File | null>(null);\n const [uploadingVideo, setUploadingVideo] = useState(false);\n const [drillError, setDrillError] = useState<string | null>(null);\n\n const drillsByWeek = useMemo(() => {\n const drills = program.data?.drills?.items ?? [];\n const groups = new Map<number, AthDrill[]>();\n for (const d of drills) {\n const list = groups.get(d.weekNumber) ?? [];\n list.push(d);\n groups.set(d.weekNumber, list);\n }\n return Array.from(groups.entries())\n .sort(([a], [b]) => a - b)\n .map(([week, list]) => ({\n week,\n drills: [...list].sort((a, b) => a.orderIndex - b.orderIndex),\n }));\n }, [program.data]);\n\n if (program.isLoading) {\n return (\n <PageLayout title={tr('atheletebridge.pages.programDetail.title', 'Program')}>\n <LoadingState\n label={tr('atheletebridge.pages.programDetail.loading', 'Loading program…')}\n />\n </PageLayout>\n );\n }\n\n if (program.isError || !program.data) {\n return (\n <PageLayout title={tr('atheletebridge.pages.programDetail.title', 'Program')}>\n <ErrorState\n title={tr('atheletebridge.pages.programDetail.notFoundTitle', 'Program not found')}\n message={tr(\n 'atheletebridge.pages.programDetail.notFoundMessage',\n 'This program does not exist or you do not have access to it.'\n )}\n onRetry={() => navigateTo('/programs')}\n />\n </PageLayout>\n );\n }\n\n const p = program.data;\n const drillCount = p.drills?.items.length ?? 0;\n const SportIcon = SPORT_ICONS[p.sport];\n const openWeeks = drillsByWeek.map(({ week }) => `week-${week}`);\n\n const handleTransition = (status: AthProgramStatus) => {\n if (!id) return;\n void transition.mutate({ id, status });\n };\n\n const handleArchive = async () => {\n if (!id) return;\n await archive.mutateAsync(id);\n navigateTo('/programs');\n };\n\n const handleAddDrill = async (e: React.FormEvent) => {\n e.preventDefault();\n setDrillError(null);\n if (!id) return;\n if (!drillForm.name.trim()) {\n setDrillError('Drill name is required.');\n return;\n }\n const week = numOrUndefined(drillForm.weekNumber) ?? 1;\n\n let videoFileId: string | undefined;\n if (videoFile) {\n setUploadingVideo(true);\n try {\n videoFileId = await uploadFile(videoFile);\n } catch (err) {\n setDrillError(err instanceof Error ? err.message : 'Failed to upload the demo video.');\n setUploadingVideo(false);\n return;\n }\n setUploadingVideo(false);\n }\n\n try {\n await addDrill.mutateAsync({\n programId: id,\n name: drillForm.name.trim(),\n description: drillForm.description.trim() || undefined,\n weekNumber: week,\n orderIndex: program.data?.drills?.items.filter((d) => d.weekNumber === week).length ?? 0,\n sets: numOrUndefined(drillForm.sets),\n reps: numOrUndefined(drillForm.reps),\n durationSec: numOrUndefined(drillForm.durationSec),\n restSec: numOrUndefined(drillForm.restSec),\n videoFileId,\n });\n setDrillForm(EMPTY_DRILL);\n setVideoFile(null);\n setDrillOpen(false);\n } catch (err) {\n setDrillError(err instanceof Error ? err.message : 'Failed to add drill.');\n }\n };\n\n return (\n <PageLayout\n title={p.name}\n gradientTitle\n description={`${SPORT_LABELS[p.sport]} · ${PROGRAM_LEVEL_LABELS[p.level]} · ${p.weeks} ${\n p.weeks === 1 ? 'week' : 'weeks'\n }`}\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/programs')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" />{' '}\n {tr('atheletebridge.pages.programDetail.back', 'Back')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/programs/${p.id}/edit`)}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <Edit className=\"h-4 w-4\" /> {tr('atheletebridge.pages.programDetail.edit', 'Edit')}\n </button>\n {p.status !== 'ARCHIVED' && (\n <button\n type=\"button\"\n onClick={handleArchive}\n disabled={archive.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:opacity-60\"\n >\n {archive.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Archive className=\"h-4 w-4\" />\n )}{' '}\n {tr('atheletebridge.pages.programDetail.archive', 'Archive')}\n </button>\n )}\n </div>\n }\n >\n {/* Summary + lifecycle */}\n <GlassCard treatment=\"insight\" className=\"mb-8 p-6\">\n <div className=\"flex items-start gap-4\">\n <div className=\"flex h-14 w-14 shrink-0 items-center justify-center rounded-2xl bg-[var(--color-accent-soft)] text-text-primary\">\n <SportIcon className=\"h-7 w-7\" aria-hidden=\"true\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <Badge variant=\"secondary\">{SPORT_LABELS[p.sport]}</Badge>\n <Badge variant=\"secondary\">{PROGRAM_LEVEL_LABELS[p.level]}</Badge>\n <StatusBadge status={p.status} />\n {p.isTemplate && (\n <span className=\"inline-flex items-center gap-1 text-xs text-text-secondary\">\n <Copy className=\"h-3.5 w-3.5\" /> Template\n </span>\n )}\n </div>\n <p className=\"mt-2 text-sm text-text-secondary\">\n {p.weeks} {p.weeks === 1 ? 'week' : 'weeks'} · {drillCount}{' '}\n {drillCount === 1 ? 'drill' : 'drills'}\n </p>\n </div>\n </div>\n {p.summary && <p className=\"mt-4 text-sm text-text-secondary\">{p.summary}</p>}\n {p.tags.length > 0 && (\n <div className=\"mt-4 flex flex-wrap gap-1.5\">\n {p.tags.map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\"\n >\n {tag}\n </span>\n ))}\n </div>\n )}\n\n {/* Lifecycle transitions */}\n <div className=\"mt-5 flex flex-wrap items-center gap-2 border-t border-border-seam pt-5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('atheletebridge.pages.programDetail.lifecycle', 'Publish lifecycle:')}\n </span>\n {p.status === 'DRAFT' && (\n <button\n type=\"button\"\n onClick={() => handleTransition('PUBLISHED')}\n disabled={transition.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-60\"\n >\n <Send className=\"h-4 w-4\" />{' '}\n {tr('atheletebridge.pages.programDetail.publish', 'Publish')}\n </button>\n )}\n {p.status === 'PUBLISHED' && (\n <>\n <button\n type=\"button\"\n onClick={() => handleTransition('DRAFT')}\n disabled={transition.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:opacity-60\"\n >\n <Undo2 className=\"h-4 w-4\" />{' '}\n {tr('atheletebridge.pages.programDetail.pullBackToDraft', 'Pull back to draft')}\n </button>\n <button\n type=\"button\"\n onClick={() => handleTransition('ARCHIVED')}\n disabled={transition.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:opacity-60\"\n >\n <Archive className=\"h-4 w-4\" />{' '}\n {tr('atheletebridge.pages.programDetail.archiveAction', 'Archive')}\n </button>\n </>\n )}\n {p.status === 'ARCHIVED' && (\n <span className=\"text-sm text-text-secondary\">\n {tr('atheletebridge.pages.programDetail.archived', 'This program is archived.')}\n </span>\n )}\n {transition.isPending && <Loader2 className=\"h-4 w-4 animate-spin text-text-secondary\" />}\n </div>\n </GlassCard>\n\n {/* Drills */}\n <section>\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('atheletebridge.pages.programDetail.drillsTitle', 'Drills')} ({drillCount})\n </h2>\n <button\n type=\"button\"\n onClick={() => {\n setDrillError(null);\n setDrillOpen(true);\n }}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" />{' '}\n {tr('atheletebridge.pages.programDetail.addDrill', 'Add drill')}\n </button>\n </div>\n\n {drillCount === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Plus className=\"h-10 w-10\" />}\n title={tr('atheletebridge.pages.programDetail.noDrillsTitle', 'No drills yet')}\n description={tr(\n 'atheletebridge.pages.programDetail.noDrillsHint',\n 'Add drills with sets, reps and durations, organized by week.'\n )}\n />\n ) : (\n <Accordion type=\"multiple\" defaultValue={openWeeks} className=\"flex flex-col gap-3\">\n {drillsByWeek.map(({ week, drills }) => (\n <GlassCard key={week} treatment=\"insight\" className=\"overflow-hidden px-4 py-0\">\n <AccordionItem value={`week-${week}`} className=\"border-b-0\">\n <AccordionTrigger className=\"py-4\">\n <span className=\"flex items-center gap-2.5\">\n <span className=\"text-sm font-semibold text-text-primary\">Week {week}</span>\n <Badge variant=\"secondary\">\n {drills.length} {drills.length === 1 ? 'drill' : 'drills'}\n </Badge>\n </span>\n </AccordionTrigger>\n <AccordionContent className=\"pb-2\">\n <div className=\"divide-y divide-border-seam\">\n {drills.map((d) => (\n <div key={d.id} className=\"flex items-start gap-4 py-3\">\n <div className=\"min-w-0 flex-1\">\n <p className=\"text-sm font-medium text-text-primary\">{d.name}</p>\n {d.description && (\n <p className=\"mt-0.5 text-xs text-text-secondary\">{d.description}</p>\n )}\n <div className=\"mt-2 flex flex-wrap gap-1.5\">\n {d.sets !== null && <MetricChip>{d.sets} sets</MetricChip>}\n {d.reps !== null && <MetricChip>{d.reps} reps</MetricChip>}\n {d.durationSec !== null && (\n <MetricChip>{d.durationSec}s work</MetricChip>\n )}\n {d.restSec !== null && <MetricChip>{d.restSec}s rest</MetricChip>}\n </div>\n {d.video?.id && (\n <div>\n <DrillVideoLink fileId={d.video.id} />\n </div>\n )}\n </div>\n <button\n type=\"button\"\n onClick={() => void removeDrill.mutate(d.id)}\n disabled={removeDrill.isPending}\n aria-label={`${tr('common.delete', 'Delete')} ${d.name}`}\n className=\"rounded-lg p-2 text-text-secondary transition-colors hover:bg-status-error-bg-subtle hover:text-status-error-text disabled:opacity-60\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n ))}\n </div>\n </AccordionContent>\n </AccordionItem>\n </GlassCard>\n ))}\n </Accordion>\n )}\n </section>\n\n {/* AI decisions recorded against this program (BOFF-3574) */}\n <SubjectDecisions className=\"mt-8\" subject={{ kind: 'program', programId: p.id }} />\n\n {/* Add drill dialog */}\n <ResponsiveDialog open={drillOpen} onOpenChange={setDrillOpen}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n {tr('atheletebridge.pages.programDetail.addDrill', 'Add drill')}\n </ResponsiveDialogTitle>\n </ResponsiveDialogHeader>\n <form onSubmit={handleAddDrill} className=\"space-y-4 px-1\">\n <FormField label=\"Name\" htmlFor=\"drillName\" required>\n <input\n id=\"drillName\"\n className={fieldInputClass}\n value={drillForm.name}\n onChange={(e) => setDrillForm((p) => ({ ...p, name: e.target.value }))}\n placeholder=\"e.g. Box jumps\"\n />\n </FormField>\n <FormField label=\"Description\" htmlFor=\"drillDesc\">\n <textarea\n id=\"drillDesc\"\n className={fieldInputClass}\n rows={2}\n value={drillForm.description}\n onChange={(e) => setDrillForm((p) => ({ ...p, description: e.target.value }))}\n placeholder=\"How to perform the drill.\"\n />\n </FormField>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <FormField label=\"Week\" htmlFor=\"drillWeek\">\n <input\n id=\"drillWeek\"\n inputMode=\"numeric\"\n className={fieldInputClass}\n value={drillForm.weekNumber}\n onChange={(e) => setDrillForm((p) => ({ ...p, weekNumber: e.target.value }))}\n />\n </FormField>\n <FormField label=\"Sets\" htmlFor=\"drillSets\">\n <input\n id=\"drillSets\"\n inputMode=\"numeric\"\n className={fieldInputClass}\n value={drillForm.sets}\n onChange={(e) => setDrillForm((p) => ({ ...p, sets: e.target.value }))}\n />\n </FormField>\n <FormField label=\"Reps\" htmlFor=\"drillReps\">\n <input\n id=\"drillReps\"\n inputMode=\"numeric\"\n className={fieldInputClass}\n value={drillForm.reps}\n onChange={(e) => setDrillForm((p) => ({ ...p, reps: e.target.value }))}\n />\n </FormField>\n </div>\n <div className=\"grid grid-cols-2 gap-4\">\n <FormField label=\"Work (sec)\" htmlFor=\"drillDuration\">\n <input\n id=\"drillDuration\"\n inputMode=\"numeric\"\n className={fieldInputClass}\n value={drillForm.durationSec}\n onChange={(e) => setDrillForm((p) => ({ ...p, durationSec: e.target.value }))}\n />\n </FormField>\n <FormField label=\"Rest (sec)\" htmlFor=\"drillRest\">\n <input\n id=\"drillRest\"\n inputMode=\"numeric\"\n className={fieldInputClass}\n value={drillForm.restSec}\n onChange={(e) => setDrillForm((p) => ({ ...p, restSec: e.target.value }))}\n />\n </FormField>\n </div>\n <FormField\n label=\"Demo video\"\n htmlFor=\"drillVideo\"\n hint=\"Optional — attach a short demo clip; it's stored in workspace files.\"\n >\n <input\n id=\"drillVideo\"\n type=\"file\"\n accept=\"video/*\"\n className={fieldInputClass}\n onChange={(e) => setVideoFile(e.target.files?.[0] ?? null)}\n />\n {videoFile && (\n <p className=\"mt-1 inline-flex items-center gap-1 text-xs text-text-secondary\">\n <FileVideo className=\"h-3.5 w-3.5\" /> {videoFile.name}\n </p>\n )}\n </FormField>\n {drillError && <p className=\"text-sm text-status-error-text\">{drillError}</p>}\n <ResponsiveDialogFooter>\n <button\n type=\"button\"\n onClick={() => {\n setDrillOpen(false);\n setVideoFile(null);\n }}\n className=\"rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n disabled={addDrill.isPending || uploadingVideo}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-60\"\n >\n {(addDrill.isPending || uploadingVideo) && (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n )}\n {uploadingVideo\n ? tr('atheletebridge.pages.programDetail.uploadingVideo', 'Uploading video…')\n : tr('atheletebridge.pages.programDetail.addDrill', 'Add drill')}\n </button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"TeamDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/atheletebridge/pages/teams/TeamDetailPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAmHH,wBAAgB,cAAc,gCA+rB7B"}
1
+ {"version":3,"file":"TeamDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/atheletebridge/pages/teams/TeamDetailPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAoHH,wBAAgB,cAAc,gCAksB7B"}