- Add cut_style, shampoo_preference, special_care_notes, custom_fields columns to pets table
- Add grooming_visit_logs table to track per-visit grooming details (cut, products, notes)
- Extend pets API to accept and return new profile fields
- Add /api/grooming-logs endpoint (GET by petId, POST, DELETE)
- Update Pet type with new fields; add GroomingVisitLog type
- Update Clients page: grooming preferences section in pet card, "Log visit" button,
visit history panel showing last 3 visits, expanded pet form with grooming preferences
Co-Authored-By: Paperclip <noreply@paperclip.ing>
* feat: multi-pet client group booking (closesgroombook/groombook#10) (GRO-27)
- Add appointment_groups table: links multiple appointments from one client visit
- Add group_id FK on appointments (nullable, backward-compatible)
- Add GET/POST/PATCH/DELETE /api/appointment-groups endpoints
- POST creates group record + one appointment per pet atomically (with conflict checks)
- DELETE soft-cancels all appointments in the group
- Add GroupBooking.tsx page at /group-bookings with:
- Dynamic pet-slot form (min 2 pets, each with their own groomer/service/end time)
- Auto-calculates end time from service duration
- Group card list showing all pets, groomers, and statuses side-by-side
- Client filter and cancel-all action
- Wire into nav and routing in App.tsx
- Export AppointmentGroup type; add groupId field to Appointment type
Co-Authored-By: Paperclip <noreply@paperclip.ing>
* fix: remove eslint-disable for uninstalled react-hooks plugin; remove unused clientMap (GRO-27)
Co-Authored-By: Paperclip <noreply@paperclip.ing>
---------
Co-authored-by: Groom Book CTO <cto@groombook.app>
Co-authored-by: Paperclip <noreply@paperclip.ing>
* feat: reporting dashboard (closesgroombook/groombook#6) (GRO-24)
- Add GET /api/reports/summary — KPI cards (revenue, appointments, clients)
- Add GET /api/reports/revenue — revenue by day/week/month and by groomer
- Add GET /api/reports/appointments — appointment trends with status breakdown
- Add GET /api/reports/services — service popularity and revenue by service
- Add GET /api/reports/clients — new clients, active count, churn risk list
- Add GET /api/reports/export.csv — CSV export for revenue, appointments, services
- Add Reports page at /reports with date range picker and group-by control
- Wire Reports into nav and routing in App.tsx
Co-Authored-By: Paperclip <noreply@paperclip.ing>
* fix: remove eslint-disable comment for uninstalled react-hooks plugin (GRO-24)
Co-Authored-By: Paperclip <noreply@paperclip.ing>
---------
Co-authored-by: Groom Book CTO <cto@groombook.app>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Implements Phase 1 of groombook/groombook#4 — automated email reminders
for upcoming appointments, with booking confirmations sent immediately
on creation.
- **DB**: new `reminder_logs` table tracks sent reminders per appointment
(unique on appointmentId+type prevents duplicates); `clients` gains
`email_opt_out` boolean (migration 0004_reminder_logs)
- **Email service**: `apps/api/src/services/email.ts` — nodemailer SMTP
transport (disabled when SMTP_HOST is unset, so self-hosted installs
without email config are unaffected); confirmation and reminder email
templates included
- **Reminder scheduler**: `apps/api/src/services/reminders.ts` — node-cron
job runs every minute, checks for appointments in the upcoming reminder
windows (default: 24 h and 2 h), sends emails for opted-in clients,
and records sends in reminder_logs (idempotent via ON CONFLICT DO NOTHING)
- **Confirmation email**: sent fire-and-forget after successful appointment
creation (both single and recurring); never blocks the API response
- **Config**: SMTP_HOST, SMTP_PORT, SMTP_SECURE, SMTP_USER, SMTP_PASS,
SMTP_FROM, REMINDER_HOURS_EARLY, REMINDER_HOURS_LATE env vars documented
in .env.example; all optional — feature is silently disabled without them
- **Types**: Client.emailOptOut field added to shared types package
Co-authored-by: Groom Book CTO <cto@groombook.app>
Co-authored-by: Paperclip <noreply@paperclip.ing>
* feat: recurring appointments with cascading change propagation
Implements GitHub issue #9 — recurring appointment scheduling with
configurable frequency and cascade edit/cancel options.
Changes:
- DB: add `recurring_series` table (frequency_weeks) and series_id /
series_index columns on appointments (migration 0003)
- API POST /appointments: accepts optional `recurrence` object
(frequencyWeeks + count) that creates a full series in one transaction
- API PATCH /appointments/🆔 new `cascadeMode` field
(this_only | this_and_future | all) applies time-delta shifts and
field updates across the series
- API DELETE /appointments/🆔 new `?cascade=` query param cancels
this_only / this_and_future / all series members
- Frontend: booking form gains a "Recurring appointment" checkbox with
frequency and count pickers; calendar chips show a ↻ recurring label;
detail modal shows "Recurring series" badge and a cascade-delete radio
picker for series appointments
Co-Authored-By: Paperclip <noreply@paperclip.ing>
* fix: resolve TypeScript errors in recurring appointments route
Guard against possibly-undefined results from Drizzle .returning()
destructuring — use indexed access + explicit null checks instead of
array destructuring for the recurring_series insert, and add an early
throw when the series or first appointment row is missing.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
---------
Co-authored-by: Groom Book CTO <cto@groombook.app>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Add customer-facing booking flow with three public API endpoints
(/api/book/services, /api/book/availability, /api/book/appointments)
and a four-step React wizard (service → date/time → contact info → confirm).
Availability is computed from real groomer schedules with slot-level
conflict detection. Booking auto-creates or matches clients by email
and uses a transaction to guard against race conditions.
Co-authored-by: Groom Book CTO <cto@groombook.app>
Co-authored-by: Paperclip <noreply@paperclip.ing>
- Add invoice_status and payment_method enums to schema
- Add invoices table: appointmentId, clientId, subtotal/tax/tip/total cents,
status (draft/pending/paid/void), paymentMethod, paidAt, notes
- Add invoice_line_items table: invoiceId, description, qty, unitPrice, total
- Migration 0002_invoices.sql with FK constraints and journal entry
- POST /api/invoices — create invoice with line items
- POST /api/invoices/from-appointment/:id — one-click invoice from appointment,
pre-populated with service name and price; returns 409 if already invoiced
- GET /api/invoices — list with optional ?status/clientId/appointmentId filters
- GET /api/invoices/:id — invoice with line items
- PATCH /api/invoices/:id — update status, payment method, tip, notes; auto-sets
paidAt when marking paid; blocks edits on voided invoices
- Add Invoice/InvoiceLineItem types to @groombook/types
- InvoicesPage: list view with status filter, create from appointment modal,
detail modal with tip input, payment method selector, Mark as Paid/Void actions
- Add Invoices nav link in App.tsx
Co-authored-by: Groom Book CTO <cto@groombook.app>
Co-authored-by: Paperclip <noreply@paperclip.ing>
- Add health_alerts column to pets table (migration 0001)
- Update DB schema, shared types, and API Zod validation
- Show health alerts prominently (red badge) in pet cards
- Add health alerts field to pet form with allergy/condition placeholder
- Add delete button per pet with confirmation dialog
- Add delete client button with cascade warning
Closesgroombook/groombook#2
Co-authored-by: Groom Book CTO <cto@groombook.app>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Fixes five bugs flagged in CEO code review (GitHub issues #18–22):
- #18: Wrap conflict check + insert/update in a DB transaction to
prevent double-booking race conditions under concurrent load.
- #19: PATCH conflict detection now falls back to the existing
appointment's staffId when staffId is omitted from the request body,
so rescheduling always checks for conflicts.
- #20: DELETE endpoint now soft-deletes (status = 'cancelled') instead
of hard-deleting, preserving audit trail and financial records.
- #21: Staff DELETE checks for existing non-cancelled appointments
before deleting and returns 409 if any are found, preventing orphaned
references.
- #22: AUTH_DISABLED=true now logs a startup warning in development and
calls process.exit(1) in production, preventing accidental auth
bypass in deployed environments.
Co-authored-by: Groom Book CTO <cto@groombook.app>
Co-authored-by: Paperclip <noreply@paperclip.ing>
pnpm-lock.yaml landed with PR #15, so setup-node can cache pnpm deps
again. This speeds up CI by avoiding full re-installs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Paperclip <noreply@paperclip.ing>
- Fix Dockerfiles to copy pnpm-lock.yaml (frozen-lockfile compliance)
- Add migrate target to API Dockerfile using builder stage
- Add migrate service to docker-compose that runs before API starts
- Add AUTH_DISABLED env var bypass to auth middleware for dev/Docker
- Proxy /api/ from nginx to API container (no CORS needed)
- Include initial Drizzle migration (0000_colossal_colossus.sql)
- Add .env.example with all configurable variables
- Update README with Docker self-hosting instructions
Closes#7
Co-authored-by: Groom Book CTO <cto@groombook.app>
Co-authored-by: Paperclip <noreply@paperclip.ing>
* feat: appointment scheduling, client/pet/service/staff CRUD UI
- Weekly calendar view with navigation, color-coded by status
- Booking form with client→pet→service→staff→date/time flow
- Double-booking conflict detection on POST/PATCH appointments
- DELETE /api/appointments endpoint
- Staff API route (/api/staff) with full CRUD
- Clients page: searchable list, create/edit clients, add/edit pets
- Services page: table with create/edit/toggle-active
- Staff page: table with create/edit/toggle-active
- Nav bar with active-link highlighting, Staff link added
Resolves GitHub groombook/groombook#1, #2, #8
Co-Authored-By: Paperclip <noreply@paperclip.ing>
* fix: remove unused import, fix useCallback deps
- Remove unused `or` import from drizzle-orm in appointments route
- Compute week end directly in loadAppointments callback to avoid
exhaustive-deps lint warning (weekEnd derived from weekStart)
Co-Authored-By: Paperclip <noreply@paperclip.ing>
* chore: add pnpm lockfile
Required for CI --frozen-lockfile installs.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
* fix: resolve all typecheck, lint, and test failures
- Add @types/node to packages/db devDependencies (typecheck was missing process)
- Re-export drizzle-orm helpers (eq, gte, etc.) from @groombook/db to avoid
duplicate-instance type conflicts; remove drizzle-orm direct dep from API
- Add @hono/zod-validator and jose as direct API dependencies
- Merge duplicate @groombook/db imports in all route files
- Fix noUncheckedIndexedAccess errors: appointments PATCH, web calendar grid
- Fix weightKg/dateOfBirth type conversion in pets route (numeric→string, string→Date)
- Add eslint.config.js for API and web (ESLint 9 flat config format)
- Add vitest.config.ts with passWithNoTests for API and web
Co-Authored-By: Paperclip <noreply@paperclip.ing>
---------
Co-authored-by: Groom Book CTO <cto@groombook.app>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Merging with admin override — CI fails because main has no pnpm-lock.yaml yet (coming in PR #15). The runner and pnpm-setup fixes in this PR are verified correct from the CI logs.
setup-node's cache: pnpm requires pnpm-lock.yaml to exist. The lockfile
is coming in PR #15 but isn't on main yet. Remove caching for now —
it'll be re-enabled once the lockfile lands.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
pnpm/action-setup@v4 now errors when both the action's `version` input
and package.json's `packageManager` field specify a version. Remove the
action input — package.json's `pnpm@9.15.4` is authoritative.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All CI runs are stuck in queued — zero self-hosted runners are registered
for the groombook-runners label. Switch to ubuntu-latest to unblock PRs.
Tracked in groombook/infra#2 — will revert once self-hosted runners are
deployed to the cluster.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sets up the initial project structure for groombook/groombook:
- pnpm monorepo with apps/api (Hono + TypeScript), apps/web (React + Vite + PWA), packages/db (Drizzle ORM), packages/types (shared types)
- Core DB schema: clients, pets, services, appointments, staff with CNPG-compatible Postgres
- REST API routes for clients, pets, services, appointments with Zod validation
- OIDC auth middleware for Authentik integration
- React PWA with vite-plugin-pwa, service worker, offline caching, installable manifest
- GitHub Actions CI: lint, typecheck, test, build, Docker image build (groombook-runners)
- Dockerfiles for API (Node.js) and Web (nginx)
- docker-compose.yml for local development
Co-Authored-By: Paperclip <noreply@paperclip.ing>