New vertical layout

dev
Antonio De Lucreziis 2 years ago
parent 19bea2f48f
commit 45cd07fa85

@ -8,7 +8,7 @@
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@48,400,0,0" /> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@48,400,0,0" />
<link rel="stylesheet" href="/src/styles/main.scss"> <link rel="stylesheet" href="/src/styles/main.scss">

@ -6,7 +6,7 @@ import { differenceInMinutes, format, getWeek, startOfDay } from 'date-fns'
import { it } from 'date-fns/locale' import { it } from 'date-fns/locale'
import _ from 'lodash' import _ from 'lodash'
import { useEffect, useRef } from 'preact/hooks' import { useEffect, useRef, useState } from 'preact/hooks'
import { layoutIntervals } from '../interval-layout.js' import { layoutIntervals } from '../interval-layout.js'
function hashString(str, seed = 0) { function hashString(str, seed = 0) {
@ -36,12 +36,10 @@ const WorkWeekView = ({ events }) => {
) )
) )
console.log(rowLayouts)
const weekDays = ['Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì'] const weekDays = ['Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì']
return ( return (
<div class="workweek-view"> <div class="work-week-h-view">
<div class="week"> <div class="week">
{weekDays.map((label, index) => ( {weekDays.map((label, index) => (
<div class="day" style={{ '--size': rowLayouts[index + 1]?.length ?? 0 }}> <div class="day" style={{ '--size': rowLayouts[index + 1]?.length ?? 0 }}>
@ -126,52 +124,136 @@ const WorkWeekView = ({ events }) => {
) )
} }
/* const WorkWeekVerticalView = ({ events }) => {
{sortedKeys.map(blockStart => ( const eventsByWeekday = _.groupBy(events, event => event.start.getDay())
<div
class="stacked" const dayIntervalLayout = _.mapValues(eventsByWeekday, events =>
style={{ layoutIntervals(
'--start': events.map(e => ({
differenceInMinutes( start: differenceInMinutes(e.start, startOfDay(e.start)),
stacked[blockStart][0].start, end: differenceInMinutes(e.end, startOfDay(e.start)),
startOfDay(stacked[blockStart][0].start) data: e,
) - }))
60 * 8, )
}} )
>
{stacked[blockStart].map(event => ( const weekDays = ['Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì']
<Event {...event} />
))} const [currentlyHovered, setCurrentlyHovered] = useState(null)
</div> const element = useRef()
))}
useEffect(() => {
{Object.values(eventsByWeekday).map(dayEvents => { 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)
return () => {
element.current.removeEventListener('mousemove', l)
}
}
}, [element.current])
return ( return (
<div class="day"> <div class="work-week-v-view" ref={element}>
{dayEvents.map(({ name, start, end }) => { <div class="pivot"></div>
return ( <div class="left-header">
<div class="blocks">
<div
class="block skip-border"
style={{
'--start': 9 - 7,
'--size': 2,
}}
>
9:00 &ndash; 11:00
</div>
<div
class="block"
style={{
'--start': 11 - 7,
'--size': 2,
}}
>
11:00 &ndash; 13:00
</div>
<div
class="block skip-border"
style={{
'--start': 14 - 7,
'--size': 2,
}}
>
14:00 &ndash; 16:00
</div>
<div <div
class="event" class="block"
style={{ style={{
'--hue': '--start': 16 - 7,
(Math.abs(hashString('seed3' + name)) % 360) + '--size': 2,
'deg',
'--start':
differenceInMinutes(start, startOfDay(start)) -
60 * 8,
'--size': differenceInMinutes(end, start),
}} }}
> >
{_.startCase(_.lowerCase(name)) 16:00 &ndash; 18:00
.replaceAll('Ii', 'II')
.replaceAll('Iii', 'III')}
</div> </div>
) </div>
})} </div>
{Object.values(dayIntervalLayout).map((layout, index) => (
<div class="day" style={{ '--size': layout.length }}>
<div class="top-header">{weekDays[index]}</div>
<div class="events">
{layout.map((events, stackIndex) => (
<>
{events.map(event => (
<div
class={
'event' +
(currentlyHovered === event.data.name
? ' highlight'
: '')
}
data-event-id={event.data.name}
style={{
'--start': event.start / 60 - 7,
'--stack': stackIndex + 1,
'--size': (event.end - event.start) / 60,
'--hue':
(Math.abs(hashString('seed3' + event.data.name)) %
360) +
'deg',
}}
>
<div class="title">
{_.startCase(_.lowerCase(event.data.name))
.replaceAll('Ii', 'II')
.replaceAll('Iii', 'III')
.replaceAll(/\bE\b/g, 'e')}
</div>
<div class="aula">{event.data.aula}</div>
</div>
))}
</>
))}
{/* Grid Tracks */}
{[1, 3, 5].map(i => (
<div class="grid-line-h" style={{ '--track': i }}></div>
))}
{[6, 8, 10].map(i => (
<div class="grid-line-h" style={{ '--track': i }}></div>
))}
</div>
</div>
))}
</div> </div>
) )
})} }
*/
const CourseView = ({ events }) => { const CourseView = ({ events }) => {
const eventsByCourse = _.groupBy(events, 'name') const eventsByCourse = _.groupBy(events, 'name')
@ -183,7 +265,8 @@ const CourseView = ({ events }) => {
<div class="title"> <div class="title">
{_.startCase(_.lowerCase(name)) {_.startCase(_.lowerCase(name))
.replaceAll('Ii', 'II') .replaceAll('Ii', 'II')
.replaceAll('Iii', 'III')} .replaceAll('Iii', 'III')
.replaceAll(/\bE\b/g, 'e')}
</div> </div>
<div class="docenti">{courseEvents[0].docenti.join(', ')}</div> <div class="docenti">{courseEvents[0].docenti.join(', ')}</div>
<div class="events"> <div class="events">
@ -207,7 +290,8 @@ const CourseView = ({ events }) => {
// //
const viewModeMap = { const viewModeMap = {
'work-week': WorkWeekView, 'work-week-h': WorkWeekView,
'work-week-v': WorkWeekVerticalView,
'course': CourseView, 'course': CourseView,
} }

@ -1,3 +1,5 @@
import { Icon } from './Icon.jsx'
export const Toolbar = ({ mode, setMode }) => { export const Toolbar = ({ mode, setMode }) => {
return ( return (
<div class="toolbar"> <div class="toolbar">
@ -12,16 +14,29 @@ export const Toolbar = ({ mode, setMode }) => {
Corso Corso
</button> </button>
<button <button
class={'radio' + (mode === 'work-week' ? ' selected' : '')} class={'radio' + (mode === 'work-week-v' ? ' selected' : '')}
onClick={() => setMode('work-week')} onClick={() => setMode('work-week-v')}
> >
Settimana Settimana
</button> </button>
<button
class={'radio' + (mode === 'work-week-h' ? ' selected' : '')}
onClick={() => setMode('work-week-h')}
>
Settimana<sup>T</sup>
</button>
</div> </div>
</div> </div>
<div class="item option">Opzione 2</div> <div class="item option">
<div class="item option">Opzione 3</div> <button class="icon">
<div class="item option">Opzione 4</div> <Icon name="filter_list" />
</button>
</div>
<div class="item option">
<button class="icon">
<Icon name="print" />
</button>
</div>
</div> </div>
</div> </div>
) )

@ -5,8 +5,6 @@ import { useEffect, useState } from 'preact/hooks'
import { EventsView } from './components/EventsView.jsx' import { EventsView } from './components/EventsView.jsx'
import { Toolbar } from './components/Toolbar.jsx' import { Toolbar } from './components/Toolbar.jsx'
window._ = _
const App = ({}) => { const App = ({}) => {
const [eventi, setEventi] = useState([]) const [eventi, setEventi] = useState([])
useEffect(() => { useEffect(() => {
@ -40,7 +38,7 @@ const App = ({}) => {
}) })
}, []) }, [])
const [mode, setMode] = useState('work-week') const [mode, setMode] = useState('work-week-v')
return ( return (
<> <>

@ -15,9 +15,11 @@ html {
:root { :root {
--gray-000: #ffffff; --gray-000: #ffffff;
--gray-100: #f8f8f8;
--gray-300: #f0f0f0; --gray-300: #f0f0f0;
--gray-350: #e8e8e8; --gray-350: #e8e8e8;
--gray-500: #d8d8d8; --gray-500: #d8d8d8;
--gray-600: #c0c0c0;
--gray-900: #282828; --gray-900: #282828;
// --accent-100: #e6ceff; // --accent-100: #e6ceff;
@ -29,12 +31,18 @@ html {
--accent-500: hsl(221, 70%, 75%); --accent-500: hsl(221, 70%, 75%);
} }
$device-s-width: 480px;
// Elements // Elements
code { code {
font-size: 90%; font-size: 90%;
} }
sup {
padding-bottom: 0.5rem;
}
button, button,
.button { .button {
display: flex; display: flex;
@ -61,6 +69,10 @@ button,
inset: 0; inset: 0;
background: #00000008; background: #00000008;
} }
&.icon {
padding: 0.5rem;
}
} }
// Components // Components
@ -73,12 +85,18 @@ button,
border-radius: 0; border-radius: 0;
border-right: none; border-right: none;
padding: inherit 0.5rem;
&:first-child { &:first-child {
padding-left: 1rem;
border-top-left-radius: 0.5rem; border-top-left-radius: 0.5rem;
border-bottom-left-radius: 0.5rem; border-bottom-left-radius: 0.5rem;
} }
&:last-child { &:last-child {
padding-right: 1rem;
border-top-right-radius: 0.5rem; border-top-right-radius: 0.5rem;
border-bottom-right-radius: 0.5rem; border-bottom-right-radius: 0.5rem;
border: 2px solid var(--gray-350); border: 2px solid var(--gray-350);
@ -133,10 +151,6 @@ body {
gap: 0.5rem; gap: 0.5rem;
} }
.item {
padding: 0.5rem;
}
} }
.events-view { .events-view {
@ -185,7 +199,7 @@ body {
} }
} }
.workweek-view { .work-week-h-view {
--header-height: 3rem; --header-height: 3rem;
--event-height: 2rem; --event-height: 2rem;
@ -234,7 +248,7 @@ body {
display: grid; display: grid;
grid-template-rows: auto 1fr; grid-template-rows: auto 1fr;
--hour-width: 7rem; --hour-width: 7.5rem;
.header { .header {
display: grid; display: grid;
@ -282,16 +296,20 @@ body {
grid-row: var(--row) / span 1; grid-row: var(--row) / span 1;
grid-column: var(--start) / span var(--size); grid-column: var(--start) / span var(--size);
background: hsl(var(--hue), 90%, 60%); // $color: hsl(var(--hue), 80%, 60%);
$color: var(--gray-000);
background: $color;
color: #000; color: #000;
border-radius: 0.25rem; border-radius: 0.25rem;
border: 1px solid var(--gray-600);
display: flex; display: flex;
padding: 0.4rem; padding: 0.4rem;
font-size: 15px; font-size: 15px;
// font-weight: 500;
width: 100%; width: 100%;
@ -309,11 +327,7 @@ body {
border-radius: 0.25rem; border-radius: 0.25rem;
background: linear-gradient( background: linear-gradient(to top, $color 0%, transparent 25%);
to top,
hsl(var(--hue), 90%, 60%) 0%,
transparent 25%
);
} }
&:hover { &:hover {
@ -340,6 +354,177 @@ body {
} }
} }
} }
.work-week-v-view {
position: relative;
display: grid;
grid-template-columns: max-content repeat(5, auto);
height: 100%;
overflow: scroll;
.pivot {
height: 3rem;
grid-column: 1 / 2;
grid-row: 1 / 2;
position: sticky;
top: 0;
left: 0;
z-index: 2;
background: var(--gray-000);
border-bottom: 1px solid var(--gray-500);
border-right: 1px solid var(--gray-500);
}
.left-header {
grid-column: 1 / 2;
grid-row: 1 / 2;
display: grid;
position: sticky;
left: 0;
border-right: 1px solid var(--gray-500);
background: var(--gray-000);
z-index: 1;
padding-top: 3rem;
.blocks {
display: grid;
grid-template-rows: repeat(12, 6rem);
.block {
grid-row: var(--start) / span var(--size);
display: grid;
place-content: center;
padding: 0.5rem;
font-size: 16px;
font-weight: 400;
border-top: 1px solid var(--gray-500);
@media screen and (max-width: $device-s-width) {
writing-mode: vertical-lr;
}
&:not(.skip-border) {
height: calc(2 * 6rem + 1px);
border-bottom: 1px solid var(--gray-500);
}
}
}
}
.day {
position: relative;
display: flex;
flex-direction: column;
.top-header {
position: sticky;
top: 0;
height: 3rem;
flex: 0 0 auto;
z-index: 1;
grid-column: span var(--size);
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
font-weight: 400;
background: var(--gray-000);
border-bottom: 1px solid var(--gray-500);
}
&:not(:last-child) {
border-right: 1px solid var(--gray-500);
}
.events {
position: relative;
display: grid;
grid-template-columns: repeat(var(--size), auto);
grid-template-rows: repeat(12, 5rem);
padding: 0.5rem;
gap: 1rem 0.5rem;
.event {
grid-row: var(--start) / span var(--size);
grid-column: var(--stack) / span 1;
background: var(--gray-000);
border: 1px solid var(--gray-600);
// border: 4px solid hsl(var(--hue), 80%, 80%);
border-radius: 0.5rem;
display: flex;
flex-direction: column;
gap: 0.25rem;
padding: 0.5rem 1rem 0.5rem 0.5rem;
&.highlight {
background: var(--gray-100);
border-color: var(--gray-500);
}
.title {
font-size: 18px;
font-weight: 400;
}
.aula {
font-size: 16px;
font-weight: 300;
}
}
.grid-line-h {
grid-column: 1 / span var(--size);
grid-row: var(--track) / span 2;
height: 0px;
border-bottom: 1px dashed var(--gray-500);
align-self: center;
position: relative;
left: -0.5rem;
width: calc(100% + 1rem);
z-index: -1;
}
}
}
}
} }
// Utilities // Utilities

Loading…
Cancel
Save