Skip to content

Commit bf5b813

Browse files
committed
google-calendar: reimplement with vanilla JS
Kauhea työ mutta tulipahan tehtyä.
1 parent d2e5e23 commit bf5b813

5 files changed

Lines changed: 163 additions & 71 deletions

File tree

components/google-calendar/google-calendar.css

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,42 @@
1-
@import 'react-big-calendar/lib/css/react-big-calendar.css';
2-
3-
.CalendarWrapper {
4-
background-color: white;
5-
color: var(--color-purple);
6-
padding: 30px;
7-
border-radius: 25px;
8-
9-
.rbc-event,
10-
.rbc-event.rbc-event-selected,
11-
.rbc-event {
12-
background-color: var(--color-purple);
1+
#calendar table {
2+
margin: 1.5em auto;
3+
border-collapse: collapse;
4+
--shade-of-purple: var(--color-purple);
5+
:is(td, th):nth-child(7) {
6+
--shade-of-purple: var(--color-dark-purple);
137
}
14-
15-
.rbc-show-more {
16-
color: var(--color-purple);
8+
* {
9+
color: var(--shade-of-purple);
10+
}
11+
caption {
12+
font-size: 1.2em;
13+
margin-bottom: 0.5em;
14+
&::first-letter {
15+
text-transform: capitalize;
16+
}
17+
}
18+
td {
19+
--tablecell-width: calc(min(12em, 12vw));
20+
border: calc(min(5px, 0.5vw)) ridge var(--shade-of-purple);
21+
min-width: var(--tablecell-width);
22+
height: 5em;
23+
padding: 0.5em calc(min(4px, 0.4vw));
24+
text-align: center;
25+
vertical-align: top;
26+
> div {
27+
max-width: var(--tablecell-width);
28+
max-height: 5em;
29+
padding: 2px;
30+
border-radius: 4px;
31+
margin-top: 0.2em;
32+
background: var(--shade-of-purple);
33+
color: white;
34+
overflow: hidden;
35+
&:not(:only-of-type) {
36+
white-space: nowrap;
37+
text-overflow: ellipsis;
38+
}
39+
font-size: calc(min(1em, 2vw));
40+
}
1741
}
1842
}
19-
Lines changed: 98 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,106 @@
1-
import React, { useEffect, useState } from 'react';
1+
/*
2+
* Copyright (c) 2025 Tuomas Ahola <taahol@utu.fi>
3+
*
4+
* Permission to use, copy, modify, and distribute this software for any
5+
* purpose with or without fee is hereby granted, provided that the above
6+
* copyright notice and this permission notice appear in all copies.
7+
*
8+
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9+
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10+
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11+
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12+
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13+
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14+
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15+
*/
216

3-
import { Calendar, dateFnsLocalizer } from 'react-big-calendar';
17+
import { addDays, addMonths, startOfDay, calendarPageRange } from '../../lib/dateUtils.js';
18+
import { getCalendarEventsByDay } from '../../lib/calendarUtils.js';
419

5-
import { getCalendarEvents } from '../../lib/calendarUtils.js';
6-
7-
import format from 'date-fns/format';
8-
import parse from 'date-fns/parse';
9-
import startOfWeek from 'date-fns/startOfWeek';
10-
import getDay from 'date-fns/getDay';
11-
import { fi } from 'date-fns/locale'
12-
const locales = {
13-
'fi': fi,
20+
const calendarPage = (date) => {
21+
const pageRange = calendarPageRange(date);
22+
const page = [];
23+
for (var i=0, tmp=[], day=new Date(pageRange[0]); day<=pageRange[1]; addDays(day, 1)) {
24+
tmp.push(new Date(day));
25+
if (++i % 7 == 0) {
26+
page.push(Array(...tmp));
27+
tmp.length=0;
28+
}
29+
}
30+
return page;
1431
}
15-
const localizer = dateFnsLocalizer({
16-
format,
17-
parse,
18-
startOfWeek,
19-
getDay,
20-
locales,
21-
});
2232

23-
function GoogleCalendar() {
24-
const [events, setEvents] = useState([]);
33+
export const renderCalendar = (events, date=new Date(), document=window.document) => {
34+
const page = calendarPage(new Date(date));
35+
const wrapper = document.createElement("div");
36+
const table = document.createElement("table");
37+
const caption = document.createElement("caption");
38+
const tableHeader = document.createElement("tr");
39+
caption.appendChild(document.createTextNode(new Intl.DateTimeFormat("fi-FI", {
40+
month: "long",
41+
year: "numeric",
42+
}).format(date)));
43+
table.appendChild(caption);
44+
Array("ma", "ti", "ke", "to", "pe", "la", "su").forEach((day) => {
45+
const th = document.createElement("th");
46+
th.appendChild(document.createTextNode(day));
47+
tableHeader.appendChild(th);
48+
});
49+
table.appendChild(tableHeader);
50+
page.forEach((week) => {
51+
const row = document.createElement("tr");
52+
week.forEach((day) => {
53+
const cell = document.createElement("td");
54+
cell.id = day.toJSON();
55+
cell.appendChild(document.createTextNode(day.getDate()));
56+
(events[cell.id] || []).forEach((event) => {
57+
const entry = document.createElement("div");
58+
entry.title = Intl.DateTimeFormat("fi-FI", {
59+
dateStyle: "short",
60+
...(!event.allDay && {
61+
timeStyle: "short"
62+
}),
63+
timeZone: "Europe/Helsinki",
64+
}).formatRange(new Date(event.start), new Date(event.end));
65+
66+
entry.appendChild(document.createTextNode(event.title));
67+
cell.appendChild(entry);
68+
});
69+
row.appendChild(cell);
70+
});
71+
table.appendChild(row);
72+
});
73+
if (document === window?.document) {
74+
renderButtons(events, date, wrapper, document).forEach(button => {
75+
wrapper.appendChild(button);
76+
});
77+
}
78+
wrapper.appendChild(table);
79+
wrapper.id = "calendar";
80+
return wrapper;
81+
}
2582

26-
useEffect(() => {
27-
const callApi = async () => {
28-
setEvents(await getCalendarEvents());
29-
}
30-
callApi();
31-
}, []);
83+
const renderButtons = (events, date, element, document) => Array(
84+
{ label: "Edellinen kuukausi", add: -1 },
85+
{ label: "Seuraava kuukausi", add: 1 }
86+
).map(b => {
87+
const button = document.createElement("button");
88+
button.appendChild(document.createTextNode(b.label));
89+
button.onclick = calendarBuilder(addMonths(new Date(date), b.add), element);
90+
return button;
91+
});
3292

33-
return (
34-
<div className="CalendarWrapper">
35-
<Calendar
36-
popup
37-
localizer={localizer}
38-
culture={'fi'}
39-
events={events.map((event) => ({
40-
...event,
41-
start: new Date(event.start),
42-
end: new Date(event.end),
43-
}))}
44-
startAccessor="start"
45-
endAccessor="end"
46-
style={{height: 755}}
47-
/>
48-
</div>
49-
);
93+
export const calendarBuilder = (
94+
date = new Date(),
95+
element = document.getElementById("calendar"),
96+
events = getCalendarEventsByDay(date),
97+
) => async () => {
98+
element.replaceWith(renderCalendar(await events, date));
99+
setBgColor(document.getElementById(startOfDay(new Date()).toJSON()),
100+
"var(--color-pink)");
50101
}
51102

52-
export default GoogleCalendar;
103+
const setBgColor = (element, color) => {
104+
if (element)
105+
element.style.backgroundColor = color;
106+
}

components/index.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@ import Header from './header/header';
22
import Hero from './hero/hero';
33
import Footer from './footer/footer';
44
import Calendar from './calendar/calendar';
5-
import GoogleCalendar from './google-calendar/google-calendar';
65
import Sponsors from './sponsors/sponsors';
76
import Brand from './brand/brand';
87
import Tags from './tags/tags';
98

10-
export { Header, Brand, Hero, Footer, Calendar, GoogleCalendar, Sponsors, Tags };
9+
export { Header, Brand, Hero, Footer, Calendar, Sponsors, Tags };

pages/page.css

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@ body {
1717
min-height: calc(100vh - var(--hero-height) - var(--footer-height));
1818
box-sizing: border-box;
1919
max-width: 100%;
20-
width: 900px;
2120
margin: 0 auto;
2221
padding: 10px;
22+
&:not:has(> #calendar) {
23+
width: 900px;
24+
}
2325

2426
> .Content {
2527
font-family: 'Karla', sans-serif;

pages/tapahtumakalenteri.js

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,34 @@
11
import Head from 'next/head';
2-
import { Header, Hero, Footer, GoogleCalendar } from '../components';
2+
import { Header, Hero, Footer } from '../components';
3+
import { renderCalendar } from '../components/google-calendar/google-calendar.js';
4+
import { getCalendarEventsByDay } from '../lib/calendarUtils.js';
5+
import { Node } from '../lib/DOMPolyfill.js';
36
import { getNavigationItems } from '../lib/wordpress';
47
import { readJSON } from '../lib/fsUtils';
58

6-
function CalendarPage ({ navItems }) {
7-
9+
function CalendarPage ({ navItems, calendarEvents }) {
10+
if (typeof window === 'undefined') {
11+
var document = new Node();
12+
var events = calendarEvents;
13+
} else {
14+
var [events, setEvents] = useState(calendarEvents);
15+
useEffect(() => {
16+
const callApi = async () => {
17+
setEvents(await getCalendarEventsByDay());
18+
};
19+
callApi();
20+
}, []);
21+
}
822
return (
923
<>
1024
<Head>
1125
<title>Tapahtumakalenteri | Delta ry</title>
1226
</Head>
1327
<Header navItems={navItems}/>
1428
<Hero title="Tapahtumakalenteri" />
15-
<article className="Page">
16-
<div className="ContentWrapper">
17-
<GoogleCalendar />
18-
</div>
29+
<article className="Page" dangerouslySetInnerHTML={{
30+
__html: renderCalendar(events, new Date(), document).outerHTML,
31+
}}>
1932
</article>
2033
<Footer />
2134
</>
@@ -27,7 +40,8 @@ export async function getStaticProps() {
2740

2841
return {
2942
props: {
30-
navItems: await getNavigationItems(navigation)
43+
navItems: await getNavigationItems(navigation),
44+
calendarEvents: await getCalendarEventsByDay(),
3145
}
3246
};
3347
}

0 commit comments

Comments
 (0)