organize events by months, weeks and days and print a calendar view

This commit is contained in:
2024-05-12 16:06:49 +02:00
parent e12a9a82fa
commit 7dfb505af0
4 changed files with 139 additions and 22 deletions

View File

@ -3,19 +3,20 @@
import { useState } from "react"; import { useState } from "react";
import { EventItem } from "./EventItem"; import { EventItem } from "./EventItem";
import { EventFilter } from "./EventFilter"; import { EventFilter } from "./EventFilter";
import { EventFragment, getSingularEvents } from "@/lib/event";
import { import {
toLocalTime, EventFragment,
formatDate, getSingularEvents,
commonDateFormat, organizeEventsByDate,
isTodayOrFuture, } from "@/lib/event";
} from "@/lib/date"; import { isTodayOrFuture } from "@/lib/date";
import styles from "./eventContainer.module.scss"; import styles from "./eventContainer.module.scss";
import { formatDate, formatYearMonth } from "@/lib/date";
import { getYear, parse } from "date-fns";
type EventContainerMode = "list" | "calendar"; type EventContainerMode = "list" | "calendar";
export const EventContainer = ({ events }: { events: EventFragment[] }) => { export const EventContainer = ({ events }: { events: EventFragment[] }) => {
const [mode, setMode] = useState<EventContainerMode>("calendar"); const [mode, setMode] = useState<EventContainerMode>("list");
return ( return (
<div className={styles.events}> <div className={styles.events}>
@ -43,15 +44,38 @@ const EventList = ({ events }: { events: EventFragment[] }) => {
}; };
const EventCalendar = ({ events }: { events: EventFragment[] }) => { const EventCalendar = ({ events }: { events: EventFragment[] }) => {
const singularEvents = getSingularEvents(events).filter((x) => const futureSingularEvents = getSingularEvents(events).filter(
x.occurrence?.start && isTodayOrFuture(x.occurrence.start) (x) => x.occurrence?.start && isTodayOrFuture(x.occurrence.start)
); );
const eventsByDate = organizeEventsByDate(futureSingularEvents);
return ( return (
<ul className={styles.eventList}> <div className={styles.eventList}>
{singularEvents.map((event) => ( {Object.keys(eventsByDate).map((yearMonth) => (
<div key={yearMonth} className={styles.calendarYearMonth}>
<h2>{formatYearMonth(yearMonth)}</h2>
{Object.keys(eventsByDate[yearMonth]).map((week) => (
<div key={week} className={styles.calendarWeek}>
{Object.keys(eventsByDate[yearMonth][week]).map((day) => (
<div key={day} className={styles.calendarDay}>
<h3>
{formatDate(
parse(day, "yyyy-MM-dd", new Date()),
"eeee dd.MM."
)}
</h3>
<ul>
{eventsByDate[yearMonth][week][day].map((event) => (
<EventItem key={event.id} event={event} /> <EventItem key={event.id} event={event} />
))} ))}
</ul> </ul>
</div>
))}
</div>
))}
</div>
))}
</div>
); );
}; };

View File

@ -22,3 +22,18 @@
border-radius: 0; border-radius: 0;
} }
} }
.calendarYearMonth {
h2:first-letter {
text-transform: capitalize
}
}
.calendarWeek {}
.calendarDay {
h3:first-letter {
text-transform: capitalize
}
}

View File

@ -1,4 +1,5 @@
import { isToday, isAfter, parseISO } from "date-fns"; import { isToday, isAfter, parse } from "date-fns";
import { nb } from "date-fns/locale";
import { toZonedTime, format} from "date-fns-tz"; import { toZonedTime, format} from "date-fns-tz";
const timeZone = "Europe/Oslo"; const timeZone = "Europe/Oslo";
@ -10,7 +11,16 @@ export function toLocalTime(date: Date | string | number) {
} }
export function formatDate(date: Date | string | number, formatStr: string) { export function formatDate(date: Date | string | number, formatStr: string) {
return format(toLocalTime(date), formatStr, { timeZone }); return format(toLocalTime(date), formatStr, { timeZone, locale: nb });
}
export function formatYearMonth(yearMonth: string) {
// full name of month if year is current year, otherwise name of month + year
const parsed = parse(yearMonth, "yyyy-MM", new Date());
if (parsed.getFullYear === new Date().getFullYear) {
return formatDate(parsed, "MMMM");
}
return formatDate(parsed, "MMMM yyyy");
} }
export function isTodayOrFuture( export function isTodayOrFuture(

View File

@ -1,7 +1,23 @@
import {
compareAsc,
getYear,
getMonth,
getWeek,
getDate,
endOfWeek,
startOfWeek,
eachDayOfInterval,
addWeeks,
} from "date-fns";
import { toLocalTime, formatDate } from "./date";
import { graphql } from "@/gql"; import { graphql } from "@/gql";
import { EventFragment, EventOccurrence } from "@/gql/graphql"; import { EventFragment, EventOccurrence } from "@/gql/graphql";
export type { EventFragment } from "@/gql/graphql" export type { EventFragment } from "@/gql/graphql";
export type SingularEvent = EventFragment & {
occurrence: EventOccurrence;
};
const EventFragmentDefinition = graphql(` const EventFragmentDefinition = graphql(`
fragment Event on EventPage { fragment Event on EventPage {
@ -55,10 +71,6 @@ export const allEventsQuery = graphql(`
} }
`); `);
export type SingularEvent = EventFragment & {
occurrence: EventOccurrence;
};
export function getSingularEvents(events: EventFragment[]) { export function getSingularEvents(events: EventFragment[]) {
return events return events
.map((event) => { .map((event) => {
@ -77,3 +89,59 @@ export function getSingularEvents(events: EventFragment[]) {
function isDefined<T>(val: T | undefined | null): val is T { function isDefined<T>(val: T | undefined | null): val is T {
return val !== undefined && val !== null; return val !== undefined && val !== null;
} }
interface EventsByDate {
[yearMonth: string]: {
[week: number]: {
[day: number]: SingularEvent[];
};
};
}
export function organizeEventsByDate(events: SingularEvent[]): EventsByDate {
const sortedEvents = events.sort((a, b) =>
compareAsc(new Date(a.occurrence.start), new Date(b.occurrence.start))
);
const eventsByDate: EventsByDate = {};
const minDate = new Date(sortedEvents[0]?.occurrence.start);
const maxDate = new Date(
sortedEvents[sortedEvents.length - 1]?.occurrence.start
);
let currentDate = startOfWeek(minDate, { weekStartsOn: 1 });
while (currentDate <= maxDate) {
const yearMonth = formatDate(currentDate, "yyyy-MM");
const week = formatDate(currentDate, "w");
const daysOfWeek = eachDayOfInterval({
start: currentDate,
end: endOfWeek(currentDate, { weekStartsOn: 1 }),
});
if (!eventsByDate[yearMonth]) {
eventsByDate[yearMonth] = {};
}
if (!eventsByDate[yearMonth][week]) {
eventsByDate[yearMonth][week] = {};
}
daysOfWeek.forEach((day) => {
const formattedDay = formatDate(day, "yyyy-MM-dd");
if (!eventsByDate[yearMonth][week][formattedDay]) {
eventsByDate[yearMonth][week][formattedDay] = [];
}
});
currentDate = addWeeks(currentDate, 1);
}
sortedEvents.forEach((event) => {
const start = toLocalTime(event.occurrence.start);
const yearMonth = formatDate(start, "yyyy-MM");
const week = formatDate(start, "w");
const day = formatDate(start, "yyyy-MM-dd");
eventsByDate[yearMonth][week][day].push(event);
});
return eventsByDate;
}