Commit Graph
12 Commits
Author SHA1 Message Date
Tomas DvorakandDevin 7b9080eb1c feat(reports): capacity-based utilization + report CSVs
Utilization now measures booked minutes against each member's
availability-rule capacity in the tenant timezone, replacing the
confirmed-share proxy. StaffMetric exposes booked/capacity minutes.

New endpoints: GET /v1/export/staff-report.csv (reuses StaffMetrics)
and GET /v1/export/service-report.csv (per-service bookings/cancelled/
revenue, class bookings rolled up). Export buttons on the staff
performance panel and services page.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 17:10:15 +02:00
Tomas DvorakandDevin 76e45d4178 feat(metrics): per-staff performance view in dashboard
GET /v1/metrics/staff returns bookings, completed, cancelled,
utilization, and revenue per staff member over a trailing window
(default 30d, max 365d). Appointment bookings only — class sessions
have no staff assignment. All active staff appear, including
zero-booking rows.

Staff page gains a localized Performance panel; demo mode seeds
trailing-window bookings and matching metric rows.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 16:34:45 +02:00
Tomas DvorakandDevin 3892e9ce8c feat(waitlist): queue position on the customer manage page
A waitlisted customer saw only a bare status — worse, the badge fell
back to "Pending" for both waitlisted and cancelled states. The view
now carries a 1-based waitlistPosition (earlier-created waitlisted
bookings on the session + 1) and the page renders a warning-tone
notice with the queue spot and auto-promotion explanation, in CS/EN.
Also fixed the badge's nonexistent --danger-soft/--warning-soft tokens.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 13:07:06 +02:00
Tomas DvorakandDevin 7509e82261 feat(reminders): SMS channel for reminder jobs
Tenants paying for the SMS add-on still got email-only reminders —
the channel column existed but nothing armed or dispatched sms jobs.
Booking creation now adds a second job when SMS is enabled and the
customer left a phone; dispatch routes sms jobs through an injected
sender adapter wired to the existing sms.Service, so monthly limits,
usage metering, and Stripe billing apply unchanged. Lifecycle handling
from the previous milestone clears and re-arms both channels together.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 13:03:11 +02:00
Tomas DvorakandDevin c7172544b8 feat(metrics): new-clients KPI and scheduled reminder dispatch
The dashboard promised a new-clients card the API never produced; the
metric now counts distinct customer emails whose earliest-ever booking
lands in the window, joining the other four KPIs with the same trend
comparison. The card grid rebalances for five items.

Reminder dispatch also had no trigger — the endpoint existed but
nothing called it. A scheduled GitHub Actions workflow now posts to it
every 15 minutes using the job-runner key, so reminders actually fire
once the two secrets are configured.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 12:59:44 +02:00
Tomas DvorakandDevin cdad7875e1 fix(reminders): lifecycle-aware jobs and dispatch skip guard
The reminder pipeline fired regardless of what happened to the booking
after creation: cancelled customers got reminded about bookings that
weren't happening, reschedules kept the old reminder time, and
waitlisted bookings armed jobs too. Pending jobs are now cancelled on
both cancel paths, deleted and re-armed on reschedule, and dispatch
joins bookings.status to skip anything not confirmed. Reschedule also
gained the same availability-window and blocked-day validation as
booking creation, closing a 3am-reschedule hole via the manage URL.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 12:47:07 +02:00
Tomas DvorakandDevin 0dc26e0c16 feat(dashboard): real business metrics with week-over-week trends
Dashboard KPIs were three flat counters with no trend data — the cards
rendered a neutral dash where the design intends movement indicators.
Summary now queries three trailing windows and emits four KPIs with
real deltas: bookings this week, completed (new CompletedCount metric
in both repositories), cancellations, and utilization in percentage
points. KPI labels localize in the frontend via a code-to-key map so
Czech operators no longer see English labels on live data.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 11:34:00 +02:00
Tomas DvorakandDevin 668a54ae1d feat(bookings): dashboard cancellation and waitlist promotion
- POST /v1/catalog/bookings/{id}/cancel lets businesses cancel
  tenant-owned bookings; two-step confirm in the detail modal
- Shared PromoteWaitlist helper promotes earliest waitlisted bookings
  when a class seat frees, on both dashboard and customer cancel paths;
  promoted customers get a confirmation email, waitlist ledger marked
  resolved
- waitlisted status surfaced in bookings UI: stats card, filter, warning
  badge, demo seed, CS+EN strings
- BookingRecord gains created_at for fair ordering; GetBookingByID and
  ResolveWaitlistEntry added to the repository surface

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 11:21:46 +02:00
Tomas DvorakandDevin d9aa29dbcb feat(booking): stepped public booking flow with service/staff selection
The public booking page showed a capped flat preview of slots with no way
to pick a service, day, or staff member. Replaces it with a stepped flow:
pick service, optional staff preference, pick a day, pick a time.

Backend: availability accepts serviceId/staffId/date filters (default
response unchanged for the embed widget), a new public catalog endpoint
exposes bookable services and active staff, and slots carry staffName.
Slot generation and booking creation now honor tenant-wide and per-staff
blocked days; appointment requests must fit inside a matching
availability rule and reference tenant-owned, active staff and locations.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-18 10:18:03 +02:00
Tomas DvorakandDevin f2f7439050 refactor(db): replace sqlc with handwritten pgx repos
- Finish Paddle-to-Stripe billing migration; drop paddle/stripe libs
- Renumber migrations; add 00012 memberships+manage_token,
  00013 location_type_capacity
- Add tests: admin, auth, bookings customer service
- Remove graphify-out artifacts and stale env files

Authored By: TDvorak <[email protected]>

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-17 20:21:34 +02:00
Tomas Dvorak 48c3e15a38 cleanup 2026-05-05 09:48:07 +02:00
Tomas Dvorak 035ac8ddb5 first commit 2026-04-10 12:01:36 +02:00