Skip to content

Commit 11273b5

Browse files
authored
feat(program): mobile friendly calendar (#358)
1 parent 04c8bbb commit 11273b5

File tree

4 files changed

+152
-64
lines changed

4 files changed

+152
-64
lines changed

packages/frontend/src/data/program/overviews/index.ts

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,38 @@
11
import { workshops } from './sections/workshops';
22
import { conferenceDescription, general_events } from './sections/general';
3-
import { conference } from './sections/conference';
3+
import { conferenceFirst, conferenceSecond, conferenceThird } from './sections/conference';
44
import type { Headline, ScheduleEvent } from '#/types';
55

66
export interface Sections extends Headline {
77
events: ScheduleEvent[];
8+
split?: ScheduleEvent[][];
9+
}
10+
11+
/**
12+
* Splits events into two groups based on location.
13+
*
14+
* @param events - full array of ScheduleEvent
15+
* @param groups - configuration with two arrays of locations
16+
* @returns an object with { groupA, groupB }
17+
*/
18+
export function splitByLocation(
19+
events: ScheduleEvent[],
20+
groups: { groupA: string[]; groupB: string[] }
21+
): ScheduleEvent[][] {
22+
const groupA: ScheduleEvent[] = [];
23+
const groupB: ScheduleEvent[] = [];
24+
25+
for (const e of events) {
26+
const loc = e.location as string;
27+
if (groups.groupA.includes(loc)) {
28+
groupA.push(e);
29+
} else if (groups.groupB.includes(loc)) {
30+
groupB.push(e);
31+
}
32+
// you could also add an `else` branch if you want to catch unclassified
33+
}
34+
35+
return [groupA, groupB];
836
}
937

1038
export const sections: Sections[] = [
@@ -21,6 +49,12 @@ export const sections: Sections[] = [
2149
title: 'Conference',
2250
tagline: 'Tuesday, 2nd - Thursday, 4th<br />September 2025',
2351
subtitle: conferenceDescription,
24-
events: conference
52+
events: conferenceFirst.concat(conferenceSecond, conferenceThird),
53+
split: [
54+
conferenceFirst,
55+
conferenceSecond,
56+
conferenceThird
57+
]
58+
2559
}
2660
];

packages/frontend/src/data/program/overviews/sections/conference.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ function getHref(category: string, sessionIndex: number) {
1919
return getPermalink(`/program/${category}/#${category}-s${sessionIndex}`);
2020
}
2121

22-
export const conference: ScheduleEvent[] = [
22+
export const conferenceFirst: ScheduleEvent[] = [
2323
{
2424
title: 'Main Track 1',
2525
start: new Date(2025, 8, 2, 11, 0),
@@ -135,7 +135,10 @@ export const conference: ScheduleEvent[] = [
135135
category: 'journal_first',
136136
location: locations.triana,
137137
href: getHref('journal-first-track', 2)
138-
},
138+
}
139+
];
140+
141+
export const conferenceSecond: ScheduleEvent[] = [
139142
{
140143
title: 'Main Track 4',
141144
start: new Date(2025, 8, 3, 11, 0),
@@ -224,7 +227,10 @@ export const conference: ScheduleEvent[] = [
224227
category: 'journal_first',
225228
location: locations.magnolia,
226229
href: getHref('journal-first-track', 3)
227-
},
230+
}
231+
];
232+
233+
export const conferenceThird: ScheduleEvent[] = [
228234
{
229235
title: 'Main Track 6',
230236
start: new Date(2025, 8, 4, 11, 0),

packages/frontend/src/data/program/overviews/sections/general.ts

Lines changed: 41 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -303,49 +303,49 @@ export const general_events: ScheduleEvent[] = [
303303
category: 'social',
304304
location: locations.raza,
305305
href: getPermalink('/program/social-events/#farewell-party')
306-
},
306+
}
307307

308308
// 31 de Agosto
309-
{
310-
title: 'Invited Talk – ADA Representative',
311-
start: new Date(2025, 8, 5, 9, 15),
312-
end: new Date(2025, 8, 5, 10, 15),
313-
category: 'conference_2',
314-
description: postConferenceDescription
315-
},
316-
{
317-
title: 'Coffee Break',
318-
start: new Date(2025, 8, 5, 10, 15),
319-
end: new Date(2025, 8, 5, 10, 45),
320-
category: 'lunch'
321-
},
322-
{
323-
title: 'Real Cases of Process Automation and Improvement',
324-
start: new Date(2025, 8, 5, 10, 45),
325-
end: new Date(2025, 8, 5, 12, 15),
326-
category: 'conference_2',
327-
description: postConferenceDescription
328-
},
329-
{
330-
title: 'Process Mining in the Public Administration',
331-
start: new Date(2025, 8, 5, 12, 15),
332-
end: new Date(2025, 8, 5, 12, 45),
333-
category: 'conference_2',
334-
description: postConferenceDescription
335-
},
336-
{
337-
title: 'Round Table: AI, RPA, and Automation in Action',
338-
start: new Date(2025, 8, 5, 12, 45),
339-
end: new Date(2025, 8, 5, 13, 30),
340-
category: 'conference_2',
341-
description: postConferenceDescription
342-
},
343-
{
344-
title: 'Cocktail & Networking',
345-
start: new Date(2025, 8, 5, 13, 30),
346-
end: new Date(2025, 8, 5, 14, 0),
347-
category: 'lunch'
348-
}
309+
// {
310+
// title: 'Invited Talk – ADA Representative',
311+
// start: new Date(2025, 8, 5, 9, 15),
312+
// end: new Date(2025, 8, 5, 10, 15),
313+
// category: 'conference_2',
314+
// description: postConferenceDescription
315+
// },
316+
// {
317+
// title: 'Coffee Break',
318+
// start: new Date(2025, 8, 5, 10, 15),
319+
// end: new Date(2025, 8, 5, 10, 45),
320+
// category: 'lunch'
321+
// },
322+
// {
323+
// title: 'Real Cases of Process Automation and Improvement',
324+
// start: new Date(2025, 8, 5, 10, 45),
325+
// end: new Date(2025, 8, 5, 12, 15),
326+
// category: 'conference_2',
327+
// description: postConferenceDescription
328+
// },
329+
// {
330+
// title: 'Process Mining in the Public Administration',
331+
// start: new Date(2025, 8, 5, 12, 15),
332+
// end: new Date(2025, 8, 5, 12, 45),
333+
// category: 'conference_2',
334+
// description: postConferenceDescription
335+
// },
336+
// {
337+
// title: 'Round Table: AI, RPA, and Automation in Action',
338+
// start: new Date(2025, 8, 5, 12, 45),
339+
// end: new Date(2025, 8, 5, 13, 30),
340+
// category: 'conference_2',
341+
// description: postConferenceDescription
342+
// },
343+
// {
344+
// title: 'Cocktail & Networking',
345+
// start: new Date(2025, 8, 5, 13, 30),
346+
// end: new Date(2025, 8, 5, 14, 0),
347+
// category: 'lunch'
348+
// }
349349
].map((event) => {
350350
const overview = program_overviews.find(o => o.name === event.title);
351351

packages/frontend/src/pages/program/overview.astro

Lines changed: 66 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,38 +7,86 @@ import Headline from '#/components/ui/Headline.astro';
77
import LocationView from '#/components/ui/Schedule/LocationView.astro';
88
import pdfBooklet from '#/assets/documents/program/Program_booklet.pdf';
99
import Booklet from '#/components/widgets/Booklet.astro';
10+
import type { ScheduleEvent } from '#/types';
11+
12+
function sortEventsForWeekView(a: ScheduleEvent, b: ScheduleEvent) {
13+
if (a.category === 'conference_1' && b.category !== 'conference_1') return -1;
14+
if (a.category !== 'conference_1' && b.category === 'conference_1') return 1;
15+
return 0;
16+
}
17+
18+
function formatDayLabel(events: ScheduleEvent[]) {
19+
const d = events?.[0]?.start;
20+
if (!d) return 'Day';
21+
return new Intl.DateTimeFormat('en-GB', {
22+
weekday: 'short',
23+
day: 'numeric',
24+
month: 'short'
25+
}).format(d); // e.g., "Tue 2 Sep"
26+
}
1027
---
1128

1229
<PageLayout
13-
metadata={{
14-
title: 'Program Overview',
15-
description: 'Schedule of the conference'
16-
}}
30+
metadata={{ title: 'Program Overview', description: 'Schedule of the conference' }}
1731
heroProps={{
1832
subtitle: 'Hover over the events to see the description & the location<br />Click on them to see the full program',
19-
actions: [
20-
{ variant: 'primary', text: 'View program booklet', icon: 'i-tabler:download', href: pdfBooklet, target: '_blank' }
21-
]
22-
23-
}}>
33+
actions: [{ variant: 'primary', text: 'View program booklet', icon: 'i-tabler:download', href: pdfBooklet, target: '_blank' }]
34+
}}
35+
>
2436
{sections.map((s, index) => {
37+
const sorted_events = [...s.events].sort(sortEventsForWeekView);
2538
const days = new Set(s.events.map(e => e.start.getDate()));
26-
const sorted_events = s.events.sort((a, b) => {
27-
// Prioritize main track events
28-
if (a.category === 'conference_1' && b.category !== 'conference_1') return -1;
29-
if (a.category !== 'conference_1' && b.category === 'conference_1') return 1;
30-
return 0;
31-
});
39+
const hasSplit = Array.isArray(s.split) && s.split.length > 0;
3240

3341
return (
3442
<WidgetWrapper bg={index % 2 === 0} id={s.title?.split(' ')[0]?.toLowerCase()}>
3543
<Headline {...s} />
36-
{days.size === 1
37-
? <LocationView events={sorted_events} />
38-
: <WeekView events={sorted_events} />}
44+
45+
{/* MOBILE: if split, stack WeekView per day; else original fallback */}
46+
<div class="md:hidden">
47+
{hasSplit
48+
? (
49+
<div class="space-y-6">
50+
{s.split!.map((dayEvents) => {
51+
const daySorted = [...dayEvents].sort(sortEventsForWeekView);
52+
return (
53+
<section class="rounded-lg">
54+
<h3 class="text-sm text-gray-600 font-semibold mb-2">
55+
{formatDayLabel(daySorted)}
56+
</h3>
57+
<WeekView events={daySorted} />
58+
</section>
59+
);
60+
})}
61+
</div>
62+
)
63+
: (
64+
<>
65+
{days.size === 1
66+
? (
67+
<LocationView events={sorted_events} />
68+
)
69+
: (
70+
<WeekView events={sorted_events} />
71+
)}
72+
</>
73+
)}
74+
</div>
75+
76+
{/* DESKTOP: unchanged */}
77+
<div class="hidden md:block">
78+
{days.size === 1
79+
? (
80+
<LocationView events={sorted_events} />
81+
)
82+
: (
83+
<WeekView events={sorted_events} />
84+
)}
85+
</div>
3986
</WidgetWrapper>
4087
);
4188
})}
89+
4290
<WidgetWrapper>
4391
<Headline title="Program Booklet" />
4492
<Booklet />

0 commit comments

Comments
 (0)