Repository: SwitchbackTech/compass
Pull request #188 (2024-12-14 00:00:02)

♻️ refactor: make header labels more accurate
This resolves issue #165 

What I've done:

- Created a `getCalendarHeadingLabel` util function for `Header.tsx`.
- Refactored `getWeekRangeLabel` util function so it can return two different formats.
- Added a `web.date.util.test.ts` file, containing unit tests for `getWeekRangeLabel` and `getCalendarHeadingLabel`.
- Left `getMonthListLabel` unmodified, as it already behaves as expected (returns the full month name of week start).

I didn't write unit tests for `getMonthListLabel` due to its simplicity, but I can add it if you see it as necessary.
All unit tests (including the added ones) have passed.

murilo9: Some observations: in my opinion, the format of Someday Week label could be improved, as "M.D - D" is not very intuitive. Maybe the "MMM D - D" format could be better? That way it would be more intuitive and not repeat the full month name ("MMMM") like in the Someday Month label.
tyler-dane: > Some observations: in my opinion, the format of Someday Week label could be improved, as "M.D - D" is not very intuitive. Maybe the "MMM D - D" format could be better? That way it would be more intuitive and not repeat the full month name ("MMMM") like in the Someday Month label.

Thank you for the suggestion! I'll keep this in mind and compare it to other user feedback on this change

Patch:
diff --git a/packages/web/src/common/utils/web.date.util.ts b/packages/web/src/common/utils/web.date.util.ts
index 5da8f14a..a28e49f2 100644
--- a/packages/web/src/common/utils/web.date.util.ts
+++ b/packages/web/src/common/utils/web.date.util.ts
@@ -210,12 +210,32 @@ export const getTimesLabel = (startDate: string, endDate: string) => {
 };
 
 export const getWeekRangeLabel = (weekStart: Dayjs, weekEnd: Dayjs) => {
+  const isSameMonth = weekStart.month() === weekEnd.month();
   const start = weekStart.format("M.D");
-  const end = weekEnd.format("D");
+  const end = weekEnd.format(isSameMonth ? "D" : "M.D");
   const label = start + " - " + end;
   return label;
 };
 
+export const getCalendarHeadingLabel = (
+  start: Dayjs,
+  end: Dayjs,
+  now: Dayjs
+) => {
+  const startsThisYear = now.year() === start.year();
+  const endsThisYear = now.year() === end.year();
+
+  if (startsThisYear && endsThisYear) {
+    return start.format("MMMM");
+  } else if (startsThisYear || endsThisYear) {
+    const startLabel = start.format("MMM YY");
+    const endLabel = end.format("MMM YY");
+    return `${startLabel} - ${endLabel}`;
+  } else {
+    return start.format("MMMM YYYY");
+  }
+};
+
 export const mapToBackend = (s: Schema_SelectedDates) => {
   if (s.isAllDay) {
     const adjustedEnd = dayjs(s.endDate).add(1, "day");
diff --git a/packages/web/src/views/Calendar/components/Header/Header.tsx b/packages/web/src/views/Calendar/components/Header/Header.tsx
index 214fbbcc..de52fccd 100644
--- a/packages/web/src/views/Calendar/components/Header/Header.tsx
+++ b/packages/web/src/views/Calendar/components/Header/Header.tsx
@@ -1,8 +1,7 @@
 import React, { FC } from "react";
-import { Dayjs } from "dayjs";
+import dayjs, { Dayjs } from "dayjs";
 import { useAppDispatch, useAppSelector } from "@web/store/store.hooks";
 import { AlignItems } from "@web/components/Flex/styled";
-import { SpaceCharacter } from "@web/components/SpaceCharacter";
 import { Text } from "@web/components/Text";
 import { TodayButton } from "@web/views/Calendar/components/TodayButton";
 import { RootProps } from "@web/views/Calendar/calendarView.types";
@@ -14,6 +13,7 @@ import { isEventFormOpen } from "@web/common/utils";
 import { SidebarIcon } from "@web/components/Icons/Sidebar";
 import { selectIsSidebarOpen } from "@web/ducks/events/selectors/view.selectors";
 import { viewSlice } from "@web/ducks/events/slices/view.slice";
+import { getCalendarHeadingLabel } from "@web/common/utils/web.date.util";
 
 import {
   StyledHeaderRow,
@@ -42,7 +42,7 @@ export const Header: FC<Props> = ({
   const dispatch = useAppDispatch();
   const { scrollToNow } = scrollUtil;
 
-  const { startOfView } = weekProps.component;
+  const { startOfView, endOfView } = weekProps.component;
   const isSidebarOpen = useAppSelector(selectIsSidebarOpen);
 
   const onSectionClick = () => {
@@ -59,6 +59,8 @@ export const Header: FC<Props> = ({
     scrollToNow();
   };
 
+  const headerLabel = getCalendarHeadingLabel(startOfView, endOfView, dayjs());
+
   return (
     <>
       <StyledHeaderRow
@@ -74,11 +76,7 @@ export const Header: FC<Props> = ({
         </TooltipWrapper>
         <StyledLeftGroup>
           <StyledHeaderLabel aria-level={1} role="heading">
-            <Text size="4xl">{startOfView.format("MMMM")}</Text>
-
-            <SpaceCharacter />
-
-            <Text size="xxxl">{startOfView.format("YYYY")}</Text>
+            <Text size="4xl">{headerLabel}</Text>
           </StyledHeaderLabel>
         </StyledLeftGroup>
 


Test patch:
diff --git a/packages/web/src/common/utils/web.date.util.test.ts b/packages/web/src/common/utils/web.date.util.test.ts
new file mode 100644
index 00000000..1eaa25ff
--- /dev/null
+++ b/packages/web/src/common/utils/web.date.util.test.ts
@@ -0,0 +1,61 @@
+import dayjs from "dayjs";
+import { getCalendarHeadingLabel, getWeekRangeLabel } from "./web.date.util";
+
+describe("getWeekRangeLabel", () => {
+  it("should return 'M.D - D' format when week is within single month", () => {
+    const weekInViewStart = dayjs("2025-01-05");
+    const weekInViewEnd = dayjs("2025-01-11");
+    const label = getWeekRangeLabel(weekInViewStart, weekInViewEnd);
+    const expectedLabel = "1.5 - 11";
+    expect(label).toBe(expectedLabel);
+  });
+
+  it("should return 'M.D - M.D' format when week covers two months", () => {
+    const weekInViewStart = dayjs("2024-12-29");
+    const weekInViewEnd = dayjs("2025-01-4");
+    const label = getWeekRangeLabel(weekInViewStart, weekInViewEnd);
+    const expectedLabel = "12.29 - 1.4";
+    expect(label).toBe(expectedLabel);
+  });
+});
+
+describe("getCalendarHeadingLabel", () => {
+  it("should return month only when week is within a single month and today is in the same year", () => {
+    const today = dayjs("2025-01-08");
+    const weekInViewStart = dayjs("2025-01-05");
+    const weekInViewEnd = dayjs("2025-01-11");
+    const label = getCalendarHeadingLabel(
+      weekInViewStart,
+      weekInViewEnd,
+      today
+    );
+    const expectedlabel = "January";
+    expect(label).toBe(expectedlabel);
+  });
+
+  it("should return month and year when week is within a single month and today is in a different year", () => {
+    const today = dayjs("2024-12-30");
+    const weekInViewStart = dayjs("2025-01-05");
+    const weekInViewEnd = dayjs("2025-01-11");
+    const label = getCalendarHeadingLabel(
+      weekInViewStart,
+      weekInViewEnd,
+      today
+    );
+    const expectedlabel = "January 2025";
+    expect(label).toBe(expectedlabel);
+  });
+
+  it("should return 'MMM yy - MMM yy' format when week covers two months", () => {
+    const today = dayjs("2024-12-30");
+    const weekInViewStart = dayjs("2024-12-29");
+    const weekInViewEnd = dayjs("2025-01-04");
+    const label = getCalendarHeadingLabel(
+      weekInViewStart,
+      weekInViewEnd,
+      today
+    );
+    const expectedlabel = "Dec 24 - Jan 25";
+    expect(label).toBe(expectedlabel);
+  });
+});