forked from phc/orario
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.
267 lines
8.3 KiB
JavaScript
267 lines
8.3 KiB
JavaScript
import { useEffect, useRef, useState } from 'preact/hooks'
|
|
|
|
import _ from 'lodash'
|
|
import { differenceInMinutes, startOfDay } from 'date-fns'
|
|
|
|
import { WEEK_DAYS_SHORT, prettyCourseName, usePersistentState } from '../../utils.jsx'
|
|
import { layoutEvents, layoutIntervals } from '../../interval-layout.js'
|
|
import { Popup } from '../Popup.jsx'
|
|
import { Icon } from '../Icon.jsx'
|
|
|
|
const TransposePopup = ({ onClose }) => {
|
|
return (
|
|
<Popup
|
|
title={
|
|
<>
|
|
<Icon name="info" /> Attenzione! La tabella è stata trasposta!
|
|
</>
|
|
}
|
|
onClose={onClose}
|
|
>
|
|
<p>A grande richiesta popolare abbiamo trasposto la tabella dell'orario!</p>
|
|
|
|
<p>
|
|
Assicurati quindi di leggerla correttamente (dall'alto verso il basso
|
|
invece che da sinistra verso destra).
|
|
</p>
|
|
|
|
<p>
|
|
Se preferisci utilizzare la versione vecchia, puoi utilizzare il pulsante
|
|
Trasponi <Icon name="switch_left" style="transform: rotate(-45deg)" />{' '}
|
|
nell'origine della tabella per trasporla. Questa scelta verrà salvata nei
|
|
cookie e verrà ricordata in futuro
|
|
</p>
|
|
</Popup>
|
|
)
|
|
}
|
|
|
|
const NoCourseWarning = () => {
|
|
return (
|
|
<div class="warning">
|
|
<p>Non hai ancora selezionato nessun corso.</p>
|
|
<p>
|
|
Clicca sui corsi nelle altre visuali per selezionarli e visualizzarli
|
|
nell'orario
|
|
</p>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
const Layout = ({ layout, day, colors }) => {
|
|
return (
|
|
<>
|
|
{layout.map(block => (
|
|
<div
|
|
class="event-block-wrapper"
|
|
style={{
|
|
'--time-start': block.start,
|
|
'--time-end': block.end,
|
|
'--day-position': day,
|
|
}}
|
|
>
|
|
<div class="event-block">
|
|
{block.events.map((event, index) => (
|
|
<div
|
|
class="event-wrapper"
|
|
style={{
|
|
'--block-size': block.end - block.start,
|
|
'--size': event.end - event.start,
|
|
'--relative-start': event.start - block.start,
|
|
'--index': event.index,
|
|
'--of': block.layers,
|
|
'--color': `var(--event-${colors[event.id]})`,
|
|
}}
|
|
>
|
|
<div class="event">
|
|
{event.aule.map(aula => (
|
|
<div>{aula.replace(/^Fib /, '')}</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</>
|
|
)
|
|
}
|
|
|
|
const ScheduleGrid = ({
|
|
orientation,
|
|
setOrientation,
|
|
weekStart,
|
|
weekEnd,
|
|
dayBlocksLayout,
|
|
colors,
|
|
}) => {
|
|
return (
|
|
<div
|
|
class={`grid ${orientation}`}
|
|
style={{
|
|
'--time-slots': weekEnd - weekStart,
|
|
}}
|
|
>
|
|
<div class="transpose-button">
|
|
<button
|
|
class="small"
|
|
onClick={() =>
|
|
setOrientation(
|
|
orientation === 'original' ? 'transposed' : 'original'
|
|
)
|
|
}
|
|
>
|
|
<Icon name="switch_left" style="transform: rotate(-45deg)" />
|
|
</button>
|
|
</div>
|
|
{[1, 2, 3, 4, 5].map(n => (
|
|
<>
|
|
<div class="day-label" style={`--position: ${n + 1}`}>
|
|
{WEEK_DAYS_SHORT[n]}
|
|
</div>
|
|
<div class="day-line" style={`--position: ${n + 1}`}></div>
|
|
</>
|
|
))}
|
|
|
|
{[9, 11, 14, 16].map(n => (
|
|
<div
|
|
class="time-label"
|
|
style={{
|
|
'--position': n * 2 - weekStart,
|
|
}}
|
|
>
|
|
{n}-{n + 2}
|
|
</div>
|
|
))}
|
|
<div class="time-line" style="--position: 0"></div>
|
|
{[9, 11, 13, 14, 16, 18].map(n => (
|
|
<>
|
|
{n * 2 > weekStart && n * 2 < weekEnd && (
|
|
<div
|
|
class="time-line"
|
|
style={{
|
|
'--position': n * 2 - weekStart,
|
|
}}
|
|
></div>
|
|
)}
|
|
</>
|
|
))}
|
|
|
|
{Object.entries(dayBlocksLayout).map(([day, layout]) => (
|
|
<Layout layout={layout} day={day} colors={colors} />
|
|
))}
|
|
</div>
|
|
)
|
|
}
|
|
const ScheduleLegend = ({ courses, colors }) => {
|
|
return (
|
|
<div class="legend">
|
|
{courses.map(course => (
|
|
<>
|
|
<div
|
|
class="color"
|
|
style={{
|
|
'--color': `var(--event-${colors[course.id]})`,
|
|
}}
|
|
></div>
|
|
<div class="name">{prettyCourseName(course.name)}</div>
|
|
</>
|
|
))}
|
|
</div>
|
|
)
|
|
}
|
|
const ScheduleCard = ({
|
|
orientation,
|
|
setOrientation,
|
|
weekStart,
|
|
weekEnd,
|
|
dayBlocksLayout,
|
|
courses,
|
|
colors,
|
|
}) => {
|
|
return (
|
|
<div class="schedule-card">
|
|
<ScheduleGrid
|
|
orientation={orientation}
|
|
setOrientation={setOrientation}
|
|
weekStart={weekStart}
|
|
weekEnd={weekEnd}
|
|
dayBlocksLayout={dayBlocksLayout}
|
|
colors={colors}
|
|
/>
|
|
<ScheduleLegend courses={courses} colors={colors} />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export const Schedule = ({ timetables, selection }) => {
|
|
const [hasSeenTranspose, setHasSeenTranspose] = usePersistentState(
|
|
'transpose_info',
|
|
'false'
|
|
)
|
|
const [orientation, setOrientation] = usePersistentState('orientation', 'original')
|
|
|
|
const colorList = ['red', 'purple', 'blue', 'yellow', 'green', 'orange', 'lightblue']
|
|
|
|
const allEvents = timetables['tutti']
|
|
const selectionSet = new Set(selection)
|
|
const events = allEvents
|
|
.filter(e => selectionSet.has(e.id))
|
|
.map(e => ({
|
|
...e,
|
|
day: e.start.getDay(),
|
|
start: differenceInMinutes(e.start, startOfDay(e.start)),
|
|
end: differenceInMinutes(e.end, startOfDay(e.start)),
|
|
}))
|
|
|
|
const weekStart = Math.min(...events.map(e => e.start), 9 * 60) / 30
|
|
const weekEnd = Math.max(...events.map(e => e.end), 18 * 60) / 30
|
|
const relativeEvents = events.map(e => ({
|
|
...e,
|
|
start: Math.round(e.start / 30 - weekStart),
|
|
end: Math.round(e.end / 30 - weekStart),
|
|
}))
|
|
|
|
const courses = _.uniqBy(events, event => event.id)
|
|
const colors = Object.fromEntries(
|
|
courses.map((course, index) => {
|
|
return [course.id, colorList[index % colorList.length]]
|
|
})
|
|
)
|
|
|
|
const base = {
|
|
1: [],
|
|
2: [],
|
|
3: [],
|
|
4: [],
|
|
5: [],
|
|
}
|
|
const eventsByWeekday = {
|
|
...base,
|
|
..._.groupBy(relativeEvents, event => event.day),
|
|
}
|
|
|
|
const dayBlocksLayout = _.mapValues(eventsByWeekday, dayEvents =>
|
|
layoutEvents(dayEvents)
|
|
)
|
|
|
|
return (
|
|
<>
|
|
{hasSeenTranspose === 'false' && (
|
|
<TransposePopup onClose={() => setHasSeenTranspose('true')} />
|
|
)}
|
|
<div class="schedule-view">
|
|
{selection.length === 0 && <NoCourseWarning />}
|
|
<ScheduleCard
|
|
orientation={orientation}
|
|
setOrientation={setOrientation}
|
|
weekStart={weekStart}
|
|
weekEnd={weekEnd}
|
|
dayBlocksLayout={dayBlocksLayout}
|
|
courses={courses}
|
|
colors={colors}
|
|
/>
|
|
</div>
|
|
</>
|
|
)
|
|
}
|