{{ t.message }}
Please wait...
{{ slide.caption }}

{{ settings.page_tagline || 'Dominance Starts On The Court.' }}

Select a branch, pick your court and time, choose your payment option, and secure your slot — all on this page.

Find Your Booking

Enter your reference number to view booking details.

{{ verifierError }}
Booking Reference: {{ verifierSummary.booking_ref || verifierSummary.id }}
Court {{ verifierSummary.court_name }}
Status {{ verifierSummary.status }} Varies · see below
Booking Date {{ formatDateMDY(verifierSummary.booking_date) }}
Time Slot {{ formatTime12(verifierSummary.start_time) }} - {{ formatTime12(verifierSummary.end_time) }}
{{ verifierSummary.dates.length }} Reservations
{{ b.court_name ? b.court_name + ' · ' : '' }}{{ formatDateMDY(b.booking_date) }} {{ formatTime12(b.start_time) }} - {{ formatTime12(b.end_time) }} · ₱{{ formatAmount(Math.max(0, (parseFloat(b.amount) || 0) - (parseFloat(b.paddle_fee_amount) || 0))) }} · 🏓 {{ parseInt(b.paddles_rented) }} {{ b.status }}
Player Name: {{ verifierSummary.customer_name }}
Email: {{ maskEmail(verifierSummary.customer_email) }}
Phone: {{ maskPhone(verifierSummary.customer_phone) }}
Court Fee ₱{{ formatAmount(Math.max(0, verifierSummary.amount - verifierSummary.paddle_fee_amount)) }}
🏓 Paddle Rental ({{ verifierSummary.paddles_rented }} paddle{{ verifierSummary.paddles_rented === 1 ? '' : 's' }}) ₱{{ formatAmount(verifierSummary.paddle_fee_amount) }}
Transaction Fee ₱{{ formatAmount(verifierSummary.transaction_fee_amount) }}
{{ verifierTotalLabel }} ₱{{ formatAmount(verifierSummary.amount + verifierSummary.transaction_fee_amount) }} PHP

How To Book

  1. Pick your branch, then select a date and click an open hour on the Court Availability grid below.
  2. Select a date and click an open hour on the Court Availability grid below.
  3. Click Book Now and enter your name and contact number.
  4. Scan the payment QR code, pay the total amount, and upload a screenshot of your payment receipt to confirm - you'll get a booking reference number to track your reservation anytime via Find Your Booking.
⚠️ No refund and cancellations once a booking is confirmed and paid.

Court Booking Form

Click any available hour on the grid below to book it.

Court Availability

Branch: {{ (auth.user.branch_id && branchNameById(auth.user.branch_id)) || 'Unassigned' }}
{{ publicGridWeekLabel }}

Court Availability - {{ formatDateMDY(publicGridDate) }}

Selected Open Pending Payment Booked Past

⚠️ Click an open hour to select it.

{{ publicGridLoading ? 'Loading court availability…' : (publicGridSportFilter ? `No ${publicGridSportFilter} courts at this branch.` : 'No active courts to display for this branch yet.') }}
Time {{ court.name }}
{{ formatHourRangeLabel(row.hour) }}
{{ cell.isBeyond ? 'Closed' : (cell.isPast ? 'Past' : publicGridCellLabel(cell)) }} ₱{{ formatAmount(publicGridCellPrice(cell.court.id, row.hour)) }}
{{ slot.court_name }} · {{ formatDateMDY(slot.date) }} · {{ formatTime12(slot.start_time) }} – {{ formatTime12(slot.end_time) }} {{ slot.hours }} {{ slot.hours === 1 ? 'Hour' : 'Hours' }} ✕
{{ allSelectedDaySlots().length }} reservations selected · {{ calculatedHours }} {{ calculatedHours === 1 ? 'Hour' : 'Hours' }} total
No time selected yet.
🏓 Rent Paddles for each court
{{ ps.slot.court_name }} · {{ formatDateMDY(ps.slot.date) }} · {{ formatTime12(ps.slot.start_time) }} – {{ formatTime12(ps.slot.end_time) }}
{{ ps.info.available }} of {{ ps.info.total }} left for this slot · max {{ ps.info.cap }} per court · ₱{{ formatAmount(ps.info.rate) }} each
+ ₱{{ formatAmount(paddleFeeAmount) }} paddle rental ({{ totalPaddlesSelected }} {{ totalPaddlesSelected === 1 ? 'paddle' : 'paddles' }})
Total Calculated Fee:
₱{{ formatAmount(calculatedGrandTotal) }} PHP
Court Fee: ₱{{ formatAmount(bookingForm.amount) }} + Paddles: ₱{{ formatAmount(paddleFeeAmount) }} + Transaction Fee: ₱{{ formatAmount(calculatedTransactionFee) }}

Step 1 of 2 — Your Details

Tell us who's booking. Payment is the next step.

Selected:
{{ slot.court_name }} · {{ formatDateMDY(slot.date) }} · {{ formatTime12(slot.start_time) }} – {{ formatTime12(slot.end_time) }}
Total ₱{{ formatAmount(calculatedGrandTotal) }}
Court Fee: ₱{{ formatAmount(bookingForm.amount) }} + Paddle Rental ({{ totalPaddlesSelected }} {{ totalPaddlesSelected === 1 ? 'paddle' : 'paddles' }}): ₱{{ formatAmount(paddleFeeAmount) }} + Transaction Fee: ₱{{ formatAmount(calculatedTransactionFee) }}

Please enter a valid email address.

Step 2 of 2 — Payment

4. Payment (GCASH/BANK)

🚶 Walk-in / Pay Later — no receipt required. The booking will be saved as approved right away and the slot is locked in; the customer simply pays at the counter.
Total to Pay ₱{{ formatAmount(calculatedGrandTotal) }} PHP

Frequently Asked Questions

{{ selectedBranch.name }}

{{ faq.a }}

Find Us

{{ selectedBranch.name }}
{{ [selectedBranch.address, selectedBranch.city].filter(Boolean).join(', ') }}
{{ selectedBranch.contact_number }}

Portal Login

Staff/Admin Only

👁️ Viewing as Admin (read-only) — you're seeing every branch's dashboard. Changes are disabled.
🔒

Dashboard Temporarily Locked

Temporarily locked due to not paying the transaction fee. Please settle the outstanding balance to restore access.

Please contact your primary administrator to resolve this.

Developer Dashboard

Platform-wide transaction fee revenue and payment approvals.

Total Collected
₱{{ formatAmount(developerDashboard.total_collected) }}
From approved/completed bookings, all-time.
Remitted (Paid)
₱{{ formatAmount(developerDashboard.total_remitted) }}
Sum of periods marked Paid.
Outstanding
₱{{ formatAmount(developerDashboard.total_outstanding) }}
Collected but not yet remitted.

{{ isDashboardLocked ? '🔒 Staff/Admin Dashboard is LOCKED' : 'Staff/Admin Dashboard is Unlocked' }}

Temporarily lock every admin/staff out of their dashboard if the transaction fee has gone unpaid. The nav bar and System page stay reachable so they can still pay.

My Account

Reset a User's Password

For any admin or staff account, across every branch - use this if someone's locked out or forgot their password.

Transaction Fee Settings

Per Hour charges the rate below for every hour booked. Per Transaction charges one flat fee for the whole reservation instead - once per booking reference, regardless of how many hours, courts, or dates it covers.

Used when Billing Mode above is Per Hour. Charged on top of the customer's court fee, shown in the booking's price breakdown.

Used when Billing Mode above is Per Transaction. Both rates are always saved together, so switching the toggle later doesn't lose whichever one isn't currently active.

Your Payment Methods (shown to admins on the "Pay" screen)

Every Active method appears as a tab in the Pay Transaction Fee modal. Inactive methods are hidden. Add, Update and Delete save right away on their own - they are not part of the Save Settings button.

{{ devPaymentMethodForm.id ? 'Edit Payment Method' : 'Add Payment Method' }}
New image selected - not saved yet
No payment methods yet - admins can't see where to pay until you add one.
QR
{{ pm.label }} Active Inactive
{{ pm.account_name }}{{ pm.account_number ? ' · ' + pm.account_number : '' }}

OCR.space API Key

Used to extract text from uploaded payment receipts (via the free OCR.space API). The receipt image itself is kept in a private server folder (see Receipt Image Retention below) so staff can view it on the booking Ticket, then deleted automatically. This key is required: it can be replaced, but not removed. "Test Connection" checks the currently saved key (save first if you just pasted a new one).

Receipt Image Retention

Customer payment receipt images shown on the booking Ticket.
Transaction fee remittance receipts shown in Payment Approvals and the admin's System billing.
Images are deleted automatically once they are older than this (1–365 days, default 7). Lowering a value permanently deletes images older than the new limit shortly after you save. The extracted OCR text is not affected.

Pre-launch Bookings

Add a reservation that was already paid before the system launched. It blocks the courts and counts in Total Bookings / Players, but adds ₱0 revenue and no transaction fee.

1. Pick courts, date and time, then click Add Slot. Repeat for more courts, dates or times.
Courts (tick one or more)
Select a branch first.
This branch has no courts.
Slots in this booking ({{ prelaunchForm.slots.length }})
{{ s.branch_name }} · {{ s.court_name }} · {{ formatDateMDY(s.booking_date) }} · {{ formatTime12(s.start_time) }} – {{ formatTime12(s.end_time) }}
No slots added yet.
Ref Customer Slots Status Action
No pre-launch bookings added yet.
{{ g.booking_ref }} {{ g.customer_name }}
{{ r.branch_name || 'Unassigned' }} · {{ r.court_name }} · {{ formatDateMDY(r.booking_date) }} · {{ formatTime12(r.start_time) }} – {{ formatTime12(r.end_time) }} · 🏓 {{ Number(r.paddles_rented) }}
{{ g.status }}

Payment Approvals

Period Amount Submitted By Receipt (Image) Receipt (OCR) Status Submitted Updated Action
No payment submissions yet.
{{ p.period_start }} – {{ p.period_end }} ₱{{ formatAmount(p.amount) }} {{ p.submitted_by_name || '—' }} No image on file {{ p.status === 'pending_approval' ? 'Pending' : p.status }} {{ p.submitted_at ? formatDateTime12(p.submitted_at) : '—' }} {{ p.reviewed_at ? formatDateTime12(p.reviewed_at) : '—' }}
—
Fee Payment Receipt
{{ feeReceiptModal.label }}
Fee payment receipt

Stored privately and deleted automatically {{ settings.fee_receipt_retention_days || 7 }} days after the payment is submitted.

{{ receiptModalBooking && receiptModalBooking.status === 'approved' ? 'Booking Confirmed!' : 'Booking Pending Payment!' }}
{{ receiptModalBooking && receiptModalBooking.status === 'approved' ? 'This reservation is approved and the slot is locked in - the customer can settle payment at the counter. You can look this booking up anytime in the FIND YOUR BOOKING section.' : 'Once we receive your payment, the status will be updated to paid. You can check your booking status anytime in the FIND YOUR BOOKING section.' }}
📧 You can also check your email ({{ receiptModalBooking.customer_email }}) for updates on your booking.

Booking Ticket

{{ settings.page_title || 'Hard Courts Reservation System' }} Confirmation

📌 Notice: You need to take a screenshot of this ticket to present at the court.
Booking Reference: {{ receiptModalBooking.booking_ref || receiptModalBooking.id }}
Court {{ receiptModalBooking.court_name }}
Status {{ receiptModalBooking.status }}
Booking Date {{ formatDateMDY(receiptModalBooking.booking_date) }}
Time Slot {{ formatTime12(receiptModalBooking.start_time) }} - {{ formatTime12(receiptModalBooking.end_time) }}
{{ receiptModalBooking.dates.length }} Reservations
{{ d.court_name ? d.court_name + ' · ' : '' }}{{ formatDateMDY(d.booking_date) }} {{ formatTime12(d.start_time) }} - {{ formatTime12(d.end_time) }} · ₱{{ formatAmount(Math.max(0, (parseFloat(d.amount) || 0) - (parseFloat(d.paddle_fee_amount) || 0))) }} · 🏓 {{ parseInt(d.paddles_rented) }}
Player Name: {{ receiptModalBooking.customer_name }}
Email: {{ receiptModalBooking.customer_email }}
Phone: {{ receiptModalBooking.customer_phone }}
Payment Receipt (Uploaded Image)
Payment Receipt (Extracted Details)
{{ f.label }} {{ f.value }} Not detected

          
No text could be extracted from this receipt (OCR found nothing, or OCR isn't configured - check Settings for the OCR.space API Key).
Court Fee ₱{{ formatAmount(Math.max(0, (parseFloat(receiptModalBooking.amount) || 0) - (parseFloat(receiptModalBooking.paddle_fee_amount) || 0))) }}
🏓 Paddle Rental ({{ parseInt(receiptModalBooking.paddles_rented) || 0 }} paddle{{ (parseInt(receiptModalBooking.paddles_rented) || 0) === 1 ? '' : 's' }}) ₱{{ formatAmount(receiptModalBooking.paddle_fee_amount) }}
Transaction Fee ₱{{ formatAmount(receiptModalBooking.transaction_fee_amount) }}
{{ receiptModalBooking.status === 'completed' ? 'Total Amount Paid:' : ((receiptModalBooking.payment_ref === 'PAY_AFTER_GAME' || receiptModalBooking.payment_ref === 'WALK_IN_PAY_LATER') ? 'Total Amount Due:' : 'Total Amount Paid:') }} ₱{{ formatAmount((parseFloat(receiptModalBooking.amount) || 0) + (parseFloat(receiptModalBooking.transaction_fee_amount) || 0)) }} PHP

Edit Slot / Time

Booking Ref: {{ editEndTimeModal.booking_ref || editEndTimeModal.id }}

Court: {{ editEndTimeModal.court_name }}
Date: {{ formatDateMDY(editEndTimeModal.booking_date) }}
Start Time: {{ formatTime12(editEndTimeModal.start_time) }}
Current End Time: {{ formatTime12(editEndTimeModal.end_time) }}
🏓 Paddles ₱{{ formatAmount(editEndTimePaddleInfo.unitRate) }} each · {{ editEndTimePaddleInfo.freeMore }} more available
Currently rented: {{ editEndTimePaddleInfo.current }} · max {{ editEndTimePaddleInfo.cap }} per court

{{ editEndTimePaddleInfo.cap > 0 && editEndTimePaddleInfo.current >= editEndTimePaddleInfo.cap ? 'This booking already has the maximum paddles allowed per court.' : 'No more paddles are free for this time.' }}

+ {{ editEndTimePaddles - editEndTimePaddleInfo.current }} paddle{{ (editEndTimePaddles - editEndTimePaddleInfo.current) === 1 ? '' : 's' }} added (+₱{{ formatAmount((editEndTimePaddles - editEndTimePaddleInfo.current) * editEndTimePaddleInfo.unitRate) }})

Court Fee: ₱{{ formatAmount(editEndTimeCalculatedAmount) }}
🏓 Paddle Rental ({{ editEndTimePaddles }}): ₱{{ formatAmount(editEndTimePaddleFee) }}
Transaction Fee{{ settings.transaction_fee_mode === 'per_transaction' ? ' (flat - unaffected by hours)' : '' }}: ₱{{ formatAmount(editEndTimeCalculatedFee) }}
New Total Amount: ₱{{ formatAmount(editEndTimeCalculatedAmount + editEndTimePaddleFee + editEndTimeCalculatedFee) }} PHP

System Activity & Audit Logs

Tracks all administrative user actions.

ID User Role Branch Action Details Timestamp
No activity logs recorded yet.
#{{ log.id }} {{ log.user_name }} {{ log.user_role }} {{ log.branch_name || 'All Branches' }} {{ log.action }} {{ formatLogDetails(log.details) }} {{ formatDateTime12(log.created_at) }}

System — Transaction Fee Billing

One combined statement covering every branch, billed twice a month (due the 5th and 20th).

Period Due Date Amount Status Submitted Paid On Action
No billing periods yet.
{{ formatDateMDY(p.period_start) }} – {{ formatDateMDY(p.period_end) }} {{ formatDateMDY(p.due_date) }} ₱{{ formatAmount(p.amount) }} {{ p.status === 'unpaid' && p.is_overdue ? 'Overdue' : (p.status === 'pending_approval' ? 'Pending Approval' : p.status) }}
Rejected: {{ p.rejection_reason }}

              
{{ p.submitted_at ? formatDateTime12(p.submitted_at) : '—' }} {{ p.paid_at ? formatDateTime12(p.paid_at) : '—' }} Opens {{ formatDateMDY(p.payable_from) }} Awaiting developer review ✓ Settled —
Submitting your payment...

Pay Transaction Fee

Period: {{ formatDateMDY(payFeePeriodModal.period_start) }} – {{ formatDateMDY(payFeePeriodModal.period_end) }}
Due: {{ formatDateMDY(payFeePeriodModal.due_date) }}
Amount: ₱{{ formatAmount(payFeePeriodModal.amount) }}
Select Payment Method
Scan {{ selectedDevPaymentMethod().label }} to Pay
Developer Payment QR Code
{{ selectedDevPaymentMethod().account_name }} — Acct #: {{ selectedDevPaymentMethod().account_number }}
⚠️ The system developer hasn't configured a payment QR yet. Contact them directly to arrange payment.

Extracted via OCR so the system developer can verify it against your payment.

Payment Receipt Preview

{{ auth.user.role === 'staff' ? 'My Account Details' : 'Staff & Admin Account Management' }}

{{ userForm.id ? 'Edit User Account' : 'Add New Staff / Admin' }}

All Branches
The primary admin manages every branch and can't be assigned to one.
Which branch this account is scoped to in the Court Availability grid.
ℹ️ Select your own individual account record row below to update your authentic credential properties.

{{ auth.user.role === 'staff' ? 'Your Personal Account Record' : 'Existing System Users' }}

{{ u.name }}
{{ u.email }}
{{ u.role }} {{ u.branch_id ? (branchNameById(u.branch_id) || 'Unknown Branch') : 'All Branches' }}

Court Management & Settings

📌 Reserve Court Form Time Settings and Hidden Hours are configured per branch — see Manage Branches. Day/time pricing is set per court in each court's Rate Schedule below.

{{ courtForm.id ? 'Edit Court Facility' : 'Add New Court Facility' }}

📋 Duplicating "{{ courtDuplicateSource }}" - same settings and rate schedule, just enter a new Court Name.

⚠️ Staff cannot edit hourly rate
Lets customers and staff filter the court grid to one sport. Leave blank if this venue only offers one sport.
Status
Inactive courts are hidden from the booking grids and can't be booked. Existing bookings on it stay on record and are still honored.

Paddles are a shared pool for the whole branch (set the pool size, rate, and per-court limit in Manage Branches). Tick this to let customers booking this court rent from that pool. Availability is worked out per time slot across all the branch's courts automatically.

{{ (auth.user.branch_id && branchNameById(auth.user.branch_id)) || 'Unassigned' }}
New/edited courts are automatically assigned to your branch.

Rate Schedule (optional)

Custom price bands for specific days/hours - e.g. a separate weekday vs. weekend rate, or morning/afternoon/evening pricing. Any hour not covered by a band below charges the plain Hourly Rate. Saved together with the court when you click "{{ courtForm.id ? 'Update Court' : 'Save Court' }}" below.

{{ r.days.map(d => weekdayOptions.find(o => o.value === d).label).join(', ') }}  {{ formatHour12(r.start_hour) }}–{{ formatHour12(r.end_hour) }}  ₱{{ formatAmount(r.price) }}/hr

No rate bands yet - this court always charges its plain Hourly Rate.

Existing Courts

{{ c.name }}
₱{{ formatAmount(c.hourly_rate) }}/hr {{ transactionFeeNote }}
📅 {{ c.rate_rules.length }} rate band{{ c.rate_rules.length === 1 ? '' : 's' }} {{ transactionFeeNote }}
{{ c.branch_name || branchNameById(c.branch_id) || 'Unassigned' }} {{ c.sport }} 🏓 Paddles Inactive

Branch Management

{{ branchForm.id ? 'Edit Branch' : 'Add New Branch' }}

Hour range shown on this branch's booking form and Court Availability grid.

Blocks out a dead stretch of hours from the booking grid entirely - e.g. Form Display 1:00 AM–12:00 MN with Hidden Hours 2:00 AM–8:00 AM shows customers 1:00 AM, then 8:00 AM onward, with the gap simply not there to select. Each hour keeps its own real date either way - this is purely which hours are shown, nothing about how bookings are dated or priced changes. Leave both blank to show every hour in the Form Display range above.

🏓 Paddle Rental Pool (optional)

A shared set of paddles for this whole branch. Customers booking any court you've marked as offering paddles (in Manage Courts) can rent from this pool. Availability is worked out per time slot across all the branch's courts - a paddle returns to the pool once its booking ends. Set the total to 0 to turn paddle rental off for this branch.

Max Paddles Per Court is the most a customer can rent for each court they book (e.g. 4 = one per player), so booking 3 courts allows up to 12 in total. 0 means no limit beyond what's left in the pool. The rate is charged per paddle, once per booking (not per hour), and added to the customer's total.

Existing Branches

{{ b.name }}
{{ b.city || b.address || 'No location set' }}
{{ b.status }} Main Branch
🕐 {{ formatBranchHours(b.court_start_hour, b.court_end_hour) }}
🚫 Hidden: {{ formatHour12(b.hidden_hour_start) }}–{{ formatHour12(b.hidden_hour_end) }}
🏓 {{ b.paddles_total }} paddle{{ Number(b.paddles_total) === 1 ? '' : 's' }} · ₱{{ formatAmount(b.paddle_rate_per_unit) }} each

Payment Methods

Branch:
ℹ️ No branch selected, so there's no payment methods list to manage.

🚧 Block A Court

Branch: {{ branchNameById(auth.user.branch_id) || 'Unassigned' }}
Duration: {{ blockCalculatedHours }} hour{{ blockCalculatedHours === 1 ? '' : 's' }} selected

Bookings on {{ formatDateMDY(selectedDayModal.dateStr) }}

No active bookings on this date.
{{ b.court_name }} {{ b.status }}
{{ formatTime12(b.start_time) }} - {{ formatTime12(b.end_time) }} {{ b.timesSummary }}
Player: {{ b.customer_name }} ({{ b.customer_phone }})
Booking Ref #: {{ b.booking_ref || b.id }}
₱{{ formatAmount((parseFloat(b.amount) || 0) + (parseFloat(b.transaction_fee_amount) || 0)) }}
🏓 {{ parseInt(b.paddles_rented) }} paddle{{ parseInt(b.paddles_rented) === 1 ? '' : 's' }} rented · ✓ {{ paddlesReturnedLabel(b) }}
🔒 Pre-launch
⚠️ Past dates cannot be reserved.

System Settings, Carousel & FAQs

General System Settings

Adjusts readability text scaling across system screens.
minutes
A customer booking that stays unpaid/pending this long is automatically cancelled and its time slot freed for others. Default 30. Range: 1 minute to 10080 (7 days). Currently: {{ formatPendingExpiry(settingsForm.pending_expiry_minutes) }}.
days
Applies to every branch. Customers can only book from today up to this many days ahead (e.g. 7 = a booking made today can be for any date through next week's same weekday). Admin and staff are exempt. Default 7. Range: 1 to 365.
minutes
Applies to every branch. A customer can still book the hour that is currently in progress until it is this many minutes old (e.g. 30 = a 3 PM slot stays open online until 3:30 PM). 0 = the current hour closes for customers the moment it starts; 60 = it stays open the whole hour. Admin and staff can always book the whole current hour (walk-ins). Earlier hours are always closed. Default 30. Range: 0 to 60.
System logo preview

Dynamic Landing Carousel Slides

Shown on the landing page for every branch. Maximum of 5 slides ({{ carouselSlides.length }}/5 used).

Slide preview
Slide preview
{{ slide.caption || 'No Caption' }}
ID: #{{ slide.id }} • All Branches

Frequently Asked Questions

Your account isn't assigned to a branch, so there are no FAQs for you to edit.