@burdenoff/microfe-atheletebridge 2026.626.1 → 2026.629.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 (162) hide show
  1. package/dist/atheletebridge/AthleteBridgeRoot.js +49 -129
  2. package/dist/atheletebridge/AthleteBridgeRoot.js.map +1 -1
  3. package/dist/atheletebridge/AthleteBridgeRoutes.js +65 -184
  4. package/dist/atheletebridge/AthleteBridgeRoutes.js.map +1 -1
  5. package/dist/atheletebridge/components/CursorPager.js +34 -0
  6. package/dist/atheletebridge/components/CursorPager.js.map +1 -0
  7. package/dist/atheletebridge/components/FilterSelect.js +22 -0
  8. package/dist/atheletebridge/components/FilterSelect.js.map +1 -0
  9. package/dist/atheletebridge/components/FixtureCard.js +76 -0
  10. package/dist/atheletebridge/components/FixtureCard.js.map +1 -0
  11. package/dist/atheletebridge/components/LoadingState.js +36 -0
  12. package/dist/atheletebridge/components/LoadingState.js.map +1 -0
  13. package/dist/atheletebridge/components/ProgramCard.js +69 -0
  14. package/dist/atheletebridge/components/ProgramCard.js.map +1 -0
  15. package/dist/atheletebridge/components/SessionCard.js +76 -0
  16. package/dist/atheletebridge/components/SessionCard.js.map +1 -0
  17. package/dist/atheletebridge/components/StatusBadge.js +44 -35
  18. package/dist/atheletebridge/components/StatusBadge.js.map +1 -1
  19. package/dist/atheletebridge/components/TeamCard.js +81 -0
  20. package/dist/atheletebridge/components/TeamCard.js.map +1 -0
  21. package/dist/atheletebridge/components/domain.js +47 -0
  22. package/dist/atheletebridge/components/domain.js.map +1 -0
  23. package/dist/atheletebridge/components/form.js +29 -0
  24. package/dist/atheletebridge/components/form.js.map +1 -0
  25. package/dist/atheletebridge/context/AthleteBridgeContext.js +6 -6
  26. package/dist/atheletebridge/context/AthleteBridgeContext.js.map +1 -1
  27. package/dist/atheletebridge/hooks/useAthleteBridgeApi.js +342 -165
  28. package/dist/atheletebridge/hooks/useAthleteBridgeApi.js.map +1 -1
  29. package/dist/atheletebridge/index.js +1 -1
  30. package/dist/atheletebridge/pages/DashboardPage.js +169 -0
  31. package/dist/atheletebridge/pages/DashboardPage.js.map +1 -0
  32. package/dist/atheletebridge/pages/StandingsPage.js +166 -0
  33. package/dist/atheletebridge/pages/StandingsPage.js.map +1 -0
  34. package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js +201 -0
  35. package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js.map +1 -0
  36. package/dist/atheletebridge/pages/fixtures/FixtureFormPage.js +205 -0
  37. package/dist/atheletebridge/pages/fixtures/FixtureFormPage.js.map +1 -0
  38. package/dist/atheletebridge/pages/fixtures/FixturesListPage.js +92 -0
  39. package/dist/atheletebridge/pages/fixtures/FixturesListPage.js.map +1 -0
  40. package/dist/atheletebridge/pages/programs/ProgramDetailPage.js +374 -0
  41. package/dist/atheletebridge/pages/programs/ProgramDetailPage.js.map +1 -0
  42. package/dist/atheletebridge/pages/programs/ProgramFormPage.js +207 -0
  43. package/dist/atheletebridge/pages/programs/ProgramFormPage.js.map +1 -0
  44. package/dist/atheletebridge/pages/programs/ProgramsListPage.js +113 -0
  45. package/dist/atheletebridge/pages/programs/ProgramsListPage.js.map +1 -0
  46. package/dist/atheletebridge/pages/sessions/SessionDetailPage.js +224 -0
  47. package/dist/atheletebridge/pages/sessions/SessionDetailPage.js.map +1 -0
  48. package/dist/atheletebridge/pages/sessions/SessionFormPage.js +206 -0
  49. package/dist/atheletebridge/pages/sessions/SessionFormPage.js.map +1 -0
  50. package/dist/atheletebridge/pages/sessions/SessionsListPage.js +92 -0
  51. package/dist/atheletebridge/pages/sessions/SessionsListPage.js.map +1 -0
  52. package/dist/atheletebridge/pages/teams/TeamDetailPage.js +350 -0
  53. package/dist/atheletebridge/pages/teams/TeamDetailPage.js.map +1 -0
  54. package/dist/atheletebridge/pages/teams/TeamFormPage.js +222 -0
  55. package/dist/atheletebridge/pages/teams/TeamFormPage.js.map +1 -0
  56. package/dist/atheletebridge/pages/teams/TeamsListPage.js +101 -0
  57. package/dist/atheletebridge/pages/teams/TeamsListPage.js.map +1 -0
  58. package/dist/atheletebridge/store/atheletebridgeStore.js +2 -13
  59. package/dist/atheletebridge/store/atheletebridgeStore.js.map +1 -1
  60. package/dist/atheletebridge/utils/datetime.js +10 -0
  61. package/dist/atheletebridge/utils/datetime.js.map +1 -0
  62. package/dist/atheletebridge/utils/formatters.js +1 -4
  63. package/dist/atheletebridge/utils/formatters.js.map +1 -1
  64. package/package.json +1 -1
  65. package/dist/atheletebridge/components/InvestmentCard.js +0 -96
  66. package/dist/atheletebridge/components/InvestmentCard.js.map +0 -1
  67. package/dist/atheletebridge/components/RatingDisplay.js +0 -35
  68. package/dist/atheletebridge/components/RatingDisplay.js.map +0 -1
  69. package/dist/atheletebridge/mocks/data.js +0 -1517
  70. package/dist/atheletebridge/mocks/data.js.map +0 -1
  71. package/dist/atheletebridge/mocks/users.js +0 -34
  72. package/dist/atheletebridge/mocks/users.js.map +0 -1
  73. package/dist/atheletebridge/pages/HomePage.js +0 -936
  74. package/dist/atheletebridge/pages/HomePage.js.map +0 -1
  75. package/dist/atheletebridge/pages/admin/AdminAnalyticsPage.js +0 -653
  76. package/dist/atheletebridge/pages/admin/AdminAnalyticsPage.js.map +0 -1
  77. package/dist/atheletebridge/pages/admin/AdminDashboard.js +0 -671
  78. package/dist/atheletebridge/pages/admin/AdminDashboard.js.map +0 -1
  79. package/dist/atheletebridge/pages/admin/AdminFinancialPage.js +0 -429
  80. package/dist/atheletebridge/pages/admin/AdminFinancialPage.js.map +0 -1
  81. package/dist/atheletebridge/pages/admin/AdminOrdersPage.js +0 -667
  82. package/dist/atheletebridge/pages/admin/AdminOrdersPage.js.map +0 -1
  83. package/dist/atheletebridge/pages/admin/AdminProfilePage.js +0 -261
  84. package/dist/atheletebridge/pages/admin/AdminProfilePage.js.map +0 -1
  85. package/dist/atheletebridge/pages/admin/AdminSettingsPage.js +0 -653
  86. package/dist/atheletebridge/pages/admin/AdminSettingsPage.js.map +0 -1
  87. package/dist/atheletebridge/pages/admin/CartManagementPage.js +0 -876
  88. package/dist/atheletebridge/pages/admin/CartManagementPage.js.map +0 -1
  89. package/dist/atheletebridge/pages/admin/UserManagementPage.js +0 -900
  90. package/dist/atheletebridge/pages/admin/UserManagementPage.js.map +0 -1
  91. package/dist/atheletebridge/pages/cart-owner/AnalyticsDashboard.js +0 -839
  92. package/dist/atheletebridge/pages/cart-owner/AnalyticsDashboard.js.map +0 -1
  93. package/dist/atheletebridge/pages/cart-owner/BusinessSettingsPage.js +0 -704
  94. package/dist/atheletebridge/pages/cart-owner/BusinessSettingsPage.js.map +0 -1
  95. package/dist/atheletebridge/pages/cart-owner/CartOwnerDashboard.js +0 -792
  96. package/dist/atheletebridge/pages/cart-owner/CartOwnerDashboard.js.map +0 -1
  97. package/dist/atheletebridge/pages/cart-owner/CartOwnerProfilePage.js +0 -891
  98. package/dist/atheletebridge/pages/cart-owner/CartOwnerProfilePage.js.map +0 -1
  99. package/dist/atheletebridge/pages/cart-owner/CartServiceConfigPage.js +0 -633
  100. package/dist/atheletebridge/pages/cart-owner/CartServiceConfigPage.js.map +0 -1
  101. package/dist/atheletebridge/pages/cart-owner/InventoryManagementPage.js +0 -913
  102. package/dist/atheletebridge/pages/cart-owner/InventoryManagementPage.js.map +0 -1
  103. package/dist/atheletebridge/pages/cart-owner/OrderManagementPage.js +0 -881
  104. package/dist/atheletebridge/pages/cart-owner/OrderManagementPage.js.map +0 -1
  105. package/dist/atheletebridge/pages/cart-owner/StaffManagementPage.js +0 -841
  106. package/dist/atheletebridge/pages/cart-owner/StaffManagementPage.js.map +0 -1
  107. package/dist/atheletebridge/pages/common/HelpSupportPage.js +0 -1319
  108. package/dist/atheletebridge/pages/common/HelpSupportPage.js.map +0 -1
  109. package/dist/atheletebridge/pages/common/NotificationCenterPage.js +0 -674
  110. package/dist/atheletebridge/pages/common/NotificationCenterPage.js.map +0 -1
  111. package/dist/atheletebridge/pages/common/SettingsPage.js +0 -972
  112. package/dist/atheletebridge/pages/common/SettingsPage.js.map +0 -1
  113. package/dist/atheletebridge/pages/customer/BookingDetailsPage.js +0 -602
  114. package/dist/atheletebridge/pages/customer/BookingDetailsPage.js.map +0 -1
  115. package/dist/atheletebridge/pages/customer/CartDetailPage.js +0 -656
  116. package/dist/atheletebridge/pages/customer/CartDetailPage.js.map +0 -1
  117. package/dist/atheletebridge/pages/customer/CartDiscoveryPage.js +0 -558
  118. package/dist/atheletebridge/pages/customer/CartDiscoveryPage.js.map +0 -1
  119. package/dist/atheletebridge/pages/customer/CheckoutPage.js +0 -536
  120. package/dist/atheletebridge/pages/customer/CheckoutPage.js.map +0 -1
  121. package/dist/atheletebridge/pages/customer/CustomerDashboard.js +0 -539
  122. package/dist/atheletebridge/pages/customer/CustomerDashboard.js.map +0 -1
  123. package/dist/atheletebridge/pages/customer/CustomerProfilePage.js +0 -657
  124. package/dist/atheletebridge/pages/customer/CustomerProfilePage.js.map +0 -1
  125. package/dist/atheletebridge/pages/customer/DirectServiceBookingPage.js +0 -635
  126. package/dist/atheletebridge/pages/customer/DirectServiceBookingPage.js.map +0 -1
  127. package/dist/atheletebridge/pages/customer/FavoritesPage.js +0 -486
  128. package/dist/atheletebridge/pages/customer/FavoritesPage.js.map +0 -1
  129. package/dist/atheletebridge/pages/customer/OrderHistoryPage.js +0 -447
  130. package/dist/atheletebridge/pages/customer/OrderHistoryPage.js.map +0 -1
  131. package/dist/atheletebridge/pages/customer/OrderTrackingPage.js +0 -393
  132. package/dist/atheletebridge/pages/customer/OrderTrackingPage.js.map +0 -1
  133. package/dist/atheletebridge/pages/customer/PaymentMethodsPage.js +0 -494
  134. package/dist/atheletebridge/pages/customer/PaymentMethodsPage.js.map +0 -1
  135. package/dist/atheletebridge/pages/customer/ReviewsPage.js +0 -615
  136. package/dist/atheletebridge/pages/customer/ReviewsPage.js.map +0 -1
  137. package/dist/atheletebridge/pages/customer/SavedAddressesPage.js +0 -451
  138. package/dist/atheletebridge/pages/customer/SavedAddressesPage.js.map +0 -1
  139. package/dist/atheletebridge/pages/investor/InvestmentDetailsPage.js +0 -881
  140. package/dist/atheletebridge/pages/investor/InvestmentDetailsPage.js.map +0 -1
  141. package/dist/atheletebridge/pages/investor/InvestmentOpportunitiesPage.js +0 -945
  142. package/dist/atheletebridge/pages/investor/InvestmentOpportunitiesPage.js.map +0 -1
  143. package/dist/atheletebridge/pages/investor/InvestorAnalyticsPage.js +0 -686
  144. package/dist/atheletebridge/pages/investor/InvestorAnalyticsPage.js.map +0 -1
  145. package/dist/atheletebridge/pages/investor/InvestorDashboard.js +0 -453
  146. package/dist/atheletebridge/pages/investor/InvestorDashboard.js.map +0 -1
  147. package/dist/atheletebridge/pages/investor/InvestorInvestmentsPage.js +0 -405
  148. package/dist/atheletebridge/pages/investor/InvestorInvestmentsPage.js.map +0 -1
  149. package/dist/atheletebridge/pages/investor/InvestorPortfolioPage.js +0 -577
  150. package/dist/atheletebridge/pages/investor/InvestorPortfolioPage.js.map +0 -1
  151. package/dist/atheletebridge/pages/investor/InvestorProfilePage.js +0 -1069
  152. package/dist/atheletebridge/pages/investor/InvestorProfilePage.js.map +0 -1
  153. package/dist/atheletebridge/pages/worker/TaskDetailsPage.js +0 -530
  154. package/dist/atheletebridge/pages/worker/TaskDetailsPage.js.map +0 -1
  155. package/dist/atheletebridge/pages/worker/WorkerDashboard.js +0 -669
  156. package/dist/atheletebridge/pages/worker/WorkerDashboard.js.map +0 -1
  157. package/dist/atheletebridge/pages/worker/WorkerOrdersPage.js +0 -681
  158. package/dist/atheletebridge/pages/worker/WorkerOrdersPage.js.map +0 -1
  159. package/dist/atheletebridge/pages/worker/WorkerProfilePage.js +0 -691
  160. package/dist/atheletebridge/pages/worker/WorkerProfilePage.js.map +0 -1
  161. package/dist/atheletebridge/pages/worker/WorkerSchedulePage.js +0 -588
  162. package/dist/atheletebridge/pages/worker/WorkerSchedulePage.js.map +0 -1
@@ -0,0 +1,374 @@
1
+ import { useAthleteBridge as e } from "../../context/AthleteBridgeContext.js";
2
+ import { PageLayout as t } from "../../components/PageLayout.js";
3
+ import { StatusBadge as n } from "../../components/StatusBadge.js";
4
+ import { PROGRAM_LEVEL_LABELS as r, SPORT_LABELS as i } from "../../components/domain.js";
5
+ import { EmptyState as a } from "../../components/EmptyState.js";
6
+ import { ErrorState as ee, LoadingState as te } from "../../components/LoadingState.js";
7
+ import { useAddDrill as o, useArchiveProgram as s, useProgram as c, useRemoveDrill as l, useTransitionProgram as u } from "../../hooks/useAthleteBridgeApi.js";
8
+ import { FormField as d, fieldInputClass as f } from "../../components/form.js";
9
+ import { useMemo as p, useState as m } from "react";
10
+ import { Archive as h, ArrowLeft as g, Copy as _, Edit as v, Loader2 as y, Plus as b, Send as x, Trash2 as S, Undo2 as C } from "lucide-react";
11
+ import { useParams as w } from "react-router-dom";
12
+ import { Fragment as T, jsx as E, jsxs as D } from "react/jsx-runtime";
13
+ import { EmphasisPanel as O, GlassCard as k, ResponsiveDialog as A, ResponsiveDialogContent as j, ResponsiveDialogFooter as M, ResponsiveDialogHeader as N, ResponsiveDialogTitle as P } from "@burdenoff/fe-libs/ui";
14
+ //#region src/atheletebridge/pages/programs/ProgramDetailPage.tsx
15
+ var F = {
16
+ name: "",
17
+ description: "",
18
+ weekNumber: "1",
19
+ sets: "",
20
+ reps: "",
21
+ durationSec: "",
22
+ restSec: ""
23
+ }, I = (e) => {
24
+ let t = e.trim();
25
+ if (!t) return;
26
+ let n = Number.parseInt(t, 10);
27
+ return Number.isNaN(n) ? void 0 : n;
28
+ };
29
+ function L() {
30
+ let { id: L } = w(), { navigateTo: R } = e(), z = c(L), B = u(), V = s(), H = o(), U = l(L), [W, G] = m(!1), [K, q] = m(F), [J, Y] = m(null), X = p(() => {
31
+ let e = z.data?.drills ?? [], t = /* @__PURE__ */ new Map();
32
+ for (let n of e) {
33
+ let e = t.get(n.weekNumber) ?? [];
34
+ e.push(n), t.set(n.weekNumber, e);
35
+ }
36
+ return Array.from(t.entries()).sort(([e], [t]) => e - t).map(([e, t]) => ({
37
+ week: e,
38
+ drills: [...t].sort((e, t) => e.orderIndex - t.orderIndex)
39
+ }));
40
+ }, [z.data]);
41
+ if (z.isLoading) return /* @__PURE__ */ E(t, {
42
+ title: "Program",
43
+ children: /* @__PURE__ */ E(te, { label: "Loading program…" })
44
+ });
45
+ if (z.isError || !z.data) return /* @__PURE__ */ E(t, {
46
+ title: "Program",
47
+ children: /* @__PURE__ */ E(ee, {
48
+ title: "Program not found",
49
+ message: "This program does not exist or you do not have access to it.",
50
+ onRetry: () => R("/programs")
51
+ })
52
+ });
53
+ let Z = z.data, Q = Z.drills?.length ?? 0, $ = (e) => {
54
+ L && B.mutate({
55
+ id: L,
56
+ status: e
57
+ });
58
+ };
59
+ return /* @__PURE__ */ D(t, {
60
+ title: Z.name,
61
+ description: `${i[Z.sport]} · ${r[Z.level]} · ${Z.weeks} ${Z.weeks === 1 ? "week" : "weeks"}`,
62
+ actions: /* @__PURE__ */ D("div", {
63
+ className: "flex flex-wrap items-center gap-2",
64
+ children: [
65
+ /* @__PURE__ */ D("button", {
66
+ type: "button",
67
+ onClick: () => R("/programs"),
68
+ className: "inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken",
69
+ children: [/* @__PURE__ */ E(g, { className: "h-4 w-4" }), " Back"]
70
+ }),
71
+ /* @__PURE__ */ D("button", {
72
+ type: "button",
73
+ onClick: () => R(`/programs/${Z.id}/edit`),
74
+ className: "inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken",
75
+ children: [/* @__PURE__ */ E(v, { className: "h-4 w-4" }), " Edit"]
76
+ }),
77
+ Z.status !== "ARCHIVED" && /* @__PURE__ */ D("button", {
78
+ type: "button",
79
+ onClick: async () => {
80
+ L && (await V.mutateAsync(L), R("/programs"));
81
+ },
82
+ disabled: V.isPending,
83
+ className: "inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:opacity-60",
84
+ children: [
85
+ V.isPending ? /* @__PURE__ */ E(y, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ E(h, { className: "h-4 w-4" }),
86
+ " ",
87
+ "Archive"
88
+ ]
89
+ })
90
+ ]
91
+ }),
92
+ children: [
93
+ /* @__PURE__ */ D(k, {
94
+ treatment: "insight",
95
+ className: "mb-8 p-6",
96
+ children: [
97
+ /* @__PURE__ */ D("div", {
98
+ className: "flex flex-wrap items-center gap-3",
99
+ children: [/* @__PURE__ */ E(n, { status: Z.status }), Z.isTemplate && /* @__PURE__ */ D("span", {
100
+ className: "inline-flex items-center gap-1 text-sm text-text-secondary",
101
+ children: [/* @__PURE__ */ E(_, { className: "h-3.5 w-3.5" }), " Template"]
102
+ })]
103
+ }),
104
+ Z.summary && /* @__PURE__ */ E("p", {
105
+ className: "mt-4 text-sm text-text-secondary",
106
+ children: Z.summary
107
+ }),
108
+ Z.tags.length > 0 && /* @__PURE__ */ E("div", {
109
+ className: "mt-4 flex flex-wrap gap-1.5",
110
+ children: Z.tags.map((e) => /* @__PURE__ */ E("span", {
111
+ className: "inline-flex items-center rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary",
112
+ children: e
113
+ }, e))
114
+ }),
115
+ /* @__PURE__ */ D("div", {
116
+ className: "mt-5 flex flex-wrap items-center gap-2 border-t border-border-seam pt-5",
117
+ children: [
118
+ /* @__PURE__ */ E("span", {
119
+ className: "text-sm font-medium text-text-primary",
120
+ children: "Publish lifecycle:"
121
+ }),
122
+ Z.status === "DRAFT" && /* @__PURE__ */ D("button", {
123
+ type: "button",
124
+ onClick: () => $("PUBLISHED"),
125
+ disabled: B.isPending,
126
+ className: "inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-60",
127
+ children: [/* @__PURE__ */ E(x, { className: "h-4 w-4" }), " Publish"]
128
+ }),
129
+ Z.status === "PUBLISHED" && /* @__PURE__ */ D(T, { children: [/* @__PURE__ */ D("button", {
130
+ type: "button",
131
+ onClick: () => $("DRAFT"),
132
+ disabled: B.isPending,
133
+ className: "inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:opacity-60",
134
+ children: [/* @__PURE__ */ E(C, { className: "h-4 w-4" }), " Pull back to draft"]
135
+ }), /* @__PURE__ */ D("button", {
136
+ type: "button",
137
+ onClick: () => $("ARCHIVED"),
138
+ disabled: B.isPending,
139
+ className: "inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:opacity-60",
140
+ children: [/* @__PURE__ */ E(h, { className: "h-4 w-4" }), " Archive"]
141
+ })] }),
142
+ Z.status === "ARCHIVED" && /* @__PURE__ */ E("span", {
143
+ className: "text-sm text-text-secondary",
144
+ children: "This program is archived."
145
+ }),
146
+ B.isPending && /* @__PURE__ */ E(y, { className: "h-4 w-4 animate-spin text-text-secondary" })
147
+ ]
148
+ })
149
+ ]
150
+ }),
151
+ /* @__PURE__ */ D("section", { children: [/* @__PURE__ */ D("div", {
152
+ className: "mb-4 flex items-center justify-between",
153
+ children: [/* @__PURE__ */ D("h2", {
154
+ className: "text-lg font-semibold text-text-primary",
155
+ children: [
156
+ "Drills (",
157
+ Q,
158
+ ")"
159
+ ]
160
+ }), /* @__PURE__ */ D("button", {
161
+ type: "button",
162
+ onClick: () => {
163
+ Y(null), G(!0);
164
+ },
165
+ className: "inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90",
166
+ children: [/* @__PURE__ */ E(b, { className: "h-4 w-4" }), " Add drill"]
167
+ })]
168
+ }), Q === 0 ? /* @__PURE__ */ E(a, {
169
+ illustration: "empty-data",
170
+ icon: /* @__PURE__ */ E(b, { className: "h-10 w-10" }),
171
+ title: "No drills yet",
172
+ description: "Add drills with sets, reps and durations, organized by week."
173
+ }) : /* @__PURE__ */ E("div", {
174
+ className: "space-y-6",
175
+ children: X.map(({ week: e, drills: t }) => /* @__PURE__ */ D("div", { children: [/* @__PURE__ */ D("h3", {
176
+ className: "mb-3 text-sm font-semibold uppercase tracking-wide text-text-secondary",
177
+ children: ["Week ", e]
178
+ }), /* @__PURE__ */ E(O, { children: /* @__PURE__ */ E("div", {
179
+ className: "divide-y divide-border-seam",
180
+ children: t.map((e) => /* @__PURE__ */ D("div", {
181
+ className: "flex items-start gap-4 py-3",
182
+ children: [/* @__PURE__ */ D("div", {
183
+ className: "min-w-0 flex-1",
184
+ children: [
185
+ /* @__PURE__ */ E("p", {
186
+ className: "text-sm font-medium text-text-primary",
187
+ children: e.name
188
+ }),
189
+ e.description && /* @__PURE__ */ E("p", {
190
+ className: "mt-0.5 text-xs text-text-secondary",
191
+ children: e.description
192
+ }),
193
+ /* @__PURE__ */ D("div", {
194
+ className: "mt-1.5 flex flex-wrap gap-x-3 gap-y-1 text-xs text-text-secondary",
195
+ children: [
196
+ e.sets !== null && /* @__PURE__ */ D("span", { children: [e.sets, " sets"] }),
197
+ e.reps !== null && /* @__PURE__ */ D("span", { children: [e.reps, " reps"] }),
198
+ e.durationSec !== null && /* @__PURE__ */ D("span", { children: [e.durationSec, "s work"] }),
199
+ e.restSec !== null && /* @__PURE__ */ D("span", { children: [e.restSec, "s rest"] })
200
+ ]
201
+ })
202
+ ]
203
+ }), /* @__PURE__ */ E("button", {
204
+ type: "button",
205
+ onClick: () => void U.mutate(e.id),
206
+ disabled: U.isPending,
207
+ "aria-label": `Remove ${e.name}`,
208
+ className: "rounded-lg p-2 text-text-secondary transition-colors hover:bg-status-error-bg-subtle hover:text-status-error-text disabled:opacity-60",
209
+ children: /* @__PURE__ */ E(S, { className: "h-4 w-4" })
210
+ })]
211
+ }, e.id))
212
+ }) })] }, e))
213
+ })] }),
214
+ /* @__PURE__ */ E(A, {
215
+ open: W,
216
+ onOpenChange: G,
217
+ children: /* @__PURE__ */ D(j, { children: [/* @__PURE__ */ E(N, { children: /* @__PURE__ */ E(P, { children: "Add drill" }) }), /* @__PURE__ */ D("form", {
218
+ onSubmit: async (e) => {
219
+ if (e.preventDefault(), Y(null), !L) return;
220
+ if (!K.name.trim()) {
221
+ Y("Drill name is required.");
222
+ return;
223
+ }
224
+ let t = I(K.weekNumber) ?? 1;
225
+ try {
226
+ await H.mutateAsync({
227
+ programId: L,
228
+ name: K.name.trim(),
229
+ description: K.description.trim() || void 0,
230
+ weekNumber: t,
231
+ orderIndex: z.data?.drills?.filter((e) => e.weekNumber === t).length ?? 0,
232
+ sets: I(K.sets),
233
+ reps: I(K.reps),
234
+ durationSec: I(K.durationSec),
235
+ restSec: I(K.restSec)
236
+ }), q(F), G(!1);
237
+ } catch (e) {
238
+ Y(e instanceof Error ? e.message : "Failed to add drill.");
239
+ }
240
+ },
241
+ className: "space-y-4 px-1",
242
+ children: [
243
+ /* @__PURE__ */ E(d, {
244
+ label: "Name",
245
+ htmlFor: "drillName",
246
+ required: !0,
247
+ children: /* @__PURE__ */ E("input", {
248
+ id: "drillName",
249
+ className: f,
250
+ value: K.name,
251
+ onChange: (e) => q((t) => ({
252
+ ...t,
253
+ name: e.target.value
254
+ })),
255
+ placeholder: "e.g. Box jumps"
256
+ })
257
+ }),
258
+ /* @__PURE__ */ E(d, {
259
+ label: "Description",
260
+ htmlFor: "drillDesc",
261
+ children: /* @__PURE__ */ E("textarea", {
262
+ id: "drillDesc",
263
+ className: f,
264
+ rows: 2,
265
+ value: K.description,
266
+ onChange: (e) => q((t) => ({
267
+ ...t,
268
+ description: e.target.value
269
+ })),
270
+ placeholder: "How to perform the drill."
271
+ })
272
+ }),
273
+ /* @__PURE__ */ D("div", {
274
+ className: "grid grid-cols-3 gap-4",
275
+ children: [
276
+ /* @__PURE__ */ E(d, {
277
+ label: "Week",
278
+ htmlFor: "drillWeek",
279
+ children: /* @__PURE__ */ E("input", {
280
+ id: "drillWeek",
281
+ inputMode: "numeric",
282
+ className: f,
283
+ value: K.weekNumber,
284
+ onChange: (e) => q((t) => ({
285
+ ...t,
286
+ weekNumber: e.target.value
287
+ }))
288
+ })
289
+ }),
290
+ /* @__PURE__ */ E(d, {
291
+ label: "Sets",
292
+ htmlFor: "drillSets",
293
+ children: /* @__PURE__ */ E("input", {
294
+ id: "drillSets",
295
+ inputMode: "numeric",
296
+ className: f,
297
+ value: K.sets,
298
+ onChange: (e) => q((t) => ({
299
+ ...t,
300
+ sets: e.target.value
301
+ }))
302
+ })
303
+ }),
304
+ /* @__PURE__ */ E(d, {
305
+ label: "Reps",
306
+ htmlFor: "drillReps",
307
+ children: /* @__PURE__ */ E("input", {
308
+ id: "drillReps",
309
+ inputMode: "numeric",
310
+ className: f,
311
+ value: K.reps,
312
+ onChange: (e) => q((t) => ({
313
+ ...t,
314
+ reps: e.target.value
315
+ }))
316
+ })
317
+ })
318
+ ]
319
+ }),
320
+ /* @__PURE__ */ D("div", {
321
+ className: "grid grid-cols-2 gap-4",
322
+ children: [/* @__PURE__ */ E(d, {
323
+ label: "Work (sec)",
324
+ htmlFor: "drillDuration",
325
+ children: /* @__PURE__ */ E("input", {
326
+ id: "drillDuration",
327
+ inputMode: "numeric",
328
+ className: f,
329
+ value: K.durationSec,
330
+ onChange: (e) => q((t) => ({
331
+ ...t,
332
+ durationSec: e.target.value
333
+ }))
334
+ })
335
+ }), /* @__PURE__ */ E(d, {
336
+ label: "Rest (sec)",
337
+ htmlFor: "drillRest",
338
+ children: /* @__PURE__ */ E("input", {
339
+ id: "drillRest",
340
+ inputMode: "numeric",
341
+ className: f,
342
+ value: K.restSec,
343
+ onChange: (e) => q((t) => ({
344
+ ...t,
345
+ restSec: e.target.value
346
+ }))
347
+ })
348
+ })]
349
+ }),
350
+ J && /* @__PURE__ */ E("p", {
351
+ className: "text-sm text-status-error-text",
352
+ children: J
353
+ }),
354
+ /* @__PURE__ */ D(M, { children: [/* @__PURE__ */ E("button", {
355
+ type: "button",
356
+ onClick: () => G(!1),
357
+ className: "rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken",
358
+ children: "Cancel"
359
+ }), /* @__PURE__ */ D("button", {
360
+ type: "submit",
361
+ disabled: H.isPending,
362
+ className: "inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-60",
363
+ children: [H.isPending && /* @__PURE__ */ E(y, { className: "h-4 w-4 animate-spin" }), "Add drill"]
364
+ })] })
365
+ ]
366
+ })] })
367
+ })
368
+ ]
369
+ });
370
+ }
371
+ //#endregion
372
+ export { L as ProgramDetailPage };
373
+
374
+ //# sourceMappingURL=ProgramDetailPage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProgramDetailPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/programs/ProgramDetailPage.tsx"],"sourcesContent":["/**\n * Program Detail\n * @module atheletebridge/pages/programs\n *\n * Reads a single program with its drills (athProgram), supports the publish\n * lifecycle (athTransitionProgram), editing/archiving, and adding/removing\n * drills (athAddDrill / athRemoveDrill).\n */\n\nimport { useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { ArrowLeft, Edit, Archive, Plus, Trash2, Loader2, Send, Undo2, Copy } from 'lucide-react';\nimport {\n GlassCard,\n EmphasisPanel,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { FormField, fieldInputClass } from '../../components/form';\nimport { SPORT_LABELS, PROGRAM_LEVEL_LABELS } from '../../components/domain';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport {\n useProgram,\n useTransitionProgram,\n useArchiveProgram,\n useAddDrill,\n useRemoveDrill,\n} from '../../hooks/useAthleteBridgeApi';\nimport type { AthDrill, AthProgramStatus } from '../../types';\n\ninterface DrillForm {\n name: string;\n description: string;\n weekNumber: string;\n sets: string;\n reps: string;\n durationSec: string;\n restSec: string;\n}\n\nconst EMPTY_DRILL: DrillForm = {\n name: '',\n description: '',\n weekNumber: '1',\n sets: '',\n reps: '',\n durationSec: '',\n restSec: '',\n};\n\nconst numOrUndefined = (v: string): number | undefined => {\n const t = v.trim();\n if (!t) return undefined;\n const n = Number.parseInt(t, 10);\n return Number.isNaN(n) ? undefined : n;\n};\n\nexport function ProgramDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useAthleteBridge();\n\n const program = useProgram(id);\n const transition = useTransitionProgram();\n const archive = useArchiveProgram();\n const addDrill = useAddDrill();\n const removeDrill = useRemoveDrill(id);\n\n const [drillOpen, setDrillOpen] = useState(false);\n const [drillForm, setDrillForm] = useState<DrillForm>(EMPTY_DRILL);\n const [drillError, setDrillError] = useState<string | null>(null);\n\n const drillsByWeek = useMemo(() => {\n const drills = program.data?.drills ?? [];\n const groups = new Map<number, AthDrill[]>();\n for (const d of drills) {\n const list = groups.get(d.weekNumber) ?? [];\n list.push(d);\n groups.set(d.weekNumber, list);\n }\n return Array.from(groups.entries())\n .sort(([a], [b]) => a - b)\n .map(([week, list]) => ({\n week,\n drills: [...list].sort((a, b) => a.orderIndex - b.orderIndex),\n }));\n }, [program.data]);\n\n if (program.isLoading) {\n return (\n <PageLayout title=\"Program\">\n <LoadingState label=\"Loading program…\" />\n </PageLayout>\n );\n }\n\n if (program.isError || !program.data) {\n return (\n <PageLayout title=\"Program\">\n <ErrorState\n title=\"Program not found\"\n message=\"This program does not exist or you do not have access to it.\"\n onRetry={() => navigateTo('/programs')}\n />\n </PageLayout>\n );\n }\n\n const p = program.data;\n const drillCount = p.drills?.length ?? 0;\n\n const handleTransition = (status: AthProgramStatus) => {\n if (!id) return;\n void transition.mutate({ id, status });\n };\n\n const handleArchive = async () => {\n if (!id) return;\n await archive.mutateAsync(id);\n navigateTo('/programs');\n };\n\n const handleAddDrill = async (e: React.FormEvent) => {\n e.preventDefault();\n setDrillError(null);\n if (!id) return;\n if (!drillForm.name.trim()) {\n setDrillError('Drill name is required.');\n return;\n }\n const week = numOrUndefined(drillForm.weekNumber) ?? 1;\n try {\n await addDrill.mutateAsync({\n programId: id,\n name: drillForm.name.trim(),\n description: drillForm.description.trim() || undefined,\n weekNumber: week,\n orderIndex: program.data?.drills?.filter((d) => d.weekNumber === week).length ?? 0,\n sets: numOrUndefined(drillForm.sets),\n reps: numOrUndefined(drillForm.reps),\n durationSec: numOrUndefined(drillForm.durationSec),\n restSec: numOrUndefined(drillForm.restSec),\n });\n setDrillForm(EMPTY_DRILL);\n setDrillOpen(false);\n } catch (err) {\n setDrillError(err instanceof Error ? err.message : 'Failed to add drill.');\n }\n };\n\n return (\n <PageLayout\n title={p.name}\n description={`${SPORT_LABELS[p.sport]} · ${PROGRAM_LEVEL_LABELS[p.level]} · ${p.weeks} ${\n p.weeks === 1 ? 'week' : 'weeks'\n }`}\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/programs')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/programs/${p.id}/edit`)}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <Edit className=\"h-4 w-4\" /> Edit\n </button>\n {p.status !== 'ARCHIVED' && (\n <button\n type=\"button\"\n onClick={handleArchive}\n disabled={archive.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:opacity-60\"\n >\n {archive.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Archive className=\"h-4 w-4\" />\n )}{' '}\n Archive\n </button>\n )}\n </div>\n }\n >\n {/* Summary + lifecycle */}\n <GlassCard treatment=\"insight\" className=\"mb-8 p-6\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <StatusBadge status={p.status} />\n {p.isTemplate && (\n <span className=\"inline-flex items-center gap-1 text-sm text-text-secondary\">\n <Copy className=\"h-3.5 w-3.5\" /> Template\n </span>\n )}\n </div>\n {p.summary && <p className=\"mt-4 text-sm text-text-secondary\">{p.summary}</p>}\n {p.tags.length > 0 && (\n <div className=\"mt-4 flex flex-wrap gap-1.5\">\n {p.tags.map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\"\n >\n {tag}\n </span>\n ))}\n </div>\n )}\n\n {/* Lifecycle transitions */}\n <div className=\"mt-5 flex flex-wrap items-center gap-2 border-t border-border-seam pt-5\">\n <span className=\"text-sm font-medium text-text-primary\">Publish lifecycle:</span>\n {p.status === 'DRAFT' && (\n <button\n type=\"button\"\n onClick={() => handleTransition('PUBLISHED')}\n disabled={transition.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-60\"\n >\n <Send className=\"h-4 w-4\" /> Publish\n </button>\n )}\n {p.status === 'PUBLISHED' && (\n <>\n <button\n type=\"button\"\n onClick={() => handleTransition('DRAFT')}\n disabled={transition.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:opacity-60\"\n >\n <Undo2 className=\"h-4 w-4\" /> Pull back to draft\n </button>\n <button\n type=\"button\"\n onClick={() => handleTransition('ARCHIVED')}\n disabled={transition.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:opacity-60\"\n >\n <Archive className=\"h-4 w-4\" /> Archive\n </button>\n </>\n )}\n {p.status === 'ARCHIVED' && (\n <span className=\"text-sm text-text-secondary\">This program is archived.</span>\n )}\n {transition.isPending && <Loader2 className=\"h-4 w-4 animate-spin text-text-secondary\" />}\n </div>\n </GlassCard>\n\n {/* Drills */}\n <section>\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Drills ({drillCount})</h2>\n <button\n type=\"button\"\n onClick={() => {\n setDrillError(null);\n setDrillOpen(true);\n }}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" /> Add drill\n </button>\n </div>\n\n {drillCount === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Plus className=\"h-10 w-10\" />}\n title=\"No drills yet\"\n description=\"Add drills with sets, reps and durations, organized by week.\"\n />\n ) : (\n <div className=\"space-y-6\">\n {drillsByWeek.map(({ week, drills }) => (\n <div key={week}>\n <h3 className=\"mb-3 text-sm font-semibold uppercase tracking-wide text-text-secondary\">\n Week {week}\n </h3>\n <EmphasisPanel>\n <div className=\"divide-y divide-border-seam\">\n {drills.map((d) => (\n <div key={d.id} className=\"flex items-start gap-4 py-3\">\n <div className=\"min-w-0 flex-1\">\n <p className=\"text-sm font-medium text-text-primary\">{d.name}</p>\n {d.description && (\n <p className=\"mt-0.5 text-xs text-text-secondary\">{d.description}</p>\n )}\n <div className=\"mt-1.5 flex flex-wrap gap-x-3 gap-y-1 text-xs text-text-secondary\">\n {d.sets !== null && <span>{d.sets} sets</span>}\n {d.reps !== null && <span>{d.reps} reps</span>}\n {d.durationSec !== null && <span>{d.durationSec}s work</span>}\n {d.restSec !== null && <span>{d.restSec}s rest</span>}\n </div>\n </div>\n <button\n type=\"button\"\n onClick={() => void removeDrill.mutate(d.id)}\n disabled={removeDrill.isPending}\n aria-label={`Remove ${d.name}`}\n className=\"rounded-lg p-2 text-text-secondary transition-colors hover:bg-status-error-bg-subtle hover:text-status-error-text disabled:opacity-60\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n ))}\n </div>\n </EmphasisPanel>\n </div>\n ))}\n </div>\n )}\n </section>\n\n {/* Add drill dialog */}\n <ResponsiveDialog open={drillOpen} onOpenChange={setDrillOpen}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Add drill</ResponsiveDialogTitle>\n </ResponsiveDialogHeader>\n <form onSubmit={handleAddDrill} className=\"space-y-4 px-1\">\n <FormField label=\"Name\" htmlFor=\"drillName\" required>\n <input\n id=\"drillName\"\n className={fieldInputClass}\n value={drillForm.name}\n onChange={(e) => setDrillForm((p) => ({ ...p, name: e.target.value }))}\n placeholder=\"e.g. Box jumps\"\n />\n </FormField>\n <FormField label=\"Description\" htmlFor=\"drillDesc\">\n <textarea\n id=\"drillDesc\"\n className={fieldInputClass}\n rows={2}\n value={drillForm.description}\n onChange={(e) => setDrillForm((p) => ({ ...p, description: e.target.value }))}\n placeholder=\"How to perform the drill.\"\n />\n </FormField>\n <div className=\"grid grid-cols-3 gap-4\">\n <FormField label=\"Week\" htmlFor=\"drillWeek\">\n <input\n id=\"drillWeek\"\n inputMode=\"numeric\"\n className={fieldInputClass}\n value={drillForm.weekNumber}\n onChange={(e) => setDrillForm((p) => ({ ...p, weekNumber: e.target.value }))}\n />\n </FormField>\n <FormField label=\"Sets\" htmlFor=\"drillSets\">\n <input\n id=\"drillSets\"\n inputMode=\"numeric\"\n className={fieldInputClass}\n value={drillForm.sets}\n onChange={(e) => setDrillForm((p) => ({ ...p, sets: e.target.value }))}\n />\n </FormField>\n <FormField label=\"Reps\" htmlFor=\"drillReps\">\n <input\n id=\"drillReps\"\n inputMode=\"numeric\"\n className={fieldInputClass}\n value={drillForm.reps}\n onChange={(e) => setDrillForm((p) => ({ ...p, reps: e.target.value }))}\n />\n </FormField>\n </div>\n <div className=\"grid grid-cols-2 gap-4\">\n <FormField label=\"Work (sec)\" htmlFor=\"drillDuration\">\n <input\n id=\"drillDuration\"\n inputMode=\"numeric\"\n className={fieldInputClass}\n value={drillForm.durationSec}\n onChange={(e) => setDrillForm((p) => ({ ...p, durationSec: e.target.value }))}\n />\n </FormField>\n <FormField label=\"Rest (sec)\" htmlFor=\"drillRest\">\n <input\n id=\"drillRest\"\n inputMode=\"numeric\"\n className={fieldInputClass}\n value={drillForm.restSec}\n onChange={(e) => setDrillForm((p) => ({ ...p, restSec: e.target.value }))}\n />\n </FormField>\n </div>\n {drillError && <p className=\"text-sm text-status-error-text\">{drillError}</p>}\n <ResponsiveDialogFooter>\n <button\n type=\"button\"\n onClick={() => setDrillOpen(false)}\n className=\"rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={addDrill.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-60\"\n >\n {addDrill.isPending && <Loader2 className=\"h-4 w-4 animate-spin\" />}\n Add drill\n </button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,207 @@
1
+ import { useAthleteBridge as e } from "../../context/AthleteBridgeContext.js";
2
+ import { PageLayout as t } from "../../components/PageLayout.js";
3
+ import { PROGRAM_LEVEL_LABELS as n, SPORT_LABELS as r } from "../../components/domain.js";
4
+ import { ErrorState as i, LoadingState as a } from "../../components/LoadingState.js";
5
+ import { useCreateProgram as o, useProgram as s, useUpdateProgram as c } from "../../hooks/useAthleteBridgeApi.js";
6
+ import { FormField as l, fieldInputClass as u } from "../../components/form.js";
7
+ import { useEffect as d, useState as f } from "react";
8
+ import { ArrowLeft as p, Loader2 as m, Save as h } from "lucide-react";
9
+ import { useParams as g } from "react-router-dom";
10
+ import { jsx as _, jsxs as v } from "react/jsx-runtime";
11
+ //#region src/atheletebridge/pages/programs/ProgramFormPage.tsx
12
+ var y = Object.keys(r), b = Object.keys(n), x = {
13
+ name: "",
14
+ sport: "OTHER",
15
+ level: "BEGINNER",
16
+ summary: "",
17
+ weeks: "4",
18
+ isTemplate: !1,
19
+ tagsText: ""
20
+ };
21
+ function S() {
22
+ let { id: S } = g(), C = !!S, { navigateTo: w } = e(), T = s(S), E = o(), D = c(), [O, k] = f(x), [A, j] = f(null);
23
+ d(() => {
24
+ let e = T.data;
25
+ C && e && k({
26
+ name: e.name,
27
+ sport: e.sport,
28
+ level: e.level,
29
+ summary: e.summary ?? "",
30
+ weeks: String(e.weeks),
31
+ isTemplate: e.isTemplate,
32
+ tagsText: e.tags.join(", ")
33
+ });
34
+ }, [C, T.data]);
35
+ let M = (e, t) => k((n) => ({
36
+ ...n,
37
+ [e]: t
38
+ })), N = async (e) => {
39
+ if (e.preventDefault(), j(null), !O.name.trim()) {
40
+ j("Program name is required.");
41
+ return;
42
+ }
43
+ let t = Number.parseInt(O.weeks, 10);
44
+ if (Number.isNaN(t) || t < 1) {
45
+ j("Weeks must be a positive number.");
46
+ return;
47
+ }
48
+ let n = O.tagsText.split(",").map((e) => e.trim()).filter(Boolean);
49
+ try {
50
+ C && S ? (await D.mutateAsync({
51
+ id: S,
52
+ input: {
53
+ name: O.name.trim(),
54
+ sport: O.sport,
55
+ level: O.level,
56
+ summary: O.summary.trim() || void 0,
57
+ weeks: t,
58
+ isTemplate: O.isTemplate,
59
+ tags: n
60
+ }
61
+ }), w(`/programs/${S}`)) : w(`/programs/${(await E.mutateAsync({
62
+ name: O.name.trim(),
63
+ sport: O.sport,
64
+ level: O.level,
65
+ summary: O.summary.trim() || void 0,
66
+ weeks: t,
67
+ isTemplate: O.isTemplate,
68
+ tags: n
69
+ })).id}`);
70
+ } catch (e) {
71
+ j(e instanceof Error ? e.message : "Failed to save program.");
72
+ }
73
+ }, P = E.isPending || D.isPending;
74
+ return C && T.isLoading ? /* @__PURE__ */ _(t, {
75
+ title: "Edit program",
76
+ children: /* @__PURE__ */ _(a, { label: "Loading program…" })
77
+ }) : C && (T.isError || !T.isLoading && !T.data) ? /* @__PURE__ */ _(t, {
78
+ title: "Edit program",
79
+ children: /* @__PURE__ */ _(i, {
80
+ title: "Program not found",
81
+ message: "This program does not exist or you do not have access to it.",
82
+ onRetry: () => w("/programs")
83
+ })
84
+ }) : /* @__PURE__ */ _(t, {
85
+ title: C ? "Edit program" : "New program",
86
+ description: C ? "Update the training plan details." : "Create a new training plan in draft.",
87
+ actions: /* @__PURE__ */ v("button", {
88
+ type: "button",
89
+ onClick: () => w(C && S ? `/programs/${S}` : "/programs"),
90
+ className: "inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken",
91
+ children: [/* @__PURE__ */ _(p, { className: "h-4 w-4" }), " Cancel"]
92
+ }),
93
+ children: /* @__PURE__ */ v("form", {
94
+ onSubmit: N,
95
+ className: "max-w-2xl space-y-5",
96
+ children: [
97
+ /* @__PURE__ */ _(l, {
98
+ label: "Program name",
99
+ htmlFor: "name",
100
+ required: !0,
101
+ children: /* @__PURE__ */ _("input", {
102
+ id: "name",
103
+ className: u,
104
+ value: O.name,
105
+ onChange: (e) => M("name", e.target.value),
106
+ placeholder: "e.g. Pre-season strength block"
107
+ })
108
+ }),
109
+ /* @__PURE__ */ v("div", {
110
+ className: "grid grid-cols-1 gap-5 sm:grid-cols-3",
111
+ children: [
112
+ /* @__PURE__ */ _(l, {
113
+ label: "Sport",
114
+ htmlFor: "sport",
115
+ children: /* @__PURE__ */ _("select", {
116
+ id: "sport",
117
+ className: u,
118
+ value: O.sport,
119
+ onChange: (e) => M("sport", e.target.value),
120
+ children: y.map((e) => /* @__PURE__ */ _("option", {
121
+ value: e,
122
+ children: r[e]
123
+ }, e))
124
+ })
125
+ }),
126
+ /* @__PURE__ */ _(l, {
127
+ label: "Level",
128
+ htmlFor: "level",
129
+ children: /* @__PURE__ */ _("select", {
130
+ id: "level",
131
+ className: u,
132
+ value: O.level,
133
+ onChange: (e) => M("level", e.target.value),
134
+ children: b.map((e) => /* @__PURE__ */ _("option", {
135
+ value: e,
136
+ children: n[e]
137
+ }, e))
138
+ })
139
+ }),
140
+ /* @__PURE__ */ _(l, {
141
+ label: "Weeks",
142
+ htmlFor: "weeks",
143
+ children: /* @__PURE__ */ _("input", {
144
+ id: "weeks",
145
+ inputMode: "numeric",
146
+ className: u,
147
+ value: O.weeks,
148
+ onChange: (e) => M("weeks", e.target.value),
149
+ placeholder: "4"
150
+ })
151
+ })
152
+ ]
153
+ }),
154
+ /* @__PURE__ */ _(l, {
155
+ label: "Summary",
156
+ htmlFor: "summary",
157
+ children: /* @__PURE__ */ _("textarea", {
158
+ id: "summary",
159
+ className: u,
160
+ rows: 3,
161
+ value: O.summary,
162
+ onChange: (e) => M("summary", e.target.value),
163
+ placeholder: "What this program is for and who it's aimed at."
164
+ })
165
+ }),
166
+ /* @__PURE__ */ _(l, {
167
+ label: "Tags",
168
+ htmlFor: "tags",
169
+ hint: "Comma-separated, e.g. strength, pre-season.",
170
+ children: /* @__PURE__ */ _("input", {
171
+ id: "tags",
172
+ className: u,
173
+ value: O.tagsText,
174
+ onChange: (e) => M("tagsText", e.target.value),
175
+ placeholder: "strength, conditioning"
176
+ })
177
+ }),
178
+ /* @__PURE__ */ v("label", {
179
+ className: "flex items-center gap-2.5 text-sm text-text-primary",
180
+ children: [/* @__PURE__ */ _("input", {
181
+ type: "checkbox",
182
+ checked: O.isTemplate,
183
+ onChange: (e) => M("isTemplate", e.target.checked),
184
+ className: "h-4 w-4 rounded border-border-seam text-action-primary-bg focus:ring-[var(--color-focus-ring)]"
185
+ }), "Mark as reusable template"]
186
+ }),
187
+ A && /* @__PURE__ */ _("p", {
188
+ className: "text-sm text-status-error-text",
189
+ children: A
190
+ }),
191
+ /* @__PURE__ */ _("div", {
192
+ className: "pt-2",
193
+ children: /* @__PURE__ */ v("button", {
194
+ type: "submit",
195
+ disabled: P,
196
+ className: "inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-60",
197
+ children: [P ? /* @__PURE__ */ _(m, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ _(h, { className: "h-4 w-4" }), C ? "Save changes" : "Create program"]
198
+ })
199
+ })
200
+ ]
201
+ })
202
+ });
203
+ }
204
+ //#endregion
205
+ export { S as ProgramFormPage };
206
+
207
+ //# sourceMappingURL=ProgramFormPage.js.map