forked from phc/orario
Refactor delle view-modes, readme and better dockerfile
parent
23c1510455
commit
01fa4b16e2
@ -0,0 +1,16 @@
|
||||
# Orario
|
||||
|
||||
Sito per visualizzare l'orario delle lezioni per i vari anni di corso.
|
||||
|
||||
## Usage
|
||||
|
||||
You need to have installed `node` and `npm` (or `pnpm`). To setup the project just run `npm install`.
|
||||
|
||||
### Development
|
||||
|
||||
To start the development server run `npm run dev`.
|
||||
|
||||
### Production
|
||||
|
||||
To build the ViteJS project run `npm run build`, for deployment a `.env` files can be used to set the `BASE_URL` variable.
|
||||
|
@ -1,81 +1,40 @@
|
||||
[
|
||||
"ALGEBRA 1",
|
||||
"ANALISI ARMONICA - ANALISI ARMONICA/a",
|
||||
"ANALISI MATEMATICA 1",
|
||||
"ANALISI MATEMATICA 2",
|
||||
"ANALISI MATEMATICA 3",
|
||||
"ANALISI NUMERICA CON LABORATORIO - ANALISI NUMERICA",
|
||||
"ARITMETICA",
|
||||
"ASPETTI MATEMATICI NELLA COMPUTAZIONE QUANTISTICA",
|
||||
"CALCOLO SCIENTIFICO",
|
||||
"COMBINATORIA ALGEBRICA",
|
||||
"DETERMINAZIONE ORBITALE",
|
||||
"DINAMICA DEL SISTEMA SOLARE",
|
||||
"DINAMICA DEL SISTEMA SOLARE",
|
||||
"ELEMENTI DI GEOMETRIA ALGEBRICA",
|
||||
"ELEMENTI DI GEOMETRIA ALGEBRICA",
|
||||
"ELEMENTI DI GEOMETRIA ALGEBRICA",
|
||||
"ELEMENTI DI GEOMETRIA ALGEBRICA",
|
||||
"ELEMENTI DI TOPOLOGIA ALGEBRICA",
|
||||
"ELEMENTI DI TOPOLOGIA ALGEBRICA",
|
||||
"ELEMENTI DI TOPOLOGIA ALGEBRICA",
|
||||
"ELEMENTI DI TOPOLOGIA ALGEBRICA",
|
||||
"FISICA II",
|
||||
"FISICA II",
|
||||
"FISICA II",
|
||||
"FISICA II",
|
||||
"FISICA II",
|
||||
"FISICA II",
|
||||
"FISICA II",
|
||||
"FISICA II",
|
||||
"GEOMETRIA E TOPOLOGIA DIFFERENZIALE",
|
||||
"GEOMETRIA E TOPOLOGIA DIFFERENZIALE",
|
||||
"GEOMETRIA E TOPOLOGIA DIFFERENZIALE",
|
||||
"GEOMETRIA E TOPOLOGIA DIFFERENZIALE",
|
||||
"GEOMETRIA E TOPOLOGIA DIFFERENZIALE",
|
||||
"FONDAMENTI DI PROGRAMMAZIONE CON LABORATORIO - FONDAMENTI DI PROGRAMMAZIONE ",
|
||||
"GEOMETRIA 1",
|
||||
"GEOMETRIA 2 - GEOMETRIA 2 A",
|
||||
"GEOMETRIA E TOPOLOGIA DIFFERENZIALE",
|
||||
"INGLESE SCIENTIFICO",
|
||||
"ISTITUZIONI DI ALGEBRA",
|
||||
"ISTITUZIONI DI ALGEBRA",
|
||||
"ISTITUZIONI DI ANALISI MATEMATICA",
|
||||
"ISTITUZIONI DI ANALISI MATEMATICA",
|
||||
"ISTITUZIONI DI ANALISI MATEMATICA",
|
||||
"ISTITUZIONI DI DIDATTICA DELLA MATEMATICA",
|
||||
"ISTITUZIONI DI DIDATTICA DELLA MATEMATICA",
|
||||
"ISTITUZIONI DI DIDATTICA DELLA MATEMATICA",
|
||||
"ISTITUZIONI DI FISICA MATEMATICA",
|
||||
"ISTITUZIONI DI FISICA MATEMATICA",
|
||||
"LABORATORIO COMPUTAZIONALE",
|
||||
"LABORATORIO DI INTRODUZIONE ALLA MATEMATICA COMPUTAZIONALE",
|
||||
"LOGICA MATEMATICA",
|
||||
"LOGICA MATEMATICA",
|
||||
"LOGICA MATEMATICA",
|
||||
"LOGICA MATEMATICA",
|
||||
"MECCANICA SUPERIORE - MECCANICA SUPERIORE/a",
|
||||
"MECCANICA SUPERIORE - MECCANICA SUPERIORE/a",
|
||||
"METODI NUMERICI PER CATENE DI MARKOV - METODI NUMERICI PER CATENE DI MARKOV/a",
|
||||
"METODI NUMERICI PER CATENE DI MARKOV - METODI NUMERICI PER CATENE DI MARKOV/a",
|
||||
"METODI NUMERICI PER LA GRAFICA - METODI NUMERICI PER LA GRAFICA/a",
|
||||
"METODI NUMERICI PER LA GRAFICA - METODI NUMERICI PER LA GRAFICA/a",
|
||||
"PROBABILITÀ",
|
||||
"PROBABILITÀ",
|
||||
"PROBABILITÀ",
|
||||
"PROBABILITÀ",
|
||||
"PROBABILITÀ",
|
||||
"PROBABILITÀ",
|
||||
"RICERCA OPERATIVA",
|
||||
"RICERCA OPERATIVA",
|
||||
"RICERCA OPERATIVA",
|
||||
"RICERCA OPERATIVA",
|
||||
"RICERCA OPERATIVA",
|
||||
"RICERCA OPERATIVA",
|
||||
"SISTEMI DINAMICI",
|
||||
"SISTEMI DINAMICI",
|
||||
"SISTEMI DINAMICI",
|
||||
"SISTEMI DINAMICI",
|
||||
"STORIA DELLA MATEMATICA",
|
||||
"STORIA DELLA MATEMATICA",
|
||||
"STORIA DELLA MATEMATICA",
|
||||
"STORIA DELLA MATEMATICA",
|
||||
"TECNOLOGIE PER LA DIDATTICA",
|
||||
"TECNOLOGIE PER LA DIDATTICA",
|
||||
"TEORIA ANALITICA DEI NUMERI A - TEORIA ANALITICA DEI NUMERI A/a",
|
||||
"TEORIA ANALITICA DEI NUMERI A - TEORIA ANALITICA DEI NUMERI A/a",
|
||||
"TEORIA E METODI DELL'OTTIMIZZAZIONE",
|
||||
"TEORIA E METODI DELL'OTTIMIZZAZIONE",
|
||||
"TOPOLOGIA DIFFERENZIALE - TOPOLOGIA DIFFERENZIALE/a",
|
||||
"TOPOLOGIA DIFFERENZIALE - TOPOLOGIA DIFFERENZIALE/a"
|
||||
]
|
||||
|
@ -1,25 +0,0 @@
|
||||
// import { createContext } from 'preact'
|
||||
// import { toChildArray } from 'preact'
|
||||
// import { useContext, useState } from 'preact/hooks'
|
||||
|
||||
// const AnimationContext = createContext()
|
||||
|
||||
// export const useAnimation = animation => {
|
||||
// const [styles, setStyles] = useState({})
|
||||
|
||||
// const start = () => {
|
||||
// setStyles({ animation })
|
||||
// }
|
||||
|
||||
// return [
|
||||
// start,
|
||||
// {
|
||||
// styles,
|
||||
// onAnimationEnd: e => {
|
||||
// console.log('animation end')
|
||||
// },
|
||||
// },
|
||||
// ]
|
||||
// }
|
||||
|
||||
// export const Animation = ({ animation, children }) => {}
|
@ -1,28 +1,18 @@
|
||||
import { useRef, useState } from 'preact/hooks'
|
||||
import { Icon } from './Icon.jsx'
|
||||
|
||||
let ids = 0
|
||||
|
||||
function generateId() {
|
||||
return 'combo-id-' + ids++
|
||||
}
|
||||
|
||||
export const ComboBox = ({ options, value, setValue }) => {
|
||||
const [uid] = useState(() => generateId())
|
||||
const selectRef = useRef()
|
||||
|
||||
return (
|
||||
<div class="input-combo">
|
||||
<select ref={selectRef} id={uid} onInput={e => setValue(e.target.value)}>
|
||||
<select onInput={e => setValue(e.target.value)}>
|
||||
{options.map(option => (
|
||||
<option value={option.value} selected={option.value === value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<label class="icon" for={uid}>
|
||||
<div class="icon">
|
||||
<Icon name="expand_more" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -0,0 +1,70 @@
|
||||
import { format } from 'date-fns'
|
||||
|
||||
import _ from 'lodash'
|
||||
import { useEffect, useRef, useState } from 'preact/hooks'
|
||||
import { normalizeCourseName, WEEK_DAYS } from '../../utils.jsx'
|
||||
|
||||
export const Course = ({ events, selection, setSelection, hideOtherCourses }) => {
|
||||
const selectionSet = new Set(selection)
|
||||
|
||||
const visibleEvents = !hideOtherCourses ? events : events.filter(e => selectionSet.has(e.name))
|
||||
|
||||
const eventsByCourse = _.groupBy(_.sortBy(visibleEvents, 'name'), 'name')
|
||||
|
||||
const [currentlyHovered, setCurrentlyHovered] = useState(null)
|
||||
const element = useRef()
|
||||
|
||||
useEffect(() => {
|
||||
if (element.current) {
|
||||
const l = e => {
|
||||
const $course = e.target.closest('.course')
|
||||
if ($course) {
|
||||
setCurrentlyHovered($course.dataset.courseId)
|
||||
} else {
|
||||
setCurrentlyHovered(null)
|
||||
}
|
||||
}
|
||||
|
||||
element.current.addEventListener('mousemove', l)
|
||||
element.current.addEventListener('mouseleave', l)
|
||||
|
||||
return () => {
|
||||
element.current.removeEventListener('mousemove', l)
|
||||
element.current.removeEventListener('mouseleave', l)
|
||||
}
|
||||
}
|
||||
}, [element.current])
|
||||
|
||||
return (
|
||||
<div class="course-view" ref={element}>
|
||||
<div class="wrap-container">
|
||||
{Object.entries(eventsByCourse).map(([name, courseEvents]) => (
|
||||
<div
|
||||
class={
|
||||
'course' +
|
||||
(currentlyHovered === name ? ' highlight' : '') +
|
||||
(selectionSet.has(name) ? ' selected' : '')
|
||||
}
|
||||
data-course-id={name}
|
||||
onClick={() => {
|
||||
if (!selectionSet.has(name)) setSelection([...selection, name])
|
||||
else setSelection(selection.filter(n => n !== name))
|
||||
}}
|
||||
>
|
||||
<div class="title">{normalizeCourseName(name)}</div>
|
||||
<div class="docenti">{courseEvents[0].docenti.join(', ')}</div>
|
||||
<div class="events">
|
||||
{courseEvents.map(course => (
|
||||
<div>
|
||||
{WEEK_DAYS[course.start.getDay()]}{' '}
|
||||
{format(course.start, 'H:mm')} –{' '}
|
||||
{format(course.end, 'H:mm')} {course.aula}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -0,0 +1,160 @@
|
||||
import { useEffect, useRef, useState } from 'preact/hooks'
|
||||
|
||||
import _ from 'lodash'
|
||||
import { differenceInMinutes, startOfDay } from 'date-fns'
|
||||
|
||||
import { hashString, normalizeCourseName, WEEK_DAYS } from '../../utils.jsx'
|
||||
import { layoutIntervals } from '../../interval-layout.js'
|
||||
|
||||
export const WorkWeek = ({ events, selection, setSelection, hideOtherCourses }) => {
|
||||
const selectionSet = new Set(selection)
|
||||
|
||||
// const base = {
|
||||
// 1: [],
|
||||
// 2: [],
|
||||
// 3: [],
|
||||
// 4: [],
|
||||
// 5: [],
|
||||
// }
|
||||
|
||||
const eventsByWeekday = _.groupBy(
|
||||
!hideOtherCourses ? events : events.filter(e => selectionSet.has(e.name)),
|
||||
event => event.start.getDay()
|
||||
)
|
||||
|
||||
// const dayIntervalLayout = _.mapValues(Object.assign(base, eventsByWeekday), events =>
|
||||
const dayIntervalLayout = _.mapValues(eventsByWeekday, events =>
|
||||
layoutIntervals(
|
||||
events.map(e => ({
|
||||
start: differenceInMinutes(e.start, startOfDay(e.start)),
|
||||
end: differenceInMinutes(e.end, startOfDay(e.start)),
|
||||
data: e,
|
||||
}))
|
||||
)
|
||||
)
|
||||
|
||||
const [currentlyHovered, setCurrentlyHovered] = useState(null)
|
||||
const element = useRef()
|
||||
|
||||
useEffect(() => {
|
||||
if (element.current) {
|
||||
const l = e => {
|
||||
const $event = e.target.closest('.event')
|
||||
if ($event) {
|
||||
setCurrentlyHovered($event.dataset.eventId)
|
||||
} else {
|
||||
setCurrentlyHovered(null)
|
||||
}
|
||||
}
|
||||
|
||||
element.current.addEventListener('mousemove', l)
|
||||
element.current.addEventListener('mouseleave', l)
|
||||
|
||||
return () => {
|
||||
element.current.removeEventListener('mousemove', l)
|
||||
element.current.removeEventListener('mouseleave', l)
|
||||
}
|
||||
}
|
||||
}, [element.current])
|
||||
|
||||
return (
|
||||
<div class="work-week-v-view" ref={element}>
|
||||
<div class="pivot"></div>
|
||||
<div class="left-header">
|
||||
<div class="blocks">
|
||||
<div
|
||||
class="block skip-border"
|
||||
style={{
|
||||
'--start': 9 - 7,
|
||||
'--size': 2,
|
||||
}}
|
||||
>
|
||||
9:00 – 11:00
|
||||
</div>
|
||||
<div
|
||||
class="block"
|
||||
style={{
|
||||
'--start': 11 - 7,
|
||||
'--size': 2,
|
||||
}}
|
||||
>
|
||||
11:00 – 13:00
|
||||
</div>
|
||||
<div
|
||||
class="block skip-border"
|
||||
style={{
|
||||
'--start': 14 - 7,
|
||||
'--size': 2,
|
||||
}}
|
||||
>
|
||||
14:00 – 16:00
|
||||
</div>
|
||||
<div
|
||||
class="block"
|
||||
style={{
|
||||
'--start': 16 - 7,
|
||||
'--size': 2,
|
||||
}}
|
||||
>
|
||||
16:00 – 18:00
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{Object.entries(dayIntervalLayout).map(([index, layout]) => (
|
||||
<div class="day" style={{ '--size': Math.max(1, layout.length) }}>
|
||||
<div class="top-header">{WEEK_DAYS[parseInt(index)]}</div>
|
||||
<div class="events">
|
||||
{layout.map((events, stackIndex) => (
|
||||
<>
|
||||
{events.map(event => (
|
||||
<div
|
||||
class={
|
||||
'event' +
|
||||
(currentlyHovered === event.data.name
|
||||
? ' highlight'
|
||||
: '') +
|
||||
(selectionSet.has(event.data.name) ? ' selected' : '')
|
||||
}
|
||||
data-event-id={event.data.name}
|
||||
style={{
|
||||
'--start': event.start / 60 - 7,
|
||||
'--stack': stackIndex + 1,
|
||||
'--size': (event.end - event.start) / 60,
|
||||
'--hue':
|
||||
(Math.abs(hashString('seed3' + event.data.name)) %
|
||||
360) +
|
||||
'deg',
|
||||
}}
|
||||
onClick={() => {
|
||||
if (!selectionSet.has(event.data.name))
|
||||
setSelection([...selection, event.data.name])
|
||||
else
|
||||
setSelection(
|
||||
selection.filter(
|
||||
name => event.data.name !== name
|
||||
)
|
||||
)
|
||||
}}
|
||||
>
|
||||
<div class="title">
|
||||
{normalizeCourseName(event.data.name)}
|
||||
</div>
|
||||
<div class="aula">{event.data.aula}</div>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
))}
|
||||
|
||||
{/* Grid Tracks */}
|
||||
{[1, 3, 5].map(i => (
|
||||
<div class="grid-line-h" style={{ '--track': i }}></div>
|
||||
))}
|
||||
{[6, 8, 10].map(i => (
|
||||
<div class="grid-line-h" style={{ '--track': i }}></div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
import { useEffect, useRef } from 'preact/hooks'
|
||||
|
||||
import _ from 'lodash'
|
||||
import { differenceInMinutes, startOfDay } from 'date-fns'
|
||||
|
||||
import { hashString, normalizeCourseName, WEEK_DAYS } from '../../utils.jsx'
|
||||
import { layoutIntervals } from '../../interval-layout.js'
|
||||
|
||||
export const WorkWeekTranspose = ({ events }) => {
|
||||
const eventsByWeekday = _.groupBy(events, event => event.start.getDay())
|
||||
|
||||
// For each weekday compute the interval layout
|
||||
const rowLayouts = _.mapValues(eventsByWeekday, events =>
|
||||
layoutIntervals(
|
||||
events.map(e => ({
|
||||
start: differenceInMinutes(e.start, startOfDay(e.start)),
|
||||
end: differenceInMinutes(e.end, startOfDay(e.start)),
|
||||
data: e,
|
||||
}))
|
||||
)
|
||||
)
|
||||
|
||||
return (
|
||||
<div class="work-week-h-view">
|
||||
<div class="week">
|
||||
{WEEK_DAYS.slice(1, 6).map((label, index) => (
|
||||
<div class="day" style={{ '--size': rowLayouts[index + 1]?.length ?? 0 }}>
|
||||
{label}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div class="events">
|
||||
<div class="header">
|
||||
<div class="label" style={{ 'grid-column': '2 / span 2' }}>
|
||||
9:00 – 11:00
|
||||
</div>
|
||||
<div class="label" style={{ 'grid-column': '4 / span 2' }}>
|
||||
11:00 – 13:00
|
||||
</div>
|
||||
<div class="label" style={{ 'grid-column': '7 / span 2' }}>
|
||||
14:00 – 16:00
|
||||
</div>
|
||||
<div class="label" style={{ 'grid-column': '9 / span 2' }}>
|
||||
16:00 – 18:00
|
||||
</div>
|
||||
</div>
|
||||
<div class="days">
|
||||
{Object.values(rowLayouts).map(layout => (
|
||||
<div class="day" style={{ '--size': layout.length }}>
|
||||
{layout.map((events, rowIndex) =>
|
||||
events.map(event => {
|
||||
const Local = () => {
|
||||
const eventRef = useRef()
|
||||
|
||||
const updateMinWidth = () => {
|
||||
eventRef.current.style.minWidth =
|
||||
eventRef.current.offsetWidth + 'px'
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (eventRef.current) {
|
||||
setTimeout(updateMinWidth, 100)
|
||||
window.addEventListener('resize', updateMinWidth)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener(
|
||||
'resize',
|
||||
updateMinWidth
|
||||
)
|
||||
}
|
||||
}
|
||||
}, [eventRef.current])
|
||||
|
||||
return (
|
||||
<div
|
||||
class="event"
|
||||
style={{
|
||||
'--row': rowIndex + 1,
|
||||
'--start': event.start / 60 - 7,
|
||||
'--size': (event.end - event.start) / 60,
|
||||
'--hue':
|
||||
(Math.abs(
|
||||
hashString('seed3' + event.data.name)
|
||||
) %
|
||||
360) +
|
||||
'deg',
|
||||
}}
|
||||
ref={eventRef}
|
||||
>
|
||||
{normalizeCourseName(event.data.name)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return <Local />
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
Loading…
Reference in New Issue