Building a Full-Featured Calendar in Phoenix LiveView (recurring events, drag-drop, multi-user)

We built a Google Calendar-style scheduling interface entirely in Phoenix LiveView for a clinic management platform. This post walks through the data model, recurring appointments, overlap validation, multi-user views, drag-and-drop with JS hooks, CSS-based positioning tricks, and performance patterns that kept it fast at scale.

13 Likes

Very cool. Do you have a live demo, I want to play with it, especially for drag and drop with optimistic update.

2 Likes

Its on the clients platform, but maybe we can do a stripped down version, a git repo folks can try, let me see if I can get the team to cook this up quickly :smiley:

2 Likes

Thanks for sharing back with the community! I like how you described not only what you did, but why you did it and some of the tradeoffs involved.

Performance Considerations

LiveView re-renders can add up with complex UIs. A few patterns kept the calendar smooth:

1. Use phx-update="replace" strategically

The default phx-update mode diffs and patches. For the appointments list, we use replace on the container — it’s faster to replace the whole grid than diff dozens of positioned cards.

The default phx-update is "replace"(Bindings — Phoenix LiveView v1.1.22). I wonder what performance problem you were hitting?

1 Like

Dang, how did this pass the proof read, yikes.

Sorry that sentence has a ton of “brain fart” in it, for example “For the appointments list, we use replace on ….”

Their is no appointments list, lol :joy:

Here is what I was intending to say here,

The calendar is composition of several LiveView components, with a parent at top, it renders the calendar, renders appointment cards in slots and so on. Empty slots are also interactive, you can click on empty slot, and it shows a modal with date and time pre-filled for those slots to create an appointment no, very similar to google calendar. When someone create an appointment, we do the creation and use optimistic update to render the appointment card, as we fire the phx-update, but instead of doing all the event handling on each component, all of it is handled by the parent component and child component just call handles, which trigger logic from parent component, this allows us to refresh the entire calendar rendering when we get updated data, to make sure the final state of view is always consistent with the server representation. That is what the, “it’s faster to replace the whole grid than diff dozens of positioned cards.” was supposed to mean.