// // Modes // import { differenceInMinutes, format, startOfDay } from 'date-fns' import _ from 'lodash' import { useEffect, useRef, useState } from 'preact/hooks' import { layoutIntervals } from '../interval-layout.js' const WEEK_DAYS = ['Domenica', 'Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato'] function hashString(str, seed = 0) { let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed for (let i = 0, ch; i < str.length; i++) { ch = str.charCodeAt(i) h1 = Math.imul(h1 ^ ch, 2654435761) h2 = Math.imul(h2 ^ ch, 1597334677) } h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909) h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909) return 4294967296 * (2097151 & h2) + (h1 >>> 0) } function normalizeCourseName(name) { return name .split(' ') .map(word => word.toLowerCase()) .map(word => { if (word.trim().length === 0) return word return /(^del|^nel|^di$|^dei$|^con$|^alla$|^per$|^e$|^la$)/.test(word) ? word : word[0].toUpperCase() + word.slice(1) }) .join(' ') .replaceAll('Ii', 'II') .replaceAll('Iii', 'III') .replaceAll(/'(.)/g, ({}, letter) => "'" + letter.toUpperCase()) } const WorkWeekView = ({ 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 (
{WEEK_DAYS.slice(1, 6).map((label, index) => (
{label}
))}
9:00 – 11:00
11:00 – 13:00
14:00 – 16:00
16:00 – 18:00
{Object.values(rowLayouts).map(layout => (
{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 (
{normalizeCourseName(event.data.name)}
) } return }) )}
))}
) } const WorkWeekVerticalView = ({ 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 (
9:00 – 11:00
11:00 – 13:00
14:00 – 16:00
16:00 – 18:00
{Object.entries(dayIntervalLayout).map(([index, layout]) => (
{WEEK_DAYS[parseInt(index)]}
{layout.map((events, stackIndex) => ( <> {events.map(event => (
{ if (!selectionSet.has(event.data.name)) setSelection([...selection, event.data.name]) else setSelection( selection.filter( name => event.data.name !== name ) ) }} >
{normalizeCourseName(event.data.name)}
{event.data.aula}
))} ))} {/* Grid Tracks */} {[1, 3, 5].map(i => (
))} {[6, 8, 10].map(i => (
))}
))}
) } const CourseView = ({ 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 (
{Object.entries(eventsByCourse).map(([name, courseEvents]) => (
{ if (!selectionSet.has(name)) setSelection([...selection, name]) else setSelection(selection.filter(n => n !== name)) }} >
{normalizeCourseName(name)}
{courseEvents[0].docenti.join(', ')}
{courseEvents.map(course => (
{WEEK_DAYS[course.start.getDay()]}{' '} {format(course.start, 'H:mm')} – {format(course.end, 'H:mm')} {course.aula}
))}
))}
) } // // EventsView // const viewModeMap = { 'work-week-h': WorkWeekView, 'work-week-v': WorkWeekVerticalView, 'course': CourseView, } export const EventsView = ({ mode, ...viewProps }) => { const Mode = viewModeMap[mode] return (
) }