< Back to list of posts

I Built a Simple Self-Service Lecture Scheduler for My Chief Year

Didactics Scheduler

I Built a Simple Self-Service Lecture Scheduler for My Chief Year

I recently began my tenure as chief resident. This means that, among other things, I am responsible for coordinating a full year of subspecialty didactic lectures. That includes eighty four slots and dozens of speakers, all of which have historically been tracked through spreadsheets, email threads, and memory.

I wanted something better. Something where a speaker could visit a link, see what dates are open, and book a slot themselves. No back-and-forth emails. No spreadsheet collisions. Real-time.

So I built it. One HTML file. No framework. No build step. Deployed in under ten minutes.

The Stack

The whole thing runs on three services, all free tier.

Firebase Firestore handles the data and real-time sync. When a speaker books a slot on their phone, every other browser looking at the page updates instantly. No polling. No refresh button.

EmailJS sends me a notification every time someone books. I get the speaker name, their topic, and the date, straight to my inbox.

Firebase Hosting (or Netlify, if you prefer dragging and dropping a folder) serves the single HTML file.

That is it. No backend server. No database to manage. No dependencies to update six months from now when I am busy running a program.

Why One File

I know this sounds wrong. Twelve hundred lines of HTML, CSS, and JavaScript in a single file. But for a tool like this, it is the right call.

There is no build process to break. There is nothing to install. If I need to hand this off to next year's chief, I send them one file and three API keys. They do not need to know what Node is. They do not need a package manager.

The whole thing loads from CDN links for Firebase and EmailJS. The fonts come from Google. Everything else is inline.

Setting It Up

Firebase

Create a project at console.firebase.google.com. Enable Firestore in production mode. Grab your config object from Project Settings and drop it in.

const FIREBASE_CONFIG = { apiKey: "your-api-key", authDomain: "your-project.firebaseapp.com", projectId: "your-project-id", storageBucket: "your-project.firebasestorage.app", messagingSenderId: "123456789", appId: "1:123456789:web:abc123", }

Then lock down the Firestore rules. Speakers can read all bookings and create new ones. Nobody can edit or delete through the client.

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /bookings/{date} {
      allow read: if true;
      allow create: if true;
      allow update, delete: if false;
    }
  }
}

Cancellations happen through the Firebase Console directly. That is intentional. I do not want speakers accidentally unbooking each other.

EmailJS

Create a free account. Connect your email service (Gmail, Outlook, whatever you use). Build a template with variables like {{speaker_name}}, {{topic}}, and {{date}}. Then plug in the three values.

const EMAILJS_CONFIG = { publicKey: "your-public-key", serviceId: "your-service-id", templateId: "your-template-id", }

The Slot Data

Every available lecture date lives in a plain array. No database seeding. No migration scripts. Just an array of objects.

const ALL_SLOTS = [ { date: "2026-05-01", day: "Friday" }, { date: "2026-05-08", day: "Friday" }, { date: "2026-05-15", day: "Friday" }, // ... 84 total slots through June 2027 ]

Adding a slot means adding a line. Removing one means deleting a line. If the date is not in the array, it does not show up. Simple.

The Booking Flow

A speaker visits the page and sees every slot for the academic year, grouped by month. Green means open. Gray means taken. They click an open slot, fill in their name, email, and topic, confirm on a second screen, and they are done.

Under the hood, the booking uses a Firestore transaction. This matters. If two speakers click the same slot at the same time, only one booking goes through. The other gets a polite error and picks a different date. No double bookings. No awkward emails.

Deploying

Two options, both take about five minutes.

Option 1: Firebase Hosting

npm install -g firebase-tools firebase login firebase init hosting firebase deploy --only hosting

Option 2: Netlify Drop

Drag the folder containing index.html onto app.netlify.com/drop. You get a live URL immediately.

I went with Firebase Hosting since I was already using Firestore. One fewer account to manage.

What I Learned

The hardest part of this project was not the code. It was defining the slots. I had to sit down with the academic calendar, cross-reference holidays, conference weeks, and rotation schedules, and manually build out 84 valid lecture dates. That took longer than writing the JavaScript.

The code itself is straightforward. Firestore's onSnapshot listener handles real-time updates. The modal is plain DOM manipulation. The styling uses CSS custom properties so the SGMC brand colors are easy to change if the program moves institutions. I also added a dark mode and light mode toggle that respects the user's system preference on first load and remembers their choice after that.

There is no login system. That was a deliberate choice. Speakers visit the link, book, and leave. Adding authentication would have added friction to a process I am trying to make frictionless. The Firestore rules prevent tampering, and I get an email for every booking. That is enough oversight for a residency lecture schedule.

See It in Action

Didactics Scheduler demo

The Result

I now have a single URL I can send to any subspecialist in the hospital. They see what is available, pick a date, and I get notified. The whole interaction takes about thirty seconds from their end.

For a tool I will rely on every week for the next year, the simplicity is the feature. One file. Three API keys. No maintenance. If something breaks, I open one file and read it top to bottom.

If you are a chief resident or program coordinator dealing with the same scheduling headache, the entire thing is a single HTML file you can fork and customize in an afternoon.

Buy Me A Coffee