You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
orario/src/main.jsx

228 lines
7.3 KiB
JavaScript

import _ from 'lodash'
import { render } from 'preact'
import { useEffect, useState } from 'preact/hooks'
import { ToolOverlay } from './components/CourseVisibility.jsx'
import { EventsView, MODE_COURSE } 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': ['6308cfcb1df5cb026699ce32'],
'anno-2': ['6308e2dc09352a0208fefdd9'],
'anno-3': ['6308e42a1df5cb026699ced4'],
'magistrale': ['63ce74397a9aee064ee01ad7'],
'tutti': [
'6308cfcb1df5cb026699ce32',
'6308e2dc09352a0208fefdd9',
'6308e42a1df5cb026699ced4',
'63ce74397a9aee064ee01ad7',
],
}
const DATE_RANGES = {
'6308cfcb1df5cb026699ce32': {
from: '2022-10-02T22:00:00.000Z',
to: '2022-10-07T22:00:00.000Z',
},
'6308e2dc09352a0208fefdd9': {
from: '2022-10-02T22:00:00.000Z',
to: '2022-10-07T22:00:00.000Z',
},
'6308e42a1df5cb026699ced4': {
from: '2022-10-02T22:00:00.000Z',
to: '2022-10-07T22:00:00.000Z',
},
'63ce74397a9aee064ee01ad7': {
from: '2023-02-27T22:00:00.000Z',
to: '2023-03-05T22:00:00.000Z',
},
}
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
}
async function loadEventi(ids) {
const calendari = await Promise.all(
ids.map(async 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()
})
)
if (ids.length === 1) {
return specialEventPatches(calendari[0])
}
return specialEventPatches(_.uniqBy(_.concat(...calendari), 'id'))
}
const App = ({}) => {
// Data Sources
const [source, setSource] = usePersistentState('orario.source', 'magistrale')
const [eventi, setEventi] = useState([])
// View Modes
const [mode, setMode] = usePersistentState('orario.mode', MODE_COURSE)
// Selection
const [selectedCourses, setSelectedCourses] = usePersistentState('orario.selection', [])
const [hideOtherCourses, setHideOtherCourses] = usePersistentState('orario.hide-other', false)
// Menus
const [helpVisible, setHelpVisible] = useState(false)
const [showMobileMenu, setShowMobileMenu] = useState(false)
useEffect(async () => {
const eventi = await loadEventi(CALENDAR_IDS[source])
window.dataBuffer[source] = eventi
setEventi(eventi)
}, [source])
const groupIds = new Set(eventi.map(e => e.nome))
const toolOverlayVisible =
selectedCourses.length > 0 && selectedCourses.filter(id => groupIds.has(id)).length > 0
useEffect(() => {
const groupIds = new Set(eventi.map(e => e.nome))
if (
selectedCourses.length === 0 ||
(eventi.length > 0 && selectedCourses.filter(id => groupIds.has(id)).length === 0)
) {
setHideOtherCourses(false)
}
}, [eventi, selectedCourses.length])
const [theme, setTheme] = usePersistentState(
'orario.theme',
window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
)
document.body.classList.toggle('dark-mode', theme === 'dark')
return (
<>
<Toolbar
{...{
mode,
setMode,
source,
setSource,
onShowMenu: () => setShowMobileMenu(true),
onHelp: () => setHelpVisible(true),
theme,
setTheme,
}}
/>
<OptionBar
{...{
mode,
setMode,
source,
setSource,
onHelp: () => setHelpVisible(true),
}}
/>
<EventsView
mode={mode}
selection={selectedCourses}
setSelection={setSelectedCourses}
hideOtherCourses={hideOtherCourses}
start={new Date(2022, 10, 3)}
events={eventi.map(({ nome, dataInizio, dataFine, docenti, aule }) => ({
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)),
}))}
/>
{toolOverlayVisible && (
<ToolOverlay
visibility={hideOtherCourses}
toggleVisibility={() => setHideOtherCourses(s => !s)}
onClose={() => {
setSelectedCourses([])
setHideOtherCourses(false)
}}
/>
)}
{showMobileMenu && (
<HamburgerMenu
{...{
mode,
setMode,
source,
setSource,
theme,
setTheme,
onClose: () => {
setShowMobileMenu(false)
},
}}
/>
)}
{helpVisible && (
<Popup
title={
<>
<Icon name="info" /> Guida
</>
}
onClose={() => setHelpVisible(false)}
>
<Help />
</Popup>
)}
</>
)
}
render(<App />, document.body)