Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ddf325c03 | ||
|
|
973acad17a |
+2
-1
@@ -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",
|
||||||
|
|||||||
Generated
+8
@@ -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,9 +52,10 @@ 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">
|
||||||
|
|||||||
@@ -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')}–
|
{String(course.start % 60).padStart(2, '0')}
|
||||||
{format(course.end, 'H:mm')}{' '}
|
–
|
||||||
|
{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
|
||||||
|
|||||||
+48
-16
@@ -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>
|
||||||
|
|||||||
+24
-3
@@ -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 {
|
||||||
|
|||||||
+60
-11
@@ -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 =>
|
||||||
|
|||||||
Reference in New Issue
Block a user