Skip to content

Basic Design (UI Specification)

ProjectPTX Channel Manager (ptx-cm)
Version2.2.0
Date2026-02-20
StyleProfessional Blue (Cloudbeds/SiteMinder-inspired)

1. Design System

1.1 Reference Source

  • Style: Professional Blue — hotel operations dashboard (Cloudbeds/SiteMinder inspired)
  • Rationale: Staff use 8+ hours/day; blue palette reduces eye strain, conveys trust/reliability, suits data-dense operational dashboards

1.2 Color Palette

TokenValueUsage
--color-primary#1E3A5FSidebar, nav headers, primary actions
--color-primary-light#2B5A8FHover states, active nav items
--color-primary-dark#0F1F33Sidebar active state, deep headers
--color-accent#3B82F6Links, interactive elements, focus rings
--color-accent-light#60A5FAHover on links, secondary buttons
--color-success#22C55ESynced status, available rooms, confirmed bookings
--color-warning#F59E0BSession expiring, low availability, sync delayed
--color-danger#EF4444Overbooking alert, sync failed, session expired
--color-info#06B6D4Informational badges, tooltips
--color-bg-page#F1F5F9Page background (slate-100)
--color-bg-card#FFFFFFCards, panels, modals
--color-bg-sidebar#1E3A5FSidebar background
--color-text-primary#0F172AHeadings, body text (slate-900)
--color-text-secondary#64748BDescriptions, labels (slate-500)
--color-text-muted#94A3B8Placeholders, disabled text (slate-400)
--color-text-inverse#FFFFFFText on dark backgrounds
--color-border#E2E8F0Card borders, dividers (slate-200)
--color-border-focus#3B82F6Input focus ring

1.3 OTA Brand Colors (Status Indicators)

OTAColorUsage
Booking.com#003580OTA badge, channel label
Agoda#5F2688OTA badge, channel label
Traveloka#0194F3OTA badge, channel label
Expedia#FBCE00OTA badge, channel label (dark text)

1.4 Country Display

Countries displayed as rounded pill badges matching OTA badge style (Section 3.2). 2-letter country code on colored background. White text for contrast. No flag emojis — text-only for cross-platform consistency.

CodePill BG ColorText ColorExample
VN#DA251D#FFFFFF[VN]
ID#CE1126#FFFFFF[ID]
MY#010066#FFFFFF[MY]

Style: Same border-radius: 9999px (pill), padding: 3px 10px, font-size: 12px/600 as OTA badges. Ensures visual consistency between OTA and country indicators.

Contrast: All combinations meet WCAG AA contrast ratio (white text on dark backgrounds). Indonesia uses #CE1126 (darker crimson) instead of #FF0000 for better white text readability.

Usage: Property tables, booking rows, user lists, dashboard dropdown.

1.5 Format Presets (Per-User)

Each user selects a display format preset via Settings. Frontend uses Intl API.

PresetLocaleDateNumberCurrency Example
Vietnamesevidd/MM/yyyy1.000.0001.000.000 VND
Indonesianiddd/MM/yyyy1.000.000Rp 1.000.000
Malaymsdd/MM/yyyy1,000,000RM 1,000,000
Englishenyyyy-MM-dd1,000,000VND 1,000,000

Default: en. Stored in User.locale field. Sync logs (S-17) always use ISO format regardless of user locale.

1.6 Typography

TokenValueUsage
--font-sans'Inter', system-ui, -apple-system, sans-serifAll UI text
--font-mono'JetBrains Mono', 'Fira Code', monospaceIDs, timestamps, logs
--text-h124px / 700Page titles
--text-h220px / 600Section headers
--text-h316px / 600Card titles, subsections
--text-h414px / 600Labels, small headings
--text-body14px / 400Body text, table cells
--text-small13px / 400Helper text, timestamps
--text-caption12px / 400Badges, meta info

1.7 Spacing Scale

TokenValue
--space-xs4px
--space-sm8px
--space-md16px
--space-lg24px
--space-xl32px
--space-2xl48px
--space-3xl64px

1.8 Border Radius

TokenValueUsage
--radius-sm4pxInputs, small badges
--radius-md8pxCards, buttons, dropdowns
--radius-lg12pxModals, large panels
--radius-full9999pxAvatars, pill badges, OTA dots

1.9 Shadows

TokenValueUsage
--shadow-sm0 1px 2px rgba(0,0,0,0.05)Cards at rest
--shadow-md0 4px 6px rgba(0,0,0,0.07)Cards on hover, dropdowns
--shadow-lg0 10px 15px rgba(0,0,0,0.1)Modals, floating panels

1.10 CJX Stage Variables

StageDescriptionScreens
OnboardingFirst-time setup: connect OTA accounts, import properties, map roomsS-05, S-06, S-07, S-04
UsageDaily operations: monitor sync, review bookings, manage availability, manage own profileS-02, S-08, S-09, S-10, S-18
RetentionEfficiency gains: bulk rate updates, analytics, role management, workflow configS-11, S-12, S-14, S-19, S-20
DiscoveryAdvanced features: rate rules, parity checkerS-13, S-15

2. Layout Structure

2.1 Shell Layout

┌──────────────────────────────────────────────────────┐
│ [Sidebar 240px]  │  [Main Content Area]              │
│                  │                                    │
│  Logo            │  ┌─ Top Bar ───────────────────┐  │
│  ─────────       │  │ [🇻🇳 VN] ▾              🔔 👤 │  │
│  Dashboard       │  └────────────────────────────┘  │
│  Properties      │                                    │
│  Bookings        │  ┌─ Content ──────────────────┐  │
│  Calendar        │  │                              │  │
│  Rates           │  │  [Page-specific content]     │  │
│  Reports         │  │                              │  │
│  ─────────       │  │                              │  │
│  OTA Accounts    │  └──────────────────────────────┘  │
│  Sync Logs       │                                    │
│  Settings        │                                    │
│  ─────────       │                                    │
│  OTA Status      │                                    │
│  ● Booking.com   │                                    │
│  ● Agoda         │                                    │
│  ○ Traveloka     │                                    │
│  ✕ Expedia       │                                    │
└──────────────────────────────────────────────────────┘
  • Sidebar: Fixed, 240px wide, --color-bg-sidebar background
  • Sidebar items: Visibility controlled by user's role permissions. Items where user has no View permission are hidden. Uses hasPermission(module, VIEW) check.
  • Sidebar bottom: Real-time OTA account status summary (dots: green=active, yellow=expiring, red=expired/error)
  • Top bar: Country dropdown selector ([VN] ▾ pill badge style), notification bell (alert count badge), user avatar/menu. Top bar has padding-top: 8px for visual breathing room.
  • Content area: Scrollable, --color-bg-page background, max-width 1440px
  • Country selector: Persists last selection in localStorage; staff users locked to their assigned country

2.2 Responsive Breakpoints

BreakpointLayout
>= 1280pxFull sidebar + content
768-1279pxCollapsible sidebar (icons only), content expands
< 768pxHidden sidebar (hamburger toggle), full-width content

3. Component Patterns

3.1 Status Badge

[● Active]    — green bg, green text, rounded pill
[⚠ Expiring]  — yellow bg, yellow text
[✕ Expired]   — red bg, red text
[○ Inactive]  — gray bg, gray text
[? 2FA]       — blue bg, blue text (requires manual login)

3.2 OTA Channel Badge

[Booking.com]  — #003580 bg, white text, rounded pill
[Agoda]        — #5F2688 bg, white text
[Traveloka]    — #0194F3 bg, white text
[Expedia]      — #FBCE00 bg, dark text

3.3 Country Badge

Same visual style as OTA badges (Section 3.2) — rounded pill with 2-letter code, no flag emojis.

[VN]  — #DA251D bg, white text, rounded pill
[ID]  — #CE1126 bg, white text, rounded pill
[MY]  — #010066 bg, white text, rounded pill

Matches OTA badge dimensions and border-radius. Tooltip shows full country name on hover.

3.4 KPI Card

┌─────────────────────┐
│ Total Bookings Today │  ← label (text-secondary)
│ 47                   │  ← value (text-h1, text-primary)
│ ↑ 12% vs yesterday  │  ← trend (text-small, success/danger)
└─────────────────────┘

3.5 Data Table

  • Header: --color-bg-page background, --text-h4 font
  • Rows: White background, hover --color-bg-page
  • Borders: Bottom border --color-border
  • Pagination: Bottom right, page numbers + per-page selector
  • Sorting: Clickable headers with arrow indicators
  • Filtering: Dropdown filters above table

3.6 Alert Banner

┌─ 🔴 ─────────────────────────────────────────────┐
│ OVERBOOKING: Property "Siam Lodge" - Deluxe Room  │
│ Date: 2026/02/15 | Booked: 12/10 rooms             │
│ Sources: Booking.com (2), Agoda (1)   [Resolve →]  │
└───────────────────────────────────────────────────┘
  • Critical: Red left border + light red background
  • Warning: Yellow left border + light yellow background
  • Info: Blue left border + light blue background

3.7 OTA Account Card

┌──────────────────────────────────────┐
│ [Booking.com]  ● Active              │
│ Main Account                          │
│ 12 properties | Last sync: 2 min ago │
│ [Manage] [Refresh] [Import]          │
└──────────────────────────────────────┘

3.8 Property Import Row

┌──────────────────────────────────────────────────┐
│ ☐ Grand Palace Hotel, Ho Chi Minh City           │
│   5 room types | 42 rooms total                   │
│   Match: "Grand Palace HCMC" (Agoda) [✓ Linked]  │  ← cross-OTA match
└──────────────────────────────────────────────────┘

4. Screen Specifications

S-01: Login Screen

  • Phase: P1
  • Layout: Centered card on --color-primary-dark full-page background
  • Elements:
    • Logo + "PTX Channel Manager" heading
    • Email input (required, type=email)
    • Password input (masked, toggle visibility)
    • "Sign In" button (primary, full-width)
    • Error toast on failed login
  • Transitions:
    • Success → S-02 Dashboard
    • Error → Inline error message

S-02: Dashboard

  • Phase: P1
  • Layout: Grid layout with country tabs + KPI row + 2-column content + activity log panel (super admin only)
  • CJX Stage: Usage

Activity Log Panel (Super Admin Only):

  • Visibility: Only visible when user has super_admin role
  • Position: Right column, above OTA Account Status (if admin) or alone (if non-admin)
  • Style: Dark terminal-style panel with monospace font (--font-mono)
  • Refresh: SWR polling every 5 seconds, pausable by user
  • Display: Last N entries from activity log file (default: 100 entries, user can increase via limit selector 1-500)
  • Log Format: Each row shows: TIMESTAMP | EMAIL | METHOD | PATH | STATUS | SCREEN
  • Method Badges: Color-coded HTTP method pills
    • GET: Gray (#6B7280)
    • POST: Green (#22C55E)
    • PATCH: Blue (#3B82F6)
    • PUT: Yellow (#F59E0B)
    • DELETE: Red (#EF4444)
  • Status Colors:
    • 2xx/3xx: White text on dark background (default)
    • 4xx: Amber text
    • 5xx: Red text
  • Controls: Pause/Resume toggle button, Limit selector (1-500 entries), Auto-scroll toggle

Country Selector (top bar):

  • Dropdown select styled like OTA badges: selected value shows as pill badge [VN] ▾
  • Dropdown options: [VN] Vietnam, [ID] Indonesia, [MY] Malaysia, [All] All Countries
  • Each option shows country pill badge + full name for clarity
  • Staff: locked to assigned country (tabs disabled)
  • Manager: free selection, "All" default
  • Persisted in localStorage

KPI Row (4 cards):

CardDataSource
Properties OnlineCount of properties (in selected country) with all OTAs syncedE-05 via E-04 status
Today's BookingsNew bookings detected today (in selected country)E-08 created_at
Sync Health% of OTA accounts with status=activeE-04 status
Active AlertsCount of unresolved alerts (in selected country)E-11 is_resolved

Left Column (60%):

  • Alert Panel: Unresolved alerts sorted by severity. Each: property name, alert type, message, timestamp, [Resolve] button
  • Recent Bookings: Last 10 bookings. Columns: Property, Guest, OTA (badge), Check-in, Check-out, Amount, Status

Right Column (40%):

  • OTA Account Status: Per-account accordion. Each shows: OTA badge, status dot, properties count, last sync time, pending jobs
  • Quick Actions: "Connect OTA Account" button, "Refresh All Sessions" button, "Force Sync" button

S-03: Properties List

  • Phase: P1
  • Layout: Data table with action buttons
  • CJX Stage: Onboarding / Usage
  • Elements:
    • "Add Property" button (secondary, top right) — manual creation fallback
    • Property table columns: Name, Country (pill badge), Timezone, Currency, OTA Connections (dots), Sync Status, Actions (Edit/View)
    • Filter: Country dropdown (auto-set for staff), Status dropdown
    • Search: Property name search
  • Transitions:
    • Click property name → S-04 Property Detail
    • Click "Add Property" → S-04 (create mode)

S-04: Property Detail

  • Phase: P1
  • Layout: Tabbed interface within property context
  • CJX Stage: Onboarding

Info Tab:

  • Property name, country, timezone, currency, address (edit form)
  • Save/Cancel buttons

Room Types Tab:

  • Room type table: Name, Base Rate, Total Rooms, Max Occupancy, Suppliers (allocation badges), Actions
  • "Suppliers" column: compact badges showing SupplierName: N per allocation. Warning badge (⚠) if SUM != totalRooms. Pencil icon → S-23 Supplier Allocation Manager modal.
  • "Add Room Type" button
  • Inline edit or modal form

OTA Connections Tab:

  • List of OTA connections linked via OtaAccount
  • Each row shows: OTA badge, OTA account label, OTA property ID, status (from parent OtaAccount), is_active toggle
  • Room mapping sub-table per connection: internal room → OTA room/rate plan
  • "Edit Mapping" action per connection

S-05: OTA Accounts List

  • Phase: P1
  • Layout: Card grid or data table
  • CJX Stage: Onboarding
  • Elements:
    • "Connect OTA Account" button (primary, top right)
    • OTA account cards (see 3.7 pattern): OTA badge, label, status, property count, last sync, actions
    • Filter: OTA type, Status
    • Actions per card: [Manage] → edit credentials, [Refresh] → refresh session, [Import] → S-07, [Delete]
  • Transitions:
    • Click "Connect OTA Account" → S-06
    • Click [Import] → S-07

S-06: Connect OTA Account

  • Phase: P1
  • Layout: Step wizard (2 steps)
  • CJX Stage: Onboarding

Step 1: OTA & Credentials

  • OTA selector: 4 OTA cards with brand colors (click to select)
  • Login form (email/username + password)
  • 2FA method selector: None / TOTP (enter secret) / Manual Login
  • If Manual Login: "Open Extranet" button launches Playwright browser for manual authentication. System captures session after login.
  • "Test Connection" button

Step 2: Confirmation

  • Connection test result (success/fail)

  • If success: OTA account label input, "Save & Import Properties" or "Save & Skip Import"

  • If fail: Error message, retry option

  • Transitions:

    • "Save & Import Properties" → S-07
    • "Save & Skip Import" → S-05

S-07: Import Properties ❌ NOT IMPLEMENTED

  • Phase: P1
  • Implementation Status: OTA adapter fetchProperties() is a stub. No import UI exists.
  • Layout: Property selection list with matching UI
  • CJX Stage: Onboarding
  • Elements:

Property Discovery:

  • Auto-fetches properties from OTA account on load
  • Loading state: skeleton list
  • Property list with checkboxes (see 3.8 pattern): name, city, country, room type count, room count

Cross-OTA Matching (when 2nd+ OTA connected):

  • For each discovered property, show best match from existing properties
  • Match confidence: High (auto-linked, editable) / Low (suggest, ask) / None (create new)
  • User can override: select different existing property or "Create New"

Actions:

  • "Import Selected" button → creates properties, room types, connections, mappings

  • Progress indicator during import

  • Success summary: N properties imported, N room types created, N mappings set

  • Transitions:

    • Import complete → S-05 (with success toast)

S-08: Bookings List

  • Phase: P1
  • Layout: Filterable data table
  • CJX Stage: Usage
  • Elements:
    • Filters row: Property (dropdown), OTA (multi-select badges), Status (confirmed/cancelled), Date range (check-in), Country (auto-set for staff)
    • Table columns: Property, Guest Name, OTA (badge), Room Type, Check-in, Check-out, Rooms, Amount, Status (badge), Detected At
    • Sort by any column
    • Pagination (25/50/100 per page)
    • Export CSV button
  • Transitions:
    • Click row → S-09 Booking Detail

S-09: Booking Detail

  • Phase: P1 (base), P2 (workflow enhancement)
  • Layout: Header + card grid (2-col) with conditional visibility
  • CJX Stage: Usage
  • Related FR: FR-03, FR-26, FR-28

Header (BookingHeader):

  • Back button + "Booking Detail" title
  • StatusBadge with dynamic color from BookingStatusDef (fallback to hardcoded STATUS_COLORS)
  • Status transition buttons row (P2): one button per available transition for current status + user role
  • Each transition button shows target status label + color dot
  • Buttons hidden if user lacks Bookings EDIT permission or no transitions available

Card Grid (2-column on lg, 1-column on mobile):

Guest Info Card (conditional visibility via uiConfig.sections):

  • Guest name (inline editable if in uiConfig.editableFields)
  • Guest email (inline editable if in uiConfig.editableFields)
  • Number of guests (inline editable if in uiConfig.editableFields)
  • Number of rooms (inline editable if in uiConfig.editableFields)

Booking Info Card (conditional visibility):

  • OTA source (badge with brand color)
  • OTA booking ID (monospace, copyable)
  • Booking status (StatusBadge with dynamic color)
  • Total amount + currency

Stay Details Card (conditional visibility):

  • Check-in / Check-out dates
  • Number of nights (calculated)

Property & Room Card (conditional visibility):

  • Property name
  • Room type
  • Created at timestamp

Booking History Section (below card grid):

  • Vertical timeline of audit log entries fetched from GET /bookings/:id/history
  • Each entry shows: dynamic status icon (from BookingStatusDef.icon), action description, actor name, timestamp (user's dateFormat)
  • Entries connected by vertical line for visual continuity
  • OTA sync events (action=create from system) show with OTA brand icon
  • "Revert" action available on latest history entry if user has Bookings EDIT permission
  • Revert opens confirmation dialog → PATCH /bookings/:id/revert
  • Sync history: When booking was detected, when availability was pushed to other OTAs (from syncHistory in booking detail response)
  • Raw OTA data (collapsible JSON view, visible per uiConfig)

Status Transition Dialog (P2):

  • Modal opens on transition button click
  • Shows: "Change status from {current} to {target}?"
  • Optional note textarea
  • Confirm/Cancel buttons
  • Loading spinner during API call
  • On confirm: PATCH /bookings/:id/status → refresh booking data via SWR mutate

Data Flow (P2):

useApiGet('bookings/{id}') → booking data
useApiGet('booking-status/workflow') → { statuses, transitions } (SWR cached)
useAuth() → user.roleName

Resolve transitions: filter by fromKey=booking.status + role in allowedRoles
Resolve uiConfig: statuses[booking.status].uiConfig[roleName] || uiConfig['*']
Resolve statusColor: statuses[booking.status].color

Graceful Degradation:

  • If workflow not loaded: show all sections, hide transition buttons
  • If no transitions for current status: show no buttons
  • If user lacks BOOKINGS.EDIT: hide all mutation UI

Layout (P2 enhanced):

┌─────────────────────────────────────────────────────┐
│  ← Back    Booking Detail                            │
│                                                       │
│  [● Confirmed]   [→ Check-in] [→ Cancel] [→ No Show]│
│                                                       │
│  ┌─ Guest Info ────────┐  ┌─ Booking Info ─────────┐│
│  │ Name: John Doe  [✎] │  │ OTA: [Booking.com]     ││
│  │ Email: j@mail   [✎] │  │ Ref: BC-123456         ││
│  │ Guests: 2       [✎] │  │ Status: [● Confirmed]  ││
│  │ Rooms: 1        [✎] │  │ Amount: 2,500 THB      ││
│  └──────────────────────┘  └────────────────────────┘│
│                                                       │
│  ┌─ Stay Details ──────┐  ┌─ Property & Room ──────┐│
│  │ Check-in:  02/15    │  │ Property: Siam Lodge   ││
│  │ Check-out: 02/18    │  │ Room: Deluxe Double    ││
│  │ Nights: 3           │  │ Created: 02/14 09:30   ││
│  └──────────────────────┘  └────────────────────────┘│
└─────────────────────────────────────────────────────┘

S-10: Availability Calendar ❌ NOT IMPLEMENTED

  • Phase: P2
  • Implementation Status: No route, no component, no page exists.
  • Layout: Grid — rows: room types, columns: dates (14-day view default)
  • CJX Stage: Usage
  • Elements:
    • Property selector (dropdown, top)
    • Date range navigation: < [02/01-02/14] >
    • Grid cells show: available / total (e.g., "3/10")
    • Cell colors: Green (>50% available), Yellow (20-50%), Red (<20%), Black (0 available)
    • Click cell → popover to block/unblock rooms
    • Block action → triggers availability push to all OTAs
    • Legend bar at bottom

S-11: Rate Manager ❌ NOT IMPLEMENTED

  • Phase: P2
  • Implementation Status: No route, no component, no backend API. Rate/RateRule DB models exist but unused.
  • Layout: Form + preview table
  • CJX Stage: Retention
  • Elements:
    • Property selector
    • Room type selector
    • Date range picker
    • Base rate input
    • Per-OTA adjustments: checkboxes + markup/discount percentage
    • Preview table: Date | Base Rate | Booking.com | Agoda | Traveloka | Expedia (calculated rates)
    • "Push Rates" button → confirmation modal → queues rate push jobs

S-12: Booking Timeline ❌ NOT IMPLEMENTED

  • Phase: P2
  • Implementation Status: No route, no component, no page exists.
  • Layout: Horizontal Gantt chart
  • CJX Stage: Usage
  • Elements:
    • Y-axis: Room types (grouped by property)
    • X-axis: Dates (scrollable, 30-day default view)
    • Bars: Booking blocks, color-coded by OTA
    • Hover: Guest name, dates, OTA, amount
    • Click: Opens S-09 Booking Detail
    • Filters: Property, OTA, date range

S-13: Rate Parity Report ❌ NOT IMPLEMENTED

  • Phase: P3
  • Implementation Status: Not built.
  • Layout: Comparison table
  • CJX Stage: Discovery
  • Elements:
    • Property selector
    • Date range selector
    • Table: Room Type | Date | Booking.com Rate | Agoda Rate | Traveloka Rate | Expedia Rate | Parity Status
    • Parity status: "Match" (green) or "Mismatch +X%" (red)
    • Filter: Show mismatches only toggle

S-14: Analytics Dashboard ❌ NOT IMPLEMENTED

  • Phase: P3
  • Implementation Status: Not built.
  • Layout: Chart grid (2x2)
  • CJX Stage: Retention
  • Elements:
    • Date range selector (7d / 30d / 90d / custom)
    • Property filter (all or specific)
    • Country filter
    • Chart 1: Revenue by OTA (stacked bar chart, monthly)
    • Chart 2: Occupancy rate trend (line chart, daily)
    • Chart 3: Booking source distribution (donut chart)
    • Chart 4: ADR trend (line chart, daily)
    • Summary KPI cards below charts: Total Revenue, Avg Occupancy, Avg ADR, Total Bookings

S-15: Rate Rules Config ❌ NOT IMPLEMENTED

  • Phase: P3
  • Implementation Status: Not built.
  • Layout: Rule list + add form
  • CJX Stage: Discovery
  • Elements:
    • Active rules table: Property, Room Type, OTA, Rule Type, Value, Date Range, Status, Actions
    • "Add Rule" button → modal form:
      • Property selector
      • Room type selector (or "All")
      • OTA selector (or "All")
      • Rule type: Markup % | Discount % | Fixed override
      • Value input
      • Date range (optional, for seasonal)
      • Enable/disable toggle

S-16: Settings

  • Phase: P1
  • Layout: Tabbed settings page (4 tabs)
  • CJX Stage: Onboarding
  • Note: Users, Roles, Countries, Booking Statuses moved to S-24 Master Data (/master-data)

Workflow Tab (default):

  • Booking workflow configuration (same content as S-20, now embedded here)
  • Status list + Mermaid diagram + transition management + UI visibility config

Preferences Tab (per-user):

  • Display format: select dropdown (Vietnamese / Indonesian / Malay / English)
  • Live preview showing sample formatted values:
    • Date: 10/02/2026
    • Number: 1.000.000
    • Currency: 1.000.000 VND
  • Save button

Notifications Tab:

  • LINE Notify: Token input, test button
  • Email: SMTP config (host, port, user, password), test button
  • Alert preferences: Which alert types to notify on (checkboxes)

System Tab:

  • Sync intervals: Booking pull frequency (default 3 min), Verification frequency (default 10 min)
  • Buffer rooms: Default buffer per room type (0-3)
  • Session refresh interval
  • Audit log retention: Number of days to keep audit logs (default 90)

S-17: Sync Job Log

  • Phase: P1
  • Layout: Filterable log table
  • CJX Stage: Usage
  • Elements:
    • Filters: Property, OTA Account, Job Type, Status, Date range
    • Table: Timestamp, Property, OTA (badge), Job Type, Status (badge), Duration, Error (truncated)
    • Click row → expand to show full error message + payload
    • Auto-refresh toggle (5s interval)
    • Clear completed jobs button

S-18: User Profile

  • Phase: P2
  • Layout: Single-column form card, max-width 640px, centered in content area
  • CJX Stage: Usage
  • Related FR: FR-16, FR-17, FR-19
  • Access: Top bar user avatar → "Profile" link, or direct navigation to /profile

Profile Info Section:

  • Card with heading "Profile"
  • Initials-based avatar (circle, --color-primary bg, --text-h1 white text, first letter of name)
  • Name input (editable, required)
  • Email input (editable, required, validates uniqueness on blur)
  • Country display (read-only text with pill badge for staff; read-only for all users — country assigned by manager)
  • Role display (read-only badge: "Manager" or "Staff")
  • Locale selector dropdown (Vietnamese / Indonesian / Malay / English) with live format preview:
    • Date: 10/02/2026
    • Number: 1.000.000
    • Currency: 1.000.000 VND
  • "Save Changes" button (primary, disabled until form dirty)
  • Success toast on save

Password Section:

  • Separate card with heading "Change Password"
  • Current password input (masked, required)
  • New password input (masked, required, min 8 chars)
  • Confirm new password input (masked, must match new)
  • Inline validation: password strength indicator, match check
  • "Update Password" button (primary)
  • Error toast on wrong current password (401)
  • Success toast + clear fields on success

Preferences Section:

  • Separate card with heading "Preferences"
  • Theme toggle: Light / Dark switch
  • Persisted in localStorage, applied immediately via CSS class on <html>
  • No backend call

Layout:

┌─────────────────────────────────────────┐
│  ← Back to Dashboard                    │
│                                          │
│  ┌─ Profile ──────────────────────────┐ │
│  │  [NV]  Nguyen Van                  │ │
│  │                                     │ │
│  │  Name:     [Nguyen Van          ]  │ │
│  │  Email:    [nguyen@ptx.com      ]  │ │
│  │  Country:  [VN]  Vietnam           │ │
│  │  Role:     [Staff]                 │ │
│  │  Locale:   [Vietnamese ▾]          │ │
│  │            Preview: 10/02/2026     │ │
│  │                                     │ │
│  │            [Save Changes]          │ │
│  └─────────────────────────────────────┘ │
│                                          │
│  ┌─ Change Password ─────────────────┐ │
│  │  Current:  [••••••••          ]    │ │
│  │  New:      [••••••••          ]    │ │
│  │  Confirm:  [••••••••          ]    │ │
│  │                                     │ │
│  │            [Update Password]       │ │
│  └─────────────────────────────────────┘ │
│                                          │
│  ┌─ Preferences ─────────────────────┐ │
│  │  Theme:    [Light ○ ● Dark]        │ │
│  └─────────────────────────────────────┘ │
└─────────────────────────────────────────┘
  • Transitions:
    • Back link → S-02 Dashboard
    • Save success → toast, stay on page
    • Password update success → toast, clear password fields

S-19: Role Management

  • Phase: P2
  • Layout: Full-width form with permission matrix grid
  • CJX Stage: Retention
  • Related FR: FR-20, FR-21, FR-22, FR-23
  • Access: Settings > Roles tab → "Create Role" or click existing role

Role Info Section:

  • Name input (slug format, e.g. warehouse_staff, used as key — disabled for system roles)
  • Label input (display name, e.g. "Warehouse Staff")
  • Description textarea (optional)

Permission Matrix Section:

  • Grid table: rows = modules, columns = permission actions (View, Create, Edit, Delete)
  • Each cell = checkbox (checked = bit set, unchecked = bit unset)
  • Module rows: Dashboard, Properties, Room Types, OTA Accounts, OTA Connections, Bookings, Availability, Sync Jobs, Rates, Rate Rules, Alerts, Settings, Users
  • Row header shows module name with icon
  • Column header shows action name with bit value (V=1, C=2, E=4, D=8)
  • "Select All" checkbox per row (sets all 4 bits = 15)
  • "Select All" checkbox per column (e.g., check all View permissions)
  • For system preset roles: checkboxes are editable (admin can adjust preset permissions)
  • Bitmask preview: shows computed integer per module (e.g., properties: 15)

Actions:

  • "Save Role" button (primary)
  • "Delete Role" button (danger, hidden for system roles)
  • "Duplicate Role" button (secondary — creates copy with "_copy" suffix)

Layout:

┌─────────────────────────────────────────────────────────┐
│  ← Back to Settings                                     │
│                                                          │
│  ┌─ Role Info ────────────────────────────────────────┐ │
│  │  Name:        [warehouse_staff        ]            │ │
│  │  Label:       [Warehouse Staff        ]            │ │
│  │  Description: [Manages warehouse...   ]            │ │
│  └────────────────────────────────────────────────────┘ │
│                                                          │
│  ┌─ Permissions ──────────────────────────────────────┐ │
│  │                     View  Create  Edit  Delete All │ │
│  │  ─────────────────────────────────────────────     │ │
│  │  Dashboard          [✓]   [ ]     [ ]   [ ]   [ ] │ │
│  │  Properties         [✓]   [✓]     [✓]   [✓]   [✓] │ │
│  │  Room Types         [✓]   [✓]     [✓]   [✓]   [✓] │ │
│  │  OTA Accounts       [✓]   [ ]     [ ]   [ ]   [ ] │ │
│  │  OTA Connections    [✓]   [ ]     [ ]   [ ]   [ ] │ │
│  │  Bookings           [✓]   [ ]     [✓]   [ ]   [ ] │ │
│  │  Availability       [✓]   [ ]     [✓]   [ ]   [ ] │ │
│  │  Sync Jobs          [✓]   [ ]     [ ]   [ ]   [ ] │ │
│  │  Rates              [✓]   [ ]     [ ]   [ ]   [ ] │ │
│  │  Rate Rules         [ ]   [ ]     [ ]   [ ]   [ ] │ │
│  │  Alerts             [✓]   [ ]     [✓]   [ ]   [ ] │ │
│  │  Settings           [ ]   [ ]     [ ]   [ ]   [ ] │ │
│  │  Users              [ ]   [ ]     [ ]   [ ]   [ ] │ │
│  └────────────────────────────────────────────────────┘ │
│                                                          │
│  [Delete Role]              [Duplicate]  [Save Role]    │
└─────────────────────────────────────────────────────────┘
  • Validation:
    • Name: required, unique, slug format (lowercase + underscores)
    • Label: required
    • At least one permission must be set
  • Transitions:
    • Save → back to S-16 Roles tab with success toast
    • Delete → confirmation modal → back to S-16 Roles tab

S-20: Workflow Config

  • Phase: P2
  • Layout: 2-panel top + detail panel bottom
  • CJX Stage: Retention
  • Related FR: FR-25, FR-28, FR-29, FR-30
  • Access: Settings sidebar → "Booking Workflow" or direct /settings/booking-workflow
  • Permission: Requires Settings EDIT permission

Top Left Panel — Status List:

  • Clickable rows listing all BookingStatusDef entries (non-deleted)
  • Each row: color dot + label + key (muted) + transition count badge
  • Selected status highlighted with accent border
  • Sorted by sortOrder

Top Right Panel — Mermaid Diagram:

  • Read-only Mermaid stateDiagram-v2 preview
  • Rendered with mermaid.js client-side
  • Shows all active transitions with role annotations on arrows
  • Auto-refreshes when transitions are modified
  • Example:
    stateDiagram-v2
      confirmed --> checked_in : [SA, Admin, OTA]
      confirmed --> cancelled : [SA, Admin, CS]
      confirmed --> no_show : [SA, Admin]
      checked_in --> checked_out : [SA, Admin]
      cancelled --> confirmed : [SA, Admin]

Bottom Panel — Selected Status Detail (appears on status click):

Transitions Tab:

  • Table: Target Status | Allowed Roles | Hooks | Sort Order | Active | Actions
  • Each row shows one outgoing transition from selected status
  • "Add Transition" button → inline form or modal:
    • Target status dropdown (excludes self and existing targets)
    • Role multi-select (empty = all roles allowed)
    • Hooks checkboxes: ☑ Audit Log, ☑ Update Availability (action: restore/reduce), ☑ Send Notification (template, channels)
    • Sort order input
  • Edit/Delete actions per row
  • Changes saved via POST/PATCH/DELETE /booking-status-transitions

UI Visibility Tab:

  • Role selector dropdown (shows all role names + "*" wildcard)
  • Per selected role, checkboxes for:
    • Sections: ☑ Guest Info, ☑ Booking Info, ☑ Stay Details, ☑ Property & Room
    • Buttons: ☑ Edit Guest, ☑ Change Room (future)
    • Editable Fields: ☑ guestName, ☑ guestEmail, ☑ numGuests, ☑ numRooms
  • Save updates PATCH /booking-status/:key/ui-config

Layout:

┌─────────────────────────────────────────────────────────┐
│  Booking Workflow Configuration                          │
├─────────────────────┬───────────────────────────────────┤
│                     │                                    │
│  Status List        │  Mermaid Flow Diagram              │
│  (clickable)        │  (read-only preview)               │
│                     │                                    │
│  ● confirmed    ▸   │   confirmed ──→ checked_in         │
│  ● checked_in   ▸   │   checked_in ──→ checked_out      │
│  ● cancelled    ▸   │   confirmed ──→ cancelled          │
│  ● no_show          │   confirmed ──→ no_show            │
│  ● checked_out      │   cancelled ──→ confirmed          │
│                     │                                    │
├─────────────────────┴───────────────────────────────────┤
│                                                          │
│  Selected: "confirmed"                                   │
│  [Transitions] [UI Visibility]                           │
│                                                          │
│  ┌─ Transitions ──────────────────────────────────────┐ │
│  │ → checked_in   [SA, Admin, OTA]  [audit, avail]   │ │
│  │ → cancelled    [SA, Admin, CS]   [audit, avail]   │ │
│  │ → no_show      [SA, Admin]       [audit]          │ │
│  │ [+ Add Transition]                                 │ │
│  └────────────────────────────────────────────────────┘ │
│                                                          │
└─────────────────────────────────────────────────────────┘
  • Transitions:
    • Back → Settings page or sidebar navigation
    • Add/edit transition → modal with form → save → refresh diagram
    • Switch role in UI Visibility → update checkboxes → save

S-21: Suppliers List

  • Phase: P2
  • Layout: Data table with filters
  • CJX Stage: Usage
  • Related FR: FR-31, FR-33
  • Permission: Requires Suppliers VIEW

Elements:

  • Paginated table: Supplier Code, Name, Country (CountryPill), Phone, Email, Total Rooms (SUM of active allocations)
  • Filters: search (debounced), status (active/inactive), country scope
  • Actions: Export CSV, Import CSV (batch 50, add/clear modes), Add Supplier modal
  • Row click → /suppliers/{id} (S-22)

Layout:

┌─────────────────────────────────────────────────────────┐
│  Suppliers                         [Import] [Export] [+] │
├─────────────────────────────────────────────────────────┤
│  [Search...] [Status ▼] [Country: auto-scoped]          │
├──────┬──────────┬─────┬───────┬─────────┬───────────────┤
│ Code │ Name     │ CC  │ Phone │ Email   │ Total Rooms   │
├──────┼──────────┼─────┼───────┼─────────┼───────────────┤
│ ID10 │ Narendra │ [ID]│ ...   │ ...     │ 14            │
│ VN05 │ Minh     │ [VN]│ ...   │ ...     │ 8             │
└──────┴──────────┴─────┴───────┴─────────┴───────────────┘

S-22: Supplier Detail

  • Phase: P2
  • Layout: Two-column info + allocations table
  • CJX Stage: Usage
  • Related FR: FR-31, FR-33
  • Permission: Requires Suppliers VIEW; EDIT for modifications

Info Section:

  • Two cards: Basic Info (code, name, country, phone, email, Airbnb URL, notes) + Bank Details (bankName, accountNo, accountName)
  • Edit via modal (SupplierForm)

Room Allocations Section (replaces "Linked Properties"):

  • Table grouped by property: Property Name | Room Type | Rooms Allocated
  • Grouped rows: property name spans multiple room type rows
  • Click property name → /properties/{id}
  • Shows total rooms across all properties at bottom

Layout:

┌─────────────────────────┬─────────────────────────────┐
│  Basic Info              │  Bank Details               │
│  Code: ID10              │  Bank: BCA                  │
│  Name: Narendra          │  Account: 874xxxxx          │
│  Country: [ID]           │  Name: Patricia V.          │
│  Phone: +62...           │                             │
│  Airbnb: [link]          │                             │
│                    [Edit]│                       [Edit]│
├─────────────────────────┴─────────────────────────────┤
│  Room Allocations                                      │
├──────────────┬────────────┬────────────────────────────┤
│ Property     │ Room Type  │ Rooms Allocated             │
├──────────────┼────────────┼────────────────────────────┤
│ Beach Villa  │ Deluxe     │ 4                          │
│              │ Standard   │ 6                          │
│ City Hotel   │ Suite      │ 2                          │
├──────────────┴────────────┼────────────────────────────┤
│              Total        │ 12                         │
└───────────────────────────┴────────────────────────────┘

S-23: Supplier Allocation Manager

  • Phase: P2
  • Layout: Modal dialog on room type table row
  • CJX Stage: Usage
  • Related FR: FR-31, FR-32
  • Permission: Requires Suppliers CREATE/EDIT/DELETE

Trigger: Click pencil icon in "Suppliers" column of room type table (S-04 Room Types Tab)

Elements:

  • Modal title: "Manage Suppliers — {RoomTypeName}"
  • Header: "Total Rooms: {totalRooms} | Allocated: {sumAllocated}" with warning badge if mismatch
  • Allocation list: Supplier Name | Room Count | Notes | Actions (edit/deactivate)
  • Add form: Supplier dropdown (active suppliers) + Room Count input + Notes (optional)
  • Submit: POST /room-types/{id}/supplier-allocations
  • Edit inline: PUT /supplier-allocations/{id}
  • Deactivate: soft delete → isActive=false

Layout:

┌─────────────────────────────────────────────────────────┐
│  Manage Suppliers — Deluxe Room                    [✕]  │
├─────────────────────────────────────────────────────────┤
│  Total Rooms: 10  │  Allocated: 8  │  ⚠ 2 unallocated  │
├─────────────────────────────────────────────────────────┤
│  Supplier        │ Rooms │ Notes          │ Actions      │
│  Narendra        │ 4     │ contract Q1    │ [✏] [🗑]     │
│  Minh            │ 4     │                │ [✏] [🗑]     │
├─────────────────────────────────────────────────────────┤
│  [Supplier ▼]    │ [___] │ [notes...]     │ [+ Add]      │
└─────────────────────────────────────────────────────────┘

Transitions:

  • Close → return to room type table, SWR revalidate
  • On add/edit/delete → revalidate room-types and allocations SWR keys

S-24: Master Data

  • Phase: P2
  • Route: /master-data
  • Layout: Tabbed data management page
  • CJX Stage: Retention
  • Permission: Requires Settings VIEW; individual tabs gated by Users VIEW / Users EDIT
  • Access: Sidebar navigation item

Users Tab (requires Users VIEW):

  • User list: Name, Email, Role (from roles table), Country (pill badge), Status (active/inactive), Actions
  • "Invite User" / "Add User" button
  • Role selector: dropdown populated from active roles
  • Assign country, deactivate user
  • Country field: dropdown (VN/ID/MY) for country-scoped roles, empty/disabled for full-access roles
  • Admin actions: Reset Password (temp), Send Reset Link

Roles Tab (requires Users EDIT):

  • Role list table: Label, Key, Description, System badge, Actions
  • System preset roles show "System" badge, Delete disabled
  • "Create Role" button → S-19 Role Management form
  • Click role row → S-19 Role Management (edit mode)
  • Actions: Edit → S-19, Delete (custom roles only)

Countries Tab (all Settings viewers):

  • Countries table: Code, Name, Timezone, Currency, Pill Preview, Sort Order, Active, Actions
  • Edit color/sort via inline form or modal
  • Inactive countries hidden from country selectors

Booking Statuses Tab (all Settings viewers):

  • Booking status list: Color dot, Label, Key, Sort Order, Default/Terminal flags, Actions
  • "Add Status" button → create form
  • Edit: modify label, color, icon, sort order
  • Soft delete: sets isDeleted=true (cannot delete statuses with active bookings)
  • Restore: re-activates deleted status

Layout:

┌─────────────────────────────────────────────────────────┐
│  Master Data                                             │
├─────────────────────────────────────────────────────────┤
│  [Users] [Roles] [Countries] [Booking Statuses]          │
├─────────────────────────────────────────────────────────┤
│  (Tab content below)                                     │
└─────────────────────────────────────────────────────────┘

5. Screen Flow

[S-01 Login]


[S-02 Dashboard] ◄────────────────────────────────────────┐
    │                                                       │
    ├──→ [S-03 Properties List]                             │
    │        │                                              │
    │        └──→ [S-04 Property Detail]                    │
    │                                                       │
    ├──→ [S-05 OTA Accounts List]                           │
    │        │                                              │
    │        ├──→ [S-06 Connect OTA Account]                │
    │        │        │                                     │
    │        │        └──→ [S-07 Import Properties]         │
    │        │                    │                          │
    │        │                    └── (success) ────────────►│
    │        │                                              │
    │        └──→ [S-07 Import Properties] (from [Import])  │
    │                                                       │
    ├──→ [S-08 Bookings List]                               │
    │        │                                              │
    │        └──→ [S-09 Booking Detail]                     │
    │                    │                                  │
    │                    └── (status change) → PATCH API   │
    │                                                       │
    ├──→ [S-10 Availability Calendar]                       │
    │        │                                              │
    │        └── (block/unblock) → sync job triggered       │
    │                                                       │
    ├──→ [S-11 Rate Manager]                                │
    │        │                                              │
    │        └── (push rates) → sync job triggered          │
    │                                                       │
    ├──→ [S-12 Booking Timeline]                            │
    │        │                                              │
    │        └──→ [S-09 Booking Detail]                     │
    │                                                       │
    ├──→ [S-13 Rate Parity Report]                          │
    │                                                       │
    ├──→ [S-14 Analytics Dashboard]                         │
    │                                                       │
    ├──→ [S-15 Rate Rules Config]                           │
    │                                                       │
    ├──→ [S-21 Suppliers List]                               │
    │        │                                              │
    │        └──→ [S-22 Supplier Detail]                    │
    │                                                       │
    ├──→ [S-04 Property Detail]                             │
    │        └──→ Room Types Tab                            │
    │              └──→ [S-23 Supplier Allocation Manager]  │
    │                                                       │
    ├──→ [S-16 Settings]                                    │
    │        │  (Workflow, Preferences, Notifications, System)│
    │        │                                              │
    │        └──→ [S-19 Role Management] (from S-24 Roles tab)│
    │                                                       │
    ├──→ [S-24 Master Data] (/master-data)                  │
    │        │  (Users, Roles, Countries, Booking Statuses)  │
    │        │                                              │
    │        └──→ [S-19 Role Management] (Roles tab)       │
    │                                                       │
    ├──→ [S-17 Sync Job Log]                                │
    │        │                                              │
    │        └── breadcrumb back ──────────────────────────►│
    │                                                       │
    └──→ [S-18 User Profile] (via TopBar avatar menu)       │
              │                                             │
              └── back ────────────────────────────────────┘

Primary Onboarding Flow:

[S-01 Login] → [S-02 Dashboard] → [S-05 OTA Accounts]
    → [S-06 Connect OTA Account] → [S-07 Import Properties]
    → [S-05 OTA Accounts] (repeat for each OTA)
    → [S-02 Dashboard] (ready for daily use)

6. Design Rationale

6.1 Why Professional Blue

  • Trust & reliability: Blue is the industry standard for operations tools. Staff needs to trust sync status at a glance
  • Eye comfort: Staff use this 8+ hours daily. Blue palette with light content area reduces fatigue
  • OTA brand contrast: OTA badges (Booking navy, Agoda purple, Traveloka blue, Expedia yellow) pop against neutral content area
  • Status visibility: Green/yellow/red status indicators are immediately visible against white cards and blue sidebar

6.2 Why Fixed Sidebar

  • Navigation is always visible — staff switches between screens frequently
  • Bottom OTA account status panel gives constant awareness without navigating
  • Sidebar collapses on smaller screens to preserve content space

6.3 Why Data Tables Over Cards

  • Staff manages 100+ properties x 4 OTAs = 400+ data points. Cards waste vertical space
  • Tables allow sorting, filtering, batch actions — essential for operational efficiency
  • Card layout only used for KPI metrics, OTA account cards, and property import selection

6.4 Why Country Tabs in Top Bar

  • The #1 organizational dimension for 100+ properties is country
  • Persistent in top bar = always visible, instant switching
  • Staff users auto-locked to their country = no clutter
  • Manager can view "All" or filter = flexibility without complexity

6.5 Why OTA-First Onboarding

  • Matches real-world workflow: OTA accounts already exist → discover properties from them
  • Reduces manual data entry: property names, room types, rates auto-imported
  • Cross-OTA matching prevents duplicate properties when connecting 2nd OTA
  • Step wizard (S-06 → S-07) guides user through the flow naturally

6.6 Why OTA Account Status in Sidebar (Not Per-Connection)

  • At 100+ properties, showing per-connection status is too noisy
  • OTA accounts are the real health indicator: if the account session is dead, all its properties are affected
  • Green/yellow/red dots per OTA account are scannable in <1 second

6.7 Why Separate Profile Page from Settings

  • Settings (S-16) is manager-gated and contains system-wide configuration
  • Profile (S-18) is self-service for all users — staff should not need manager permissions to update their own name or password
  • Profile accessed via TopBar avatar menu — natural, discoverable location
  • Single-column narrow form reduces cognitive load for personal info editing

7. Interaction Patterns

7.1 Sync Job Feedback

When user triggers a manual sync or availability update:

  1. Button changes to loading spinner + "Syncing..."
  2. Toast notification: "Pushing availability to Booking.com, Agoda..."
  3. On completion: Toast updates to "Availability synced to 3/4 OTAs" (green) or "Sync failed for Expedia" (red with retry link)

7.2 Session Expiry Flow

  1. Sidebar OTA account dot turns yellow → tooltip "Session expiring in 30 min"
  2. Dot turns red → tooltip "Session expired"
  3. Alert created (E-11) → notification sent (LINE/email)
  4. Staff clicks dot or alert → navigates to S-05 → [Refresh] or [Manage] to re-authenticate
  5. Session restored → dot turns green

7.3 Overbooking Alert Flow

  1. Booking pull detects booked_rooms > total_rooms
  2. Alert banner appears at top of S-02 Dashboard (red, persistent)
  3. LINE Notify + email sent immediately
  4. Alert shows: property, room type, date, which OTAs contributed
  5. Staff clicks [Resolve] → opens property detail with options
  6. Staff marks alert as resolved with notes

7.4 OTA-First Onboarding Flow

  1. User navigates to S-05 OTA Accounts → clicks "Connect OTA Account"
  2. S-06: Selects OTA, enters credentials, chooses 2FA method
  3. System tests connection via Playwright → success/fail feedback
  4. On success → S-07: System auto-discovers properties from OTA
  5. User selects properties to import, reviews cross-OTA matches (if 2nd+ OTA)
  6. System creates: Property + RoomTypes + OtaConnection + OtaRoomMappings
  7. Redirect to S-05 with success summary
  8. Repeat for each OTA account

7.5 Country Switching

  1. Manager clicks country dropdown in top bar, selects country (VN/ID/MY/All)
  2. All dashboard data refreshes for selected country
  3. Selection persisted in localStorage
  4. Staff user: dropdown locked to their assigned country (other options disabled)

7.6 TopBar User Menu

  1. User clicks avatar/initials circle in top bar right area
  2. Dropdown shows: user name, email, role badge, divider, "Profile" link, "Logout" button
  3. "Profile" → navigates to S-18
  4. "Logout" → clears JWT, redirects to S-01

7.7 Profile Edit Flow

  1. User navigates to S-18 via TopBar user menu
  2. Profile form pre-populated with current user data from auth context
  3. User edits name, email, or locale → "Save Changes" button becomes enabled
  4. On save → PATCH /users/me → success toast → auth context updated in-place
  5. Email change validates uniqueness on blur (debounced API call)

7.8 Password Change Flow

  1. User fills current password, new password, confirm password
  2. Client-side validation: min 8 chars, passwords match
  3. On submit → POST /users/me/password
  4. If current password wrong → 401 → error toast "Current password incorrect"
  5. If success → success toast, all password fields cleared

7.9 Auth Hydration on Refresh

  1. App mounts → checks for JWT in cookie/localStorage
  2. If JWT exists → GET /users/me → populate auth context (includes role permissions)
  3. If 401 → clear JWT → redirect to S-01 Login
  4. If no JWT → redirect to S-01 Login
  5. All routes except S-01 are protected by auth context check
  6. Sidebar items filtered by hasPermission(module, VIEW) from auth context

7.10 Role Management Flow

  1. SA/Admin navigates to S-16 Settings → Roles tab
  2. Views list of all roles (7 system presets + any custom roles)
  3. Click "Create Role" → S-19 with empty form
  4. Fill role info (name, label, description)
  5. Check permission matrix checkboxes for each module × action
  6. Save → role created with computed JSONB permissions
  7. Assign role to users in Users tab via role dropdown

7.11 Permission-Based UI Rendering

  1. JWT includes permissions: { module: bitmask } from user's role
  2. Auth context provides hasPermission(module: string, action: number): boolean
  3. Sidebar items: hidden when !hasPermission(module, VIEW)
  4. Action buttons (Create/Edit/Delete): hidden when user lacks corresponding permission
  5. API endpoints: PermissionsGuard checks @RequirePermission(module, action) decorator
  6. Unauthorized API call returns 403 with { message: "Insufficient permissions" }
  7. Frontend shows toast on 403: "You don't have permission to perform this action"

7.12 Permission Bit Calculation

Permission bits:
  VIEW   = 0b0001 = 1
  CREATE = 0b0010 = 2
  EDIT   = 0b0100 = 4
  DELETE = 0b1000 = 8
  ALL    = 0b1111 = 15

Check: (permissions[module] & action) !== 0
Set:   permissions[module] |= action
Unset: permissions[module] &= ~action

Example: Role with View + Edit on bookings
  permissions.bookings = VIEW | EDIT = 1 | 4 = 5 = 0b0101
  hasPermission('bookings', VIEW)   → (5 & 1) = 1 → true
  hasPermission('bookings', CREATE) → (5 & 2) = 0 → false
  hasPermission('bookings', EDIT)   → (5 & 4) = 4 → true
  hasPermission('bookings', DELETE) → (5 & 8) = 0 → false

7.13 Booking Status Change Flow

  1. User opens S-09 Booking Detail
  2. useBookingWorkflow hook fetches workflow config via SWR (cached)
  3. Hook resolves available transitions for current status + user's roleName
  4. Transition buttons rendered in header (one per available transition)
  5. User clicks transition button → StatusTransitionDialog opens
  6. Dialog shows: "Change status from {current} to {target}?" + optional note field
  7. User clicks Confirm → PATCH /bookings/:id/status with { status: targetKey }
  8. Server validates transition, checks role, updates status, executes hooks
  9. On success → SWR mutate refreshes booking data → StatusBadge updates
  10. On 400 (invalid transition) or 403 (role denied) → error toast
  11. Hook failures logged server-side, don't affect UI response

7.14 Workflow Config Flow

  1. SA/Admin navigates to S-20 Workflow Config (requires Settings EDIT)
  2. Left panel shows all booking statuses. Right panel shows Mermaid diagram
  3. Click status → bottom panel shows outgoing transitions + uiConfig
  4. Add transition: click "Add Transition" → select target status, roles, hooks → POST /booking-status-transitions
  5. Edit transition: click edit icon → update roles/hooks/active → PATCH /booking-status-transitions/:id
  6. Delete transition: click delete → confirm → DELETE /booking-status-transitions/:id
  7. Mermaid diagram auto-refreshes on any transition change
  8. UI Visibility: switch to UI Visibility tab → select role → toggle section/button/field checkboxes → PATCH /booking-status/:key/ui-config
  9. Changes take effect immediately for users viewing booking detail pages

7.15 Conditional Section Visibility

  1. Booking detail page loads workflow config (SWR cached)
  2. Resolves uiConfig for current booking status + user role: uiConfig[roleName] || uiConfig['*']
  3. If uiConfig specifies sections array → only show listed sections
  4. If uiConfig is null/missing → show all sections (graceful degradation)
  5. If uiConfig specifies editableFields → show edit icons on those fields
  6. Inline editing: click edit icon → field becomes input → save on blur/enter → PATCH /bookings/:id

8. Data Fetching Patterns (FR-24)

8.1 SWR Configuration

All GET requests use SWR with a global config wrapping the app:

tsx
// lib/swr-config.tsx
<SWRConfig value={{
  fetcher: apiGet,
  revalidateOnFocus: false,    // no refetch on tab focus
  dedupingInterval: 5000,      // dedupe same-key requests within 5s
}}>

8.2 Data Loading Pattern

Replace all useEffect + apiGet + useState patterns with useSWR:

tsx
// Before
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
  apiGet('roles').then(setData).finally(() => setLoading(false));
}, []);

// After
const { data = [], isLoading } = useSWR('roles', apiGet);

8.3 Cache Invalidation on Mutations

After any create/update/delete, call mutate(key) to revalidate:

tsx
await apiPost('roles', payload);
mutate('roles');  // triggers refetch for all components using 'roles' key

8.4 Polling Intervals

ScreenEndpointIntervalPause on Hidden
S-02 Dashboarddashboard/summary30sYes
S-17 Sync Jobssync-jobs10sYes

SWR's refreshInterval with refreshWhenHidden: false handles both polling and tab-visibility pausing automatically.

8.5 Filter/Search Debounce

Search and filter inputs use 300ms debounce before updating the SWR key:

tsx
const [debouncedSearch] = useDebounce(search, 300);
const { data } = useSWR(`properties?search=${debouncedSearch}`, apiGet);

8.6 Shared Data Deduplication

SWR automatically deduplicates requests with the same key. Components on the same page sharing a key (e.g., roles used by both UsersTab and RolesTab in S-16) trigger only one network request.


GATE 2: Requirements Validation

Before proceeding to /ipa:design:

  • [ ] Stakeholders reviewed SRD.md
  • [ ] Feature priorities (P1/P2/P3) confirmed
  • [ ] Screen list and flows approved
  • [ ] Design System (Professional Blue) accepted
  • [ ] OTA-first onboarding flow validated (OtaAccount entity, S-05/S-06/S-07)
  • [ ] Country scoping design approved (pill badges matching OTA style + dropdown selector)
  • [ ] Per-user format presets accepted (vi/id/ms/en locale)
  • [ ] Entity model accepted (E-04 OtaAccount replaces old credential model)
  • [ ] Scale target acknowledged (100+ properties, 400+ connections)
  • [ ] Out-of-scope items acknowledged (PMS deferred, TH removed)
  • [ ] User Profile (S-18) scope confirmed: profile edit, password change, theme toggle
  • [ ] Auth hydration approach accepted (GET /users/me on mount)
  • [ ] No avatar upload in v1 confirmed (initials-based)
  • [ ] Bitwise permission model accepted (JSONB {module: bitmask}, 4 bits per module: V/C/E/D)
  • [ ] 7 preset roles confirmed (SA, Admin, Manager, OTA, CS, FIN, PO)
  • [ ] Custom role creation by SA/Admin accepted
  • [ ] Permission matrix UI (S-19) design approved (checkbox grid per module × action)
  • [ ] JWT includes permissions (larger token, no DB lookup per request)
  • [ ] Migration plan accepted (existing managermanager role, staffota role)
  • [ ] SWR caching strategy accepted (FR-24: load once, mutate on change, pause polling when hidden)
  • [ ] Hybrid workflow model accepted (D-17: transition table + JSONB uiConfig)
  • [ ] Hooks on transitions accepted (D-18: audit_log, update_availability, send_notification)
  • [ ] Workflow Config page (S-20) design approved (status list + Mermaid + transitions + uiConfig)
  • [ ] Enhanced Booking Detail (S-09) approved (status buttons, conditional sections, inline editing)
  • [ ] BookingStatusTransition entity (E-15) accepted (UNIQUE(from,to), allowedRoles, hooks)
  • [ ] Guest-only editable fields confirmed (guestName, guestEmail, numGuests, numRooms)

Next: /ipa:design to generate HTML prototypes from this spec

PTX Channel Manager — Internal Documentation