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/components/view/Schedule.jsx

295 lines
8.8 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="no-courses-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>
</>
)
}