Add UI for day view

This commit is contained in:
Bruno Bernardino
2024-03-17 20:00:36 +00:00
parent f87a4ab0f1
commit bd48e26b64

View File

@@ -30,13 +30,11 @@ export default function MainCalendar({ initialCalendars, initialCalendarEvents,
const dateFormat = new Intl.DateTimeFormat('en-GB', { year: 'numeric', month: 'long' }); const dateFormat = new Intl.DateTimeFormat('en-GB', { year: 'numeric', month: 'long' });
const hourFormat = new Intl.DateTimeFormat('en-GB', { hour12: false, hour: '2-digit', minute: '2-digit' }); const hourFormat = new Intl.DateTimeFormat('en-GB', { hour12: false, hour: '2-digit', minute: '2-digit' });
const eventDateFormat = new Intl.DateTimeFormat('en-GB', { const dayFormat = new Intl.DateTimeFormat('en-GB', {
year: 'numeric', weekday: 'long',
month: 'long',
day: 'numeric', day: 'numeric',
hour12: false, month: 'long',
hour: '2-digit', year: 'numeric',
minute: '2-digit',
}); });
const allDayEventDateFormat = new Intl.DateTimeFormat('en-GB', { year: 'numeric', month: 'long', day: 'numeric' }); const allDayEventDateFormat = new Intl.DateTimeFormat('en-GB', { year: 'numeric', month: 'long', day: 'numeric' });
const today = new Date().toISOString().substring(0, 10); const today = new Date().toISOString().substring(0, 10);
@@ -331,7 +329,26 @@ export default function MainCalendar({ initialCalendars, initialCalendarEvents,
const visibleCalendarEvents = calendarEvents.value; const visibleCalendarEvents = calendarEvents.value;
// TODO: Send in / consider user timezone // TODO: Send in / consider user timezone
const weeks = getWeeksForMonth(new Date(startDate)); const weeks = view === 'month' ? getWeeksForMonth(new Date(startDate)) : [];
const hours: { date: Date; isCurrentHour: boolean }[] = view === 'day'
? Array.from({ length: 24 }).map((_, index) => {
const hourNumber = index;
const date = new Date(startDate);
date.setHours(hourNumber);
const shortIsoDate = date.toISOString().substring(0, 10);
const isCurrentHour = shortIsoDate === today && new Date().getHours() === hourNumber;
return {
date,
isCurrentHour,
};
})
: [];
// TODO: days with hours
return ( return (
<> <>
@@ -515,8 +532,101 @@ export default function MainCalendar({ initialCalendars, initialCalendarEvents,
<section class='mx-auto max-w-7xl my-8'> <section class='mx-auto max-w-7xl my-8'>
{view === 'day' {view === 'day'
? ( ? (
<section> <section class='shadow-md lg:flex lg:flex-auto lg:flex-col rounded-md'>
TODO: Build day view <section class='border-b border-slate-500 bg-slate-700 text-center text-base font-semibold text-white lg:flex-none rounded-t-md'>
<div class='flex justify-center bg-gray-900 py-2 rounded-t-md'>
<span>{dayFormat.format(new Date(startDate))}</span>
</div>
</section>
<section class='flex bg-slate-500 text-sm text-white lg:flex-auto rounded-b-md'>
<section class='w-full rounded-b-md'>
{hours.map((hour, hourIndex) => {
const shortIsoDate = hour.date.toISOString().substring(0, 10);
const startHourDate = new Date(shortIsoDate);
startHourDate.setHours(hour.date.getHours());
const endHourDate = new Date(shortIsoDate);
endHourDate.setHours(hour.date.getHours());
endHourDate.setMinutes(59);
endHourDate.setSeconds(59);
endHourDate.setMilliseconds(999);
const isLastHour = hourIndex === 23;
const hourEvents = calendarEvents.value.filter((calendarEvent) => {
const eventStartDate = new Date(calendarEvent.start_date);
const eventEndDate = new Date(calendarEvent.end_date);
eventEndDate.setSeconds(eventEndDate.getSeconds() - 1); // Take one second back so events don't bleed into the next hour
// Event starts and ends on this hour
if (eventStartDate >= startHourDate && eventEndDate <= endHourDate) {
return true;
}
// Event starts before and ends after this hour
if (eventStartDate <= startHourDate && eventEndDate >= endHourDate) {
return true;
}
// Event starts on and ends after this hour
if (
eventStartDate >= startHourDate && eventStartDate <= endHourDate && eventEndDate >= endHourDate
) {
return true;
}
// Event starts before and ends on this hour
if (
eventStartDate <= startHourDate && eventEndDate >= startHourDate && eventEndDate <= endHourDate
) {
return true;
}
return false;
});
return (
<section
class={`relative ${hour.isCurrentHour ? 'bg-slate-600' : 'bg-slate-700'} min-h-16 px-3 py-2 ${
hour.isCurrentHour ? '' : 'text-slate-100'
} ${isLastHour ? 'rounded-b-md' : ''} border-b border-b-slate-600`}
>
<time datetime={startHourDate.toISOString()}>
{hourFormat.format(startHourDate)}
</time>
{hourEvents.length > 0
? (
<ol class='mt-2'>
{hourEvents.map((hourEvent) => (
<li class='mb-1'>
<a
href='javascript:void(0);'
class={`flex px-2 py-2 rounded-md hover:no-underline hover:opacity-60 ${
visibleCalendars.find((calendar) => calendar.id === hourEvent.calendar_id)
?.color || 'bg-gray-700'
}`}
onClick={() => openEvent.value = hourEvent}
>
<time
datetime={new Date(hourEvent.start_date).toISOString()}
class='mr-2 flex-none text-slate-100 block'
>
{hourFormat.format(new Date(hourEvent.start_date))}
</time>
<p class='flex-auto truncate font-medium text-white'>
{hourEvent.title}
</p>
</a>
</li>
))}
</ol>
)
: null}
</section>
);
})}
</section>
</section>
</section> </section>
) )
: null} : null}
@@ -529,126 +639,142 @@ export default function MainCalendar({ initialCalendars, initialCalendarEvents,
: null} : null}
{view === 'month' {view === 'month'
? ( ? (
<section> <section class='shadow-md lg:flex lg:flex-auto lg:flex-col rounded-md'>
<section class='shadow-md lg:flex lg:flex-auto lg:flex-col rounded-md'> <section class='grid grid-cols-7 gap-px border-b border-slate-500 bg-slate-700 text-center text-xs font-semibold text-white lg:flex-none rounded-t-md'>
<section class='grid grid-cols-7 gap-px border-b border-slate-500 bg-slate-700 text-center text-xs font-semibold text-white lg:flex-none rounded-t-md'> <div class='flex justify-center bg-gray-900 py-2 rounded-tl-md'>
<div class='flex justify-center bg-gray-900 py-2 rounded-tl-md'> <span>Mon</span>
<span>M</span> </div>
<span class='sr-only sm:not-sr-only'>on</span> <div class='flex justify-center bg-gray-900 py-2'>
</div> <span>Tue</span>
<div class='flex justify-center bg-gray-900 py-2'> </div>
<span>T</span> <div class='flex justify-center bg-gray-900 py-2'>
<span class='sr-only sm:not-sr-only'>ue</span> <span>Wed</span>
</div> </div>
<div class='flex justify-center bg-gray-900 py-2'> <div class='flex justify-center bg-gray-900 py-2'>
<span>W</span> <span>Thu</span>
<span class='sr-only sm:not-sr-only'>ed</span> </div>
</div> <div class='flex justify-center bg-gray-900 py-2'>
<div class='flex justify-center bg-gray-900 py-2'> <span>Fri</span>
<span>T</span> </div>
<span class='sr-only sm:not-sr-only'>hu</span> <div class='flex justify-center bg-gray-900 py-2'>
</div> <span>Sat</span>
<div class='flex justify-center bg-gray-900 py-2'> </div>
<span>F</span> <div class='flex justify-center bg-gray-900 py-2 rounded-tr-md'>
<span class='sr-only sm:not-sr-only'>ri</span> <span>Sun</span>
</div> </div>
<div class='flex justify-center bg-gray-900 py-2'> </section>
<span>S</span> <section class='flex bg-slate-500 text-xs text-white lg:flex-auto rounded-b-md'>
<span class='sr-only sm:not-sr-only'>at</span> <section class='w-full grid lg:grid-cols-7 lg:grid-rows-5 lg:gap-px rounded-b-md'>
</div> {weeks.map((week, weekIndex) =>
<div class='flex justify-center bg-gray-900 py-2 rounded-tr-md'> week.map((day, dayIndex) => {
<span>S</span> const shortIsoDate = day.date.toISOString().substring(0, 10);
<span class='sr-only sm:not-sr-only'>un</span>
</div>
</section>
<section class='flex bg-slate-500 text-xs leading-6 text-white lg:flex-auto rounded-b-md'>
<section class='w-full grid lg:grid-cols-7 lg:grid-rows-5 lg:gap-px rounded-b-md'>
{weeks.map((week, weekIndex) =>
week.map((day, dayIndex) => {
const shortIsoDate = day.date.toISOString().substring(0, 10);
const startDayDate = new Date(shortIsoDate); const startDayDate = new Date(shortIsoDate);
const endDayDate = new Date(shortIsoDate); const endDayDate = new Date(shortIsoDate);
endDayDate.setHours(23); endDayDate.setHours(23);
endDayDate.setMinutes(59); endDayDate.setMinutes(59);
endDayDate.setSeconds(59); endDayDate.setSeconds(59);
endDayDate.setMilliseconds(999); endDayDate.setMilliseconds(999);
const isBottomLeftDay = weekIndex === weeks.length - 1 && dayIndex === 0; const isBottomLeftDay = weekIndex === weeks.length - 1 && dayIndex === 0;
const isBottomRightDay = weekIndex === weeks.length - 1 && dayIndex === week.length - 1; const isBottomRightDay = weekIndex === weeks.length - 1 && dayIndex === week.length - 1;
const isToday = today === shortIsoDate; const isToday = today === shortIsoDate;
// TODO: Consider events that span multiple days const dayEvents = calendarEvents.value.filter((calendarEvent) => {
const dayEvents = calendarEvents.value.filter((calendarEvent) => const eventStartDate = new Date(calendarEvent.start_date);
new Date(calendarEvent.start_date) >= startDayDate && const eventEndDate = new Date(calendarEvent.end_date);
new Date(calendarEvent.end_date) <= endDayDate
);
return ( // Event starts and ends on this day
<section if (eventStartDate >= startDayDate && eventEndDate <= endDayDate) {
class={`relative ${day.isSameMonth ? 'bg-slate-600' : 'bg-slate-700'} min-h-16 px-3 py-2 ${ return true;
day.isSameMonth ? '' : 'text-slate-100' }
} ${isBottomLeftDay ? 'rounded-bl-md' : ''} ${isBottomRightDay ? 'rounded-br-md' : ''}`}
// Event starts before and ends after this day
if (eventStartDate <= startDayDate && eventEndDate >= endDayDate) {
return true;
}
// Event starts on and ends after this day
if (
eventStartDate >= startDayDate && eventStartDate <= endDayDate && eventEndDate >= endDayDate
) {
return true;
}
// Event starts before and ends on this day
if (
eventStartDate <= startDayDate && eventEndDate >= startDayDate && eventEndDate <= endDayDate
) {
return true;
}
return false;
});
return (
<section
class={`relative ${day.isSameMonth ? 'bg-slate-600' : 'bg-slate-700'} min-h-16 px-3 py-2 ${
day.isSameMonth ? '' : 'text-slate-100'
} ${isBottomLeftDay ? 'rounded-bl-md' : ''} ${isBottomRightDay ? 'rounded-br-md' : ''}`}
>
<time
datetime={shortIsoDate}
class={`${
isToday
? 'flex h-6 w-6 items-center justify-center rounded-full bg-[#51A4FB] font-semibold'
: ''
}`}
> >
<time {day.date.getDate()}
datetime={shortIsoDate} </time>
class={`${ {dayEvents.length > 0
isToday ? (
? 'flex h-6 w-6 items-center justify-center rounded-full bg-[#51A4FB] font-semibold' <ol class='mt-2'>
: '' {[...dayEvents].slice(0, 2).map((dayEvent) => (
}`} <li class='mb-1'>
> <a
{day.date.getDate()} href='javascript:void(0);'
</time> class={`flex px-2 py-1 rounded-md hover:no-underline hover:opacity-60 ${
{dayEvents.length > 0 visibleCalendars.find((calendar) => calendar.id === dayEvent.calendar_id)
? ( ?.color || 'bg-gray-700'
<ol class='mt-2'> }`}
{[...dayEvents].slice(0, 2).map((dayEvent) => ( onClick={() => openEvent.value = dayEvent}
>
<time
datetime={new Date(dayEvent.start_date).toISOString()}
class='mr-2 flex-none text-slate-100 block'
>
{hourFormat.format(new Date(dayEvent.start_date))}
</time>
<p class='flex-auto truncate font-medium text-white'>
{dayEvent.title}
</p>
</a>
</li>
))}
{dayEvents.length > 2
? (
<li class='mb-1'> <li class='mb-1'>
<a <a
href='javascript:void(0);' href={`/calendar/view=day&startDate=${shortIsoDate}`}
class={`flex px-2 py-0 rounded-md hover:no-underline hover:opacity-60 ${ class='flex bg-gray-700 px-2 py-1 rounded-md hover:no-underline hover:opacity-60'
visibleCalendars.find((calendar) => calendar.id === dayEvent.calendar_id) target='_blank'
?.color || 'bg-gray-700'
}`}
onClick={() => openEvent.value = dayEvent}
> >
<time
datetime={new Date(dayEvent.start_date).toISOString()}
class='mr-2 flex-none text-slate-100 block'
>
{hourFormat.format(new Date(dayEvent.start_date))}
</time>
<p class='flex-auto truncate font-medium text-white'> <p class='flex-auto truncate font-medium text-white'>
{dayEvent.title} ...{dayEvents.length - 2} more event{dayEvents.length - 2 === 1 ? '' : 's'}
</p> </p>
</a> </a>
</li> </li>
))} )
{dayEvents.length > 2 : null}
? ( </ol>
<li class='mb-1'> )
<a : null}
href={`/calendar/view=day&startDate=${shortIsoDate}`} </section>
class='flex bg-gray-700 px-2 py-0 rounded-md hover:no-underline hover:opacity-60' );
target='_blank' })
> )}
<p class='flex-auto truncate font-medium text-white'>
...{dayEvents.length - 2} more event{dayEvents.length - 2 === 1 ? '' : 's'}
</p>
</a>
</li>
)
: null}
</ol>
)
: null}
</section>
);
})
)}
</section>
</section> </section>
</section> </section>
</section> </section>