Skip to main content

TeeTime Benefits & Reciprocity Admin UI — Specification

Status: Implemented (v1.2) Owners: TeeTime Eng Last Updated: 2025-12-06


Goal

Provide an admin UI for managing reciprocity agreements, network memberships, home-club mappings, and eligibility previews so operators can configure and validate benefits without manual API calls.


Scope

In scope:

  • TeeTime Admin app (web)
  • CRUD for reciprocity agreements (Network + Bilateral) with rate configs
  • Network membership management (add/remove clubs from networks)
  • Home-club map editing/validation per provider
  • Eligibility preview/testing surface
  • Diagnostics combining home-club map warnings with preview results

Out of scope:

  • Corporate benefit flows (Nedbank/Discovery) UI
  • End-user/mobile benefit presentation
  • Audit log backend (future enhancement)
  • Bulk import/export of agreements

Endpoints

Available (backend implemented)

MethodPathDescription
GET/admin/reciprocity/agreementsList agreements with filters
GET/admin/reciprocity/agreements/:idGet single agreement
POST/admin/reciprocity/agreementsCreate agreement
PUT/admin/reciprocity/agreements/:idUpdate agreement
DELETE/admin/reciprocity/agreements/:idDelete agreement
GET/admin/reciprocity/networks/membershipsList network memberships
PUT/admin/reciprocity/networks/membershipsUpsert membership
DELETE/admin/reciprocity/networks/:networkCode/clubs/:clubIdRemove membership
GET/admin/reciprocity/home-clubs/:providerCodeGet home-club map diagnostics
PUT/admin/reciprocity/home-clubs/:providerCodeUpdate home-club map
POST/admin/reciprocity/previewPreview eligibility

Additional (implemented beyond original spec)

MethodPathDescription
GET/admin/benefits/providersList provider codes for dropdowns
GET/admin/reciprocity/diagnosticsCombined policy + provider + home-club diagnostics
GET/admin/reciprocity/agreements/:id/auditGet audit history for agreement (v1.2)

UX Surfaces

1. Navigation

Benefits & Reciprocity entry in Admin sidebar under "Configuration" section.

Admin
├── Dashboard
├── Clubs
├── Configuration
│ ├── Benefits & Reciprocity ← NEW
│ ├── Rate Rules
│ └── Specials
└── ...

2. Agreements Tab (default)

Layout: Filter bar + data table + slide-out drawer for create/edit.

Filters:

FilterTypeOptions
TypeSelectAll / Network / Bilateral
NetworkAutocompleteNetwork codes from memberships
ClubAutocompleteClub search by name/slug
StatusToggleActive / Inactive / All

Table Columns:

ColumnDescriptionSortable
TypeBadge: NETWORK or BILATERALYes
PartiesNetwork code or "Club A ↔ Club B"No
DirectionBOTH / A→B / B→ANo
DiscountSummary: "15%" or "R250 fixed"No
PriorityNumber (lower = higher priority)Yes
ValidDate range or "Ongoing"Yes
StatusActive/Inactive toggleNo
UpdatedRelative timestampYes

Row Actions:

  • Edit → Opens drawer with form
  • Clone → Opens drawer pre-filled (clears ID)
  • Delete → Confirmation modal

Create/Edit Drawer Fields:

┌─────────────────────────────────────────────┐
│ [×] Create Agreement │
├─────────────────────────────────────────────┤
│ Type* [Network ▾] │
│ │
│ ┌─ Network Agreement ─────────────────────┐ │
│ │ Network Code* [SAGA_NETWORK ▾] │ │
│ └─────────────────────────────────────────┘ │
│ │
│ OR │
│ │
│ ┌─ Bilateral Agreement ───────────────────┐ │
│ │ Club A* [Randpark Golf Club ▾] │ │
│ │ Club B* [Glendower Golf Club ▾] │ │
│ │ Direction [Both ▾] │ │
│ └─────────────────────────────────────────┘ │
│ │
│ ─── Validity ───────────────────────────── │
│ Start Date* [2025-01-01] │
│ End Date [________] (optional) │
│ Active [✓] │
│ │
│ ─── Rate Configuration ─────────────────── │
│ Discount Type* [Percent ▾] │
│ Value* [15] % │
│ Priority [100] │
│ │
│ ─── Restrictions (optional) ────────────── │
│ Valid Days [☑Mon ☑Tue ☑Wed ☑Thu ☐Fri...│
│ Time Window [06:00] to [14:00] │
│ Blackout Dates [+ Add date] │
│ Handicap Range [0] to [24] │
│ │
│ ─── Preview ────────────────────────────── │
│ ┌──────────────────────────────────────┐ │
│ │ Members get 15% off visitor rates. │ │
│ │ Priority 100 (applies after p<100). │ │
│ └──────────────────────────────────────┘ │
│ │
│ [Cancel] [Save Agreement] │
└─────────────────────────────────────────────┘

3. Networks Tab

Layout: Accordion per network code with member list.

┌─────────────────────────────────────────────┐
│ Networks [+ Add Network] │
├─────────────────────────────────────────────┤
│ ▼ SAGA_NETWORK (12 clubs) │
│ ┌─────────────────────────────────────┐ │
│ │ Club │ Status │ Action │ │
│ ├───────────────────┼────────┼────────┤ │
│ │ Randpark Golf │ Active │ [🗑] │ │
│ │ Glendower Golf │ Active │ [🗑] │ │
│ │ Steenberg Golf │ Inactive│ [🗑] │ │
│ └─────────────────────────────────────┘ │
│ [+ Add Club to SAGA_NETWORK] │
│ │
│ ▶ OPEN_FAIRWAYS (8 clubs) │
│ ▶ ERNIE_ELS_TRAIL (5 clubs) │
└─────────────────────────────────────────────┘

Add Membership Modal:

  • Club selector (autocomplete)
  • Active toggle (default: true)

4. Home-Club Map Tab

Layout: Provider selector + key-value editor + diagnostics panel.

┌─────────────────────────────────────────────┐
│ Home-Club Mappings │
├─────────────────────────────────────────────┤
│ Provider: [SAGA_NETWORK ▾] [Refresh] │
│ │
│ ┌─ Mappings ────────────────────────────┐ │
│ │ Provider Code │ TeeTime Club │ │
│ ├──────────────────┼────────────────────┤ │
│ │ RANDPARK │ randpark-firethorn │ │
│ │ RPGC │ randpark-bushwillow│ │
│ │ GLENDOWER │ glendower │ │
│ │ [+ Add mapping] │ │
│ └───────────────────────────────────────┘ │
│ │
│ ┌─ Diagnostics ─────────────────────────┐ │
│ │ ⚠ 2 warnings │ │
│ │ • STEENBERG: No matching club found │ │
│ │ • RPGC: Mapped to inactive club │ │
│ │ │ │
│ │ ✓ 15 mappings resolved successfully │ │
│ └───────────────────────────────────────┘ │
│ │
│ [Discard Changes] [Save Map] │
└─────────────────────────────────────────────┘

5. Eligibility Preview Tab

Layout: Input form + results panel (side-by-side on desktop).

┌──────────────────────┬──────────────────────┐
│ Test Eligibility │ Results │
├──────────────────────┼──────────────────────┤
│ Tenant* [______] │ │
│ Club* [______] │ ┌─ Eligibility ────┐ │
│ Member #* [______] │ │ ✓ ELIGIBLE │ │
│ │ │ │ │
│ Provider [SAGA_..] │ │ Base: R500.00 │ │
│ Home Club [______] │ │ Disc: -R75.00 │ │
│ │ │ Final: R425.00 │ │
│ Tee Date [______] │ └──────────────────┘ │
│ Tee Time [__:__] │ │
│ Handicap [__] │ ┌─ Applied ────────┐ │
│ │ │ 1. SAGA_NETWORK │ │
│ Stacking: │ │ 15% off │ │
│ ○ Best Price │ │ -R75.00 │ │
│ ● Stack All │ └──────────────────┘ │
│ │ │
│ [Run Preview] │ [Copy as JSON] │
└──────────────────────┴──────────────────────┘

Result States:

  • Eligible: Green checkmark, shows discount breakdown
  • Not Eligible: Red X, shows reason (e.g., "No matching agreement", "Blackout date")
  • Loading: Skeleton loader
  • Error: Red alert with API error message

Functional Rules

Agreement Validation

TypeRequired FieldsValidation
BILATERALclubAId, clubBId, startDateclubA ≠ clubB
NETWORKnetworkCode, startDatenetworkCode must exist

Rate Configuration

Discount TypeRequired FieldExample
PERCENTdiscountValue (0-100)15% off
FIXED_AMOUNTdiscountValue (cents)R50 off
FIXED_RATEfixedRateCentsR250 flat rate
RATE_TIERrateTierCodeUse "MEMBER" tier

Priority & Stacking

  • Priority: Lower number = higher priority (default: 100)
  • BEST_PRICE mode: Applies single best discount
  • STACK mode: Applies bilateral agreements in priority order, then network agreement

Data Formats

FieldFormatExample
TimesHH:mm (24h)06:00, 14:30
DatesISO 86012025-12-25
Days of week3-letter codesMON, TUE, WED

Validation & Error UX

Client-side Validation

  • Required field indicators (*)
  • Inline validation messages on blur
  • Disable submit until form valid
  • Type-specific field visibility (show networkCode OR club selectors)

API Error Handling

  • Preserve form state on failure
  • Show error banner with message
  • Highlight affected fields when possible
  • Retry button for network errors

Destructive Actions

  • Delete agreement: "Delete agreement between [parties]? This cannot be undone."
  • Remove membership: "Remove [club] from [network]?"
  • Clear home-club map: Confirmation required

Success Feedback

  • Toast notification on save/delete
  • Table row highlight animation on create
  • Auto-close drawer on successful save

Accessibility

  • All form inputs labeled with aria-label or visible labels
  • Focus management: trap focus in modals/drawers
  • Keyboard navigation: Tab through form, Escape to close
  • Screen reader announcements for state changes
  • Color-blind safe status indicators (icons + color)
  • Minimum touch target: 44×44px on mobile

Keyboard Shortcuts (v1.1)

ShortcutActionContext
/Focus search inputAgreements tab
Ctrl/Cmd+NOpen create drawerAgreements tab
EscapeClose drawer/modalAny

Enhancements (v1.1)

Features implemented beyond original spec:

Optimistic UI

  • Inline toggle: Status switches update immediately, rollback on failure
  • Undo delete: Toast with "Undo" button for 5 seconds after deletion
  • Row highlight: Newly created rows highlight blue and auto-scroll into view

Conflict Detection

  • Overlap warnings: Form shows Alert when date range overlaps existing agreement for same network/club pair
  • Duplicate club prevention: Bilateral agreements validate Club A ≠ Club B inline

Filter Enhancements

  • Removable pills: Active filters shown as closable tags below filter bar
  • Network/Club filters: Autocomplete dropdowns for precise filtering
  • "Clear filters" button: One-click reset when filters active

Form Improvements

  • Day presets: Quick buttons for "All days", "Weekdays", "Weekends"
  • Time/blackout summaries: Inline text showing selected restrictions
  • End-date validation: Prevents end date before start date

Home-Club Map

  • Per-row diagnostics: Warning icons on individual mapping rows
  • Issue counts: Badge showing total warnings per provider

Diagnostics Tab

  • Dedicated tab for combined diagnostics view (bonus surface)

Bulk Operations (v1.1, enhanced v1.2)

  • Multi-select: Checkbox column for row selection
  • Bulk toggle: Enable/disable multiple agreements at once (with loading state)
  • Bulk delete: Delete multiple agreements with confirmation
  • Selection bar: Shows count with Activate/Deactivate/Delete/Clear buttons
  • Optimistic updates: Bulk ops update UI immediately, rollback on failure

Import/Export (v1.2)

  • Export CSV: Exports filtered agreements to CSV file (benefits-agreements.csv)
  • Import CSV: Modal with text input, preview parsing, header mapping
  • CSV format:
    id,type,networkCode,clubAId,clubBId,discountType,discountValue,priority,
    startDate,endDate,isActive,validDays,timeWindowStart,timeWindowEnd,blackoutDates
  • Multi-value fields: validDays and blackoutDates use ; separator

Audit History (v1.2)

  • History action: Per-row "History" menu item in actions dropdown
  • History drawer: Shows timeline of changes (create, update, delete)
  • Audit entry fields: action, actor, timestamp, details, changes (JSON diff)
  • Endpoint: GET /admin/reciprocity/agreements/:id/audit
  • Error states: Loading spinner, error alert if backend unavailable

Component Library

Use existing Ant Design components from teetime-admin:

ComponentUsage
TableAgreements list with sorting/filtering
DrawerCreate/edit agreement form
Form / Form.ItemAll form fields with validation
Select / AutoCompleteClub/network selectors
DatePickerStart/end dates, blackout dates
TimePickerTime window fields
SwitchActive toggles
Checkbox.GroupDays of week selector
InputNumberPriority, handicap, discount values
TagType badges, status indicators
CollapseNetwork accordions
AlertDiagnostics warnings
ModalConfirmation dialogs
messageSuccess/error toasts

Telemetry

Events to Track

EventProperties
benefits_admin.agreement.createtype, hasRateConfig, success
benefits_admin.agreement.updateagreementId, changedFields, success
benefits_admin.agreement.deleteagreementId, type
benefits_admin.membership.addnetworkCode, clubId
benefits_admin.membership.removenetworkCode, clubId
benefits_admin.homeclubmap.updateproviderCode, mappingCount, warningCount
benefits_admin.preview.runapplicable, stackingMode, appliedCount, durationMs

Metrics

  • Time to complete agreement creation flow
  • Preview API latency (p50, p95)
  • Error rate by operation type

Testing

Unit Tests

  • Form validation helper functions
  • DTO transformation (API ↔ form values)
  • Stacking priority calculation display
  • Date/time formatting utilities

React Testing Library

describe('AgreementsTable', () => {
it('renders agreements with correct badges');
it('filters by type when selected');
it('calls delete API with confirmation');
});

describe('AgreementDrawer', () => {
it('shows network fields when type=NETWORK');
it('shows bilateral fields when type=BILATERAL');
it('validates required fields before submit');
it('displays rate config summary');
});

describe('EligibilityPreview', () => {
it('shows eligible state with discount breakdown');
it('shows ineligible state with reason');
it('displays applied agreements list');
});

describe('HomeClubMap', () => {
it('renders diagnostics warnings');
it('validates mappings before save');
});

E2E Tests (Playwright) — Implemented (v1.1)

Location: apps/teetime/teetime-admin-e2e/src/benefits-admin.e2e.ts

test('loads benefits admin and filters agreements', async ({ page }) => {
// Stubs reciprocity APIs with mock data
// Navigates to /benefits
// Verifies Agreements tab loads with SAGA_NETWORK agreement
// Tests network filter selection
// Opens and closes create drawer
});

Run: pnpm nx run teetime-admin-e2e:e2e -- --project=chromium

Planned additional E2E tests:

  1. Create network agreement → verify in table
  2. Add club to network → verify membership count
  3. Run eligibility preview → verify discount applied
  4. Update home-club map → verify diagnostics refresh

Rollout

Feature Flag

// Gate on nav entry and routes
if (featureFlags.ENABLE_BENEFITS_ADMIN) {
router.addRoute('/admin/benefits', BenefitsAdminPage);
sidebar.addItem({ label: 'Benefits & Reciprocity', path: '/admin/benefits' });
}

Phased Rollout

  1. Phase 1: Internal testing (DigiWedge staff only)
  2. Phase 2: Pilot clubs (SAGA network operators)
  3. Phase 3: General availability
  • Help icon in header → /docs/benefits/admin
  • "Learn more" links in form sections → /docs/benefits/reciprocity
  • Error states include link to troubleshooting guide

Open Questions

  1. Bulk operations: Should we support bulk enable/disable of agreements?Implemented v1.1
  2. History/audit: Show last-modified-by and change history?Implemented v1.2
  3. Import/export: CSV import for home-club mappings?Implemented v1.2 (agreements import/export)
  4. Notifications: Alert when agreements expire soon?
  5. Home-club map import: CSV import for home-club mappings (agreements done, mappings pending)

Revision History

DateAuthorChanges
2025-12-05RudiInitial draft with wireframes
2025-12-05RudiAdded accessibility, component library, enhanced testing
2025-12-06Rudiv1.1: Keyboard shortcuts, optimistic UI, conflict detection, filter pills, day presets, bulk operations, CSV import, E2E tests
2025-12-06Rudiv1.2: CSV export, import modal with preview, audit history drawer, bulk action loading states, selection bar UX