@burdenoff/microfe-atheletebridge 2026.827.1 → 2026.829.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/atheletebridge/components/LoadingState.d.ts.map +1 -1
- package/dist/atheletebridge/components/LoadingState.js +39 -32
- package/dist/atheletebridge/components/LoadingState.js.map +1 -1
- package/dist/atheletebridge/components/TeamCard.d.ts.map +1 -1
- package/dist/atheletebridge/components/TeamCard.js +45 -49
- package/dist/atheletebridge/components/TeamCard.js.map +1 -1
- package/dist/atheletebridge/pages/DashboardPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/DashboardPage.js +56 -24
- package/dist/atheletebridge/pages/DashboardPage.js.map +1 -1
- package/dist/atheletebridge/pages/analytics/CoachAnalyticsPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/analytics/CoachAnalyticsPage.js +58 -56
- package/dist/atheletebridge/pages/analytics/CoachAnalyticsPage.js.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js +110 -106
- package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixturesListPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixturesListPage.js +80 -71
- package/dist/atheletebridge/pages/fixtures/FixturesListPage.js.map +1 -1
- package/dist/atheletebridge/pages/onboarding/OnboardingPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/onboarding/OnboardingPage.js +244 -210
- package/dist/atheletebridge/pages/onboarding/OnboardingPage.js.map +1 -1
- package/dist/atheletebridge/pages/performance/PerformancePage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/performance/PerformancePage.js +197 -148
- package/dist/atheletebridge/pages/performance/PerformancePage.js.map +1 -1
- package/dist/atheletebridge/pages/programs/ProgramDetailPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/programs/ProgramDetailPage.js +169 -143
- package/dist/atheletebridge/pages/programs/ProgramDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/programs/ProgramsListPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/programs/ProgramsListPage.js +77 -72
- package/dist/atheletebridge/pages/programs/ProgramsListPage.js.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionDetailPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionDetailPage.js +104 -86
- package/dist/atheletebridge/pages/sessions/SessionDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionsListPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionsListPage.js +54 -49
- package/dist/atheletebridge/pages/sessions/SessionsListPage.js.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamDetailPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamDetailPage.js +286 -267
- package/dist/atheletebridge/pages/teams/TeamDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamFormPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamFormPage.js +185 -180
- package/dist/atheletebridge/pages/teams/TeamFormPage.js.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamsListPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamsListPage.js +70 -74
- package/dist/atheletebridge/pages/teams/TeamsListPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"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';\nimport { ArrowLeft, CalendarDays, Loader2, MapPin, Save, Search, X } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { FormField, fieldInputClass } from '../../components/form';\nimport { UserAvatar } from '../../components/UserAvatar';\nimport { SPORT_ICONS, SPORT_LABELS } from '../../components/domain';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport {\n useTeam,\n useCreateTeam,\n useUpdateTeam,\n useUserDirectory,\n useUserSearch,\n directoryUserName,\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'];\nconst DESCRIPTION_LIMIT = 300;\n\n/** Faint sport glyph rendered inside the Sport select trigger. */\nfunction SportGlyph({ sport }: { sport: AthSport }) {\n const Icon = SPORT_ICONS[sport];\n return (\n <Icon\n className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-muted\"\n aria-hidden=\"true\"\n />\n );\n}\n\ninterface HeadCoachPickerProps {\n userId: string;\n displayName: string;\n onSelect: (value: { userId: string; displayName: string }) => void;\n}\n\n/**\n * Optional head-coach picker — a lightweight directory search wired to the real\n * `useUserSearch` hook, degrading to a paste-a-user-id field when the directory\n * is unavailable. Labelled correctly for the head-coach role.\n */\nfunction HeadCoachPicker({ userId, displayName, onSelect }: HeadCoachPickerProps) {\n const [term, setTerm] = useState('');\n const search = useUserSearch(term);\n const results = search.data ?? [];\n const active = term.trim().length >= 2;\n const unavailable = search.isError;\n\n if (userId) {\n return (\n <FormField label=\"Head coach\" hint=\"Optional — the coach who leads this squad.\">\n <div className=\"flex items-center gap-2.5 rounded-lg border border-border-seam bg-bg-surface px-3 py-2\">\n <UserAvatar name={displayName} id={userId} size=\"sm\" />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {displayName || 'Selected coach'}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">{userId}</p>\n </div>\n <button\n type=\"button\"\n aria-label=\"Clear head coach\"\n onClick={() => onSelect({ userId: '', displayName: '' })}\n className=\"rounded-md p-1 text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n </div>\n </FormField>\n );\n }\n\n if (unavailable) {\n return (\n <FormField\n label=\"Head coach\"\n htmlFor=\"headCoachId\"\n hint=\"Optional — directory unavailable here; paste the auth-svc user id.\"\n >\n <input\n id=\"headCoachId\"\n className={fieldInputClass}\n value={userId}\n onChange={(e) => onSelect({ userId: e.target.value, displayName })}\n placeholder=\"user-uuid\"\n />\n </FormField>\n );\n }\n\n return (\n <FormField label=\"Head coach\" hint=\"Optional — the coach who leads this squad.\">\n <div className=\"space-y-2\">\n <div className=\"relative\">\n <Search\n className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-muted\"\n aria-hidden=\"true\"\n />\n <input\n className={`${fieldInputClass} pl-9`}\n value={term}\n onChange={(e) => setTerm(e.target.value)}\n placeholder=\"Search coaches by name or email…\"\n aria-label=\"Search for a head coach\"\n />\n {search.isFetching && (\n <Loader2 className=\"absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 animate-spin text-text-muted\" />\n )}\n </div>\n {active && !search.isFetching && results.length === 0 && (\n <p className=\"text-xs text-text-secondary\">No users match “{term.trim()}”.</p>\n )}\n {results.length > 0 && (\n <ul className=\"max-h-56 divide-y divide-border-seam overflow-auto rounded-lg border border-border-seam bg-bg-surface\">\n {results.map((user) => (\n <li key={user.id}>\n <button\n type=\"button\"\n onClick={() => {\n onSelect({ userId: user.id, displayName: directoryUserName(user) });\n setTerm('');\n }}\n className=\"flex w-full items-center gap-2.5 px-3 py-2 text-left transition-colors hover:bg-bg-sunken\"\n >\n <UserAvatar name={directoryUserName(user)} id={user.id} size=\"sm\" />\n <span className=\"min-w-0 flex-1\">\n <span className=\"block truncate text-sm font-medium text-text-primary\">\n {directoryUserName(user)}\n </span>\n <span className=\"block truncate text-xs text-text-secondary\">{user.email}</span>\n </span>\n </button>\n </li>\n ))}\n </ul>\n )}\n </div>\n </FormField>\n );\n}\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 headCoachId: string;\n headCoachName: string;\n tags: string[];\n}\n\nconst EMPTY_FORM: TeamFormState = {\n name: '',\n sport: 'OTHER',\n status: 'FORMING',\n description: '',\n ageGroup: '',\n season: '',\n homeVenue: '',\n headCoachId: '',\n headCoachName: '',\n tags: [],\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 [tagInput, setTagInput] = useState('');\n const [error, setError] = useState<string | null>(null);\n\n // Resolve the existing head coach's display name so the picker shows it on edit.\n const coachDir = useUserDirectory(isEdit ? [existing.data?.headCoachId] : []);\n const resolvedCoachName = coachDir.nameOf(existing.data?.headCoachId ?? undefined);\n\n useEffect(() => {\n const team = existing.data;\n if (isEdit && team) {\n setForm((prev) => ({\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 headCoachId: team.headCoachId ?? '',\n headCoachName: resolvedCoachName ?? prev.headCoachName,\n tags: team.tags,\n }));\n }\n }, [isEdit, existing.data, resolvedCoachName]);\n\n const set = <K extends keyof TeamFormState>(key: K, value: TeamFormState[K]) =>\n setForm((prev) => ({ ...prev, [key]: value }));\n\n const commitTag = (raw: string) => {\n const tag = raw.trim();\n if (!tag) return;\n setForm((prev) =>\n prev.tags.some((t) => t.toLowerCase() === tag.toLowerCase())\n ? prev\n : { ...prev, tags: [...prev.tags, tag] }\n );\n setTagInput('');\n };\n\n const removeTag = (tag: string) =>\n setForm((prev) => ({ ...prev, tags: prev.tags.filter((t) => t !== tag) }));\n\n const handleTagKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Enter' || e.key === ',') {\n e.preventDefault();\n commitTag(tagInput);\n } else if (e.key === 'Backspace' && !tagInput && form.tags.length) {\n removeTag(form.tags[form.tags.length - 1]);\n }\n };\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 // Fold any half-typed tag still in the input into the submission.\n const pending = tagInput.trim();\n const tags =\n pending && !form.tags.some((t) => t.toLowerCase() === pending.toLowerCase())\n ? [...form.tags, pending]\n : form.tags;\n\n const shared = {\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 headCoachId: form.headCoachId.trim() || undefined,\n tags,\n };\n\n try {\n if (isEdit && id) {\n await updateTeam.mutateAsync({ id, input: shared });\n navigateTo(`/teams/${id}`);\n } else {\n const created = await createTeam.mutateAsync(shared);\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=\"mx-auto max-w-3xl\">\n <GlassCard treatment=\"insight\" className=\"p-6 sm:p-8\">\n <div className=\"space-y-5\">\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\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 <FormField label=\"Sport\" htmlFor=\"sport\">\n <div className=\"relative\">\n <SportGlyph sport={form.sport} />\n <select\n id=\"sport\"\n className={`${fieldInputClass} pl-9`}\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 </div>\n </FormField>\n </div>\n\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\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\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 </div>\n\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <FormField label=\"Season\" htmlFor=\"season\">\n <div className=\"relative\">\n <CalendarDays\n className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-muted\"\n aria-hidden=\"true\"\n />\n <input\n id=\"season\"\n className={`${fieldInputClass} pl-9`}\n value={form.season}\n onChange={(e) => set('season', e.target.value)}\n placeholder=\"e.g. 2025/26\"\n />\n </div>\n </FormField>\n\n <FormField label=\"Home venue\" htmlFor=\"homeVenue\">\n <div className=\"relative\">\n <MapPin\n className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-muted\"\n aria-hidden=\"true\"\n />\n <input\n id=\"homeVenue\"\n className={`${fieldInputClass} pl-9`}\n value={form.homeVenue}\n onChange={(e) => set('homeVenue', e.target.value)}\n placeholder=\"e.g. Riverside Stadium\"\n />\n </div>\n </FormField>\n </div>\n\n <HeadCoachPicker\n userId={form.headCoachId}\n displayName={form.headCoachName}\n onSelect={({ userId, displayName }) =>\n setForm((prev) => ({\n ...prev,\n headCoachId: userId,\n headCoachName: displayName,\n }))\n }\n />\n\n <FormField label=\"Description\" htmlFor=\"description\">\n <textarea\n id=\"description\"\n className={fieldInputClass}\n rows={3}\n maxLength={DESCRIPTION_LIMIT}\n value={form.description}\n onChange={(e) => set('description', e.target.value)}\n placeholder=\"A short description of the team.\"\n />\n <div className=\"mt-1 text-right text-xs tabular-nums text-text-muted\">\n {form.description.length}/{DESCRIPTION_LIMIT}\n </div>\n </FormField>\n\n <FormField\n label=\"Tags\"\n htmlFor=\"tags\"\n hint=\"Type a tag and press Enter to add it; click × to remove.\"\n >\n <div className=\"flex flex-wrap items-center gap-1.5 rounded-lg border border-border-seam bg-bg-surface px-2 py-1.5 focus-within:ring-2 focus-within:ring-[var(--color-focus-ring)]\">\n {form.tags.map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center gap-1 rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\"\n >\n {tag}\n <button\n type=\"button\"\n onClick={() => removeTag(tag)}\n aria-label={`Remove tag ${tag}`}\n className=\"rounded-full text-text-muted transition-colors hover:text-status-error-text\"\n >\n <X className=\"h-3 w-3\" />\n </button>\n </span>\n ))}\n <input\n id=\"tags\"\n className=\"min-w-[8rem] flex-1 bg-transparent px-1 py-0.5 text-sm text-text-primary placeholder:text-text-muted focus:outline-none\"\n value={tagInput}\n onChange={(e) => setTagInput(e.target.value)}\n onKeyDown={handleTagKeyDown}\n onBlur={() => commitTag(tagInput)}\n placeholder={form.tags.length ? 'Add tag and press Enter…' : 'youth, development'}\n />\n </div>\n </FormField>\n\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n </div>\n\n <div className=\"mt-6 flex items-center justify-end gap-3 border-t border-border-seam pt-5\">\n <button\n type=\"button\"\n onClick={() => navigateTo(isEdit && id ? `/teams/${id}` : '/teams')}\n className=\"rounded-lg px-4 py-2 text-sm font-medium text-text-secondary transition-colors hover:text-text-primary\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={saving}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-5 py-2 text-sm font-semibold 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 </GlassCard>\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';\nimport { ArrowLeft, CalendarDays, Loader2, MapPin, Save, Search, X } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { FormField, fieldInputClass } from '../../components/form';\nimport { UserAvatar } from '../../components/UserAvatar';\nimport { SPORT_ICONS, SPORT_LABELS } from '../../components/domain';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport {\n useTeam,\n useCreateTeam,\n useUpdateTeam,\n useUserDirectory,\n useUserSearch,\n directoryUserName,\n} from '../../hooks/useAthleteBridgeApi';\nimport { useAbTranslate } from '../../i18n';\nimport type { AthSport, AthTeamStatus } from '../../types';\n\nconst SPORTS = Object.keys(SPORT_LABELS) as AthSport[];\nconst STATUSES: AthTeamStatus[] = ['FORMING', 'ACTIVE', 'INACTIVE', 'ARCHIVED'];\nconst DESCRIPTION_LIMIT = 300;\n\n/** Faint sport glyph rendered inside the Sport select trigger. */\nfunction SportGlyph({ sport }: { sport: AthSport }) {\n const Icon = SPORT_ICONS[sport];\n return (\n <Icon\n className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-muted\"\n aria-hidden=\"true\"\n />\n );\n}\n\ninterface HeadCoachPickerProps {\n userId: string;\n displayName: string;\n onSelect: (value: { userId: string; displayName: string }) => void;\n}\n\n/**\n * Optional head-coach picker — a lightweight directory search wired to the real\n * `useUserSearch` hook, degrading to a paste-a-user-id field when the directory\n * is unavailable. Labelled correctly for the head-coach role.\n */\nfunction HeadCoachPicker({ userId, displayName, onSelect }: HeadCoachPickerProps) {\n const [term, setTerm] = useState('');\n const search = useUserSearch(term);\n const results = search.data ?? [];\n const active = term.trim().length >= 2;\n const unavailable = search.isError;\n\n if (userId) {\n return (\n <FormField label=\"Head coach\" hint=\"Optional — the coach who leads this squad.\">\n <div className=\"flex items-center gap-2.5 rounded-lg border border-border-seam bg-bg-surface px-3 py-2\">\n <UserAvatar name={displayName} id={userId} size=\"sm\" />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {displayName || 'Selected coach'}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">{userId}</p>\n </div>\n <button\n type=\"button\"\n aria-label=\"Clear head coach\"\n onClick={() => onSelect({ userId: '', displayName: '' })}\n className=\"rounded-md p-1 text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n </div>\n </FormField>\n );\n }\n\n if (unavailable) {\n return (\n <FormField\n label=\"Head coach\"\n htmlFor=\"headCoachId\"\n hint=\"Optional — directory unavailable here; paste the auth-svc user id.\"\n >\n <input\n id=\"headCoachId\"\n className={fieldInputClass}\n value={userId}\n onChange={(e) => onSelect({ userId: e.target.value, displayName })}\n placeholder=\"user-uuid\"\n />\n </FormField>\n );\n }\n\n return (\n <FormField label=\"Head coach\" hint=\"Optional — the coach who leads this squad.\">\n <div className=\"space-y-2\">\n <div className=\"relative\">\n <Search\n className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-muted\"\n aria-hidden=\"true\"\n />\n <input\n className={`${fieldInputClass} pl-9`}\n value={term}\n onChange={(e) => setTerm(e.target.value)}\n placeholder=\"Search coaches by name or email…\"\n aria-label=\"Search for a head coach\"\n />\n {search.isFetching && (\n <Loader2 className=\"absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 animate-spin text-text-muted\" />\n )}\n </div>\n {active && !search.isFetching && results.length === 0 && (\n <p className=\"text-xs text-text-secondary\">No users match “{term.trim()}”.</p>\n )}\n {results.length > 0 && (\n <ul className=\"max-h-56 divide-y divide-border-seam overflow-auto rounded-lg border border-border-seam bg-bg-surface\">\n {results.map((user) => (\n <li key={user.id}>\n <button\n type=\"button\"\n onClick={() => {\n onSelect({ userId: user.id, displayName: directoryUserName(user) });\n setTerm('');\n }}\n className=\"flex w-full items-center gap-2.5 px-3 py-2 text-left transition-colors hover:bg-bg-sunken\"\n >\n <UserAvatar name={directoryUserName(user)} id={user.id} size=\"sm\" />\n <span className=\"min-w-0 flex-1\">\n <span className=\"block truncate text-sm font-medium text-text-primary\">\n {directoryUserName(user)}\n </span>\n <span className=\"block truncate text-xs text-text-secondary\">{user.email}</span>\n </span>\n </button>\n </li>\n ))}\n </ul>\n )}\n </div>\n </FormField>\n );\n}\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 headCoachId: string;\n headCoachName: string;\n tags: string[];\n}\n\nconst EMPTY_FORM: TeamFormState = {\n name: '',\n sport: 'OTHER',\n status: 'FORMING',\n description: '',\n ageGroup: '',\n season: '',\n homeVenue: '',\n headCoachId: '',\n headCoachName: '',\n tags: [],\n};\n\nexport function TeamFormPage() {\n const tr = useAbTranslate();\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 [tagInput, setTagInput] = useState('');\n const [error, setError] = useState<string | null>(null);\n\n // Resolve the existing head coach's display name so the picker shows it on edit.\n const coachDir = useUserDirectory(isEdit ? [existing.data?.headCoachId] : []);\n const resolvedCoachName = coachDir.nameOf(existing.data?.headCoachId ?? undefined);\n\n useEffect(() => {\n const team = existing.data;\n if (isEdit && team) {\n setForm((prev) => ({\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 headCoachId: team.headCoachId ?? '',\n headCoachName: resolvedCoachName ?? prev.headCoachName,\n tags: team.tags,\n }));\n }\n }, [isEdit, existing.data, resolvedCoachName]);\n\n const set = <K extends keyof TeamFormState>(key: K, value: TeamFormState[K]) =>\n setForm((prev) => ({ ...prev, [key]: value }));\n\n const commitTag = (raw: string) => {\n const tag = raw.trim();\n if (!tag) return;\n setForm((prev) =>\n prev.tags.some((t) => t.toLowerCase() === tag.toLowerCase())\n ? prev\n : { ...prev, tags: [...prev.tags, tag] }\n );\n setTagInput('');\n };\n\n const removeTag = (tag: string) =>\n setForm((prev) => ({ ...prev, tags: prev.tags.filter((t) => t !== tag) }));\n\n const handleTagKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Enter' || e.key === ',') {\n e.preventDefault();\n commitTag(tagInput);\n } else if (e.key === 'Backspace' && !tagInput && form.tags.length) {\n removeTag(form.tags[form.tags.length - 1]);\n }\n };\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 // Fold any half-typed tag still in the input into the submission.\n const pending = tagInput.trim();\n const tags =\n pending && !form.tags.some((t) => t.toLowerCase() === pending.toLowerCase())\n ? [...form.tags, pending]\n : form.tags;\n\n const shared = {\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 headCoachId: form.headCoachId.trim() || undefined,\n tags,\n };\n\n try {\n if (isEdit && id) {\n await updateTeam.mutateAsync({ id, input: shared });\n navigateTo(`/teams/${id}`);\n } else {\n const created = await createTeam.mutateAsync(shared);\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={tr('atheletebridge.pages.teamForm.editTitle', 'Edit team')}>\n <LoadingState label={tr('atheletebridge.pages.teamForm.loading', 'Loading team…')} />\n </PageLayout>\n );\n }\n\n if (isEdit && (existing.isError || (!existing.isLoading && !existing.data))) {\n return (\n <PageLayout title={tr('atheletebridge.pages.teamForm.editTitle', 'Edit team')}>\n <ErrorState\n title={tr('atheletebridge.pages.teamForm.notFoundTitle', 'Team not found')}\n message={tr(\n 'atheletebridge.pages.teamForm.notFoundMessage',\n 'This team does not exist or you do not have access to it.'\n )}\n onRetry={() => navigateTo('/teams')}\n />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title={\n isEdit\n ? tr('atheletebridge.pages.teamForm.editTitle', 'Edit team')\n : tr('atheletebridge.pages.teamForm.newTitle', 'New team')\n }\n description={\n isEdit\n ? tr('atheletebridge.pages.teamForm.editDescription', 'Update the squad details.')\n : tr(\n 'atheletebridge.pages.teamForm.newDescription',\n 'Create a new squad to build a roster.'\n )\n }\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\" /> {tr('common.cancel', 'Cancel')}\n </button>\n }\n >\n <form onSubmit={handleSubmit} className=\"mx-auto max-w-3xl\">\n <GlassCard treatment=\"insight\" className=\"p-6 sm:p-8\">\n <div className=\"space-y-5\">\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\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 <FormField label=\"Sport\" htmlFor=\"sport\">\n <div className=\"relative\">\n <SportGlyph sport={form.sport} />\n <select\n id=\"sport\"\n className={`${fieldInputClass} pl-9`}\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 </div>\n </FormField>\n </div>\n\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\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\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 </div>\n\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <FormField label=\"Season\" htmlFor=\"season\">\n <div className=\"relative\">\n <CalendarDays\n className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-muted\"\n aria-hidden=\"true\"\n />\n <input\n id=\"season\"\n className={`${fieldInputClass} pl-9`}\n value={form.season}\n onChange={(e) => set('season', e.target.value)}\n placeholder=\"e.g. 2025/26\"\n />\n </div>\n </FormField>\n\n <FormField label=\"Home venue\" htmlFor=\"homeVenue\">\n <div className=\"relative\">\n <MapPin\n className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-muted\"\n aria-hidden=\"true\"\n />\n <input\n id=\"homeVenue\"\n className={`${fieldInputClass} pl-9`}\n value={form.homeVenue}\n onChange={(e) => set('homeVenue', e.target.value)}\n placeholder=\"e.g. Riverside Stadium\"\n />\n </div>\n </FormField>\n </div>\n\n <HeadCoachPicker\n userId={form.headCoachId}\n displayName={form.headCoachName}\n onSelect={({ userId, displayName }) =>\n setForm((prev) => ({\n ...prev,\n headCoachId: userId,\n headCoachName: displayName,\n }))\n }\n />\n\n <FormField label=\"Description\" htmlFor=\"description\">\n <textarea\n id=\"description\"\n className={fieldInputClass}\n rows={3}\n maxLength={DESCRIPTION_LIMIT}\n value={form.description}\n onChange={(e) => set('description', e.target.value)}\n placeholder=\"A short description of the team.\"\n />\n <div className=\"mt-1 text-right text-xs tabular-nums text-text-muted\">\n {form.description.length}/{DESCRIPTION_LIMIT}\n </div>\n </FormField>\n\n <FormField\n label=\"Tags\"\n htmlFor=\"tags\"\n hint=\"Type a tag and press Enter to add it; click × to remove.\"\n >\n <div className=\"flex flex-wrap items-center gap-1.5 rounded-lg border border-border-seam bg-bg-surface px-2 py-1.5 focus-within:ring-2 focus-within:ring-[var(--color-focus-ring)]\">\n {form.tags.map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center gap-1 rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\"\n >\n {tag}\n <button\n type=\"button\"\n onClick={() => removeTag(tag)}\n aria-label={`Remove tag ${tag}`}\n className=\"rounded-full text-text-muted transition-colors hover:text-status-error-text\"\n >\n <X className=\"h-3 w-3\" />\n </button>\n </span>\n ))}\n <input\n id=\"tags\"\n className=\"min-w-[8rem] flex-1 bg-transparent px-1 py-0.5 text-sm text-text-primary placeholder:text-text-muted focus:outline-none\"\n value={tagInput}\n onChange={(e) => setTagInput(e.target.value)}\n onKeyDown={handleTagKeyDown}\n onBlur={() => commitTag(tagInput)}\n placeholder={form.tags.length ? 'Add tag and press Enter…' : 'youth, development'}\n />\n </div>\n </FormField>\n\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n </div>\n\n <div className=\"mt-6 flex items-center justify-end gap-3 border-t border-border-seam pt-5\">\n <button\n type=\"button\"\n onClick={() => navigateTo(isEdit && id ? `/teams/${id}` : '/teams')}\n className=\"rounded-lg px-4 py-2 text-sm font-medium text-text-secondary transition-colors hover:text-text-primary\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n disabled={saving}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-5 py-2 text-sm font-semibold 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\n ? tr('atheletebridge.pages.teamForm.saveChanges', 'Save changes')\n : tr('atheletebridge.pages.teamForm.createTeam', 'Create team')}\n </button>\n </div>\n </GlassCard>\n </form>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TeamsListPage.d.ts","sourceRoot":"","sources":["../../../../src/atheletebridge/pages/teams/TeamsListPage.tsx"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"TeamsListPage.d.ts","sourceRoot":"","sources":["../../../../src/atheletebridge/pages/teams/TeamsListPage.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAqCH,wBAAgB,aAAa,gCAgH5B"}
|
|
@@ -1,106 +1,102 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
1
|
+
import { useAbTranslate as e } from "../../i18n.js";
|
|
2
|
+
import { useAthleteBridge as t } from "../../context/AthleteBridgeContext.js";
|
|
3
|
+
import { PageLayout as n } from "../../components/PageLayout.js";
|
|
4
|
+
import { SPORT_LABELS as r } from "../../components/domain.js";
|
|
5
|
+
import { TeamCard as i } from "../../components/TeamCard.js";
|
|
6
|
+
import { EmptyState as a } from "../../components/EmptyState.js";
|
|
7
|
+
import { ErrorState as o, LoadingState as s } from "../../components/LoadingState.js";
|
|
8
|
+
import { useTeams as c } from "../../hooks/useAthleteBridgeApi.js";
|
|
9
|
+
import { CursorPager as l } from "../../components/CursorPager.js";
|
|
10
|
+
import { FilterSelect as u } from "../../components/FilterSelect.js";
|
|
11
|
+
import { useState as d } from "react";
|
|
12
|
+
import { Plus as f, Users as p } from "lucide-react";
|
|
13
|
+
import { Fragment as m, jsx as h, jsxs as g } from "react/jsx-runtime";
|
|
13
14
|
//#region src/atheletebridge/pages/teams/TeamsListPage.tsx
|
|
14
|
-
var
|
|
15
|
+
var _ = 12, v = Object.keys(r).map((e) => ({
|
|
15
16
|
value: e,
|
|
16
|
-
label:
|
|
17
|
-
})),
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
{
|
|
31
|
-
value: "ARCHIVED",
|
|
32
|
-
label: "Archived"
|
|
33
|
-
}
|
|
34
|
-
];
|
|
35
|
-
function y() {
|
|
36
|
-
let { navigateTo: n } = e(), [y, b] = u(""), [x, S] = u(""), [C, w] = u(0), T = s({
|
|
37
|
-
sport: y || void 0,
|
|
38
|
-
status: x || void 0,
|
|
17
|
+
label: r[e]
|
|
18
|
+
})), y = [
|
|
19
|
+
"FORMING",
|
|
20
|
+
"ACTIVE",
|
|
21
|
+
"INACTIVE",
|
|
22
|
+
"ARCHIVED"
|
|
23
|
+
].map((e) => ({
|
|
24
|
+
value: e,
|
|
25
|
+
label: e === "FORMING" ? "Forming" : e === "ACTIVE" ? "Active" : e === "INACTIVE" ? "Inactive" : "Archived"
|
|
26
|
+
}));
|
|
27
|
+
function b() {
|
|
28
|
+
let r = e(), { navigateTo: b } = t(), [x, S] = d(""), [C, w] = d(""), [T, E] = d(0), D = c({
|
|
29
|
+
sport: x || void 0,
|
|
30
|
+
status: C || void 0,
|
|
39
31
|
pagination: {
|
|
40
|
-
limit:
|
|
41
|
-
offset:
|
|
32
|
+
limit: _,
|
|
33
|
+
offset: T * _
|
|
42
34
|
}
|
|
43
|
-
}),
|
|
44
|
-
return /* @__PURE__ */
|
|
45
|
-
title: "Teams",
|
|
46
|
-
description: "Squads, club sides, academy groups and esports rosters.",
|
|
35
|
+
}), O = D.data?.items ?? [], k = D.data?.pageInfo.hasMore ?? !1;
|
|
36
|
+
return /* @__PURE__ */ g(n, {
|
|
37
|
+
title: r("atheletebridge.pages.teams.title", "Teams"),
|
|
38
|
+
description: r("atheletebridge.pages.teams.description", "Squads, club sides, academy groups and esports rosters."),
|
|
47
39
|
hero: !0,
|
|
48
40
|
gradientTitle: !0,
|
|
49
|
-
actions: /* @__PURE__ */
|
|
41
|
+
actions: /* @__PURE__ */ g("button", {
|
|
50
42
|
type: "button",
|
|
51
43
|
"data-tour": "teams-create",
|
|
52
|
-
onClick: () =>
|
|
44
|
+
onClick: () => b("/teams/new"),
|
|
53
45
|
className: "inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90",
|
|
54
|
-
children: [
|
|
46
|
+
children: [
|
|
47
|
+
/* @__PURE__ */ h(f, { className: "h-4 w-4" }),
|
|
48
|
+
" ",
|
|
49
|
+
r("atheletebridge.pages.teams.new", "New team")
|
|
50
|
+
]
|
|
55
51
|
}),
|
|
56
|
-
children: [/* @__PURE__ */
|
|
52
|
+
children: [/* @__PURE__ */ g("div", {
|
|
57
53
|
"data-tour": "teams-filters",
|
|
58
54
|
className: "mb-6 flex flex-wrap items-center gap-3",
|
|
59
|
-
children: [/* @__PURE__ */
|
|
60
|
-
ariaLabel: "Filter by sport",
|
|
61
|
-
allLabel: "All sports",
|
|
62
|
-
value: y,
|
|
63
|
-
onChange: (e) => {
|
|
64
|
-
b(e), w(0);
|
|
65
|
-
},
|
|
66
|
-
options: _
|
|
67
|
-
}), /* @__PURE__ */ m(l, {
|
|
68
|
-
ariaLabel: "Filter by status",
|
|
69
|
-
allLabel: "All statuses",
|
|
55
|
+
children: [/* @__PURE__ */ h(u, {
|
|
56
|
+
ariaLabel: r("atheletebridge.pages.teams.filterSport", "Filter by sport"),
|
|
57
|
+
allLabel: r("atheletebridge.pages.teams.allSports", "All sports"),
|
|
70
58
|
value: x,
|
|
71
59
|
onChange: (e) => {
|
|
72
|
-
S(e),
|
|
60
|
+
S(e), E(0);
|
|
73
61
|
},
|
|
74
62
|
options: v
|
|
63
|
+
}), /* @__PURE__ */ h(u, {
|
|
64
|
+
ariaLabel: r("atheletebridge.pages.teams.filterStatus", "Filter by status"),
|
|
65
|
+
allLabel: r("atheletebridge.pages.teams.allStatuses", "All statuses"),
|
|
66
|
+
value: C,
|
|
67
|
+
onChange: (e) => {
|
|
68
|
+
w(e), E(0);
|
|
69
|
+
},
|
|
70
|
+
options: y
|
|
75
71
|
})]
|
|
76
|
-
}),
|
|
72
|
+
}), D.isLoading ? /* @__PURE__ */ h(s, { label: r("atheletebridge.pages.teams.loading", "Loading teams…") }) : D.isError ? /* @__PURE__ */ h(o, { onRetry: () => void D.refetch() }) : O.length === 0 ? /* @__PURE__ */ h(a, {
|
|
77
73
|
illustration: "empty-data",
|
|
78
|
-
icon: /* @__PURE__ */
|
|
79
|
-
title: "No teams found",
|
|
80
|
-
description:
|
|
81
|
-
action: /* @__PURE__ */
|
|
74
|
+
icon: /* @__PURE__ */ h(p, { className: "h-10 w-10" }),
|
|
75
|
+
title: r("atheletebridge.pages.teams.noTeams", "No teams found"),
|
|
76
|
+
description: x || C ? r("atheletebridge.pages.teams.noTeamsFilterHint", "Try clearing your filters to see all teams.") : r("atheletebridge.pages.teams.noTeamsHint", "Create your first team to build a roster and start scheduling fixtures."),
|
|
77
|
+
action: /* @__PURE__ */ h("button", {
|
|
82
78
|
type: "button",
|
|
83
|
-
onClick: () =>
|
|
79
|
+
onClick: () => b("/teams/new"),
|
|
84
80
|
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",
|
|
85
|
-
children: "Create team"
|
|
81
|
+
children: r("atheletebridge.pages.teams.createTeam", "Create team")
|
|
86
82
|
})
|
|
87
|
-
}) : /* @__PURE__ */
|
|
83
|
+
}) : /* @__PURE__ */ g(m, { children: [/* @__PURE__ */ h("div", {
|
|
88
84
|
className: "grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3",
|
|
89
|
-
children:
|
|
85
|
+
children: O.map((e) => /* @__PURE__ */ h(i, {
|
|
90
86
|
team: e,
|
|
91
87
|
memberCount: e.members?.items.length,
|
|
92
|
-
onClick: () =>
|
|
88
|
+
onClick: () => b(`/teams/${e.id}`)
|
|
93
89
|
}, e.id))
|
|
94
|
-
}), /* @__PURE__ */
|
|
90
|
+
}), /* @__PURE__ */ h(l, {
|
|
95
91
|
className: "mt-6",
|
|
96
|
-
page:
|
|
97
|
-
hasMore:
|
|
98
|
-
onPrev: () =>
|
|
99
|
-
onNext: () =>
|
|
92
|
+
page: T,
|
|
93
|
+
hasMore: k,
|
|
94
|
+
onPrev: () => E((e) => Math.max(0, e - 1)),
|
|
95
|
+
onNext: () => E((e) => e + 1)
|
|
100
96
|
})] })]
|
|
101
97
|
});
|
|
102
98
|
}
|
|
103
99
|
//#endregion
|
|
104
|
-
export {
|
|
100
|
+
export { b as TeamsListPage };
|
|
105
101
|
|
|
106
102
|
//# sourceMappingURL=TeamsListPage.js.map
|
|
@@ -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
|
|
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 { useAbTranslate } from '../../i18n';\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 = (['FORMING', 'ACTIVE', 'INACTIVE', 'ARCHIVED'] as AthTeamStatus[]).map(\n (value) => ({\n value,\n label:\n value === 'FORMING'\n ? 'Forming'\n : value === 'ACTIVE'\n ? 'Active'\n : value === 'INACTIVE'\n ? 'Inactive'\n : 'Archived',\n })\n);\n\nexport function TeamsListPage() {\n const tr = useAbTranslate();\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={tr('atheletebridge.pages.teams.title', 'Teams')}\n description={tr(\n 'atheletebridge.pages.teams.description',\n 'Squads, club sides, academy groups and esports rosters.'\n )}\n hero\n gradientTitle\n actions={\n <button\n type=\"button\"\n data-tour=\"teams-create\"\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\" /> {tr('atheletebridge.pages.teams.new', 'New team')}\n </button>\n }\n >\n <div data-tour=\"teams-filters\" className=\"mb-6 flex flex-wrap items-center gap-3\">\n <FilterSelect\n ariaLabel={tr('atheletebridge.pages.teams.filterSport', 'Filter by sport')}\n allLabel={tr('atheletebridge.pages.teams.allSports', 'All sports')}\n value={sport}\n onChange={(v) => {\n setSport(v);\n setPage(0);\n }}\n options={SPORT_OPTS}\n />\n <FilterSelect\n ariaLabel={tr('atheletebridge.pages.teams.filterStatus', 'Filter by status')}\n allLabel={tr('atheletebridge.pages.teams.allStatuses', '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={tr('atheletebridge.pages.teams.loading', '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={tr('atheletebridge.pages.teams.noTeams', 'No teams found')}\n description={\n sport || status\n ? tr(\n 'atheletebridge.pages.teams.noTeamsFilterHint',\n 'Try clearing your filters to see all teams.'\n )\n : tr(\n 'atheletebridge.pages.teams.noTeamsHint',\n 'Create your first team to build a roster and start scheduling fixtures.'\n )\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 {tr('atheletebridge.pages.teams.createTeam', '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 memberCount={team.members?.items.length}\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":";;;;;;;;;;;;;;AAmBA,IAAM,IAAY,IAEZ,IAAc,OAAO,KAAK,CAAY,EAAiB,KAAK,OAAW;CAC3E;CACA,OAAO,EAAa;AACtB,EAAE,GAEI,IAAe;CAAC;CAAW;CAAU;CAAY;AAAU,EAAsB,KACpF,OAAW;CACV;CACA,OACE,MAAU,YACN,YACA,MAAU,WACR,WACA,MAAU,aACR,aACA;AACZ,EACF;AAEA,SAAgB,IAAgB;CAC9B,IAAM,IAAK,EAAe,GACpB,EAAE,kBAAe,EAAiB,GAClC,CAAC,GAAO,KAAY,EAAwB,EAAE,GAC9C,CAAC,GAAQ,KAAa,EAA6B,EAAE,GACrD,CAAC,GAAM,KAAW,EAAS,CAAC,GAE5B,IAAQ,EAAS;EACrB,OAAO,KAAS,KAAA;EAChB,QAAQ,KAAU,KAAA;EAClB,YAAY;GAAE,OAAO;GAAW,QAAQ,IAAO;EAAU;CAC3D,CAAC,GAEK,IAAQ,EAAM,MAAM,SAAS,CAAC,GAC9B,IAAU,EAAM,MAAM,SAAS,WAAW;CAEhD,OACE,kBAAC,GAAD;EACE,OAAO,EAAG,oCAAoC,OAAO;EACrD,aAAa,EACX,0CACA,yDACF;EACA,MAAA;EACA,eAAA;EACA,SACE,kBAAC,UAAD;GACE,MAAK;GACL,aAAU;GACV,eAAe,EAAW,YAAY;GACtC,WAAU;aAJZ;IAME,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA;IAAC;IAAE,EAAG,kCAAkC,UAAU;GACvE;;YAhBZ,CAmBE,kBAAC,OAAD;GAAK,aAAU;GAAgB,WAAU;aAAzC,CACE,kBAAC,GAAD;IACE,WAAW,EAAG,0CAA0C,iBAAiB;IACzE,UAAU,EAAG,wCAAwC,YAAY;IACjE,OAAO;IACP,WAAW,MAAM;KAEf,AADA,EAAS,CAAC,GACV,EAAQ,CAAC;IACX;IACA,SAAS;GACV,CAAA,GACD,kBAAC,GAAD;IACE,WAAW,EAAG,2CAA2C,kBAAkB;IAC3E,UAAU,EAAG,0CAA0C,cAAc;IACrE,OAAO;IACP,WAAW,MAAM;KAEf,AADA,EAAU,CAAC,GACX,EAAQ,CAAC;IACX;IACA,SAAS;GACV,CAAA,CACE;MAEJ,EAAM,YACL,kBAAC,GAAD,EAAc,OAAO,EAAG,sCAAsC,gBAAgB,EAAI,CAAA,IAChF,EAAM,UACR,kBAAC,GAAD,EAAY,eAAe,KAAK,EAAM,QAAQ,EAAI,CAAA,IAChD,EAAM,WAAW,IACnB,kBAAC,GAAD;GACE,cAAa;GACb,MAAM,kBAAC,GAAD,EAAO,WAAU,YAAa,CAAA;GACpC,OAAO,EAAG,sCAAsC,gBAAgB;GAChE,aACE,KAAS,IACL,EACE,gDACA,6CACF,IACA,EACE,0CACA,yEACF;GAEN,QACE,kBAAC,UAAD;IACE,MAAK;IACL,eAAe,EAAW,YAAY;IACtC,WAAU;cAET,EAAG,yCAAyC,aAAa;GACpD,CAAA;EAEX,CAAA,IAED,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAM,KAAK,MACV,kBAAC,GAAD;IAEQ;IACN,aAAa,EAAK,SAAS,MAAM;IACjC,eAAe,EAAW,UAAU,EAAK,IAAI;GAC9C,GAJM,EAAK,EAIX,CACF;EACE,CAAA,GACL,kBAAC,GAAD;GACE,WAAU;GACJ;GACG;GACT,cAAc,GAAS,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC,CAAC;GAC/C,cAAc,GAAS,MAAM,IAAI,CAAC;EACnC,CAAA,CACD,EAAA,CAAA,CAEM;;AAEhB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@burdenoff/microfe-atheletebridge",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.829.2",
|
|
4
4
|
"description": "Burdenoff AthleteBridge Microfrontend - Sports, coaching & training platform for teams, fixtures, programs, sessions and athlete performance",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|