Compare commits

...

2 Commits

Author SHA1 Message Date
Francesco Baldino 3ddf325c03 prototipo eventi custom 1 year ago
Francesco Baldino 973acad17a small fix on dynamic viewport height 1 year ago

@ -16,7 +16,8 @@
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"preact": "^10.10.6", "preact": "^10.10.6",
"sass": "^1.54.8", "sass": "^1.54.8",
"vite": "^3.0.9" "vite": "^3.0.9",
"yaml": "^2.3.2"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.18.13", "@babel/core": "^7.18.13",

@ -23,6 +23,9 @@ dependencies:
vite: vite:
specifier: ^3.0.9 specifier: ^3.0.9
version: 3.0.9(sass@1.54.8) version: 3.0.9(sass@1.54.8)
yaml:
specifier: ^2.3.2
version: 2.3.2
devDependencies: devDependencies:
'@babel/core': '@babel/core':
@ -937,3 +940,8 @@ packages:
sass: 1.54.8 sass: 1.54.8
optionalDependencies: optionalDependencies:
fsevents: 2.3.2 fsevents: 2.3.2
/yaml@2.3.2:
resolution: {integrity: sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==}
engines: {node: '>= 14'}
dev: false

@ -15,6 +15,16 @@ export const SettingsBar = ({ theme, setTheme, date, setDate }) => {
name={theme === 'dark' ? 'dark_mode' : 'light_mode'} name={theme === 'dark' ? 'dark_mode' : 'light_mode'}
/> />
</button> </button>
<button
class="icon"
onClick={() =>
setTheme(theme === 'dark' ? 'light' : 'dark')
}
>
<Icon
name="add"
/>
</button>
<DatePicker date={date} setDate={setDate} /> <DatePicker date={date} setDate={setDate} />
</div> </div>
</div> </div>

@ -36,6 +36,7 @@ export const Toolbar = ({
{ value: 'anno-3', label: 'III' }, { value: 'anno-3', label: 'III' },
{ value: 'magistrale', label: 'Magistrale' }, { value: 'magistrale', label: 'Magistrale' },
{ value: 'tutti', label: 'Tutti' }, { value: 'tutti', label: 'Tutti' },
{ value: 'custom', label: 'Custom' },
]} ]}
value={source} value={source}
setValue={setSource} setValue={setSource}
@ -51,10 +52,11 @@ export const Toolbar = ({
setValue={setSource} setValue={setSource}
/> />
</div> </div>
</div> <div class="empty"></div>
<div class="option-group"> <div class="item option">
<DatePicker date={date} setDate={setDate} /> <DatePicker date={date} setDate={setDate} />
</div> </div>
</div>
<div class="option-group"> <div class="option-group">
<div class="item option"> <div class="item option">
<button class="icon" onClick={() => window.print()}> <button class="icon" onClick={() => window.print()}>

@ -2,7 +2,7 @@ import { format } from 'date-fns'
import _ from 'lodash' import _ from 'lodash'
import { useEffect, useRef, useState } from 'preact/hooks' import { useEffect, useRef, useState } from 'preact/hooks'
import { prettyCourseName, WEEK_DAYS } from '../../utils.jsx' import { parseCustomEvents, prettyCourseName, WEEK_DAYS } from '../../utils.jsx'
import { Icon } from '../Icon.jsx' import { Icon } from '../Icon.jsx'
export const Courses = ({ export const Courses = ({
@ -10,13 +10,18 @@ export const Courses = ({
timetables, timetables,
selection, selection,
setSelection, setSelection,
hideOtherCourses, custom,
isRestrictedList,
}) => { }) => {
const events = timetables[source] const events = isRestrictedList ? timetables['tutti'] : timetables[source]
const selectionSet = new Set(selection) const selectionSet = new Set(selection)
const visibleEvents = hideOtherCourses console.log(events)
? events.filter(e => selectionSet.has(e.id))
const visibleEvents = isRestrictedList
? events
.filter(e => selectionSet.has(e.id))
.concat(parseCustomEvents(custom))
: events : events
const eventsByCourse = _.groupBy(_.sortBy(visibleEvents, 'id'), 'id') const eventsByCourse = _.groupBy(_.sortBy(visibleEvents, 'id'), 'id')
@ -53,7 +58,7 @@ export const Courses = ({
return ( return (
<div class="course-view" ref={element}> <div class="course-view" ref={element}>
{hideOtherCourses && selection.length === 0 && ( {isRestrictedList && selection.length === 0 && (
<div class="warning"> <div class="warning">
<p>Non hai ancora selezionato nessun corso.</p> <p>Non hai ancora selezionato nessun corso.</p>
<p> <p>
@ -67,11 +72,16 @@ export const Courses = ({
<div <div
class={ class={
'course' + 'course' +
(currentlyHovered === id ? ' highlight' : '') + (currentlyHovered === id && !isRestrictedList
(selectionSet.has(id) ? ' selected' : '') ? ' highlight'
: '') +
(selectionSet.has(id) && !isRestrictedList
? ' selected'
: '')
} }
data-course-id={id} data-course-id={id}
onClick={() => { onClick={() => {
if (isRestrictedList) return
if (!selectionSet.has(id)) if (!selectionSet.has(id))
setSelection([...selection, id]) setSelection([...selection, id])
else else
@ -89,9 +99,11 @@ export const Courses = ({
<div class="events"> <div class="events">
{courseEvents.map(course => ( {courseEvents.map(course => (
<div> <div>
{WEEK_DAYS[course.start.getDay()]}{' '} {WEEK_DAYS[course.day]} {course.start / 60}:
{format(course.start, 'H:mm')}&ndash; {String(course.start % 60).padStart(2, '0')}
{format(course.end, 'H:mm')}{' '} &ndash;
{course.end / 60}:
{String(course.end % 60).padStart(2, '0')}{' '}
{course.aule.join(', ')} {course.aule.join(', ')}
</div> </div>
))} ))}

@ -1,7 +1,9 @@
import { useEffect, useRef, useState } from 'preact/hooks' import { useEffect, useRef, useState } from 'preact/hooks'
import _ from 'lodash' import _, { parseInt } from 'lodash'
import { differenceInMinutes, startOfDay } from 'date-fns' import { differenceInMinutes, startOfDay } from 'date-fns'
import { parse } from 'yaml'
import { parseCustomEvents } from '../../utils.jsx'
import { import {
WEEK_DAYS_SHORT, WEEK_DAYS_SHORT,
@ -210,7 +212,7 @@ const ScheduleCard = ({
) )
} }
export const Schedule = ({ timetables, selection }) => { export const Schedule = ({ timetables, selection, custom }) => {
const [hasSeenTranspose, setHasSeenTranspose] = usePersistentState( const [hasSeenTranspose, setHasSeenTranspose] = usePersistentState(
'transpose_info', 'transpose_info',
'false' 'false'
@ -232,14 +234,8 @@ export const Schedule = ({ timetables, selection }) => {
const allEvents = timetables['tutti'] const allEvents = timetables['tutti']
const selectionSet = new Set(selection) const selectionSet = new Set(selection)
const events = allEvents const events = allEvents.filter(e => selectionSet.has(e.id))
.filter(e => selectionSet.has(e.id)) parseCustomEvents(custom).forEach(event => events.push(event))
.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 weekStart = Math.min(...events.map(e => e.start), 9 * 60) / 30
const weekEnd = Math.max(...events.map(e => e.end), 18 * 60) / 30 const weekEnd = Math.max(...events.map(e => e.end), 18 * 60) / 30

@ -1,6 +1,7 @@
import _ from 'lodash' import _ from 'lodash'
import { render } from 'preact' import { render } from 'preact'
import { useEffect, useState } from 'preact/hooks' import { useEffect, useState } from 'preact/hooks'
import { differenceInMinutes, startOfDay } from 'date-fns'
// import { ToolOverlay } from './components/ToolOverlay.jsx' // import { ToolOverlay } from './components/ToolOverlay.jsx'
// //
@ -18,12 +19,7 @@ import { Icon } from './components/Icon.jsx'
import { Popup } from './components/Popup.jsx' import { Popup } from './components/Popup.jsx'
import { Toolbar } from './components/Toolbar.jsx' import { Toolbar } from './components/Toolbar.jsx'
import { OptionBar } from './components/OptionBar.jsx' import { OptionBar } from './components/OptionBar.jsx'
import { import { prettyAulaName, prettyProfName, usePersistentState } from './utils.jsx'
prettyAulaName,
prettyProfName,
clearOldPersistentStates,
usePersistentState,
} from './utils.jsx'
import { SettingsBar } from './components/SettingsBar.jsx' import { SettingsBar } from './components/SettingsBar.jsx'
// Che fanno queste due righe? // Che fanno queste due righe?
@ -67,11 +63,14 @@ function specialEventPatches(eventi) {
function formatEvents(timetable) { function formatEvents(timetable) {
return timetable.map(({ nome, dataInizio, dataFine, docenti, aule }) => { return timetable.map(({ nome, dataInizio, dataFine, docenti, aule }) => {
const start = new Date(dataInizio)
const end = new Date(dataFine)
return { return {
id: nome, id: nome,
name: _.split(nome, '-', 1)[0].trim(), name: _.split(nome, '-', 1)[0].trim(),
start: new Date(dataInizio), day: start.getDay(),
end: new Date(dataFine), start: differenceInMinutes(start, startOfDay(start)),
end: differenceInMinutes(end, startOfDay(start)),
docenti: docenti.map(({ nome, cognome }) => docenti: docenti.map(({ nome, cognome }) =>
prettyProfName(nome, cognome) prettyProfName(nome, cognome)
), ),
@ -142,19 +141,50 @@ async function loadCalendari(date) {
} }
} }
const View = ({ view, selection, setSelection, timetables }) => { const View = ({
view,
selection,
setSelection,
timetables,
custom,
setCustom,
}) => {
if (view === 'orario') { if (view === 'orario') {
return <Schedule selection={selection} timetables={timetables} /> return (
<Schedule
selection={selection}
timetables={timetables}
custom={custom}
/>
)
} else if (view === 'lista') { } else if (view === 'lista') {
return ( return (
<Courses <Courses
selection={selection} selection={selection}
setSelection={setSelection} setSelection={setSelection}
source={'tutti'}
timetables={timetables} timetables={timetables}
hideOtherCourses={true} custom={custom}
isRestrictedList={true}
/> />
) )
} else if (view === 'custom') {
return (
<div class="custom-events-view">
<textarea
value={custom}
onChange={e => setCustom(e.target.value)}
/>
<p>Esempio di evento personalizzato</p>
<p>
Nome evento [label globale]:
<br />
- Lun 9-11
<br />
- Mar 9:00-11:00
<br />- Gio 8:00-12:00 [label locale]
</p>
</div>
)
} else { } else {
return ( return (
<Courses <Courses
@ -162,7 +192,7 @@ const View = ({ view, selection, setSelection, timetables }) => {
setSelection={setSelection} setSelection={setSelection}
source={view} source={view}
timetables={timetables} timetables={timetables}
hideOtherCourses={false} isRestrictedList={false}
/> />
) )
} }
@ -184,15 +214,15 @@ const App = ({}) => {
setTimetables(await loadCalendari(new Date(date))) setTimetables(await loadCalendari(new Date(date)))
}, [date]) }, [date])
// View Modes
// const [mode, setMode] = usePersistentState('orario.mode', MODE_COURSES)
// Selection // Selection
const [selectedCourses, setSelectedCourses] = usePersistentState( const [selectedCourses, setSelectedCourses] = usePersistentState(
'selection', 'selection',
[] []
) )
// Custom Events
const [custom, setCustom] = usePersistentState('custom', [])
// Menus // Menus
const [helpVisible, setHelpVisible] = useState(false) const [helpVisible, setHelpVisible] = useState(false)
const [showMobileMenu, setShowMobileMenu] = useState(false) const [showMobileMenu, setShowMobileMenu] = useState(false)
@ -281,6 +311,8 @@ const App = ({}) => {
setSelection={setSelectedCourses} setSelection={setSelectedCourses}
view={view} view={view}
timetables={timetables} timetables={timetables}
custom={custom}
setCustom={setCustom}
/> />
))} ))}
</div> </div>

@ -530,9 +530,9 @@ body {
} }
.content { .content {
height: calc(100vh - 4rem); height: calc(100dvh - 4rem);
@media screen and (max-width: $device-s-width), (pointer: coarse) { @media screen and (max-width: $device-s-width), (pointer: coarse) {
height: calc(100vh - 8rem); height: calc(100dvh - 8rem);
} }
overflow-y: scroll; overflow-y: scroll;
@ -624,7 +624,7 @@ body {
.schedule-view { .schedule-view {
min-height: 100%; min-height: 100%;
width: 100%; width: 100%;
max-width: 57rem; max-width: 55rem;
margin: auto; margin: auto;
padding: 0rem 0.5rem; padding: 0rem 0.5rem;
@ -897,6 +897,27 @@ body {
overflow-y: scroll; overflow-y: scroll;
} }
} }
.custom-events-view {
min-height: 100%;
width: 100%;
max-width: 30rem;
margin: auto;
padding: 1rem 1rem;
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
textarea {
width: 100%;
min-height: 20rem;
font-size: 20px;
}
}
} }
.overlay { .overlay {

@ -1,6 +1,7 @@
// Calendar
import { useEffect, useState } from 'preact/hooks' import { useEffect, useState } from 'preact/hooks'
import { parse } from 'yaml'
// Calendar
export const WEEK_DAYS = [ export const WEEK_DAYS = [
'Domenica', 'Domenica',
@ -12,15 +13,7 @@ export const WEEK_DAYS = [
'Sabato', 'Sabato',
] ]
export const WEEK_DAYS_SHORT = [ export const WEEK_DAYS_SHORT = ['Dom', 'Lun', 'Mar', 'Mer', 'Gio', 'Ven', 'Sab']
'Dom',
'Lun',
'Mar',
'Mer',
'Gio',
'Ven',
'Sab'
]
// Hashing // Hashing
@ -77,6 +70,62 @@ export function prettyProfName(nome, cognome) {
.join('') .join('')
} }
// Custom events
export function parseCustomEvents(customEventsString) {
try {
const customEvents = []
const parsedEvents = parse(customEventsString)
for (const customCourse in parsedEvents) {
const [_, name, teachers] = /([^\[]*)(?:\[(.*)\])?/.exec(
customCourse
)
const docenti = teachers ? teachers.split(',') : []
for (const customEvent of parsedEvents[customCourse]) {
const [_b, day, startH, startM, endH, endM, label] =
/(lun|lunedì|mar|martedì|mer|mercoledì|gio|giovedì|ven|venerdì)\s*(\d{1,2})(?:\:(\d\d))?\s*-\s*(\d{1,2})(?:\:(\d\d))?\s*(.*)?/i.exec(
customEvent
)
const dayNumber = {
lun: 1,
lunedì: 1,
mar: 2,
martedì: 2,
mer: 3,
mercoledì: 3,
gio: 4,
giovedì: 4,
ven: 5,
venerdì: 5,
}[day.toLowerCase()]
const start = startM
? parseInt(startH) * 60 + parseInt(startM)
: parseInt(startH) * 60
const end = endM
? parseInt(endH) * 60 + parseInt(endM)
: parseInt(endH) * 60
const aule = label ? label.split(',') : []
customEvents.push({
id: name,
name,
docenti,
start,
end,
day: dayNumber,
aule,
})
}
}
return customEvents
} catch (e) {
console.log('ehi', e)
return []
}
}
// JSX // JSX
export const withClasses = classes => export const withClasses = classes =>

Loading…
Cancel
Save