@burdenoff/microfe-atheletebridge 2026.628.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.
@@ -1 +1 @@
1
- {"version":3,"file":"FixtureCard.js","names":[],"sources":["../../../src/atheletebridge/components/FixtureCard.tsx"],"sourcesContent":["/**\n * Fixture Card\n * @module atheletebridge/components\n */\n\nimport type { FC } from 'react';\nimport { CalendarClock, MapPin } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { StatusBadge } from './StatusBadge';\nimport { FIXTURE_TYPE_LABELS } from './domain';\nimport { formatDate } from '../utils/formatters';\nimport { cn } from '../utils/cn';\nimport type { AthFixture } from '../types';\n\ninterface FixtureCardProps {\n fixture: AthFixture;\n onClick?: () => void;\n}\n\nexport const FixtureCard: FC<FixtureCardProps> = ({ fixture, onClick }) => {\n const homeName = fixture.homeTeam?.name ?? 'Home';\n const awayName = fixture.awayTeam?.name ?? (fixture.awayTeamId ? 'Away' : 'TBD');\n const hasScore = fixture.homeScore !== null && fixture.awayScore !== null;\n\n return (\n <GlassCard\n treatment=\"metric\"\n role=\"button\"\n tabIndex={0}\n onClick={onClick}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onClick?.();\n }\n }}\n className={cn(\n 'cursor-pointer p-5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]'\n )}\n >\n <div className=\"flex items-center justify-between gap-3\">\n <span className=\"inline-flex items-center rounded-full bg-bg-sunken px-2 py-0.5 text-xs font-medium text-text-secondary\">\n {FIXTURE_TYPE_LABELS[fixture.type]}\n </span>\n <StatusBadge status={fixture.status} />\n </div>\n\n <div className=\"mt-4 flex items-center justify-between gap-3\">\n <span className=\"flex-1 truncate text-right text-sm font-semibold text-text-primary\">\n {homeName}\n </span>\n <span className=\"shrink-0 rounded-lg bg-bg-sunken px-3 py-1 text-sm font-bold tabular-nums text-text-primary\">\n {hasScore ? `${fixture.homeScore} : ${fixture.awayScore}` : 'vs'}\n </span>\n <span className=\"flex-1 truncate text-left text-sm font-semibold text-text-primary\">\n {awayName}\n </span>\n </div>\n\n <div className=\"mt-4 flex flex-wrap items-center gap-x-4 gap-y-1.5 text-xs text-text-secondary\">\n <span className=\"flex items-center gap-1\">\n <CalendarClock className=\"h-3.5 w-3.5\" /> {formatDate(fixture.scheduledAt)}\n </span>\n {fixture.venue && (\n <span className=\"flex items-center gap-1 truncate\">\n <MapPin className=\"h-3.5 w-3.5 shrink-0\" /> {fixture.venue}\n </span>\n )}\n {fixture.competition && (\n <span className=\"truncate\">{fixture.competition}</span>\n )}\n </div>\n </GlassCard>\n );\n};\n"],"mappings":";;;;;;;;AAmBA,IAAa,KAAqC,EAAE,YAAS,iBAAc;CACzE,IAAM,IAAW,EAAQ,UAAU,QAAQ,QACrC,IAAW,EAAQ,UAAU,SAAS,EAAQ,aAAa,SAAS,QACpE,IAAW,EAAQ,cAAc,QAAQ,EAAQ,cAAc;AAErE,QACE,kBAAC,GAAD;EACE,WAAU;EACV,MAAK;EACL,UAAU;EACD;EACT,YAAY,MAAM;AAChB,IAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,SACjC,EAAE,gBAAgB,EAClB,KAAW;;EAGf,WAAW,EACT,kHACD;YAbH;GAeE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KAAM,WAAU;eACb,EAAoB,EAAQ;KACxB,CAAA,EACP,kBAAC,GAAD,EAAa,QAAQ,EAAQ,QAAU,CAAA,CACnC;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,QAAD;MAAM,WAAU;gBACb;MACI,CAAA;KACP,kBAAC,QAAD;MAAM,WAAU;gBACb,IAAW,GAAG,EAAQ,UAAU,KAAK,EAAQ,cAAc;MACvD,CAAA;KACP,kBAAC,QAAD;MAAM,WAAU;gBACb;MACI,CAAA;KACH;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,QAAD;MAAM,WAAU;gBAAhB;OACE,kBAAC,GAAD,EAAe,WAAU,eAAgB,CAAA;;OAAE,EAAW,EAAQ,YAAY;OACrE;;KACN,EAAQ,SACP,kBAAC,QAAD;MAAM,WAAU;gBAAhB;OACE,kBAAC,GAAD,EAAQ,WAAU,wBAAyB,CAAA;;OAAE,EAAQ;OAChD;;KAER,EAAQ,eACP,kBAAC,QAAD;MAAM,WAAU;gBAAY,EAAQ;MAAmB,CAAA;KAErD;;GACI"}
1
+ {"version":3,"file":"FixtureCard.js","names":[],"sources":["../../../src/atheletebridge/components/FixtureCard.tsx"],"sourcesContent":["/**\n * Fixture Card\n * @module atheletebridge/components\n */\n\nimport type { FC } from 'react';\nimport { CalendarClock, MapPin } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { StatusBadge } from './StatusBadge';\nimport { FIXTURE_TYPE_LABELS } from './domain';\nimport { formatDate } from '../utils/formatters';\nimport { cn } from '../utils/cn';\nimport type { AthFixture } from '../types';\n\ninterface FixtureCardProps {\n fixture: AthFixture;\n onClick?: () => void;\n}\n\nexport const FixtureCard: FC<FixtureCardProps> = ({ fixture, onClick }) => {\n const homeName = fixture.homeTeam?.name ?? 'Home';\n const awayName = fixture.awayTeam?.name ?? (fixture.awayTeamId ? 'Away' : 'TBD');\n const hasScore = fixture.homeScore !== null && fixture.awayScore !== null;\n\n return (\n <GlassCard\n treatment=\"metric\"\n role=\"button\"\n tabIndex={0}\n onClick={onClick}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onClick?.();\n }\n }}\n className={cn(\n 'cursor-pointer p-5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]'\n )}\n >\n <div className=\"flex items-center justify-between gap-3\">\n <span className=\"inline-flex items-center rounded-full bg-bg-sunken px-2 py-0.5 text-xs font-medium text-text-secondary\">\n {FIXTURE_TYPE_LABELS[fixture.type]}\n </span>\n <StatusBadge status={fixture.status} />\n </div>\n\n <div className=\"mt-4 flex items-center justify-between gap-3\">\n <span className=\"flex-1 truncate text-right text-sm font-semibold text-text-primary\">\n {homeName}\n </span>\n <span className=\"shrink-0 rounded-lg bg-bg-sunken px-3 py-1 text-sm font-bold tabular-nums text-text-primary\">\n {hasScore ? `${fixture.homeScore} : ${fixture.awayScore}` : 'vs'}\n </span>\n <span className=\"flex-1 truncate text-left text-sm font-semibold text-text-primary\">\n {awayName}\n </span>\n </div>\n\n <div className=\"mt-4 flex flex-wrap items-center gap-x-4 gap-y-1.5 text-xs text-text-secondary\">\n <span className=\"flex items-center gap-1\">\n <CalendarClock className=\"h-3.5 w-3.5\" /> {formatDate(fixture.scheduledAt)}\n </span>\n {fixture.venue && (\n <span className=\"flex items-center gap-1 truncate\">\n <MapPin className=\"h-3.5 w-3.5 shrink-0\" /> {fixture.venue}\n </span>\n )}\n {fixture.competition && <span className=\"truncate\">{fixture.competition}</span>}\n </div>\n </GlassCard>\n );\n};\n"],"mappings":";;;;;;;;AAmBA,IAAa,KAAqC,EAAE,YAAS,iBAAc;CACzE,IAAM,IAAW,EAAQ,UAAU,QAAQ,QACrC,IAAW,EAAQ,UAAU,SAAS,EAAQ,aAAa,SAAS,QACpE,IAAW,EAAQ,cAAc,QAAQ,EAAQ,cAAc;AAErE,QACE,kBAAC,GAAD;EACE,WAAU;EACV,MAAK;EACL,UAAU;EACD;EACT,YAAY,MAAM;AAChB,IAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,SACjC,EAAE,gBAAgB,EAClB,KAAW;;EAGf,WAAW,EACT,kHACD;YAbH;GAeE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KAAM,WAAU;eACb,EAAoB,EAAQ;KACxB,CAAA,EACP,kBAAC,GAAD,EAAa,QAAQ,EAAQ,QAAU,CAAA,CACnC;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,QAAD;MAAM,WAAU;gBACb;MACI,CAAA;KACP,kBAAC,QAAD;MAAM,WAAU;gBACb,IAAW,GAAG,EAAQ,UAAU,KAAK,EAAQ,cAAc;MACvD,CAAA;KACP,kBAAC,QAAD;MAAM,WAAU;gBACb;MACI,CAAA;KACH;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,QAAD;MAAM,WAAU;gBAAhB;OACE,kBAAC,GAAD,EAAe,WAAU,eAAgB,CAAA;;OAAE,EAAW,EAAQ,YAAY;OACrE;;KACN,EAAQ,SACP,kBAAC,QAAD;MAAM,WAAU;gBAAhB;OACE,kBAAC,GAAD,EAAQ,WAAU,wBAAyB,CAAA;;OAAE,EAAQ;OAChD;;KAER,EAAQ,eAAe,kBAAC,QAAD;MAAM,WAAU;gBAAY,EAAQ;MAAmB,CAAA;KAC3E;;GACI"}
@@ -1 +1 @@
1
- {"version":3,"file":"LoadingState.js","names":[],"sources":["../../../src/atheletebridge/components/LoadingState.tsx"],"sourcesContent":["/**\n * Loading / Error State Components\n * @module atheletebridge/components\n *\n * Small calm, centered states shared across list and detail pages.\n */\n\nimport type { FC } from 'react';\nimport { AlertCircle, Loader2 } from 'lucide-react';\nimport { cn } from '../utils/cn';\n\ninterface LoadingStateProps {\n label?: string;\n className?: string;\n}\n\nexport const LoadingState: FC<LoadingStateProps> = ({ label = 'Loading…', className }) => (\n <div\n className={cn('flex min-h-[40vh] flex-col items-center justify-center gap-3', className)}\n role=\"status\"\n aria-live=\"polite\"\n >\n <Loader2 className=\"h-8 w-8 animate-spin text-text-secondary\" />\n <p className=\"text-sm text-text-secondary\">{label}</p>\n </div>\n);\n\ninterface ErrorStateProps {\n title?: string;\n message?: string;\n onRetry?: () => void;\n className?: string;\n}\n\nexport const ErrorState: FC<ErrorStateProps> = ({\n title = 'Something went wrong',\n message = 'We could not load this data. Please try again.',\n onRetry,\n className,\n}) => (\n <div\n className={cn('flex min-h-[40vh] flex-col items-center justify-center gap-3 text-center', className)}\n >\n <AlertCircle className=\"h-10 w-10 text-status-error-text\" />\n <h3 className=\"text-lg font-semibold text-text-primary\">{title}</h3>\n <p className=\"max-w-sm text-sm text-text-secondary\">{message}</p>\n {onRetry && (\n <button\n type=\"button\"\n onClick={onRetry}\n className=\"mt-2 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 Try again\n </button>\n )}\n </div>\n);\n"],"mappings":";;;;AAgBA,IAAa,KAAuC,EAAE,WAAQ,YAAY,mBACxE,kBAAC,OAAD;CACE,WAAW,EAAG,gEAAgE,EAAU;CACxF,MAAK;CACL,aAAU;WAHZ,CAKE,kBAAC,GAAD,EAAS,WAAU,4CAA6C,CAAA,EAChE,kBAAC,KAAD;EAAG,WAAU;YAA+B;EAAU,CAAA,CAClD;IAUK,KAAmC,EAC9C,WAAQ,wBACR,aAAU,kDACV,YACA,mBAEA,kBAAC,OAAD;CACE,WAAW,EAAG,4EAA4E,EAAU;WADtG;EAGE,kBAAC,GAAD,EAAa,WAAU,oCAAqC,CAAA;EAC5D,kBAAC,MAAD;GAAI,WAAU;aAA2C;GAAW,CAAA;EACpE,kBAAC,KAAD;GAAG,WAAU;aAAwC;GAAY,CAAA;EAChE,KACC,kBAAC,UAAD;GACE,MAAK;GACL,SAAS;GACT,WAAU;aACX;GAEQ,CAAA;EAEP"}
1
+ {"version":3,"file":"LoadingState.js","names":[],"sources":["../../../src/atheletebridge/components/LoadingState.tsx"],"sourcesContent":["/**\n * Loading / Error State Components\n * @module atheletebridge/components\n *\n * Small calm, centered states shared across list and detail pages.\n */\n\nimport type { FC } from 'react';\nimport { AlertCircle, Loader2 } from 'lucide-react';\nimport { cn } from '../utils/cn';\n\ninterface LoadingStateProps {\n label?: string;\n className?: string;\n}\n\nexport const LoadingState: FC<LoadingStateProps> = ({ label = 'Loading…', className }) => (\n <div\n className={cn('flex min-h-[40vh] flex-col items-center justify-center gap-3', className)}\n role=\"status\"\n aria-live=\"polite\"\n >\n <Loader2 className=\"h-8 w-8 animate-spin text-text-secondary\" />\n <p className=\"text-sm text-text-secondary\">{label}</p>\n </div>\n);\n\ninterface ErrorStateProps {\n title?: string;\n message?: string;\n onRetry?: () => void;\n className?: string;\n}\n\nexport const ErrorState: FC<ErrorStateProps> = ({\n title = 'Something went wrong',\n message = 'We could not load this data. Please try again.',\n onRetry,\n className,\n}) => (\n <div\n className={cn(\n 'flex min-h-[40vh] flex-col items-center justify-center gap-3 text-center',\n className\n )}\n >\n <AlertCircle className=\"h-10 w-10 text-status-error-text\" />\n <h3 className=\"text-lg font-semibold text-text-primary\">{title}</h3>\n <p className=\"max-w-sm text-sm text-text-secondary\">{message}</p>\n {onRetry && (\n <button\n type=\"button\"\n onClick={onRetry}\n className=\"mt-2 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 Try again\n </button>\n )}\n </div>\n);\n"],"mappings":";;;;AAgBA,IAAa,KAAuC,EAAE,WAAQ,YAAY,mBACxE,kBAAC,OAAD;CACE,WAAW,EAAG,gEAAgE,EAAU;CACxF,MAAK;CACL,aAAU;WAHZ,CAKE,kBAAC,GAAD,EAAS,WAAU,4CAA6C,CAAA,EAChE,kBAAC,KAAD;EAAG,WAAU;YAA+B;EAAU,CAAA,CAClD;IAUK,KAAmC,EAC9C,WAAQ,wBACR,aAAU,kDACV,YACA,mBAEA,kBAAC,OAAD;CACE,WAAW,EACT,4EACA,EACD;WAJH;EAME,kBAAC,GAAD,EAAa,WAAU,oCAAqC,CAAA;EAC5D,kBAAC,MAAD;GAAI,WAAU;aAA2C;GAAW,CAAA;EACpE,kBAAC,KAAD;GAAG,WAAU;aAAwC;GAAY,CAAA;EAChE,KACC,kBAAC,UAAD;GACE,MAAK;GACL,SAAS;GACT,WAAU;aACX;GAEQ,CAAA;EAEP"}
@@ -1 +1 @@
1
- {"version":3,"file":"AthleteBridgeContext.js","names":[],"sources":["../../../src/atheletebridge/context/AthleteBridgeContext.tsx"],"sourcesContent":["/**\n * AthleteBridge Context Provider\n * @module atheletebridge/context\n *\n * The provider exposes the host-supplied props (basePath, workspaceId,\n * tenantId, apiGatewayUrl, authToken, currentUser, entity-select callbacks) plus\n * path/navigation helpers. The Root mounts this with the exact props the shell\n * passes; the export name/signature is part of the host contract.\n */\n\nimport type { FC, PropsWithChildren } from 'react';\nimport { createContext, useContext, useMemo, useCallback } from 'react';\nimport { MemoryRouter, useNavigate as useRouterNavigate } from 'react-router-dom';\nimport type { AthleteBridgeRootProps } from '../types';\nimport { joinPath } from '../utils/navigation';\n\n// ============================================================================\n// Context\n// ============================================================================\n\ninterface AthleteBridgeContextValue\n extends Omit<AthleteBridgeRootProps, 'registerNavItems' | 'registerFooterItems' | 'navigate'> {\n isStandalone: boolean;\n /** Shell's navigate function (only available in integrated mode) */\n navigate?: (path: string) => void;\n /** Navigate to a path relative to basePath */\n navigateTo: (relativePath: string) => void;\n /** Get the full path for a relative path */\n getPath: (relativePath: string) => string;\n}\n\nconst AthleteBridgeContext = createContext<AthleteBridgeContextValue | null>(null);\n\n// ============================================================================\n// Hook\n// ============================================================================\n\nexport function useAthleteBridge(): AthleteBridgeContextValue {\n const context = useContext(AthleteBridgeContext);\n if (!context) {\n throw new Error('useAthleteBridge must be used within a AthleteBridgeProvider');\n }\n return context;\n}\n\n// ============================================================================\n// Provider Component\n// ============================================================================\n\ntype ProviderProps = Omit<AthleteBridgeRootProps, 'registerNavItems' | 'registerFooterItems'> & {\n isStandalone: boolean;\n};\n\n/** Provider for standalone mode (has access to MemoryRouter) */\nconst StandaloneProviderInner: FC<PropsWithChildren<ProviderProps>> = ({\n children,\n basePath,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n onTeamSelect,\n onFixtureSelect,\n}) => {\n const routerNavigate = useRouterNavigate();\n const effectiveBasePath = basePath || '/';\n\n const getPath = useCallback(\n (relativePath: string) => joinPath(effectiveBasePath, relativePath),\n [effectiveBasePath]\n );\n\n const navigateTo = useCallback(\n (relativePath: string) => {\n const fullPath = joinPath(effectiveBasePath, relativePath);\n routerNavigate(fullPath);\n },\n [effectiveBasePath, routerNavigate]\n );\n\n const contextValue = useMemo<AthleteBridgeContextValue>(\n () => ({\n basePath: effectiveBasePath,\n navigate: undefined,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n onTeamSelect,\n onFixtureSelect,\n isStandalone: true,\n navigateTo,\n getPath,\n }),\n [\n effectiveBasePath,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n onTeamSelect,\n onFixtureSelect,\n navigateTo,\n getPath,\n ]\n );\n\n return (\n <AthleteBridgeContext.Provider value={contextValue}>{children}</AthleteBridgeContext.Provider>\n );\n};\n\n/** Provider for integrated mode (shell provides navigate) */\nconst IntegratedProviderInner: FC<\n PropsWithChildren<ProviderProps & { navigate: (path: string) => void }>\n> = ({\n children,\n basePath,\n navigate,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n onTeamSelect,\n onFixtureSelect,\n}) => {\n const effectiveBasePath = basePath || '/';\n\n const getPath = useCallback(\n (relativePath: string) => joinPath(effectiveBasePath, relativePath),\n [effectiveBasePath]\n );\n\n const navigateTo = useCallback(\n (relativePath: string) => {\n const fullPath = joinPath(effectiveBasePath, relativePath);\n navigate(fullPath);\n },\n [effectiveBasePath, navigate]\n );\n\n const contextValue = useMemo<AthleteBridgeContextValue>(\n () => ({\n basePath: effectiveBasePath,\n navigate,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n onTeamSelect,\n onFixtureSelect,\n isStandalone: false,\n navigateTo,\n getPath,\n }),\n [\n effectiveBasePath,\n navigate,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n onTeamSelect,\n onFixtureSelect,\n navigateTo,\n getPath,\n ]\n );\n\n return (\n <AthleteBridgeContext.Provider value={contextValue}>{children}</AthleteBridgeContext.Provider>\n );\n};\n\nexport const AthleteBridgeProvider: FC<PropsWithChildren<AthleteBridgeRootProps>> = (props) => {\n const { children, navigate } = props;\n // Standalone mode: no navigate function provided (MFE manages its own router)\n // Integrated mode: navigate function provided by shell (shell provides router)\n const isStandalone = !navigate;\n\n // Standalone mode: wrap in MemoryRouter\n if (isStandalone) {\n return (\n <MemoryRouter>\n <StandaloneProviderInner {...props} isStandalone={true}>\n {children}\n </StandaloneProviderInner>\n </MemoryRouter>\n );\n }\n\n // Integrated mode: shell provides router\n return (\n <IntegratedProviderInner {...props} navigate={navigate} isStandalone={false}>\n {children}\n </IntegratedProviderInner>\n );\n};\n"],"mappings":";;;;;AA+BA,IAAM,IAAuB,EAAgD,KAAK;AAMlF,SAAgB,IAA8C;CAC5D,IAAM,IAAU,EAAW,EAAqB;AAChD,KAAI,CAAC,EACH,OAAU,MAAM,+DAA+D;AAEjF,QAAO;;AAYT,IAAM,KAAiE,EACrE,aACA,aACA,gBACA,aACA,gBACA,kBACA,cACA,iBACA,yBACI;CACJ,IAAM,IAAiB,GAAmB,EACpC,IAAoB,KAAY,KAEhC,IAAU,GACb,MAAyB,EAAS,GAAmB,EAAa,EACnE,CAAC,EAAkB,CACpB,EAEK,IAAa,GAChB,MAAyB;AAExB,IADiB,EAAS,GAAmB,EAAa,CAClC;IAE1B,CAAC,GAAmB,EAAe,CACpC,EAEK,IAAe,SACZ;EACL,UAAU;EACV,UAAU,KAAA;EACV;EACA;EACA;EACA;EACA;EACA;EACA;EACA,cAAc;EACd;EACA;EACD,GACD;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CACF;AAED,QACE,kBAAC,EAAqB,UAAtB;EAA+B,OAAO;EAAe;EAAyC,CAAA;GAK5F,KAED,EACH,aACA,aACA,aACA,gBACA,aACA,gBACA,kBACA,cACA,iBACA,yBACI;CACJ,IAAM,IAAoB,KAAY,KAEhC,IAAU,GACb,MAAyB,EAAS,GAAmB,EAAa,EACnE,CAAC,EAAkB,CACpB,EAEK,IAAa,GAChB,MAAyB;AAExB,IADiB,EAAS,GAAmB,EAAa,CACxC;IAEpB,CAAC,GAAmB,EAAS,CAC9B,EAEK,IAAe,SACZ;EACL,UAAU;EACV;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,cAAc;EACd;EACA;EACD,GACD;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CACF;AAED,QACE,kBAAC,EAAqB,UAAtB;EAA+B,OAAO;EAAe;EAAyC,CAAA;GAIrF,KAAwE,MAAU;CAC7F,IAAM,EAAE,aAAU,gBAAa;AAiB/B,QAdsB,IAepB,kBAAC,GAAD;EAAyB,GAAI;EAAiB;EAAU,cAAc;EACnE;EACuB,CAAA,GAZxB,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;EAAyB,GAAI;EAAO,cAAc;EAC/C;EACuB,CAAA,EACb,CAAA"}
1
+ {"version":3,"file":"AthleteBridgeContext.js","names":[],"sources":["../../../src/atheletebridge/context/AthleteBridgeContext.tsx"],"sourcesContent":["/**\n * AthleteBridge Context Provider\n * @module atheletebridge/context\n *\n * The provider exposes the host-supplied props (basePath, workspaceId,\n * tenantId, apiGatewayUrl, authToken, currentUser, entity-select callbacks) plus\n * path/navigation helpers. The Root mounts this with the exact props the shell\n * passes; the export name/signature is part of the host contract.\n */\n\nimport type { FC, PropsWithChildren } from 'react';\nimport { createContext, useContext, useMemo, useCallback } from 'react';\nimport { MemoryRouter, useNavigate as useRouterNavigate } from 'react-router-dom';\nimport type { AthleteBridgeRootProps } from '../types';\nimport { joinPath } from '../utils/navigation';\n\n// ============================================================================\n// Context\n// ============================================================================\n\ninterface AthleteBridgeContextValue extends Omit<\n AthleteBridgeRootProps,\n 'registerNavItems' | 'registerFooterItems' | 'navigate'\n> {\n isStandalone: boolean;\n /** Shell's navigate function (only available in integrated mode) */\n navigate?: (path: string) => void;\n /** Navigate to a path relative to basePath */\n navigateTo: (relativePath: string) => void;\n /** Get the full path for a relative path */\n getPath: (relativePath: string) => string;\n}\n\nconst AthleteBridgeContext = createContext<AthleteBridgeContextValue | null>(null);\n\n// ============================================================================\n// Hook\n// ============================================================================\n\nexport function useAthleteBridge(): AthleteBridgeContextValue {\n const context = useContext(AthleteBridgeContext);\n if (!context) {\n throw new Error('useAthleteBridge must be used within a AthleteBridgeProvider');\n }\n return context;\n}\n\n// ============================================================================\n// Provider Component\n// ============================================================================\n\ntype ProviderProps = Omit<AthleteBridgeRootProps, 'registerNavItems' | 'registerFooterItems'> & {\n isStandalone: boolean;\n};\n\n/** Provider for standalone mode (has access to MemoryRouter) */\nconst StandaloneProviderInner: FC<PropsWithChildren<ProviderProps>> = ({\n children,\n basePath,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n onTeamSelect,\n onFixtureSelect,\n}) => {\n const routerNavigate = useRouterNavigate();\n const effectiveBasePath = basePath || '/';\n\n const getPath = useCallback(\n (relativePath: string) => joinPath(effectiveBasePath, relativePath),\n [effectiveBasePath]\n );\n\n const navigateTo = useCallback(\n (relativePath: string) => {\n const fullPath = joinPath(effectiveBasePath, relativePath);\n routerNavigate(fullPath);\n },\n [effectiveBasePath, routerNavigate]\n );\n\n const contextValue = useMemo<AthleteBridgeContextValue>(\n () => ({\n basePath: effectiveBasePath,\n navigate: undefined,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n onTeamSelect,\n onFixtureSelect,\n isStandalone: true,\n navigateTo,\n getPath,\n }),\n [\n effectiveBasePath,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n onTeamSelect,\n onFixtureSelect,\n navigateTo,\n getPath,\n ]\n );\n\n return (\n <AthleteBridgeContext.Provider value={contextValue}>{children}</AthleteBridgeContext.Provider>\n );\n};\n\n/** Provider for integrated mode (shell provides navigate) */\nconst IntegratedProviderInner: FC<\n PropsWithChildren<ProviderProps & { navigate: (path: string) => void }>\n> = ({\n children,\n basePath,\n navigate,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n onTeamSelect,\n onFixtureSelect,\n}) => {\n const effectiveBasePath = basePath || '/';\n\n const getPath = useCallback(\n (relativePath: string) => joinPath(effectiveBasePath, relativePath),\n [effectiveBasePath]\n );\n\n const navigateTo = useCallback(\n (relativePath: string) => {\n const fullPath = joinPath(effectiveBasePath, relativePath);\n navigate(fullPath);\n },\n [effectiveBasePath, navigate]\n );\n\n const contextValue = useMemo<AthleteBridgeContextValue>(\n () => ({\n basePath: effectiveBasePath,\n navigate,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n onTeamSelect,\n onFixtureSelect,\n isStandalone: false,\n navigateTo,\n getPath,\n }),\n [\n effectiveBasePath,\n navigate,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n onTeamSelect,\n onFixtureSelect,\n navigateTo,\n getPath,\n ]\n );\n\n return (\n <AthleteBridgeContext.Provider value={contextValue}>{children}</AthleteBridgeContext.Provider>\n );\n};\n\nexport const AthleteBridgeProvider: FC<PropsWithChildren<AthleteBridgeRootProps>> = (props) => {\n const { children, navigate } = props;\n // Standalone mode: no navigate function provided (MFE manages its own router)\n // Integrated mode: navigate function provided by shell (shell provides router)\n const isStandalone = !navigate;\n\n // Standalone mode: wrap in MemoryRouter\n if (isStandalone) {\n return (\n <MemoryRouter>\n <StandaloneProviderInner {...props} isStandalone={true}>\n {children}\n </StandaloneProviderInner>\n </MemoryRouter>\n );\n }\n\n // Integrated mode: shell provides router\n return (\n <IntegratedProviderInner {...props} navigate={navigate} isStandalone={false}>\n {children}\n </IntegratedProviderInner>\n );\n};\n"],"mappings":";;;;;AAiCA,IAAM,IAAuB,EAAgD,KAAK;AAMlF,SAAgB,IAA8C;CAC5D,IAAM,IAAU,EAAW,EAAqB;AAChD,KAAI,CAAC,EACH,OAAU,MAAM,+DAA+D;AAEjF,QAAO;;AAYT,IAAM,KAAiE,EACrE,aACA,aACA,gBACA,aACA,gBACA,kBACA,cACA,iBACA,yBACI;CACJ,IAAM,IAAiB,GAAmB,EACpC,IAAoB,KAAY,KAEhC,IAAU,GACb,MAAyB,EAAS,GAAmB,EAAa,EACnE,CAAC,EAAkB,CACpB,EAEK,IAAa,GAChB,MAAyB;AAExB,IADiB,EAAS,GAAmB,EAAa,CAClC;IAE1B,CAAC,GAAmB,EAAe,CACpC,EAEK,IAAe,SACZ;EACL,UAAU;EACV,UAAU,KAAA;EACV;EACA;EACA;EACA;EACA;EACA;EACA;EACA,cAAc;EACd;EACA;EACD,GACD;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CACF;AAED,QACE,kBAAC,EAAqB,UAAtB;EAA+B,OAAO;EAAe;EAAyC,CAAA;GAK5F,KAED,EACH,aACA,aACA,aACA,gBACA,aACA,gBACA,kBACA,cACA,iBACA,yBACI;CACJ,IAAM,IAAoB,KAAY,KAEhC,IAAU,GACb,MAAyB,EAAS,GAAmB,EAAa,EACnE,CAAC,EAAkB,CACpB,EAEK,IAAa,GAChB,MAAyB;AAExB,IADiB,EAAS,GAAmB,EAAa,CACxC;IAEpB,CAAC,GAAmB,EAAS,CAC9B,EAEK,IAAe,SACZ;EACL,UAAU;EACV;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,cAAc;EACd;EACA;EACD,GACD;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CACF;AAED,QACE,kBAAC,EAAqB,UAAtB;EAA+B,OAAO;EAAe;EAAyC,CAAA;GAIrF,KAAwE,MAAU;CAC7F,IAAM,EAAE,aAAU,gBAAa;AAiB/B,QAdsB,IAepB,kBAAC,GAAD;EAAyB,GAAI;EAAiB;EAAU,cAAc;EACnE;EACuB,CAAA,GAZxB,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;EAAyB,GAAI;EAAO,cAAc;EAC/C;EACuB,CAAA,EACb,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"DashboardPage.js","names":[],"sources":["../../../src/atheletebridge/pages/DashboardPage.tsx"],"sourcesContent":["/**\n * Dashboard / Overview\n * @module atheletebridge/pages\n *\n * The AthleteBridge landing page: a workspace-wide command center that reads\n * live counts and recent items across teams, fixtures, programs and sessions\n * from the real `ath*` API.\n */\n\nimport { useMemo } from 'react';\nimport {\n Users,\n CalendarDays,\n Dumbbell,\n ClipboardCheck,\n ArrowRight,\n Plus,\n Trophy,\n} from 'lucide-react';\nimport { EmphasisPanel, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../components/PageLayout';\nimport { MetricsCard } from '../components/MetricsCard';\nimport { TeamCard } from '../components/TeamCard';\nimport { FixtureCard } from '../components/FixtureCard';\nimport { SessionCard } from '../components/SessionCard';\nimport { EmptyState } from '../components/EmptyState';\nimport { LoadingState } from '../components/LoadingState';\nimport { useAthleteBridge } from '../context/AthleteBridgeContext';\nimport {\n useTeams,\n useFixtures,\n usePrograms,\n useSessions,\n} from '../hooks/useAthleteBridgeApi';\n\nexport function DashboardPage() {\n const { navigateTo, currentUser } = useAthleteBridge();\n\n const teams = useTeams({ pagination: { limit: 50 } });\n const fixtures = useFixtures({ pagination: { limit: 50 } });\n const programs = usePrograms({ pagination: { limit: 50 } });\n const sessions = useSessions({ pagination: { limit: 50 } });\n\n const firstName = currentUser?.firstName ?? currentUser?.name ?? 'coach';\n\n const teamItems = useMemo(() => teams.data?.items ?? [], [teams.data]);\n const fixtureItems = useMemo(() => fixtures.data?.items ?? [], [fixtures.data]);\n const programItems = useMemo(() => programs.data?.items ?? [], [programs.data]);\n const sessionItems = useMemo(() => sessions.data?.items ?? [], [sessions.data]);\n\n const upcomingFixtures = useMemo(\n () =>\n [...fixtureItems]\n .filter((f) => f.status === 'SCHEDULED' || f.status === 'LIVE')\n .sort((a, b) => new Date(a.scheduledAt).getTime() - new Date(b.scheduledAt).getTime())\n .slice(0, 3),\n [fixtureItems]\n );\n\n const upcomingSessions = useMemo(\n () =>\n [...sessionItems]\n .filter((s) => s.status === 'REQUESTED' || s.status === 'CONFIRMED')\n .sort((a, b) => new Date(a.scheduledAt).getTime() - new Date(b.scheduledAt).getTime())\n .slice(0, 3),\n [sessionItems]\n );\n\n const recentTeams = useMemo(() => teamItems.slice(0, 6), [teamItems]);\n\n const publishedPrograms = programItems.filter((p) => p.status === 'PUBLISHED').length;\n\n const isLoading =\n teams.isLoading || fixtures.isLoading || programs.isLoading || sessions.isLoading;\n\n if (isLoading) {\n return (\n <PageLayout title=\"AthleteBridge\" hero gradientTitle>\n <LoadingState label=\"Loading your sporting operation…\" />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title={`Welcome back, ${firstName}`}\n description=\"Your teams, fixtures, training and coaching — one connected record.\"\n hero\n gradientTitle\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/teams/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 team\n </button>\n }\n >\n <PagePurpose className=\"mb-6\">\n AthleteBridge is the operating system for sports, coaching and training. From here you can\n assemble your roster, schedule fixtures, build training programs, run coaching sessions and\n track athlete performance — all reading and writing the same workspace-scoped source of\n truth.\n </PagePurpose>\n\n {/* KPIs */}\n <EmphasisPanel className=\"mb-8\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">At a glance</h2>\n <div className=\"grid grid-cols-2 gap-4 lg:grid-cols-4\">\n <MetricsCard\n icon={<Users className=\"h-5 w-5\" />}\n value={teamItems.length}\n label=\"Teams\"\n featured\n />\n <MetricsCard\n icon={<CalendarDays className=\"h-5 w-5\" />}\n value={fixtureItems.length}\n label=\"Fixtures\"\n />\n <MetricsCard\n icon={<Dumbbell className=\"h-5 w-5\" />}\n value={`${publishedPrograms}/${programItems.length}`}\n label=\"Programs published\"\n />\n <MetricsCard\n icon={<ClipboardCheck className=\"h-5 w-5\" />}\n value={sessionItems.length}\n label=\"Sessions\"\n />\n </div>\n </EmphasisPanel>\n\n {/* Upcoming fixtures */}\n <section className=\"mb-8\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Upcoming fixtures</h2>\n <button\n type=\"button\"\n onClick={() => navigateTo('/fixtures')}\n className=\"flex items-center gap-1 text-sm font-medium text-primary hover:underline\"\n >\n View all <ArrowRight className=\"h-3.5 w-3.5\" />\n </button>\n </div>\n {upcomingFixtures.length === 0 ? (\n <EmptyState\n illustration=\"empty-calendar\"\n icon={<CalendarDays className=\"h-10 w-10\" />}\n title=\"No upcoming fixtures\"\n description=\"Schedule a match to start tracking results and standings.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/fixtures/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 Schedule fixture\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {upcomingFixtures.map((fixture) => (\n <FixtureCard\n key={fixture.id}\n fixture={fixture}\n onClick={() => navigateTo(`/fixtures/${fixture.id}`)}\n />\n ))}\n </div>\n )}\n </section>\n\n {/* Upcoming sessions */}\n <section className=\"mb-8\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Upcoming sessions</h2>\n <button\n type=\"button\"\n onClick={() => navigateTo('/sessions')}\n className=\"flex items-center gap-1 text-sm font-medium text-primary hover:underline\"\n >\n View all <ArrowRight className=\"h-3.5 w-3.5\" />\n </button>\n </div>\n {upcomingSessions.length === 0 ? (\n <EmptyState\n illustration=\"empty-calendar\"\n icon={<ClipboardCheck className=\"h-10 w-10\" />}\n title=\"No upcoming sessions\"\n description=\"Book a coaching session with an athlete to get started.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/sessions/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 Book session\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {upcomingSessions.map((session) => (\n <SessionCard\n key={session.id}\n session={session}\n onClick={() => navigateTo(`/sessions/${session.id}`)}\n />\n ))}\n </div>\n )}\n </section>\n\n {/* Teams */}\n <section>\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Your teams</h2>\n <button\n type=\"button\"\n onClick={() => navigateTo('/teams')}\n className=\"flex items-center gap-1 text-sm font-medium text-primary hover:underline\"\n >\n View all <ArrowRight className=\"h-3.5 w-3.5\" />\n </button>\n </div>\n {recentTeams.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Trophy className=\"h-10 w-10\" />}\n title=\"No teams yet\"\n description=\"Create your first team to build a roster and schedule fixtures.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/teams/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 team\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {recentTeams.map((team) => (\n <TeamCard\n key={team.id}\n team={team}\n onClick={() => navigateTo(`/teams/${team.id}`)}\n />\n ))}\n </div>\n )}\n </section>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"DashboardPage.js","names":[],"sources":["../../../src/atheletebridge/pages/DashboardPage.tsx"],"sourcesContent":["/**\n * Dashboard / Overview\n * @module atheletebridge/pages\n *\n * The AthleteBridge landing page: a workspace-wide command center that reads\n * live counts and recent items across teams, fixtures, programs and sessions\n * from the real `ath*` API.\n */\n\nimport { useMemo } from 'react';\nimport {\n Users,\n CalendarDays,\n Dumbbell,\n ClipboardCheck,\n ArrowRight,\n Plus,\n Trophy,\n} from 'lucide-react';\nimport { EmphasisPanel, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../components/PageLayout';\nimport { MetricsCard } from '../components/MetricsCard';\nimport { TeamCard } from '../components/TeamCard';\nimport { FixtureCard } from '../components/FixtureCard';\nimport { SessionCard } from '../components/SessionCard';\nimport { EmptyState } from '../components/EmptyState';\nimport { LoadingState } from '../components/LoadingState';\nimport { useAthleteBridge } from '../context/AthleteBridgeContext';\nimport { useTeams, useFixtures, usePrograms, useSessions } from '../hooks/useAthleteBridgeApi';\n\nexport function DashboardPage() {\n const { navigateTo, currentUser } = useAthleteBridge();\n\n const teams = useTeams({ pagination: { limit: 50 } });\n const fixtures = useFixtures({ pagination: { limit: 50 } });\n const programs = usePrograms({ pagination: { limit: 50 } });\n const sessions = useSessions({ pagination: { limit: 50 } });\n\n const firstName = currentUser?.firstName ?? currentUser?.name ?? 'coach';\n\n const teamItems = useMemo(() => teams.data?.items ?? [], [teams.data]);\n const fixtureItems = useMemo(() => fixtures.data?.items ?? [], [fixtures.data]);\n const programItems = useMemo(() => programs.data?.items ?? [], [programs.data]);\n const sessionItems = useMemo(() => sessions.data?.items ?? [], [sessions.data]);\n\n const upcomingFixtures = useMemo(\n () =>\n [...fixtureItems]\n .filter((f) => f.status === 'SCHEDULED' || f.status === 'LIVE')\n .sort((a, b) => new Date(a.scheduledAt).getTime() - new Date(b.scheduledAt).getTime())\n .slice(0, 3),\n [fixtureItems]\n );\n\n const upcomingSessions = useMemo(\n () =>\n [...sessionItems]\n .filter((s) => s.status === 'REQUESTED' || s.status === 'CONFIRMED')\n .sort((a, b) => new Date(a.scheduledAt).getTime() - new Date(b.scheduledAt).getTime())\n .slice(0, 3),\n [sessionItems]\n );\n\n const recentTeams = useMemo(() => teamItems.slice(0, 6), [teamItems]);\n\n const publishedPrograms = programItems.filter((p) => p.status === 'PUBLISHED').length;\n\n const isLoading =\n teams.isLoading || fixtures.isLoading || programs.isLoading || sessions.isLoading;\n\n if (isLoading) {\n return (\n <PageLayout title=\"AthleteBridge\" hero gradientTitle>\n <LoadingState label=\"Loading your sporting operation…\" />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title={`Welcome back, ${firstName}`}\n description=\"Your teams, fixtures, training and coaching — one connected record.\"\n hero\n gradientTitle\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/teams/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 team\n </button>\n }\n >\n <PagePurpose className=\"mb-6\">\n AthleteBridge is the operating system for sports, coaching and training. From here you can\n assemble your roster, schedule fixtures, build training programs, run coaching sessions and\n track athlete performance — all reading and writing the same workspace-scoped source of\n truth.\n </PagePurpose>\n\n {/* KPIs */}\n <EmphasisPanel className=\"mb-8\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">At a glance</h2>\n <div className=\"grid grid-cols-2 gap-4 lg:grid-cols-4\">\n <MetricsCard\n icon={<Users className=\"h-5 w-5\" />}\n value={teamItems.length}\n label=\"Teams\"\n featured\n />\n <MetricsCard\n icon={<CalendarDays className=\"h-5 w-5\" />}\n value={fixtureItems.length}\n label=\"Fixtures\"\n />\n <MetricsCard\n icon={<Dumbbell className=\"h-5 w-5\" />}\n value={`${publishedPrograms}/${programItems.length}`}\n label=\"Programs published\"\n />\n <MetricsCard\n icon={<ClipboardCheck className=\"h-5 w-5\" />}\n value={sessionItems.length}\n label=\"Sessions\"\n />\n </div>\n </EmphasisPanel>\n\n {/* Upcoming fixtures */}\n <section className=\"mb-8\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Upcoming fixtures</h2>\n <button\n type=\"button\"\n onClick={() => navigateTo('/fixtures')}\n className=\"flex items-center gap-1 text-sm font-medium text-primary hover:underline\"\n >\n View all <ArrowRight className=\"h-3.5 w-3.5\" />\n </button>\n </div>\n {upcomingFixtures.length === 0 ? (\n <EmptyState\n illustration=\"empty-calendar\"\n icon={<CalendarDays className=\"h-10 w-10\" />}\n title=\"No upcoming fixtures\"\n description=\"Schedule a match to start tracking results and standings.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/fixtures/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 Schedule fixture\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {upcomingFixtures.map((fixture) => (\n <FixtureCard\n key={fixture.id}\n fixture={fixture}\n onClick={() => navigateTo(`/fixtures/${fixture.id}`)}\n />\n ))}\n </div>\n )}\n </section>\n\n {/* Upcoming sessions */}\n <section className=\"mb-8\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Upcoming sessions</h2>\n <button\n type=\"button\"\n onClick={() => navigateTo('/sessions')}\n className=\"flex items-center gap-1 text-sm font-medium text-primary hover:underline\"\n >\n View all <ArrowRight className=\"h-3.5 w-3.5\" />\n </button>\n </div>\n {upcomingSessions.length === 0 ? (\n <EmptyState\n illustration=\"empty-calendar\"\n icon={<ClipboardCheck className=\"h-10 w-10\" />}\n title=\"No upcoming sessions\"\n description=\"Book a coaching session with an athlete to get started.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/sessions/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 Book session\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {upcomingSessions.map((session) => (\n <SessionCard\n key={session.id}\n session={session}\n onClick={() => navigateTo(`/sessions/${session.id}`)}\n />\n ))}\n </div>\n )}\n </section>\n\n {/* Teams */}\n <section>\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Your teams</h2>\n <button\n type=\"button\"\n onClick={() => navigateTo('/teams')}\n className=\"flex items-center gap-1 text-sm font-medium text-primary hover:underline\"\n >\n View all <ArrowRight className=\"h-3.5 w-3.5\" />\n </button>\n </div>\n {recentTeams.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Trophy className=\"h-10 w-10\" />}\n title=\"No teams yet\"\n description=\"Create your first team to build a roster and schedule fixtures.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/teams/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 team\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {recentTeams.map((team) => (\n <TeamCard key={team.id} team={team} onClick={() => navigateTo(`/teams/${team.id}`)} />\n ))}\n </div>\n )}\n </section>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"StandingsPage.js","names":[],"sources":["../../../src/atheletebridge/pages/StandingsPage.tsx"],"sourcesContent":["/**\n * Standings\n * @module atheletebridge/pages\n *\n * Reads the live competition table (athStandings) for a chosen competition.\n * The svc requires a competition name, so the user picks one (seeded from\n * fixtures that carry a competition, or typed directly).\n */\n\nimport { useEffect, useMemo, useState } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport { Search, Trophy } from 'lucide-react';\nimport { EmphasisPanel } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../components/PageLayout';\nimport { EmptyState } from '../components/EmptyState';\nimport { LoadingState, ErrorState } from '../components/LoadingState';\nimport { fieldInputClass } from '../components/form';\nimport { useFixtures, useStandings } from '../hooks/useAthleteBridgeApi';\n\nexport function StandingsPage() {\n const [searchParams] = useSearchParams();\n const initialCompetition = searchParams.get('competition') ?? '';\n\n const [competition, setCompetition] = useState(initialCompetition);\n const [activeCompetition, setActiveCompetition] = useState(initialCompetition);\n\n // Suggest competitions that appear on existing fixtures.\n const fixtures = useFixtures({ pagination: { limit: 100 } });\n const competitions = useMemo(() => {\n const set = new Set<string>();\n for (const f of fixtures.data?.items ?? []) {\n if (f.competition) set.add(f.competition);\n }\n return Array.from(set).sort();\n }, [fixtures.data]);\n\n // If no competition selected yet but fixtures have one, default to the first.\n useEffect(() => {\n if (!activeCompetition && competitions.length > 0) {\n setCompetition(competitions[0]);\n setActiveCompetition(competitions[0]);\n }\n }, [activeCompetition, competitions]);\n\n const standings = useStandings(activeCompetition || undefined);\n const rows = useMemo(() => {\n const items = standings.data ?? [];\n return [...items].sort((a, b) => {\n if (b.points !== a.points) return b.points - a.points;\n return b.goalsFor - a.goalsFor;\n });\n }, [standings.data]);\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n setActiveCompetition(competition.trim());\n };\n\n return (\n <PageLayout\n title=\"Standings\"\n description=\"Live competition tables — recomputed automatically from results.\"\n >\n <form onSubmit={handleSubmit} className=\"mb-6 flex flex-wrap items-end gap-3\">\n <div className=\"flex-1 min-w-[220px]\">\n <label htmlFor=\"competition\" className=\"mb-1.5 block text-sm font-medium text-text-primary\">\n Competition\n </label>\n <input\n id=\"competition\"\n list=\"ath-competitions\"\n className={fieldInputClass}\n value={competition}\n onChange={(e) => setCompetition(e.target.value)}\n placeholder=\"e.g. Spring League 2026\"\n />\n <datalist id=\"ath-competitions\">\n {competitions.map((c) => (\n <option key={c} value={c} />\n ))}\n </datalist>\n </div>\n <button\n type=\"submit\"\n className=\"inline-flex h-[38px] items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n <Search className=\"h-4 w-4\" /> View table\n </button>\n </form>\n\n {!activeCompetition ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Trophy className=\"h-10 w-10\" />}\n title=\"Pick a competition\"\n description=\"Enter a competition name to see its live standings. Standings are computed from recorded fixture results on a 3-1-0 model.\"\n />\n ) : standings.isLoading ? (\n <LoadingState label=\"Loading standings…\" />\n ) : standings.isError ? (\n <ErrorState onRetry={() => void standings.refetch()} />\n ) : rows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Trophy className=\"h-10 w-10\" />}\n title=\"No standings yet\"\n description={`No results have been recorded for \"${activeCompetition}\". Record a fixture result to build the table.`}\n />\n ) : (\n <EmphasisPanel className=\"overflow-x-auto\">\n <table className=\"w-full min-w-[640px] text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam text-left text-xs uppercase tracking-wide text-text-secondary\">\n <th className=\"px-3 py-2 font-medium\">#</th>\n <th className=\"px-3 py-2 font-medium\">Team</th>\n <th className=\"px-3 py-2 text-center font-medium\">P</th>\n <th className=\"px-3 py-2 text-center font-medium\">W</th>\n <th className=\"px-3 py-2 text-center font-medium\">D</th>\n <th className=\"px-3 py-2 text-center font-medium\">L</th>\n <th className=\"px-3 py-2 text-center font-medium\">GF</th>\n <th className=\"px-3 py-2 text-center font-medium\">GA</th>\n <th className=\"px-3 py-2 text-center font-medium\">GD</th>\n <th className=\"px-3 py-2 text-center font-semibold text-text-primary\">Pts</th>\n </tr>\n </thead>\n <tbody>\n {rows.map((row, idx) => (\n <tr\n key={row.id}\n className=\"border-b border-border-seam/60 last:border-0 hover:bg-bg-sunken/50\"\n >\n <td className=\"px-3 py-2.5 tabular-nums text-text-secondary\">\n {row.rank ?? idx + 1}\n </td>\n <td className=\"px-3 py-2.5 font-medium text-text-primary\">\n {row.team?.name ?? 'Unknown team'}\n </td>\n <td className=\"px-3 py-2.5 text-center tabular-nums text-text-secondary\">\n {row.played}\n </td>\n <td className=\"px-3 py-2.5 text-center tabular-nums text-text-secondary\">\n {row.won}\n </td>\n <td className=\"px-3 py-2.5 text-center tabular-nums text-text-secondary\">\n {row.drawn}\n </td>\n <td className=\"px-3 py-2.5 text-center tabular-nums text-text-secondary\">\n {row.lost}\n </td>\n <td className=\"px-3 py-2.5 text-center tabular-nums text-text-secondary\">\n {row.goalsFor}\n </td>\n <td className=\"px-3 py-2.5 text-center tabular-nums text-text-secondary\">\n {row.goalsAgainst}\n </td>\n <td className=\"px-3 py-2.5 text-center tabular-nums text-text-secondary\">\n {row.goalsFor - row.goalsAgainst}\n </td>\n <td className=\"px-3 py-2.5 text-center font-bold tabular-nums text-text-primary\">\n {row.points}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </EmphasisPanel>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"StandingsPage.js","names":[],"sources":["../../../src/atheletebridge/pages/StandingsPage.tsx"],"sourcesContent":["/**\n * Standings\n * @module atheletebridge/pages\n *\n * Reads the live competition table (athStandings) for a chosen competition.\n * The svc requires a competition name, so the user picks one (seeded from\n * fixtures that carry a competition, or typed directly).\n */\n\nimport { useEffect, useMemo, useState } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport { Search, Trophy } from 'lucide-react';\nimport { EmphasisPanel } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../components/PageLayout';\nimport { EmptyState } from '../components/EmptyState';\nimport { LoadingState, ErrorState } from '../components/LoadingState';\nimport { fieldInputClass } from '../components/form';\nimport { useFixtures, useStandings } from '../hooks/useAthleteBridgeApi';\n\nexport function StandingsPage() {\n const [searchParams] = useSearchParams();\n const initialCompetition = searchParams.get('competition') ?? '';\n\n const [competition, setCompetition] = useState(initialCompetition);\n const [activeCompetition, setActiveCompetition] = useState(initialCompetition);\n\n // Suggest competitions that appear on existing fixtures.\n const fixtures = useFixtures({ pagination: { limit: 100 } });\n const competitions = useMemo(() => {\n const set = new Set<string>();\n for (const f of fixtures.data?.items ?? []) {\n if (f.competition) set.add(f.competition);\n }\n return Array.from(set).sort();\n }, [fixtures.data]);\n\n // If no competition selected yet but fixtures have one, default to the first.\n useEffect(() => {\n if (!activeCompetition && competitions.length > 0) {\n setCompetition(competitions[0]);\n setActiveCompetition(competitions[0]);\n }\n }, [activeCompetition, competitions]);\n\n const standings = useStandings(activeCompetition || undefined);\n const rows = useMemo(() => {\n const items = standings.data ?? [];\n return [...items].sort((a, b) => {\n if (b.points !== a.points) return b.points - a.points;\n return b.goalsFor - a.goalsFor;\n });\n }, [standings.data]);\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n setActiveCompetition(competition.trim());\n };\n\n return (\n <PageLayout\n title=\"Standings\"\n description=\"Live competition tables — recomputed automatically from results.\"\n >\n <form onSubmit={handleSubmit} className=\"mb-6 flex flex-wrap items-end gap-3\">\n <div className=\"flex-1 min-w-[220px]\">\n <label\n htmlFor=\"competition\"\n className=\"mb-1.5 block text-sm font-medium text-text-primary\"\n >\n Competition\n </label>\n <input\n id=\"competition\"\n list=\"ath-competitions\"\n className={fieldInputClass}\n value={competition}\n onChange={(e) => setCompetition(e.target.value)}\n placeholder=\"e.g. Spring League 2026\"\n />\n <datalist id=\"ath-competitions\">\n {competitions.map((c) => (\n <option key={c} value={c} />\n ))}\n </datalist>\n </div>\n <button\n type=\"submit\"\n className=\"inline-flex h-[38px] items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n <Search className=\"h-4 w-4\" /> View table\n </button>\n </form>\n\n {!activeCompetition ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Trophy className=\"h-10 w-10\" />}\n title=\"Pick a competition\"\n description=\"Enter a competition name to see its live standings. Standings are computed from recorded fixture results on a 3-1-0 model.\"\n />\n ) : standings.isLoading ? (\n <LoadingState label=\"Loading standings…\" />\n ) : standings.isError ? (\n <ErrorState onRetry={() => void standings.refetch()} />\n ) : rows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Trophy className=\"h-10 w-10\" />}\n title=\"No standings yet\"\n description={`No results have been recorded for \"${activeCompetition}\". Record a fixture result to build the table.`}\n />\n ) : (\n <EmphasisPanel className=\"overflow-x-auto\">\n <table className=\"w-full min-w-[640px] text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam text-left text-xs uppercase tracking-wide text-text-secondary\">\n <th className=\"px-3 py-2 font-medium\">#</th>\n <th className=\"px-3 py-2 font-medium\">Team</th>\n <th className=\"px-3 py-2 text-center font-medium\">P</th>\n <th className=\"px-3 py-2 text-center font-medium\">W</th>\n <th className=\"px-3 py-2 text-center font-medium\">D</th>\n <th className=\"px-3 py-2 text-center font-medium\">L</th>\n <th className=\"px-3 py-2 text-center font-medium\">GF</th>\n <th className=\"px-3 py-2 text-center font-medium\">GA</th>\n <th className=\"px-3 py-2 text-center font-medium\">GD</th>\n <th className=\"px-3 py-2 text-center font-semibold text-text-primary\">Pts</th>\n </tr>\n </thead>\n <tbody>\n {rows.map((row, idx) => (\n <tr\n key={row.id}\n className=\"border-b border-border-seam/60 last:border-0 hover:bg-bg-sunken/50\"\n >\n <td className=\"px-3 py-2.5 tabular-nums text-text-secondary\">\n {row.rank ?? idx + 1}\n </td>\n <td className=\"px-3 py-2.5 font-medium text-text-primary\">\n {row.team?.name ?? 'Unknown team'}\n </td>\n <td className=\"px-3 py-2.5 text-center tabular-nums text-text-secondary\">\n {row.played}\n </td>\n <td className=\"px-3 py-2.5 text-center tabular-nums text-text-secondary\">\n {row.won}\n </td>\n <td className=\"px-3 py-2.5 text-center tabular-nums text-text-secondary\">\n {row.drawn}\n </td>\n <td className=\"px-3 py-2.5 text-center tabular-nums text-text-secondary\">\n {row.lost}\n </td>\n <td className=\"px-3 py-2.5 text-center tabular-nums text-text-secondary\">\n {row.goalsFor}\n </td>\n <td className=\"px-3 py-2.5 text-center tabular-nums text-text-secondary\">\n {row.goalsAgainst}\n </td>\n <td className=\"px-3 py-2.5 text-center tabular-nums text-text-secondary\">\n {row.goalsFor - row.goalsAgainst}\n </td>\n <td className=\"px-3 py-2.5 text-center font-bold tabular-nums text-text-primary\">\n {row.points}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </EmphasisPanel>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -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-dom';\nimport {\n ArrowLeft,\n Edit,\n Archive,\n Plus,\n Trash2,\n Loader2,\n Send,\n Undo2,\n Copy,\n} 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 && (\n <Loader2 className=\"h-4 w-4 animate-spin text-text-secondary\" />\n )}\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) =>\n setDrillForm((p) => ({ ...p, description: e.target.value }))\n }\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) =>\n setDrillForm((p) => ({ ...p, weekNumber: e.target.value }))\n }\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) =>\n setDrillForm((p) => ({ ...p, durationSec: e.target.value }))\n }\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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"}
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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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ProgramFormPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/programs/ProgramFormPage.tsx"],"sourcesContent":["/**\n * Program Create / Edit Form\n * @module atheletebridge/pages/programs\n */\n\nimport { useEffect, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { ArrowLeft, Loader2, Save } from 'lucide-react';\nimport { PageLayout } from '../../components/PageLayout';\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 useCreateProgram,\n useUpdateProgram,\n} from '../../hooks/useAthleteBridgeApi';\nimport type { AthProgramLevel, AthSport } from '../../types';\n\nconst SPORTS = Object.keys(SPORT_LABELS) as AthSport[];\nconst LEVELS = Object.keys(PROGRAM_LEVEL_LABELS) as AthProgramLevel[];\n\ninterface ProgramFormState {\n name: string;\n sport: AthSport;\n level: AthProgramLevel;\n summary: string;\n weeks: string;\n isTemplate: boolean;\n tagsText: string;\n}\n\nconst EMPTY_FORM: ProgramFormState = {\n name: '',\n sport: 'OTHER',\n level: 'BEGINNER',\n summary: '',\n weeks: '4',\n isTemplate: false,\n tagsText: '',\n};\n\nexport function ProgramFormPage() {\n const { id } = useParams<{ id: string }>();\n const isEdit = Boolean(id);\n const { navigateTo } = useAthleteBridge();\n\n const existing = useProgram(id);\n const createProgram = useCreateProgram();\n const updateProgram = useUpdateProgram();\n\n const [form, setForm] = useState<ProgramFormState>(EMPTY_FORM);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n const program = existing.data;\n if (isEdit && program) {\n setForm({\n name: program.name,\n sport: program.sport,\n level: program.level,\n summary: program.summary ?? '',\n weeks: String(program.weeks),\n isTemplate: program.isTemplate,\n tagsText: program.tags.join(', '),\n });\n }\n }, [isEdit, existing.data]);\n\n const set = <K extends keyof ProgramFormState>(key: K, value: ProgramFormState[K]) =>\n setForm((prev) => ({ ...prev, [key]: value }));\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n\n if (!form.name.trim()) {\n setError('Program name is required.');\n return;\n }\n const weeks = Number.parseInt(form.weeks, 10);\n if (Number.isNaN(weeks) || weeks < 1) {\n setError('Weeks must be a positive number.');\n return;\n }\n\n const tags = form.tagsText\n .split(',')\n .map((t) => t.trim())\n .filter(Boolean);\n\n try {\n if (isEdit && id) {\n await updateProgram.mutateAsync({\n id,\n input: {\n name: form.name.trim(),\n sport: form.sport,\n level: form.level,\n summary: form.summary.trim() || undefined,\n weeks,\n isTemplate: form.isTemplate,\n tags,\n },\n });\n navigateTo(`/programs/${id}`);\n } else {\n const created = await createProgram.mutateAsync({\n name: form.name.trim(),\n sport: form.sport,\n level: form.level,\n summary: form.summary.trim() || undefined,\n weeks,\n isTemplate: form.isTemplate,\n tags,\n });\n navigateTo(`/programs/${created.id}`);\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to save program.');\n }\n };\n\n const saving = createProgram.isPending || updateProgram.isPending;\n\n if (isEdit && existing.isLoading) {\n return (\n <PageLayout title=\"Edit program\">\n <LoadingState label=\"Loading program…\" />\n </PageLayout>\n );\n }\n\n if (isEdit && (existing.isError || (!existing.isLoading && !existing.data))) {\n return (\n <PageLayout title=\"Edit 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 return (\n <PageLayout\n title={isEdit ? 'Edit program' : 'New program'}\n description={\n isEdit ? 'Update the training plan details.' : 'Create a new training plan in draft.'\n }\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo(isEdit && id ? `/programs/${id}` : '/programs')}\n 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\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Cancel\n </button>\n }\n >\n <form onSubmit={handleSubmit} className=\"max-w-2xl space-y-5\">\n <FormField label=\"Program name\" htmlFor=\"name\" required>\n <input\n id=\"name\"\n className={fieldInputClass}\n value={form.name}\n onChange={(e) => set('name', e.target.value)}\n placeholder=\"e.g. Pre-season strength block\"\n />\n </FormField>\n\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-3\">\n <FormField label=\"Sport\" htmlFor=\"sport\">\n <select\n id=\"sport\"\n className={fieldInputClass}\n value={form.sport}\n onChange={(e) => set('sport', e.target.value as AthSport)}\n >\n {SPORTS.map((s) => (\n <option key={s} value={s}>\n {SPORT_LABELS[s]}\n </option>\n ))}\n </select>\n </FormField>\n\n <FormField label=\"Level\" htmlFor=\"level\">\n <select\n id=\"level\"\n className={fieldInputClass}\n value={form.level}\n onChange={(e) => set('level', e.target.value as AthProgramLevel)}\n >\n {LEVELS.map((l) => (\n <option key={l} value={l}>\n {PROGRAM_LEVEL_LABELS[l]}\n </option>\n ))}\n </select>\n </FormField>\n\n <FormField label=\"Weeks\" htmlFor=\"weeks\">\n <input\n id=\"weeks\"\n inputMode=\"numeric\"\n className={fieldInputClass}\n value={form.weeks}\n onChange={(e) => set('weeks', e.target.value)}\n placeholder=\"4\"\n />\n </FormField>\n </div>\n\n <FormField label=\"Summary\" htmlFor=\"summary\">\n <textarea\n id=\"summary\"\n className={fieldInputClass}\n rows={3}\n value={form.summary}\n onChange={(e) => set('summary', e.target.value)}\n placeholder=\"What this program is for and who it's aimed at.\"\n />\n </FormField>\n\n <FormField\n label=\"Tags\"\n htmlFor=\"tags\"\n hint=\"Comma-separated, e.g. strength, pre-season.\"\n >\n <input\n id=\"tags\"\n className={fieldInputClass}\n value={form.tagsText}\n onChange={(e) => set('tagsText', e.target.value)}\n placeholder=\"strength, conditioning\"\n />\n </FormField>\n\n <label className=\"flex items-center gap-2.5 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={form.isTemplate}\n onChange={(e) => set('isTemplate', e.target.checked)}\n className=\"h-4 w-4 rounded border-border-seam text-action-primary-bg focus:ring-[var(--color-focus-ring)]\"\n />\n Mark as reusable template\n </label>\n\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n\n <div className=\"pt-2\">\n <button\n type=\"submit\"\n disabled={saving}\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 {saving ? <Loader2 className=\"h-4 w-4 animate-spin\" /> : <Save className=\"h-4 w-4\" />}\n {isEdit ? 'Save changes' : 'Create program'}\n </button>\n </div>\n </form>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;AAoBA,IAAM,IAAS,OAAO,KAAK,EAAa,EAClC,IAAS,OAAO,KAAK,EAAqB,EAY1C,IAA+B;CACnC,MAAM;CACN,OAAO;CACP,OAAO;CACP,SAAS;CACT,OAAO;CACP,YAAY;CACZ,UAAU;CACX;AAED,SAAgB,IAAkB;CAChC,IAAM,EAAE,UAAO,GAA2B,EACpC,IAAS,EAAQ,GACjB,EAAE,kBAAe,GAAkB,EAEnC,IAAW,EAAW,EAAG,EACzB,IAAgB,GAAkB,EAClC,IAAgB,GAAkB,EAElC,CAAC,GAAM,KAAW,EAA2B,EAAW,EACxD,CAAC,GAAO,KAAY,EAAwB,KAAK;AAEvD,SAAgB;EACd,IAAM,IAAU,EAAS;AACzB,EAAI,KAAU,KACZ,EAAQ;GACN,MAAM,EAAQ;GACd,OAAO,EAAQ;GACf,OAAO,EAAQ;GACf,SAAS,EAAQ,WAAW;GAC5B,OAAO,OAAO,EAAQ,MAAM;GAC5B,YAAY,EAAQ;GACpB,UAAU,EAAQ,KAAK,KAAK,KAAK;GAClC,CAAC;IAEH,CAAC,GAAQ,EAAS,KAAK,CAAC;CAE3B,IAAM,KAAyC,GAAQ,MACrD,GAAS,OAAU;EAAE,GAAG;GAAO,IAAM;EAAO,EAAE,EAE1C,IAAe,OAAO,MAAuB;AAIjD,MAHA,EAAE,gBAAgB,EAClB,EAAS,KAAK,EAEV,CAAC,EAAK,KAAK,MAAM,EAAE;AACrB,KAAS,4BAA4B;AACrC;;EAEF,IAAM,IAAQ,OAAO,SAAS,EAAK,OAAO,GAAG;AAC7C,MAAI,OAAO,MAAM,EAAM,IAAI,IAAQ,GAAG;AACpC,KAAS,mCAAmC;AAC5C;;EAGF,IAAM,IAAO,EAAK,SACf,MAAM,IAAI,CACV,KAAK,MAAM,EAAE,MAAM,CAAC,CACpB,OAAO,QAAQ;AAElB,MAAI;AACF,GAAI,KAAU,KACZ,MAAM,EAAc,YAAY;IAC9B;IACA,OAAO;KACL,MAAM,EAAK,KAAK,MAAM;KACtB,OAAO,EAAK;KACZ,OAAO,EAAK;KACZ,SAAS,EAAK,QAAQ,MAAM,IAAI,KAAA;KAChC;KACA,YAAY,EAAK;KACjB;KACD;IACF,CAAC,EACF,EAAW,aAAa,IAAK,IAW7B,EAAW,cATK,MAAM,EAAc,YAAY;IAC9C,MAAM,EAAK,KAAK,MAAM;IACtB,OAAO,EAAK;IACZ,OAAO,EAAK;IACZ,SAAS,EAAK,QAAQ,MAAM,IAAI,KAAA;IAChC;IACA,YAAY,EAAK;IACjB;IACD,CAAC,EAC8B,KAAK;WAEhC,GAAK;AACZ,KAAS,aAAe,QAAQ,EAAI,UAAU,0BAA0B;;IAItE,IAAS,EAAc,aAAa,EAAc;AAsBxD,QApBI,KAAU,EAAS,YAEnB,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD,EAAc,OAAM,oBAAqB,CAAA;EAC9B,CAAA,GAIb,MAAW,EAAS,WAAY,CAAC,EAAS,aAAa,CAAC,EAAS,QAEjE,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD;GACE,OAAM;GACN,SAAQ;GACR,eAAe,EAAW,YAAY;GACtC,CAAA;EACS,CAAA,GAKf,kBAAC,GAAD;EACE,OAAO,IAAS,iBAAiB;EACjC,aACE,IAAS,sCAAsC;EAEjD,SACE,kBAAC,UAAD;GACE,MAAK;GACL,eAAe,EAAW,KAAU,IAAK,aAAa,MAAO,YAAY;GACzE,WAAU;aAHZ,CAKE,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA,EAAA,UAC1B;;YAGX,kBAAC,QAAD;GAAM,UAAU;GAAc,WAAU;aAAxC;IACE,kBAAC,GAAD;KAAW,OAAM;KAAe,SAAQ;KAAO,UAAA;eAC7C,kBAAC,SAAD;MACE,IAAG;MACH,WAAW;MACX,OAAO,EAAK;MACZ,WAAW,MAAM,EAAI,QAAQ,EAAE,OAAO,MAAM;MAC5C,aAAY;MACZ,CAAA;KACQ,CAAA;IAEZ,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD;OAAW,OAAM;OAAQ,SAAQ;iBAC/B,kBAAC,UAAD;QACE,IAAG;QACH,WAAW;QACX,OAAO,EAAK;QACZ,WAAW,MAAM,EAAI,SAAS,EAAE,OAAO,MAAkB;kBAExD,EAAO,KAAK,MACX,kBAAC,UAAD;SAAgB,OAAO;mBACpB,EAAa;SACP,EAFI,EAEJ,CACT;QACK,CAAA;OACC,CAAA;MAEZ,kBAAC,GAAD;OAAW,OAAM;OAAQ,SAAQ;iBAC/B,kBAAC,UAAD;QACE,IAAG;QACH,WAAW;QACX,OAAO,EAAK;QACZ,WAAW,MAAM,EAAI,SAAS,EAAE,OAAO,MAAyB;kBAE/D,EAAO,KAAK,MACX,kBAAC,UAAD;SAAgB,OAAO;mBACpB,EAAqB;SACf,EAFI,EAEJ,CACT;QACK,CAAA;OACC,CAAA;MAEZ,kBAAC,GAAD;OAAW,OAAM;OAAQ,SAAQ;iBAC/B,kBAAC,SAAD;QACE,IAAG;QACH,WAAU;QACV,WAAW;QACX,OAAO,EAAK;QACZ,WAAW,MAAM,EAAI,SAAS,EAAE,OAAO,MAAM;QAC7C,aAAY;QACZ,CAAA;OACQ,CAAA;MACR;;IAEN,kBAAC,GAAD;KAAW,OAAM;KAAU,SAAQ;eACjC,kBAAC,YAAD;MACE,IAAG;MACH,WAAW;MACX,MAAM;MACN,OAAO,EAAK;MACZ,WAAW,MAAM,EAAI,WAAW,EAAE,OAAO,MAAM;MAC/C,aAAY;MACZ,CAAA;KACQ,CAAA;IAEZ,kBAAC,GAAD;KACE,OAAM;KACN,SAAQ;KACR,MAAK;eAEL,kBAAC,SAAD;MACE,IAAG;MACH,WAAW;MACX,OAAO,EAAK;MACZ,WAAW,MAAM,EAAI,YAAY,EAAE,OAAO,MAAM;MAChD,aAAY;MACZ,CAAA;KACQ,CAAA;IAEZ,kBAAC,SAAD;KAAO,WAAU;eAAjB,CACE,kBAAC,SAAD;MACE,MAAK;MACL,SAAS,EAAK;MACd,WAAW,MAAM,EAAI,cAAc,EAAE,OAAO,QAAQ;MACpD,WAAU;MACV,CAAA,EAAA,4BAEI;;IAEP,KAAS,kBAAC,KAAD;KAAG,WAAU;eAAkC;KAAU,CAAA;IAEnE,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,UAAD;MACE,MAAK;MACL,UAAU;MACV,WAAU;gBAHZ,CAKG,IAAS,kBAAC,GAAD,EAAS,WAAU,wBAAyB,CAAA,GAAG,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EACpF,IAAS,iBAAiB,iBACpB;;KACL,CAAA;IACD;;EACI,CAAA"}
1
+ {"version":3,"file":"ProgramFormPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/programs/ProgramFormPage.tsx"],"sourcesContent":["/**\n * Program Create / Edit Form\n * @module atheletebridge/pages/programs\n */\n\nimport { useEffect, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { ArrowLeft, Loader2, Save } from 'lucide-react';\nimport { PageLayout } from '../../components/PageLayout';\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 { useProgram, useCreateProgram, useUpdateProgram } from '../../hooks/useAthleteBridgeApi';\nimport type { AthProgramLevel, AthSport } from '../../types';\n\nconst SPORTS = Object.keys(SPORT_LABELS) as AthSport[];\nconst LEVELS = Object.keys(PROGRAM_LEVEL_LABELS) as AthProgramLevel[];\n\ninterface ProgramFormState {\n name: string;\n sport: AthSport;\n level: AthProgramLevel;\n summary: string;\n weeks: string;\n isTemplate: boolean;\n tagsText: string;\n}\n\nconst EMPTY_FORM: ProgramFormState = {\n name: '',\n sport: 'OTHER',\n level: 'BEGINNER',\n summary: '',\n weeks: '4',\n isTemplate: false,\n tagsText: '',\n};\n\nexport function ProgramFormPage() {\n const { id } = useParams<{ id: string }>();\n const isEdit = Boolean(id);\n const { navigateTo } = useAthleteBridge();\n\n const existing = useProgram(id);\n const createProgram = useCreateProgram();\n const updateProgram = useUpdateProgram();\n\n const [form, setForm] = useState<ProgramFormState>(EMPTY_FORM);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n const program = existing.data;\n if (isEdit && program) {\n setForm({\n name: program.name,\n sport: program.sport,\n level: program.level,\n summary: program.summary ?? '',\n weeks: String(program.weeks),\n isTemplate: program.isTemplate,\n tagsText: program.tags.join(', '),\n });\n }\n }, [isEdit, existing.data]);\n\n const set = <K extends keyof ProgramFormState>(key: K, value: ProgramFormState[K]) =>\n setForm((prev) => ({ ...prev, [key]: value }));\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n\n if (!form.name.trim()) {\n setError('Program name is required.');\n return;\n }\n const weeks = Number.parseInt(form.weeks, 10);\n if (Number.isNaN(weeks) || weeks < 1) {\n setError('Weeks must be a positive number.');\n return;\n }\n\n const tags = form.tagsText\n .split(',')\n .map((t) => t.trim())\n .filter(Boolean);\n\n try {\n if (isEdit && id) {\n await updateProgram.mutateAsync({\n id,\n input: {\n name: form.name.trim(),\n sport: form.sport,\n level: form.level,\n summary: form.summary.trim() || undefined,\n weeks,\n isTemplate: form.isTemplate,\n tags,\n },\n });\n navigateTo(`/programs/${id}`);\n } else {\n const created = await createProgram.mutateAsync({\n name: form.name.trim(),\n sport: form.sport,\n level: form.level,\n summary: form.summary.trim() || undefined,\n weeks,\n isTemplate: form.isTemplate,\n tags,\n });\n navigateTo(`/programs/${created.id}`);\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to save program.');\n }\n };\n\n const saving = createProgram.isPending || updateProgram.isPending;\n\n if (isEdit && existing.isLoading) {\n return (\n <PageLayout title=\"Edit program\">\n <LoadingState label=\"Loading program…\" />\n </PageLayout>\n );\n }\n\n if (isEdit && (existing.isError || (!existing.isLoading && !existing.data))) {\n return (\n <PageLayout title=\"Edit 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 return (\n <PageLayout\n title={isEdit ? 'Edit program' : 'New program'}\n description={\n isEdit ? 'Update the training plan details.' : 'Create a new training plan in draft.'\n }\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo(isEdit && id ? `/programs/${id}` : '/programs')}\n 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\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Cancel\n </button>\n }\n >\n <form onSubmit={handleSubmit} className=\"max-w-2xl space-y-5\">\n <FormField label=\"Program name\" htmlFor=\"name\" required>\n <input\n id=\"name\"\n className={fieldInputClass}\n value={form.name}\n onChange={(e) => set('name', e.target.value)}\n placeholder=\"e.g. Pre-season strength block\"\n />\n </FormField>\n\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-3\">\n <FormField label=\"Sport\" htmlFor=\"sport\">\n <select\n id=\"sport\"\n className={fieldInputClass}\n value={form.sport}\n onChange={(e) => set('sport', e.target.value as AthSport)}\n >\n {SPORTS.map((s) => (\n <option key={s} value={s}>\n {SPORT_LABELS[s]}\n </option>\n ))}\n </select>\n </FormField>\n\n <FormField label=\"Level\" htmlFor=\"level\">\n <select\n id=\"level\"\n className={fieldInputClass}\n value={form.level}\n onChange={(e) => set('level', e.target.value as AthProgramLevel)}\n >\n {LEVELS.map((l) => (\n <option key={l} value={l}>\n {PROGRAM_LEVEL_LABELS[l]}\n </option>\n ))}\n </select>\n </FormField>\n\n <FormField label=\"Weeks\" htmlFor=\"weeks\">\n <input\n id=\"weeks\"\n inputMode=\"numeric\"\n className={fieldInputClass}\n value={form.weeks}\n onChange={(e) => set('weeks', e.target.value)}\n placeholder=\"4\"\n />\n </FormField>\n </div>\n\n <FormField label=\"Summary\" htmlFor=\"summary\">\n <textarea\n id=\"summary\"\n className={fieldInputClass}\n rows={3}\n value={form.summary}\n onChange={(e) => set('summary', e.target.value)}\n placeholder=\"What this program is for and who it's aimed at.\"\n />\n </FormField>\n\n <FormField label=\"Tags\" htmlFor=\"tags\" hint=\"Comma-separated, e.g. strength, pre-season.\">\n <input\n id=\"tags\"\n className={fieldInputClass}\n value={form.tagsText}\n onChange={(e) => set('tagsText', e.target.value)}\n placeholder=\"strength, conditioning\"\n />\n </FormField>\n\n <label className=\"flex items-center gap-2.5 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={form.isTemplate}\n onChange={(e) => set('isTemplate', e.target.checked)}\n className=\"h-4 w-4 rounded border-border-seam text-action-primary-bg focus:ring-[var(--color-focus-ring)]\"\n />\n Mark as reusable template\n </label>\n\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n\n <div className=\"pt-2\">\n <button\n type=\"submit\"\n disabled={saving}\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 {saving ? <Loader2 className=\"h-4 w-4 animate-spin\" /> : <Save className=\"h-4 w-4\" />}\n {isEdit ? 'Save changes' : 'Create program'}\n </button>\n </div>\n </form>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;AAgBA,IAAM,IAAS,OAAO,KAAK,EAAa,EAClC,IAAS,OAAO,KAAK,EAAqB,EAY1C,IAA+B;CACnC,MAAM;CACN,OAAO;CACP,OAAO;CACP,SAAS;CACT,OAAO;CACP,YAAY;CACZ,UAAU;CACX;AAED,SAAgB,IAAkB;CAChC,IAAM,EAAE,UAAO,GAA2B,EACpC,IAAS,EAAQ,GACjB,EAAE,kBAAe,GAAkB,EAEnC,IAAW,EAAW,EAAG,EACzB,IAAgB,GAAkB,EAClC,IAAgB,GAAkB,EAElC,CAAC,GAAM,KAAW,EAA2B,EAAW,EACxD,CAAC,GAAO,KAAY,EAAwB,KAAK;AAEvD,SAAgB;EACd,IAAM,IAAU,EAAS;AACzB,EAAI,KAAU,KACZ,EAAQ;GACN,MAAM,EAAQ;GACd,OAAO,EAAQ;GACf,OAAO,EAAQ;GACf,SAAS,EAAQ,WAAW;GAC5B,OAAO,OAAO,EAAQ,MAAM;GAC5B,YAAY,EAAQ;GACpB,UAAU,EAAQ,KAAK,KAAK,KAAK;GAClC,CAAC;IAEH,CAAC,GAAQ,EAAS,KAAK,CAAC;CAE3B,IAAM,KAAyC,GAAQ,MACrD,GAAS,OAAU;EAAE,GAAG;GAAO,IAAM;EAAO,EAAE,EAE1C,IAAe,OAAO,MAAuB;AAIjD,MAHA,EAAE,gBAAgB,EAClB,EAAS,KAAK,EAEV,CAAC,EAAK,KAAK,MAAM,EAAE;AACrB,KAAS,4BAA4B;AACrC;;EAEF,IAAM,IAAQ,OAAO,SAAS,EAAK,OAAO,GAAG;AAC7C,MAAI,OAAO,MAAM,EAAM,IAAI,IAAQ,GAAG;AACpC,KAAS,mCAAmC;AAC5C;;EAGF,IAAM,IAAO,EAAK,SACf,MAAM,IAAI,CACV,KAAK,MAAM,EAAE,MAAM,CAAC,CACpB,OAAO,QAAQ;AAElB,MAAI;AACF,GAAI,KAAU,KACZ,MAAM,EAAc,YAAY;IAC9B;IACA,OAAO;KACL,MAAM,EAAK,KAAK,MAAM;KACtB,OAAO,EAAK;KACZ,OAAO,EAAK;KACZ,SAAS,EAAK,QAAQ,MAAM,IAAI,KAAA;KAChC;KACA,YAAY,EAAK;KACjB;KACD;IACF,CAAC,EACF,EAAW,aAAa,IAAK,IAW7B,EAAW,cATK,MAAM,EAAc,YAAY;IAC9C,MAAM,EAAK,KAAK,MAAM;IACtB,OAAO,EAAK;IACZ,OAAO,EAAK;IACZ,SAAS,EAAK,QAAQ,MAAM,IAAI,KAAA;IAChC;IACA,YAAY,EAAK;IACjB;IACD,CAAC,EAC8B,KAAK;WAEhC,GAAK;AACZ,KAAS,aAAe,QAAQ,EAAI,UAAU,0BAA0B;;IAItE,IAAS,EAAc,aAAa,EAAc;AAsBxD,QApBI,KAAU,EAAS,YAEnB,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD,EAAc,OAAM,oBAAqB,CAAA;EAC9B,CAAA,GAIb,MAAW,EAAS,WAAY,CAAC,EAAS,aAAa,CAAC,EAAS,QAEjE,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD;GACE,OAAM;GACN,SAAQ;GACR,eAAe,EAAW,YAAY;GACtC,CAAA;EACS,CAAA,GAKf,kBAAC,GAAD;EACE,OAAO,IAAS,iBAAiB;EACjC,aACE,IAAS,sCAAsC;EAEjD,SACE,kBAAC,UAAD;GACE,MAAK;GACL,eAAe,EAAW,KAAU,IAAK,aAAa,MAAO,YAAY;GACzE,WAAU;aAHZ,CAKE,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA,EAAA,UAC1B;;YAGX,kBAAC,QAAD;GAAM,UAAU;GAAc,WAAU;aAAxC;IACE,kBAAC,GAAD;KAAW,OAAM;KAAe,SAAQ;KAAO,UAAA;eAC7C,kBAAC,SAAD;MACE,IAAG;MACH,WAAW;MACX,OAAO,EAAK;MACZ,WAAW,MAAM,EAAI,QAAQ,EAAE,OAAO,MAAM;MAC5C,aAAY;MACZ,CAAA;KACQ,CAAA;IAEZ,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD;OAAW,OAAM;OAAQ,SAAQ;iBAC/B,kBAAC,UAAD;QACE,IAAG;QACH,WAAW;QACX,OAAO,EAAK;QACZ,WAAW,MAAM,EAAI,SAAS,EAAE,OAAO,MAAkB;kBAExD,EAAO,KAAK,MACX,kBAAC,UAAD;SAAgB,OAAO;mBACpB,EAAa;SACP,EAFI,EAEJ,CACT;QACK,CAAA;OACC,CAAA;MAEZ,kBAAC,GAAD;OAAW,OAAM;OAAQ,SAAQ;iBAC/B,kBAAC,UAAD;QACE,IAAG;QACH,WAAW;QACX,OAAO,EAAK;QACZ,WAAW,MAAM,EAAI,SAAS,EAAE,OAAO,MAAyB;kBAE/D,EAAO,KAAK,MACX,kBAAC,UAAD;SAAgB,OAAO;mBACpB,EAAqB;SACf,EAFI,EAEJ,CACT;QACK,CAAA;OACC,CAAA;MAEZ,kBAAC,GAAD;OAAW,OAAM;OAAQ,SAAQ;iBAC/B,kBAAC,SAAD;QACE,IAAG;QACH,WAAU;QACV,WAAW;QACX,OAAO,EAAK;QACZ,WAAW,MAAM,EAAI,SAAS,EAAE,OAAO,MAAM;QAC7C,aAAY;QACZ,CAAA;OACQ,CAAA;MACR;;IAEN,kBAAC,GAAD;KAAW,OAAM;KAAU,SAAQ;eACjC,kBAAC,YAAD;MACE,IAAG;MACH,WAAW;MACX,MAAM;MACN,OAAO,EAAK;MACZ,WAAW,MAAM,EAAI,WAAW,EAAE,OAAO,MAAM;MAC/C,aAAY;MACZ,CAAA;KACQ,CAAA;IAEZ,kBAAC,GAAD;KAAW,OAAM;KAAO,SAAQ;KAAO,MAAK;eAC1C,kBAAC,SAAD;MACE,IAAG;MACH,WAAW;MACX,OAAO,EAAK;MACZ,WAAW,MAAM,EAAI,YAAY,EAAE,OAAO,MAAM;MAChD,aAAY;MACZ,CAAA;KACQ,CAAA;IAEZ,kBAAC,SAAD;KAAO,WAAU;eAAjB,CACE,kBAAC,SAAD;MACE,MAAK;MACL,SAAS,EAAK;MACd,WAAW,MAAM,EAAI,cAAc,EAAE,OAAO,QAAQ;MACpD,WAAU;MACV,CAAA,EAAA,4BAEI;;IAEP,KAAS,kBAAC,KAAD;KAAG,WAAU;eAAkC;KAAU,CAAA;IAEnE,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,UAAD;MACE,MAAK;MACL,UAAU;MACV,WAAU;gBAHZ,CAKG,IAAS,kBAAC,GAAD,EAAS,WAAU,wBAAyB,CAAA,GAAG,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EACpF,IAAS,iBAAiB,iBACpB;;KACL,CAAA;IACD;;EACI,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"TeamDetailPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/teams/TeamDetailPage.tsx"],"sourcesContent":["/**\n * Team Detail\n * @module atheletebridge/pages/teams\n *\n * Reads a single team (with members + fixtures) from `athTeam`, supports adding\n * and removing members (athAddMember / athRemoveMember), editing and archiving\n * the team (athUpdateTeam via the form / athArchiveTeam), and links into the\n * team's fixtures.\n */\n\nimport { useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport {\n ArrowLeft,\n CalendarDays,\n Edit,\n Archive,\n MapPin,\n Plus,\n Trash2,\n UserPlus,\n Loader2,\n} 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 { FixtureCard } from '../../components/FixtureCard';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { FormField, fieldInputClass } from '../../components/form';\nimport { SPORT_LABELS, MEMBER_ROLE_LABELS } from '../../components/domain';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport {\n useTeam,\n useArchiveTeam,\n useAddMember,\n useRemoveMember,\n} from '../../hooks/useAthleteBridgeApi';\nimport type { AthMemberRole, AthMemberStatus } from '../../types';\n\nconst ROLES = Object.keys(MEMBER_ROLE_LABELS) as AthMemberRole[];\nconst MEMBER_STATUSES: AthMemberStatus[] = [\n 'TRIAL',\n 'ACTIVE',\n 'INJURED',\n 'SUSPENDED',\n 'RELEASED',\n];\n\ninterface AddMemberForm {\n userId: string;\n displayName: string;\n role: AthMemberRole;\n status: AthMemberStatus;\n position: string;\n jerseyNumber: string;\n}\n\nconst EMPTY_MEMBER: AddMemberForm = {\n userId: '',\n displayName: '',\n role: 'PLAYER',\n status: 'ACTIVE',\n position: '',\n jerseyNumber: '',\n};\n\nexport function TeamDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useAthleteBridge();\n\n const team = useTeam(id);\n const archiveTeam = useArchiveTeam();\n const addMember = useAddMember();\n const removeMember = useRemoveMember(id);\n\n const [addOpen, setAddOpen] = useState(false);\n const [memberForm, setMemberForm] = useState<AddMemberForm>(EMPTY_MEMBER);\n const [memberError, setMemberError] = useState<string | null>(null);\n\n if (team.isLoading) {\n return (\n <PageLayout title=\"Team\">\n <LoadingState label=\"Loading team…\" />\n </PageLayout>\n );\n }\n\n if (team.isError || !team.data) {\n return (\n <PageLayout title=\"Team\">\n <ErrorState\n title=\"Team not found\"\n message=\"This team does not exist or you do not have access to it.\"\n onRetry={() => navigateTo('/teams')}\n />\n </PageLayout>\n );\n }\n\n const t = team.data;\n const members = t.members ?? [];\n const fixtures = t.fixtures ?? [];\n\n const handleArchive = async () => {\n if (!id) return;\n await archiveTeam.mutateAsync(id);\n navigateTo('/teams');\n };\n\n const handleAddMember = async (e: React.FormEvent) => {\n e.preventDefault();\n setMemberError(null);\n if (!id) return;\n if (!memberForm.userId.trim() || !memberForm.displayName.trim()) {\n setMemberError('User ID and display name are required.');\n return;\n }\n const jersey = memberForm.jerseyNumber.trim()\n ? Number.parseInt(memberForm.jerseyNumber, 10)\n : undefined;\n if (jersey !== undefined && Number.isNaN(jersey)) {\n setMemberError('Jersey number must be a number.');\n return;\n }\n try {\n await addMember.mutateAsync({\n teamId: id,\n userId: memberForm.userId.trim(),\n displayName: memberForm.displayName.trim(),\n role: memberForm.role,\n status: memberForm.status,\n position: memberForm.position.trim() || undefined,\n jerseyNumber: jersey,\n });\n setMemberForm(EMPTY_MEMBER);\n setAddOpen(false);\n } catch (err) {\n setMemberError(err instanceof Error ? err.message : 'Failed to add member.');\n }\n };\n\n return (\n <PageLayout\n title={t.name}\n description={`${SPORT_LABELS[t.sport]}${t.season ? ` · ${t.season}` : ''}`}\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/teams')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/teams/${t.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 {t.status !== 'ARCHIVED' && (\n <button\n type=\"button\"\n onClick={handleArchive}\n disabled={archiveTeam.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 {archiveTeam.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 */}\n <GlassCard treatment=\"insight\" className=\"mb-8 p-6\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <StatusBadge status={t.status} />\n {t.ageGroup && (\n <span className=\"text-sm text-text-secondary\">Age group: {t.ageGroup}</span>\n )}\n {t.homeVenue && (\n <span className=\"flex items-center gap-1 text-sm text-text-secondary\">\n <MapPin className=\"h-3.5 w-3.5\" /> {t.homeVenue}\n </span>\n )}\n </div>\n {t.description && <p className=\"mt-4 text-sm text-text-secondary\">{t.description}</p>}\n {t.tags.length > 0 && (\n <div className=\"mt-4 flex flex-wrap gap-1.5\">\n {t.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 </GlassCard>\n\n {/* Roster */}\n <section className=\"mb-8\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Roster ({members.length})</h2>\n <button\n type=\"button\"\n onClick={() => {\n setMemberError(null);\n setAddOpen(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 <UserPlus className=\"h-4 w-4\" /> Add member\n </button>\n </div>\n\n {members.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<UserPlus className=\"h-10 w-10\" />}\n title=\"No members yet\"\n description=\"Add players, coaches and staff to build the roster.\"\n />\n ) : (\n <EmphasisPanel>\n <div className=\"divide-y divide-border-seam\">\n {members.map((m) => (\n <div key={m.id} className=\"flex items-center gap-4 py-3\">\n <div className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-sm font-semibold tabular-nums text-text-primary\">\n {m.jerseyNumber ?? m.displayName.charAt(0).toUpperCase()}\n </div>\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {m.displayName}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {MEMBER_ROLE_LABELS[m.role]}\n {m.position ? ` · ${m.position}` : ''}\n </p>\n </div>\n <StatusBadge status={m.status} />\n <button\n type=\"button\"\n onClick={() => void removeMember.mutate(m.id)}\n disabled={removeMember.isPending}\n aria-label={`Remove ${m.displayName}`}\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 )}\n </section>\n\n {/* Fixtures */}\n <section>\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Fixtures ({fixtures.length})</h2>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/fixtures/new?homeTeamId=${t.id}`)}\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\"\n >\n <Plus className=\"h-4 w-4\" /> Schedule\n </button>\n </div>\n {fixtures.length === 0 ? (\n <EmptyState\n illustration=\"empty-calendar\"\n icon={<CalendarDays className=\"h-10 w-10\" />}\n title=\"No fixtures scheduled\"\n description=\"Schedule a match for this team to start tracking results.\"\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {fixtures.map((f) => (\n <FixtureCard\n key={f.id}\n fixture={f}\n onClick={() => navigateTo(`/fixtures/${f.id}`)}\n />\n ))}\n </div>\n )}\n </section>\n\n {/* Add member dialog */}\n <ResponsiveDialog open={addOpen} onOpenChange={setAddOpen}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Add member</ResponsiveDialogTitle>\n </ResponsiveDialogHeader>\n <form onSubmit={handleAddMember} className=\"space-y-4 px-1\">\n <FormField label=\"User ID\" htmlFor=\"userId\" required hint=\"The auth-svc user id.\">\n <input\n id=\"userId\"\n className={fieldInputClass}\n value={memberForm.userId}\n onChange={(e) =>\n setMemberForm((p) => ({ ...p, userId: e.target.value }))\n }\n placeholder=\"user-uuid\"\n />\n </FormField>\n <FormField label=\"Display name\" htmlFor=\"displayName\" required>\n <input\n id=\"displayName\"\n className={fieldInputClass}\n value={memberForm.displayName}\n onChange={(e) =>\n setMemberForm((p) => ({ ...p, displayName: e.target.value }))\n }\n placeholder=\"e.g. Alex Morgan\"\n />\n </FormField>\n <div className=\"grid grid-cols-2 gap-4\">\n <FormField label=\"Role\" htmlFor=\"role\">\n <select\n id=\"role\"\n className={fieldInputClass}\n value={memberForm.role}\n onChange={(e) =>\n setMemberForm((p) => ({ ...p, role: e.target.value as AthMemberRole }))\n }\n >\n {ROLES.map((r) => (\n <option key={r} value={r}>\n {MEMBER_ROLE_LABELS[r]}\n </option>\n ))}\n </select>\n </FormField>\n <FormField label=\"Status\" htmlFor=\"memberStatus\">\n <select\n id=\"memberStatus\"\n className={fieldInputClass}\n value={memberForm.status}\n onChange={(e) =>\n setMemberForm((p) => ({ ...p, status: e.target.value as AthMemberStatus }))\n }\n >\n {MEMBER_STATUSES.map((s) => (\n <option key={s} value={s}>\n {s.charAt(0) + s.slice(1).toLowerCase()}\n </option>\n ))}\n </select>\n </FormField>\n </div>\n <div className=\"grid grid-cols-2 gap-4\">\n <FormField label=\"Position\" htmlFor=\"position\">\n <input\n id=\"position\"\n className={fieldInputClass}\n value={memberForm.position}\n onChange={(e) =>\n setMemberForm((p) => ({ ...p, position: e.target.value }))\n }\n placeholder=\"e.g. Striker\"\n />\n </FormField>\n <FormField label=\"Jersey #\" htmlFor=\"jersey\">\n <input\n id=\"jersey\"\n className={fieldInputClass}\n inputMode=\"numeric\"\n value={memberForm.jerseyNumber}\n onChange={(e) =>\n setMemberForm((p) => ({ ...p, jerseyNumber: e.target.value }))\n }\n placeholder=\"9\"\n />\n </FormField>\n </div>\n {memberError && <p className=\"text-sm text-status-error-text\">{memberError}</p>}\n <ResponsiveDialogFooter>\n <button\n type=\"button\"\n onClick={() => setAddOpen(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={addMember.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 {addMember.isPending && <Loader2 className=\"h-4 w-4 animate-spin\" />}\n Add member\n </button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;AAgDA,IAAM,IAAQ,OAAO,KAAK,EAAmB,EACvC,IAAqC;CACzC;CACA;CACA;CACA;CACA;CACD,EAWK,IAA8B;CAClC,QAAQ;CACR,aAAa;CACb,MAAM;CACN,QAAQ;CACR,UAAU;CACV,cAAc;CACf;AAED,SAAgB,IAAiB;CAC/B,IAAM,EAAE,UAAO,GAA2B,EACpC,EAAE,kBAAe,GAAkB,EAEnC,IAAO,EAAQ,EAAG,EAClB,IAAc,GAAgB,EAC9B,IAAY,GAAc,EAC1B,IAAe,EAAgB,EAAG,EAElC,CAAC,GAAS,KAAc,EAAS,GAAM,EACvC,CAAC,GAAY,KAAiB,EAAwB,EAAa,EACnE,CAAC,GAAa,KAAkB,EAAwB,KAAK;AAEnE,KAAI,EAAK,UACP,QACE,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD,EAAc,OAAM,iBAAkB,CAAA;EAC3B,CAAA;AAIjB,KAAI,EAAK,WAAW,CAAC,EAAK,KACxB,QACE,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD;GACE,OAAM;GACN,SAAQ;GACR,eAAe,EAAW,SAAS;GACnC,CAAA;EACS,CAAA;CAIjB,IAAM,IAAI,EAAK,MACT,IAAU,EAAE,WAAW,EAAE,EACzB,IAAW,EAAE,YAAY,EAAE;AAwCjC,QACE,kBAAC,GAAD;EACE,OAAO,EAAE;EACT,aAAa,GAAG,EAAa,EAAE,SAAS,EAAE,SAAS,MAAM,EAAE,WAAW;EACtE,SACE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAW,SAAS;KACnC,WAAU;eAHZ,CAKE,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA,EAAA,QAC1B;;IACT,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAW,UAAU,EAAE,GAAG,OAAO;KAChD,WAAU;eAHZ,CAKE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,QACrB;;IACR,EAAE,WAAW,cACZ,kBAAC,UAAD;KACE,MAAK;KACL,SA7DU,YAAY;AAC3B,YACL,MAAM,EAAY,YAAY,EAAG,EACjC,EAAW,SAAS;;KA2DV,UAAU,EAAY;KACtB,WAAU;eAJZ;MAMG,EAAY,YACX,kBAAC,GAAD,EAAS,WAAU,wBAAyB,CAAA,GAE5C,kBAAC,GAAD,EAAS,WAAU,WAAY,CAAA;MAC9B;MAAI;MAEA;;IAEP;;YAlCV;GAsCE,kBAAC,GAAD;IAAW,WAAU;IAAU,WAAU;cAAzC;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,GAAD,EAAa,QAAQ,EAAE,QAAU,CAAA;OAChC,EAAE,YACD,kBAAC,QAAD;QAAM,WAAU;kBAAhB,CAA8C,eAAY,EAAE,SAAgB;;OAE7E,EAAE,aACD,kBAAC,QAAD;QAAM,WAAU;kBAAhB;SACE,kBAAC,GAAD,EAAQ,WAAU,eAAgB,CAAA;;SAAE,EAAE;SACjC;;OAEL;;KACL,EAAE,eAAe,kBAAC,KAAD;MAAG,WAAU;gBAAoC,EAAE;MAAgB,CAAA;KACpF,EAAE,KAAK,SAAS,KACf,kBAAC,OAAD;MAAK,WAAU;gBACZ,EAAE,KAAK,KAAK,MACX,kBAAC,QAAD;OAEE,WAAU;iBAET;OACI,EAJA,EAIA,CACP;MACE,CAAA;KAEE;;GAGZ,kBAAC,WAAD;IAAS,WAAU;cAAnB,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,MAAD;MAAI,WAAU;gBAAd;OAAwD;OAAS,EAAQ;OAAO;OAAM;SACtF,kBAAC,UAAD;MACE,MAAK;MACL,eAAe;AAEb,OADA,EAAe,KAAK,EACpB,EAAW,GAAK;;MAElB,WAAU;gBANZ,CAQE,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA,EAAA,cACzB;QACL;QAEL,EAAQ,WAAW,IAClB,kBAAC,GAAD;KACE,cAAa;KACb,MAAM,kBAAC,GAAD,EAAU,WAAU,aAAc,CAAA;KACxC,OAAM;KACN,aAAY;KACZ,CAAA,GAEF,kBAAC,GAAD,EAAA,UACE,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAQ,KAAK,MACZ,kBAAC,OAAD;MAAgB,WAAU;gBAA1B;OACE,kBAAC,OAAD;QAAK,WAAU;kBACZ,EAAE,gBAAgB,EAAE,YAAY,OAAO,EAAE,CAAC,aAAa;QACpD,CAAA;OACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAE;SACD,CAAA,EACJ,kBAAC,KAAD;SAAG,WAAU;mBAAb,CACG,EAAmB,EAAE,OACrB,EAAE,WAAW,MAAM,EAAE,aAAa,GACjC;WACA;;OACN,kBAAC,GAAD,EAAa,QAAQ,EAAE,QAAU,CAAA;OACjC,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,KAAK,EAAa,OAAO,EAAE,GAAG;QAC7C,UAAU,EAAa;QACvB,cAAY,UAAU,EAAE;QACxB,WAAU;kBAEV,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA;QACvB,CAAA;OACL;QAvBI,EAAE,GAuBN,CACN;KACE,CAAA,EACQ,CAAA,CAEV;;GAGV,kBAAC,WAAD,EAAA,UAAA,CACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,MAAD;KAAI,WAAU;eAAd;MAAwD;MAAW,EAAS;MAAO;MAAM;QACzF,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAW,4BAA4B,EAAE,KAAK;KAC7D,WAAU;eAHZ,CAKE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,YACrB;OACL;OACL,EAAS,WAAW,IACnB,kBAAC,GAAD;IACE,cAAa;IACb,MAAM,kBAAC,GAAD,EAAc,WAAU,aAAc,CAAA;IAC5C,OAAM;IACN,aAAY;IACZ,CAAA,GAEF,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAS,KAAK,MACb,kBAAC,GAAD;KAEE,SAAS;KACT,eAAe,EAAW,aAAa,EAAE,KAAK;KAC9C,EAHK,EAAE,GAGP,CACF;IACE,CAAA,CAEA,EAAA,CAAA;GAGV,kBAAC,GAAD;IAAkB,MAAM;IAAS,cAAc;cAC7C,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAuB,cAAkC,CAAA,EAClC,CAAA,EACzB,kBAAC,QAAD;KAAM,UAjMU,OAAO,MAAuB;AAGpD,UAFA,EAAE,gBAAgB,EAClB,EAAe,KAAK,EAChB,CAAC,EAAI;AACT,UAAI,CAAC,EAAW,OAAO,MAAM,IAAI,CAAC,EAAW,YAAY,MAAM,EAAE;AAC/D,SAAe,yCAAyC;AACxD;;MAEF,IAAM,IAAS,EAAW,aAAa,MAAM,GACzC,OAAO,SAAS,EAAW,cAAc,GAAG,GAC5C,KAAA;AACJ,UAAI,MAAW,KAAA,KAAa,OAAO,MAAM,EAAO,EAAE;AAChD,SAAe,kCAAkC;AACjD;;AAEF,UAAI;AAWF,OAVA,MAAM,EAAU,YAAY;QAC1B,QAAQ;QACR,QAAQ,EAAW,OAAO,MAAM;QAChC,aAAa,EAAW,YAAY,MAAM;QAC1C,MAAM,EAAW;QACjB,QAAQ,EAAW;QACnB,UAAU,EAAW,SAAS,MAAM,IAAI,KAAA;QACxC,cAAc;QACf,CAAC,EACF,EAAc,EAAa,EAC3B,EAAW,GAAM;eACV,GAAK;AACZ,SAAe,aAAe,QAAQ,EAAI,UAAU,wBAAwB;;;KAqKvC,WAAU;eAA3C;MACE,kBAAC,GAAD;OAAW,OAAM;OAAU,SAAQ;OAAS,UAAA;OAAS,MAAK;iBACxD,kBAAC,SAAD;QACE,IAAG;QACH,WAAW;QACX,OAAO,EAAW;QAClB,WAAW,MACT,GAAe,OAAO;SAAE,GAAG;SAAG,QAAQ,EAAE,OAAO;SAAO,EAAE;QAE1D,aAAY;QACZ,CAAA;OACQ,CAAA;MACZ,kBAAC,GAAD;OAAW,OAAM;OAAe,SAAQ;OAAc,UAAA;iBACpD,kBAAC,SAAD;QACE,IAAG;QACH,WAAW;QACX,OAAO,EAAW;QAClB,WAAW,MACT,GAAe,OAAO;SAAE,GAAG;SAAG,aAAa,EAAE,OAAO;SAAO,EAAE;QAE/D,aAAY;QACZ,CAAA;OACQ,CAAA;MACZ,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD;QAAW,OAAM;QAAO,SAAQ;kBAC9B,kBAAC,UAAD;SACE,IAAG;SACH,WAAW;SACX,OAAO,EAAW;SAClB,WAAW,MACT,GAAe,OAAO;UAAE,GAAG;UAAG,MAAM,EAAE,OAAO;UAAwB,EAAE;mBAGxE,EAAM,KAAK,MACV,kBAAC,UAAD;UAAgB,OAAO;oBACpB,EAAmB;UACb,EAFI,EAEJ,CACT;SACK,CAAA;QACC,CAAA,EACZ,kBAAC,GAAD;QAAW,OAAM;QAAS,SAAQ;kBAChC,kBAAC,UAAD;SACE,IAAG;SACH,WAAW;SACX,OAAO,EAAW;SAClB,WAAW,MACT,GAAe,OAAO;UAAE,GAAG;UAAG,QAAQ,EAAE,OAAO;UAA0B,EAAE;mBAG5E,EAAgB,KAAK,MACpB,kBAAC,UAAD;UAAgB,OAAO;oBACpB,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,aAAa;UAChC,EAFI,EAEJ,CACT;SACK,CAAA;QACC,CAAA,CACR;;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD;QAAW,OAAM;QAAW,SAAQ;kBAClC,kBAAC,SAAD;SACE,IAAG;SACH,WAAW;SACX,OAAO,EAAW;SAClB,WAAW,MACT,GAAe,OAAO;UAAE,GAAG;UAAG,UAAU,EAAE,OAAO;UAAO,EAAE;SAE5D,aAAY;SACZ,CAAA;QACQ,CAAA,EACZ,kBAAC,GAAD;QAAW,OAAM;QAAW,SAAQ;kBAClC,kBAAC,SAAD;SACE,IAAG;SACH,WAAW;SACX,WAAU;SACV,OAAO,EAAW;SAClB,WAAW,MACT,GAAe,OAAO;UAAE,GAAG;UAAG,cAAc,EAAE,OAAO;UAAO,EAAE;SAEhE,aAAY;SACZ,CAAA;QACQ,CAAA,CACR;;MACL,KAAe,kBAAC,KAAD;OAAG,WAAU;iBAAkC;OAAgB,CAAA;MAC/E,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,EAAW,GAAM;OAChC,WAAU;iBACX;OAEQ,CAAA,EACT,kBAAC,UAAD;OACE,MAAK;OACL,UAAU,EAAU;OACpB,WAAU;iBAHZ,CAKG,EAAU,aAAa,kBAAC,GAAD,EAAS,WAAU,wBAAyB,CAAA,EAAC,aAE9D;SACc,EAAA,CAAA;MACpB;OACiB,EAAA,CAAA;IACT,CAAA;GACR"}
1
+ {"version":3,"file":"TeamDetailPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/teams/TeamDetailPage.tsx"],"sourcesContent":["/**\n * Team Detail\n * @module atheletebridge/pages/teams\n *\n * Reads a single team (with members + fixtures) from `athTeam`, supports adding\n * and removing members (athAddMember / athRemoveMember), editing and archiving\n * the team (athUpdateTeam via the form / athArchiveTeam), and links into the\n * team's fixtures.\n */\n\nimport { useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport {\n ArrowLeft,\n CalendarDays,\n Edit,\n Archive,\n MapPin,\n Plus,\n Trash2,\n UserPlus,\n Loader2,\n} 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 { FixtureCard } from '../../components/FixtureCard';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { FormField, fieldInputClass } from '../../components/form';\nimport { SPORT_LABELS, MEMBER_ROLE_LABELS } from '../../components/domain';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport {\n useTeam,\n useArchiveTeam,\n useAddMember,\n useRemoveMember,\n} from '../../hooks/useAthleteBridgeApi';\nimport type { AthMemberRole, AthMemberStatus } from '../../types';\n\nconst ROLES = Object.keys(MEMBER_ROLE_LABELS) as AthMemberRole[];\nconst MEMBER_STATUSES: AthMemberStatus[] = ['TRIAL', 'ACTIVE', 'INJURED', 'SUSPENDED', 'RELEASED'];\n\ninterface AddMemberForm {\n userId: string;\n displayName: string;\n role: AthMemberRole;\n status: AthMemberStatus;\n position: string;\n jerseyNumber: string;\n}\n\nconst EMPTY_MEMBER: AddMemberForm = {\n userId: '',\n displayName: '',\n role: 'PLAYER',\n status: 'ACTIVE',\n position: '',\n jerseyNumber: '',\n};\n\nexport function TeamDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useAthleteBridge();\n\n const team = useTeam(id);\n const archiveTeam = useArchiveTeam();\n const addMember = useAddMember();\n const removeMember = useRemoveMember(id);\n\n const [addOpen, setAddOpen] = useState(false);\n const [memberForm, setMemberForm] = useState<AddMemberForm>(EMPTY_MEMBER);\n const [memberError, setMemberError] = useState<string | null>(null);\n\n if (team.isLoading) {\n return (\n <PageLayout title=\"Team\">\n <LoadingState label=\"Loading team…\" />\n </PageLayout>\n );\n }\n\n if (team.isError || !team.data) {\n return (\n <PageLayout title=\"Team\">\n <ErrorState\n title=\"Team not found\"\n message=\"This team does not exist or you do not have access to it.\"\n onRetry={() => navigateTo('/teams')}\n />\n </PageLayout>\n );\n }\n\n const t = team.data;\n const members = t.members ?? [];\n const fixtures = t.fixtures ?? [];\n\n const handleArchive = async () => {\n if (!id) return;\n await archiveTeam.mutateAsync(id);\n navigateTo('/teams');\n };\n\n const handleAddMember = async (e: React.FormEvent) => {\n e.preventDefault();\n setMemberError(null);\n if (!id) return;\n if (!memberForm.userId.trim() || !memberForm.displayName.trim()) {\n setMemberError('User ID and display name are required.');\n return;\n }\n const jersey = memberForm.jerseyNumber.trim()\n ? Number.parseInt(memberForm.jerseyNumber, 10)\n : undefined;\n if (jersey !== undefined && Number.isNaN(jersey)) {\n setMemberError('Jersey number must be a number.');\n return;\n }\n try {\n await addMember.mutateAsync({\n teamId: id,\n userId: memberForm.userId.trim(),\n displayName: memberForm.displayName.trim(),\n role: memberForm.role,\n status: memberForm.status,\n position: memberForm.position.trim() || undefined,\n jerseyNumber: jersey,\n });\n setMemberForm(EMPTY_MEMBER);\n setAddOpen(false);\n } catch (err) {\n setMemberError(err instanceof Error ? err.message : 'Failed to add member.');\n }\n };\n\n return (\n <PageLayout\n title={t.name}\n description={`${SPORT_LABELS[t.sport]}${t.season ? ` · ${t.season}` : ''}`}\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/teams')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/teams/${t.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 {t.status !== 'ARCHIVED' && (\n <button\n type=\"button\"\n onClick={handleArchive}\n disabled={archiveTeam.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 {archiveTeam.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 */}\n <GlassCard treatment=\"insight\" className=\"mb-8 p-6\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <StatusBadge status={t.status} />\n {t.ageGroup && (\n <span className=\"text-sm text-text-secondary\">Age group: {t.ageGroup}</span>\n )}\n {t.homeVenue && (\n <span className=\"flex items-center gap-1 text-sm text-text-secondary\">\n <MapPin className=\"h-3.5 w-3.5\" /> {t.homeVenue}\n </span>\n )}\n </div>\n {t.description && <p className=\"mt-4 text-sm text-text-secondary\">{t.description}</p>}\n {t.tags.length > 0 && (\n <div className=\"mt-4 flex flex-wrap gap-1.5\">\n {t.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 </GlassCard>\n\n {/* Roster */}\n <section className=\"mb-8\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Roster ({members.length})</h2>\n <button\n type=\"button\"\n onClick={() => {\n setMemberError(null);\n setAddOpen(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 <UserPlus className=\"h-4 w-4\" /> Add member\n </button>\n </div>\n\n {members.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<UserPlus className=\"h-10 w-10\" />}\n title=\"No members yet\"\n description=\"Add players, coaches and staff to build the roster.\"\n />\n ) : (\n <EmphasisPanel>\n <div className=\"divide-y divide-border-seam\">\n {members.map((m) => (\n <div key={m.id} className=\"flex items-center gap-4 py-3\">\n <div className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-sm font-semibold tabular-nums text-text-primary\">\n {m.jerseyNumber ?? m.displayName.charAt(0).toUpperCase()}\n </div>\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {m.displayName}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {MEMBER_ROLE_LABELS[m.role]}\n {m.position ? ` · ${m.position}` : ''}\n </p>\n </div>\n <StatusBadge status={m.status} />\n <button\n type=\"button\"\n onClick={() => void removeMember.mutate(m.id)}\n disabled={removeMember.isPending}\n aria-label={`Remove ${m.displayName}`}\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 )}\n </section>\n\n {/* Fixtures */}\n <section>\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Fixtures ({fixtures.length})</h2>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/fixtures/new?homeTeamId=${t.id}`)}\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\"\n >\n <Plus className=\"h-4 w-4\" /> Schedule\n </button>\n </div>\n {fixtures.length === 0 ? (\n <EmptyState\n illustration=\"empty-calendar\"\n icon={<CalendarDays className=\"h-10 w-10\" />}\n title=\"No fixtures scheduled\"\n description=\"Schedule a match for this team to start tracking results.\"\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {fixtures.map((f) => (\n <FixtureCard key={f.id} fixture={f} onClick={() => navigateTo(`/fixtures/${f.id}`)} />\n ))}\n </div>\n )}\n </section>\n\n {/* Add member dialog */}\n <ResponsiveDialog open={addOpen} onOpenChange={setAddOpen}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Add member</ResponsiveDialogTitle>\n </ResponsiveDialogHeader>\n <form onSubmit={handleAddMember} className=\"space-y-4 px-1\">\n <FormField label=\"User ID\" htmlFor=\"userId\" required hint=\"The auth-svc user id.\">\n <input\n id=\"userId\"\n className={fieldInputClass}\n value={memberForm.userId}\n onChange={(e) => setMemberForm((p) => ({ ...p, userId: e.target.value }))}\n placeholder=\"user-uuid\"\n />\n </FormField>\n <FormField label=\"Display name\" htmlFor=\"displayName\" required>\n <input\n id=\"displayName\"\n className={fieldInputClass}\n value={memberForm.displayName}\n onChange={(e) => setMemberForm((p) => ({ ...p, displayName: e.target.value }))}\n placeholder=\"e.g. Alex Morgan\"\n />\n </FormField>\n <div className=\"grid grid-cols-2 gap-4\">\n <FormField label=\"Role\" htmlFor=\"role\">\n <select\n id=\"role\"\n className={fieldInputClass}\n value={memberForm.role}\n onChange={(e) =>\n setMemberForm((p) => ({ ...p, role: e.target.value as AthMemberRole }))\n }\n >\n {ROLES.map((r) => (\n <option key={r} value={r}>\n {MEMBER_ROLE_LABELS[r]}\n </option>\n ))}\n </select>\n </FormField>\n <FormField label=\"Status\" htmlFor=\"memberStatus\">\n <select\n id=\"memberStatus\"\n className={fieldInputClass}\n value={memberForm.status}\n onChange={(e) =>\n setMemberForm((p) => ({ ...p, status: e.target.value as AthMemberStatus }))\n }\n >\n {MEMBER_STATUSES.map((s) => (\n <option key={s} value={s}>\n {s.charAt(0) + s.slice(1).toLowerCase()}\n </option>\n ))}\n </select>\n </FormField>\n </div>\n <div className=\"grid grid-cols-2 gap-4\">\n <FormField label=\"Position\" htmlFor=\"position\">\n <input\n id=\"position\"\n className={fieldInputClass}\n value={memberForm.position}\n onChange={(e) => setMemberForm((p) => ({ ...p, position: e.target.value }))}\n placeholder=\"e.g. Striker\"\n />\n </FormField>\n <FormField label=\"Jersey #\" htmlFor=\"jersey\">\n <input\n id=\"jersey\"\n className={fieldInputClass}\n inputMode=\"numeric\"\n value={memberForm.jerseyNumber}\n onChange={(e) => setMemberForm((p) => ({ ...p, jerseyNumber: e.target.value }))}\n placeholder=\"9\"\n />\n </FormField>\n </div>\n {memberError && <p className=\"text-sm text-status-error-text\">{memberError}</p>}\n <ResponsiveDialogFooter>\n <button\n type=\"button\"\n onClick={() => setAddOpen(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={addMember.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 {addMember.isPending && <Loader2 className=\"h-4 w-4 animate-spin\" />}\n Add member\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":"TeamFormPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/teams/TeamFormPage.tsx"],"sourcesContent":["/**\n * Team Create / Edit Form\n * @module atheletebridge/pages/teams\n */\n\nimport { useEffect, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { ArrowLeft, Loader2, Save } from 'lucide-react';\nimport { PageLayout } from '../../components/PageLayout';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { FormField, fieldInputClass } from '../../components/form';\nimport { SPORT_LABELS } from '../../components/domain';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport {\n useTeam,\n useCreateTeam,\n useUpdateTeam,\n} from '../../hooks/useAthleteBridgeApi';\nimport type { AthSport, AthTeamStatus } from '../../types';\n\nconst SPORTS = Object.keys(SPORT_LABELS) as AthSport[];\nconst STATUSES: AthTeamStatus[] = ['FORMING', 'ACTIVE', 'INACTIVE', 'ARCHIVED'];\n\ninterface TeamFormState {\n name: string;\n sport: AthSport;\n status: AthTeamStatus;\n description: string;\n ageGroup: string;\n season: string;\n homeVenue: string;\n tagsText: string;\n}\n\nconst EMPTY_FORM: TeamFormState = {\n name: '',\n sport: 'OTHER',\n status: 'FORMING',\n description: '',\n ageGroup: '',\n season: '',\n homeVenue: '',\n tagsText: '',\n};\n\nexport function TeamFormPage() {\n const { id } = useParams<{ id: string }>();\n const isEdit = Boolean(id);\n const { navigateTo } = useAthleteBridge();\n\n const existing = useTeam(id);\n const createTeam = useCreateTeam();\n const updateTeam = useUpdateTeam();\n\n const [form, setForm] = useState<TeamFormState>(EMPTY_FORM);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n const team = existing.data;\n if (isEdit && team) {\n setForm({\n name: team.name,\n sport: team.sport,\n status: team.status,\n description: team.description ?? '',\n ageGroup: team.ageGroup ?? '',\n season: team.season ?? '',\n homeVenue: team.homeVenue ?? '',\n tagsText: team.tags.join(', '),\n });\n }\n }, [isEdit, existing.data]);\n\n const set = <K extends keyof TeamFormState>(key: K, value: TeamFormState[K]) =>\n setForm((prev) => ({ ...prev, [key]: value }));\n\n const parseTags = (text: string): string[] =>\n text\n .split(',')\n .map((t) => t.trim())\n .filter(Boolean);\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n\n if (!form.name.trim()) {\n setError('Team name is required.');\n return;\n }\n\n const tags = parseTags(form.tagsText);\n\n try {\n if (isEdit && id) {\n await updateTeam.mutateAsync({\n id,\n input: {\n name: form.name.trim(),\n sport: form.sport,\n status: form.status,\n description: form.description.trim() || undefined,\n ageGroup: form.ageGroup.trim() || undefined,\n season: form.season.trim() || undefined,\n homeVenue: form.homeVenue.trim() || undefined,\n tags,\n },\n });\n navigateTo(`/teams/${id}`);\n } else {\n const created = await createTeam.mutateAsync({\n name: form.name.trim(),\n sport: form.sport,\n status: form.status,\n description: form.description.trim() || undefined,\n ageGroup: form.ageGroup.trim() || undefined,\n season: form.season.trim() || undefined,\n homeVenue: form.homeVenue.trim() || undefined,\n tags,\n });\n navigateTo(`/teams/${created.id}`);\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to save team.');\n }\n };\n\n const saving = createTeam.isPending || updateTeam.isPending;\n\n if (isEdit && existing.isLoading) {\n return (\n <PageLayout title=\"Edit team\">\n <LoadingState label=\"Loading team…\" />\n </PageLayout>\n );\n }\n\n if (isEdit && (existing.isError || (!existing.isLoading && !existing.data))) {\n return (\n <PageLayout title=\"Edit team\">\n <ErrorState\n title=\"Team not found\"\n message=\"This team does not exist or you do not have access to it.\"\n onRetry={() => navigateTo('/teams')}\n />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title={isEdit ? 'Edit team' : 'New team'}\n description={isEdit ? 'Update the squad details.' : 'Create a new squad to build a roster.'}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo(isEdit && id ? `/teams/${id}` : '/teams')}\n 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\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Cancel\n </button>\n }\n >\n <form onSubmit={handleSubmit} className=\"max-w-2xl space-y-5\">\n <FormField label=\"Team name\" htmlFor=\"name\" required>\n <input\n id=\"name\"\n className={fieldInputClass}\n value={form.name}\n onChange={(e) => set('name', e.target.value)}\n placeholder=\"e.g. Riverside United U18\"\n />\n </FormField>\n\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <FormField label=\"Sport\" htmlFor=\"sport\">\n <select\n id=\"sport\"\n className={fieldInputClass}\n value={form.sport}\n onChange={(e) => set('sport', e.target.value as AthSport)}\n >\n {SPORTS.map((s) => (\n <option key={s} value={s}>\n {SPORT_LABELS[s]}\n </option>\n ))}\n </select>\n </FormField>\n\n <FormField label=\"Status\" htmlFor=\"status\">\n <select\n id=\"status\"\n className={fieldInputClass}\n value={form.status}\n onChange={(e) => set('status', e.target.value as AthTeamStatus)}\n >\n {STATUSES.map((s) => (\n <option key={s} value={s}>\n {s.charAt(0) + s.slice(1).toLowerCase()}\n </option>\n ))}\n </select>\n </FormField>\n </div>\n\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <FormField label=\"Age group\" htmlFor=\"ageGroup\">\n <input\n id=\"ageGroup\"\n className={fieldInputClass}\n value={form.ageGroup}\n onChange={(e) => set('ageGroup', e.target.value)}\n placeholder=\"e.g. U18\"\n />\n </FormField>\n\n <FormField label=\"Season\" htmlFor=\"season\">\n <input\n id=\"season\"\n className={fieldInputClass}\n value={form.season}\n onChange={(e) => set('season', e.target.value)}\n placeholder=\"e.g. 2025/26\"\n />\n </FormField>\n </div>\n\n <FormField label=\"Home venue\" htmlFor=\"homeVenue\">\n <input\n id=\"homeVenue\"\n className={fieldInputClass}\n value={form.homeVenue}\n onChange={(e) => set('homeVenue', e.target.value)}\n placeholder=\"e.g. Riverside Stadium\"\n />\n </FormField>\n\n <FormField label=\"Description\" htmlFor=\"description\">\n <textarea\n id=\"description\"\n className={fieldInputClass}\n rows={3}\n value={form.description}\n onChange={(e) => set('description', e.target.value)}\n placeholder=\"A short description of the team.\"\n />\n </FormField>\n\n <FormField\n label=\"Tags\"\n htmlFor=\"tags\"\n hint=\"Comma-separated, e.g. youth, development, first-team.\"\n >\n <input\n id=\"tags\"\n className={fieldInputClass}\n value={form.tagsText}\n onChange={(e) => set('tagsText', e.target.value)}\n placeholder=\"youth, development\"\n />\n </FormField>\n\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n\n <div className=\"flex items-center gap-3 pt-2\">\n <button\n type=\"submit\"\n disabled={saving}\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 {saving ? <Loader2 className=\"h-4 w-4 animate-spin\" /> : <Save className=\"h-4 w-4\" />}\n {isEdit ? 'Save changes' : 'Create team'}\n </button>\n </div>\n </form>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"TeamFormPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/teams/TeamFormPage.tsx"],"sourcesContent":["/**\n * Team Create / Edit Form\n * @module atheletebridge/pages/teams\n */\n\nimport { useEffect, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { ArrowLeft, Loader2, Save } from 'lucide-react';\nimport { PageLayout } from '../../components/PageLayout';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { FormField, fieldInputClass } from '../../components/form';\nimport { SPORT_LABELS } from '../../components/domain';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport { useTeam, useCreateTeam, useUpdateTeam } from '../../hooks/useAthleteBridgeApi';\nimport type { AthSport, AthTeamStatus } from '../../types';\n\nconst SPORTS = Object.keys(SPORT_LABELS) as AthSport[];\nconst STATUSES: AthTeamStatus[] = ['FORMING', 'ACTIVE', 'INACTIVE', 'ARCHIVED'];\n\ninterface TeamFormState {\n name: string;\n sport: AthSport;\n status: AthTeamStatus;\n description: string;\n ageGroup: string;\n season: string;\n homeVenue: string;\n tagsText: string;\n}\n\nconst EMPTY_FORM: TeamFormState = {\n name: '',\n sport: 'OTHER',\n status: 'FORMING',\n description: '',\n ageGroup: '',\n season: '',\n homeVenue: '',\n tagsText: '',\n};\n\nexport function TeamFormPage() {\n const { id } = useParams<{ id: string }>();\n const isEdit = Boolean(id);\n const { navigateTo } = useAthleteBridge();\n\n const existing = useTeam(id);\n const createTeam = useCreateTeam();\n const updateTeam = useUpdateTeam();\n\n const [form, setForm] = useState<TeamFormState>(EMPTY_FORM);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n const team = existing.data;\n if (isEdit && team) {\n setForm({\n name: team.name,\n sport: team.sport,\n status: team.status,\n description: team.description ?? '',\n ageGroup: team.ageGroup ?? '',\n season: team.season ?? '',\n homeVenue: team.homeVenue ?? '',\n tagsText: team.tags.join(', '),\n });\n }\n }, [isEdit, existing.data]);\n\n const set = <K extends keyof TeamFormState>(key: K, value: TeamFormState[K]) =>\n setForm((prev) => ({ ...prev, [key]: value }));\n\n const parseTags = (text: string): string[] =>\n text\n .split(',')\n .map((t) => t.trim())\n .filter(Boolean);\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n\n if (!form.name.trim()) {\n setError('Team name is required.');\n return;\n }\n\n const tags = parseTags(form.tagsText);\n\n try {\n if (isEdit && id) {\n await updateTeam.mutateAsync({\n id,\n input: {\n name: form.name.trim(),\n sport: form.sport,\n status: form.status,\n description: form.description.trim() || undefined,\n ageGroup: form.ageGroup.trim() || undefined,\n season: form.season.trim() || undefined,\n homeVenue: form.homeVenue.trim() || undefined,\n tags,\n },\n });\n navigateTo(`/teams/${id}`);\n } else {\n const created = await createTeam.mutateAsync({\n name: form.name.trim(),\n sport: form.sport,\n status: form.status,\n description: form.description.trim() || undefined,\n ageGroup: form.ageGroup.trim() || undefined,\n season: form.season.trim() || undefined,\n homeVenue: form.homeVenue.trim() || undefined,\n tags,\n });\n navigateTo(`/teams/${created.id}`);\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to save team.');\n }\n };\n\n const saving = createTeam.isPending || updateTeam.isPending;\n\n if (isEdit && existing.isLoading) {\n return (\n <PageLayout title=\"Edit team\">\n <LoadingState label=\"Loading team…\" />\n </PageLayout>\n );\n }\n\n if (isEdit && (existing.isError || (!existing.isLoading && !existing.data))) {\n return (\n <PageLayout title=\"Edit team\">\n <ErrorState\n title=\"Team not found\"\n message=\"This team does not exist or you do not have access to it.\"\n onRetry={() => navigateTo('/teams')}\n />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title={isEdit ? 'Edit team' : 'New team'}\n description={isEdit ? 'Update the squad details.' : 'Create a new squad to build a roster.'}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo(isEdit && id ? `/teams/${id}` : '/teams')}\n 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\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Cancel\n </button>\n }\n >\n <form onSubmit={handleSubmit} className=\"max-w-2xl space-y-5\">\n <FormField label=\"Team name\" htmlFor=\"name\" required>\n <input\n id=\"name\"\n className={fieldInputClass}\n value={form.name}\n onChange={(e) => set('name', e.target.value)}\n placeholder=\"e.g. Riverside United U18\"\n />\n </FormField>\n\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <FormField label=\"Sport\" htmlFor=\"sport\">\n <select\n id=\"sport\"\n className={fieldInputClass}\n value={form.sport}\n onChange={(e) => set('sport', e.target.value as AthSport)}\n >\n {SPORTS.map((s) => (\n <option key={s} value={s}>\n {SPORT_LABELS[s]}\n </option>\n ))}\n </select>\n </FormField>\n\n <FormField label=\"Status\" htmlFor=\"status\">\n <select\n id=\"status\"\n className={fieldInputClass}\n value={form.status}\n onChange={(e) => set('status', e.target.value as AthTeamStatus)}\n >\n {STATUSES.map((s) => (\n <option key={s} value={s}>\n {s.charAt(0) + s.slice(1).toLowerCase()}\n </option>\n ))}\n </select>\n </FormField>\n </div>\n\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <FormField label=\"Age group\" htmlFor=\"ageGroup\">\n <input\n id=\"ageGroup\"\n className={fieldInputClass}\n value={form.ageGroup}\n onChange={(e) => set('ageGroup', e.target.value)}\n placeholder=\"e.g. U18\"\n />\n </FormField>\n\n <FormField label=\"Season\" htmlFor=\"season\">\n <input\n id=\"season\"\n className={fieldInputClass}\n value={form.season}\n onChange={(e) => set('season', e.target.value)}\n placeholder=\"e.g. 2025/26\"\n />\n </FormField>\n </div>\n\n <FormField label=\"Home venue\" htmlFor=\"homeVenue\">\n <input\n id=\"homeVenue\"\n className={fieldInputClass}\n value={form.homeVenue}\n onChange={(e) => set('homeVenue', e.target.value)}\n placeholder=\"e.g. Riverside Stadium\"\n />\n </FormField>\n\n <FormField label=\"Description\" htmlFor=\"description\">\n <textarea\n id=\"description\"\n className={fieldInputClass}\n rows={3}\n value={form.description}\n onChange={(e) => set('description', e.target.value)}\n placeholder=\"A short description of the team.\"\n />\n </FormField>\n\n <FormField\n label=\"Tags\"\n htmlFor=\"tags\"\n hint=\"Comma-separated, e.g. youth, development, first-team.\"\n >\n <input\n id=\"tags\"\n className={fieldInputClass}\n value={form.tagsText}\n onChange={(e) => set('tagsText', e.target.value)}\n placeholder=\"youth, development\"\n />\n </FormField>\n\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n\n <div className=\"flex items-center gap-3 pt-2\">\n <button\n type=\"submit\"\n disabled={saving}\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 {saving ? <Loader2 className=\"h-4 w-4 animate-spin\" /> : <Save className=\"h-4 w-4\" />}\n {isEdit ? 'Save changes' : 'Create team'}\n </button>\n </div>\n </form>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"TeamsListPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/teams/TeamsListPage.tsx"],"sourcesContent":["/**\n * Teams List\n * @module atheletebridge/pages/teams\n */\n\nimport { useState } from 'react';\nimport { Plus, Users } from 'lucide-react';\nimport { PageLayout } from '../../components/PageLayout';\nimport { TeamCard } from '../../components/TeamCard';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { FilterSelect } from '../../components/FilterSelect';\nimport { CursorPager } from '../../components/CursorPager';\nimport { SPORT_LABELS } from '../../components/domain';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport { useTeams } from '../../hooks/useAthleteBridgeApi';\nimport type { AthSport, AthTeamStatus } 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 STATUS_OPTS: ReadonlyArray<{ value: AthTeamStatus; label: string }> = [\n { value: 'FORMING', label: 'Forming' },\n { value: 'ACTIVE', label: 'Active' },\n { value: 'INACTIVE', label: 'Inactive' },\n { value: 'ARCHIVED', label: 'Archived' },\n];\n\nexport function TeamsListPage() {\n const { navigateTo } = useAthleteBridge();\n const [sport, setSport] = useState<AthSport | ''>('');\n const [status, setStatus] = useState<AthTeamStatus | ''>('');\n const [page, setPage] = useState(0);\n\n const teams = useTeams({\n sport: sport || undefined,\n status: status || undefined,\n pagination: { limit: PAGE_SIZE, offset: page * PAGE_SIZE },\n });\n\n const items = teams.data?.items ?? [];\n const hasMore = teams.data?.pageInfo.hasMore ?? false;\n\n return (\n <PageLayout\n title=\"Teams\"\n description=\"Squads, club sides, academy groups and esports rosters.\"\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/teams/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 team\n </button>\n }\n >\n <div className=\"mb-6 flex flex-wrap items-center gap-3\">\n <FilterSelect\n ariaLabel=\"Filter by sport\"\n allLabel=\"All sports\"\n value={sport}\n onChange={(v) => {\n setSport(v);\n setPage(0);\n }}\n options={SPORT_OPTS}\n />\n <FilterSelect\n ariaLabel=\"Filter by status\"\n allLabel=\"All statuses\"\n value={status}\n onChange={(v) => {\n setStatus(v);\n setPage(0);\n }}\n options={STATUS_OPTS}\n />\n </div>\n\n {teams.isLoading ? (\n <LoadingState label=\"Loading teams…\" />\n ) : teams.isError ? (\n <ErrorState onRetry={() => void teams.refetch()} />\n ) : items.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Users className=\"h-10 w-10\" />}\n title=\"No teams found\"\n description={\n sport || status\n ? 'Try clearing your filters to see all teams.'\n : 'Create your first team to build a roster and start scheduling fixtures.'\n }\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/teams/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 team\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((team) => (\n <TeamCard\n key={team.id}\n team={team}\n onClick={() => navigateTo(`/teams/${team.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":";;;;;;;;;;;;;AAkBA,IAAM,IAAY,IAEZ,IAAc,OAAO,KAAK,EAAa,CAAgB,KAAK,OAAW;CAC3E;CACA,OAAO,EAAa;CACrB,EAAE,EAEG,IAAsE;CAC1E;EAAE,OAAO;EAAW,OAAO;EAAW;CACtC;EAAE,OAAO;EAAU,OAAO;EAAU;CACpC;EAAE,OAAO;EAAY,OAAO;EAAY;CACxC;EAAE,OAAO;EAAY,OAAO;EAAY;CACzC;AAED,SAAgB,IAAgB;CAC9B,IAAM,EAAE,kBAAe,GAAkB,EACnC,CAAC,GAAO,KAAY,EAAwB,GAAG,EAC/C,CAAC,GAAQ,KAAa,EAA6B,GAAG,EACtD,CAAC,GAAM,KAAW,EAAS,EAAE,EAE7B,IAAQ,EAAS;EACrB,OAAO,KAAS,KAAA;EAChB,QAAQ,KAAU,KAAA;EAClB,YAAY;GAAE,OAAO;GAAW,QAAQ,IAAO;GAAW;EAC3D,CAAC,EAEI,IAAQ,EAAM,MAAM,SAAS,EAAE,EAC/B,IAAU,EAAM,MAAM,SAAS,WAAW;AAEhD,QACE,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,SACE,kBAAC,UAAD;GACE,MAAK;GACL,eAAe,EAAW,aAAa;GACvC,WAAU;aAHZ,CAKE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,YACrB;;YAVb,CAaE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD;IACE,WAAU;IACV,UAAS;IACT,OAAO;IACP,WAAW,MAAM;AAEf,KADA,EAAS,EAAE,EACX,EAAQ,EAAE;;IAEZ,SAAS;IACT,CAAA,EACF,kBAAC,GAAD;IACE,WAAU;IACV,UAAS;IACT,OAAO;IACP,WAAW,MAAM;AAEf,KADA,EAAU,EAAE,EACZ,EAAQ,EAAE;;IAEZ,SAAS;IACT,CAAA,CACE;MAEL,EAAM,YACL,kBAAC,GAAD,EAAc,OAAM,kBAAmB,CAAA,GACrC,EAAM,UACR,kBAAC,GAAD,EAAY,eAAe,KAAK,EAAM,SAAS,EAAI,CAAA,GACjD,EAAM,WAAW,IACnB,kBAAC,GAAD;GACE,cAAa;GACb,MAAM,kBAAC,GAAD,EAAO,WAAU,aAAc,CAAA;GACrC,OAAM;GACN,aACE,KAAS,IACL,gDACA;GAEN,QACE,kBAAC,UAAD;IACE,MAAK;IACL,eAAe,EAAW,aAAa;IACvC,WAAU;cACX;IAEQ,CAAA;GAEX,CAAA,GAEF,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAM,KAAK,MACV,kBAAC,GAAD;IAEQ;IACN,eAAe,EAAW,UAAU,EAAK,KAAK;IAC9C,EAHK,EAAK,GAGV,CACF;GACE,CAAA,EACN,kBAAC,GAAD;GACE,WAAU;GACJ;GACG;GACT,cAAc,GAAS,MAAM,KAAK,IAAI,GAAG,IAAI,EAAE,CAAC;GAChD,cAAc,GAAS,MAAM,IAAI,EAAE;GACnC,CAAA,CACD,EAAA,CAAA,CAEM"}
1
+ {"version":3,"file":"TeamsListPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/teams/TeamsListPage.tsx"],"sourcesContent":["/**\n * Teams List\n * @module atheletebridge/pages/teams\n */\n\nimport { useState } from 'react';\nimport { Plus, Users } from 'lucide-react';\nimport { PageLayout } from '../../components/PageLayout';\nimport { TeamCard } from '../../components/TeamCard';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { FilterSelect } from '../../components/FilterSelect';\nimport { CursorPager } from '../../components/CursorPager';\nimport { SPORT_LABELS } from '../../components/domain';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport { useTeams } from '../../hooks/useAthleteBridgeApi';\nimport type { AthSport, AthTeamStatus } 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 STATUS_OPTS: ReadonlyArray<{ value: AthTeamStatus; label: string }> = [\n { value: 'FORMING', label: 'Forming' },\n { value: 'ACTIVE', label: 'Active' },\n { value: 'INACTIVE', label: 'Inactive' },\n { value: 'ARCHIVED', label: 'Archived' },\n];\n\nexport function TeamsListPage() {\n const { navigateTo } = useAthleteBridge();\n const [sport, setSport] = useState<AthSport | ''>('');\n const [status, setStatus] = useState<AthTeamStatus | ''>('');\n const [page, setPage] = useState(0);\n\n const teams = useTeams({\n sport: sport || undefined,\n status: status || undefined,\n pagination: { limit: PAGE_SIZE, offset: page * PAGE_SIZE },\n });\n\n const items = teams.data?.items ?? [];\n const hasMore = teams.data?.pageInfo.hasMore ?? false;\n\n return (\n <PageLayout\n title=\"Teams\"\n description=\"Squads, club sides, academy groups and esports rosters.\"\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/teams/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 team\n </button>\n }\n >\n <div className=\"mb-6 flex flex-wrap items-center gap-3\">\n <FilterSelect\n ariaLabel=\"Filter by sport\"\n allLabel=\"All sports\"\n value={sport}\n onChange={(v) => {\n setSport(v);\n setPage(0);\n }}\n options={SPORT_OPTS}\n />\n <FilterSelect\n ariaLabel=\"Filter by status\"\n allLabel=\"All statuses\"\n value={status}\n onChange={(v) => {\n setStatus(v);\n setPage(0);\n }}\n options={STATUS_OPTS}\n />\n </div>\n\n {teams.isLoading ? (\n <LoadingState label=\"Loading teams…\" />\n ) : teams.isError ? (\n <ErrorState onRetry={() => void teams.refetch()} />\n ) : items.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Users className=\"h-10 w-10\" />}\n title=\"No teams found\"\n description={\n sport || status\n ? 'Try clearing your filters to see all teams.'\n : 'Create your first team to build a roster and start scheduling fixtures.'\n }\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/teams/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 team\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((team) => (\n <TeamCard key={team.id} team={team} onClick={() => navigateTo(`/teams/${team.id}`)} />\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":";;;;;;;;;;;;;AAkBA,IAAM,IAAY,IAEZ,IAAc,OAAO,KAAK,EAAa,CAAgB,KAAK,OAAW;CAC3E;CACA,OAAO,EAAa;CACrB,EAAE,EAEG,IAAsE;CAC1E;EAAE,OAAO;EAAW,OAAO;EAAW;CACtC;EAAE,OAAO;EAAU,OAAO;EAAU;CACpC;EAAE,OAAO;EAAY,OAAO;EAAY;CACxC;EAAE,OAAO;EAAY,OAAO;EAAY;CACzC;AAED,SAAgB,IAAgB;CAC9B,IAAM,EAAE,kBAAe,GAAkB,EACnC,CAAC,GAAO,KAAY,EAAwB,GAAG,EAC/C,CAAC,GAAQ,KAAa,EAA6B,GAAG,EACtD,CAAC,GAAM,KAAW,EAAS,EAAE,EAE7B,IAAQ,EAAS;EACrB,OAAO,KAAS,KAAA;EAChB,QAAQ,KAAU,KAAA;EAClB,YAAY;GAAE,OAAO;GAAW,QAAQ,IAAO;GAAW;EAC3D,CAAC,EAEI,IAAQ,EAAM,MAAM,SAAS,EAAE,EAC/B,IAAU,EAAM,MAAM,SAAS,WAAW;AAEhD,QACE,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,SACE,kBAAC,UAAD;GACE,MAAK;GACL,eAAe,EAAW,aAAa;GACvC,WAAU;aAHZ,CAKE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,YACrB;;YAVb,CAaE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD;IACE,WAAU;IACV,UAAS;IACT,OAAO;IACP,WAAW,MAAM;AAEf,KADA,EAAS,EAAE,EACX,EAAQ,EAAE;;IAEZ,SAAS;IACT,CAAA,EACF,kBAAC,GAAD;IACE,WAAU;IACV,UAAS;IACT,OAAO;IACP,WAAW,MAAM;AAEf,KADA,EAAU,EAAE,EACZ,EAAQ,EAAE;;IAEZ,SAAS;IACT,CAAA,CACE;MAEL,EAAM,YACL,kBAAC,GAAD,EAAc,OAAM,kBAAmB,CAAA,GACrC,EAAM,UACR,kBAAC,GAAD,EAAY,eAAe,KAAK,EAAM,SAAS,EAAI,CAAA,GACjD,EAAM,WAAW,IACnB,kBAAC,GAAD;GACE,cAAa;GACb,MAAM,kBAAC,GAAD,EAAO,WAAU,aAAc,CAAA;GACrC,OAAM;GACN,aACE,KAAS,IACL,gDACA;GAEN,QACE,kBAAC,UAAD;IACE,MAAK;IACL,eAAe,EAAW,aAAa;IACvC,WAAU;cACX;IAEQ,CAAA;GAEX,CAAA,GAEF,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAM,KAAK,MACV,kBAAC,GAAD;IAA8B;IAAM,eAAe,EAAW,UAAU,EAAK,KAAK;IAAI,EAAvE,EAAK,GAAkE,CACtF;GACE,CAAA,EACN,kBAAC,GAAD;GACE,WAAU;GACJ;GACG;GACT,cAAc,GAAS,MAAM,KAAK,IAAI,GAAG,IAAI,EAAE,CAAC;GAChD,cAAc,GAAS,MAAM,IAAI,EAAE;GACnC,CAAA,CACD,EAAA,CAAA,CAEM"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-atheletebridge",
3
- "version": "2026.628.1",
3
+ "version": "2026.629.1",
4
4
  "description": "Burdenoff Kadaikodi Microfrontend - Mobile services marketplace platform for connecting service providers with customers",
5
5
  "type": "module",
6
6
  "files": [