first brutal rework and first prototype
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
import { Icon } from './Icon.jsx'
|
||||
import { MODE_COURSES } from './EventsView.jsx'
|
||||
|
||||
export const ToolOverlay = ({ visibility, toggleVisibility, onClose }) => (
|
||||
export const ToolOverlay = ({ mode, toggleMode, onClose }) => (
|
||||
<div class="overlay">
|
||||
<button class="icon primary" onClick={toggleVisibility}>
|
||||
<Icon name={visibility ? 'visibility_off' : 'visibility'} />
|
||||
<button class="icon primary" onClick={toggleMode}>
|
||||
<Icon name={mode === MODE_COURSES ? 'calendar_view_month' : 'list'} />
|
||||
</button>
|
||||
<button class="icon" onClick={onClose}>
|
||||
<Icon name="close" />
|
||||
|
||||
@@ -1,20 +1,14 @@
|
||||
import { Course } from './view/Course.jsx'
|
||||
import { Schedule } from './view/Schedule.jsx'
|
||||
import { WorkWeek } from './view/WorkWeek.jsx'
|
||||
import { WorkWeekGrid } from './view/WorkWeekGrid.jsx'
|
||||
import { WorkWeekTranspose } from './view/WorkWeekTranspose.jsx'
|
||||
import { Courses } from './view/Courses.jsx'
|
||||
import { WorkWeekGrid as Schedule } from './view/WorkWeekGrid.jsx'
|
||||
|
||||
export const MODE_COURSE = 'course'
|
||||
export const MODE_COURSES = 'course'
|
||||
export const MODE_WORKWEEK = 'work-week'
|
||||
export const MODE_SCHEDULE = 'schedule'
|
||||
export const MODE_WORKWEEK_GRID = 'work-week-grid'
|
||||
export const MODE_WORKWEEK_TRANSPOSE = 'work-week-transpose'
|
||||
|
||||
const viewModeMap = {
|
||||
[MODE_COURSE]: Course,
|
||||
[MODE_WORKWEEK]: WorkWeek,
|
||||
[MODE_WORKWEEK_GRID]: WorkWeekGrid,
|
||||
[MODE_WORKWEEK_TRANSPOSE]: WorkWeekTranspose,
|
||||
[MODE_COURSES]: Courses,
|
||||
[MODE_SCHEDULE]: Schedule,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ComboBox } from './ComboBox.jsx'
|
||||
import { MODE_COURSE, MODE_SCHEDULE, MODE_WORKWEEK, MODE_WORKWEEK_GRID } from './EventsView.jsx'
|
||||
import { MODE_COURSES, MODE_SCHEDULE, MODE_WORKWEEK, MODE_WORKWEEK_GRID } from './EventsView.jsx'
|
||||
import { Help } from './Help.jsx'
|
||||
import { Icon } from './Icon.jsx'
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { CompoundButton } from './CompoundButton.jsx'
|
||||
import { MODE_COURSE, MODE_SCHEDULE, MODE_WORKWEEK_GRID } from './EventsView.jsx'
|
||||
import { Icon } from './Icon.jsx'
|
||||
|
||||
export const OptionBar = ({ mode, setMode, source, setSource }) => {
|
||||
export const OptionBar = ({ source, setSource }) => {
|
||||
return (
|
||||
<div class="option-bar">
|
||||
<div class="option-group">
|
||||
@@ -20,31 +19,6 @@ export const OptionBar = ({ mode, setMode, source, setSource }) => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="option-group">
|
||||
<div class="item option">
|
||||
<CompoundButton
|
||||
options={[
|
||||
{
|
||||
value: MODE_COURSE,
|
||||
label: <Icon name="list" />,
|
||||
icon: true,
|
||||
},
|
||||
{
|
||||
value: MODE_WORKWEEK_GRID,
|
||||
label: <Icon name="calendar_view_month" />,
|
||||
icon: true,
|
||||
},
|
||||
{
|
||||
value: MODE_SCHEDULE,
|
||||
label: <Icon name="calendar_view_day" />,
|
||||
icon: true,
|
||||
},
|
||||
]}
|
||||
value={mode}
|
||||
setValue={setMode}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { CompoundButton } from './CompoundButton.jsx'
|
||||
import { MODE_COURSE, MODE_SCHEDULE, MODE_WORKWEEK, MODE_WORKWEEK_GRID } from './EventsView.jsx'
|
||||
import { Icon } from './Icon.jsx'
|
||||
|
||||
export const Toolbar = ({
|
||||
mode,
|
||||
setMode,
|
||||
source,
|
||||
setSource,
|
||||
onShowMenu,
|
||||
@@ -38,18 +35,6 @@ export const Toolbar = ({
|
||||
</div>
|
||||
</div>
|
||||
<div class="option-group">
|
||||
<div class="item option">
|
||||
<CompoundButton
|
||||
options={[
|
||||
{ value: MODE_COURSE, label: 'Corsi' },
|
||||
{ value: MODE_WORKWEEK, label: 'Settimana' },
|
||||
{ value: MODE_WORKWEEK_GRID, label: 'Schema' },
|
||||
// { value: MODE_SCHEDULE, label: 'Giorno' },
|
||||
]}
|
||||
value={mode}
|
||||
setValue={setMode}
|
||||
/>
|
||||
</div>
|
||||
<div class="item option">
|
||||
<button class="icon" onClick={() => window.print()}>
|
||||
<Icon name="print" />
|
||||
|
||||
@@ -4,12 +4,11 @@ import _ from 'lodash'
|
||||
import { useEffect, useRef, useState } from 'preact/hooks'
|
||||
import { prettyCourseName, WEEK_DAYS } from '../../utils.jsx'
|
||||
|
||||
export const Course = ({ events, selection, setSelection, hideOtherCourses }) => {
|
||||
export const Courses = ({ source, timetables, selection, setSelection }) => {
|
||||
const events = timetables[source]
|
||||
const selectionSet = new Set(selection)
|
||||
|
||||
const visibleEvents = !hideOtherCourses ? events : events.filter(e => selectionSet.has(e.id))
|
||||
|
||||
const eventsByCourse = _.groupBy(_.sortBy(visibleEvents, 'id'), 'id')
|
||||
const eventsByCourse = _.groupBy(_.sortBy(events, 'id'), 'id')
|
||||
|
||||
const profsPerCourse = _.mapValues(eventsByCourse, events =>
|
||||
_.uniq(events.flatMap(event => event.docenti))
|
||||
@@ -2,10 +2,11 @@ import { format } from 'date-fns'
|
||||
import _ from 'lodash'
|
||||
import { prettyCourseName, WEEK_DAYS, withClasses } from '../../utils.jsx'
|
||||
|
||||
export const Schedule = ({ events, selection, setSelection, hideOtherCourses }) => {
|
||||
export const Schedule = ({ timetables, selection, setSelection }) => {
|
||||
const events = timetables['tutti']
|
||||
const selectionSet = new Set(selection)
|
||||
|
||||
const visibleEvents = !hideOtherCourses ? events : events.filter(e => selectionSet.has(e.id))
|
||||
const visibleEvents = events.filter(e => selectionSet.has(e.id))
|
||||
|
||||
const eventsByWeekday = _.mapValues(
|
||||
_.groupBy(visibleEvents, e => e.start.getDay()),
|
||||
|
||||
@@ -6,13 +6,14 @@ import { differenceInMinutes, startOfDay } from 'date-fns'
|
||||
import { prettyCourseName } from '../../utils.jsx'
|
||||
import { layoutIntervals } from '../../interval-layout.js'
|
||||
|
||||
export const WorkWeekGrid = ({ events, selection, setSelection, hideOtherCourses }) => {
|
||||
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(
|
||||
!hideOtherCourses ? events : events.filter(e => selectionSet.has(e.id)),
|
||||
events.filter(e => selectionSet.has(e.id)),
|
||||
event => event.id
|
||||
)
|
||||
const colors = Object.fromEntries(
|
||||
@@ -39,7 +40,7 @@ export const WorkWeekGrid = ({ events, selection, setSelection, hideOtherCourses
|
||||
const eventsByWeekday = {
|
||||
...base,
|
||||
..._.groupBy(
|
||||
!hideOtherCourses ? events : events.filter(e => selectionSet.has(e.id)),
|
||||
events.filter(e => selectionSet.has(e.id)),
|
||||
event => event.start.getDay()
|
||||
),
|
||||
}
|
||||
@@ -183,14 +184,14 @@ export const WorkWeekGrid = ({ events, selection, setSelection, hideOtherCourses
|
||||
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)
|
||||
)
|
||||
}}
|
||||
// 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">{`(${
|
||||
|
||||
+93
-82
@@ -3,7 +3,7 @@ import { render } from 'preact'
|
||||
import { useEffect, useState } from 'preact/hooks'
|
||||
import { ToolOverlay } from './components/CourseVisibility.jsx'
|
||||
|
||||
import { EventsView, MODE_COURSE } from './components/EventsView.jsx'
|
||||
import { EventsView, MODE_COURSES, MODE_SCHEDULE } from './components/EventsView.jsx'
|
||||
import { HamburgerMenu } from './components/HamburgerMenu.jsx'
|
||||
import { Help } from './components/Help.jsx'
|
||||
import { Icon } from './components/Icon.jsx'
|
||||
@@ -28,6 +28,13 @@ const CALENDAR_IDS = {
|
||||
],
|
||||
}
|
||||
|
||||
const TIMETABLE_IDS = {
|
||||
'anno-1': '64a7c1c651f079001d52e9c8',
|
||||
'anno-2': '6308e2dc09352a0208fefdd9',
|
||||
'anno-3': '6308e42a1df5cb026699ced4',
|
||||
'magistrale': '64a7c7091ab813002c5d9ede',
|
||||
}
|
||||
|
||||
const DEFAULT_DATE_RANGE = {
|
||||
from: '2023-10-02T00:00:00.000Z',
|
||||
to: '2023-10-07T00:00:00.000Z',
|
||||
@@ -53,83 +60,90 @@ function specialEventPatches(eventi) {
|
||||
return eventi
|
||||
}
|
||||
|
||||
async function loadEventi(ids) {
|
||||
const calendari = await Promise.all(
|
||||
ids.map(async id => {
|
||||
// Almost directly copy-pasted from Chrome Dev Tools
|
||||
const req = await fetch(
|
||||
'https://apache.prod.up.cineca.it/api/Impegni/getImpegniCalendarioPubblico',
|
||||
{
|
||||
headers: {
|
||||
'content-type': 'application/json;charset=UTF-8',
|
||||
'sec-fetch-dest': 'empty',
|
||||
'sec-fetch-mode': 'cors',
|
||||
'sec-fetch-site': 'same-site',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
mostraImpegniAnnullati: true,
|
||||
mostraIndisponibilitaTotali: false,
|
||||
linkCalendarioId: id,
|
||||
clienteId: '628de8b9b63679f193b87046',
|
||||
pianificazioneTemplate: false,
|
||||
dataInizio: DATE_RANGES[id].from,
|
||||
dataFine: DATE_RANGES[id].to,
|
||||
}),
|
||||
method: 'POST',
|
||||
mode: 'cors',
|
||||
credentials: 'omit',
|
||||
}
|
||||
)
|
||||
|
||||
return await req.json()
|
||||
})
|
||||
function formatEvents(timetable) {
|
||||
return timetable.map(
|
||||
({ nome, dataInizio, dataFine, docenti, aule }) => {
|
||||
return {
|
||||
id: nome,
|
||||
name: _.split(nome, '-', 1)[0].trim(),
|
||||
start: new Date(dataInizio),
|
||||
end: new Date(dataFine),
|
||||
docenti: docenti.map(({ nome, cognome }) => prettyProfName(nome, cognome)),
|
||||
aule: aule.map(aula => prettyAulaName(aula.codice)),
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (ids.length === 1) {
|
||||
return specialEventPatches(calendari[0])
|
||||
async function loadCalendari() {
|
||||
async function req(id) {
|
||||
// Almost directly copy-pasted from Chrome Dev Tools
|
||||
const req = await fetch(
|
||||
'https://apache.prod.up.cineca.it/api/Impegni/getImpegniCalendarioPubblico',
|
||||
{
|
||||
headers: {
|
||||
'content-type': 'application/json;charset=UTF-8',
|
||||
'sec-fetch-dest': 'empty',
|
||||
'sec-fetch-mode': 'cors',
|
||||
'sec-fetch-site': 'same-site',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
mostraImpegniAnnullati: true,
|
||||
mostraIndisponibilitaTotali: false,
|
||||
linkCalendarioId: id,
|
||||
clienteId: '628de8b9b63679f193b87046',
|
||||
pianificazioneTemplate: false,
|
||||
dataInizio: DATE_RANGES[id].from,
|
||||
dataFine: DATE_RANGES[id].to,
|
||||
}),
|
||||
method: 'POST',
|
||||
mode: 'cors',
|
||||
credentials: 'omit',
|
||||
}
|
||||
)
|
||||
|
||||
return await req.json()
|
||||
}
|
||||
|
||||
return specialEventPatches(_.uniqBy(_.concat(...calendari), 'id'))
|
||||
const requests = [
|
||||
req(TIMETABLE_IDS['anno-1']),
|
||||
req(TIMETABLE_IDS['anno-2']),
|
||||
req(TIMETABLE_IDS['anno-3']),
|
||||
req(TIMETABLE_IDS['magistrale']),
|
||||
]
|
||||
const results = await Promise.all(requests)
|
||||
const timetablesRaw = results.map(timetable => specialEventPatches(_.uniqBy(timetable, 'id')))
|
||||
const allRaw = specialEventPatches(_.uniqBy(_.concat(...results), 'id'))
|
||||
|
||||
return {
|
||||
'anno-1': formatEvents(timetablesRaw[0]),
|
||||
'anno-2': formatEvents(timetablesRaw[1]),
|
||||
'anno-3': formatEvents(timetablesRaw[2]),
|
||||
'magistrale': formatEvents(timetablesRaw[3]),
|
||||
'tutti': formatEvents(allRaw)
|
||||
}
|
||||
}
|
||||
|
||||
const App = ({}) => {
|
||||
// Data Sources
|
||||
const [source, setSource] = usePersistentState('orario.source', 'magistrale')
|
||||
const [eventi, setEventi] = useState([])
|
||||
const [timetables, setTimetables] = useState(null)
|
||||
useEffect(async () => {
|
||||
setTimetables(await loadCalendari())
|
||||
}, [])
|
||||
|
||||
// View Modes
|
||||
const [mode, setMode] = usePersistentState('orario.mode', MODE_COURSE)
|
||||
const [mode, setMode] = usePersistentState('orario.mode', MODE_COURSES)
|
||||
|
||||
// Selection
|
||||
const [selectedCourses, setSelectedCourses] = usePersistentState('orario.selection', [])
|
||||
const [hideOtherCourses, setHideOtherCourses] = usePersistentState('orario.hide-other', false)
|
||||
|
||||
// Menus
|
||||
const [helpVisible, setHelpVisible] = useState(false)
|
||||
const [showMobileMenu, setShowMobileMenu] = useState(false)
|
||||
|
||||
useEffect(async () => {
|
||||
const eventi = await loadEventi(CALENDAR_IDS[source])
|
||||
|
||||
window.dataBuffer[source] = eventi
|
||||
|
||||
setEventi(eventi)
|
||||
}, [source])
|
||||
|
||||
const groupIds = new Set(eventi.map(e => e.nome))
|
||||
const toolOverlayVisible =
|
||||
selectedCourses.length > 0 && selectedCourses.filter(id => groupIds.has(id)).length > 0
|
||||
|
||||
useEffect(() => {
|
||||
const groupIds = new Set(eventi.map(e => e.nome))
|
||||
|
||||
if (
|
||||
selectedCourses.length === 0 ||
|
||||
(eventi.length > 0 && selectedCourses.filter(id => groupIds.has(id)).length === 0)
|
||||
) {
|
||||
setHideOtherCourses(false)
|
||||
}
|
||||
}, [eventi, selectedCourses.length])
|
||||
// const groupIds = new Set(eventi.map(e => e.nome))
|
||||
const toolOverlayVisible = selectedCourses.length > 0
|
||||
|
||||
const [theme, setTheme] = usePersistentState(
|
||||
'orario.theme',
|
||||
@@ -152,37 +166,34 @@ const App = ({}) => {
|
||||
setTheme,
|
||||
}}
|
||||
/>
|
||||
<OptionBar
|
||||
{...{
|
||||
mode,
|
||||
setMode,
|
||||
source,
|
||||
setSource,
|
||||
onHelp: () => setHelpVisible(true),
|
||||
}}
|
||||
/>
|
||||
<EventsView
|
||||
{mode === MODE_COURSES && (
|
||||
<OptionBar
|
||||
{...{
|
||||
mode,
|
||||
setMode,
|
||||
source,
|
||||
setSource,
|
||||
onHelp: () => setHelpVisible(true),
|
||||
}}orizzontale
|
||||
/>
|
||||
)}
|
||||
{timetables && (
|
||||
<EventsView
|
||||
mode={mode}
|
||||
selection={selectedCourses}
|
||||
setSelection={setSelectedCourses}
|
||||
hideOtherCourses={hideOtherCourses}
|
||||
start={new Date(2022, 10, 3)}
|
||||
events={eventi.map(({ nome, dataInizio, dataFine, docenti, aule }) => ({
|
||||
id: nome,
|
||||
name: _.split(nome, '-', 1)[0].trim(),
|
||||
start: new Date(dataInizio),
|
||||
end: new Date(dataFine),
|
||||
docenti: docenti.map(({ nome, cognome }) => prettyProfName(nome, cognome)),
|
||||
aule: aule.map(aula => prettyAulaName(aula.codice)),
|
||||
}))}
|
||||
/>
|
||||
source={source}
|
||||
timetables={timetables}
|
||||
/>
|
||||
)}
|
||||
{toolOverlayVisible && (
|
||||
<ToolOverlay
|
||||
visibility={hideOtherCourses}
|
||||
toggleVisibility={() => setHideOtherCourses(s => !s)}
|
||||
mode={mode}
|
||||
toggleMode={() => setMode(mode === MODE_COURSES ? MODE_SCHEDULE : MODE_COURSES)}
|
||||
onClose={() => {
|
||||
setSelectedCourses([])
|
||||
setHideOtherCourses(false)
|
||||
setMode(MODE_COURSES)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -476,7 +476,7 @@ body {
|
||||
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
justify-content: center;
|
||||
|
||||
overflow-y: hidden;
|
||||
overflow-x: auto;
|
||||
|
||||
Reference in New Issue
Block a user