@burdenoff/microfe-atheletebridge 2026.827.1 → 2026.829.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/atheletebridge/components/LoadingState.d.ts.map +1 -1
- package/dist/atheletebridge/components/LoadingState.js +39 -32
- package/dist/atheletebridge/components/LoadingState.js.map +1 -1
- package/dist/atheletebridge/components/TeamCard.d.ts.map +1 -1
- package/dist/atheletebridge/components/TeamCard.js +45 -49
- package/dist/atheletebridge/components/TeamCard.js.map +1 -1
- package/dist/atheletebridge/pages/DashboardPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/DashboardPage.js +56 -24
- package/dist/atheletebridge/pages/DashboardPage.js.map +1 -1
- package/dist/atheletebridge/pages/analytics/CoachAnalyticsPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/analytics/CoachAnalyticsPage.js +58 -56
- package/dist/atheletebridge/pages/analytics/CoachAnalyticsPage.js.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js +110 -106
- package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixturesListPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixturesListPage.js +80 -71
- package/dist/atheletebridge/pages/fixtures/FixturesListPage.js.map +1 -1
- package/dist/atheletebridge/pages/onboarding/OnboardingPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/onboarding/OnboardingPage.js +244 -210
- package/dist/atheletebridge/pages/onboarding/OnboardingPage.js.map +1 -1
- package/dist/atheletebridge/pages/performance/PerformancePage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/performance/PerformancePage.js +197 -148
- package/dist/atheletebridge/pages/performance/PerformancePage.js.map +1 -1
- package/dist/atheletebridge/pages/programs/ProgramDetailPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/programs/ProgramDetailPage.js +169 -143
- package/dist/atheletebridge/pages/programs/ProgramDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/programs/ProgramsListPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/programs/ProgramsListPage.js +77 -72
- package/dist/atheletebridge/pages/programs/ProgramsListPage.js.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionDetailPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionDetailPage.js +104 -86
- package/dist/atheletebridge/pages/sessions/SessionDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionsListPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionsListPage.js +54 -49
- package/dist/atheletebridge/pages/sessions/SessionsListPage.js.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamDetailPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamDetailPage.js +286 -267
- package/dist/atheletebridge/pages/teams/TeamDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamFormPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamFormPage.js +185 -180
- package/dist/atheletebridge/pages/teams/TeamFormPage.js.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamsListPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamsListPage.js +70 -74
- package/dist/atheletebridge/pages/teams/TeamsListPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProgramDetailPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/programs/ProgramDetailPage.tsx"],"sourcesContent":["/**\n * Program Detail\n * @module atheletebridge/pages/programs\n *\n * Reads a single program with its drills (athProgram), supports the publish\n * lifecycle (athTransitionProgram), editing/archiving, and adding/removing\n * drills (athAddDrill / athRemoveDrill).\n */\n\nimport { useMemo, useState } from 'react';\nimport { useParams } from 'react-router';\nimport {\n ArrowLeft,\n Edit,\n Archive,\n Plus,\n Trash2,\n Loader2,\n Send,\n Undo2,\n Copy,\n FileVideo,\n} from 'lucide-react';\nimport {\n GlassCard,\n Badge,\n Accordion,\n AccordionItem,\n AccordionTrigger,\n AccordionContent,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { FormField, fieldInputClass } from '../../components/form';\nimport { DrillVideoLink } from '../../components/DrillVideoLink';\nimport { SPORT_ICONS, SPORT_LABELS, PROGRAM_LEVEL_LABELS } from '../../components/domain';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport {\n useProgram,\n useTransitionProgram,\n useArchiveProgram,\n useAddDrill,\n useRemoveDrill,\n useUploadFile,\n} from '../../hooks/useAthleteBridgeApi';\nimport type { AthDrill, AthProgramStatus } from '../../types';\n\ninterface DrillForm {\n name: string;\n description: string;\n weekNumber: string;\n sets: string;\n reps: string;\n durationSec: string;\n restSec: string;\n}\n\nconst EMPTY_DRILL: DrillForm = {\n name: '',\n description: '',\n weekNumber: '1',\n sets: '',\n reps: '',\n durationSec: '',\n restSec: '',\n};\n\n/** Small tokenized metric pill for a drill's sets/reps/work/rest. */\nconst MetricChip = ({ children }: { children: React.ReactNode }) => (\n <span className=\"inline-flex items-center rounded-md border border-border-seam bg-bg-sunken px-2 py-0.5 text-xs font-medium tabular-nums text-text-secondary\">\n {children}\n </span>\n);\n\nconst numOrUndefined = (v: string): number | undefined => {\n const t = v.trim();\n if (!t) return undefined;\n const n = Number.parseInt(t, 10);\n return Number.isNaN(n) ? undefined : n;\n};\n\nexport function ProgramDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useAthleteBridge();\n\n const program = useProgram(id);\n const transition = useTransitionProgram();\n const archive = useArchiveProgram();\n const addDrill = useAddDrill();\n const removeDrill = useRemoveDrill(id);\n const uploadFile = useUploadFile();\n\n const [drillOpen, setDrillOpen] = useState(false);\n const [drillForm, setDrillForm] = useState<DrillForm>(EMPTY_DRILL);\n const [videoFile, setVideoFile] = useState<File | null>(null);\n const [uploadingVideo, setUploadingVideo] = useState(false);\n const [drillError, setDrillError] = useState<string | null>(null);\n\n const drillsByWeek = useMemo(() => {\n const drills = program.data?.drills?.items ?? [];\n const groups = new Map<number, AthDrill[]>();\n for (const d of drills) {\n const list = groups.get(d.weekNumber) ?? [];\n list.push(d);\n groups.set(d.weekNumber, list);\n }\n return Array.from(groups.entries())\n .sort(([a], [b]) => a - b)\n .map(([week, list]) => ({\n week,\n drills: [...list].sort((a, b) => a.orderIndex - b.orderIndex),\n }));\n }, [program.data]);\n\n if (program.isLoading) {\n return (\n <PageLayout title=\"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?.items.length ?? 0;\n const SportIcon = SPORT_ICONS[p.sport];\n const openWeeks = drillsByWeek.map(({ week }) => `week-${week}`);\n\n const handleTransition = (status: AthProgramStatus) => {\n if (!id) return;\n void transition.mutate({ id, status });\n };\n\n const handleArchive = async () => {\n if (!id) return;\n await archive.mutateAsync(id);\n navigateTo('/programs');\n };\n\n const handleAddDrill = async (e: React.FormEvent) => {\n e.preventDefault();\n setDrillError(null);\n if (!id) return;\n if (!drillForm.name.trim()) {\n setDrillError('Drill name is required.');\n return;\n }\n const week = numOrUndefined(drillForm.weekNumber) ?? 1;\n\n let videoFileId: string | undefined;\n if (videoFile) {\n setUploadingVideo(true);\n try {\n videoFileId = await uploadFile(videoFile);\n } catch (err) {\n setDrillError(err instanceof Error ? err.message : 'Failed to upload the demo video.');\n setUploadingVideo(false);\n return;\n }\n setUploadingVideo(false);\n }\n\n try {\n await addDrill.mutateAsync({\n programId: id,\n name: drillForm.name.trim(),\n description: drillForm.description.trim() || undefined,\n weekNumber: week,\n orderIndex: program.data?.drills?.items.filter((d) => d.weekNumber === week).length ?? 0,\n sets: numOrUndefined(drillForm.sets),\n reps: numOrUndefined(drillForm.reps),\n durationSec: numOrUndefined(drillForm.durationSec),\n restSec: numOrUndefined(drillForm.restSec),\n videoFileId,\n });\n setDrillForm(EMPTY_DRILL);\n setVideoFile(null);\n setDrillOpen(false);\n } catch (err) {\n setDrillError(err instanceof Error ? err.message : 'Failed to add drill.');\n }\n };\n\n return (\n <PageLayout\n title={p.name}\n gradientTitle\n description={`${SPORT_LABELS[p.sport]} · ${PROGRAM_LEVEL_LABELS[p.level]} · ${p.weeks} ${\n p.weeks === 1 ? 'week' : 'weeks'\n }`}\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/programs')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> 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 items-start gap-4\">\n <div className=\"flex h-14 w-14 shrink-0 items-center justify-center rounded-2xl bg-[var(--color-accent-soft)] text-text-primary\">\n <SportIcon className=\"h-7 w-7\" aria-hidden=\"true\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <Badge variant=\"secondary\">{SPORT_LABELS[p.sport]}</Badge>\n <Badge variant=\"secondary\">{PROGRAM_LEVEL_LABELS[p.level]}</Badge>\n <StatusBadge status={p.status} />\n {p.isTemplate && (\n <span className=\"inline-flex items-center gap-1 text-xs text-text-secondary\">\n <Copy className=\"h-3.5 w-3.5\" /> Template\n </span>\n )}\n </div>\n <p className=\"mt-2 text-sm text-text-secondary\">\n {p.weeks} {p.weeks === 1 ? 'week' : 'weeks'} · {drillCount}{' '}\n {drillCount === 1 ? 'drill' : 'drills'}\n </p>\n </div>\n </div>\n {p.summary && <p className=\"mt-4 text-sm text-text-secondary\">{p.summary}</p>}\n {p.tags.length > 0 && (\n <div className=\"mt-4 flex flex-wrap gap-1.5\">\n {p.tags.map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\"\n >\n {tag}\n </span>\n ))}\n </div>\n )}\n\n {/* Lifecycle transitions */}\n <div className=\"mt-5 flex flex-wrap items-center gap-2 border-t border-border-seam pt-5\">\n <span className=\"text-sm font-medium text-text-primary\">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 <Accordion type=\"multiple\" defaultValue={openWeeks} className=\"flex flex-col gap-3\">\n {drillsByWeek.map(({ week, drills }) => (\n <GlassCard key={week} treatment=\"insight\" className=\"overflow-hidden px-4 py-0\">\n <AccordionItem value={`week-${week}`} className=\"border-b-0\">\n <AccordionTrigger className=\"py-4\">\n <span className=\"flex items-center gap-2.5\">\n <span className=\"text-sm font-semibold text-text-primary\">Week {week}</span>\n <Badge variant=\"secondary\">\n {drills.length} {drills.length === 1 ? 'drill' : 'drills'}\n </Badge>\n </span>\n </AccordionTrigger>\n <AccordionContent className=\"pb-2\">\n <div className=\"divide-y divide-border-seam\">\n {drills.map((d) => (\n <div key={d.id} className=\"flex items-start gap-4 py-3\">\n <div className=\"min-w-0 flex-1\">\n <p className=\"text-sm font-medium text-text-primary\">{d.name}</p>\n {d.description && (\n <p className=\"mt-0.5 text-xs text-text-secondary\">{d.description}</p>\n )}\n <div className=\"mt-2 flex flex-wrap gap-1.5\">\n {d.sets !== null && <MetricChip>{d.sets} sets</MetricChip>}\n {d.reps !== null && <MetricChip>{d.reps} reps</MetricChip>}\n {d.durationSec !== null && (\n <MetricChip>{d.durationSec}s work</MetricChip>\n )}\n {d.restSec !== null && <MetricChip>{d.restSec}s rest</MetricChip>}\n </div>\n {d.video?.id && (\n <div>\n <DrillVideoLink fileId={d.video.id} />\n </div>\n )}\n </div>\n <button\n type=\"button\"\n onClick={() => void removeDrill.mutate(d.id)}\n disabled={removeDrill.isPending}\n aria-label={`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 </AccordionContent>\n </AccordionItem>\n </GlassCard>\n ))}\n </Accordion>\n )}\n </section>\n\n {/* Add drill dialog */}\n <ResponsiveDialog open={drillOpen} onOpenChange={setDrillOpen}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>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-1 gap-4 sm:grid-cols-3\">\n <FormField label=\"Week\" htmlFor=\"drillWeek\">\n <input\n id=\"drillWeek\"\n inputMode=\"numeric\"\n className={fieldInputClass}\n value={drillForm.weekNumber}\n onChange={(e) => setDrillForm((p) => ({ ...p, weekNumber: e.target.value }))}\n />\n </FormField>\n <FormField label=\"Sets\" htmlFor=\"drillSets\">\n <input\n id=\"drillSets\"\n inputMode=\"numeric\"\n className={fieldInputClass}\n value={drillForm.sets}\n onChange={(e) => setDrillForm((p) => ({ ...p, sets: e.target.value }))}\n />\n </FormField>\n <FormField label=\"Reps\" htmlFor=\"drillReps\">\n <input\n id=\"drillReps\"\n inputMode=\"numeric\"\n className={fieldInputClass}\n value={drillForm.reps}\n onChange={(e) => setDrillForm((p) => ({ ...p, reps: e.target.value }))}\n />\n </FormField>\n </div>\n <div className=\"grid grid-cols-2 gap-4\">\n <FormField label=\"Work (sec)\" htmlFor=\"drillDuration\">\n <input\n id=\"drillDuration\"\n inputMode=\"numeric\"\n className={fieldInputClass}\n value={drillForm.durationSec}\n onChange={(e) => setDrillForm((p) => ({ ...p, durationSec: e.target.value }))}\n />\n </FormField>\n <FormField label=\"Rest (sec)\" htmlFor=\"drillRest\">\n <input\n id=\"drillRest\"\n inputMode=\"numeric\"\n className={fieldInputClass}\n value={drillForm.restSec}\n onChange={(e) => setDrillForm((p) => ({ ...p, restSec: e.target.value }))}\n />\n </FormField>\n </div>\n <FormField\n label=\"Demo video\"\n htmlFor=\"drillVideo\"\n hint=\"Optional — attach a short demo clip; it's stored in workspace files.\"\n >\n <input\n id=\"drillVideo\"\n type=\"file\"\n accept=\"video/*\"\n className={fieldInputClass}\n onChange={(e) => setVideoFile(e.target.files?.[0] ?? null)}\n />\n {videoFile && (\n <p className=\"mt-1 inline-flex items-center gap-1 text-xs text-text-secondary\">\n <FileVideo className=\"h-3.5 w-3.5\" /> {videoFile.name}\n </p>\n )}\n </FormField>\n {drillError && <p className=\"text-sm text-status-error-text\">{drillError}</p>}\n <ResponsiveDialogFooter>\n <button\n type=\"button\"\n onClick={() => {\n setDrillOpen(false);\n setVideoFile(null);\n }}\n className=\"rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={addDrill.isPending || uploadingVideo}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-60\"\n >\n {(addDrill.isPending || uploadingVideo) && (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n )}\n {uploadingVideo ? 'Uploading video…' : 'Add drill'}\n </button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ProgramDetailPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/programs/ProgramDetailPage.tsx"],"sourcesContent":["/**\n * Program Detail\n * @module atheletebridge/pages/programs\n *\n * Reads a single program with its drills (athProgram), supports the publish\n * lifecycle (athTransitionProgram), editing/archiving, and adding/removing\n * drills (athAddDrill / athRemoveDrill).\n */\n\nimport { useMemo, useState } from 'react';\nimport { useParams } from 'react-router';\nimport {\n ArrowLeft,\n Edit,\n Archive,\n Plus,\n Trash2,\n Loader2,\n Send,\n Undo2,\n Copy,\n FileVideo,\n} from 'lucide-react';\nimport {\n GlassCard,\n Badge,\n Accordion,\n AccordionItem,\n AccordionTrigger,\n AccordionContent,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { FormField, fieldInputClass } from '../../components/form';\nimport { DrillVideoLink } from '../../components/DrillVideoLink';\nimport { SPORT_ICONS, SPORT_LABELS, PROGRAM_LEVEL_LABELS } from '../../components/domain';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport {\n useProgram,\n useTransitionProgram,\n useArchiveProgram,\n useAddDrill,\n useRemoveDrill,\n useUploadFile,\n} from '../../hooks/useAthleteBridgeApi';\nimport { useAbTranslate } from '../../i18n';\nimport type { AthDrill, AthProgramStatus } from '../../types';\n\ninterface DrillForm {\n name: string;\n description: string;\n weekNumber: string;\n sets: string;\n reps: string;\n durationSec: string;\n restSec: string;\n}\n\nconst EMPTY_DRILL: DrillForm = {\n name: '',\n description: '',\n weekNumber: '1',\n sets: '',\n reps: '',\n durationSec: '',\n restSec: '',\n};\n\n/** Small tokenized metric pill for a drill's sets/reps/work/rest. */\nconst MetricChip = ({ children }: { children: React.ReactNode }) => (\n <span className=\"inline-flex items-center rounded-md border border-border-seam bg-bg-sunken px-2 py-0.5 text-xs font-medium tabular-nums text-text-secondary\">\n {children}\n </span>\n);\n\nconst numOrUndefined = (v: string): number | undefined => {\n const t = v.trim();\n if (!t) return undefined;\n const n = Number.parseInt(t, 10);\n return Number.isNaN(n) ? undefined : n;\n};\n\nexport function ProgramDetailPage() {\n const tr = useAbTranslate();\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useAthleteBridge();\n\n const program = useProgram(id);\n const transition = useTransitionProgram();\n const archive = useArchiveProgram();\n const addDrill = useAddDrill();\n const removeDrill = useRemoveDrill(id);\n const uploadFile = useUploadFile();\n\n const [drillOpen, setDrillOpen] = useState(false);\n const [drillForm, setDrillForm] = useState<DrillForm>(EMPTY_DRILL);\n const [videoFile, setVideoFile] = useState<File | null>(null);\n const [uploadingVideo, setUploadingVideo] = useState(false);\n const [drillError, setDrillError] = useState<string | null>(null);\n\n const drillsByWeek = useMemo(() => {\n const drills = program.data?.drills?.items ?? [];\n const groups = new Map<number, AthDrill[]>();\n for (const d of drills) {\n const list = groups.get(d.weekNumber) ?? [];\n list.push(d);\n groups.set(d.weekNumber, list);\n }\n return Array.from(groups.entries())\n .sort(([a], [b]) => a - b)\n .map(([week, list]) => ({\n week,\n drills: [...list].sort((a, b) => a.orderIndex - b.orderIndex),\n }));\n }, [program.data]);\n\n if (program.isLoading) {\n return (\n <PageLayout title={tr('atheletebridge.pages.programDetail.title', 'Program')}>\n <LoadingState\n label={tr('atheletebridge.pages.programDetail.loading', 'Loading program…')}\n />\n </PageLayout>\n );\n }\n\n if (program.isError || !program.data) {\n return (\n <PageLayout title={tr('atheletebridge.pages.programDetail.title', 'Program')}>\n <ErrorState\n title={tr('atheletebridge.pages.programDetail.notFoundTitle', 'Program not found')}\n message={tr(\n 'atheletebridge.pages.programDetail.notFoundMessage',\n 'This program does not exist or you do not have access to it.'\n )}\n onRetry={() => navigateTo('/programs')}\n />\n </PageLayout>\n );\n }\n\n const p = program.data;\n const drillCount = p.drills?.items.length ?? 0;\n const SportIcon = SPORT_ICONS[p.sport];\n const openWeeks = drillsByWeek.map(({ week }) => `week-${week}`);\n\n const handleTransition = (status: AthProgramStatus) => {\n if (!id) return;\n void transition.mutate({ id, status });\n };\n\n const handleArchive = async () => {\n if (!id) return;\n await archive.mutateAsync(id);\n navigateTo('/programs');\n };\n\n const handleAddDrill = async (e: React.FormEvent) => {\n e.preventDefault();\n setDrillError(null);\n if (!id) return;\n if (!drillForm.name.trim()) {\n setDrillError('Drill name is required.');\n return;\n }\n const week = numOrUndefined(drillForm.weekNumber) ?? 1;\n\n let videoFileId: string | undefined;\n if (videoFile) {\n setUploadingVideo(true);\n try {\n videoFileId = await uploadFile(videoFile);\n } catch (err) {\n setDrillError(err instanceof Error ? err.message : 'Failed to upload the demo video.');\n setUploadingVideo(false);\n return;\n }\n setUploadingVideo(false);\n }\n\n try {\n await addDrill.mutateAsync({\n programId: id,\n name: drillForm.name.trim(),\n description: drillForm.description.trim() || undefined,\n weekNumber: week,\n orderIndex: program.data?.drills?.items.filter((d) => d.weekNumber === week).length ?? 0,\n sets: numOrUndefined(drillForm.sets),\n reps: numOrUndefined(drillForm.reps),\n durationSec: numOrUndefined(drillForm.durationSec),\n restSec: numOrUndefined(drillForm.restSec),\n videoFileId,\n });\n setDrillForm(EMPTY_DRILL);\n setVideoFile(null);\n setDrillOpen(false);\n } catch (err) {\n setDrillError(err instanceof Error ? err.message : 'Failed to add drill.');\n }\n };\n\n return (\n <PageLayout\n title={p.name}\n gradientTitle\n description={`${SPORT_LABELS[p.sport]} · ${PROGRAM_LEVEL_LABELS[p.level]} · ${p.weeks} ${\n p.weeks === 1 ? 'week' : 'weeks'\n }`}\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/programs')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" />{' '}\n {tr('atheletebridge.pages.programDetail.back', 'Back')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/programs/${p.id}/edit`)}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <Edit className=\"h-4 w-4\" /> {tr('atheletebridge.pages.programDetail.edit', 'Edit')}\n </button>\n {p.status !== 'ARCHIVED' && (\n <button\n type=\"button\"\n onClick={handleArchive}\n disabled={archive.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:opacity-60\"\n >\n {archive.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Archive className=\"h-4 w-4\" />\n )}{' '}\n {tr('atheletebridge.pages.programDetail.archive', 'Archive')}\n </button>\n )}\n </div>\n }\n >\n {/* Summary + lifecycle */}\n <GlassCard treatment=\"insight\" className=\"mb-8 p-6\">\n <div className=\"flex items-start gap-4\">\n <div className=\"flex h-14 w-14 shrink-0 items-center justify-center rounded-2xl bg-[var(--color-accent-soft)] text-text-primary\">\n <SportIcon className=\"h-7 w-7\" aria-hidden=\"true\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <Badge variant=\"secondary\">{SPORT_LABELS[p.sport]}</Badge>\n <Badge variant=\"secondary\">{PROGRAM_LEVEL_LABELS[p.level]}</Badge>\n <StatusBadge status={p.status} />\n {p.isTemplate && (\n <span className=\"inline-flex items-center gap-1 text-xs text-text-secondary\">\n <Copy className=\"h-3.5 w-3.5\" /> Template\n </span>\n )}\n </div>\n <p className=\"mt-2 text-sm text-text-secondary\">\n {p.weeks} {p.weeks === 1 ? 'week' : 'weeks'} · {drillCount}{' '}\n {drillCount === 1 ? 'drill' : 'drills'}\n </p>\n </div>\n </div>\n {p.summary && <p className=\"mt-4 text-sm text-text-secondary\">{p.summary}</p>}\n {p.tags.length > 0 && (\n <div className=\"mt-4 flex flex-wrap gap-1.5\">\n {p.tags.map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\"\n >\n {tag}\n </span>\n ))}\n </div>\n )}\n\n {/* Lifecycle transitions */}\n <div className=\"mt-5 flex flex-wrap items-center gap-2 border-t border-border-seam pt-5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('atheletebridge.pages.programDetail.lifecycle', 'Publish lifecycle:')}\n </span>\n {p.status === 'DRAFT' && (\n <button\n type=\"button\"\n onClick={() => handleTransition('PUBLISHED')}\n disabled={transition.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-60\"\n >\n <Send className=\"h-4 w-4\" />{' '}\n {tr('atheletebridge.pages.programDetail.publish', 'Publish')}\n </button>\n )}\n {p.status === 'PUBLISHED' && (\n <>\n <button\n type=\"button\"\n onClick={() => handleTransition('DRAFT')}\n disabled={transition.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:opacity-60\"\n >\n <Undo2 className=\"h-4 w-4\" />{' '}\n {tr('atheletebridge.pages.programDetail.pullBackToDraft', 'Pull back to draft')}\n </button>\n <button\n type=\"button\"\n onClick={() => handleTransition('ARCHIVED')}\n disabled={transition.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:opacity-60\"\n >\n <Archive className=\"h-4 w-4\" />{' '}\n {tr('atheletebridge.pages.programDetail.archiveAction', 'Archive')}\n </button>\n </>\n )}\n {p.status === 'ARCHIVED' && (\n <span className=\"text-sm text-text-secondary\">\n {tr('atheletebridge.pages.programDetail.archived', 'This program is archived.')}\n </span>\n )}\n {transition.isPending && <Loader2 className=\"h-4 w-4 animate-spin text-text-secondary\" />}\n </div>\n </GlassCard>\n\n {/* Drills */}\n <section>\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('atheletebridge.pages.programDetail.drillsTitle', 'Drills')} ({drillCount})\n </h2>\n <button\n type=\"button\"\n onClick={() => {\n setDrillError(null);\n setDrillOpen(true);\n }}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" />{' '}\n {tr('atheletebridge.pages.programDetail.addDrill', 'Add drill')}\n </button>\n </div>\n\n {drillCount === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Plus className=\"h-10 w-10\" />}\n title={tr('atheletebridge.pages.programDetail.noDrillsTitle', 'No drills yet')}\n description={tr(\n 'atheletebridge.pages.programDetail.noDrillsHint',\n 'Add drills with sets, reps and durations, organized by week.'\n )}\n />\n ) : (\n <Accordion type=\"multiple\" defaultValue={openWeeks} className=\"flex flex-col gap-3\">\n {drillsByWeek.map(({ week, drills }) => (\n <GlassCard key={week} treatment=\"insight\" className=\"overflow-hidden px-4 py-0\">\n <AccordionItem value={`week-${week}`} className=\"border-b-0\">\n <AccordionTrigger className=\"py-4\">\n <span className=\"flex items-center gap-2.5\">\n <span className=\"text-sm font-semibold text-text-primary\">Week {week}</span>\n <Badge variant=\"secondary\">\n {drills.length} {drills.length === 1 ? 'drill' : 'drills'}\n </Badge>\n </span>\n </AccordionTrigger>\n <AccordionContent className=\"pb-2\">\n <div className=\"divide-y divide-border-seam\">\n {drills.map((d) => (\n <div key={d.id} className=\"flex items-start gap-4 py-3\">\n <div className=\"min-w-0 flex-1\">\n <p className=\"text-sm font-medium text-text-primary\">{d.name}</p>\n {d.description && (\n <p className=\"mt-0.5 text-xs text-text-secondary\">{d.description}</p>\n )}\n <div className=\"mt-2 flex flex-wrap gap-1.5\">\n {d.sets !== null && <MetricChip>{d.sets} sets</MetricChip>}\n {d.reps !== null && <MetricChip>{d.reps} reps</MetricChip>}\n {d.durationSec !== null && (\n <MetricChip>{d.durationSec}s work</MetricChip>\n )}\n {d.restSec !== null && <MetricChip>{d.restSec}s rest</MetricChip>}\n </div>\n {d.video?.id && (\n <div>\n <DrillVideoLink fileId={d.video.id} />\n </div>\n )}\n </div>\n <button\n type=\"button\"\n onClick={() => void removeDrill.mutate(d.id)}\n disabled={removeDrill.isPending}\n aria-label={`${tr('common.delete', 'Delete')} ${d.name}`}\n className=\"rounded-lg p-2 text-text-secondary transition-colors hover:bg-status-error-bg-subtle hover:text-status-error-text disabled:opacity-60\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n ))}\n </div>\n </AccordionContent>\n </AccordionItem>\n </GlassCard>\n ))}\n </Accordion>\n )}\n </section>\n\n {/* Add drill dialog */}\n <ResponsiveDialog open={drillOpen} onOpenChange={setDrillOpen}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n {tr('atheletebridge.pages.programDetail.addDrill', 'Add drill')}\n </ResponsiveDialogTitle>\n </ResponsiveDialogHeader>\n <form onSubmit={handleAddDrill} className=\"space-y-4 px-1\">\n <FormField label=\"Name\" htmlFor=\"drillName\" required>\n <input\n id=\"drillName\"\n className={fieldInputClass}\n value={drillForm.name}\n onChange={(e) => setDrillForm((p) => ({ ...p, name: e.target.value }))}\n placeholder=\"e.g. Box jumps\"\n />\n </FormField>\n <FormField label=\"Description\" htmlFor=\"drillDesc\">\n <textarea\n id=\"drillDesc\"\n className={fieldInputClass}\n rows={2}\n value={drillForm.description}\n onChange={(e) => setDrillForm((p) => ({ ...p, description: e.target.value }))}\n placeholder=\"How to perform the drill.\"\n />\n </FormField>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <FormField label=\"Week\" htmlFor=\"drillWeek\">\n <input\n id=\"drillWeek\"\n inputMode=\"numeric\"\n className={fieldInputClass}\n value={drillForm.weekNumber}\n onChange={(e) => setDrillForm((p) => ({ ...p, weekNumber: e.target.value }))}\n />\n </FormField>\n <FormField label=\"Sets\" htmlFor=\"drillSets\">\n <input\n id=\"drillSets\"\n inputMode=\"numeric\"\n className={fieldInputClass}\n value={drillForm.sets}\n onChange={(e) => setDrillForm((p) => ({ ...p, sets: e.target.value }))}\n />\n </FormField>\n <FormField label=\"Reps\" htmlFor=\"drillReps\">\n <input\n id=\"drillReps\"\n inputMode=\"numeric\"\n className={fieldInputClass}\n value={drillForm.reps}\n onChange={(e) => setDrillForm((p) => ({ ...p, reps: e.target.value }))}\n />\n </FormField>\n </div>\n <div className=\"grid grid-cols-2 gap-4\">\n <FormField label=\"Work (sec)\" htmlFor=\"drillDuration\">\n <input\n id=\"drillDuration\"\n inputMode=\"numeric\"\n className={fieldInputClass}\n value={drillForm.durationSec}\n onChange={(e) => setDrillForm((p) => ({ ...p, durationSec: e.target.value }))}\n />\n </FormField>\n <FormField label=\"Rest (sec)\" htmlFor=\"drillRest\">\n <input\n id=\"drillRest\"\n inputMode=\"numeric\"\n className={fieldInputClass}\n value={drillForm.restSec}\n onChange={(e) => setDrillForm((p) => ({ ...p, restSec: e.target.value }))}\n />\n </FormField>\n </div>\n <FormField\n label=\"Demo video\"\n htmlFor=\"drillVideo\"\n hint=\"Optional — attach a short demo clip; it's stored in workspace files.\"\n >\n <input\n id=\"drillVideo\"\n type=\"file\"\n accept=\"video/*\"\n className={fieldInputClass}\n onChange={(e) => setVideoFile(e.target.files?.[0] ?? null)}\n />\n {videoFile && (\n <p className=\"mt-1 inline-flex items-center gap-1 text-xs text-text-secondary\">\n <FileVideo className=\"h-3.5 w-3.5\" /> {videoFile.name}\n </p>\n )}\n </FormField>\n {drillError && <p className=\"text-sm text-status-error-text\">{drillError}</p>}\n <ResponsiveDialogFooter>\n <button\n type=\"button\"\n onClick={() => {\n setDrillOpen(false);\n setVideoFile(null);\n }}\n className=\"rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n disabled={addDrill.isPending || uploadingVideo}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-60\"\n >\n {(addDrill.isPending || uploadingVideo) && (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n )}\n {uploadingVideo\n ? tr('atheletebridge.pages.programDetail.uploadingVideo', 'Uploading video…')\n : tr('atheletebridge.pages.programDetail.addDrill', 'Add drill')}\n </button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProgramsListPage.d.ts","sourceRoot":"","sources":["../../../../src/atheletebridge/pages/programs/ProgramsListPage.tsx"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"ProgramsListPage.d.ts","sourceRoot":"","sources":["../../../../src/atheletebridge/pages/programs/ProgramsListPage.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAkCH,wBAAgB,gBAAgB,gCAwJ/B"}
|
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
1
|
+
import { useAbTranslate as e } from "../../i18n.js";
|
|
2
|
+
import { useAthleteBridge as t } from "../../context/AthleteBridgeContext.js";
|
|
3
|
+
import { PageLayout as n } from "../../components/PageLayout.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 o, LoadingState as s } from "../../components/LoadingState.js";
|
|
7
|
+
import { usePrograms as c } from "../../hooks/useAthleteBridgeApi.js";
|
|
8
|
+
import { CursorPager as l } from "../../components/CursorPager.js";
|
|
9
|
+
import { FilterSelect as u } from "../../components/FilterSelect.js";
|
|
10
|
+
import { ProgramCard as d } from "../../components/ProgramCard.js";
|
|
11
|
+
import { useState as f } from "react";
|
|
12
|
+
import { Dumbbell as p, Plus as m, Search as h } from "lucide-react";
|
|
13
|
+
import { Fragment as g, jsx as _, jsxs as v } from "react/jsx-runtime";
|
|
13
14
|
//#region src/atheletebridge/pages/programs/ProgramsListPage.tsx
|
|
14
|
-
var
|
|
15
|
+
var y = 12, b = Object.keys(i).map((e) => ({
|
|
15
16
|
value: e,
|
|
16
|
-
label:
|
|
17
|
-
})),
|
|
17
|
+
label: i[e]
|
|
18
|
+
})), x = Object.keys(r).map((e) => ({
|
|
18
19
|
value: e,
|
|
19
|
-
label:
|
|
20
|
-
})),
|
|
20
|
+
label: r[e]
|
|
21
|
+
})), S = [
|
|
21
22
|
{
|
|
22
23
|
value: "DRAFT",
|
|
23
24
|
label: "Draft"
|
|
@@ -31,102 +32,106 @@ var v = 12, y = Object.keys(r).map((e) => ({
|
|
|
31
32
|
label: "Archived"
|
|
32
33
|
}
|
|
33
34
|
];
|
|
34
|
-
function
|
|
35
|
-
let { navigateTo:
|
|
36
|
-
sport:
|
|
37
|
-
level:
|
|
38
|
-
status:
|
|
35
|
+
function C() {
|
|
36
|
+
let r = e(), { navigateTo: i } = t(), [C, w] = f(""), [T, E] = f(""), [D, O] = f(""), [k, A] = f(""), [j, M] = f(0), N = c({
|
|
37
|
+
sport: C || void 0,
|
|
38
|
+
level: T || void 0,
|
|
39
|
+
status: D || void 0,
|
|
39
40
|
pagination: {
|
|
40
|
-
limit:
|
|
41
|
-
offset:
|
|
41
|
+
limit: y,
|
|
42
|
+
offset: j * y
|
|
42
43
|
}
|
|
43
|
-
}),
|
|
44
|
-
return /* @__PURE__ */
|
|
45
|
-
title: "Programs",
|
|
46
|
-
description: "Coach-built training plans with a publish lifecycle and reusable templates.",
|
|
44
|
+
}), P = N.data?.items ?? [], F = k.trim().toLowerCase(), I = F ? P.filter((e) => e.name.toLowerCase().includes(F) || (e.summary ?? "").toLowerCase().includes(F) || e.tags.some((e) => e.toLowerCase().includes(F))) : P, L = N.data?.pageInfo.hasMore ?? !1, R = () => M(0);
|
|
45
|
+
return /* @__PURE__ */ v(n, {
|
|
46
|
+
title: r("atheletebridge.pages.programs.title", "Programs"),
|
|
47
|
+
description: r("atheletebridge.pages.programs.description", "Coach-built training plans with a publish lifecycle and reusable templates."),
|
|
47
48
|
hero: !0,
|
|
48
49
|
gradientTitle: !0,
|
|
49
|
-
actions: /* @__PURE__ */
|
|
50
|
+
actions: /* @__PURE__ */ v("button", {
|
|
50
51
|
type: "button",
|
|
51
52
|
"data-tour": "programs-create",
|
|
52
|
-
onClick: () =>
|
|
53
|
+
onClick: () => i("/programs/new"),
|
|
53
54
|
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",
|
|
54
|
-
children: [
|
|
55
|
+
children: [
|
|
56
|
+
/* @__PURE__ */ _(m, { className: "h-4 w-4" }),
|
|
57
|
+
" ",
|
|
58
|
+
r("atheletebridge.pages.programs.new", "New program")
|
|
59
|
+
]
|
|
55
60
|
}),
|
|
56
|
-
children: [/* @__PURE__ */
|
|
61
|
+
children: [/* @__PURE__ */ v("div", {
|
|
57
62
|
"data-tour": "programs-filters",
|
|
58
63
|
className: "mb-6 flex flex-wrap items-center gap-3",
|
|
59
64
|
children: [
|
|
60
|
-
/* @__PURE__ */
|
|
65
|
+
/* @__PURE__ */ v("div", {
|
|
61
66
|
className: "relative min-w-[14rem] flex-1",
|
|
62
|
-
children: [/* @__PURE__ */
|
|
67
|
+
children: [/* @__PURE__ */ _(h, {
|
|
63
68
|
className: "pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-muted",
|
|
64
69
|
"aria-hidden": "true"
|
|
65
|
-
}), /* @__PURE__ */
|
|
70
|
+
}), /* @__PURE__ */ _("input", {
|
|
66
71
|
type: "search",
|
|
67
|
-
value:
|
|
68
|
-
onChange: (e) =>
|
|
69
|
-
placeholder: "Search programs…",
|
|
70
|
-
"aria-label": "Search programs",
|
|
72
|
+
value: k,
|
|
73
|
+
onChange: (e) => A(e.target.value),
|
|
74
|
+
placeholder: r("atheletebridge.pages.programs.searchPlaceholder", "Search programs…"),
|
|
75
|
+
"aria-label": r("atheletebridge.pages.programs.searchAria", "Search programs"),
|
|
71
76
|
className: "w-full rounded-lg border border-border-seam bg-bg-surface py-2 pl-9 pr-3 text-sm text-text-primary placeholder:text-text-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]"
|
|
72
77
|
})]
|
|
73
78
|
}),
|
|
74
|
-
/* @__PURE__ */
|
|
75
|
-
ariaLabel: "Filter by sport",
|
|
76
|
-
allLabel: "All sports",
|
|
77
|
-
value: r,
|
|
78
|
-
onChange: (e) => {
|
|
79
|
-
S(e), I();
|
|
80
|
-
},
|
|
81
|
-
options: y
|
|
82
|
-
}),
|
|
83
|
-
/* @__PURE__ */ g(l, {
|
|
84
|
-
ariaLabel: "Filter by level",
|
|
85
|
-
allLabel: "All levels",
|
|
79
|
+
/* @__PURE__ */ _(u, {
|
|
80
|
+
ariaLabel: r("atheletebridge.pages.programs.filterSport", "Filter by sport"),
|
|
81
|
+
allLabel: r("atheletebridge.pages.programs.allSports", "All sports"),
|
|
86
82
|
value: C,
|
|
87
83
|
onChange: (e) => {
|
|
88
|
-
w(e),
|
|
84
|
+
w(e), R();
|
|
89
85
|
},
|
|
90
86
|
options: b
|
|
91
87
|
}),
|
|
92
|
-
/* @__PURE__ */
|
|
93
|
-
ariaLabel: "Filter by
|
|
94
|
-
allLabel: "All
|
|
88
|
+
/* @__PURE__ */ _(u, {
|
|
89
|
+
ariaLabel: r("atheletebridge.pages.programs.filterLevel", "Filter by level"),
|
|
90
|
+
allLabel: r("atheletebridge.pages.programs.allLevels", "All levels"),
|
|
95
91
|
value: T,
|
|
96
92
|
onChange: (e) => {
|
|
97
|
-
E(e),
|
|
93
|
+
E(e), R();
|
|
98
94
|
},
|
|
99
95
|
options: x
|
|
96
|
+
}),
|
|
97
|
+
/* @__PURE__ */ _(u, {
|
|
98
|
+
ariaLabel: r("atheletebridge.pages.programs.filterStatus", "Filter by status"),
|
|
99
|
+
allLabel: r("atheletebridge.pages.programs.allStatuses", "All statuses"),
|
|
100
|
+
value: D,
|
|
101
|
+
onChange: (e) => {
|
|
102
|
+
O(e), R();
|
|
103
|
+
},
|
|
104
|
+
options: S
|
|
100
105
|
})
|
|
101
106
|
]
|
|
102
|
-
}),
|
|
107
|
+
}), N.isLoading ? /* @__PURE__ */ _(s, { label: r("atheletebridge.pages.programs.loading", "Loading programs…") }) : N.isError ? /* @__PURE__ */ _(o, { onRetry: () => void N.refetch() }) : I.length === 0 ? /* @__PURE__ */ _(a, {
|
|
103
108
|
illustration: "empty-data",
|
|
104
|
-
icon: /* @__PURE__ */
|
|
105
|
-
title: "No programs found",
|
|
106
|
-
description:
|
|
107
|
-
action: /* @__PURE__ */
|
|
109
|
+
icon: /* @__PURE__ */ _(p, { className: "h-10 w-10" }),
|
|
110
|
+
title: r("atheletebridge.pages.programs.noPrograms", "No programs found"),
|
|
111
|
+
description: C || T || D || F ? r("atheletebridge.pages.programs.noProgramsFilterHint", "Try clearing your search or filters to see all programs.") : r("atheletebridge.pages.programs.noProgramsHint", "Create your first training program to build drills and publish to athletes."),
|
|
112
|
+
action: /* @__PURE__ */ _("button", {
|
|
108
113
|
type: "button",
|
|
109
|
-
onClick: () =>
|
|
114
|
+
onClick: () => i("/programs/new"),
|
|
110
115
|
className: "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",
|
|
111
|
-
children: "Create program"
|
|
116
|
+
children: r("atheletebridge.pages.programs.createProgram", "Create program")
|
|
112
117
|
})
|
|
113
|
-
}) : /* @__PURE__ */
|
|
118
|
+
}) : /* @__PURE__ */ v(g, { children: [/* @__PURE__ */ _("div", {
|
|
114
119
|
className: "grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3",
|
|
115
|
-
children:
|
|
120
|
+
children: I.map((e) => /* @__PURE__ */ _(d, {
|
|
116
121
|
program: e,
|
|
117
122
|
drillCount: e.drills?.items.length,
|
|
118
|
-
onClick: () =>
|
|
123
|
+
onClick: () => i(`/programs/${e.id}`)
|
|
119
124
|
}, e.id))
|
|
120
|
-
}), /* @__PURE__ */
|
|
125
|
+
}), /* @__PURE__ */ _(l, {
|
|
121
126
|
className: "mt-6",
|
|
122
|
-
page:
|
|
123
|
-
hasMore:
|
|
124
|
-
onPrev: () =>
|
|
125
|
-
onNext: () =>
|
|
127
|
+
page: j,
|
|
128
|
+
hasMore: L,
|
|
129
|
+
onPrev: () => M((e) => Math.max(0, e - 1)),
|
|
130
|
+
onNext: () => M((e) => e + 1)
|
|
126
131
|
})] })]
|
|
127
132
|
});
|
|
128
133
|
}
|
|
129
134
|
//#endregion
|
|
130
|
-
export {
|
|
135
|
+
export { C as ProgramsListPage };
|
|
131
136
|
|
|
132
137
|
//# sourceMappingURL=ProgramsListPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProgramsListPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/programs/ProgramsListPage.tsx"],"sourcesContent":["/**\n * Programs List\n * @module atheletebridge/pages/programs\n */\n\nimport { useState } from 'react';\nimport { Dumbbell, Plus, Search } from 'lucide-react';\nimport { PageLayout } from '../../components/PageLayout';\nimport { ProgramCard } from '../../components/ProgramCard';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { FilterSelect } from '../../components/FilterSelect';\nimport { CursorPager } from '../../components/CursorPager';\nimport { SPORT_LABELS, PROGRAM_LEVEL_LABELS } from '../../components/domain';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport { usePrograms } from '../../hooks/useAthleteBridgeApi';\nimport type { AthProgramLevel, AthProgramStatus, AthSport } from '../../types';\n\nconst PAGE_SIZE = 12;\n\nconst SPORT_OPTS = (Object.keys(SPORT_LABELS) as AthSport[]).map((value) => ({\n value,\n label: SPORT_LABELS[value],\n}));\n\nconst LEVEL_OPTS = (Object.keys(PROGRAM_LEVEL_LABELS) as AthProgramLevel[]).map((value) => ({\n value,\n label: PROGRAM_LEVEL_LABELS[value],\n}));\n\nconst STATUS_OPTS: ReadonlyArray<{ value: AthProgramStatus; label: string }> = [\n { value: 'DRAFT', label: 'Draft' },\n { value: 'PUBLISHED', label: 'Published' },\n { value: 'ARCHIVED', label: 'Archived' },\n];\n\nexport function ProgramsListPage() {\n const { navigateTo } = useAthleteBridge();\n const [sport, setSport] = useState<AthSport | ''>('');\n const [level, setLevel] = useState<AthProgramLevel | ''>('');\n const [status, setStatus] = useState<AthProgramStatus | ''>('');\n const [search, setSearch] = useState('');\n const [page, setPage] = useState(0);\n\n const programs = usePrograms({\n sport: sport || undefined,\n level: level || undefined,\n status: status || undefined,\n pagination: { limit: PAGE_SIZE, offset: page * PAGE_SIZE },\n });\n\n const allItems = programs.data?.items ?? [];\n // Name search filters the loaded page client-side (the query exposes no name\n // arg); it never fabricates rows, only narrows what's already fetched.\n const query = search.trim().toLowerCase();\n const items = query\n ? allItems.filter(\n (p) =>\n p.name.toLowerCase().includes(query) ||\n (p.summary ?? '').toLowerCase().includes(query) ||\n p.tags.some((t) => t.toLowerCase().includes(query))\n )\n : allItems;\n const hasMore = programs.data?.pageInfo.hasMore ?? false;\n\n const resetPage = () => setPage(0);\n\n return (\n <PageLayout\n title=\"Programs\"\n description=\"Coach-built training plans with a publish lifecycle and reusable templates.\"\n hero\n gradientTitle\n actions={\n <button\n type=\"button\"\n data-tour=\"programs-create\"\n onClick={() => navigateTo('/programs/new')}\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\"\n >\n <Plus className=\"h-4 w-4\" /> New program\n </button>\n }\n >\n <div data-tour=\"programs-filters\" className=\"mb-6 flex flex-wrap items-center gap-3\">\n <div className=\"relative min-w-[14rem] flex-1\">\n <Search\n className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-muted\"\n aria-hidden=\"true\"\n />\n <input\n type=\"search\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n placeholder=\"Search programs…\"\n aria-label=\"Search programs\"\n className=\"w-full rounded-lg border border-border-seam bg-bg-surface py-2 pl-9 pr-3 text-sm text-text-primary placeholder:text-text-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n />\n </div>\n <FilterSelect\n ariaLabel=\"Filter by sport\"\n allLabel=\"All sports\"\n value={sport}\n onChange={(v) => {\n setSport(v);\n resetPage();\n }}\n options={SPORT_OPTS}\n />\n <FilterSelect\n ariaLabel=\"Filter by level\"\n allLabel=\"All levels\"\n value={level}\n onChange={(v) => {\n setLevel(v);\n resetPage();\n }}\n options={LEVEL_OPTS}\n />\n <FilterSelect\n ariaLabel=\"Filter by status\"\n allLabel=\"All statuses\"\n value={status}\n onChange={(v) => {\n setStatus(v);\n resetPage();\n }}\n options={STATUS_OPTS}\n />\n </div>\n\n {programs.isLoading ? (\n <LoadingState label=\"Loading programs…\" />\n ) : programs.isError ? (\n <ErrorState onRetry={() => void programs.refetch()} />\n ) : items.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Dumbbell className=\"h-10 w-10\" />}\n title=\"No programs found\"\n description={\n sport || level || status || query\n ? 'Try clearing your search or filters to see all programs.'\n : 'Create your first training program to build drills and publish to athletes.'\n }\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/programs/new')}\n className=\"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\"\n >\n Create program\n </button>\n }\n />\n ) : (\n <>\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {items.map((program) => (\n <ProgramCard\n key={program.id}\n program={program}\n drillCount={program.drills?.items.length}\n onClick={() => navigateTo(`/programs/${program.id}`)}\n />\n ))}\n </div>\n <CursorPager\n className=\"mt-6\"\n page={page}\n hasMore={hasMore}\n onPrev={() => setPage((p) => Math.max(0, p - 1))}\n onNext={() => setPage((p) => p + 1)}\n />\n </>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ProgramsListPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/programs/ProgramsListPage.tsx"],"sourcesContent":["/**\n * Programs List\n * @module atheletebridge/pages/programs\n */\n\nimport { useState } from 'react';\nimport { Dumbbell, Plus, Search } from 'lucide-react';\nimport { PageLayout } from '../../components/PageLayout';\nimport { ProgramCard } from '../../components/ProgramCard';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { FilterSelect } from '../../components/FilterSelect';\nimport { CursorPager } from '../../components/CursorPager';\nimport { SPORT_LABELS, PROGRAM_LEVEL_LABELS } from '../../components/domain';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport { usePrograms } from '../../hooks/useAthleteBridgeApi';\nimport { useAbTranslate } from '../../i18n';\nimport type { AthProgramLevel, AthProgramStatus, AthSport } from '../../types';\n\nconst PAGE_SIZE = 12;\n\nconst SPORT_OPTS = (Object.keys(SPORT_LABELS) as AthSport[]).map((value) => ({\n value,\n label: SPORT_LABELS[value],\n}));\n\nconst LEVEL_OPTS = (Object.keys(PROGRAM_LEVEL_LABELS) as AthProgramLevel[]).map((value) => ({\n value,\n label: PROGRAM_LEVEL_LABELS[value],\n}));\n\nconst STATUS_OPTS: ReadonlyArray<{ value: AthProgramStatus; label: string }> = [\n { value: 'DRAFT', label: 'Draft' },\n { value: 'PUBLISHED', label: 'Published' },\n { value: 'ARCHIVED', label: 'Archived' },\n];\n\nexport function ProgramsListPage() {\n const tr = useAbTranslate();\n const { navigateTo } = useAthleteBridge();\n const [sport, setSport] = useState<AthSport | ''>('');\n const [level, setLevel] = useState<AthProgramLevel | ''>('');\n const [status, setStatus] = useState<AthProgramStatus | ''>('');\n const [search, setSearch] = useState('');\n const [page, setPage] = useState(0);\n\n const programs = usePrograms({\n sport: sport || undefined,\n level: level || undefined,\n status: status || undefined,\n pagination: { limit: PAGE_SIZE, offset: page * PAGE_SIZE },\n });\n\n const allItems = programs.data?.items ?? [];\n // Name search filters the loaded page client-side (the query exposes no name\n // arg); it never fabricates rows, only narrows what's already fetched.\n const query = search.trim().toLowerCase();\n const items = query\n ? allItems.filter(\n (p) =>\n p.name.toLowerCase().includes(query) ||\n (p.summary ?? '').toLowerCase().includes(query) ||\n p.tags.some((t) => t.toLowerCase().includes(query))\n )\n : allItems;\n const hasMore = programs.data?.pageInfo.hasMore ?? false;\n\n const resetPage = () => setPage(0);\n\n return (\n <PageLayout\n title={tr('atheletebridge.pages.programs.title', 'Programs')}\n description={tr(\n 'atheletebridge.pages.programs.description',\n 'Coach-built training plans with a publish lifecycle and reusable templates.'\n )}\n hero\n gradientTitle\n actions={\n <button\n type=\"button\"\n data-tour=\"programs-create\"\n onClick={() => navigateTo('/programs/new')}\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\"\n >\n <Plus className=\"h-4 w-4\" /> {tr('atheletebridge.pages.programs.new', 'New program')}\n </button>\n }\n >\n <div data-tour=\"programs-filters\" className=\"mb-6 flex flex-wrap items-center gap-3\">\n <div className=\"relative min-w-[14rem] flex-1\">\n <Search\n className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-muted\"\n aria-hidden=\"true\"\n />\n <input\n type=\"search\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n placeholder={tr('atheletebridge.pages.programs.searchPlaceholder', 'Search programs…')}\n aria-label={tr('atheletebridge.pages.programs.searchAria', 'Search programs')}\n className=\"w-full rounded-lg border border-border-seam bg-bg-surface py-2 pl-9 pr-3 text-sm text-text-primary placeholder:text-text-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n />\n </div>\n <FilterSelect\n ariaLabel={tr('atheletebridge.pages.programs.filterSport', 'Filter by sport')}\n allLabel={tr('atheletebridge.pages.programs.allSports', 'All sports')}\n value={sport}\n onChange={(v) => {\n setSport(v);\n resetPage();\n }}\n options={SPORT_OPTS}\n />\n <FilterSelect\n ariaLabel={tr('atheletebridge.pages.programs.filterLevel', 'Filter by level')}\n allLabel={tr('atheletebridge.pages.programs.allLevels', 'All levels')}\n value={level}\n onChange={(v) => {\n setLevel(v);\n resetPage();\n }}\n options={LEVEL_OPTS}\n />\n <FilterSelect\n ariaLabel={tr('atheletebridge.pages.programs.filterStatus', 'Filter by status')}\n allLabel={tr('atheletebridge.pages.programs.allStatuses', 'All statuses')}\n value={status}\n onChange={(v) => {\n setStatus(v);\n resetPage();\n }}\n options={STATUS_OPTS}\n />\n </div>\n\n {programs.isLoading ? (\n <LoadingState label={tr('atheletebridge.pages.programs.loading', 'Loading programs…')} />\n ) : programs.isError ? (\n <ErrorState onRetry={() => void programs.refetch()} />\n ) : items.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Dumbbell className=\"h-10 w-10\" />}\n title={tr('atheletebridge.pages.programs.noPrograms', 'No programs found')}\n description={\n sport || level || status || query\n ? tr(\n 'atheletebridge.pages.programs.noProgramsFilterHint',\n 'Try clearing your search or filters to see all programs.'\n )\n : tr(\n 'atheletebridge.pages.programs.noProgramsHint',\n 'Create your first training program to build drills and publish to athletes.'\n )\n }\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/programs/new')}\n className=\"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\"\n >\n {tr('atheletebridge.pages.programs.createProgram', 'Create program')}\n </button>\n }\n />\n ) : (\n <>\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {items.map((program) => (\n <ProgramCard\n key={program.id}\n program={program}\n drillCount={program.drills?.items.length}\n onClick={() => navigateTo(`/programs/${program.id}`)}\n />\n ))}\n </div>\n <CursorPager\n className=\"mt-6\"\n page={page}\n hasMore={hasMore}\n onPrev={() => setPage((p) => Math.max(0, p - 1))}\n onNext={() => setPage((p) => p + 1)}\n />\n </>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SessionDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/atheletebridge/pages/sessions/SessionDetailPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;
|
|
1
|
+
{"version":3,"file":"SessionDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/atheletebridge/pages/sessions/SessionDetailPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAiFH,wBAAgB,iBAAiB,gCAoVhC"}
|