27 Commits (7055eb2f43c426357c68d11d4a3ec56e3a87da21)

Author SHA1 Message Date
Guillermo Pages 7055eb2f43 feat: add admin UI for credits, transfers, and plan entitlements
continuous-integration/drone/push Build is passing Details
- 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
2 months ago
Guillermo Pages 9f059bcbfe feat: add plan template picker for membership plan creation
continuous-integration/drone/push Build is passing Details
- 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.)
2 months ago
Guillermo Pages d29bf7884d fix: update billing period to match backend validation
continuous-integration/drone/push Build is passing Details
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'
2 months ago
Guillermo Pages 7c1c3a7568 feat: add admin context support and fix API type mismatches
continuous-integration/drone/push Build is passing Details
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.
2 months ago
Guillermo Pages 5dd283525b feat: add facility admin management UI (plans, members, policy)
continuous-integration/drone/push Build is passing Details
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.
2 months ago
Guillermo Pages ff50bdfa2c fix: move address fields from settings to top-level facility object
continuous-integration/drone/push Build is passing Details
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.
3 months ago
Guillermo Pages 94a1979201 fix: update facility detail types to match current API response shape
continuous-integration/drone/push Build is passing Details
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.
3 months ago
Guillermo Pages 4d9aeb9045 fix: update admin endpoints from /admin/clubs to /admin/facilities (Build 363+)
continuous-integration/drone/push Build is passing Details
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
3 months ago
Guillermo Pages 6ebc57dd36 feat: migrate to API v2.0 with facility-based routing (Build 358+)
continuous-integration/drone/push Build is passing Details
Complete migration to match backend breaking changes from Build 355+ and Build 358+:

**Terminology Migration (Build 355+):**
- Rename club → facility (club_id → facility_id, club_name → facility_name)
- Rename remote → origin/provider (remote_server_id → origin_id, remote_type → provider)
- Update all field references: remote_name → origin_name, remote_logo_url → origin_logo_url

**Facility-Based Routing (Build 358+):**
- Change routing from provider-based to facility-based
- Update URL parameters: remote_slug → facility_slug in all booking/slot endpoints
- Update discovery endpoint: /remotes → /facilities
- Update slot responses: remote object → facility object with new field structure
- Update user settings: default_remote_sport.origin_slug → facility_slug

**Files Updated:**
- 32 files modified (TypeScript types, API clients, hooks, components)
- 150+ field name changes across the codebase
- All response interfaces updated to match new API contract

**Verification:**
- TypeScript compilation: ✓ No errors
- ESLint: ✓ No migration-related issues

BREAKING CHANGES: Requires backend Build 358+ for facility-based routing
3 months ago
Guillermo Pages 3b7f937505 feat(slot-definitions): add real-time job tracking to materialization status panel
continuous-integration/drone/push Build is passing Details
- 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
3 months ago
Guillermo Pages 2881721b18 feat(slot-definitions): add materialization policy selector (SAFE/CLEANUP)
continuous-integration/drone/push Build is passing Details
- Add MaterialisationPolicyProfile type and MATERIALISATION_POLICIES constants
- Add policy selector UI with radio buttons for SAFE and CLEANUP modes
- SAFE mode (default): skips overlapping slots to protect existing schedule
- CLEANUP mode: removes conflicting slots to fix overlaps (keeps bookings safe)
- Update MaterialisationTriggerRequest to include policy_profile field
- Pass selected policy to triggerMaterialisation API call
- Fixes issue where partial overlaps blocked slot materialization
- Implements Backend Brooke's BUILD 352 policy preset feature
3 months ago
Guillermo Pages b239081211 fix: update response types to match actual API contract
continuous-integration/drone/push Build was killed Details
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.
3 months ago
Guillermo Pages f450c83c4e revert: restore preset field name (Backend reverted breaking change)
continuous-integration/drone/push Build is passing Details
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.
3 months ago
Guillermo Pages 834e1a8195 fix: rename preset to pattern_type in generate request (BREAKING CHANGE)
continuous-integration/drone/push Build is passing Details
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.
3 months ago
Guillermo Pages 9c802ea2aa feat: migrate admin API clients to send locale/timezone headers
continuous-integration/drone/push Build is passing Details
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
3 months ago
Guillermo Pages 13ec348fb2 feat: add bulk operations API support (generate/clone)
continuous-integration/drone/push Build is passing Details
Added TypeScript types and API client functions for slot definition bulk operations.

Types:
- SlotDefinitionPreset enum with 4 presets
- PRESET_OPTIONS metadata for UI display
- GenerateSlotDefinitionsRequest/Response
- CloneSlotDefinitionRequest/Response
- PatternOverrides for preset customization
- SkippedSlot interface for overlap handling

API Functions:
- generateSlotDefinitions() - bulk create from presets
- cloneSlotDefinition() - copy to multiple courts/days

Based on Backend Brooke's spec (BUILD:345, staging).
Next: Build UI modals for generate and clone flows.
3 months ago
Guillermo Pages bd2cbb3a0d feat: implement booking admin test infrastructure for 8-scenario E2E grid
continuous-integration/drone/push Build is passing Details
Built complete booking admin API integration for idempotency validation testing requested by Backend Brooke.

Components:
- TypeScript types for booking admin operations (cancel/move/attendees)
- API client with full idempotency and ETag support
- Comprehensive test page with 8 scenario buttons
- Real-time result tracking with request/response capture

Features:
- X-Idempotency-Key header support (UUID v4)
- ETag-based optimistic concurrency (If-Match)
- Provider gating checks (local vs fairplay)
- User-friendly error message mapping (12 error codes)
- HAR capture instructions for integration log

Test Scenarios:
1. Cancel within grace (≤15 min)
2. Cancel beyond grace (>15 min)
3. Move dry_run success
4. Move dry_run failure
5. Move window exceeded (>14 days)
6. Attendee capacity limits
7. Capacity race (concurrent moves)
8. ETag guard (stale If-Match)

Ready for E2E execution against booking admin endpoints.
3 months ago
Guillermo Pages 20971e53c0 fix(api): update policies type and materialisation endpoint URL
continuous-integration/drone/push Build is passing Details
Per Backend Brooke messages:
- Made BookingDetail.policies required (commit 75c8edf deployed)
- Fixed materialisation endpoint: /materialisation-trigger → /slot-materialize
- CORS headers fix deploying on backend (X-Idempotency-Key, If-Match, If-None-Match)

Artifacts: src/types/bookings.ts:69-73, src/lib/api/materialisation.ts:91
3 months ago
Guillermo Pages 9186b35649 fix(types): add missing useEffect import and fix type mismatches
continuous-integration/drone/push Build is passing Details
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.
3 months ago
Guillermo Pages 86fa2a92e6 fix(courts): use club detail courts data instead of separate fetch
continuous-integration/drone/push Build is passing Details
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.
3 months ago
Guillermo Pages 71367143c6 feat(bookings): add TypeScript types and API client for Booking Admin v1.1
continuous-integration/drone/push Build is passing Details
Implemented foundation for Phase 3 Booking Admin integration:

**TypeScript Types (src/types/bookings.ts):**
- BookingDetail with nested slot, attendees, provider info
- Request types: CancelBookingRequest, MoveBookingRequest, UpdateAttendeesRequest
- Response types for all operations (cancel, move, attendees)
- MoveDryRunResponse for slot picker validation
- ProviderInfo for FairPlay gating
- 18 booking error codes with user-friendly message mapping
- getBookingErrorMessage() helper with parameterized messages

**API Client (src/lib/api/bookings.ts):**
- getBooking() - GET /admin/bookings/{id} with ETag extraction
- cancelBooking() - PATCH /cancel with ETag + idempotency
- validateMoveBooking() - PATCH /move?dry_run=true for picker
- moveBooking() - PATCH /move with ETag + idempotency
- updateBookingAttendees() - PATCH /attendees with ETag + idempotency

**Features:**
- ETag optimistic concurrency (If-Match header)
- X-Idempotency-Key for retry safety (auto-generated UUID)
- Comprehensive error handling with RFC-7807 format
- Parameterized error messages (grace_minutes, limit_days)
- Network error fallbacks

**Production Config (per Backend Brooke):**
- Cancel grace: 15 min after slot start
- Move window: 14-day limit
- Product-type enforcement (singles ≠ doubles)
- Provider gating (FairPlay = read-only)

Related: Phase 3, Backend Brooke commit 514a3b6, v1.1 contract
3 months ago
Guillermo Pages 1b7497b6d2 fix(admin): duration validation + sport variation display
continuous-integration/drone/push Build is passing Details
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
3 months ago
Guillermo Pages 36c5fe2183 feat(admin): add sport variation selection to court creation + fix profile trim crash
continuous-integration/drone/push Build is passing Details
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
3 months ago
Guillermo Pages a61e64ded0 feat(admin): align UI with Backend Brooke Phase 2 contracts, flip mocks to real endpoints
continuous-integration/drone/push Build is passing Details
Phase 2 Integration Updates:
- Profile UI: Read/write address and contact fields from settings.address/settings.contact JSONB structure
- Court dependencies: Updated modal to show 3 counts (slot_definitions, slot_instances_future, slot_instances_booked)
- Error codes: Fixed duplicate court check to use court_name_duplicate (matches backend)
- Mock flags: Flipped all 9 USE_MOCKS flags to false (courts.ts x7, materialisation.ts x2)

Profile Tab (ClubProfileTab.tsx):
- Read address fields from profile.settings?.address?.line_1 etc.
- Read contact fields from profile.settings?.contact?.phone etc.
- Write to settings structure preserving existing settings
- Added Info icon tooltips: "Stored in settings until native fields ship"
- Removed Integration section (provider/remote_club_id not in API)

Court Dependencies Modal (ClubCourtsTab.tsx):
- Display slot_instances_future count (was upcoming_bookings)
- Display slot_instances_booked count (new field)
- Updated deletion instructions for 3-count structure

Types (courts.ts):
- Created ClubProfileSettings interface for JSONB structure
- Updated ClubProfile to use settings instead of flat fields
- Updated CourtDependencies with court_id + 3 dependency counts
- Updated ClubProfileUpdateRequest to support settings

API Clients:
- courts.ts: Updated mock data to match API contracts (settings structure, 3 counts, court_name_duplicate)
- materialisation.ts: Flipped USE_MOCKS to false for real backend integration

Integration Ready:
- All contract mismatches resolved
- Build succeeds
- Ready for Phase 1 smoke tests against BUILD:290-291

Related: Backend Brooke BUILD:290 (courts CRUD) + BUILD:291 (club profile PATCH)
Contract references: docs/owners/payloads/court-api-contract.md, club-profile-api-contract.md
3 months ago
Guillermo Pages 251b849500 feat(admin): add materialisation trigger/status UI + court management infrastructure
continuous-integration/drone/push Build is passing Details
Phase 1 continuation + Phase 2 preparation per Chief Cole request (Chief_Cole-20251107102650)

Materialisation UI (Phase 1 continuation):
- Created MaterialisationStatusPanel component with 5 states (never run, success, running, failed, rate limited)
- Polling strategy: 3-second intervals while job running
- Rate-limit countdown timer with live updates
- Idempotency key generation (UUID v4) for manual triggers
- Mock data for development with state cycling
- Integrated into slot definitions page between header and table
- TypeScript types: MaterialisationStatus, MaterialisationTriggerRequest/Response, helpers
- API client with mock implementation (USE_MOCKS flag)
- Professional slate theme consistent with existing UI

Court Management Infrastructure (Phase 2 preparation):
- Created TypeScript types for courts and club profile
- Court types: Court, CourtRequest, CourtDependencies
- Profile types: ClubProfile, ClubProfileUpdateRequest
- Common timezones list (40+ IANA zones)
- Validation helpers (email, URL)
- API client with full CRUD mocks:
  - getClubProfile, updateClubProfile
  - getCourts, createCourt, updateCourt, deleteCourt
  - getCourtDependencies (cascade blocking)
- Mock data: 3 courts, full profile, dependency simulation
- In-memory mock state management

Implementation ready for:
- Next: Club detail tab navigation (Profile/Courts/Slot Definitions)
- Next: Profile tab with edit form
- Next: Courts tab with add/edit/delete modals

Build tested and passed (npm run build successful)
3 months ago
Guillermo Pages a8ad9fed51 feat(slot-definitions): implement Phase 1 slot definition table UI
continuous-integration/drone/push Build is passing Details
Add TypeScript types, API client, and table view for slot definitions.
Uses mock data until backend endpoints are ready.

Components:
- SlotDefinitionsComponent: table with create/edit/delete actions
- TypeScript types matching API contract specs
- API client with CRUD operations (POST/GET/PATCH/DELETE)
- Mock data for local development

Features:
- Professional slate theme throughout
- Empty state with CTA
- Table showing: day, time range, duration, capacity, valid period, description
- Edit/delete action buttons (TODOs for modals)
- Loading and error states with proper UX
- Breadcrumb navigation back to club

Contract alignment:
- Day format: 0=Monday, 6=Sunday
- Time format: HH:MM:SS with display helpers
- Date range: valid_from required, valid_to optional
- RFC-7807 error handling ready

Next: Create/edit form modal with validation
3 months ago
Guillermo Pages c275bf1dcc feat(phase-0): integrate admin clubs API with staging backend
Phase 0 venue admin integration complete:
- Implemented GET /admin/clubs API integration
- Three-state response handling: 401 (auth), empty (no access), success (club list)
- RFC-7807 error handling with proper error display
- Club cards with navigation to detail pages
- Staging environment configuration (.env.staging)
- TypeScript interfaces aligned with VENUE_ADMIN_DESIGN.md

Integration points:
- API base URL: https://staging.api.playchoo.com
- Endpoints: /admin/clubs, /admin/clubs/{club_id}
- Auth: Cookie-based SwissOID (handled by API)
- Stub mode: Clubs 3 (Central Padel) and 7 (Riverside Tennis)

Files changed:
- src/app/[locale]/admin/clubs/page.tsx: API integration
- src/lib/api/admin-clubs.ts: API client with mock data
- src/types/admin-api.ts: TypeScript interfaces
- .env.staging: Staging configuration

Ready for:
- Live testing against staging stubs
- E2E tests with admin accounts (pending Access Avery seeding)

Refs: VENUE_ADMIN_DESIGN.md, VENUE_ADMIN_EXECUTION_PLAN.md
3 months ago