@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.
- package/dist/atheletebridge/AthleteBridgeRoot.js +55 -39
- package/dist/atheletebridge/AthleteBridgeRoot.js.map +1 -1
- package/dist/atheletebridge/AthleteBridgeRoutes.js +18 -6
- package/dist/atheletebridge/AthleteBridgeRoutes.js.map +1 -1
- package/dist/atheletebridge/components/Crest.js +70 -0
- package/dist/atheletebridge/components/Crest.js.map +1 -0
- package/dist/atheletebridge/components/FixtureCard.js +8 -72
- package/dist/atheletebridge/components/FixtureCard.js.map +1 -1
- package/dist/atheletebridge/components/MatchupRow.js +74 -0
- package/dist/atheletebridge/components/MatchupRow.js.map +1 -0
- package/dist/atheletebridge/components/MetricsCard.js +61 -21
- package/dist/atheletebridge/components/MetricsCard.js.map +1 -1
- package/dist/atheletebridge/components/ProgramCard.js +75 -43
- package/dist/atheletebridge/components/ProgramCard.js.map +1 -1
- package/dist/atheletebridge/components/Scoreboard.js +163 -0
- package/dist/atheletebridge/components/Scoreboard.js.map +1 -0
- package/dist/atheletebridge/components/SessionCard.js +64 -41
- package/dist/atheletebridge/components/SessionCard.js.map +1 -1
- package/dist/atheletebridge/components/SessionStatusTimeline.js +80 -0
- package/dist/atheletebridge/components/SessionStatusTimeline.js.map +1 -0
- package/dist/atheletebridge/components/StatusBadge.js +5 -5
- package/dist/atheletebridge/components/StatusBadge.js.map +1 -1
- package/dist/atheletebridge/components/TeamCard.js +65 -35
- package/dist/atheletebridge/components/TeamCard.js.map +1 -1
- package/dist/atheletebridge/components/UserAvatar.js +33 -0
- package/dist/atheletebridge/components/UserAvatar.js.map +1 -0
- package/dist/atheletebridge/components/domain.js +5 -2
- package/dist/atheletebridge/components/domain.js.map +1 -1
- package/dist/atheletebridge/components/viz/AvatarStack.js +2 -0
- package/dist/atheletebridge/components/viz/BarChart.js +83 -0
- package/dist/atheletebridge/components/viz/BarChart.js.map +1 -0
- package/dist/atheletebridge/components/viz/DonutChart.js +109 -0
- package/dist/atheletebridge/components/viz/DonutChart.js.map +1 -0
- package/dist/atheletebridge/components/viz/LineChart.js +123 -0
- package/dist/atheletebridge/components/viz/LineChart.js.map +1 -0
- package/dist/atheletebridge/components/viz/ProgressRing.js +6 -0
- package/dist/atheletebridge/components/viz/ProgressRing.js.map +1 -0
- package/dist/atheletebridge/components/viz/RatingStars.js +92 -0
- package/dist/atheletebridge/components/viz/RatingStars.js.map +1 -0
- package/dist/atheletebridge/components/viz/Sparkline.js +74 -0
- package/dist/atheletebridge/components/viz/Sparkline.js.map +1 -0
- package/dist/atheletebridge/components/viz/StatCard.js +60 -0
- package/dist/atheletebridge/components/viz/StatCard.js.map +1 -0
- package/dist/atheletebridge/components/viz/StepProgress.js +3 -0
- package/dist/atheletebridge/components/viz/Timeline.js +66 -0
- package/dist/atheletebridge/components/viz/Timeline.js.map +1 -0
- package/dist/atheletebridge/components/viz/index.js +13 -0
- package/dist/atheletebridge/components/viz/lib/Avatar.js +1 -0
- package/dist/atheletebridge/components/viz/lib/CartesianFrame.js +119 -0
- package/dist/atheletebridge/components/viz/lib/CartesianFrame.js.map +1 -0
- package/dist/atheletebridge/components/viz/lib/ResponsiveSvg.js +35 -0
- package/dist/atheletebridge/components/viz/lib/ResponsiveSvg.js.map +1 -0
- package/dist/atheletebridge/components/viz/lib/format.js +17 -0
- package/dist/atheletebridge/components/viz/lib/format.js.map +1 -0
- package/dist/atheletebridge/components/viz/lib/geometry.js +61 -0
- package/dist/atheletebridge/components/viz/lib/geometry.js.map +1 -0
- package/dist/atheletebridge/components/viz/lib/hooks.js +39 -0
- package/dist/atheletebridge/components/viz/lib/hooks.js.map +1 -0
- package/dist/atheletebridge/components/viz/lib/palette.js +27 -0
- package/dist/atheletebridge/components/viz/lib/palette.js.map +1 -0
- package/dist/atheletebridge/hooks/useAthleteBridgeApi.js +369 -163
- package/dist/atheletebridge/hooks/useAthleteBridgeApi.js.map +1 -1
- package/dist/atheletebridge/pages/DashboardPage.js +182 -117
- package/dist/atheletebridge/pages/DashboardPage.js.map +1 -1
- package/dist/atheletebridge/pages/StandingsPage.js +119 -95
- package/dist/atheletebridge/pages/StandingsPage.js.map +1 -1
- package/dist/atheletebridge/pages/analytics/CoachAnalyticsPage.js +372 -0
- package/dist/atheletebridge/pages/analytics/CoachAnalyticsPage.js.map +1 -0
- package/dist/atheletebridge/pages/athletes/AthleteProfilePage.js +511 -0
- package/dist/atheletebridge/pages/athletes/AthleteProfilePage.js.map +1 -0
- package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js +120 -131
- package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixtureFormPage.js +264 -176
- package/dist/atheletebridge/pages/fixtures/FixtureFormPage.js.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixturesListPage.js +84 -47
- package/dist/atheletebridge/pages/fixtures/FixturesListPage.js.map +1 -1
- package/dist/atheletebridge/pages/performance/PerformancePage.js +432 -281
- package/dist/atheletebridge/pages/performance/PerformancePage.js.map +1 -1
- package/dist/atheletebridge/pages/programs/ProgramDetailPage.js +187 -123
- package/dist/atheletebridge/pages/programs/ProgramDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/programs/ProgramsListPage.js +56 -39
- package/dist/atheletebridge/pages/programs/ProgramsListPage.js.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionDetailPage.js +267 -139
- package/dist/atheletebridge/pages/sessions/SessionDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionFormPage.js +253 -162
- package/dist/atheletebridge/pages/sessions/SessionFormPage.js.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionsListPage.js +38 -36
- package/dist/atheletebridge/pages/sessions/SessionsListPage.js.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamDetailPage.js +437 -189
- package/dist/atheletebridge/pages/teams/TeamDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamFormPage.js +368 -177
- package/dist/atheletebridge/pages/teams/TeamFormPage.js.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamsListPage.js +3 -0
- package/dist/atheletebridge/pages/teams/TeamsListPage.js.map +1 -1
- package/dist/atheletebridge/pages/tournaments/TournamentBracketPage.js +298 -0
- package/dist/atheletebridge/pages/tournaments/TournamentBracketPage.js.map +1 -0
- package/dist/atheletebridge/utils/identity.js +31 -0
- package/dist/atheletebridge/utils/identity.js.map +1 -0
- package/dist/atheletebridge/utils/timeAgo.js +20 -0
- package/dist/atheletebridge/utils/timeAgo.js.map +1 -0
- package/package.json +1 -1
|
@@ -1,26 +1,66 @@
|
|
|
1
1
|
import { cn as e } from "../utils/cn.js";
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
2
|
+
import { Sparkline as t } from "./viz/Sparkline.js";
|
|
3
|
+
import "./viz/index.js";
|
|
4
|
+
import { ArrowDownRight as n, ArrowUpRight as r } from "lucide-react";
|
|
5
|
+
import { jsx as i, jsxs as a } from "react/jsx-runtime";
|
|
6
|
+
import { GlassCard as o } from "@burdenoff/fe-libs/ui";
|
|
4
7
|
//#region src/atheletebridge/components/MetricsCard.tsx
|
|
5
|
-
var
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
8
|
+
var s = {
|
|
9
|
+
default: "text-text-primary",
|
|
10
|
+
accent: "text-[var(--color-accent-teal)]",
|
|
11
|
+
success: "text-status-success-text"
|
|
12
|
+
}, c = ({ icon: c, value: l, label: u, featured: d = !1, tone: f = "default", delta: p, deltaSuffix: m = "%", deltaLabel: h, invertDelta: g = !1, sparkline: _, className: v }) => {
|
|
13
|
+
let y = typeof p == "number", b = (p ?? 0) === 0, x = g ? (p ?? 0) < 0 : (p ?? 0) > 0, S = (p ?? 0) > 0, C = b ? "text-text-muted" : x ? "text-status-success-text" : "text-status-error-text";
|
|
14
|
+
return /* @__PURE__ */ a(o, {
|
|
15
|
+
treatment: d ? "glass" : "metric",
|
|
16
|
+
glow: d,
|
|
17
|
+
className: e("p-6", v),
|
|
18
|
+
children: [
|
|
19
|
+
/* @__PURE__ */ i("div", {
|
|
20
|
+
className: "relative text-text-secondary",
|
|
21
|
+
children: c
|
|
22
|
+
}),
|
|
23
|
+
/* @__PURE__ */ a("div", {
|
|
24
|
+
className: "relative mt-3",
|
|
25
|
+
children: [/* @__PURE__ */ i("p", {
|
|
26
|
+
className: e("text-2xl font-bold tabular-nums", s[f]),
|
|
27
|
+
children: l
|
|
28
|
+
}), /* @__PURE__ */ i("p", {
|
|
29
|
+
className: "mt-1 text-sm text-text-secondary",
|
|
30
|
+
children: u
|
|
31
|
+
})]
|
|
32
|
+
}),
|
|
33
|
+
y ? /* @__PURE__ */ a("div", {
|
|
34
|
+
className: "relative mt-2 flex items-center gap-1.5 text-sm",
|
|
35
|
+
children: [/* @__PURE__ */ a("span", {
|
|
36
|
+
className: e("inline-flex items-center gap-0.5 font-medium tabular-nums", C),
|
|
37
|
+
children: [
|
|
38
|
+
b ? null : i(S ? r : n, {
|
|
39
|
+
"aria-hidden": "true",
|
|
40
|
+
className: "h-4 w-4"
|
|
41
|
+
}),
|
|
42
|
+
S ? "+" : "",
|
|
43
|
+
p,
|
|
44
|
+
m
|
|
45
|
+
]
|
|
46
|
+
}), h ? /* @__PURE__ */ i("span", {
|
|
47
|
+
className: "text-text-muted",
|
|
48
|
+
children: h
|
|
49
|
+
}) : null]
|
|
50
|
+
}) : null,
|
|
51
|
+
_ && _.length > 1 ? /* @__PURE__ */ i("div", {
|
|
52
|
+
className: "relative mt-3",
|
|
53
|
+
children: /* @__PURE__ */ i(t, {
|
|
54
|
+
data: _,
|
|
55
|
+
variant: "area",
|
|
56
|
+
label: `${u} trend`,
|
|
57
|
+
height: 32
|
|
58
|
+
})
|
|
59
|
+
}) : null
|
|
60
|
+
]
|
|
61
|
+
});
|
|
62
|
+
};
|
|
23
63
|
//#endregion
|
|
24
|
-
export {
|
|
64
|
+
export { c as MetricsCard };
|
|
25
65
|
|
|
26
66
|
//# sourceMappingURL=MetricsCard.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MetricsCard.js","names":[],"sources":["../../../src/atheletebridge/components/MetricsCard.tsx"],"sourcesContent":["/**\n * Metrics Card Component\n * @module atheletebridge/components\n *\n * Boff UI 2.0: KPI tiles use the glassmorphic `GlassCard` \"metric\" treatment\n * (accent wash + seam border + soft pop-lift). The hero metric per page can\n * opt into the brighter `glass` treatment with an accent glow via `featured`.\n *
|
|
1
|
+
{"version":3,"file":"MetricsCard.js","names":[],"sources":["../../../src/atheletebridge/components/MetricsCard.tsx"],"sourcesContent":["/**\n * Metrics Card Component\n * @module atheletebridge/components\n *\n * Boff UI 2.0: KPI tiles use the glassmorphic `GlassCard` \"metric\" treatment\n * (accent wash + seam border + soft pop-lift). The hero metric per page can\n * opt into the brighter `glass` treatment with an accent glow via `featured`.\n *\n * The original API (`icon`, `value`, `label`, `featured`, `className`) is\n * unchanged. Optional props enrich the tile toward the mock's KPI style: a teal\n * `tone`, a signed `delta` pill, and a footer `sparkline` trend — all opt-in, so\n * existing call sites render exactly as before. Numerals are tabular.\n */\n\nimport type { FC, ReactNode } from 'react';\nimport { ArrowDownRight, ArrowUpRight } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { Sparkline } from './viz';\nimport { cn } from '../utils/cn';\n\ntype MetricTone = 'default' | 'accent' | 'success';\n\ninterface MetricsCardProps {\n icon: ReactNode;\n value: string | number;\n label: string;\n /** Render as the single featured hero metric (glass treatment + glow). */\n featured?: boolean;\n /** Value color: `default` (primary), `accent` (teal), or `success` (green). */\n tone?: MetricTone;\n /** Signed change; positive is good unless `invertDelta`. Renders a pill. */\n delta?: number;\n /** Unit appended to the delta (default \"%\"). */\n deltaSuffix?: string;\n /** Context line beside the delta, e.g. \"vs last month\". */\n deltaLabel?: string;\n /** When true (e.g. injuries), a negative delta is the good outcome. */\n invertDelta?: boolean;\n /** Optional trend rendered as a footer sparkline. */\n sparkline?: number[];\n className?: string;\n}\n\nconst TONE_CLASS: Record<MetricTone, string> = {\n default: 'text-text-primary',\n accent: 'text-[var(--color-accent-teal)]',\n success: 'text-status-success-text',\n};\n\nexport const MetricsCard: FC<MetricsCardProps> = ({\n icon,\n value,\n label,\n featured = false,\n tone = 'default',\n delta,\n deltaSuffix = '%',\n deltaLabel,\n invertDelta = false,\n sparkline,\n className,\n}) => {\n const hasDelta = typeof delta === 'number';\n const isNeutral = (delta ?? 0) === 0;\n const positiveIsGood = invertDelta ? (delta ?? 0) < 0 : (delta ?? 0) > 0;\n const rising = (delta ?? 0) > 0;\n const deltaTone = isNeutral\n ? 'text-text-muted'\n : positiveIsGood\n ? 'text-status-success-text'\n : 'text-status-error-text';\n\n return (\n <GlassCard\n treatment={featured ? 'glass' : 'metric'}\n glow={featured}\n className={cn('p-6', className)}\n >\n <div className=\"relative text-text-secondary\">{icon}</div>\n <div className=\"relative mt-3\">\n <p className={cn('text-2xl font-bold tabular-nums', TONE_CLASS[tone])}>{value}</p>\n <p className=\"mt-1 text-sm text-text-secondary\">{label}</p>\n </div>\n\n {hasDelta ? (\n <div className=\"relative mt-2 flex items-center gap-1.5 text-sm\">\n <span className={cn('inline-flex items-center gap-0.5 font-medium tabular-nums', deltaTone)}>\n {isNeutral ? null : rising ? (\n <ArrowUpRight aria-hidden=\"true\" className=\"h-4 w-4\" />\n ) : (\n <ArrowDownRight aria-hidden=\"true\" className=\"h-4 w-4\" />\n )}\n {rising ? '+' : ''}\n {delta}\n {deltaSuffix}\n </span>\n {deltaLabel ? <span className=\"text-text-muted\">{deltaLabel}</span> : null}\n </div>\n ) : null}\n\n {sparkline && sparkline.length > 1 ? (\n <div className=\"relative mt-3\">\n <Sparkline data={sparkline} variant=\"area\" label={`${label} trend`} height={32} />\n </div>\n ) : null}\n </GlassCard>\n );\n};\n"],"mappings":";;;;;;;AA2CA,IAAM,IAAyC;CAC7C,SAAS;CACT,QAAQ;CACR,SAAS;CACV,EAEY,KAAqC,EAChD,SACA,UACA,UACA,cAAW,IACX,UAAO,WACP,UACA,iBAAc,KACd,eACA,iBAAc,IACd,cACA,mBACI;CACJ,IAAM,IAAW,OAAO,KAAU,UAC5B,KAAa,KAAS,OAAO,GAC7B,IAAiB,KAAe,KAAS,KAAK,KAAK,KAAS,KAAK,GACjE,KAAU,KAAS,KAAK,GACxB,IAAY,IACd,oBACA,IACE,6BACA;AAEN,QACE,kBAAC,GAAD;EACE,WAAW,IAAW,UAAU;EAChC,MAAM;EACN,WAAW,EAAG,OAAO,EAAU;YAHjC;GAKE,kBAAC,OAAD;IAAK,WAAU;cAAgC;IAAW,CAAA;GAC1D,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,KAAD;KAAG,WAAW,EAAG,mCAAmC,EAAW,GAAM;eAAG;KAAU,CAAA,EAClF,kBAAC,KAAD;KAAG,WAAU;eAAoC;KAAU,CAAA,CACvD;;GAEL,IACC,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KAAM,WAAW,EAAG,6DAA6D,EAAU;eAA3F;MACG,IAAY,OACX,EADkB,IACjB,IAEA,GAFD;OAAc,eAAY;OAAO,WAAU;OAAY,CAEE;MAE1D,IAAS,MAAM;MACf;MACA;MACI;QACN,IAAa,kBAAC,QAAD;KAAM,WAAU;eAAmB;KAAkB,CAAA,GAAG,KAClE;QACJ;GAEH,KAAa,EAAU,SAAS,IAC/B,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD;KAAW,MAAM;KAAW,SAAQ;KAAO,OAAO,GAAG,EAAM;KAAS,QAAQ;KAAM,CAAA;IAC9E,CAAA,GACJ;GACM"}
|
|
@@ -1,69 +1,101 @@
|
|
|
1
1
|
import { cn as e } from "../utils/cn.js";
|
|
2
2
|
import { StatusBadge as t } from "./StatusBadge.js";
|
|
3
3
|
import { PROGRAM_LEVEL_LABELS as n, SPORT_ICONS as r, SPORT_LABELS as i } from "./domain.js";
|
|
4
|
-
import { CalendarRange as a, Copy as o } from "lucide-react";
|
|
5
|
-
import { jsx as
|
|
6
|
-
import { GlassCard as
|
|
4
|
+
import { CalendarRange as a, Copy as o, Dumbbell as s } from "lucide-react";
|
|
5
|
+
import { jsx as c, jsxs as l } from "react/jsx-runtime";
|
|
6
|
+
import { Badge as u, GlassCard as d } from "@burdenoff/fe-libs/ui";
|
|
7
7
|
//#region src/atheletebridge/components/ProgramCard.tsx
|
|
8
|
-
var
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
var f = {
|
|
9
|
+
BEGINNER: "success",
|
|
10
|
+
INTERMEDIATE: "info",
|
|
11
|
+
ADVANCED: "warning",
|
|
12
|
+
ELITE: "default"
|
|
13
|
+
}, p = ({ program: p, onClick: m, drillCount: h }) => {
|
|
14
|
+
let g = r[p.sport], _ = h ?? p.drills?.length;
|
|
15
|
+
return /* @__PURE__ */ l(d, {
|
|
11
16
|
treatment: "metric",
|
|
12
17
|
role: "button",
|
|
13
18
|
tabIndex: 0,
|
|
14
|
-
onClick:
|
|
19
|
+
onClick: m,
|
|
15
20
|
onKeyDown: (e) => {
|
|
16
|
-
(e.key === "Enter" || e.key === " ") && (e.preventDefault(),
|
|
21
|
+
(e.key === "Enter" || e.key === " ") && (e.preventDefault(), m?.());
|
|
17
22
|
},
|
|
18
|
-
className: e("cursor-pointer p-5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]"),
|
|
23
|
+
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
24
|
children: [
|
|
20
|
-
/* @__PURE__ */
|
|
25
|
+
/* @__PURE__ */ l("div", {
|
|
21
26
|
className: "flex items-start justify-between gap-3",
|
|
22
|
-
children: [/* @__PURE__ */
|
|
23
|
-
className: "flex items-center gap-3
|
|
24
|
-
children: [/* @__PURE__ */
|
|
25
|
-
className: "flex h-
|
|
26
|
-
children: /* @__PURE__ */
|
|
27
|
-
|
|
27
|
+
children: [/* @__PURE__ */ l("div", {
|
|
28
|
+
className: "flex min-w-0 items-center gap-3",
|
|
29
|
+
children: [/* @__PURE__ */ c("div", {
|
|
30
|
+
className: "flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-[var(--color-accent-soft)] text-text-primary",
|
|
31
|
+
children: /* @__PURE__ */ c(g, {
|
|
32
|
+
className: "h-5 w-5",
|
|
33
|
+
"aria-hidden": "true"
|
|
34
|
+
})
|
|
35
|
+
}), /* @__PURE__ */ l("div", {
|
|
28
36
|
className: "min-w-0",
|
|
29
|
-
children: [/* @__PURE__ */
|
|
37
|
+
children: [/* @__PURE__ */ c("h3", {
|
|
30
38
|
className: "truncate text-base font-semibold text-text-primary",
|
|
31
|
-
children:
|
|
32
|
-
}), /* @__PURE__ */
|
|
33
|
-
className: "
|
|
34
|
-
children: [
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
children: p.name
|
|
40
|
+
}), /* @__PURE__ */ l("div", {
|
|
41
|
+
className: "mt-1 flex flex-wrap items-center gap-1.5",
|
|
42
|
+
children: [/* @__PURE__ */ c(u, {
|
|
43
|
+
variant: f[p.level],
|
|
44
|
+
children: n[p.level]
|
|
45
|
+
}), /* @__PURE__ */ c("span", {
|
|
46
|
+
className: "text-xs text-text-secondary",
|
|
47
|
+
children: i[p.sport]
|
|
48
|
+
})]
|
|
39
49
|
})]
|
|
40
50
|
})]
|
|
41
|
-
}), /* @__PURE__ */
|
|
51
|
+
}), /* @__PURE__ */ c(t, { status: p.status })]
|
|
42
52
|
}),
|
|
43
|
-
|
|
53
|
+
p.summary && /* @__PURE__ */ c("p", {
|
|
44
54
|
className: "mt-3 line-clamp-2 text-sm text-text-secondary",
|
|
45
|
-
children:
|
|
55
|
+
children: p.summary
|
|
46
56
|
}),
|
|
47
|
-
/* @__PURE__ */
|
|
57
|
+
/* @__PURE__ */ l("div", {
|
|
48
58
|
className: "mt-4 flex flex-wrap items-center gap-x-4 gap-y-1.5 text-xs text-text-secondary",
|
|
49
|
-
children: [
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
children: [
|
|
60
|
+
/* @__PURE__ */ l("span", {
|
|
61
|
+
className: "flex items-center gap-1",
|
|
62
|
+
children: [
|
|
63
|
+
/* @__PURE__ */ c(a, {
|
|
64
|
+
className: "h-3.5 w-3.5",
|
|
65
|
+
"aria-hidden": "true"
|
|
66
|
+
}),
|
|
67
|
+
" ",
|
|
68
|
+
p.weeks,
|
|
69
|
+
" ",
|
|
70
|
+
p.weeks === 1 ? "week" : "weeks"
|
|
71
|
+
]
|
|
72
|
+
}),
|
|
73
|
+
_ !== void 0 && /* @__PURE__ */ l("span", {
|
|
74
|
+
className: "flex items-center gap-1",
|
|
75
|
+
children: [
|
|
76
|
+
/* @__PURE__ */ c(s, {
|
|
77
|
+
className: "h-3.5 w-3.5",
|
|
78
|
+
"aria-hidden": "true"
|
|
79
|
+
}),
|
|
80
|
+
" ",
|
|
81
|
+
_,
|
|
82
|
+
" ",
|
|
83
|
+
_ === 1 ? "drill" : "drills"
|
|
84
|
+
]
|
|
85
|
+
}),
|
|
86
|
+
p.isTemplate && /* @__PURE__ */ l("span", {
|
|
87
|
+
className: "flex items-center gap-1",
|
|
88
|
+
children: [/* @__PURE__ */ c(o, {
|
|
89
|
+
className: "h-3.5 w-3.5",
|
|
90
|
+
"aria-hidden": "true"
|
|
91
|
+
}), " Template"]
|
|
92
|
+
})
|
|
93
|
+
]
|
|
62
94
|
})
|
|
63
95
|
]
|
|
64
96
|
});
|
|
65
97
|
};
|
|
66
98
|
//#endregion
|
|
67
|
-
export {
|
|
99
|
+
export { p as ProgramCard };
|
|
68
100
|
|
|
69
101
|
//# sourceMappingURL=ProgramCard.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProgramCard.js","names":[],"sources":["../../../src/atheletebridge/components/ProgramCard.tsx"],"sourcesContent":["/**\n * Program Card\n * @module atheletebridge/components\n */\n\nimport type { FC } from 'react';\nimport { CalendarRange, Copy } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { StatusBadge } from './StatusBadge';\nimport { SPORT_ICONS, SPORT_LABELS, PROGRAM_LEVEL_LABELS } from './domain';\nimport { cn } from '../utils/cn';\nimport type { AthProgram } from '../types';\n\ninterface ProgramCardProps {\n program: AthProgram;\n onClick?: () => void;\n}\n\nexport const ProgramCard: FC<ProgramCardProps> = ({ program, onClick }) => {\n const SportIcon = SPORT_ICONS[program.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
|
|
1
|
+
{"version":3,"file":"ProgramCard.js","names":[],"sources":["../../../src/atheletebridge/components/ProgramCard.tsx"],"sourcesContent":["/**\n * Program Card\n * @module atheletebridge/components\n *\n * A training-program tile with a sport-icon tile, a level Badge tinted per tier\n * (green Beginner / blue Intermediate / amber Advanced / brand Elite), a status\n * pill, and a footer stat row of weeks + drill counts. Semantic tokens only.\n */\n\nimport type { FC } from 'react';\nimport { CalendarRange, Copy, Dumbbell } from 'lucide-react';\nimport { GlassCard, Badge, type BadgeProps } from '@burdenoff/fe-libs/ui';\nimport { StatusBadge } from './StatusBadge';\nimport { SPORT_ICONS, SPORT_LABELS, PROGRAM_LEVEL_LABELS } from './domain';\nimport { cn } from '../utils/cn';\nimport type { AthProgram, AthProgramLevel } from '../types';\n\ninterface ProgramCardProps {\n program: AthProgram;\n onClick?: () => void;\n /**\n * Drill count for the footer chip. Falls back to `program.drills?.length`\n * when the query selected drills; omit the chip if neither is available.\n */\n drillCount?: number;\n}\n\nconst LEVEL_VARIANT: Record<AthProgramLevel, BadgeProps['variant']> = {\n BEGINNER: 'success',\n INTERMEDIATE: 'info',\n ADVANCED: 'warning',\n ELITE: 'default',\n};\n\nexport const ProgramCard: FC<ProgramCardProps> = ({ program, onClick, drillCount }) => {\n const SportIcon = SPORT_ICONS[program.sport];\n const drills = drillCount ?? program.drills?.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 <div className=\"flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-[var(--color-accent-soft)] text-text-primary\">\n <SportIcon className=\"h-5 w-5\" aria-hidden=\"true\" />\n </div>\n <div className=\"min-w-0\">\n <h3 className=\"truncate text-base font-semibold text-text-primary\">{program.name}</h3>\n <div className=\"mt-1 flex flex-wrap items-center gap-1.5\">\n <Badge variant={LEVEL_VARIANT[program.level]}>\n {PROGRAM_LEVEL_LABELS[program.level]}\n </Badge>\n <span className=\"text-xs text-text-secondary\">{SPORT_LABELS[program.sport]}</span>\n </div>\n </div>\n </div>\n <StatusBadge status={program.status} />\n </div>\n\n {program.summary && (\n <p className=\"mt-3 line-clamp-2 text-sm text-text-secondary\">{program.summary}</p>\n )}\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 <CalendarRange className=\"h-3.5 w-3.5\" aria-hidden=\"true\" /> {program.weeks}{' '}\n {program.weeks === 1 ? 'week' : 'weeks'}\n </span>\n {drills !== undefined && (\n <span className=\"flex items-center gap-1\">\n <Dumbbell className=\"h-3.5 w-3.5\" aria-hidden=\"true\" /> {drills}{' '}\n {drills === 1 ? 'drill' : 'drills'}\n </span>\n )}\n {program.isTemplate && (\n <span className=\"flex items-center gap-1\">\n <Copy className=\"h-3.5 w-3.5\" aria-hidden=\"true\" /> Template\n </span>\n )}\n </div>\n </GlassCard>\n );\n};\n"],"mappings":";;;;;;;AA2BA,IAAM,IAAgE;CACpE,UAAU;CACV,cAAc;CACd,UAAU;CACV,OAAO;CACR,EAEY,KAAqC,EAAE,YAAS,YAAS,oBAAiB;CACrF,IAAM,IAAY,EAAY,EAAQ,QAChC,IAAS,KAAc,EAAQ,QAAQ;AAE7C,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,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD;OAAW,WAAU;OAAU,eAAY;OAAS,CAAA;MAChD,CAAA,EACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,MAAD;OAAI,WAAU;iBAAsD,EAAQ;OAAU,CAAA,EACtF,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD;QAAO,SAAS,EAAc,EAAQ;kBACnC,EAAqB,EAAQ;QACxB,CAAA,EACR,kBAAC,QAAD;QAAM,WAAU;kBAA+B,EAAa,EAAQ;QAAc,CAAA,CAC9E;SACF;QACF;QACN,kBAAC,GAAD,EAAa,QAAQ,EAAQ,QAAU,CAAA,CACnC;;GAEL,EAAQ,WACP,kBAAC,KAAD;IAAG,WAAU;cAAiD,EAAQ;IAAY,CAAA;GAGpF,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,QAAD;MAAM,WAAU;gBAAhB;OACE,kBAAC,GAAD;QAAe,WAAU;QAAc,eAAY;QAAS,CAAA;;OAAE,EAAQ;OAAO;OAC5E,EAAQ,UAAU,IAAI,SAAS;OAC3B;;KACN,MAAW,KAAA,KACV,kBAAC,QAAD;MAAM,WAAU;gBAAhB;OACE,kBAAC,GAAD;QAAU,WAAU;QAAc,eAAY;QAAS,CAAA;;OAAE;OAAQ;OAChE,MAAW,IAAI,UAAU;OACrB;;KAER,EAAQ,cACP,kBAAC,QAAD;MAAM,WAAU;gBAAhB,CACE,kBAAC,GAAD;OAAM,WAAU;OAAc,eAAY;OAAS,CAAA,EAAA,YAC9C;;KAEL;;GACI"}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { cn as e } from "../utils/cn.js";
|
|
2
|
+
import { StatusBadge as t } from "./StatusBadge.js";
|
|
3
|
+
import { FIXTURE_TYPE_LABELS as n, SPORT_ICONS as r } from "./domain.js";
|
|
4
|
+
import { Crest as i } from "./Crest.js";
|
|
5
|
+
import { formatDate as a } from "../utils/formatters.js";
|
|
6
|
+
import { CalendarClock as o, MapPin as s, Trophy as c } from "lucide-react";
|
|
7
|
+
import { jsx as l, jsxs as u } from "react/jsx-runtime";
|
|
8
|
+
import { GlassCard as d } from "@burdenoff/fe-libs/ui";
|
|
9
|
+
//#region src/atheletebridge/components/Scoreboard.tsx
|
|
10
|
+
function f(e) {
|
|
11
|
+
let t = e.homeTeam?.name ?? "Home", n = e.awayTeam?.name ?? (e.awayTeamId ? "Away" : "TBD"), r = e.homeScore !== null && e.awayScore !== null, i = e.homeScore, a = e.awayScore, o = r && (i ?? 0) > (a ?? 0), s = r && (a ?? 0) > (i ?? 0);
|
|
12
|
+
return {
|
|
13
|
+
homeName: t,
|
|
14
|
+
awayName: n,
|
|
15
|
+
homeSport: e.homeTeam?.sport,
|
|
16
|
+
awaySport: e.awayTeam?.sport,
|
|
17
|
+
homeScore: i,
|
|
18
|
+
awayScore: a,
|
|
19
|
+
hasScore: r,
|
|
20
|
+
homeWon: o,
|
|
21
|
+
awayWon: s
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
var p = ({ name: t, sport: n, align: r = "start", won: a = !1, dimmed: o = !1, crestSize: s = 40, stacked: c = !1, className: d }) => /* @__PURE__ */ u("div", {
|
|
25
|
+
className: e("flex min-w-0 items-center gap-3", c ? "flex-col text-center" : r === "end" ? "flex-row-reverse text-right" : "text-left", d),
|
|
26
|
+
children: [/* @__PURE__ */ l(i, {
|
|
27
|
+
name: t,
|
|
28
|
+
sport: n,
|
|
29
|
+
size: s
|
|
30
|
+
}), /* @__PURE__ */ l("span", {
|
|
31
|
+
className: e("min-w-0 truncate font-semibold", c ? "max-w-[10rem] text-sm" : "text-sm", a ? "text-status-success-text" : o ? "text-text-muted" : "text-text-primary"),
|
|
32
|
+
children: t
|
|
33
|
+
})]
|
|
34
|
+
}), m = {
|
|
35
|
+
sm: "text-lg",
|
|
36
|
+
md: "text-2xl",
|
|
37
|
+
lg: "text-4xl sm:text-5xl"
|
|
38
|
+
}, h = ({ matchup: t, time: n, size: r = "md", className: i }) => t.hasScore ? /* @__PURE__ */ u("div", {
|
|
39
|
+
className: e("flex flex-col items-center gap-0.5", i),
|
|
40
|
+
children: [/* @__PURE__ */ u("span", {
|
|
41
|
+
className: e("flex items-center gap-2 font-bold leading-none tabular-nums", m[r]),
|
|
42
|
+
children: [
|
|
43
|
+
/* @__PURE__ */ l("span", {
|
|
44
|
+
className: t.homeWon ? "text-status-success-text" : "text-text-primary",
|
|
45
|
+
children: t.homeScore
|
|
46
|
+
}),
|
|
47
|
+
/* @__PURE__ */ l("span", {
|
|
48
|
+
className: "text-text-muted",
|
|
49
|
+
children: "–"
|
|
50
|
+
}),
|
|
51
|
+
/* @__PURE__ */ l("span", {
|
|
52
|
+
className: t.awayWon ? "text-status-success-text" : "text-text-primary",
|
|
53
|
+
children: t.awayScore
|
|
54
|
+
})
|
|
55
|
+
]
|
|
56
|
+
}), n ? /* @__PURE__ */ l("span", {
|
|
57
|
+
className: "text-xs text-text-secondary",
|
|
58
|
+
children: n
|
|
59
|
+
}) : null]
|
|
60
|
+
}) : /* @__PURE__ */ u("div", {
|
|
61
|
+
className: e("flex flex-col items-center gap-0.5", i),
|
|
62
|
+
children: [/* @__PURE__ */ l("span", {
|
|
63
|
+
className: "rounded-lg bg-bg-sunken px-3 py-1 text-xs font-bold uppercase tracking-wide text-text-secondary",
|
|
64
|
+
children: "vs"
|
|
65
|
+
}), n ? /* @__PURE__ */ l("span", {
|
|
66
|
+
className: "text-xs text-text-secondary",
|
|
67
|
+
children: n
|
|
68
|
+
}) : null]
|
|
69
|
+
}), g = ({ fixture: t, center: i = !1 }) => {
|
|
70
|
+
let a = t.homeTeam?.sport ? r[t.homeTeam.sport] : c, o = [t.competition, t.round].filter(Boolean).join(" · ");
|
|
71
|
+
return /* @__PURE__ */ u("span", {
|
|
72
|
+
className: e("inline-flex items-center gap-1.5 text-xs font-medium text-text-secondary", i && "justify-center"),
|
|
73
|
+
children: [/* @__PURE__ */ l(a, {
|
|
74
|
+
className: "h-3.5 w-3.5 shrink-0 text-text-muted",
|
|
75
|
+
"aria-hidden": "true"
|
|
76
|
+
}), /* @__PURE__ */ l("span", {
|
|
77
|
+
className: "truncate",
|
|
78
|
+
children: o || n[t.type]
|
|
79
|
+
})]
|
|
80
|
+
});
|
|
81
|
+
}, _ = ({ fixture: n, onClick: r, variant: i = "card", className: c }) => {
|
|
82
|
+
let m = f(n), _ = i === "hero", v = m.hasScore ? null : a(n.scheduledAt), y = !!r;
|
|
83
|
+
return /* @__PURE__ */ u(d, {
|
|
84
|
+
treatment: _ ? "glass" : "metric",
|
|
85
|
+
glow: _,
|
|
86
|
+
role: y ? "button" : void 0,
|
|
87
|
+
tabIndex: y ? 0 : void 0,
|
|
88
|
+
onClick: r,
|
|
89
|
+
onKeyDown: y ? (e) => {
|
|
90
|
+
(e.key === "Enter" || e.key === " ") && (e.preventDefault(), r?.());
|
|
91
|
+
} : void 0,
|
|
92
|
+
className: e(_ ? "p-6 sm:p-8" : "p-5", y && "cursor-pointer transition-transform duration-200 hover:-translate-y-0.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]", c),
|
|
93
|
+
children: [
|
|
94
|
+
/* @__PURE__ */ u("div", {
|
|
95
|
+
className: e("flex items-center justify-between gap-3", _ && "justify-center"),
|
|
96
|
+
children: [/* @__PURE__ */ l(g, {
|
|
97
|
+
fixture: n,
|
|
98
|
+
center: _
|
|
99
|
+
}), _ ? null : /* @__PURE__ */ l(t, { status: n.status })]
|
|
100
|
+
}),
|
|
101
|
+
/* @__PURE__ */ u("div", {
|
|
102
|
+
className: e("mt-4 grid items-center gap-3", "grid-cols-[1fr_auto_1fr]", _ && "mt-6 gap-4 sm:gap-8"),
|
|
103
|
+
children: [
|
|
104
|
+
/* @__PURE__ */ l(p, {
|
|
105
|
+
name: m.homeName,
|
|
106
|
+
sport: m.homeSport,
|
|
107
|
+
align: _ ? void 0 : "end",
|
|
108
|
+
stacked: _,
|
|
109
|
+
won: m.homeWon,
|
|
110
|
+
dimmed: m.awayWon,
|
|
111
|
+
crestSize: _ ? 72 : 40
|
|
112
|
+
}),
|
|
113
|
+
/* @__PURE__ */ l(h, {
|
|
114
|
+
matchup: m,
|
|
115
|
+
time: v,
|
|
116
|
+
size: _ ? "lg" : "md"
|
|
117
|
+
}),
|
|
118
|
+
/* @__PURE__ */ l(p, {
|
|
119
|
+
name: m.awayName,
|
|
120
|
+
sport: m.awaySport,
|
|
121
|
+
align: _ ? void 0 : "start",
|
|
122
|
+
stacked: _,
|
|
123
|
+
won: m.awayWon,
|
|
124
|
+
dimmed: m.homeWon,
|
|
125
|
+
crestSize: _ ? 72 : 40
|
|
126
|
+
})
|
|
127
|
+
]
|
|
128
|
+
}),
|
|
129
|
+
_ ? /* @__PURE__ */ l("div", {
|
|
130
|
+
className: "mt-5 flex justify-center",
|
|
131
|
+
children: /* @__PURE__ */ l(t, { status: n.status })
|
|
132
|
+
}) : null,
|
|
133
|
+
/* @__PURE__ */ u("div", {
|
|
134
|
+
className: e("mt-4 flex flex-wrap items-center gap-x-4 gap-y-1.5 text-xs text-text-secondary", _ && "mt-6 justify-center"),
|
|
135
|
+
children: [/* @__PURE__ */ u("span", {
|
|
136
|
+
className: "flex items-center gap-1",
|
|
137
|
+
children: [
|
|
138
|
+
/* @__PURE__ */ l(o, {
|
|
139
|
+
className: "h-3.5 w-3.5",
|
|
140
|
+
"aria-hidden": "true"
|
|
141
|
+
}),
|
|
142
|
+
" ",
|
|
143
|
+
a(n.scheduledAt)
|
|
144
|
+
]
|
|
145
|
+
}), n.venue ? /* @__PURE__ */ u("span", {
|
|
146
|
+
className: "flex items-center gap-1 truncate",
|
|
147
|
+
children: [
|
|
148
|
+
/* @__PURE__ */ l(s, {
|
|
149
|
+
className: "h-3.5 w-3.5 shrink-0",
|
|
150
|
+
"aria-hidden": "true"
|
|
151
|
+
}),
|
|
152
|
+
" ",
|
|
153
|
+
n.venue
|
|
154
|
+
]
|
|
155
|
+
}) : null]
|
|
156
|
+
})
|
|
157
|
+
]
|
|
158
|
+
});
|
|
159
|
+
};
|
|
160
|
+
//#endregion
|
|
161
|
+
export { p as MatchupSide, h as ScoreCenter, _ as Scoreboard, f as resolveMatchup };
|
|
162
|
+
|
|
163
|
+
//# sourceMappingURL=Scoreboard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Scoreboard.js","names":[],"sources":["../../../src/atheletebridge/components/Scoreboard.tsx"],"sourcesContent":["/**\n * Scoreboard\n * @module atheletebridge/components\n *\n * Crest-vs-crest fixture presentation. `Scoreboard` renders the full match card\n * (competition strip → crest / score / crest → venue) used by the fixtures list\n * and fixture detail; the compact single-line `MatchupRow` (separate file)\n * reuses the same building blocks for the dashboard + team-detail feeds.\n *\n * All colors are semantic tokens. Winner side is tinted with\n * status-success-text, loser muted — driven purely by the real score.\n */\n\nimport type { FC, ReactNode } from 'react';\nimport { CalendarClock, MapPin, Trophy } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { StatusBadge } from './StatusBadge';\nimport { Crest } from './Crest';\nimport { SPORT_ICONS, FIXTURE_TYPE_LABELS } from './domain';\nimport { formatDate } from '../utils/formatters';\nimport { cn } from '../utils/cn';\nimport type { AthFixture, AthSport } from '../types';\n\n// ── Shared matchup model ─────────────────────────────────────────────────────\n\nexport interface Matchup {\n homeName: string;\n awayName: string;\n homeSport?: AthSport;\n awaySport?: AthSport;\n homeScore: number | null;\n awayScore: number | null;\n hasScore: boolean;\n homeWon: boolean;\n awayWon: boolean;\n}\n\n/** Derive the display matchup (names, scores, winner) from a fixture. */\nexport function resolveMatchup(fixture: AthFixture): Matchup {\n const homeName = fixture.homeTeam?.name ?? 'Home';\n const awayName = fixture.awayTeam?.name ?? (fixture.awayTeamId ? 'Away' : 'TBD');\n const hasScore = fixture.homeScore !== null && fixture.awayScore !== null;\n const homeScore = fixture.homeScore;\n const awayScore = fixture.awayScore;\n const homeWon = hasScore && (homeScore ?? 0) > (awayScore ?? 0);\n const awayWon = hasScore && (awayScore ?? 0) > (homeScore ?? 0);\n return {\n homeName,\n awayName,\n homeSport: fixture.homeTeam?.sport,\n awaySport: fixture.awayTeam?.sport,\n homeScore,\n awayScore,\n hasScore,\n homeWon,\n awayWon,\n };\n}\n\n// ── One team flank (crest + name) ────────────────────────────────────────────\n\nexport interface MatchupSideProps {\n name: string;\n sport?: AthSport;\n align?: 'start' | 'end';\n /** Winner emphasis — brightens the name. */\n won?: boolean;\n /** Loser de-emphasis when the fixture has a score. */\n dimmed?: boolean;\n crestSize?: number;\n /** Stack crest above name (hero/card) instead of inline (row). */\n stacked?: boolean;\n className?: string;\n}\n\nexport const MatchupSide: FC<MatchupSideProps> = ({\n name,\n sport,\n align = 'start',\n won = false,\n dimmed = false,\n crestSize = 40,\n stacked = false,\n className,\n}) => (\n <div\n className={cn(\n 'flex min-w-0 items-center gap-3',\n stacked ? 'flex-col text-center' : align === 'end' ? 'flex-row-reverse text-right' : 'text-left',\n className\n )}\n >\n <Crest name={name} sport={sport} size={crestSize} />\n <span\n className={cn(\n 'min-w-0 truncate font-semibold',\n stacked ? 'max-w-[10rem] text-sm' : 'text-sm',\n won ? 'text-status-success-text' : dimmed ? 'text-text-muted' : 'text-text-primary'\n )}\n >\n {name}\n </span>\n </div>\n);\n\n// ── Centerpiece (score or vs + kickoff) ──────────────────────────────────────\n\nexport interface ScoreCenterProps {\n matchup: Matchup;\n time?: ReactNode;\n size?: 'sm' | 'md' | 'lg';\n className?: string;\n}\n\nconst SCORE_SIZE = {\n sm: 'text-lg',\n md: 'text-2xl',\n lg: 'text-4xl sm:text-5xl',\n} as const;\n\nexport const ScoreCenter: FC<ScoreCenterProps> = ({ matchup, time, size = 'md', className }) => {\n if (!matchup.hasScore) {\n return (\n <div className={cn('flex flex-col items-center gap-0.5', className)}>\n <span className=\"rounded-lg bg-bg-sunken px-3 py-1 text-xs font-bold uppercase tracking-wide text-text-secondary\">\n vs\n </span>\n {time ? <span className=\"text-xs text-text-secondary\">{time}</span> : null}\n </div>\n );\n }\n return (\n <div className={cn('flex flex-col items-center gap-0.5', className)}>\n <span\n className={cn(\n 'flex items-center gap-2 font-bold leading-none tabular-nums',\n SCORE_SIZE[size]\n )}\n >\n <span className={matchup.homeWon ? 'text-status-success-text' : 'text-text-primary'}>\n {matchup.homeScore}\n </span>\n <span className=\"text-text-muted\">–</span>\n <span className={matchup.awayWon ? 'text-status-success-text' : 'text-text-primary'}>\n {matchup.awayScore}\n </span>\n </span>\n {time ? <span className=\"text-xs text-text-secondary\">{time}</span> : null}\n </div>\n );\n};\n\n// ── Competition eyebrow ──────────────────────────────────────────────────────\n\nconst CompetitionStrip: FC<{ fixture: AthFixture; center?: boolean }> = ({\n fixture,\n center = false,\n}) => {\n const SportIcon = fixture.homeTeam?.sport ? SPORT_ICONS[fixture.homeTeam.sport] : Trophy;\n const parts = [fixture.competition, fixture.round].filter(Boolean).join(' · ');\n return (\n <span\n className={cn(\n 'inline-flex items-center gap-1.5 text-xs font-medium text-text-secondary',\n center && 'justify-center'\n )}\n >\n <SportIcon className=\"h-3.5 w-3.5 shrink-0 text-text-muted\" aria-hidden=\"true\" />\n <span className=\"truncate\">{parts || FIXTURE_TYPE_LABELS[fixture.type]}</span>\n </span>\n );\n};\n\n// ── Full match card / hero ───────────────────────────────────────────────────\n\nexport interface ScoreboardProps {\n fixture: AthFixture;\n onClick?: () => void;\n /** `card` = fixtures-list tile; `hero` = large fixture-detail header. */\n variant?: 'card' | 'hero';\n className?: string;\n}\n\n/** Full crest-vs-crest match card (list tile or detail hero). */\nexport const Scoreboard: FC<ScoreboardProps> = ({\n fixture,\n onClick,\n variant = 'card',\n className,\n}) => {\n const matchup = resolveMatchup(fixture);\n const isHero = variant === 'hero';\n const timeLabel = matchup.hasScore ? null : formatDate(fixture.scheduledAt);\n\n const clickable = Boolean(onClick);\n\n return (\n <GlassCard\n treatment={isHero ? 'glass' : 'metric'}\n glow={isHero}\n role={clickable ? 'button' : undefined}\n tabIndex={clickable ? 0 : undefined}\n onClick={onClick}\n onKeyDown={\n clickable\n ? (e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onClick?.();\n }\n }\n : undefined\n }\n className={cn(\n isHero ? 'p-6 sm:p-8' : 'p-5',\n clickable &&\n 'cursor-pointer 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 className\n )}\n >\n <div className={cn('flex items-center justify-between gap-3', isHero && 'justify-center')}>\n <CompetitionStrip fixture={fixture} center={isHero} />\n {!isHero ? <StatusBadge status={fixture.status} /> : null}\n </div>\n\n <div\n className={cn(\n 'mt-4 grid items-center gap-3',\n 'grid-cols-[1fr_auto_1fr]',\n isHero && 'mt-6 gap-4 sm:gap-8'\n )}\n >\n <MatchupSide\n name={matchup.homeName}\n sport={matchup.homeSport}\n align={isHero ? undefined : 'end'}\n stacked={isHero}\n won={matchup.homeWon}\n dimmed={matchup.awayWon}\n crestSize={isHero ? 72 : 40}\n />\n <ScoreCenter matchup={matchup} time={timeLabel} size={isHero ? 'lg' : 'md'} />\n <MatchupSide\n name={matchup.awayName}\n sport={matchup.awaySport}\n align={isHero ? undefined : 'start'}\n stacked={isHero}\n won={matchup.awayWon}\n dimmed={matchup.homeWon}\n crestSize={isHero ? 72 : 40}\n />\n </div>\n\n {isHero ? (\n <div className=\"mt-5 flex justify-center\">\n <StatusBadge status={fixture.status} />\n </div>\n ) : null}\n\n <div\n className={cn(\n 'mt-4 flex flex-wrap items-center gap-x-4 gap-y-1.5 text-xs text-text-secondary',\n isHero && 'mt-6 justify-center'\n )}\n >\n <span className=\"flex items-center gap-1\">\n <CalendarClock className=\"h-3.5 w-3.5\" aria-hidden=\"true\" /> {formatDate(fixture.scheduledAt)}\n </span>\n {fixture.venue ? (\n <span className=\"flex items-center gap-1 truncate\">\n <MapPin className=\"h-3.5 w-3.5 shrink-0\" aria-hidden=\"true\" /> {fixture.venue}\n </span>\n ) : null}\n </div>\n </GlassCard>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1,76 +1,99 @@
|
|
|
1
1
|
import { cn as e } from "../utils/cn.js";
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
2
|
+
import { RatingStars as t } from "./viz/RatingStars.js";
|
|
3
|
+
import "./viz/index.js";
|
|
4
|
+
import { StatusBadge as n } from "./StatusBadge.js";
|
|
5
|
+
import { SESSION_TYPE_LABELS as r } from "./domain.js";
|
|
6
|
+
import { formatDate as i, formatDuration as a } from "../utils/formatters.js";
|
|
7
|
+
import { UserAvatar as o } from "./UserAvatar.js";
|
|
8
|
+
import { CalendarClock as s, Clock as c, MapPin as l, Video as u } from "lucide-react";
|
|
9
|
+
import { jsx as d, jsxs as f } from "react/jsx-runtime";
|
|
10
|
+
import { GlassCard as p } from "@burdenoff/fe-libs/ui";
|
|
8
11
|
//#region src/atheletebridge/components/SessionCard.tsx
|
|
9
|
-
var
|
|
12
|
+
var m = ({ session: m, onClick: h, athleteName: g, coachName: _ }) => /* @__PURE__ */ f(p, {
|
|
10
13
|
treatment: "metric",
|
|
11
14
|
role: "button",
|
|
12
15
|
tabIndex: 0,
|
|
13
|
-
onClick:
|
|
16
|
+
onClick: h,
|
|
14
17
|
onKeyDown: (e) => {
|
|
15
|
-
(e.key === "Enter" || e.key === " ") && (e.preventDefault(),
|
|
18
|
+
(e.key === "Enter" || e.key === " ") && (e.preventDefault(), h?.());
|
|
16
19
|
},
|
|
17
|
-
className: e("cursor-pointer p-5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]"),
|
|
18
|
-
children: [/* @__PURE__ */
|
|
20
|
+
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)]"),
|
|
21
|
+
children: [/* @__PURE__ */ f("div", {
|
|
19
22
|
className: "flex items-start justify-between gap-3",
|
|
20
|
-
children: [/* @__PURE__ */
|
|
21
|
-
className: "min-w-0",
|
|
22
|
-
children: [/* @__PURE__ */
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
children: [/* @__PURE__ */ f("div", {
|
|
24
|
+
className: "flex min-w-0 items-center gap-3",
|
|
25
|
+
children: [/* @__PURE__ */ d(o, {
|
|
26
|
+
name: g,
|
|
27
|
+
id: m.athleteId,
|
|
28
|
+
size: "md"
|
|
29
|
+
}), /* @__PURE__ */ f("div", {
|
|
30
|
+
className: "min-w-0",
|
|
31
|
+
children: [/* @__PURE__ */ d("h3", {
|
|
32
|
+
className: "truncate text-base font-semibold text-text-primary",
|
|
33
|
+
children: m.title
|
|
34
|
+
}), /* @__PURE__ */ f("p", {
|
|
35
|
+
className: "truncate text-sm text-text-secondary",
|
|
36
|
+
children: [
|
|
37
|
+
g?.trim() || "Athlete",
|
|
38
|
+
" · ",
|
|
39
|
+
r[m.type],
|
|
40
|
+
_?.trim() ? ` · ${_.trim()}` : ""
|
|
41
|
+
]
|
|
42
|
+
})]
|
|
28
43
|
})]
|
|
29
|
-
}), /* @__PURE__ */
|
|
30
|
-
}), /* @__PURE__ */
|
|
44
|
+
}), /* @__PURE__ */ d(n, { status: m.status })]
|
|
45
|
+
}), /* @__PURE__ */ f("div", {
|
|
31
46
|
className: "mt-4 flex flex-wrap items-center gap-x-4 gap-y-1.5 text-xs text-text-secondary",
|
|
32
47
|
children: [
|
|
33
|
-
/* @__PURE__ */
|
|
48
|
+
/* @__PURE__ */ f("span", {
|
|
34
49
|
className: "flex items-center gap-1",
|
|
35
50
|
children: [
|
|
36
|
-
/* @__PURE__ */
|
|
51
|
+
/* @__PURE__ */ d(s, {
|
|
52
|
+
className: "h-3.5 w-3.5",
|
|
53
|
+
"aria-hidden": "true"
|
|
54
|
+
}),
|
|
37
55
|
" ",
|
|
38
|
-
|
|
56
|
+
i(m.scheduledAt)
|
|
39
57
|
]
|
|
40
58
|
}),
|
|
41
|
-
/* @__PURE__ */
|
|
59
|
+
/* @__PURE__ */ f("span", {
|
|
42
60
|
className: "flex items-center gap-1",
|
|
43
61
|
children: [
|
|
44
|
-
/* @__PURE__ */
|
|
62
|
+
/* @__PURE__ */ d(c, {
|
|
63
|
+
className: "h-3.5 w-3.5",
|
|
64
|
+
"aria-hidden": "true"
|
|
65
|
+
}),
|
|
45
66
|
" ",
|
|
46
|
-
|
|
67
|
+
a(m.durationMin)
|
|
47
68
|
]
|
|
48
69
|
}),
|
|
49
|
-
|
|
70
|
+
m.type === "IN_PERSON" && m.location && /* @__PURE__ */ f("span", {
|
|
50
71
|
className: "flex items-center gap-1 truncate",
|
|
51
72
|
children: [
|
|
52
|
-
/* @__PURE__ */
|
|
73
|
+
/* @__PURE__ */ d(l, {
|
|
74
|
+
className: "h-3.5 w-3.5 shrink-0",
|
|
75
|
+
"aria-hidden": "true"
|
|
76
|
+
}),
|
|
53
77
|
" ",
|
|
54
|
-
|
|
78
|
+
m.location
|
|
55
79
|
]
|
|
56
80
|
}),
|
|
57
|
-
|
|
81
|
+
m.type !== "IN_PERSON" && m.meetingUrl && /* @__PURE__ */ f("span", {
|
|
58
82
|
className: "flex items-center gap-1",
|
|
59
|
-
children: [/* @__PURE__ */ u
|
|
83
|
+
children: [/* @__PURE__ */ d(u, {
|
|
84
|
+
className: "h-3.5 w-3.5",
|
|
85
|
+
"aria-hidden": "true"
|
|
86
|
+
}), " Online"]
|
|
60
87
|
}),
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
" ",
|
|
66
|
-
p.rating,
|
|
67
|
-
"/5"
|
|
68
|
-
]
|
|
88
|
+
m.rating !== null && /* @__PURE__ */ d(t, {
|
|
89
|
+
value: m.rating,
|
|
90
|
+
size: 14,
|
|
91
|
+
showValue: !0
|
|
69
92
|
})
|
|
70
93
|
]
|
|
71
94
|
})]
|
|
72
95
|
});
|
|
73
96
|
//#endregion
|
|
74
|
-
export {
|
|
97
|
+
export { m as SessionCard };
|
|
75
98
|
|
|
76
99
|
//# sourceMappingURL=SessionCard.js.map
|