From 4f9d2e2bcfae5c7f916950a6570ea8488dd6ac1e Mon Sep 17 00:00:00 2001 From: Jonas Braathen Date: Sun, 14 Jul 2024 01:25:44 +0200 Subject: [PATCH] group date pills when days are consecutive --- web/src/components/events/EventItem.tsx | 28 +++++++----- web/src/lib/date.ts | 59 ++++++++++++++++++++++++- 2 files changed, 76 insertions(+), 11 deletions(-) diff --git a/web/src/components/events/EventItem.tsx b/web/src/components/events/EventItem.tsx index 01a8b05..64e8645 100644 --- a/web/src/components/events/EventItem.tsx +++ b/web/src/components/events/EventItem.tsx @@ -8,9 +8,14 @@ import { EventFragment, getFutureOccurrences, } from "@/lib/event"; -import { formatDate, formatExtendedDateTime } from "@/lib/date"; +import { + formatDate, + formatDateRange, + formatExtendedDateTime, + groupConsecutiveDates, +} from "@/lib/date"; -const DATES_TO_SHOW = 2; +const DATE_PILLS_TO_SHOW = 2; export const EventItem = ({ event, @@ -22,8 +27,11 @@ export const EventItem = ({ size?: "small" | "medium" | "large"; }) => { const futureOccurrences = getFutureOccurrences(event); + const groupedOccurrences = groupConsecutiveDates( + futureOccurrences.map((occurrence) => occurrence.start) + ); const numOccurrences = event?.occurrences?.length ?? 0; - const nextOccurrence = numOccurrences ? futureOccurrences[0] : null; + const nextOccurrence = numOccurrences ? groupedOccurrences[0] : null; const featuredImage: any = event.featuredImage; return ( @@ -46,15 +54,15 @@ export const EventItem = ({
{mode === "list" && nextOccurrence && (
- {futureOccurrences.slice(0, DATES_TO_SHOW).map((occurrence) => ( - - {formatDate(occurrence.start, "d. MMMM")} + {groupedOccurrences.slice(0, DATE_PILLS_TO_SHOW).map((group) => ( + + {formatDateRange(group)} ))} - {futureOccurrences.length > DATES_TO_SHOW && ( - {`+${ - futureOccurrences.length - DATES_TO_SHOW - }`} + {groupedOccurrences.length > DATE_PILLS_TO_SHOW && ( + {`+${groupedOccurrences + .slice(DATE_PILLS_TO_SHOW) + .reduce((sum, group) => sum + group.length, 0)}`} )}
)} diff --git a/web/src/lib/date.ts b/web/src/lib/date.ts index 599b7d2..21103f5 100644 --- a/web/src/lib/date.ts +++ b/web/src/lib/date.ts @@ -1,4 +1,11 @@ -import { isToday, isAfter, parse, compareAsc } from "date-fns"; +import { + isToday, + isAfter, + parse, + compareAsc, + addDays, + isSameDay, +} from "date-fns"; import { nb } from "date-fns/locale"; import { toZonedTime, format } from "date-fns-tz"; @@ -51,3 +58,53 @@ export function isTodayOrFuture( export function compareDates(a: Date | string, b: Date | string) { return compareAsc(new Date(a), new Date(b)); } + +export function isConsectutiveDays(a: Date, b: Date): boolean { + const nextDay = addDays(a, 1); + return isSameDay(nextDay, b); +} + +export function groupConsecutiveDates(dates: string[]): string[][] { + const groupedDates: string[][] = []; + let group: string[] = []; + + for (let i = 0; i < dates.length; i++) { + if ( + i === 0 || + isConsectutiveDays(toLocalTime(dates[i - 1]), toLocalTime(dates[i])) + ) { + group.push(dates[i]); + } else { + groupedDates.push(group); + group = [dates[i]]; + } + } + + if (group.length > 0) { + groupedDates.push(group); + } + + return groupedDates; +} + +export function formatDateRange(dates: string[]): string { + if (dates.length === 1) { + return formatDate(dates[0], "d. MMMM"); + } + + const firstDate = toLocalTime(dates[0]); + const lastDate = toLocalTime(dates[dates.length - 1]); + + if (firstDate.getMonth() === lastDate.getMonth()) { + // same month + return `${firstDate.getDay()}.-${lastDate.getDay()}. ${formatDate( + firstDate, + "MMMM" + )}`; + } + + // continues into next month + const formattedFirstDate = formatDate(firstDate, "d. MMMM"); + const formattedLastDate = formatDate(lastDate, "d. MMMM"); + return `${formattedFirstDate} - ${formattedLastDate}`; +}