import _ from 'lodash' import { render } from 'preact' import { useEffect, useState } from 'preact/hooks' import { ToolOverlay } from './components/CourseVisibility.jsx' import { EventsView, MODE_COURSES, MODE_SCHEDULE } from './components/EventsView.jsx' import { HamburgerMenu } from './components/HamburgerMenu.jsx' import { Help } from './components/Help.jsx' import { Icon } from './components/Icon.jsx' import { Popup } from './components/Popup.jsx' import { Toolbar } from './components/Toolbar.jsx' import { OptionBar } from './components/OptionBar.jsx' import { prettyAulaName, prettyProfName, usePersistentState } from './utils.jsx' window._ = _ window.dataBuffer = {} const CALENDAR_IDS = { 'anno-1': ['64a7c1c651f079001d52e9c8'], 'anno-2': ['6308e2dc09352a0208fefdd9'], 'anno-3': ['6308e42a1df5cb026699ced4'], 'magistrale': ['64a7c7091ab813002c5d9ede'], 'tutti': [ '64a7c1c651f079001d52e9c8', '6308e2dc09352a0208fefdd9', '6308e42a1df5cb026699ced4', '64a7c7091ab813002c5d9ede', ], } const TIMETABLE_IDS = { 'anno-1': '64a7c1c651f079001d52e9c8', 'anno-2': '6308e2dc09352a0208fefdd9', 'anno-3': '6308e42a1df5cb026699ced4', 'magistrale': '64a7c7091ab813002c5d9ede', } const DEFAULT_DATE_RANGE = { from: '2023-10-02T00:00:00.000Z', to: '2023-10-07T00:00:00.000Z', } const DATE_RANGES = { '64a7c1c651f079001d52e9c8': DEFAULT_DATE_RANGE, '6308e2dc09352a0208fefdd9': DEFAULT_DATE_RANGE, '6308e42a1df5cb026699ced4': DEFAULT_DATE_RANGE, '64a7c7091ab813002c5d9ede': DEFAULT_DATE_RANGE, } function specialEventPatches(eventi) { // Il laboratorio del primo anno in realtà è in due canali separati let i = 1 eventi.forEach(evento => { if (evento.nome === 'LABORATORIO DI INTRODUZIONE ALLA MATEMATICA COMPUTAZIONALE') { evento.nome += ` (${i})` i++ } }) return eventi } function formatEvents(timetable) { return timetable.map( ({ nome, dataInizio, dataFine, docenti, aule }) => { return { id: nome, name: _.split(nome, '-', 1)[0].trim(), start: new Date(dataInizio), end: new Date(dataFine), docenti: docenti.map(({ nome, cognome }) => prettyProfName(nome, cognome)), aule: aule.map(aula => prettyAulaName(aula.codice)), } } ) } async function loadCalendari() { async function req(id) { // Almost directly copy-pasted from Chrome Dev Tools const req = await fetch( 'https://apache.prod.up.cineca.it/api/Impegni/getImpegniCalendarioPubblico', { headers: { 'content-type': 'application/json;charset=UTF-8', 'sec-fetch-dest': 'empty', 'sec-fetch-mode': 'cors', 'sec-fetch-site': 'same-site', }, body: JSON.stringify({ mostraImpegniAnnullati: true, mostraIndisponibilitaTotali: false, linkCalendarioId: id, clienteId: '628de8b9b63679f193b87046', pianificazioneTemplate: false, dataInizio: DATE_RANGES[id].from, dataFine: DATE_RANGES[id].to, }), method: 'POST', mode: 'cors', credentials: 'omit', } ) return await req.json() } const requests = [ req(TIMETABLE_IDS['anno-1']), req(TIMETABLE_IDS['anno-2']), req(TIMETABLE_IDS['anno-3']), req(TIMETABLE_IDS['magistrale']), ] const results = await Promise.all(requests) const timetablesRaw = results.map(timetable => specialEventPatches(_.uniqBy(timetable, 'id'))) const allRaw = specialEventPatches(_.uniqBy(_.concat(...results), 'id')) return { 'anno-1': formatEvents(timetablesRaw[0]), 'anno-2': formatEvents(timetablesRaw[1]), 'anno-3': formatEvents(timetablesRaw[2]), 'magistrale': formatEvents(timetablesRaw[3]), 'tutti': formatEvents(allRaw) } } const App = ({}) => { // Data Sources const [source, setSource] = usePersistentState('orario.source', 'magistrale') const [timetables, setTimetables] = useState(null) useEffect(async () => { setTimetables(await loadCalendari()) }, []) // View Modes const [mode, setMode] = usePersistentState('orario.mode', MODE_COURSES) // Selection const [selectedCourses, setSelectedCourses] = usePersistentState('orario.selection', []) // Menus const [helpVisible, setHelpVisible] = useState(false) const [showMobileMenu, setShowMobileMenu] = useState(false) // const groupIds = new Set(eventi.map(e => e.nome)) const toolOverlayVisible = selectedCourses.length > 0 const [theme, setTheme] = usePersistentState( 'orario.theme', window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' ) document.body.classList.toggle('dark-mode', theme === 'dark') return ( <> setShowMobileMenu(true), onHelp: () => setHelpVisible(true), theme, setTheme, }} /> {mode === MODE_COURSES && ( setHelpVisible(true), }}orizzontale /> )} {timetables && ( )} {toolOverlayVisible && ( setMode(mode === MODE_COURSES ? MODE_SCHEDULE : MODE_COURSES)} onClose={() => { setSelectedCourses([]) setMode(MODE_COURSES) }} /> )} {showMobileMenu && ( { setShowMobileMenu(false) }, }} /> )} {helpVisible && ( Guida } onClose={() => setHelpVisible(false)} > )} ) } render(, document.body)