@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.
- package/dist/atheletebridge/components/ai/DecisionCard.d.ts.map +1 -1
- package/dist/atheletebridge/components/ai/DecisionCard.js +217 -122
- package/dist/atheletebridge/components/ai/DecisionCard.js.map +1 -1
- package/dist/atheletebridge/components/ai/SubjectDecisions.d.ts +29 -0
- package/dist/atheletebridge/components/ai/SubjectDecisions.d.ts.map +1 -0
- package/dist/atheletebridge/components/ai/SubjectDecisions.js +69 -0
- package/dist/atheletebridge/components/ai/SubjectDecisions.js.map +1 -0
- package/dist/atheletebridge/components/ai/index.d.ts +2 -0
- package/dist/atheletebridge/components/ai/index.d.ts.map +1 -1
- package/dist/atheletebridge/hooks/useAthleteBridgeApi.d.ts +10 -1
- package/dist/atheletebridge/hooks/useAthleteBridgeApi.d.ts.map +1 -1
- package/dist/atheletebridge/hooks/useAthleteBridgeApi.js +24 -15
- package/dist/atheletebridge/hooks/useAthleteBridgeApi.js.map +1 -1
- package/dist/atheletebridge/pages/athletes/AthleteProfilePage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/athletes/AthleteProfilePage.js +135 -118
- package/dist/atheletebridge/pages/athletes/AthleteProfilePage.js.map +1 -1
- package/dist/atheletebridge/pages/programs/ProgramDetailPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/programs/ProgramDetailPage.js +42 -34
- package/dist/atheletebridge/pages/programs/ProgramDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamDetailPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamDetailPage.js +65 -57
- package/dist/atheletebridge/pages/teams/TeamDetailPage.js.map +1 -1
- package/dist/atheletebridge/utils/aiDecision.d.ts +12 -0
- package/dist/atheletebridge/utils/aiDecision.d.ts.map +1 -1
- package/dist/atheletebridge/utils/aiDecision.js +11 -1
- package/dist/atheletebridge/utils/aiDecision.js.map +1 -1
- 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;
|
|
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
|
|
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 {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
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 } =
|
|
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(
|
|
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(
|
|
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,
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
239
|
+
}) : /* @__PURE__ */ x(pe, {
|
|
239
240
|
type: "multiple",
|
|
240
|
-
defaultValue:
|
|
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(
|
|
246
|
+
children: /* @__PURE__ */ S(he, {
|
|
246
247
|
value: `week-${e}`,
|
|
247
248
|
className: "border-b-0",
|
|
248
|
-
children: [/* @__PURE__ */ x(
|
|
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(
|
|
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(
|
|
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(
|
|
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:
|
|
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
|
|
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(
|
|
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(
|
|
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;
|
|
1
|
+
{"version":3,"file":"TeamDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/atheletebridge/pages/teams/TeamDetailPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAoHH,wBAAgB,cAAc,gCAksB7B"}
|