$ cd ../projects

~/projects/klinika/README.md

IN PROGRESSPRIVATE# Full-Stack

Klinika

Klinika is client work in progress: online booking for a clinic whose secretary used to coordinate every patient by hand. Patients pick a doctor and either a fixed time slot or a numbered queue spot, and can see whether the doctor is in today. The front desk and doctors run the day from a role-based staff dashboard. The booking rules live in Postgres constraints, so two simultaneous requests cannot both win the same slot.

Next.js 16React 19TypeScriptTailwind 4PostgreSQL 17Docker ComposeVitestPlaywright
automated tests
54
real-browser checks
24
DB-level booking guards
3
staff roles
3

cat architecture.flow

System Architecture

architecture.flow

Patient booking

Public site
Pick doctor
Slot or queue spot
Booking API (validated)
PostgreSQL guards

Front desk

Staff sign-in
Server-side session
Day dashboard
Status actions
Audit log

Planned

SMS reminders
Confirm / reschedule links
Messenger reception bot
Post-visit follow-ups

cat problem.md

Business Problem

The clinic runs on a secretary who coordinates every patient by hand. There is no online booking, and patients can't tell whether their doctor is coming in today.

Booking is harder than it looks. Doctors work differently: some see patients at fixed times, others run a daily queue with a capacity. Two people booking the same slot at the same moment must never both succeed, and a cancellation must free the spot immediately.

It also handles patient contact details, so the Philippine Data Privacy Act applies. The first version stores booking data only, with no clinical notes.

cat solution.md

Solution

I built a custom Next.js and Postgres app, in phases, each verified before the next starts. Each doctor is set to either slots or queue mode. The public site shows live doctor status, and a four-step flow takes a new or returning patient from choosing a doctor to a confirmation.

Double-booking is prevented where it can't be bypassed: in the database. Partial unique indexes ignore cancelled rows, so a cancellation frees its slot at once. A row lock on the schedule keeps queue capacity and numbering from racing.

Staff get a dashboard with three roles. The secretary runs the day, doctors see and update only their own patients and status, and an admin can do everything. Every page and API call re-checks the session in the database, and sensitive actions are written to an audit log.

Messaging is deliberately not in Phase 1. The site makes no promise of reminders until SMS and Messenger actually exist.

ls ./components

Architecture Decisions

Booking engine

Slot and queue modes per doctor, day-off exceptions, a 30-day booking window, and typed error codes. Appointment times are stored as clinic wall-clock time because it is a single-timezone clinic.

Database guards

Three partial unique indexes stop a slot, a queue number, or a patient's same-day booking from being taken twice. A composite foreign key makes the database reject a queue block on a slot-mode doctor.

Public site and API

Mobile-first pages with a live "who is in today" card, plus a small validated JSON API with stable error codes, rate-limited lookups and security headers on every response.

Staff dashboard

Today's appointments with confirm, cancel, attended and no-show actions, doctor In, Delayed and Away status, and a call list of unconfirmed bookings. On phones, appointments render as cards, not a table.

Auth and audit

Admin-created accounts with a temporary password that must be changed at first sign-in. Sessions are random tokens with only a hash stored, 12-hour expiry, lockout after repeated failures, and an audit log of logins and status changes.

Test harness

Tests run against a real Postgres in a throwaway database, and the setup refuses to run against any other. The concurrency test was proven real by removing a guard index and watching it fail.

./run --trace

Booking a visit

  1. 01

    Choose

    The patient picks new or returning, then a doctor, then a day.

  2. 02

    Reserve

    A slot or queue spot is claimed inside a transaction. If another request got there first, the database rejects this one and the patient is shown fresh availability.

  3. 03

    Confirm

    The patient gets a confirmation, with a queue number where the doctor runs a queue.

  4. 04

    Run the day

    Front desk and doctors move the appointment through confirmed, attended or no-show. Cancelled is final and frees the spot.

klinika --phases

Roadmap

Phase 1: core (in progress)

Doctors and schedules, booking site, staff dashboard, double-booking guard. Built through the day dashboard; admin screens for accounts, schedules and days off are next.

Phase 2: notifications

SMS reminders, one-tap confirm, reschedule or cancel links, and a call list for unconfirmed patients.

Phase 3: reception bot

A Messenger bot for FAQs, "my appointment" lookups and doctor status. Clinical questions hand off to the secretary.

Phase 4: follow-ups

Post-visit follow-ups, waitlist, no-show tracking and reports.

tail -f incidents.log

Engineering Challenges

Proving the double-booking test is real

Symptom
A concurrency test that passes proves nothing unless it can also fail.
Root cause
If the unique index were missing, the application code alone might still happen to pass in a quiet test run.
Fix
I dropped the slot index on purpose and fired six simultaneous bookings at one slot. All six succeeded and the test failed, which confirmed the test, and the index, are doing the work.

Helper script missing a dependency in the production image

Symptom
The account-creation script crashed inside the container even though the app itself ran fine.
Root cause
Next's standalone build bundled the password-hashing library into server chunks, so it was absent from the image's node_modules.
Fix
Marked the library as an external server package so the standalone image keeps it.

cat package.json | jq .stack

Technology Stack

Next.js 16React 19TypeScriptTailwind 4PostgreSQL 17Docker ComposeVitestPlaywright

echo $EXIT_STATUS

Project Outcome

outcome.log

Phase 1 is running in a private staging environment with 54 passing tests and a real-browser check of the full booking journey, including no horizontal overflow on a phone-sized screen.

The project is client work in progress and not yet publicly exposed. Notifications and the Messenger bot are planned for later phases and are not claimed anywhere on the site.