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}`;
+}