<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Neha Mangla</title>
    <description>The latest articles on DEV Community by Neha Mangla (@nehamangla).</description>
    <link>https://dev.to/nehamangla</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4213658%2F802c0150-a289-4632-9e8b-8f9f6f7b5545.png</url>
      <title>DEV Community: Neha Mangla</title>
      <link>https://dev.to/nehamangla</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nehamangla"/>
    <language>en</language>
    <item>
      <title>Why Building a Reliable Calendar UI Is Harder Than It Looks</title>
      <dc:creator>Neha Mangla</dc:creator>
      <pubDate>Wed, 28 Oct 2026 18:43:02 +0000</pubDate>
      <link>https://dev.to/nehamangla/why-building-a-reliable-calendar-ui-is-harder-than-it-looks-5e2c</link>
      <guid>https://dev.to/nehamangla/why-building-a-reliable-calendar-ui-is-harder-than-it-looks-5e2c</guid>
      <description>&lt;p&gt;In a workforce calendar, shifts, absences, and days off can look like variations of the same event. They aren't. A day off belongs to a calendar date; a shift belongs to a span of time. Mixing those rules can move an event to the wrong day without producing an obvious error.&lt;/p&gt;

&lt;p&gt;I worked on a workforce calendar using React, TypeScript, and Bryntum. It combined shifts, absences, overtime, and days off from separate services. They looked similar in the calendar, but their date handling and visibility rules were not interchangeable.&lt;/p&gt;

&lt;h2&gt;
  
  
  A day off that moves to yesterday
&lt;/h2&gt;

&lt;p&gt;Take a day-off record with the value &lt;code&gt;2026-10-23&lt;/code&gt;. Parsing that string as midnight UTC and displaying it in Los Angeles can produce October 22. The browser has converted an instant correctly; the mistake is that a day off was never meant to be an instant.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Incorrect interpretation&lt;/th&gt;
&lt;th&gt;Expected result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;2026-10-23&lt;/code&gt; — date-only day off&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Oct 22&lt;/strong&gt; — UTC midnight formatted in Los Angeles&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Oct 23&lt;/strong&gt; — preserve the intended calendar day&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A shift timestamp is different: it represents a moment that may be displayed at different local times. We kept those two paths separate. Here's a reduced TypeScript illustration of that boundary:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TypeScript · illustrative example (not production code)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;CalendarRecord&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dayOff&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;shift&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;startsAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;toCalendarEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CalendarRecord&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dayOff&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Keep the calendar date as a date, not a UTC timestamp.&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dayOff&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;calendarDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// A timestamp represents an instant; localize at display time.&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;shift&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;startsAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startsAt&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important choice is that the date-only branch does not call &lt;code&gt;new Date()&lt;/code&gt; or silently apply timezone conversion. In a real application, the downstream rendering adapter also needs to preserve that meaning. I would test the same record across timezones and calendar views, not just test the conversion function in isolation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The boundary before Bryntum
&lt;/h2&gt;

&lt;p&gt;The services weren't returning one shared event format. We could have mapped each response directly into the calendar, then handled the differences in individual views. I chose a typed application event model instead. It gave IDs, categories, date handling, and permission metadata a consistent shape before anything reached Bryntum.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where service-specific data stops and calendar rendering begins&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SERVICE CONTRACTS
  Shifts · overtime           Absences · days off
  Identifiers and time fields Categories and visibility rules
                 ↓
        Typed application event model
        Stable identity · date handling · categories
        Permission metadata
                 ↓
  Product rules               View adapters
  Normalized events           Day · week · month · team
  Consistent access rules
                 ↓
    Bryntum: event layout, interactions, and presentation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The extra mapping layer meant more code and more tests. The alternative was to maintain the same conversion and visibility logic in several places, including day, week, month, and team views. I preferred one boundary where the behavior could be reviewed and tested.&lt;/p&gt;

&lt;h2&gt;
  
  
  A responsive calendar still needs fresh data
&lt;/h2&gt;

&lt;p&gt;Calendar navigation creates an awkward balance. People move between weeks and return to ranges they recently viewed. Fetching everything again makes the UI feel slow; never refreshing cached results can leave an old schedule on screen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example: moving across weeks, then returning&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;01&lt;/td&gt;
&lt;td&gt;Week 42&lt;/td&gt;
&lt;td&gt;Load the visible range&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;02&lt;/td&gt;
&lt;td&gt;Week 43&lt;/td&gt;
&lt;td&gt;Navigate forward&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;03&lt;/td&gt;
&lt;td&gt;Week 42&lt;/td&gt;
&lt;td&gt;Reuse cached results; revalidate&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We reused loaded ranges, debounced quick navigation, and refreshed in the background when needed. The selected date and view could be restored, but the schedule itself still needed revalidation. Another important check was ensuring a late response from an earlier range could not overwrite the one currently on screen.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I checked beyond the happy path
&lt;/h2&gt;

&lt;p&gt;A populated calendar isn't proof that its data is right. The cases worth checking were the quiet failures: date-only records shifting days, confidential absence details showing in another view, cached ranges not refreshing, and an old request returning after the user had navigated somewhere else.&lt;/p&gt;

&lt;p&gt;What I would carry into the next scheduling project is not a particular calendar configuration. It's the decision to make dates, permissions, and data freshness explicit before rendering. That gives the UI library less to guess about, and gives the rest of the application rules that can be tested without opening a calendar.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://nehamangla.com/blog/reliable-scheduling-uis" rel="noopener noreferrer"&gt;nehamangla.com&lt;/a&gt;.Explore more of my engineering work and articles on my portfolio.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>typescript</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
