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
continuous-integration/drone/push Build is passingDetails
Fixed two issues reported by user:
1. Slot definition duration validation error:
- Changed step from "15" to "1" on duration input
- HTML5 validation was failing because with min="1" and step="15",
valid values were 1, 16, 31, 46, 61, 76, 91... (not 90!)
- Now accepts any integer duration >= 1 minute
- Error "nearest 76 or 91" resolved
2. Sport variation not showing in court edit form:
- Updated Court type to include nested sport_variation structure
- Backend now returns full sport_variation object with sport details
- Updated CourtFormModal to extract sport_variation_id from nested
structure: court.sport_variation.sport_variation_id
- Added sport variation display in court list (e.g., "Padel - Indoor")
- Maintains backward compatibility with deprecated flat field
Changes:
- SlotDefinitionForm: step="1" instead of step="15"
- Court interface: added optional sport_variation nested object
- ClubCourtsTab: reads sport_variation_id from nested structure
- ClubCourtsTab: displays "Sport - Variation" in court list
Related: Backend Brooke nested sport_variation response structure
continuous-integration/drone/push Build is passingDetails
Fixed two critical issues:
1. Profile form blur bug - ACTUAL FIX:
- Removed ALL blur auto-close handlers from EditableField component
- Removed blur auto-close from name and timezone inline inputs
- Users can now type freely without inputs closing prematurely
- Form uses Save/Cancel buttons for submission, not blur events
2. Settings parsing from GET response:
- Backend GET /admin/clubs/{id} returns nested structure with club object
- Updated getClubProfile to extract club from response.club
- Settings now properly loaded and displayed from GET response
- PATCH response already worked (returns club directly)
Root cause of blur bug: blur handlers were closing inputs when field had
content, but this prevented multi-character input. Removed blur logic
entirely since form has explicit Save/Cancel buttons.
Impact: Profile form now fully functional for editing all fields including
settings (address + contact). Settings persist and display correctly.
Related: BUILD #20, Backend Brooke BUILD:297 (settings support)
continuous-integration/drone/push Build is passingDetails
Fixed critical bug where EditableField component would close after typing
first character. Root cause was stale closure variable in onBlur handler.
Changes:
- Replace inline onBlur with handleBlur() function that evaluates current
value at blur time instead of using stale hasValue closure variable
- Fixes issue where typing first char would set hasValue=true in closure,
causing immediate blur to close input
Impact: Users can now fill in profile fields without being kicked out
Related: BUILD #19
continuous-integration/drone/push Build is passingDetails
Court Sport Variation (BUILD:296-297 requirement):
- Added Sport and SportVariation types to courts.ts
- Created getSports() API client for GET /admin/sports
- Updated Court and CourtRequest types with sport_variation_id field
- Added sport variation dropdown to court creation modal
- Auto-selects first variation for new courts
- Sport type locked after creation (disabled in edit mode)
- Mock data updated with sport_variation_id values
Profile Bug Fix:
- Fixed click-to-edit crash when field values are empty/undefined
- Added null checks before calling .trim() in hasFieldValue() and EditableField
- Error: "Cannot read properties of undefined (reading 'trim')"
- Now safely handles empty string values
UI Details:
- Sport dropdown shows "Padel - Indoor", "Padel - Outdoor", etc.
- Loading spinner while fetching sports
- Validation error if no sport variation selected
- Form includes both name and sport_variation_id in POST request
Backend Brooke message: Court creation now requires sport_variation_id parameter
continuous-integration/drone/push Build is passingDetails
UX Improvements:
- Fields with existing data display as read-only text (not inputs)
- Pencil icon appears on hover (right side of field)
- Click text or icon to convert to editable input
- Auto-blur closes edit mode when field has value
- Prevents accidental edits
Implementation:
- Created reusable EditableField component with pencil icon
- Applied to all optional fields: address (5 fields), contact (3 fields)
- Name and timezone use inline click-to-edit (no separate component)
- Edit state tracked per-field with Set<string>
- Form prefills with existing profile data from API
Visual Design:
- Hover: Border changes to slate-300, background to slate-50
- Pencil icon: slate-400, opacity 0→100 on hover
- Maintains existing validation error styling (red borders)
- Consistent with professional slate theme
User Request: "should not be an input directly (put a pen on the side as well as making the text clickable) and on click convert to input (prevents accidental edits)"
continuous-integration/drone/push Build is failingDetails
Per user feedback and Chief Cole direction (Chief_Cole-20251107123442):
- Remove fallback URLs from all API clients
- Throw error if NEXT_PUBLIC_PYTHON_API_URL not defined
- Standardize on NEXT_PUBLIC_PYTHON_API_URL env var across all clients
- Production URL: https://api.playchoo.com (staging/prod share hostname)
Updated files:
- src/lib/api/courts.ts
- src/lib/api/materialisation.ts
- src/lib/api/slot-definitions.ts
All clients now fail fast with clear error if env var missing.
Build tested and passed.