When creating a competition without a template, the modal now provides
sensible default configurations for each competition type:
- league: round_robin stage with standard points (3/1/0)
- tournament: single_elimination stage
- challenge: rating_delta_challenge stage with sum metric (leaderboard)
- hybrid: group stage + knockout stage
This ensures stages are created automatically instead of leaving
competitions with 'No stages configured yet'.
- Rename /admin/clubs to /admin/facilities
- Rename [club_id] param to [facility_id]
- Update all component names: Club* -> Facility*
- Update CompetitionCard to use correct admin route with locale
- Keep API function names as-is (getAdminClubDetail) since they match backend
Backend requires scope and config_snapshot when creating competition
without a template. Frontend now:
- Defaults scope to 'facility' when facility_id is provided
- Renames config to config_snapshot for backend compatibility
- Provides empty config_snapshot when no template_id
continuous-integration/drone/push Build is passingDetails
- Add SchedulingTab with SlotPickerModal for linking matches to court slots
- Add ResultsTab with dispute management (list, resolve, reject disputes)
- Add Series management pages (list, create with RRULE configuration)
- Add SaveAsTemplateModal to save competition config as reusable template
- Add GroupAssignmentModal for assigning participants to groups
- Enable approve button for waitlisted registrations
- Add dispute types, API functions, and query/mutation hooks
- Add series types, API functions, and query/mutation hooks
continuous-integration/drone/push Build is passingDetails
- Create ClubTabNavigation component with responsive horizontal scroll
- Create ClubDetailHeader component with shared layout (breadcrumb, header, tabs)
- Add layout.tsx for club_id route to wrap all sub-pages
- Extract Courts tab into separate page route
- Remove redundant AdminAuthGuard from individual pages (now in layout)
- Remove container wrappers from components (now in shared header)
- Remove "Back to club" links (breadcrumb now in header)
- Standardize colors: gray-* to slate-* for consistent dark text styling
continuous-integration/drone/push Build is passingDetails
Add complete admin UI for managing individual slot instances:
Components:
- SlotInstancesComponent: day view with date navigation and filters
- SlotInstanceEditModal: edit times, capacity, convert to manual
- ManualSlotModal: create one-off manual slots
Features:
- Date picker with prev/next day navigation
- Filter by court and show/hide cancelled slots
- Group slots by court in table view
- Status badges (available, pending, booked, cancelled)
- Origin badges (template, manual, maintenance)
- Convert definition-based slots to manual (detach from materializer)
- Create manual slots for special events
- Edit slot times/capacity (protected when has bookings)
- Cancel and delete slots with confirmation
API Client:
- getSlotInstances, createSlotInstance, updateSlotInstance, deleteSlotInstance
- cancelSlotInstance, convertToManualSlot helpers
Types:
- SlotInstance, SlotInstancesResponse
- CreateSlotInstanceRequest, UpdateSlotInstanceRequest
- Helper functions for formatting and status colors
Also adds "Slot Instances" tab to ClubDetailTabs navigation.
continuous-integration/drone/push Build is passingDetails
- Add PlanEntitlementsEditor component for Pay-Per-Court settings
- Add EntitlementsConfigModal for editing plan entitlements
- Update PlanCard with entitlements badges and configure button
- Update MembershipPlansComponent to fetch/display entitlements
- Create credits management page with balance list and adjustment modal
- Create transfers management page with filtering and stats
- Add Credits and Transfers tabs to ClubDetailTabs navigation
- Add API client functions for credits and transfers endpoints
- Update facility-admin types with credit and transfer interfaces
continuous-integration/drone/push Build is passingDetails
- Add PlanTemplate type and listPlanTemplates API function
- Create TemplateCard and TemplatePicker components
- Modify PlanFormModal to accept initialValues from template
- Update MembershipPlansComponent to show template picker first
- Add missing translations (Venue Management, Club Management, etc.)
continuous-integration/drone/push Build is passingDetails
DELETE endpoints (deletePlan, deleteEntitlement, deleteMember) return
204 with empty body. handleApiResponse was trying to parse JSON from
empty response, causing: 'Unexpected end of JSON input'
Fix: Check for status 204 before calling response.json()
Return { success: true, data: undefined } for 204 responses
This resolves console error while maintaining successful delete operations.
continuous-integration/drone/push Build is passingDetails
Backend only accepts: 'monthly', 'annual', or null
Frontend was offering: daily, weekly, monthly, quarterly, yearly, lifetime
Changes:
- Update BillingPeriod type to 'monthly' | 'annual' | null
- Update PlanFormModal dropdown to only show valid options
- Update PlanCard formatBillingPeriod to handle new values
- Display 'Annual (Yearly)' in UI but send 'annual' to API
Fixes validation error: 'Must be monthly, annual, or null'
continuous-integration/drone/push Build is passingDetails
Add admin context API client:
- Create /admin/context types (ManagedFacility, AdminSettings)
- Add getAdminContext() API client function
- Separate admin context from player context
Fix API response type mismatches:
- UserSettings: API uses 'origin' terminology, map to internal 'remote'
- Fix transformApiSettings to use default_origin_sport/default_origin_member_id
- Update UserSettingsContext to read origin_members from API
- Fix getPolicy to handle direct object response (not wrapped)
- Fix listPlans/listMembers to extract nested arrays from API responses
These fixes ensure TypeScript types match actual API responses.
continuous-integration/drone/push Build is passingDetails
Backend returns { plans: [...] } and { members: [...] } for list
endpoints, not direct arrays. Update listPlans() and listMembers()
to extract the nested property from the response.
Fixes:
- TypeError: t.map is not a function on Plans page
- TypeError: l is not iterable on Members page
continuous-integration/drone/push Build is passingDetails
Add explicit checks for required TranslatedField wrapper structure
and throw descriptive errors with actual API response data when
fields are missing. This will help diagnose why the user context
API is failing despite returning 200 status.
continuous-integration/drone/push Build is passingDetails
Fix TypeError: t.map is not a function on Plans and Members pages
Issue: Backend wraps responses in { status: 'success', data: {...} }
but frontend was expecting direct data.
Changes:
- Update handleApiResponse to extract data from Flask wrapper
- Special handling for policy endpoint (returns { policy: {...} })
- Handles both wrapped and unwrapped responses for compatibility
Fixes the 'Application error' on Plans and Members pages.
continuous-integration/drone/push Build is passingDetails
Remove player-facing menu items:
- Remove 'Booking' (player feature)
- Remove 'Player Lookup' (player feature)
- Remove 'Assessment' (player feature)
Update remaining navigation:
- Replace 'Dashboard' with direct link to '/admin/clubs'
- Rename to 'Venue Management' for clarity
- Simplified menu now shows only admin features
- Updated animation indices after removing items
Result: Clean, focused admin portal navigation.
continuous-integration/drone/push Build is passingDetails
Add 'Venue Admin' menu item in hamburger navigation:
- Links to /admin/clubs for facility management
- Purple accent to distinguish from player features
- Positioned after assessment, before language selector
Add tabs to club detail page:
- Plans tab (purple accent)
- Members tab (purple accent)
- Settings tab (purple accent)
- All tabs link to new admin portal pages
Navigation is now complete for accessing all facility management features.
continuous-integration/drone/push Build is passingDetails
Implements Phase B1-B4 of the admin portal frontend:
Phase B1 - Setup & Types:
- Add TypeScript type definitions for all admin APIs
- Add complete API client with all 15 endpoint integrations
Phase B2 - Membership Plans UI:
- Add plans listing page with create/edit/delete functionality
- Add PlanCard, PlanFormModal, and PlanListSkeleton components
- Support billing periods, pricing, and activation status
Phase B3 - Member Management UI:
- Add members listing page with search and filters
- Add member CRUD modals with role/plan assignment
- Add RoleBadge, StatusBadge, and MemberCard components
- Support filtering by role and status
Phase B4 - Policy Configuration UI:
- Add facility settings page for policy management
- Add AccessModelSelector for membership/payg/open models
- Add GuestPricingInput and BookingLimitsForm components
- Support configuring booking limits and access control
All components follow existing design patterns with purple/indigo
gradients, responsive layouts, and loading states.
continuous-integration/drone/push Build is passingDetails
The API now returns address at facility.address instead of facility.settings.address, and uses field names like address_line_1 and zip instead of line_1 and postal_code. Updated the ClubProfile type and form to match.
continuous-integration/drone/push Build is passingDetails
The API now returns 'facility' with expanded address/settings fields instead of 'club', and slot types have been restructured to use flat properties instead of nested booking/meta objects.
continuous-integration/drone/push Build is passingDetails
Complete migration of all admin API endpoints from clubs to facilities nomenclature:
**Admin Endpoint Changes:**
- GET /admin/clubs → GET /admin/facilities
- GET /admin/clubs/{id} → GET /admin/facilities/{id}
- All 30+ admin routes migrated from /clubs/ to /facilities/
**API Client Files Updated:**
- admin-clubs.ts: List and detail endpoints (2 endpoints)
- materialisation.ts: Materialization status and trigger (2 endpoints)
- slot-definitions.ts: All CRUD operations (6 endpoints)
- courts.ts: Court management and profile (7 endpoints)
- admin-api.ts: TypeScript documentation comments (7 updates)
**Total Changes:**
- 28 endpoint path replacements across 5 files
- All /admin/clubs references removed
- Comments and documentation updated
**Verification:**
- TypeScript compilation: ✓ No errors
- All admin endpoints now use /admin/facilities
- Fixes 404 errors on admin panel endpoints
BREAKING CHANGES: Requires backend Build 363+ with /admin/facilities endpoints
continuous-integration/drone/push Build is passingDetails
Complete migration to Build 362+ facility discovery response format:
**Discovery Endpoint Changes:**
- Parse 'facilities' array instead of 'remotes' from /facilities response
- Update facility object structure to use facility_* fields exclusively
- Remove all origin_* field references from facility discovery
**Component Updates:**
- RemoteSportInfo: Update to parse facilities array and use facility_name, facility_slug, facility_logo_url
- useCourtSlots: Build remotesData with facilities array using new field structure
- Add backward compatibility fallbacks for transition period
**Response Structure Migration:**
- OLD: data.remotes[] with origin_id, origin_name, origin_slug fields
- NEW: data.facilities[] with facility_id, facility_name, facility_slug fields
- Frontend now receives pure facility/venue data, not provider/origin data
**Files Updated:**
- src/components/RemoteSportInfo.tsx (interface and parsing logic)
- src/hooks/useCourtSlots.ts (remotesData structure generation)
**Verification:**
- TypeScript compilation: ✓ No errors
- ESLint: ✓ No new issues
BREAKING CHANGES: Requires backend Build 362+ for facility-centric discovery responses
continuous-integration/drone/push Build is passingDetails
- Add MaterialisationJobInfo interface with job status, timing, and results
- Add last_job field to MaterialisationStatus (BUILD 353 support)
- Add calculateElapsedTime and formatElapsedTime helper functions
- Implement job status polling (every 2s while queued/running)
- Add elapsed time counter for running jobs with live updates
- Display job progress states:
- Queued: 'Job queued, waiting to start...'
- Running: 'Generating slots... (45s)' with policy and horizon info
- Completed: '✅ Generated X slots (Y cancelled, Z errors)' with detailed metrics
- Failed: '❌ Job failed' with error message
- Update rate limit display logic (only show when can_trigger=false AND no active job)
- Disable trigger button when job is active (queued/running)
- Resolves UX confusion where users saw rate limits while jobs were actually running
- Implements Backend Brooke BUILD 353 job tracking feature
continuous-integration/drone/push Build is passingDetails
- 'Up to date' badge now shows when status is idle with last_success_at
- 'Not yet run' badge only shows when status is idle AND last_run_at is null
- Success details section now shows for both completed and idle-after-success states
- Fixes issue where successful materialisation showed 'Not yet run' after transitioning from 'completed' to 'idle' status
continuous-integration/drone/push Build is passingDetails
- Set default value to today's date
- Add validation before form submission
- Update UI label to show required field with asterisk
- Fixes validation error: 'valid_from' required field
continuous-integration/drone/push Build was killedDetails
Backend Brooke confirmed the actual API response structure uses "definitions"
not "created_definitions". Also added missing preset and pattern fields to
GenerateSlotDefinitionsResponse.
Changes:
- GenerateSlotDefinitionsResponse: added preset and pattern fields
- GenerateSlotDefinitionsResponse: renamed created_definitions → definitions
- CloneSlotDefinitionResponse: renamed created_definitions → definitions
This matches the confirmed API contract from BUILD 348.
continuous-integration/drone/push Build is passingDetails
Backend Brooke reverted the breaking change from BUILD:347. The generate
endpoint continues to use "preset" field name, not "pattern_type".
This reverts commit 834e1a8 and restores the original API contract.
Changes:
- Reverted GenerateSlotDefinitionsRequest: pattern_type → preset
- Reverted GenerateSlotDefinitionsModal to send preset in request body
Backend BUILD:348 has restored the original "preset" field.
continuous-integration/drone/push Build is passingDetails
Backend API changed field name from "preset" to "pattern_type" in the
generate slot definitions endpoint (BUILD:347).
Changes:
- Updated GenerateSlotDefinitionsRequest interface: preset → pattern_type
- Updated GenerateSlotDefinitionsModal to send pattern_type in request body
This fixes compatibility with the updated backend API contract.
continuous-integration/drone/push Build is passingDetails
Migrated all admin API clients to automatically send X-Locale and X-Timezone headers
with every request for proper internationalization and timezone-aware operations.
Changes:
- Migrated slot-definitions.ts, materialisation.ts, courts.ts, booking-admin.ts to use apiFetch utility
- Added getLocaleHeaders() helper to admin-clubs.ts for SSR compatibility
- Added getSlotDefinitionPresets() endpoint to fetch localized preset metadata
- Updated slot-definitions types to support API-based preset loading
- Refactored GenerateSlotDefinitionsModal to fetch presets from API with localized titles/descriptions
Benefits:
- All API requests now include user's locale (e.g., en-US, fr-CH) for i18n
- Timezone-aware operations use browser's Intl.DateTimeFormat timezone
- Preset titles and descriptions are localized per user language
- Consistent header management across all admin endpoints
Technical Details:
- apiFetch extracts locale from URL pathname via getPathnameLocale()
- apiFetch automatically adds credentials: 'include' for session auth
- admin-clubs.ts uses manual headers for SSR cookie forwarding compatibility
- GenerateSlotDefinitionsModal shows loading state while fetching presets
continuous-integration/drone/push Build is passingDetails
Built complete bulk operations UI for slot definitions based on Backend Brooke's spec (BUILD:345).
New Components:
- GenerateSlotDefinitionsModal (380 lines)
- Preset selector with 4 options (workday_standard, weekend_extended, all_week_uniform, hourly_daytime)
- Court multi-select with Select All
- Validity date pickers (optional)
- Advanced pattern overrides (collapsible): custom days, time range, duration, interval, capacity
- Real-time validation and error handling
- CloneSlotDefinitionModal (280 lines)
- Source definition display with full details
- Target courts multi-select
- Target days multi-select (pre-selected source day)
- Validity date overrides (optional)
- Preview of estimated clone count
Integration:
- Added Generate button (blue, magic wand icon) to page header
- Added Clone button (blue copy icon) to each table row
- Wired up modals with success callbacks to reload data
- Success callbacks trigger table refresh
Features:
- All modals use professional slate theme
- Loading states with spinners
- Error display with user-friendly messages
- Form validation before submission
- Modal backdrop dismissal
Ready for testing against staging (BUILD:345).
continuous-integration/drone/push Build is passingDetails
Removed USE_MOCKS and USE_MOCK_DATA flags from all API client functions. All endpoints now call the real backend API directly without conditional mock data paths.
Changes:
- SlotDefinitionsComponent: removed mock flags from loadData() and loadDefinitions()
- materialisation.ts: removed mock conditionals from getMaterialisationStatus() and triggerMaterialisation()
- admin-clubs.ts: removed MaybeMock wrapper functions and USE_MOCK_DATA flag
- courts.ts: removed mock conditionals from all 7 CRUD functions
All API calls now go directly to https://api.playchoo.com backend.
continuous-integration/drone/push Build is passingDetails
Changed from hardcoded MOCK_COURTS to fetching real court data from club detail API.
Dropdown now shows actual court names (e.g., 'Padel 1', 'Tennis Int 1') instead of generic 'Court 1', 'Court 2'.
Changes:
- Fetch courts from getAdminClubDetail() on component mount
- Pass real courts array to SlotDefinitionForm
- Split loadData() (courts + definitions) and loadDefinitions() (refresh after CRUD)
Artifacts: src/app/[locale]/admin/clubs/[club_id]/slot-definitions/SlotDefinitionsComponent.tsx:36-38,298
continuous-integration/drone/push Build is passingDetails
Fixes from tsc type checking:
- Re-added useEffect import to ClubCourtsTab (used in CourtFormModal)
- Updated mock courts data to include created_at/updated_at timestamps
- Fixed CourtError dependencies type to match CourtDependencies structure
(slot_instances_future/booked instead of upcoming_bookings)
All court-related TypeScript errors resolved. Build succeeds.
continuous-integration/drone/push Build is passingDetails
Root cause: GET /admin/clubs/{id}/courts endpoint doesn't include
sport_variation nested structure, but GET /admin/clubs/{id} does.
Changes:
- Updated AdminClubDetail.courts to use Court[] type (includes sport_variation)
- ClubDetailTabs now passes courts + onUpdate callback to ClubCourtsTab
- ClubCourtsTab accepts courts as props instead of fetching separately
- Removed redundant getCourts() API call
- Removed debug logging
This fixes the court edit dropdown issue where sport variation wasn't
displaying because the data wasn't available from the courts endpoint.
continuous-integration/drone/push Build is passingDetails
Added debug logging to investigate sport variation dropdown issue:
- Log court data object on mount
- Log extracted sport_variation_id value
- Added visual disabled state (gray background) for locked dropdown
- Extracted initialSportVariationId to const for better debugging
This will help identify if:
1. Court data has sport_variation nested structure
2. sport_variation_id is being extracted correctly
3. Dropdown value is being set properly
Related: BUILD #21 sport variation display issue