organize events by months, weeks and days and print a calendar view
This commit is contained in:
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { isToday, isAfter, parseISO } from "date-fns";
|
import { isToday, isAfter, parse } from "date-fns";
|
||||||
import { toZonedTime, format } from "date-fns-tz";
|
import { nb } from "date-fns/locale";
|
||||||
|
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(
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user