Aggiunta trasposizione
parent
a34d36257b
commit
7efb311a61
@ -1,55 +1,295 @@
|
|||||||
import { format } from 'date-fns'
|
import { useEffect, useRef, useState } from 'preact/hooks'
|
||||||
|
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import { prettyCourseName, WEEK_DAYS, withClasses } from '../../utils.jsx'
|
import { differenceInMinutes, startOfDay } from 'date-fns'
|
||||||
|
|
||||||
export const Schedule = ({ timetables, selection, setSelection }) => {
|
import {
|
||||||
const events = timetables['tutti']
|
WEEK_DAYS_SHORT,
|
||||||
const selectionSet = new Set(selection)
|
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>
|
||||||
|
|
||||||
const visibleEvents = events.filter(e => selectionSet.has(e.id))
|
<p>
|
||||||
|
Assicurati quindi di leggerla correttamente (dall'alto verso il
|
||||||
|
basso invece che da sinistra verso destra).
|
||||||
|
</p>
|
||||||
|
|
||||||
const eventsByWeekday = _.mapValues(
|
<p>
|
||||||
_.groupBy(visibleEvents, e => e.start.getDay()),
|
Se preferisci utilizzare la versione vecchia, puoi utilizzare il
|
||||||
dailyEvents => _.groupBy(dailyEvents, e => e.start.getHours())
|
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 (
|
return (
|
||||||
<div class="schedule-view">
|
<div class="no-courses-warning">
|
||||||
{Object.entries(eventsByWeekday).map(([index, dailyEvents]) => (
|
<p>Non hai ancora selezionato nessun corso.</p>
|
||||||
<>
|
<p>
|
||||||
<div class="header giorno">
|
Clicca sui corsi nelle altre visuali per selezionarli e
|
||||||
<div class="inner">{WEEK_DAYS[index]}</div>
|
visualizzarli nell'orario
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{Object.values(dailyEvents).map(events => (
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const Layout = ({ layout, day, colors }) => {
|
||||||
|
console.log(layout)
|
||||||
|
return (
|
||||||
<>
|
<>
|
||||||
<div class="header orario">{format(events[0].start, 'H:mm')}</div>
|
{layout.map(block => (
|
||||||
{events.map(event => (
|
|
||||||
<div
|
<div
|
||||||
class={withClasses([
|
class="event-block-wrapper"
|
||||||
'event',
|
style={{
|
||||||
selectionSet.has(event.id) && 'selected',
|
'--time-start': block.start,
|
||||||
])}
|
'--time-end': block.end,
|
||||||
onClick={() => {
|
'--day-position': day,
|
||||||
if (!selectionSet.has(event.id))
|
}}
|
||||||
setSelection([...selection, event.id])
|
>
|
||||||
else
|
<div class="event-block">
|
||||||
setSelection(
|
{block.events.map((event, index) => (
|
||||||
selection.filter(selId => selId !== event.id)
|
<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="title">{prettyCourseName(event.name)}</div>
|
<div class="transpose-button">
|
||||||
<div class="orario">
|
<button
|
||||||
{format(event.start, 'H:mm')} –{' '}
|
class="small"
|
||||||
{format(event.end, 'H:mm')}
|
onClick={() =>
|
||||||
|
setOrientation(
|
||||||
|
orientation === 'original'
|
||||||
|
? 'transposed'
|
||||||
|
: 'original'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
name="switch_left"
|
||||||
|
style="transform: rotate(-45deg)"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="aula">{event.aule.join(', ')}</div>
|
{[1, 2, 3, 4, 5].map(n => (
|
||||||
|
<>
|
||||||
|
<div class="day-label" style={`--position: ${n + 1}`}>
|
||||||
|
{WEEK_DAYS_SHORT[n]}
|
||||||
</div>
|
</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>
|
</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, setSelection }) => {
|
||||||
|
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>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -1,157 +0,0 @@
|
|||||||
import { useEffect, useRef, useState } from 'preact/hooks'
|
|
||||||
|
|
||||||
import _ from 'lodash'
|
|
||||||
import { differenceInMinutes, startOfDay } from 'date-fns'
|
|
||||||
|
|
||||||
import { hashString, prettyCourseName, WEEK_DAYS } from '../../utils.jsx'
|
|
||||||
import { layoutIntervals } from '../../interval-layout.js'
|
|
||||||
|
|
||||||
export const WorkWeek = ({ events, selection, setSelection, hideOtherCourses }) => {
|
|
||||||
const selectionSet = new Set(selection)
|
|
||||||
|
|
||||||
const eventsByWeekday = _.groupBy(
|
|
||||||
!hideOtherCourses ? events : events.filter(e => selectionSet.has(e.id)),
|
|
||||||
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': 2 * (9 - 7),
|
|
||||||
'--size': 4,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
9:00 – 11:00
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="block"
|
|
||||||
style={{
|
|
||||||
'--start': 2 * (11 - 7),
|
|
||||||
'--size': 4,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
11:00 – 13:00
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="block skip-border"
|
|
||||||
style={{
|
|
||||||
'--start': 2 * (14 - 7),
|
|
||||||
'--size': 4,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
14:00 – 16:00
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="block"
|
|
||||||
style={{
|
|
||||||
'--start': 2 * (16 - 7),
|
|
||||||
'--size': 4,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
16:00 – 18:00
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="block"
|
|
||||||
style={{
|
|
||||||
'--start': 2 * (18 - 7),
|
|
||||||
'--size': 4,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
18:00 – 20: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.id
|
|
||||||
? ' highlight'
|
|
||||||
: '') +
|
|
||||||
(selectionSet.has(event.data.id) ? ' selected' : '')
|
|
||||||
}
|
|
||||||
data-event-id={event.data.id}
|
|
||||||
style={{
|
|
||||||
'--start': event.start / 30 - 14,
|
|
||||||
'--stack': stackIndex + 1,
|
|
||||||
'--size': (event.end - event.start) / 30,
|
|
||||||
'--hue':
|
|
||||||
(Math.abs(hashString('seed3' + event.data.id)) %
|
|
||||||
360) +
|
|
||||||
'deg',
|
|
||||||
}}
|
|
||||||
onClick={() => {
|
|
||||||
if (!selectionSet.has(event.data.id))
|
|
||||||
setSelection([...selection, event.data.id])
|
|
||||||
else
|
|
||||||
setSelection(
|
|
||||||
selection.filter(id => event.data.id !== id)
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div class="title">{prettyCourseName(event.data.name)}</div>
|
|
||||||
<div class="aula">{event.data.aule.join(', ')}</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{/* Grid Tracks */}
|
|
||||||
{[4, 8, 12].map(i => (
|
|
||||||
<div class="grid-line-h" style={{ '--track': i }}></div>
|
|
||||||
))}
|
|
||||||
{[14, 18, 22].map(i => (
|
|
||||||
<div class="grid-line-h" style={{ '--track': i }}></div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,227 +0,0 @@
|
|||||||
import { useEffect, useRef, useState } from 'preact/hooks'
|
|
||||||
|
|
||||||
import _ from 'lodash'
|
|
||||||
import { differenceInMinutes, startOfDay } from 'date-fns'
|
|
||||||
|
|
||||||
import { prettyCourseName } from '../../utils.jsx'
|
|
||||||
import { layoutIntervals } from '../../interval-layout.js'
|
|
||||||
|
|
||||||
export const WorkWeekGrid = ({ timetables, selection, setSelection }) => {
|
|
||||||
const events = timetables['tutti']
|
|
||||||
const selectionSet = new Set(selection)
|
|
||||||
|
|
||||||
const colorList = ['red', 'purple', 'blue', 'yellow', 'green', 'orange', 'lightblue']
|
|
||||||
|
|
||||||
const courses = _.uniqBy(
|
|
||||||
events.filter(e => selectionSet.has(e.id)),
|
|
||||||
event => event.id
|
|
||||||
)
|
|
||||||
const colors = Object.fromEntries(
|
|
||||||
courses.map((course, index) => {
|
|
||||||
return [
|
|
||||||
course.id,
|
|
||||||
[
|
|
||||||
colorList[index % colorList.length],
|
|
||||||
courses.length <= colorList.length
|
|
||||||
? ''
|
|
||||||
: String.fromCharCode(65 + Math.floor(index / colorList.length)),
|
|
||||||
],
|
|
||||||
]
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
const base = {
|
|
||||||
1: [],
|
|
||||||
2: [],
|
|
||||||
3: [],
|
|
||||||
4: [],
|
|
||||||
5: [],
|
|
||||||
}
|
|
||||||
const eventsByWeekday = {
|
|
||||||
...base,
|
|
||||||
..._.groupBy(
|
|
||||||
events.filter(e => selectionSet.has(e.id)),
|
|
||||||
event => event.start.getDay()
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
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 startsAndEnds = Object.entries(dayIntervalLayout).flatMap(([index, layout]) =>
|
|
||||||
layout.flatMap(events => events.map(event => [event.start, event.end]))
|
|
||||||
)
|
|
||||||
const minStart = Math.min(...startsAndEnds.map(([s, e]) => s)) / 60
|
|
||||||
const maxEnd = Math.max(...startsAndEnds.map(([s, e]) => e)) / 60
|
|
||||||
// const timeStart = minStart < 9 ? 7 :
|
|
||||||
// minStart < 11 ? 9 :
|
|
||||||
// minStart < 13 ? 11 :
|
|
||||||
// minStart < 14 ? 13 :
|
|
||||||
// minStart < 16 ? 14 :
|
|
||||||
// minStart < 18 ? 16 :
|
|
||||||
// 18;
|
|
||||||
// const timeEnd = maxEnd > 18 ? 20 :
|
|
||||||
// maxEnd > 16 ? 18 :
|
|
||||||
// maxEnd > 14 ? 16 :
|
|
||||||
// maxEnd > 13 ? 14 :
|
|
||||||
// maxEnd > 11 ? 13 :
|
|
||||||
// maxEnd > 9 ? 11 :
|
|
||||||
// 9;
|
|
||||||
const timeStart = minStart < 9 ? 7 : 9
|
|
||||||
const timeEnd = maxEnd > 18 ? 20 : 18
|
|
||||||
|
|
||||||
const daySizes = Object.entries(dayIntervalLayout).map(([index, layout]) =>
|
|
||||||
Math.max(1, layout.length)
|
|
||||||
)
|
|
||||||
const dayOffsets = daySizes.map((v, i) => {
|
|
||||||
let sum = 0
|
|
||||||
for (let j = 0; j < i; j++) {
|
|
||||||
sum += daySizes[j]
|
|
||||||
}
|
|
||||||
return sum
|
|
||||||
})
|
|
||||||
const daysLength = daySizes[4] + dayOffsets[4]
|
|
||||||
|
|
||||||
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-grid-view" ref={element}>
|
|
||||||
<div
|
|
||||||
class="grid"
|
|
||||||
style={{ '--days-length': daysLength, '--time-length': timeEnd - timeStart }}
|
|
||||||
>
|
|
||||||
{[7, 9, 11, 14, 16, 18].map(n => (
|
|
||||||
<>
|
|
||||||
{n + 2 > timeStart && n < timeEnd && (
|
|
||||||
<div class="time" style={{ '--offset': n - timeStart + 2 }}>
|
|
||||||
{n}-{n + 2}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
))}
|
|
||||||
<div class="vline" style="--offset: 2"></div>
|
|
||||||
{[9, 11, 13, 14, 16, 18].map(n => (
|
|
||||||
<>
|
|
||||||
{n > timeStart && n < timeEnd && (
|
|
||||||
<div class="vline" style={{ '--offset': n - timeStart + 2 }}></div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
))}
|
|
||||||
|
|
||||||
<div class="day-name" style={{ '--line': 2 + dayOffsets[0] }}>
|
|
||||||
Lun
|
|
||||||
</div>
|
|
||||||
<div class="day-name" style={{ '--line': 2 + dayOffsets[1] }}>
|
|
||||||
Mar
|
|
||||||
</div>
|
|
||||||
<div class="day-name" style={{ '--line': 2 + dayOffsets[2] }}>
|
|
||||||
Mer
|
|
||||||
</div>
|
|
||||||
<div class="day-name" style={{ '--line': 2 + dayOffsets[3] }}>
|
|
||||||
Gio
|
|
||||||
</div>
|
|
||||||
<div class="day-name" style={{ '--line': 2 + dayOffsets[4] }}>
|
|
||||||
Ven
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="hline" style={{ '--line': 2 + dayOffsets[0] }}></div>
|
|
||||||
<div class="hline" style={{ '--line': 2 + dayOffsets[1] }}></div>
|
|
||||||
<div class="hline" style={{ '--line': 2 + dayOffsets[2] }}></div>
|
|
||||||
<div class="hline" style={{ '--line': 2 + dayOffsets[3] }}></div>
|
|
||||||
<div class="hline" style={{ '--line': 2 + dayOffsets[4] }}></div>
|
|
||||||
|
|
||||||
{Object.entries(dayIntervalLayout).map(([index, layout]) => (
|
|
||||||
<>
|
|
||||||
{layout.map((events, stackIndex) => (
|
|
||||||
<>
|
|
||||||
{events.map(event => (
|
|
||||||
<div
|
|
||||||
class={
|
|
||||||
'event' +
|
|
||||||
(currentlyHovered === event.data.id
|
|
||||||
? ' highlight'
|
|
||||||
: '') +
|
|
||||||
(selectionSet.has(event.data.id) ? ' selected' : '')
|
|
||||||
}
|
|
||||||
data-event-id={event.data.id}
|
|
||||||
style={{
|
|
||||||
'--line': 2 + dayOffsets[index - 1] + stackIndex,
|
|
||||||
'--offset': event.start / 60 - timeStart + 2,
|
|
||||||
'--length': (event.end - event.start) / 60,
|
|
||||||
'--color': `var(--bubble-${colors[event.data.id][0]})`,
|
|
||||||
'--border-color': `var(--bubble-border-${
|
|
||||||
colors[event.data.id][0]
|
|
||||||
})`,
|
|
||||||
'--highlight-color': `var(--bubble-highlight-${
|
|
||||||
colors[event.data.id][0]
|
|
||||||
})`,
|
|
||||||
}}
|
|
||||||
// onClick={() => {
|
|
||||||
// if (!selectionSet.has(event.data.id))
|
|
||||||
// setSelection([...selection, event.data.id])
|
|
||||||
// else
|
|
||||||
// setSelection(
|
|
||||||
// selection.filter(id => event.data.id !== id)
|
|
||||||
// )
|
|
||||||
// }}
|
|
||||||
>
|
|
||||||
{colors[event.data.id][1] !== '' && (
|
|
||||||
<div className="distinguisher">{`(${
|
|
||||||
colors[event.data.id][1]
|
|
||||||
})`}</div>
|
|
||||||
)}
|
|
||||||
{event.data.aule.join(', ')}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div class="legend">
|
|
||||||
{courses.map(course => (
|
|
||||||
<>
|
|
||||||
<div
|
|
||||||
class="color"
|
|
||||||
style={{
|
|
||||||
'--color': `var(--bubble-${colors[course.id][0]})`,
|
|
||||||
'--border-color': `var(--bubble-border-${colors[course.id][0]})`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{colors[course.id][1]}
|
|
||||||
</div>
|
|
||||||
<div class="name">{prettyCourseName(course.name)}</div>
|
|
||||||
</>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,104 +0,0 @@
|
|||||||
import { useEffect, useRef } from 'preact/hooks'
|
|
||||||
|
|
||||||
import _ from 'lodash'
|
|
||||||
import { differenceInMinutes, startOfDay } from 'date-fns'
|
|
||||||
|
|
||||||
import { hashString, prettyCourseName, 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.id)
|
|
||||||
) %
|
|
||||||
360) +
|
|
||||||
'deg',
|
|
||||||
}}
|
|
||||||
ref={eventRef}
|
|
||||||
>
|
|
||||||
{prettyCourseName(event.data.name)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return <Local />
|
|
||||||
})
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
Loading…
Reference in New Issue