@burdenoff/microfe-atheletebridge 2026.705.2 → 2026.706.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/dist/atheletebridge/AthleteBridgeRoot.js +55 -39
  2. package/dist/atheletebridge/AthleteBridgeRoot.js.map +1 -1
  3. package/dist/atheletebridge/AthleteBridgeRoutes.js +18 -6
  4. package/dist/atheletebridge/AthleteBridgeRoutes.js.map +1 -1
  5. package/dist/atheletebridge/components/Crest.js +70 -0
  6. package/dist/atheletebridge/components/Crest.js.map +1 -0
  7. package/dist/atheletebridge/components/FixtureCard.js +8 -72
  8. package/dist/atheletebridge/components/FixtureCard.js.map +1 -1
  9. package/dist/atheletebridge/components/MatchupRow.js +74 -0
  10. package/dist/atheletebridge/components/MatchupRow.js.map +1 -0
  11. package/dist/atheletebridge/components/MetricsCard.js +61 -21
  12. package/dist/atheletebridge/components/MetricsCard.js.map +1 -1
  13. package/dist/atheletebridge/components/ProgramCard.js +75 -43
  14. package/dist/atheletebridge/components/ProgramCard.js.map +1 -1
  15. package/dist/atheletebridge/components/Scoreboard.js +163 -0
  16. package/dist/atheletebridge/components/Scoreboard.js.map +1 -0
  17. package/dist/atheletebridge/components/SessionCard.js +64 -41
  18. package/dist/atheletebridge/components/SessionCard.js.map +1 -1
  19. package/dist/atheletebridge/components/SessionStatusTimeline.js +80 -0
  20. package/dist/atheletebridge/components/SessionStatusTimeline.js.map +1 -0
  21. package/dist/atheletebridge/components/StatusBadge.js +5 -5
  22. package/dist/atheletebridge/components/StatusBadge.js.map +1 -1
  23. package/dist/atheletebridge/components/TeamCard.js +65 -35
  24. package/dist/atheletebridge/components/TeamCard.js.map +1 -1
  25. package/dist/atheletebridge/components/UserAvatar.js +33 -0
  26. package/dist/atheletebridge/components/UserAvatar.js.map +1 -0
  27. package/dist/atheletebridge/components/domain.js +5 -2
  28. package/dist/atheletebridge/components/domain.js.map +1 -1
  29. package/dist/atheletebridge/components/viz/AvatarStack.js +2 -0
  30. package/dist/atheletebridge/components/viz/BarChart.js +83 -0
  31. package/dist/atheletebridge/components/viz/BarChart.js.map +1 -0
  32. package/dist/atheletebridge/components/viz/DonutChart.js +109 -0
  33. package/dist/atheletebridge/components/viz/DonutChart.js.map +1 -0
  34. package/dist/atheletebridge/components/viz/LineChart.js +123 -0
  35. package/dist/atheletebridge/components/viz/LineChart.js.map +1 -0
  36. package/dist/atheletebridge/components/viz/ProgressRing.js +6 -0
  37. package/dist/atheletebridge/components/viz/ProgressRing.js.map +1 -0
  38. package/dist/atheletebridge/components/viz/RatingStars.js +92 -0
  39. package/dist/atheletebridge/components/viz/RatingStars.js.map +1 -0
  40. package/dist/atheletebridge/components/viz/Sparkline.js +74 -0
  41. package/dist/atheletebridge/components/viz/Sparkline.js.map +1 -0
  42. package/dist/atheletebridge/components/viz/StatCard.js +60 -0
  43. package/dist/atheletebridge/components/viz/StatCard.js.map +1 -0
  44. package/dist/atheletebridge/components/viz/StepProgress.js +3 -0
  45. package/dist/atheletebridge/components/viz/Timeline.js +66 -0
  46. package/dist/atheletebridge/components/viz/Timeline.js.map +1 -0
  47. package/dist/atheletebridge/components/viz/index.js +13 -0
  48. package/dist/atheletebridge/components/viz/lib/Avatar.js +1 -0
  49. package/dist/atheletebridge/components/viz/lib/CartesianFrame.js +119 -0
  50. package/dist/atheletebridge/components/viz/lib/CartesianFrame.js.map +1 -0
  51. package/dist/atheletebridge/components/viz/lib/ResponsiveSvg.js +35 -0
  52. package/dist/atheletebridge/components/viz/lib/ResponsiveSvg.js.map +1 -0
  53. package/dist/atheletebridge/components/viz/lib/format.js +17 -0
  54. package/dist/atheletebridge/components/viz/lib/format.js.map +1 -0
  55. package/dist/atheletebridge/components/viz/lib/geometry.js +61 -0
  56. package/dist/atheletebridge/components/viz/lib/geometry.js.map +1 -0
  57. package/dist/atheletebridge/components/viz/lib/hooks.js +39 -0
  58. package/dist/atheletebridge/components/viz/lib/hooks.js.map +1 -0
  59. package/dist/atheletebridge/components/viz/lib/palette.js +27 -0
  60. package/dist/atheletebridge/components/viz/lib/palette.js.map +1 -0
  61. package/dist/atheletebridge/hooks/useAthleteBridgeApi.js +369 -163
  62. package/dist/atheletebridge/hooks/useAthleteBridgeApi.js.map +1 -1
  63. package/dist/atheletebridge/pages/DashboardPage.js +182 -117
  64. package/dist/atheletebridge/pages/DashboardPage.js.map +1 -1
  65. package/dist/atheletebridge/pages/StandingsPage.js +119 -95
  66. package/dist/atheletebridge/pages/StandingsPage.js.map +1 -1
  67. package/dist/atheletebridge/pages/analytics/CoachAnalyticsPage.js +372 -0
  68. package/dist/atheletebridge/pages/analytics/CoachAnalyticsPage.js.map +1 -0
  69. package/dist/atheletebridge/pages/athletes/AthleteProfilePage.js +511 -0
  70. package/dist/atheletebridge/pages/athletes/AthleteProfilePage.js.map +1 -0
  71. package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js +120 -131
  72. package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js.map +1 -1
  73. package/dist/atheletebridge/pages/fixtures/FixtureFormPage.js +264 -176
  74. package/dist/atheletebridge/pages/fixtures/FixtureFormPage.js.map +1 -1
  75. package/dist/atheletebridge/pages/fixtures/FixturesListPage.js +84 -47
  76. package/dist/atheletebridge/pages/fixtures/FixturesListPage.js.map +1 -1
  77. package/dist/atheletebridge/pages/performance/PerformancePage.js +432 -281
  78. package/dist/atheletebridge/pages/performance/PerformancePage.js.map +1 -1
  79. package/dist/atheletebridge/pages/programs/ProgramDetailPage.js +187 -123
  80. package/dist/atheletebridge/pages/programs/ProgramDetailPage.js.map +1 -1
  81. package/dist/atheletebridge/pages/programs/ProgramsListPage.js +56 -39
  82. package/dist/atheletebridge/pages/programs/ProgramsListPage.js.map +1 -1
  83. package/dist/atheletebridge/pages/sessions/SessionDetailPage.js +267 -139
  84. package/dist/atheletebridge/pages/sessions/SessionDetailPage.js.map +1 -1
  85. package/dist/atheletebridge/pages/sessions/SessionFormPage.js +253 -162
  86. package/dist/atheletebridge/pages/sessions/SessionFormPage.js.map +1 -1
  87. package/dist/atheletebridge/pages/sessions/SessionsListPage.js +38 -36
  88. package/dist/atheletebridge/pages/sessions/SessionsListPage.js.map +1 -1
  89. package/dist/atheletebridge/pages/teams/TeamDetailPage.js +437 -189
  90. package/dist/atheletebridge/pages/teams/TeamDetailPage.js.map +1 -1
  91. package/dist/atheletebridge/pages/teams/TeamFormPage.js +368 -177
  92. package/dist/atheletebridge/pages/teams/TeamFormPage.js.map +1 -1
  93. package/dist/atheletebridge/pages/teams/TeamsListPage.js +3 -0
  94. package/dist/atheletebridge/pages/teams/TeamsListPage.js.map +1 -1
  95. package/dist/atheletebridge/pages/tournaments/TournamentBracketPage.js +298 -0
  96. package/dist/atheletebridge/pages/tournaments/TournamentBracketPage.js.map +1 -0
  97. package/dist/atheletebridge/utils/identity.js +31 -0
  98. package/dist/atheletebridge/utils/identity.js.map +1 -0
  99. package/dist/atheletebridge/utils/timeAgo.js +20 -0
  100. package/dist/atheletebridge/utils/timeAgo.js.map +1 -0
  101. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"SessionCard.js","names":[],"sources":["../../../src/atheletebridge/components/SessionCard.tsx"],"sourcesContent":["/**\n * Session Card\n * @module atheletebridge/components\n */\n\nimport type { FC } from 'react';\nimport { CalendarClock, Clock, Star, Video, MapPin } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { StatusBadge } from './StatusBadge';\nimport { SESSION_TYPE_LABELS } from './domain';\nimport { formatDate, formatDuration } from '../utils/formatters';\nimport { cn } from '../utils/cn';\nimport type { AthSession } from '../types';\n\ninterface SessionCardProps {\n session: AthSession;\n onClick?: () => void;\n}\n\nexport const SessionCard: FC<SessionCardProps> = ({ session, onClick }) => (\n <GlassCard\n treatment=\"metric\"\n role=\"button\"\n tabIndex={0}\n onClick={onClick}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onClick?.();\n }\n }}\n className={cn(\n 'cursor-pointer p-5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]'\n )}\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <h3 className=\"truncate text-base font-semibold text-text-primary\">{session.title}</h3>\n <p className=\"text-sm text-text-secondary\">{SESSION_TYPE_LABELS[session.type]}</p>\n </div>\n <StatusBadge status={session.status} />\n </div>\n\n <div className=\"mt-4 flex flex-wrap items-center gap-x-4 gap-y-1.5 text-xs text-text-secondary\">\n <span className=\"flex items-center gap-1\">\n <CalendarClock className=\"h-3.5 w-3.5\" /> {formatDate(session.scheduledAt)}\n </span>\n <span className=\"flex items-center gap-1\">\n <Clock className=\"h-3.5 w-3.5\" /> {formatDuration(session.durationMin)}\n </span>\n {session.type === 'IN_PERSON' && session.location && (\n <span className=\"flex items-center gap-1 truncate\">\n <MapPin className=\"h-3.5 w-3.5 shrink-0\" /> {session.location}\n </span>\n )}\n {session.type !== 'IN_PERSON' && session.meetingUrl && (\n <span className=\"flex items-center gap-1\">\n <Video className=\"h-3.5 w-3.5\" /> Online\n </span>\n )}\n {session.rating !== null && (\n <span className=\"flex items-center gap-1\">\n <Star className=\"h-3.5 w-3.5\" /> {session.rating}/5\n </span>\n )}\n </div>\n </GlassCard>\n);\n"],"mappings":";;;;;;;;AAmBA,IAAa,KAAqC,EAAE,YAAS,iBAC3D,kBAAC,GAAD;CACE,WAAU;CACV,MAAK;CACL,UAAU;CACD;CACT,YAAY,MAAM;AAChB,GAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,SACjC,EAAE,gBAAgB,EAClB,KAAW;;CAGf,WAAW,EACT,kHACD;WAbH,CAeE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,MAAD;IAAI,WAAU;cAAsD,EAAQ;IAAW,CAAA,EACvF,kBAAC,KAAD;IAAG,WAAU;cAA+B,EAAoB,EAAQ;IAAU,CAAA,CAC9E;MACN,kBAAC,GAAD,EAAa,QAAQ,EAAQ,QAAU,CAAA,CACnC;KAEN,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,QAAD;IAAM,WAAU;cAAhB;KACE,kBAAC,GAAD,EAAe,WAAU,eAAgB,CAAA;;KAAE,EAAW,EAAQ,YAAY;KACrE;;GACP,kBAAC,QAAD;IAAM,WAAU;cAAhB;KACE,kBAAC,GAAD,EAAO,WAAU,eAAgB,CAAA;;KAAE,EAAe,EAAQ,YAAY;KACjE;;GACN,EAAQ,SAAS,eAAe,EAAQ,YACvC,kBAAC,QAAD;IAAM,WAAU;cAAhB;KACE,kBAAC,GAAD,EAAQ,WAAU,wBAAyB,CAAA;;KAAE,EAAQ;KAChD;;GAER,EAAQ,SAAS,eAAe,EAAQ,cACvC,kBAAC,QAAD;IAAM,WAAU;cAAhB,CACE,kBAAC,GAAD,EAAO,WAAU,eAAgB,CAAA,EAAA,UAC5B;;GAER,EAAQ,WAAW,QAClB,kBAAC,QAAD;IAAM,WAAU;cAAhB;KACE,kBAAC,GAAD,EAAM,WAAU,eAAgB,CAAA;;KAAE,EAAQ;KAAO;KAC5C;;GAEL;IACI"}
1
+ {"version":3,"file":"SessionCard.js","names":[],"sources":["../../../src/atheletebridge/components/SessionCard.tsx"],"sourcesContent":["/**\n * Session Card\n * @module atheletebridge/components\n *\n * A coaching-session tile showing the athlete identity (monogram avatar +\n * resolved name when available), session type, schedule, and a colored status\n * badge (Requested amber / Confirmed blue / Completed green / Cancelled grey).\n * A stored rating renders as read-only stars. Semantic tokens only.\n */\n\nimport type { FC } from 'react';\nimport { CalendarClock, Clock, Video, MapPin } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { StatusBadge } from './StatusBadge';\nimport { UserAvatar } from './UserAvatar';\nimport { RatingStars } from './viz';\nimport { SESSION_TYPE_LABELS } from './domain';\nimport { formatDate, formatDuration } from '../utils/formatters';\nimport { cn } from '../utils/cn';\nimport type { AthSession } from '../types';\n\ninterface SessionCardProps {\n session: AthSession;\n onClick?: () => void;\n /** Resolved athlete display name (else a monogram from the id is shown). */\n athleteName?: string | null;\n /** Resolved coach display name (optional sub-label). */\n coachName?: string | null;\n}\n\nexport const SessionCard: FC<SessionCardProps> = ({\n session,\n onClick,\n athleteName,\n coachName,\n}) => (\n <GlassCard\n treatment=\"metric\"\n role=\"button\"\n tabIndex={0}\n onClick={onClick}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onClick?.();\n }\n }}\n className={cn(\n 'cursor-pointer p-5 transition-transform duration-200 hover:-translate-y-0.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]'\n )}\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex min-w-0 items-center gap-3\">\n <UserAvatar name={athleteName} id={session.athleteId} size=\"md\" />\n <div className=\"min-w-0\">\n <h3 className=\"truncate text-base font-semibold text-text-primary\">{session.title}</h3>\n <p className=\"truncate text-sm text-text-secondary\">\n {athleteName?.trim() || 'Athlete'} · {SESSION_TYPE_LABELS[session.type]}\n {coachName?.trim() ? ` · ${coachName.trim()}` : ''}\n </p>\n </div>\n </div>\n <StatusBadge status={session.status} />\n </div>\n\n <div className=\"mt-4 flex flex-wrap items-center gap-x-4 gap-y-1.5 text-xs text-text-secondary\">\n <span className=\"flex items-center gap-1\">\n <CalendarClock className=\"h-3.5 w-3.5\" aria-hidden=\"true\" /> {formatDate(session.scheduledAt)}\n </span>\n <span className=\"flex items-center gap-1\">\n <Clock className=\"h-3.5 w-3.5\" aria-hidden=\"true\" /> {formatDuration(session.durationMin)}\n </span>\n {session.type === 'IN_PERSON' && session.location && (\n <span className=\"flex items-center gap-1 truncate\">\n <MapPin className=\"h-3.5 w-3.5 shrink-0\" aria-hidden=\"true\" /> {session.location}\n </span>\n )}\n {session.type !== 'IN_PERSON' && session.meetingUrl && (\n <span className=\"flex items-center gap-1\">\n <Video className=\"h-3.5 w-3.5\" aria-hidden=\"true\" /> Online\n </span>\n )}\n {session.rating !== null && (\n <RatingStars value={session.rating} size={14} showValue />\n )}\n </div>\n </GlassCard>\n);\n"],"mappings":";;;;;;;;;;;AA8BA,IAAa,KAAqC,EAChD,YACA,YACA,gBACA,mBAEA,kBAAC,GAAD;CACE,WAAU;CACV,MAAK;CACL,UAAU;CACD;CACT,YAAY,MAAM;AAChB,GAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,SACjC,EAAE,gBAAgB,EAClB,KAAW;;CAGf,WAAW,EACT,2KACD;WAbH,CAeE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD;IAAY,MAAM;IAAa,IAAI,EAAQ;IAAW,MAAK;IAAO,CAAA,EAClE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,MAAD;KAAI,WAAU;eAAsD,EAAQ;KAAW,CAAA,EACvF,kBAAC,KAAD;KAAG,WAAU;eAAb;MACG,GAAa,MAAM,IAAI;MAAU;MAAI,EAAoB,EAAQ;MACjE,GAAW,MAAM,GAAG,MAAM,EAAU,MAAM,KAAK;MAC9C;OACA;MACF;MACN,kBAAC,GAAD,EAAa,QAAQ,EAAQ,QAAU,CAAA,CACnC;KAEN,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,QAAD;IAAM,WAAU;cAAhB;KACE,kBAAC,GAAD;MAAe,WAAU;MAAc,eAAY;MAAS,CAAA;;KAAE,EAAW,EAAQ,YAAY;KACxF;;GACP,kBAAC,QAAD;IAAM,WAAU;cAAhB;KACE,kBAAC,GAAD;MAAO,WAAU;MAAc,eAAY;MAAS,CAAA;;KAAE,EAAe,EAAQ,YAAY;KACpF;;GACN,EAAQ,SAAS,eAAe,EAAQ,YACvC,kBAAC,QAAD;IAAM,WAAU;cAAhB;KACE,kBAAC,GAAD;MAAQ,WAAU;MAAuB,eAAY;MAAS,CAAA;;KAAE,EAAQ;KACnE;;GAER,EAAQ,SAAS,eAAe,EAAQ,cACvC,kBAAC,QAAD;IAAM,WAAU;cAAhB,CACE,kBAAC,GAAD;KAAO,WAAU;KAAc,eAAY;KAAS,CAAA,EAAA,UAC/C;;GAER,EAAQ,WAAW,QAClB,kBAAC,GAAD;IAAa,OAAO,EAAQ;IAAQ,MAAM;IAAI,WAAA;IAAY,CAAA;GAExD;IACI"}
@@ -0,0 +1,80 @@
1
+ import { cn as e } from "../utils/cn.js";
2
+ import { formatDate as t } from "../utils/formatters.js";
3
+ import { Check as n, X as r } from "lucide-react";
4
+ import { jsx as i, jsxs as a } from "react/jsx-runtime";
5
+ import { Stepper as o } from "@burdenoff/fe-libs/ui";
6
+ //#region src/atheletebridge/components/SessionStatusTimeline.tsx
7
+ var s = (e) => {
8
+ if (e) return t(e, {
9
+ weekday: "short",
10
+ year: void 0
11
+ });
12
+ }, c = ({ status: t, createdAt: c, completedAt: l, className: u }) => {
13
+ if (t === "CANCELLED" || t === "NO_SHOW") {
14
+ let o = t === "CANCELLED" ? "Cancelled" : "No-show";
15
+ return /* @__PURE__ */ a("ol", {
16
+ className: e("flex items-start gap-2", u),
17
+ "aria-label": "Session lifecycle",
18
+ children: [/* @__PURE__ */ a("li", {
19
+ className: "flex flex-1 items-center gap-3",
20
+ children: [
21
+ /* @__PURE__ */ i("span", {
22
+ "aria-hidden": "true",
23
+ className: "inline-flex size-7 shrink-0 items-center justify-center rounded-full border border-action-primary-bg bg-action-primary-bg text-action-primary-text",
24
+ children: /* @__PURE__ */ i(n, { className: "size-4" })
25
+ }),
26
+ /* @__PURE__ */ a("span", {
27
+ className: "min-w-0",
28
+ children: [/* @__PURE__ */ i("span", {
29
+ className: "block text-sm font-medium text-text-primary",
30
+ children: "Requested"
31
+ }), s(c) ? /* @__PURE__ */ i("span", {
32
+ className: "block text-xs text-text-muted",
33
+ children: s(c)
34
+ }) : null]
35
+ }),
36
+ /* @__PURE__ */ i("span", {
37
+ "aria-hidden": "true",
38
+ className: "h-px flex-1 bg-status-error-border"
39
+ })
40
+ ]
41
+ }), /* @__PURE__ */ a("li", {
42
+ className: "flex items-center gap-3",
43
+ "aria-current": "step",
44
+ children: [/* @__PURE__ */ i("span", {
45
+ "aria-hidden": "true",
46
+ className: "inline-flex size-7 shrink-0 items-center justify-center rounded-full border border-status-error-border bg-status-error-bg-subtle text-status-error-text",
47
+ children: /* @__PURE__ */ i(r, { className: "size-4" })
48
+ }), /* @__PURE__ */ i("span", {
49
+ className: "text-sm font-medium text-status-error-text",
50
+ children: o
51
+ })]
52
+ })]
53
+ });
54
+ }
55
+ let d = [
56
+ {
57
+ key: "requested",
58
+ label: "Requested",
59
+ description: s(c)
60
+ },
61
+ {
62
+ key: "confirmed",
63
+ label: "Confirmed"
64
+ },
65
+ {
66
+ key: "completed",
67
+ label: "Completed",
68
+ description: s(l)
69
+ }
70
+ ];
71
+ return /* @__PURE__ */ i(o, {
72
+ steps: d,
73
+ active: t === "REQUESTED" ? 0 : t === "CONFIRMED" ? 1 : d.length,
74
+ className: u
75
+ });
76
+ };
77
+ //#endregion
78
+ export { c as SessionStatusTimeline };
79
+
80
+ //# sourceMappingURL=SessionStatusTimeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SessionStatusTimeline.js","names":[],"sources":["../../../src/atheletebridge/components/SessionStatusTimeline.tsx"],"sourcesContent":["/**\n * SessionStatusTimeline\n * @module atheletebridge/components\n *\n * A horizontal lifecycle stepper for a coaching session:\n * Requested → Confirmed → Completed\n * with a distinct terminal variant when the session was Cancelled / No-show.\n * The happy path reuses the fe-libs Stepper; timestamps are shown ONLY where the\n * schema actually has them (createdAt for Requested, completedAt for Completed)\n * — Confirmed has no dedicated timestamp field, so none is fabricated.\n * Semantic tokens only.\n */\n\nimport type { FC } from 'react';\nimport { Check, X } from 'lucide-react';\nimport { Stepper, type StepperStep } from '@burdenoff/fe-libs/ui';\nimport { formatDate } from '../utils/formatters';\nimport { cn } from '../utils/cn';\nimport type { AthSessionStatus } from '../types';\n\nexport interface SessionStatusTimelineProps {\n status: AthSessionStatus;\n /** Requested-at timestamp (session.createdAt). */\n createdAt?: string | null;\n /** Completed-at timestamp (session.completedAt). */\n completedAt?: string | null;\n className?: string;\n}\n\nconst shortWhen = (iso: string | null | undefined): string | undefined => {\n if (!iso) return undefined;\n return formatDate(iso, { weekday: 'short', year: undefined });\n};\n\n/** Lifecycle stepper for a coaching session (with cancelled/no-show terminal). */\nexport const SessionStatusTimeline: FC<SessionStatusTimelineProps> = ({\n status,\n createdAt,\n completedAt,\n className,\n}) => {\n // ── Terminal (cancelled / no-show) ─────────────────────────────────────────\n if (status === 'CANCELLED' || status === 'NO_SHOW') {\n const terminalLabel = status === 'CANCELLED' ? 'Cancelled' : 'No-show';\n return (\n <ol\n className={cn('flex items-start gap-2', className)}\n aria-label=\"Session lifecycle\"\n >\n <li className=\"flex flex-1 items-center gap-3\">\n <span\n aria-hidden=\"true\"\n className=\"inline-flex size-7 shrink-0 items-center justify-center rounded-full border border-action-primary-bg bg-action-primary-bg text-action-primary-text\"\n >\n <Check className=\"size-4\" />\n </span>\n <span className=\"min-w-0\">\n <span className=\"block text-sm font-medium text-text-primary\">Requested</span>\n {shortWhen(createdAt) ? (\n <span className=\"block text-xs text-text-muted\">{shortWhen(createdAt)}</span>\n ) : null}\n </span>\n <span\n aria-hidden=\"true\"\n className=\"h-px flex-1 bg-status-error-border\"\n />\n </li>\n <li className=\"flex items-center gap-3\" aria-current=\"step\">\n <span\n aria-hidden=\"true\"\n className=\"inline-flex size-7 shrink-0 items-center justify-center rounded-full border border-status-error-border bg-status-error-bg-subtle text-status-error-text\"\n >\n <X className=\"size-4\" />\n </span>\n <span className=\"text-sm font-medium text-status-error-text\">{terminalLabel}</span>\n </li>\n </ol>\n );\n }\n\n // ── Happy path ─────────────────────────────────────────────────────────────\n const steps: StepperStep[] = [\n { key: 'requested', label: 'Requested', description: shortWhen(createdAt) },\n { key: 'confirmed', label: 'Confirmed' },\n { key: 'completed', label: 'Completed', description: shortWhen(completedAt) },\n ];\n\n // active = index of the in-progress step; past `steps.length` marks all done.\n const active = status === 'REQUESTED' ? 0 : status === 'CONFIRMED' ? 1 : steps.length;\n\n return <Stepper steps={steps} active={active} className={className} />;\n};\n"],"mappings":";;;;;;AA6BA,IAAM,KAAa,MAAuD;AACnE,OACL,QAAO,EAAW,GAAK;EAAE,SAAS;EAAS,MAAM,KAAA;EAAW,CAAC;GAIlD,KAAyD,EACpE,WACA,cACA,gBACA,mBACI;AAEJ,KAAI,MAAW,eAAe,MAAW,WAAW;EAClD,IAAM,IAAgB,MAAW,cAAc,cAAc;AAC7D,SACE,kBAAC,MAAD;GACE,WAAW,EAAG,0BAA0B,EAAU;GAClD,cAAW;aAFb,CAIE,kBAAC,MAAD;IAAI,WAAU;cAAd;KACE,kBAAC,QAAD;MACE,eAAY;MACZ,WAAU;gBAEV,kBAAC,GAAD,EAAO,WAAU,UAAW,CAAA;MACvB,CAAA;KACP,kBAAC,QAAD;MAAM,WAAU;gBAAhB,CACE,kBAAC,QAAD;OAAM,WAAU;iBAA8C;OAAgB,CAAA,EAC7E,EAAU,EAAU,GACnB,kBAAC,QAAD;OAAM,WAAU;iBAAiC,EAAU,EAAU;OAAQ,CAAA,GAC3E,KACC;;KACP,kBAAC,QAAD;MACE,eAAY;MACZ,WAAU;MACV,CAAA;KACC;OACL,kBAAC,MAAD;IAAI,WAAU;IAA0B,gBAAa;cAArD,CACE,kBAAC,QAAD;KACE,eAAY;KACZ,WAAU;eAEV,kBAAC,GAAD,EAAG,WAAU,UAAW,CAAA;KACnB,CAAA,EACP,kBAAC,QAAD;KAAM,WAAU;eAA8C;KAAqB,CAAA,CAChF;MACF;;;CAKT,IAAM,IAAuB;EAC3B;GAAE,KAAK;GAAa,OAAO;GAAa,aAAa,EAAU,EAAU;GAAE;EAC3E;GAAE,KAAK;GAAa,OAAO;GAAa;EACxC;GAAE,KAAK;GAAa,OAAO;GAAa,aAAa,EAAU,EAAY;GAAE;EAC9E;AAKD,QAAO,kBAAC,GAAD;EAAgB;EAAe,QAFvB,MAAW,cAAc,IAAI,MAAW,cAAc,IAAI,EAAM;EAEtB;EAAa,CAAA"}
@@ -17,7 +17,7 @@ var r = {
17
17
  forming: "warning",
18
18
  active: "success",
19
19
  inactive: "secondary",
20
- archived: "outline",
20
+ archived: "secondary",
21
21
  trial: "info",
22
22
  injured: "warning",
23
23
  suspended: "destructive",
@@ -38,11 +38,11 @@ var r = {
38
38
  function o(e) {
39
39
  return e.replace(/_/g, " ").toLowerCase().replace(/\b\w/g, (e) => e.toUpperCase());
40
40
  }
41
- var s = ({ status: s, className: c }) => {
42
- let l = s.toLowerCase(), u = a[l] ?? "secondary", d = i.has(l);
41
+ var s = ({ status: s, variant: c, className: l }) => {
42
+ let u = s.toLowerCase(), d = c ?? a[u] ?? "secondary", f = i.has(u);
43
43
  return /* @__PURE__ */ n("span", {
44
- className: e("inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium", r[u], c),
45
- children: [d && /* @__PURE__ */ t("span", {
44
+ className: e("inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium", r[d], l),
45
+ children: [f && /* @__PURE__ */ t("span", {
46
46
  "aria-hidden": "true",
47
47
  className: "size-1.5 rounded-full bg-current motion-safe:animate-vc-glow-pulse"
48
48
  }), o(s)]
@@ -1 +1 @@
1
- {"version":3,"file":"StatusBadge.js","names":[],"sources":["../../../src/atheletebridge/components/StatusBadge.tsx"],"sourcesContent":["/**\n * Status Badge Component\n * @module atheletebridge/components\n *\n * A colored badge based on a domain status value, with semantic-token color\n * mapping for the AthleteBridge lifecycle states (teams, members, fixtures,\n * programs, sessions, workouts). Uses semantic tokens only — never raw colors.\n */\n\nimport type { FC } from 'react';\nimport { cn } from '../utils/cn';\n\ninterface StatusBadgeProps {\n status: string;\n className?: string;\n}\n\ntype BadgeVariant = 'success' | 'warning' | 'destructive' | 'secondary' | 'info' | 'outline';\n\nconst variantStyles: Record<BadgeVariant, string> = {\n success: 'bg-status-success-bg-subtle text-status-success-text',\n warning: 'bg-status-warning-bg-subtle text-status-warning-text',\n destructive: 'bg-status-error-bg-subtle text-status-error-text',\n secondary: 'bg-bg-sunken text-text-secondary',\n info: 'bg-[var(--color-accent-soft)] text-text-primary',\n outline: 'border border-border-seam text-text-primary',\n};\n\n// Statuses that represent a \"live\" / in-progress condition get a subtle,\n// opacity-only glow pulse (reduced-motion safe via motion-safe:).\nconst liveStatuses = new Set(['live', 'active', 'confirmed', 'in_progress']);\n\nconst statusVariantMap: Record<string, BadgeVariant> = {\n // Team status\n forming: 'warning',\n active: 'success',\n inactive: 'secondary',\n archived: 'outline',\n\n // Member status\n trial: 'info',\n injured: 'warning',\n suspended: 'destructive',\n released: 'secondary',\n\n // Fixture status\n scheduled: 'info',\n live: 'success',\n completed: 'success',\n postponed: 'warning',\n cancelled: 'destructive',\n\n // Program status\n draft: 'secondary',\n published: 'success',\n\n // Session status\n requested: 'warning',\n confirmed: 'info',\n no_show: 'destructive',\n\n // Workout status\n planned: 'info',\n skipped: 'secondary',\n};\n\nfunction formatStatusLabel(status: string): string {\n return status\n .replace(/_/g, ' ')\n .toLowerCase()\n .replace(/\\b\\w/g, (char) => char.toUpperCase());\n}\n\nexport const StatusBadge: FC<StatusBadgeProps> = ({ status, className }) => {\n const key = status.toLowerCase();\n const variant = statusVariantMap[key] ?? 'secondary';\n const isLive = liveStatuses.has(key);\n\n return (\n <span\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium',\n variantStyles[variant],\n className\n )}\n >\n {isLive && (\n <span\n aria-hidden=\"true\"\n className=\"size-1.5 rounded-full bg-current motion-safe:animate-vc-glow-pulse\"\n />\n )}\n {formatStatusLabel(status)}\n </span>\n );\n};\n"],"mappings":";;;AAmBA,IAAM,IAA8C;CAClD,SAAS;CACT,SAAS;CACT,aAAa;CACb,WAAW;CACX,MAAM;CACN,SAAS;CACV,EAIK,IAAe,IAAI,IAAI;CAAC;CAAQ;CAAU;CAAa;CAAc,CAAC,EAEtE,IAAiD;CAErD,SAAS;CACT,QAAQ;CACR,UAAU;CACV,UAAU;CAGV,OAAO;CACP,SAAS;CACT,WAAW;CACX,UAAU;CAGV,WAAW;CACX,MAAM;CACN,WAAW;CACX,WAAW;CACX,WAAW;CAGX,OAAO;CACP,WAAW;CAGX,WAAW;CACX,WAAW;CACX,SAAS;CAGT,SAAS;CACT,SAAS;CACV;AAED,SAAS,EAAkB,GAAwB;AACjD,QAAO,EACJ,QAAQ,MAAM,IAAI,CAClB,aAAa,CACb,QAAQ,UAAU,MAAS,EAAK,aAAa,CAAC;;AAGnD,IAAa,KAAqC,EAAE,WAAQ,mBAAgB;CAC1E,IAAM,IAAM,EAAO,aAAa,EAC1B,IAAU,EAAiB,MAAQ,aACnC,IAAS,EAAa,IAAI,EAAI;AAEpC,QACE,kBAAC,QAAD;EACE,WAAW,EACT,mFACA,EAAc,IACd,EACD;YALH,CAOG,KACC,kBAAC,QAAD;GACE,eAAY;GACZ,WAAU;GACV,CAAA,EAEH,EAAkB,EAAO,CACrB"}
1
+ {"version":3,"file":"StatusBadge.js","names":[],"sources":["../../../src/atheletebridge/components/StatusBadge.tsx"],"sourcesContent":["/**\n * Status Badge Component\n * @module atheletebridge/components\n *\n * A colored badge based on a domain status value, with semantic-token color\n * mapping for the AthleteBridge lifecycle states (teams, members, fixtures,\n * programs, sessions, workouts). Uses semantic tokens only — never raw colors.\n */\n\nimport type { FC } from 'react';\nimport { cn } from '../utils/cn';\n\ntype BadgeVariant = 'success' | 'warning' | 'destructive' | 'secondary' | 'info' | 'outline';\n\ninterface StatusBadgeProps {\n status: string;\n /**\n * Force a specific variant, overriding the auto status→color mapping. Useful\n * where the same status string reads differently by context (e.g. a cancelled\n * coaching session shown grey rather than the default destructive red).\n */\n variant?: BadgeVariant;\n className?: string;\n}\n\nconst variantStyles: Record<BadgeVariant, string> = {\n success: 'bg-status-success-bg-subtle text-status-success-text',\n warning: 'bg-status-warning-bg-subtle text-status-warning-text',\n destructive: 'bg-status-error-bg-subtle text-status-error-text',\n secondary: 'bg-bg-sunken text-text-secondary',\n info: 'bg-[var(--color-accent-soft)] text-text-primary',\n outline: 'border border-border-seam text-text-primary',\n};\n\n// Statuses that represent a \"live\" / in-progress condition get a subtle,\n// opacity-only glow pulse (reduced-motion safe via motion-safe:).\nconst liveStatuses = new Set(['live', 'active', 'confirmed', 'in_progress']);\n\nconst statusVariantMap: Record<string, BadgeVariant> = {\n // Team status\n forming: 'warning',\n active: 'success',\n inactive: 'secondary',\n archived: 'secondary',\n\n // Member status\n trial: 'info',\n injured: 'warning',\n suspended: 'destructive',\n released: 'secondary',\n\n // Fixture status\n scheduled: 'info',\n live: 'success',\n completed: 'success',\n postponed: 'warning',\n cancelled: 'destructive',\n\n // Program status\n draft: 'secondary',\n published: 'success',\n\n // Session status\n requested: 'warning',\n confirmed: 'info',\n no_show: 'destructive',\n\n // Workout status\n planned: 'info',\n skipped: 'secondary',\n};\n\nfunction formatStatusLabel(status: string): string {\n return status\n .replace(/_/g, ' ')\n .toLowerCase()\n .replace(/\\b\\w/g, (char) => char.toUpperCase());\n}\n\nexport const StatusBadge: FC<StatusBadgeProps> = ({ status, variant: variantOverride, className }) => {\n const key = status.toLowerCase();\n const variant = variantOverride ?? statusVariantMap[key] ?? 'secondary';\n const isLive = liveStatuses.has(key);\n\n return (\n <span\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium',\n variantStyles[variant],\n className\n )}\n >\n {isLive && (\n <span\n aria-hidden=\"true\"\n className=\"size-1.5 rounded-full bg-current motion-safe:animate-vc-glow-pulse\"\n />\n )}\n {formatStatusLabel(status)}\n </span>\n );\n};\n"],"mappings":";;;AAyBA,IAAM,IAA8C;CAClD,SAAS;CACT,SAAS;CACT,aAAa;CACb,WAAW;CACX,MAAM;CACN,SAAS;CACV,EAIK,IAAe,IAAI,IAAI;CAAC;CAAQ;CAAU;CAAa;CAAc,CAAC,EAEtE,IAAiD;CAErD,SAAS;CACT,QAAQ;CACR,UAAU;CACV,UAAU;CAGV,OAAO;CACP,SAAS;CACT,WAAW;CACX,UAAU;CAGV,WAAW;CACX,MAAM;CACN,WAAW;CACX,WAAW;CACX,WAAW;CAGX,OAAO;CACP,WAAW;CAGX,WAAW;CACX,WAAW;CACX,SAAS;CAGT,SAAS;CACT,SAAS;CACV;AAED,SAAS,EAAkB,GAAwB;AACjD,QAAO,EACJ,QAAQ,MAAM,IAAI,CAClB,aAAa,CACb,QAAQ,UAAU,MAAS,EAAK,aAAa,CAAC;;AAGnD,IAAa,KAAqC,EAAE,WAAQ,SAAS,GAAiB,mBAAgB;CACpG,IAAM,IAAM,EAAO,aAAa,EAC1B,IAAU,KAAmB,EAAiB,MAAQ,aACtD,IAAS,EAAa,IAAI,EAAI;AAEpC,QACE,kBAAC,QAAD;EACE,WAAW,EACT,mFACA,EAAc,IACd,EACD;YALH,CAOG,KACC,kBAAC,QAAD;GACE,eAAY;GACZ,WAAU;GACV,CAAA,EAEH,EAAkB,EAAO,CACrB"}
@@ -1,81 +1,111 @@
1
1
  import { cn as e } from "../utils/cn.js";
2
2
  import { StatusBadge as t } from "./StatusBadge.js";
3
3
  import { SPORT_ICONS as n, SPORT_LABELS as r } from "./domain.js";
4
- import { CalendarDays as i, MapPin as a, Users as o } from "lucide-react";
5
- import { jsx as s, jsxs as c } from "react/jsx-runtime";
6
- import { GlassCard as l } from "@burdenoff/fe-libs/ui";
4
+ import { Crest as i } from "./Crest.js";
5
+ import { CalendarDays as a, MapPin as o, Users as s } from "lucide-react";
6
+ import { jsx as c, jsxs as l } from "react/jsx-runtime";
7
+ import { Badge as u, GlassCard as d } from "@burdenoff/fe-libs/ui";
7
8
  //#region src/atheletebridge/components/TeamCard.tsx
8
- var u = ({ team: u, onClick: d }) => {
9
- let f = n[u.sport];
10
- return /* @__PURE__ */ c(l, {
9
+ var f = ({ team: f, onClick: p, memberCount: m }) => {
10
+ let h = n[f.sport], g = m ?? f.members?.length;
11
+ return /* @__PURE__ */ l(d, {
11
12
  treatment: "metric",
12
13
  role: "button",
13
14
  tabIndex: 0,
14
- onClick: d,
15
+ onClick: p,
15
16
  onKeyDown: (e) => {
16
- (e.key === "Enter" || e.key === " ") && (e.preventDefault(), d?.());
17
+ (e.key === "Enter" || e.key === " ") && (e.preventDefault(), p?.());
17
18
  },
18
- className: e("cursor-pointer p-5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]"),
19
+ className: e("cursor-pointer p-5 transition-transform duration-200 hover:-translate-y-0.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]"),
19
20
  children: [
20
- /* @__PURE__ */ c("div", {
21
+ /* @__PURE__ */ l("div", {
21
22
  className: "flex items-start justify-between gap-3",
22
- children: [/* @__PURE__ */ c("div", {
23
- className: "flex items-center gap-3 min-w-0",
24
- children: [/* @__PURE__ */ s("div", {
25
- className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-text-primary",
26
- children: /* @__PURE__ */ s(f, { className: "h-5 w-5" })
27
- }), /* @__PURE__ */ c("div", {
23
+ children: [/* @__PURE__ */ l("div", {
24
+ className: "flex min-w-0 items-center gap-3",
25
+ children: [/* @__PURE__ */ c(i, {
26
+ name: f.name,
27
+ sport: f.sport,
28
+ size: 44
29
+ }), /* @__PURE__ */ l("div", {
28
30
  className: "min-w-0",
29
- children: [/* @__PURE__ */ s("h3", {
31
+ children: [/* @__PURE__ */ c("h3", {
30
32
  className: "truncate text-base font-semibold text-text-primary",
31
- children: u.name
32
- }), /* @__PURE__ */ s("p", {
33
- className: "text-sm text-text-secondary",
34
- children: r[u.sport]
33
+ children: f.name
34
+ }), /* @__PURE__ */ c("div", {
35
+ className: "mt-1",
36
+ children: /* @__PURE__ */ l(u, {
37
+ variant: "secondary",
38
+ className: "gap-1",
39
+ children: [/* @__PURE__ */ c(h, {
40
+ className: "h-3 w-3",
41
+ "aria-hidden": "true"
42
+ }), r[f.sport]]
43
+ })
35
44
  })]
36
45
  })]
37
- }), /* @__PURE__ */ s(t, { status: u.status })]
46
+ }), /* @__PURE__ */ c(t, { status: f.status })]
38
47
  }),
39
- /* @__PURE__ */ c("div", {
48
+ /* @__PURE__ */ l("div", {
40
49
  className: "mt-4 flex flex-wrap items-center gap-x-4 gap-y-1.5 text-xs text-text-secondary",
41
50
  children: [
42
- u.ageGroup && /* @__PURE__ */ c("span", {
51
+ f.ageGroup && /* @__PURE__ */ l("span", {
43
52
  className: "flex items-center gap-1",
44
53
  children: [
45
- /* @__PURE__ */ s(o, { className: "h-3.5 w-3.5" }),
54
+ /* @__PURE__ */ c(s, {
55
+ className: "h-3.5 w-3.5",
56
+ "aria-hidden": "true"
57
+ }),
46
58
  " ",
47
- u.ageGroup
59
+ f.ageGroup
48
60
  ]
49
61
  }),
50
- u.season && /* @__PURE__ */ c("span", {
62
+ f.season && /* @__PURE__ */ l("span", {
51
63
  className: "flex items-center gap-1",
52
64
  children: [
53
- /* @__PURE__ */ s(i, { className: "h-3.5 w-3.5" }),
65
+ /* @__PURE__ */ c(a, {
66
+ className: "h-3.5 w-3.5",
67
+ "aria-hidden": "true"
68
+ }),
54
69
  " ",
55
- u.season
70
+ f.season
56
71
  ]
57
72
  }),
58
- u.homeVenue && /* @__PURE__ */ c("span", {
73
+ f.homeVenue && /* @__PURE__ */ l("span", {
59
74
  className: "flex items-center gap-1 truncate",
60
75
  children: [
61
- /* @__PURE__ */ s(a, { className: "h-3.5 w-3.5 shrink-0" }),
76
+ /* @__PURE__ */ c(o, {
77
+ className: "h-3.5 w-3.5 shrink-0",
78
+ "aria-hidden": "true"
79
+ }),
62
80
  " ",
63
- u.homeVenue
81
+ f.homeVenue
64
82
  ]
65
83
  })
66
84
  ]
67
85
  }),
68
- u.tags.length > 0 && /* @__PURE__ */ s("div", {
86
+ f.tags.length > 0 && /* @__PURE__ */ c("div", {
69
87
  className: "mt-3 flex flex-wrap gap-1.5",
70
- children: u.tags.slice(0, 4).map((e) => /* @__PURE__ */ s("span", {
88
+ children: f.tags.slice(0, 4).map((e) => /* @__PURE__ */ c("span", {
71
89
  className: "inline-flex items-center rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary",
72
90
  children: e
73
91
  }, e))
92
+ }),
93
+ g !== void 0 && /* @__PURE__ */ l("div", {
94
+ className: "mt-4 flex items-center gap-1.5 border-t border-border-seam pt-3 text-xs font-medium text-text-secondary",
95
+ children: [
96
+ /* @__PURE__ */ c(s, {
97
+ className: "h-3.5 w-3.5",
98
+ "aria-hidden": "true"
99
+ }),
100
+ g,
101
+ " ",
102
+ g === 1 ? "member" : "members"
103
+ ]
74
104
  })
75
105
  ]
76
106
  });
77
107
  };
78
108
  //#endregion
79
- export { u as TeamCard };
109
+ export { f as TeamCard };
80
110
 
81
111
  //# sourceMappingURL=TeamCard.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"TeamCard.js","names":[],"sources":["../../../src/atheletebridge/components/TeamCard.tsx"],"sourcesContent":["/**\n * Team Card\n * @module atheletebridge/components\n */\n\nimport type { FC } from 'react';\nimport { Users, MapPin, CalendarDays } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { StatusBadge } from './StatusBadge';\nimport { SPORT_ICONS, SPORT_LABELS } from './domain';\nimport { cn } from '../utils/cn';\nimport type { AthTeam } from '../types';\n\ninterface TeamCardProps {\n team: AthTeam;\n onClick?: () => void;\n}\n\nexport const TeamCard: FC<TeamCardProps> = ({ team, onClick }) => {\n const SportIcon = SPORT_ICONS[team.sport];\n\n return (\n <GlassCard\n treatment=\"metric\"\n role=\"button\"\n tabIndex={0}\n onClick={onClick}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onClick?.();\n }\n }}\n className={cn(\n 'cursor-pointer p-5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]'\n )}\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-text-primary\">\n <SportIcon className=\"h-5 w-5\" />\n </div>\n <div className=\"min-w-0\">\n <h3 className=\"truncate text-base font-semibold text-text-primary\">{team.name}</h3>\n <p className=\"text-sm text-text-secondary\">{SPORT_LABELS[team.sport]}</p>\n </div>\n </div>\n <StatusBadge status={team.status} />\n </div>\n\n <div className=\"mt-4 flex flex-wrap items-center gap-x-4 gap-y-1.5 text-xs text-text-secondary\">\n {team.ageGroup && (\n <span className=\"flex items-center gap-1\">\n <Users className=\"h-3.5 w-3.5\" /> {team.ageGroup}\n </span>\n )}\n {team.season && (\n <span className=\"flex items-center gap-1\">\n <CalendarDays className=\"h-3.5 w-3.5\" /> {team.season}\n </span>\n )}\n {team.homeVenue && (\n <span className=\"flex items-center gap-1 truncate\">\n <MapPin className=\"h-3.5 w-3.5 shrink-0\" /> {team.homeVenue}\n </span>\n )}\n </div>\n\n {team.tags.length > 0 && (\n <div className=\"mt-3 flex flex-wrap gap-1.5\">\n {team.tags.slice(0, 4).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 </GlassCard>\n );\n};\n"],"mappings":";;;;;;;AAkBA,IAAa,KAA+B,EAAE,SAAM,iBAAc;CAChE,IAAM,IAAY,EAAY,EAAK;AAEnC,QACE,kBAAC,GAAD;EACE,WAAU;EACV,MAAK;EACL,UAAU;EACD;EACT,YAAY,MAAM;AAChB,IAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,SACjC,EAAE,gBAAgB,EAClB,KAAW;;EAGf,WAAW,EACT,kHACD;YAbH;GAeE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA;MAC7B,CAAA,EACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,MAAD;OAAI,WAAU;iBAAsD,EAAK;OAAU,CAAA,EACnF,kBAAC,KAAD;OAAG,WAAU;iBAA+B,EAAa,EAAK;OAAW,CAAA,CACrE;QACF;QACN,kBAAC,GAAD,EAAa,QAAQ,EAAK,QAAU,CAAA,CAChC;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf;KACG,EAAK,YACJ,kBAAC,QAAD;MAAM,WAAU;gBAAhB;OACE,kBAAC,GAAD,EAAO,WAAU,eAAgB,CAAA;;OAAE,EAAK;OACnC;;KAER,EAAK,UACJ,kBAAC,QAAD;MAAM,WAAU;gBAAhB;OACE,kBAAC,GAAD,EAAc,WAAU,eAAgB,CAAA;;OAAE,EAAK;OAC1C;;KAER,EAAK,aACJ,kBAAC,QAAD;MAAM,WAAU;gBAAhB;OACE,kBAAC,GAAD,EAAQ,WAAU,wBAAyB,CAAA;;OAAE,EAAK;OAC7C;;KAEL;;GAEL,EAAK,KAAK,SAAS,KAClB,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAK,KAAK,MAAM,GAAG,EAAE,CAAC,KAAK,MAC1B,kBAAC,QAAD;KAEE,WAAU;eAET;KACI,EAJA,EAIA,CACP;IACE,CAAA;GAEE"}
1
+ {"version":3,"file":"TeamCard.js","names":[],"sources":["../../../src/atheletebridge/components/TeamCard.tsx"],"sourcesContent":["/**\n * Team Card\n * @module atheletebridge/components\n *\n * A team tile fronted by a deterministic gradient monogram crest, with a sport\n * Badge, a StatusBadge (green Active / amber Forming / grey Inactive·Archived),\n * a member-count chip and a hover lift. Semantic tokens only.\n */\n\nimport type { FC } from 'react';\nimport { Users, MapPin, CalendarDays } from 'lucide-react';\nimport { GlassCard, Badge } from '@burdenoff/fe-libs/ui';\nimport { StatusBadge } from './StatusBadge';\nimport { Crest } from './Crest';\nimport { SPORT_ICONS, SPORT_LABELS } from './domain';\nimport { cn } from '../utils/cn';\nimport type { AthTeam } from '../types';\n\ninterface TeamCardProps {\n team: AthTeam;\n onClick?: () => void;\n /**\n * Member count for the footer chip. Falls back to `team.members?.length`\n * when the list query selected members; omit the chip entirely if neither is\n * available (never guess).\n */\n memberCount?: number;\n}\n\nexport const TeamCard: FC<TeamCardProps> = ({ team, onClick, memberCount }) => {\n const SportIcon = SPORT_ICONS[team.sport];\n const count = memberCount ?? team.members?.length;\n\n return (\n <GlassCard\n treatment=\"metric\"\n role=\"button\"\n tabIndex={0}\n onClick={onClick}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onClick?.();\n }\n }}\n className={cn(\n 'cursor-pointer p-5 transition-transform duration-200 hover:-translate-y-0.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]'\n )}\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex min-w-0 items-center gap-3\">\n <Crest name={team.name} sport={team.sport} size={44} />\n <div className=\"min-w-0\">\n <h3 className=\"truncate text-base font-semibold text-text-primary\">{team.name}</h3>\n <div className=\"mt-1\">\n <Badge variant=\"secondary\" className=\"gap-1\">\n <SportIcon className=\"h-3 w-3\" aria-hidden=\"true\" />\n {SPORT_LABELS[team.sport]}\n </Badge>\n </div>\n </div>\n </div>\n <StatusBadge status={team.status} />\n </div>\n\n <div className=\"mt-4 flex flex-wrap items-center gap-x-4 gap-y-1.5 text-xs text-text-secondary\">\n {team.ageGroup && (\n <span className=\"flex items-center gap-1\">\n <Users className=\"h-3.5 w-3.5\" aria-hidden=\"true\" /> {team.ageGroup}\n </span>\n )}\n {team.season && (\n <span className=\"flex items-center gap-1\">\n <CalendarDays className=\"h-3.5 w-3.5\" aria-hidden=\"true\" /> {team.season}\n </span>\n )}\n {team.homeVenue && (\n <span className=\"flex items-center gap-1 truncate\">\n <MapPin className=\"h-3.5 w-3.5 shrink-0\" aria-hidden=\"true\" /> {team.homeVenue}\n </span>\n )}\n </div>\n\n {team.tags.length > 0 && (\n <div className=\"mt-3 flex flex-wrap gap-1.5\">\n {team.tags.slice(0, 4).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 {count !== undefined && (\n <div className=\"mt-4 flex items-center gap-1.5 border-t border-border-seam pt-3 text-xs font-medium text-text-secondary\">\n <Users className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n {count} {count === 1 ? 'member' : 'members'}\n </div>\n )}\n </GlassCard>\n );\n};\n"],"mappings":";;;;;;;;AA6BA,IAAa,KAA+B,EAAE,SAAM,YAAS,qBAAkB;CAC7E,IAAM,IAAY,EAAY,EAAK,QAC7B,IAAQ,KAAe,EAAK,SAAS;AAE3C,QACE,kBAAC,GAAD;EACE,WAAU;EACV,MAAK;EACL,UAAU;EACD;EACT,YAAY,MAAM;AAChB,IAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,SACjC,EAAE,gBAAgB,EAClB,KAAW;;EAGf,WAAW,EACT,2KACD;YAbH;GAeE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MAAO,MAAM,EAAK;MAAM,OAAO,EAAK;MAAO,MAAM;MAAM,CAAA,EACvD,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,MAAD;OAAI,WAAU;iBAAsD,EAAK;OAAU,CAAA,EACnF,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,GAAD;QAAO,SAAQ;QAAY,WAAU;kBAArC,CACE,kBAAC,GAAD;SAAW,WAAU;SAAU,eAAY;SAAS,CAAA,EACnD,EAAa,EAAK,OACb;;OACJ,CAAA,CACF;QACF;QACN,kBAAC,GAAD,EAAa,QAAQ,EAAK,QAAU,CAAA,CAChC;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf;KACG,EAAK,YACJ,kBAAC,QAAD;MAAM,WAAU;gBAAhB;OACE,kBAAC,GAAD;QAAO,WAAU;QAAc,eAAY;QAAS,CAAA;;OAAE,EAAK;OACtD;;KAER,EAAK,UACJ,kBAAC,QAAD;MAAM,WAAU;gBAAhB;OACE,kBAAC,GAAD;QAAc,WAAU;QAAc,eAAY;QAAS,CAAA;;OAAE,EAAK;OAC7D;;KAER,EAAK,aACJ,kBAAC,QAAD;MAAM,WAAU;gBAAhB;OACE,kBAAC,GAAD;QAAQ,WAAU;QAAuB,eAAY;QAAS,CAAA;;OAAE,EAAK;OAChE;;KAEL;;GAEL,EAAK,KAAK,SAAS,KAClB,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAK,KAAK,MAAM,GAAG,EAAE,CAAC,KAAK,MAC1B,kBAAC,QAAD;KAEE,WAAU;eAET;KACI,EAJA,EAIA,CACP;IACE,CAAA;GAGP,MAAU,KAAA,KACT,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD;MAAO,WAAU;MAAc,eAAY;MAAS,CAAA;KACnD;KAAM;KAAE,MAAU,IAAI,WAAW;KAC9B;;GAEE"}
@@ -0,0 +1,33 @@
1
+ import { cn as e } from "../utils/cn.js";
2
+ import { gradientCss as t, initials as n } from "../utils/identity.js";
3
+ import { jsx as r } from "react/jsx-runtime";
4
+ //#region src/atheletebridge/components/UserAvatar.tsx
5
+ var i = {
6
+ xs: 24,
7
+ sm: 32,
8
+ md: 40,
9
+ lg: 56,
10
+ xl: 72
11
+ }, a = ({ name: a, id: o, jersey: s, size: c = "md", ring: l = !1, className: u }) => {
12
+ let d = typeof c == "number" ? c : i[c], f = a?.trim() || (o ? `User ${o.slice(0, 6)}` : "Unknown user"), p = a?.trim() || o || f, m = typeof s == "number" && Number.isFinite(s) ? String(s) : n(f);
13
+ return /* @__PURE__ */ r("span", {
14
+ role: "img",
15
+ "aria-label": f,
16
+ title: f,
17
+ className: e("inline-flex shrink-0 items-center justify-center rounded-full font-semibold leading-none text-[var(--color-action-primary-text)]", l && "ring-2 ring-[var(--color-bg-surface)]", u),
18
+ style: {
19
+ width: d,
20
+ height: d,
21
+ fontSize: Math.max(9, Math.round(d * .4)),
22
+ background: t(p)
23
+ },
24
+ children: /* @__PURE__ */ r("span", {
25
+ "aria-hidden": "true",
26
+ children: m
27
+ })
28
+ });
29
+ };
30
+ //#endregion
31
+ export { a as UserAvatar };
32
+
33
+ //# sourceMappingURL=UserAvatar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UserAvatar.js","names":[],"sources":["../../../src/atheletebridge/components/UserAvatar.tsx"],"sourcesContent":["/**\n * User avatar (monogram)\n * @module atheletebridge/components\n *\n * A circular avatar for a user / athlete / coach. There is no photo field on the\n * schema, so this is an honest initials monogram over a deterministic gradient\n * derived from the resolved name (or the id as a stable fallback). Semantic\n * tokens only.\n */\n\nimport type { FC } from 'react';\nimport { cn } from '../utils/cn';\nimport { gradientCss, initials } from '../utils/identity';\n\nconst SIZE_MAP = { xs: 24, sm: 32, md: 40, lg: 56, xl: 72 } as const;\n\nexport type UserAvatarSize = keyof typeof SIZE_MAP | number;\n\nexport interface UserAvatarProps {\n /** Resolved display name (drives initials + gradient seed). */\n name?: string | null;\n /** Stable id — used as the gradient seed and initials fallback when no name. */\n id?: string | null;\n /** Optional jersey number rendered instead of initials (roster context). */\n jersey?: number | null;\n /** Named size token or explicit pixel diameter. */\n size?: UserAvatarSize;\n /** Extra ring (used inside overlapping stacks / on colored surfaces). */\n ring?: boolean;\n className?: string;\n}\n\n/** Circular monogram avatar for a user/athlete/coach (initials or jersey #). */\nexport const UserAvatar: FC<UserAvatarProps> = ({\n name,\n id,\n jersey,\n size = 'md',\n ring = false,\n className,\n}) => {\n const px = typeof size === 'number' ? size : SIZE_MAP[size];\n const label = name?.trim() || (id ? `User ${id.slice(0, 6)}` : 'Unknown user');\n const seed = name?.trim() || id || label;\n const content =\n typeof jersey === 'number' && Number.isFinite(jersey) ? String(jersey) : initials(label);\n\n return (\n <span\n role=\"img\"\n aria-label={label}\n title={label}\n className={cn(\n 'inline-flex shrink-0 items-center justify-center rounded-full font-semibold leading-none text-[var(--color-action-primary-text)]',\n ring && 'ring-2 ring-[var(--color-bg-surface)]',\n className\n )}\n style={{\n width: px,\n height: px,\n fontSize: Math.max(9, Math.round(px * 0.4)),\n background: gradientCss(seed),\n }}\n >\n <span aria-hidden=\"true\">{content}</span>\n </span>\n );\n};\n"],"mappings":";;;;AAcA,IAAM,IAAW;CAAE,IAAI;CAAI,IAAI;CAAI,IAAI;CAAI,IAAI;CAAI,IAAI;CAAI,EAmB9C,KAAmC,EAC9C,SACA,OACA,WACA,UAAO,MACP,UAAO,IACP,mBACI;CACJ,IAAM,IAAK,OAAO,KAAS,WAAW,IAAO,EAAS,IAChD,IAAQ,GAAM,MAAM,KAAK,IAAK,QAAQ,EAAG,MAAM,GAAG,EAAE,KAAK,iBACzD,IAAO,GAAM,MAAM,IAAI,KAAM,GAC7B,IACJ,OAAO,KAAW,YAAY,OAAO,SAAS,EAAO,GAAG,OAAO,EAAO,GAAG,EAAS,EAAM;AAE1F,QACE,kBAAC,QAAD;EACE,MAAK;EACL,cAAY;EACZ,OAAO;EACP,WAAW,EACT,oIACA,KAAQ,yCACR,EACD;EACD,OAAO;GACL,OAAO;GACP,QAAQ;GACR,UAAU,KAAK,IAAI,GAAG,KAAK,MAAM,IAAK,GAAI,CAAC;GAC3C,YAAY,EAAY,EAAK;GAC9B;YAED,kBAAC,QAAD;GAAM,eAAY;aAAQ;GAAe,CAAA;EACpC,CAAA"}
@@ -49,8 +49,11 @@ var s = {
49
49
  WEIGHT: "Weight",
50
50
  HEART_RATE: "Heart Rate",
51
51
  CUSTOM: "Custom"
52
- };
52
+ }, m = Object.keys(d);
53
+ function h(e) {
54
+ return e.replace(/_/g, " ").toLowerCase().replace(/\b\w/g, (e) => e.toUpperCase());
55
+ }
53
56
  //#endregion
54
- export { d as FIXTURE_TYPE_LABELS, u as MEMBER_ROLE_LABELS, p as METRIC_TYPE_LABELS, l as PROGRAM_LEVEL_LABELS, f as SESSION_TYPE_LABELS, s as SPORT_ICONS, c as SPORT_LABELS };
57
+ export { d as FIXTURE_TYPE_LABELS, m as FIXTURE_TYPE_OPTIONS, u as MEMBER_ROLE_LABELS, p as METRIC_TYPE_LABELS, l as PROGRAM_LEVEL_LABELS, f as SESSION_TYPE_LABELS, s as SPORT_ICONS, c as SPORT_LABELS, h as humanizeEnum };
55
58
 
56
59
  //# sourceMappingURL=domain.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"domain.js","names":[],"sources":["../../../src/atheletebridge/components/domain.ts"],"sourcesContent":["/**\n * Domain display helpers\n * @module atheletebridge/components\n *\n * Centralized, type-safe labels and icon mappings for the AthleteBridge\n * enums so list/detail/form pages render them consistently.\n */\n\nimport {\n Activity,\n Dumbbell,\n Trophy,\n Volleyball,\n Waves,\n Gamepad2,\n Footprints,\n type LucideIcon,\n} from 'lucide-react';\nimport type {\n AthFixtureType,\n AthMemberRole,\n AthMetricType,\n AthProgramLevel,\n AthSessionType,\n AthSport,\n} from '../types';\n\nexport const SPORT_ICONS: Record<AthSport, LucideIcon> = {\n ATHLETICS: Footprints,\n BASKETBALL: Volleyball,\n CRICKET: Trophy,\n ESPORTS: Gamepad2,\n FOOTBALL: Volleyball,\n OTHER: Activity,\n SWIMMING: Waves,\n TENNIS: Dumbbell,\n};\n\nexport const SPORT_LABELS: Record<AthSport, string> = {\n ATHLETICS: 'Athletics',\n BASKETBALL: 'Basketball',\n CRICKET: 'Cricket',\n ESPORTS: 'Esports',\n FOOTBALL: 'Football',\n OTHER: 'Other',\n SWIMMING: 'Swimming',\n TENNIS: 'Tennis',\n};\n\nexport const PROGRAM_LEVEL_LABELS: Record<AthProgramLevel, string> = {\n BEGINNER: 'Beginner',\n INTERMEDIATE: 'Intermediate',\n ADVANCED: 'Advanced',\n ELITE: 'Elite',\n};\n\nexport const MEMBER_ROLE_LABELS: Record<AthMemberRole, string> = {\n PLAYER: 'Player',\n CAPTAIN: 'Captain',\n COACH: 'Coach',\n ASSISTANT_COACH: 'Assistant Coach',\n PHYSIO: 'Physio',\n MANAGER: 'Manager',\n};\n\nexport const FIXTURE_TYPE_LABELS: Record<AthFixtureType, string> = {\n FRIENDLY: 'Friendly',\n LEAGUE: 'League',\n CUP: 'Cup',\n TOURNAMENT: 'Tournament',\n SCRIMMAGE: 'Scrimmage',\n};\n\nexport const SESSION_TYPE_LABELS: Record<AthSessionType, string> = {\n IN_PERSON: 'In Person',\n ONLINE: 'Online',\n HYBRID: 'Hybrid',\n};\n\nexport const METRIC_TYPE_LABELS: Record<AthMetricType, string> = {\n SPEED: 'Speed',\n ENDURANCE: 'Endurance',\n STRENGTH: 'Strength',\n AGILITY: 'Agility',\n ACCURACY: 'Accuracy',\n WEIGHT: 'Weight',\n HEART_RATE: 'Heart Rate',\n CUSTOM: 'Custom',\n};\n\nexport const SPORT_OPTIONS = Object.keys(SPORT_LABELS) as AthSport[];\nexport const PROGRAM_LEVEL_OPTIONS = Object.keys(PROGRAM_LEVEL_LABELS) as AthProgramLevel[];\nexport const MEMBER_ROLE_OPTIONS = Object.keys(MEMBER_ROLE_LABELS) as AthMemberRole[];\nexport const FIXTURE_TYPE_OPTIONS = Object.keys(FIXTURE_TYPE_LABELS) as AthFixtureType[];\nexport const SESSION_TYPE_OPTIONS = Object.keys(SESSION_TYPE_LABELS) as AthSessionType[];\n\n/** Humanize an enum value for display (fallback when no explicit label exists). */\nexport function humanizeEnum(value: string): string {\n return value\n .replace(/_/g, ' ')\n .toLowerCase()\n .replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n"],"mappings":";;AA2BA,IAAa,IAA4C;CACvD,WAAW;CACX,YAAY;CACZ,SAAS;CACT,SAAS;CACT,UAAU;CACV,OAAO;CACP,UAAU;CACV,QAAQ;CACT,EAEY,IAAyC;CACpD,WAAW;CACX,YAAY;CACZ,SAAS;CACT,SAAS;CACT,UAAU;CACV,OAAO;CACP,UAAU;CACV,QAAQ;CACT,EAEY,IAAwD;CACnE,UAAU;CACV,cAAc;CACd,UAAU;CACV,OAAO;CACR,EAEY,IAAoD;CAC/D,QAAQ;CACR,SAAS;CACT,OAAO;CACP,iBAAiB;CACjB,QAAQ;CACR,SAAS;CACV,EAEY,IAAsD;CACjE,UAAU;CACV,QAAQ;CACR,KAAK;CACL,YAAY;CACZ,WAAW;CACZ,EAEY,IAAsD;CACjE,WAAW;CACX,QAAQ;CACR,QAAQ;CACT,EAEY,IAAoD;CAC/D,OAAO;CACP,WAAW;CACX,UAAU;CACV,SAAS;CACT,UAAU;CACV,QAAQ;CACR,YAAY;CACZ,QAAQ;CACT"}
1
+ {"version":3,"file":"domain.js","names":[],"sources":["../../../src/atheletebridge/components/domain.ts"],"sourcesContent":["/**\n * Domain display helpers\n * @module atheletebridge/components\n *\n * Centralized, type-safe labels and icon mappings for the AthleteBridge\n * enums so list/detail/form pages render them consistently.\n */\n\nimport {\n Activity,\n Dumbbell,\n Trophy,\n Volleyball,\n Waves,\n Gamepad2,\n Footprints,\n type LucideIcon,\n} from 'lucide-react';\nimport type {\n AthFixtureType,\n AthMemberRole,\n AthMetricType,\n AthProgramLevel,\n AthSessionType,\n AthSport,\n} from '../types';\n\nexport const SPORT_ICONS: Record<AthSport, LucideIcon> = {\n ATHLETICS: Footprints,\n BASKETBALL: Volleyball,\n CRICKET: Trophy,\n ESPORTS: Gamepad2,\n FOOTBALL: Volleyball,\n OTHER: Activity,\n SWIMMING: Waves,\n TENNIS: Dumbbell,\n};\n\nexport const SPORT_LABELS: Record<AthSport, string> = {\n ATHLETICS: 'Athletics',\n BASKETBALL: 'Basketball',\n CRICKET: 'Cricket',\n ESPORTS: 'Esports',\n FOOTBALL: 'Football',\n OTHER: 'Other',\n SWIMMING: 'Swimming',\n TENNIS: 'Tennis',\n};\n\nexport const PROGRAM_LEVEL_LABELS: Record<AthProgramLevel, string> = {\n BEGINNER: 'Beginner',\n INTERMEDIATE: 'Intermediate',\n ADVANCED: 'Advanced',\n ELITE: 'Elite',\n};\n\nexport const MEMBER_ROLE_LABELS: Record<AthMemberRole, string> = {\n PLAYER: 'Player',\n CAPTAIN: 'Captain',\n COACH: 'Coach',\n ASSISTANT_COACH: 'Assistant Coach',\n PHYSIO: 'Physio',\n MANAGER: 'Manager',\n};\n\nexport const FIXTURE_TYPE_LABELS: Record<AthFixtureType, string> = {\n FRIENDLY: 'Friendly',\n LEAGUE: 'League',\n CUP: 'Cup',\n TOURNAMENT: 'Tournament',\n SCRIMMAGE: 'Scrimmage',\n};\n\nexport const SESSION_TYPE_LABELS: Record<AthSessionType, string> = {\n IN_PERSON: 'In Person',\n ONLINE: 'Online',\n HYBRID: 'Hybrid',\n};\n\nexport const METRIC_TYPE_LABELS: Record<AthMetricType, string> = {\n SPEED: 'Speed',\n ENDURANCE: 'Endurance',\n STRENGTH: 'Strength',\n AGILITY: 'Agility',\n ACCURACY: 'Accuracy',\n WEIGHT: 'Weight',\n HEART_RATE: 'Heart Rate',\n CUSTOM: 'Custom',\n};\n\nexport const SPORT_OPTIONS = Object.keys(SPORT_LABELS) as AthSport[];\nexport const PROGRAM_LEVEL_OPTIONS = Object.keys(PROGRAM_LEVEL_LABELS) as AthProgramLevel[];\nexport const MEMBER_ROLE_OPTIONS = Object.keys(MEMBER_ROLE_LABELS) as AthMemberRole[];\nexport const FIXTURE_TYPE_OPTIONS = Object.keys(FIXTURE_TYPE_LABELS) as AthFixtureType[];\nexport const SESSION_TYPE_OPTIONS = Object.keys(SESSION_TYPE_LABELS) as AthSessionType[];\n\n/** Humanize an enum value for display (fallback when no explicit label exists). */\nexport function humanizeEnum(value: string): string {\n return value\n .replace(/_/g, ' ')\n .toLowerCase()\n .replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n"],"mappings":";;AA2BA,IAAa,IAA4C;CACvD,WAAW;CACX,YAAY;CACZ,SAAS;CACT,SAAS;CACT,UAAU;CACV,OAAO;CACP,UAAU;CACV,QAAQ;CACT,EAEY,IAAyC;CACpD,WAAW;CACX,YAAY;CACZ,SAAS;CACT,SAAS;CACT,UAAU;CACV,OAAO;CACP,UAAU;CACV,QAAQ;CACT,EAEY,IAAwD;CACnE,UAAU;CACV,cAAc;CACd,UAAU;CACV,OAAO;CACR,EAEY,IAAoD;CAC/D,QAAQ;CACR,SAAS;CACT,OAAO;CACP,iBAAiB;CACjB,QAAQ;CACR,SAAS;CACV,EAEY,IAAsD;CACjE,UAAU;CACV,QAAQ;CACR,KAAK;CACL,YAAY;CACZ,WAAW;CACZ,EAEY,IAAsD;CACjE,WAAW;CACX,QAAQ;CACR,QAAQ;CACT,EAEY,IAAoD;CAC/D,OAAO;CACP,WAAW;CACX,UAAU;CACV,SAAS;CACT,UAAU;CACV,QAAQ;CACR,YAAY;CACZ,QAAQ;CACT,EAKY,IAAuB,OAAO,KAAK,EAAoB;AAIpE,SAAgB,EAAa,GAAuB;AAClD,QAAO,EACJ,QAAQ,MAAM,IAAI,CAClB,aAAa,CACb,QAAQ,UAAU,MAAM,EAAE,aAAa,CAAC"}
@@ -0,0 +1,2 @@
1
+ import "./lib/Avatar.js";
2
+ import "react/jsx-runtime";
@@ -0,0 +1,83 @@
1
+ import { chartPalette as e } from "./lib/palette.js";
2
+ import { useMounted as t, usePrefersReducedMotion as n } from "./lib/hooks.js";
3
+ import { ResponsiveSvg as r } from "./lib/ResponsiveSvg.js";
4
+ import { scaleLinear as i } from "./lib/geometry.js";
5
+ import { formatCompact as a } from "./lib/format.js";
6
+ import { jsx as o, jsxs as s } from "react/jsx-runtime";
7
+ //#region src/atheletebridge/components/viz/BarChart.tsx
8
+ var c = {
9
+ top: 20,
10
+ bottom: 26,
11
+ left: 8,
12
+ right: 8
13
+ };
14
+ function l({ series: l, height: u = 220, valueFormatter: d = a, colorful: f = !1, hideValues: p = !1, title: m, className: h }) {
15
+ let g = t(), _ = n();
16
+ if (l.length === 0) return null;
17
+ let v = l.map((e) => e.value), y = Math.max(...v, 0) || 1, b = e(l.length);
18
+ return /* @__PURE__ */ o(r, {
19
+ height: u,
20
+ title: m ?? `Bar chart of ${l.length} categories`,
21
+ className: h,
22
+ desc: l.map((e) => `${e.label}: ${d(e.value)}`).join(", "),
23
+ children: ({ width: e }) => {
24
+ let t = c.left, n = e - c.right, r = c.top, a = u - c.bottom, m = a - r, h = (n - t) / l.length, v = Math.min(56, h * .62);
25
+ return /* @__PURE__ */ s("g", { children: [/* @__PURE__ */ o("line", {
26
+ x1: t,
27
+ x2: n,
28
+ y1: a,
29
+ y2: a,
30
+ stroke: "var(--color-chart-axis)",
31
+ strokeWidth: 1,
32
+ vectorEffect: "non-scaling-stroke",
33
+ opacity: .5
34
+ }), l.map((e, n) => {
35
+ let r = i(e.value, 0, y, 0, m), c = t + h * n + h / 2, l = c - v / 2, x = a - r, S = e.color ?? (f ? b[n] : "var(--color-accent-primary)");
36
+ return /* @__PURE__ */ s("g", { children: [
37
+ /* @__PURE__ */ o("title", { children: `${e.label}: ${d(e.value)}` }),
38
+ /* @__PURE__ */ o("rect", {
39
+ x: l,
40
+ y: x,
41
+ width: v,
42
+ height: Math.max(0, r),
43
+ rx: 4,
44
+ fill: S,
45
+ style: {
46
+ transformBox: "fill-box",
47
+ transformOrigin: "bottom",
48
+ transform: g || _ ? "scaleY(1)" : "scaleY(0)",
49
+ transition: _ ? void 0 : "transform 620ms cubic-bezier(0.22,1,0.36,1)",
50
+ transitionDelay: _ ? void 0 : `${n * 45}ms`
51
+ }
52
+ }),
53
+ p ? null : /* @__PURE__ */ o("text", {
54
+ x: c,
55
+ y: x - 6,
56
+ textAnchor: "middle",
57
+ fontSize: 10,
58
+ fontWeight: 600,
59
+ fill: "var(--color-text-secondary)",
60
+ style: {
61
+ opacity: g || _ ? 1 : 0,
62
+ transition: _ ? void 0 : "opacity 400ms ease",
63
+ transitionDelay: _ ? void 0 : `${n * 45 + 220}ms`
64
+ },
65
+ children: d(e.value)
66
+ }),
67
+ /* @__PURE__ */ o("text", {
68
+ x: c,
69
+ y: u - 8,
70
+ textAnchor: "middle",
71
+ fontSize: 10,
72
+ fill: "var(--color-chart-axis)",
73
+ children: e.label
74
+ })
75
+ ] }, `${e.label}-${n}`);
76
+ })] });
77
+ }
78
+ });
79
+ }
80
+ //#endregion
81
+ export { l as BarChart };
82
+
83
+ //# sourceMappingURL=BarChart.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BarChart.js","names":[],"sources":["../../../../src/atheletebridge/components/viz/BarChart.tsx"],"sourcesContent":["import { ACCENT, chartPalette } from './lib/palette';\nimport { ResponsiveSvg } from './lib/ResponsiveSvg';\nimport { scaleLinear } from './lib/geometry';\nimport { formatCompact } from './lib/format';\nimport { useMounted, usePrefersReducedMotion } from './lib/hooks';\nimport type { Segment } from './lib/types';\n\nexport interface BarChartProps {\n /** Bars to render. A `color` on a datum overrides the palette. */\n series: Segment[];\n height?: number;\n valueFormatter?: (value: number) => string;\n /** Color each bar from the categorical palette instead of a single accent. */\n colorful?: boolean;\n /** Hide the value label above each bar. */\n hideValues?: boolean;\n title?: string;\n className?: string;\n}\n\nconst PAD = { top: 20, bottom: 26, left: 8, right: 8 };\n\n/** Vertical bar chart with value labels and an animated grow-in. */\nexport function BarChart({\n series,\n height = 220,\n valueFormatter = formatCompact,\n colorful = false,\n hideValues = false,\n title,\n className,\n}: BarChartProps) {\n const mounted = useMounted();\n const reduced = usePrefersReducedMotion();\n\n if (series.length === 0) {\n return null;\n }\n\n const values = series.map((s) => s.value);\n const max = Math.max(...values, 0) || 1;\n const palette = chartPalette(series.length);\n const chartTitle = title ?? `Bar chart of ${series.length} categories`;\n\n return (\n <ResponsiveSvg\n height={height}\n title={chartTitle}\n className={className}\n desc={series.map((s) => `${s.label}: ${valueFormatter(s.value)}`).join(', ')}\n >\n {({ width }) => {\n const plotLeft = PAD.left;\n const plotRight = width - PAD.right;\n const plotTop = PAD.top;\n const plotBottom = height - PAD.bottom;\n const plotH = plotBottom - plotTop;\n const slot = (plotRight - plotLeft) / series.length;\n const barWidth = Math.min(56, slot * 0.62);\n return (\n <g>\n <line\n x1={plotLeft}\n x2={plotRight}\n y1={plotBottom}\n y2={plotBottom}\n stroke=\"var(--color-chart-axis)\"\n strokeWidth={1}\n vectorEffect=\"non-scaling-stroke\"\n opacity={0.5}\n />\n {series.map((datum, i) => {\n const barH = scaleLinear(datum.value, 0, max, 0, plotH);\n const cx = plotLeft + slot * i + slot / 2;\n const x = cx - barWidth / 2;\n const y = plotBottom - barH;\n const color = datum.color ?? (colorful ? palette[i] : ACCENT);\n return (\n <g key={`${datum.label}-${i}`}>\n <title>{`${datum.label}: ${valueFormatter(datum.value)}`}</title>\n <rect\n x={x}\n y={y}\n width={barWidth}\n height={Math.max(0, barH)}\n rx={4}\n fill={color}\n style={{\n transformBox: 'fill-box',\n transformOrigin: 'bottom',\n transform: mounted || reduced ? 'scaleY(1)' : 'scaleY(0)',\n transition: reduced\n ? undefined\n : 'transform 620ms cubic-bezier(0.22,1,0.36,1)',\n transitionDelay: reduced ? undefined : `${i * 45}ms`,\n }}\n />\n {hideValues ? null : (\n <text\n x={cx}\n y={y - 6}\n textAnchor=\"middle\"\n fontSize={10}\n fontWeight={600}\n fill=\"var(--color-text-secondary)\"\n style={{\n opacity: mounted || reduced ? 1 : 0,\n transition: reduced ? undefined : 'opacity 400ms ease',\n transitionDelay: reduced ? undefined : `${i * 45 + 220}ms`,\n }}\n >\n {valueFormatter(datum.value)}\n </text>\n )}\n <text\n x={cx}\n y={height - 8}\n textAnchor=\"middle\"\n fontSize={10}\n fill=\"var(--color-chart-axis)\"\n >\n {datum.label}\n </text>\n </g>\n );\n })}\n </g>\n );\n }}\n </ResponsiveSvg>\n );\n}\n"],"mappings":";;;;;;;AAoBA,IAAM,IAAM;CAAE,KAAK;CAAI,QAAQ;CAAI,MAAM;CAAG,OAAO;CAAG;AAGtD,SAAgB,EAAS,EACvB,WACA,YAAS,KACT,oBAAiB,GACjB,cAAW,IACX,gBAAa,IACb,UACA,gBACgB;CAChB,IAAM,IAAU,GAAY,EACtB,IAAU,GAAyB;AAEzC,KAAI,EAAO,WAAW,EACpB,QAAO;CAGT,IAAM,IAAS,EAAO,KAAK,MAAM,EAAE,MAAM,EACnC,IAAM,KAAK,IAAI,GAAG,GAAQ,EAAE,IAAI,GAChC,IAAU,EAAa,EAAO,OAAO;AAG3C,QACE,kBAAC,GAAD;EACU;EACR,OALe,KAAS,gBAAgB,EAAO,OAAO;EAM3C;EACX,MAAM,EAAO,KAAK,MAAM,GAAG,EAAE,MAAM,IAAI,EAAe,EAAE,MAAM,GAAG,CAAC,KAAK,KAAK;aAE1E,EAAE,eAAY;GACd,IAAM,IAAW,EAAI,MACf,IAAY,IAAQ,EAAI,OACxB,IAAU,EAAI,KACd,IAAa,IAAS,EAAI,QAC1B,IAAQ,IAAa,GACrB,KAAQ,IAAY,KAAY,EAAO,QACvC,IAAW,KAAK,IAAI,IAAI,IAAO,IAAK;AAC1C,UACE,kBAAC,KAAD,EAAA,UAAA,CACE,kBAAC,QAAD;IACE,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,QAAO;IACP,aAAa;IACb,cAAa;IACb,SAAS;IACT,CAAA,EACD,EAAO,KAAK,GAAO,MAAM;IACxB,IAAM,IAAO,EAAY,EAAM,OAAO,GAAG,GAAK,GAAG,EAAM,EACjD,IAAK,IAAW,IAAO,IAAI,IAAO,GAClC,IAAI,IAAK,IAAW,GACpB,IAAI,IAAa,GACjB,IAAQ,EAAM,UAAU,IAAW,EAAQ,KAAA;AACjD,WACE,kBAAC,KAAD,EAAA,UAAA;KACE,kBAAC,SAAD,EAAA,UAAQ,GAAG,EAAM,MAAM,IAAI,EAAe,EAAM,MAAM,IAAW,CAAA;KACjE,kBAAC,QAAD;MACK;MACA;MACH,OAAO;MACP,QAAQ,KAAK,IAAI,GAAG,EAAK;MACzB,IAAI;MACJ,MAAM;MACN,OAAO;OACL,cAAc;OACd,iBAAiB;OACjB,WAAW,KAAW,IAAU,cAAc;OAC9C,YAAY,IACR,KAAA,IACA;OACJ,iBAAiB,IAAU,KAAA,IAAY,GAAG,IAAI,GAAG;OAClD;MACD,CAAA;KACD,IAAa,OACZ,kBAAC,QAAD;MACE,GAAG;MACH,GAAG,IAAI;MACP,YAAW;MACX,UAAU;MACV,YAAY;MACZ,MAAK;MACL,OAAO;OACL,SAAS,KAAW,IAAU,IAAI;OAClC,YAAY,IAAU,KAAA,IAAY;OAClC,iBAAiB,IAAU,KAAA,IAAY,GAAG,IAAI,KAAK,IAAI;OACxD;gBAEA,EAAe,EAAM,MAAM;MACvB,CAAA;KAET,kBAAC,QAAD;MACE,GAAG;MACH,GAAG,IAAS;MACZ,YAAW;MACX,UAAU;MACV,MAAK;gBAEJ,EAAM;MACF,CAAA;KACL,EAAA,EA7CI,GAAG,EAAM,MAAM,GAAG,IA6CtB;KAEN,CACA,EAAA,CAAA;;EAGM,CAAA"}