Courtio - {{ selectedClubName }} Dashboard
{{ userRole }}

Bookings

Calendar view of all bookings for your club.

{{ bookingStats.count }} bookings {{ bookingStats.playtomic }} playtomic {{ bookingStats.hours }} h {{ bookingStats.occupancy }} h/court

Loading...

Loading club data...

Time
{{ court.name }}

{{ bookingTypeLabel(selectedBooking) }}

Court {{ selectedBooking.court?.name || '—' }}
Date {{ formatDate(selectedBooking.date) }}
Duration {{ selectedBooking.duration }} min
Level {{ selectedBooking.minLevel ?? '—' }} - {{ selectedBooking.maxLevel ?? '—' }}
Comments {{ selectedBooking.comments }}
Coach {{ selectedBooking.coach.firstName }} {{ selectedBooking.coach.lastName }}
Created
Total Price {{ (selectedBooking.totalPrice / 100).toFixed(2) }} {{ selectedBooking.currency || '' }}
Score {{ selectedBooking.score1 ?? '—' }} - {{ selectedBooking.score2 ?? '—' }}

Players

{{ p.firstName }} {{ p.lastName }} {{ playerPaid(selectedBooking, p) ? 'Paid' : 'Unpaid' }}

{{ bookingModal.mode === 'create' ? 'Create Booking' : 'Edit Booking' }}

{{ bookingModal.players[i - 1].firstName }} {{ bookingModal.players[i - 1].lastName }} ×
{{ bookingModalError }}

Payments

Payment history for your club.

Loading...

No past due payments.

Date Court Player Type Action
{{ formatDate(p.bookingDate) }} {{ p.courtName }} {{ p.playerFirstName + ' ' + p.playerLastName }} {{ p.bookingComments ? p.bookingComments.replace(' - playtomic', '') : 'Blocking' }} Playtomic Match Blocking Event Training Other Saving...

Loading...

No upcoming payments.

Date Court Player Type Action
{{ formatDate(p.bookingDate) }} {{ p.courtName }} {{ p.playerFirstName + ' ' + p.playerLastName }} {{ p.bookingComments ? p.bookingComments.replace(' - playtomic', '') : 'Blocking' }} Playtomic Match Blocking Event Training Other Saving...

Due payments — {{ playerDuesName }}

Loading...

No due payments for this player.

Date Court Type Amount
{{ formatDate(d.bookingDate) }} {{ d.courtName }} Match Blocking Event Training Other {{ (d.amount / 100).toFixed(2) }} {{ (d.currency || 'RON').toUpperCase() }}
Total: {{ (playerDuesTotal / 100).toFixed(2) }} {{ (playerDuesCurrency || 'RON').toUpperCase() }}

Payments — {{ playerHistoryName }}

{{ paymentPeriodLabel }}

No payments for this player in the selected period.

Booking date Court Booking Type Paid at Amount
{{ formatDate(d.bookingDate) }} {{ d.courtName }} Match Blocking Event Training Refund Playtomic Paid 📱 Manual 💵 Manual 💳 VIP 👑 Manual {{ formatDate(d.paidAt) }} {{ d.paymentMethod === 'vip' ? '0.00 ' + (d.currency || 'RON').toUpperCase() : (d.amount ? (d.amount / 100).toFixed(2) + ' ' + (d.currency || '').toUpperCase() : '—') }}
Total: {{ (playerHistoryTotal / 100).toFixed(2) }} {{ (playerHistoryCurrency || clubCurrency || 'RON').toUpperCase() }} refunds already subtracted ({{ (playerHistoryRefunded / 100).toFixed(2) }})
Total: {{ paymentsTotal }}
Day Cash Card Viva Playtomic Total VIPs
{{ row.label }} {{ row.cash.toFixed(2) }} {{ row.card.toFixed(2) }} {{ row.viva.toFixed(2) }} {{ row.playtomic.toFixed(2) }} {{ row.total.toFixed(2) }} {{ historyCurrency }} {{ row.vips }}

Loading...

No payments found.

Booking date Court Booking Player Amount Type Marked by Paid at Action
{{ formatDate(p.bookingDate) }} {{ p.courtName }} Match Blocking Event Training {{ p.playerFirstName + ' ' + p.playerLastName }} {{ p.playerFirstName ? p.playerFirstName + ' ' + p.playerLastName : (p.bookingComments ? p.bookingComments.replace(' - playtomic', '') : '—') }} {{ p.paymentMethod === 'vip' ? '0.00 ' + (p.currency || 'RON').toUpperCase() : (p.amount ? (p.amount / 100).toFixed(2) + ' ' + (p.currency || '').toUpperCase() : '—') }} Refund Playtomic Paid 📱 Manual 💵 Manual 💳 VIP 👑 Manual {{ p.markedBy || '—' }} {{ formatDate(p.paidAt) }} Refunding...

Classes

Trainings per coach for the selected month.

{{ classes.length }} training{{ classes.length === 1 ? '' : 's' }}

Loading...

No trainings this month.

{{ group.coachName }} — {{ group.count }} training{{ group.count === 1 ? '' : 's' }}

Date Court Students Comment
{{ formatDate(t.date) }} {{ t.court?.name || '—' }} {{ classStudents(t) }} {{ t.comments || '—' }}

Players

Players who have bookings at your club.

{{ playersTotal }} player{{ playersTotal !== 1 ? 's' : '' }}

Edit Player {{ selectedPlayer.lastDevice }} · last seen {{ formatDateShort(selectedPlayer.lastSeenAt) }}

WhatsApp
{{ playerSaveMsg }}
{{ playerSaveError }}

Loading...

{{ playersSearch ? 'No players match your search.' : 'No players found.' }}

Player {{ sortArrow('name') }} User ID Phone Level Points {{ sortArrow('points') }} Type Matches {{ sortArrow('matchCount') }} Matches (30d) {{ sortArrow('matchCount30d') }} Last Active {{ sortArrow('lastPlayed') }} Device
{{ (p.firstName?.[0] || '') + (p.lastName?.[0] || '') }}
{{ p.firstName }} {{ p.lastName }}
Male
Female
Unknown
{{ p.userId || '—' }} {{ p.phone || '—' }} {{ playerLevel(p.points) }} {{ p.points }} {{ playerTypeName(p.type) }} {{ p.matchCount }} {{ p.matchCount30d }} {{ formatDateShort(p.lastPlayed) }} {{ p.lastDevice || '—' }}
Page {{ playersPage }} of {{ playersTotalPages }}

Club Details

Edit your club information and manage courts.

Loading...

{{ clubSaveMsg }}
{{ clubSaveError }}

Club Images

{{ clubImageError }}
Club logo
Uploading...

Courts

{{ court.name }}
No courts added yet.

Club Prices

Manage court pricing rules.

{{ editingTierKey ? 'Edit Price Tier' : 'Add Price Tier' }}

{{ clubCurrency }}
{{ priceTierError }}
{{ priceTierSuccess }}

Loading...

No prices configured yet.

Courts Days Hours Price / 30 min Price / hour
{{ g.courtIds.map(id => courtName(id)).join(', ') }} {{ g.days.map(d => dayName(d)).join(', ') }} {{ formatHour(g.startHour) }} - {{ formatHour(g.endHour) }} {{ (g.price / 100).toFixed(2) }} {{ clubCurrency }} {{ (g.price * 2 / 100).toFixed(2) }} {{ clubCurrency }}

Calendar Colors

Define keyword-based colors for bookings in the calendar. When a booking's comments contain a keyword, it will be displayed in the matching color.

Loading...

{{ colorForm.id ? 'Edit Color' : 'Add Color' }}

Matched against booking comments (case-insensitive)
Colors all Training bookings for this coach
Use alone for a type color, or with a keyword to restrict it
A rule needs at least one of: keyword, coach, or booking type. Coach > keyword > type when several could match.
{{ colorSuccess }}
{{ colorError }}
Color Keyword Coach Booking Type Actions
{{ color.hex }}
{{ color.keyword || '—' }} {{ coachName(color.coachId) }} {{ matchTypeName(color.matchType) }}
No calendar colors configured yet. Add one above.

Send Push Notification

Send a push notification to players at this club.

Filter Recipients

{{ p.firstName }} {{ p.lastName }} Lvl {{ playerLevel(p.points) }}
{{ notifSelectedPlayer.firstName }} {{ notifSelectedPlayer.lastName }} ×

Notification Content

{{ notifSuccess }}
{{ notifError }}

Club Admins

Manage who can access this club's backoffice and mobile-app admin features. Admins must already have a Courtio app account (phone-based).

Add Admin

{{ adminFormSuccess }}
{{ adminFormError }}

Loading...

No admins for this club.

Name Phone Role
{{ ((a.firstName || '') + ' ' + (a.lastName || '')).trim() || '—' }} {{ a.phone }}
{{ a.roles }} (you)

Playtomic Sync

Configure two-way sync with Playtomic for this club.

Loading...

Sync: Courtio → Playtomic (playtomic user and password)

Credentials used to create blocking events on Playtomic when bookings are made in Courtio. Use a Playtomic account with minimal rights (e.g. receptionist) — it only needs to create and delete bookings.

Found in your Playtomic Manager URL: manager.playtomic.io/tenant-id/scheduler

Sync: Playtomic → Courtio (gmail email and app-password)

Gmail inbox that receives Playtomic booking notifications. Used to sync Playtomic bookings into Courtio.

Use a Gmail App Password, not your regular password.
{{ playtomicSuccess }}
{{ playtomicError }}

Viva.com Payment Credentials

Leave blank if the club is not yet onboarded at viva.com.

Loading...

{{ paymentSettingsSuccess }}
{{ paymentSettingsError }}

Shop

Products, stock and sales for this club. Prices in {{ clubCurrency }}.

Loading...

Loading...

Loading...

DateProductRequested byPhoneNoteStatusAction
{{ formatDate(r.createdAt) }} {{ r.productName }}{{ r.size ? ' (' + r.size + ')' : '' }} {{ r.firstName ? r.firstName + ' ' + r.lastName : '—' }} {{ r.phone || '—' }} {{ r.note || '—' }} {{ r.status }}

No requests.

Logs

Who did what: bookings created/deleted, players joined/left/added/removed, scores.

Loading...

TimeWhoActionTargetBookingDetail
{{ formatDate(l.createdAt) }} {{ l.actor || 'System' }} {{ l.action }} {{ l.target || '—' }} #{{ l.bookingId }} · {{ l.court }} · {{ formatDate(l.bookingDate) }} {{ l.detail || '' }}

No logs.

Coaches

Photo and bio for each coach of this club. Coaches are managed in Club Admins; here you set what players see.

Loading...

No coaches for this club. Add coach roles in Club Admins first.

Reports

Occupancy and revenue, aggregated — no per-player data. Prime time = weekdays 18:00–22:00 · Mornings = opening–12:00 · Weekends = Sat+Sun full day. Only elapsed days count, so the current period stays honest.

{{ reportData.courtCount }} courts · open {{ reportData.openingTime }}–{{ reportData.closingTime }}

Loading...

Could not load reports.

{{ reportError }}

VIP Memberships

Players with a VIP membership pay nothing for matches in this club. Default = every match free. Morning first = one free match per day, any day of the week (weekends included), starting at or before 15:00 and ending by 16:30.

Add {{ vipTypeTab === 'morning_first' ? 'morning_first' : 'default' }} VIP

{{ p.firstName }} {{ p.lastName }} — {{ p.phone || 'no phone' }}
Selected: {{ vipSelectedPlayer.firstName }} {{ vipSelectedPlayer.lastName }} (id {{ vipSelectedPlayer.id }})
{{ vipError }}

{{ vipTypeTab === 'morning_first' ? 'Morning-first' : 'Default' }} members

Loading…

No members in this category.

Player Phone Expires
{{ m.firstName }} {{ m.lastName }} {{ m.phone || '—' }} {{ m.expiresAt || 'never' }}

Test Login

Logging in with phone +40777999000 and code 123456 will sign in as the player below. Leave unset to fall back to the default test user.

Loading...

Currently mapped to: {{ (testLogin.player.firstName || '') + ' ' + (testLogin.player.lastName || '') }} — {{ testLogin.player.phone || 'no phone' }} (userId {{ testLogin.userId }}) Not set — falling back to default test user.
{{ testLoginSuccess }}
{{ testLoginError }}