{{ 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.

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 }} 🏓 Open Play · {{ verifierSummary.open_play_spots }} player{{ verifierSummary.open_play_spots == 1 ? '' : 's' }}
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.discount_amount) || 0) - (parseFloat(b.paddle_fee_amount) || 0))) }} · 🎾 {{ rentalsText(b) }} {{ b.status }}
Player Name: {{ verifierSummary.customer_name }}
Email: {{ maskEmail(verifierSummary.customer_email) }}
Phone: {{ maskPhone(verifierSummary.customer_phone) }}
{{ verifierSummary.open_play_session_id ? 'Open Play Fee' : 'Court Fee' }} ₱{{ formatAmount(Math.max(0, verifierSummary.amount + verifierSummary.discount_amount - verifierSummary.paddle_fee_amount)) }}
🎾 {{ rl.item_name }} × {{ rl.qty }} ₱{{ formatAmount(rl.fee) }}
🏷️ Discount ({{ discountLabelOf(verifierSummary) }}) −₱{{ formatAmount(verifierSummary.discount_amount) }}
Transaction Fee ₱{{ formatAmount(verifierSummary.transaction_fee_amount) }}
{{ verifierTotalLabel }} ₱{{ formatAmount(verifierSummary.amount + verifierSummary.transaction_fee_amount) }} PHP
Balance Due (added after payment): ₱{{ formatAmount(verifierSummary.balance_due) }} 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.

🏓 Open Play

No group yet? Join an open play session and pay per player — no need to book a whole court. Already joined? Check your status in Find Your Booking with your OP- reference number.

{{ s.title || 'Open Play' }}
{{ opDateLabel(s.session_date) }} · {{ formatTime12(s.start_time) }} – {{ opEndLabel(s.end_time) }}
{{ s.sport_name }}
{{ s.court_name }} · {{ s.branch_name }} · 🔁 Every week
{{ s.notes }}
{{ pl.name }}+{{ pl.spots - 1 }}
{{ s.spots_left > 0 ? s.spots_left + ' of ' + s.max_players + ' spots left' : 'Full' }} ₱{{ formatAmount(s.price_per_player) }} / player

🏓 All Open Play

{{ opAllFiltered.length }} session{{ opAllFiltered.length === 1 ? '' : 's' }}{{ opAllFilter === 'all' ? ' coming up' : '' }} · {{ selectedBranch.name }}

{{ opAllFilter === 'date' ? 'No Open Play on ' + opDateLabel(opAllDate) + '.' : 'No Open Play sessions for this filter.' }}
{{ g.label }} {{ g.sessions.length }}
{{ s.title || 'Open Play' }}
{{ formatTime12(s.start_time) }} – {{ opEndLabel(s.end_time) }}
{{ s.sport_name }}
{{ s.court_name }} · {{ s.branch_name }} · 🔁 Every week
{{ s.notes }}
{{ pl.name }}+{{ pl.spots - 1 }}
{{ s.spots_left > 0 ? s.spots_left + ' of ' + s.max_players + ' spots left' : 'Full' }} ₱{{ formatAmount(s.price_per_player) }} / player

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 Open Play (click to join) Open Play Full 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 }}
{{ slot.court_name }} · {{ formatDateMDY(slot.date) }} · {{ formatTime12(slot.start_time) }} – {{ formatTime12(slot.end_time) }} {{ slot.hours }} {{ slot.hours === 1 ? 'Hour' : 'Hours' }} ✕
{{ selectedDaySlotsView.length }} reservations selected · {{ calculatedHours }} {{ calculatedHours === 1 ? 'Hour' : 'Hours' }} total
No time selected yet.
Which sport? this court is used for more than one - the equipment you can rent depends on it
{{ sl.court_name }} · {{ formatDateMDY(sl.date) }} · {{ formatTime12(sl.start_time) }} – {{ formatTime12(sl.end_time) }}
🎾 Rent Equipment for each court
{{ rs.slot.court_name }} · {{ formatDateMDY(rs.slot.date) }} · {{ formatTime12(rs.slot.start_time) }} – {{ formatTime12(rs.slot.end_time) }} · {{ slotSportName(rs.slot) }}
{{ info.name }}
{{ info.available }} of {{ info.stock }} left for this slot · max {{ info.cap }} per court · {{ rentalRateLabel(info) }} · {{ slotPrevNightQty(rs.slot, info.itemId) }} already paid before midnight
+ ₱{{ formatAmount(rentalFeeAmount) }} equipment rental ({{ rentalSummaryText }})
Total Calculated Fee:
₱{{ formatAmount(calculatedGrandTotal) }} PHP
Court Fee: ₱{{ formatAmount(bookingForm.amount) }} + Rentals: ₱{{ formatAmount(rentalFeeAmount) }} + 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) }} + Rentals ({{ rentalSummaryText }}): ₱{{ formatAmount(rentalFeeAmount) }} + Transaction Fee: ₱{{ formatAmount(calculatedTransactionFee) }}

Please enter a valid email address.

Step 2 of 2 — Payment

4. Payment (GCASH/BANK)

Total to Pay ₱{{ formatAmount(calculatedGrandTotal) }} PHP
🕒 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.
💵 Pay Now – Cash — no receipt required. Collect the cash now: the booking is saved as approved and paid (cash) right away.

🏓 {{ opJoin.step === 'done' ? (opJoin.mode === 'waitlist' ? 'On the Waitlist' : 'You\'re In!') : (opJoin.mode === 'waitlist' ? 'Join the Waitlist' : 'Join Open Play') }}

{{ opJoin.session.title || 'Open Play' }}

{{ opDateLabel(opJoin.session.session_date) }} · {{ formatTime12(opJoin.session.start_time) }} – {{ opEndLabel(opJoin.session.end_time) }}
{{ opJoin.session.court_name }} · {{ opJoin.session.branch_name }} · {{ opJoin.session.sport_name }}
{{ opJoin.session.notes }}
Players ({{ opJoin.session.spots_taken }}/{{ opJoin.session.max_players }})
{{ pl.name }}+{{ pl.spots - 1 }}
This session is full. Join the waitlist for free — if a spot opens up, we'll let you know so you can sign up and pay.
4. Number of Players
{{ opJoin.spots }} {{ opJoin.session.spots_left }} spot{{ opJoin.session.spots_left === 1 ? '' : 's' }} left · max {{ opJoinMaxSpots }} per signup
Your photo preview
{{ opJoin.mode === 'waitlist' ? 'Adding you to the waitlist...' : 'Submitting your signup...' }}

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) — this is the primary admin's view of every branch, including all action buttons. You can look around, but nothing can be saved, changed or deleted.
🔒

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.

Date Filter: Loading…
{{ devRangeLabel }}
Start (MM/DD/YYYY):
End (MM/DD/YYYY):
Total Collected
₱{{ formatAmount(developerDashboard.total_collected) }}
{{ devRange === 'all' ? 'From approved/completed bookings, all-time.' : 'From approved/completed bookings made in the selected range.' }}
Remitted (Paid)
₱{{ formatAmount(developerDashboard.total_remitted) }}
{{ devRange === 'all' ? 'Sum of periods marked Paid.' : 'Fees from bookings in this range that belong to Paid periods.' }}
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.

Charged on top for every Open Play player (spot), whatever the Billing Mode. Billed in the same fee periods as bookings.

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. Images over 1 MB are shrunk automatically for OCR (the saved receipt stays full size). "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.

Booking Lock

Once a booking has been approved for this long, it is locked: it can no longer be set back to Pending, Declined or Cancelled by anyone, including the primary admin. It is also how long staff may still undo a Paid mark (Mark Unpaid); admins can always undo it. Complete, Edit and Return Paddles still work. Default 10, range 1–1440.

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) }} · 🎾 {{ rentalsText(r) }}
{{ 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) : '—' }}
—

🗄️ Backups

Full copy of this site's database, made every night and emailed as a password-protected zip. Kept: {{ backupKeepText }}. Older files are removed automatically.

Loading backups…

⚠️ Danger Zone: Reset Booking Data

Deletes every booking, walk-in, block, recurring series, Open Play session, rental record, transaction fee statement and receipt image, so the site starts over like a fresh install. Settings, branches, courts, rates, sports, rental items, payment methods and accounts are kept. A backup file is saved first.

Backups saved on the server
{{ b.name }} {{ formatBytesShort(b.size) }} Download

Backups contain customer names, phone numbers and emails. Delete them once you're sure you won't need to restore.

{{ dataReset.done ? 'Reset Complete' : 'Reset Booking Data' }}

Fee Payment Receipt
{{ feeReceiptModal.label }}

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

{{ receiptFullModal.label }}
Receipt full size
{{ justBookedBanner.title }}
{{ justBookedBanner.text }}
📧 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 }} 🏓 {{ receiptModalBooking.open_play_title || 'Open Play' }} · {{ receiptModalBooking.open_play_spots }} player{{ receiptModalBooking.open_play_spots == 1 ? '' : 's' }}
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.discount_amount) || 0) - (parseFloat(d.paddle_fee_amount) || 0))) }} · 🎾 {{ rentalsText(d) }}
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).
{{ receiptModalBooking.open_play_session_id ? 'Open Play Fee' : 'Court Fee' }} ₱{{ formatAmount(Math.max(0, (parseFloat(receiptModalBooking.amount) || 0) + (parseFloat(receiptModalBooking.discount_amount) || 0) - (parseFloat(receiptModalBooking.paddle_fee_amount) || 0))) }}
🎾 Equipment Rental ({{ rentalsText(receiptModalBooking) }}) ₱{{ formatAmount(receiptModalBooking.paddle_fee_amount) }}
🏷️ Discount ({{ discountLabelOf(receiptModalBooking) }}) −₱{{ formatAmount(receiptModalBooking.discount_amount) }}
ID {{ receiptModalBooking.discount_id_no }} ({{ receiptModalBooking.discount_id_name }}) · {{ receiptModalBooking.discount_note }} · given by {{ receiptModalBooking.discount_by }}
Transaction Fee (not discounted) ₱{{ formatAmount(receiptModalBooking.transaction_fee_amount) }}
{{ ticketBalanceDue(receiptModalBooking) > 0 ? 'Total Amount:' : (receiptModalBooking.status === 'completed' ? 'Total Amount Paid:' : ((!receiptModalBooking.paid_at && (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
Balance Due (added after payment): ₱{{ formatAmount(ticketBalanceDue(receiptModalBooking)) }} PHP
↻ Rescheduled ×{{ receiptModalBooking.reschedule_count }} · last moved from {{ receiptModalBooking.rescheduled_from }}
↩ {{ receiptModalBooking.refund_type === 'credit' ? 'Credit kept' : 'Refund given' }}: ₱{{ formatAmount(receiptModalBooking.refund_amount) }}
{{ receiptModalBooking.adjustment_note }}
Payment history {{ ticketPaymentBadge.icon }} {{ ticketPaymentBadge.text }}
💳 Online receipt (at booking)
{{ p.method === 'cash' ? '💵' : '💳' }} {{ paymentKindLabel(p) }} · {{ p.method_label }} · {{ formatDateTimeShort(p.received_at) }} · {{ p.received_by }} {{ p.direction === 'out' ? '−' : '' }}₱{{ formatAmount(p.amount) }}
{{ p.receipt_text }}

Loading…

Edit / Reschedule Booking

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

Current slot
Court: {{ editEndTimeModal.court_name }}
Date: {{ formatDateMDY(editEndTimeModal.booking_date) }}
Time: {{ formatTime12(editEndTimeModal.start_time) }} - {{ formatTime12(editEndTimeModal.end_time) }} 🌙 Midnight

Branch hours: {{ rsBranchHoursLabel }}

No vacant time on this date for this court. Pick another date or court.

Hours past midnight are listed under "Past midnight" (the next date) but belong to {{ formatDateMDY(rsDayDate) }}'s night. For the early-morning hours of {{ formatDateMDY(rsDayDate) }} itself, pick the previous date.

🌙 This start is saved as {{ formatDateMDY(rsDate) }}, {{ formatHour12(parseInt(rsStart)) }} (night of {{ formatDateMDY(rsDayDate) }}).

Branch closes {{ formatHour12(rsBranchHours.end) }} (next day). An end past 12:00 AM saves the hours after midnight as a second slot on {{ formatDateMDY(addDaysToDateStr(rsDate, 1)) }}, same reservation.

🎾 Equipment Rental
{{ ri.name }} {{ rentalRateLabel(ri) }}
Currently rented: {{ ri.current }} · max {{ ri.cap }} per court · returned

{{ ri.cap > 0 && ri.current >= ri.cap ? 'Already at the maximum allowed per court.' : 'No more are free for this time.' }}

+ {{ getEditRentalQty(ri.itemId) - ri.current }} added

Whole reservation · {{ rsReservationSummary.lines.length }} slots
Court Fee (all slots) ₱{{ formatAmount(rsReservationSummary.court) }}
🎾 Equipment Rental (all slots) ₱{{ formatAmount(rsReservationSummary.rental) }}
🏷️ Discount (all slots) −₱{{ formatAmount(rsReservationSummary.discount) }}
Transaction Fee (all slots) ₱{{ formatAmount(rsReservationSummary.fee) }}
Previous total ₱{{ formatAmount(rsReservationSummary.oldTotal) }}
Already paid (all slots) ₱{{ formatAmount(rsReservationSummary.paid) }}
New Reservation Total: ₱{{ formatAmount(rsReservationSummary.newTotal) }} PHP

🌙 Includes {{ rsCarryHours }} hour{{ rsCarryHours === 1 ? '' : 's' }} after midnight on {{ formatDateMDY(addDaysToDateStr(rsDate, 1)) }} (added as an extra slot of this reservation).

Whole reservation is fully paid - nothing more to collect.

Per slot
{{ ln.court }} · editing · {{ ln.status === 'completed' ? 'completed' : 'can\'t be edited' }} · pending
{{ formatDateMDY(ln.date) }} · {{ formatTime12(ln.start) }}-{{ formatTime12(ln.end) }}
₱{{ formatAmount(ln.before) }} ₱{{ formatAmount(ln.after) }}
owes ₱{{ formatAmount(ln.due) }}
paid
Balance to collect (whole reservation): ₱{{ formatAmount(rsReservationSummary.balance) }} PHP

₱{{ formatAmount(rsReservationSummary.pendingDue) }} of it is on pending slots - approve them first to collect it.

Court Fee ₱{{ formatAmount(rsMoney.court) }}
🎾 Equipment Rental ({{ editRentalSummary }}) ₱{{ formatAmount(rsMoney.rental) }}
🏷️ Discount ({{ discountLabelOf(editEndTimeModal) }}){{ editEndTimeModal.discount_type === 'percent' ? ' - recalculated for the new price' : ' - same peso amount' }} −₱{{ formatAmount(rsMoney.discount) }}
Transaction Fee{{ rsFeeNote }} ₱{{ formatAmount(rsMoney.fee) }}
Previous total ₱{{ formatAmount(rsMoney.oldTotal) }}
Already paid by customer ₱{{ formatAmount(rsMoney.paidBasis) }}
{{ rsMoney.block ? 'Maintenance Block (always free):' : 'New Total Amount:' }} ₱{{ formatAmount(rsMoney.newTotal) }} PHP

🌙 Includes {{ rsCarryHours }} hour{{ rsCarryHours === 1 ? '' : 's' }} after midnight on {{ formatDateMDY(addDaysToDateStr(rsDate, 1)) }} (added as a second slot of this reservation).

No change in amount - nothing more to collect.

Not paid yet - the full ₱{{ formatAmount(rsMoney.newTotal) }} is still to be collected.

Balance to collect: ₱{{ formatAmount(rsMoney.balance) }} PHP

Required for {{ payMethodLabel(rsPayMethod) }}. JPG, PNG or WebP, up to 10 MB.

Check that the amount and the account match before saving. Click to view full size.

Customer overpaid: ₱{{ formatAmount(rsMoney.overpay) }} PHP

Only an admin can save a lower total. Choose a slot with the same or a higher total, or ask an admin.

Saved in the Activity Log (and on the booking when money is refunded or kept as credit). The customer is emailed the new schedule.

Saving changes...

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).

ℹ️ Total counts Approved + Completed bookings only. Pending, rejected and cancelled bookings are not charged a transaction fee.
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) : '—' }} Pay {{ formatDateMDY(p.must_pay_first.period_start) }} – {{ formatDateMDY(p.must_pay_first.period_end) }} first Opens {{ formatDateMDY(p.payable_from) }} Awaiting developer review ✓ Settled —
Total outstanding ₱{{ formatAmount(feeTotalOutstanding) }}

Each period shows only its own share. Fees are billed by the date a booking was made, and periods are paid oldest first.

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
{{ 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' }}

The primary admin's role can't be changed.
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' }} Disabled
Please wait...

Court Management & Settings

📌 Reserve Court Form Time Settings 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
No sports yet - the primary admin can add them under Sports on the dashboard.
Lets customers and staff filter the court grid by sport. A court with several sports (e.g. a gym court for basketball and volleyball) asks the customer which one they are booking it for. Leave all unticked if it doesn't matter.
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.
🎾 Equipment this court rents out
No rental items for this branch yet - add them under Rental Items on the dashboard.

Each item is a shared stock for the whole branch. Tick the items customers booking this court may rent; 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) }}–{{ formatEndHourLabel(r.end_hour) }}  ₱{{ formatAmount(r.price) }}/hr  🌙 overnight

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

For an overnight band (e.g. 10:00 PM – 2:00 AM), pick an end time marked "(next day)". It is saved as two parts: the selected days up to midnight, plus the following days from midnight - priced by the real calendar date and hour, same as the grid.

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 }} 🎾 {{ c.rental_items.map(r => r.name).join(', ') }} Inactive
Please wait...

Sports

{{ sportForm.id ? 'Edit Sport' : 'Add Sport' }}

Existing Sports

No sports yet.
{{ sp.name }}Hidden
{{ sp.court_count }} court{{ sp.court_count === 1 ? '' : 's' }} · {{ sp.booking_count }} booking{{ sp.booking_count === 1 ? '' : 's' }}
Please wait...

💵 Collect Payment

{{ payModal.title }} · {{ payModal.ref }} · 🏓 {{ payModal.spots }} player{{ payModal.spots === 1 ? '' : 's' }}

Collecting the balance added after this booking was paid. A discount can't be changed on a paid booking (mark it unpaid first).

Amount to collect ₱{{ formatAmount(payModalMath.collect) }}
Paid with
Scan {{ payAccountFor(payModal.branchId, payModal.method).label }} QR Code
Account Name: {{ payAccountFor(payModal.branchId, payModal.method).account_name }}
Account Number: {{ payAccountFor(payModal.branchId, payModal.method).account_number }}

Required for {{ payMethodLabel(payModal.method) }}. JPG, PNG or WebP, up to 10 MB.

Check that the amount and the account match before recording. Click to view full size.

Some slots of this reservation are still pending - approve them and press Pay again to collect for them. The discount is already worked out over every slot.

The booking is marked paid (no email is sent) and completes automatically once it is over.

Please wait...

🏷️ Discounts

Discounts need a one-time database update. Run migration_discounts.sql in phpMyAdmin, then reopen this window. You can already prepare the list below.

Staff and admins pick one of these reasons in the walk-in booking form, the Pay window and the Open Play walk-in form. Discounts come off the court fee and rentals only - the transaction fee is never discounted. Every discount is saved on the booking (with the ID number / note) and in the Activity Log.

% off
of the price before discount. Admins have no limit. Reasons marked "Admin only" can't be given by staff at all.
No reasons - staff won't see the Discount box until you add one.
Please wait...

Rental Items

{{ rentalItemForm.id ? 'Edit Rental Item' : 'Add Rental Item' }}

Ticking a sport also ticks the branch's courts that serve it below. Leave every sport unticked and the item is offered for any sport on its courts.
This branch has no courts yet.
Stock is shared by every court in the branch and counted per time slot - a unit returns once its booking ends or staff mark it returned. Max Per Court is the most a customer can rent of this item for each court they book (e.g. 4 paddles = one per player); 0 means no limit beyond what is left in stock. "Flat" charges the rate once per unit per booking; "Per hour" charges it for every hour booked.

Existing Items

No rental items yet.
{{ it.name }}Inactive
{{ it.stock }} in stock · ₱{{ formatAmount(it.rate) }} {{ it.rate_type === 'per_hour' ? 'per hour each' : 'each (flat)' }} · max {{ it.max_per_court }}/court
{{ rentalItemBranches.length > 1 ? branchNameForItem(it) + ' · ' : '' }}{{ it.court_ids.length }} court{{ it.court_ids.length === 1 ? '' : 's' }} · {{ (it.sport_ids || []).length ? sportNamesFor(it.sport_ids) : 'All sports' }}
Please wait...

Branch Management

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

Current branch photo
JPG, PNG or WebP. Leave empty to keep the current photo.

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

Pick 12:00 AM (Midnight) or later to stay open past midnight. The after-midnight hours appear at the bottom of the Court Availability grid and are booked on the next calendar date. The latest option (6:00 AM next day) must be at or before the Form Start Time. Open 24 hours with play across midnight: set the End Time to the SAME hour as the Start Time, next day (e.g. 6:00 AM to 6:00 AM next day). Pick your quietest hour - a single booking can't run across that hour.
🎾 Equipment Rental

Rental items (paddles, racquets, balls...) are set up per branch under Rental Items on the dashboard, with their own stock, rate and per-court limit.

Existing Branches

{{ b.name }}
{{ b.city || b.address || 'No location set' }}
{{ b.status }} Main Branch
🕐 {{ formatBranchHours(b.court_start_hour, b.court_end_hour) }}
Please wait...

Payment Methods

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

🚧 Block A Court

Branch: {{ branchNameById(auth.user.branch_id) || 'Unassigned' }}
Courts
No active courts in this branch.
{{ blockForm.court_ids.length ? (blockForm.court_ids.length + ' of ' + blockFormCourtList.length + ' court' + (blockFormCourtList.length === 1 ? '' : 's') + ' selected') : 'Tick at least one court.' }}
Duration: {{ blockCalculatedHours }} hour{{ blockCalculatedHours === 1 ? '' : 's' }} selected
Please wait...

🚧 Remove Block

Reason: {{ removeBlockModal.title }}
When: {{ removeBlockModal.rows[0].when }}
When: Different dates / times - shown under each court below (some were rescheduled).
Ref # {{ removeBlockModal.booking_ref }}
Courts to unblock
{{ removeBlockModal.selected.length ? (removeBlockModal.selected.length + ' of ' + removeBlockModal.rows.length + ' selected - the others stay blocked.') : 'Tick at least one court.' }}
{{ removeBlockModal.endedCount }} court{{ removeBlockModal.endedCount === 1 ? '' : 's' }} of this block already ended and {{ removeBlockModal.endedCount === 1 ? 'is' : 'are' }} kept on record.
Please wait...

📆 Recurring Bookings

Branch: {{ branchNameById(auth.user.branch_id) || 'Unassigned' }}
Repeats every {{ recurringWeekdayLabel }}.

🌙 This runs past midnight. Each session is saved as two parts under one reference: up to 12:00 AM on the day, plus the after-midnight hours on the next day.

New weeks are added automatically as time passes (kept booked up to the booking window). A week that clashes with another booking is skipped and noted in the Activity Log.

Please wait...

🏓 Open Play

Open Play needs a one-time database update. Run migration_open_play.sql and then migration_open_play_v2.sql in phpMyAdmin, then reopen this window.
{{ opForm.id ? 'Edit Open Play' : 'New Open Play' }}
{{ opForm.court_name }} · {{ opDateLabel(opForm.session_date) }} · {{ formatTime12(opForm.start_time) }} – {{ opEndLabel(opForm.end_time) }}
Court, date and time can't be changed. To move it, cancel this session and create a new one. A new price applies to new signups only.
Full session = ₱{{ formatAmount(opFormPotential) }} ({{ opForm.max_players }} × ₱{{ formatAmount(opForm.price_per_player) }}). Players also pay the ₱{{ formatAmount(opFeePerPlayer) }}/player transaction fee on top.
Please wait...

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

No active bookings on this date.
{{ b.court_name }} 🏓 Open Play · {{ b.open_play_spots }} player{{ b.open_play_spots == 1 ? '' : 's' }} {{ 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)) }}
🎾 Rented: {{ rentalsText(b) }} · ✓ {{ rentalsReturnedLabel(b) }}
🔒 Pre-launch {{ approvalLockInfo(b).text }}
⚠️ Past dates cannot be reserved.
Please wait...

System Settings, Carousel & FAQs

General System Settings

Adjusts readability text scaling across system screens.
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.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.

Please wait...