Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 28 additions & 36 deletions src/Pages/Events/Calendar/CalendarHeader.js
Original file line number Diff line number Diff line change
@@ -1,50 +1,38 @@
import { Link } from 'react-router-dom';
import { ChevronLeft, ChevronRight, PlusIcon } from '../EventIcons';
import { MONTHS, YEAR_RANGE } from './calendarConstants';
import { ChevronDown, ChevronLeft, ChevronRight, PlusIcon } from '../EventIcons';
import { VIEW_MODES } from './calendarConstants';

export function CalendarHeader({
month,
year,
monthEventCount,
view,
onViewChange,
title,
eventCount,
countLabel,
canCreateEvent,
onMonthChange,
onYearChange,
onTodayClick,
onPreviousMonth,
onNextMonth,
onPrevious,
onNext,
}) {
return (
<div className="flex flex-wrap items-start justify-between gap-3 px-5 py-4 border-b border-slate-600/50">
<div className="space-y-3">
<div className="flex flex-wrap items-center gap-3">
<div className="relative w-28 sm:w-36">
<select
value={month}
onChange={onMonthChange}
aria-label="Select month"
value={view}
onChange={(e) => onViewChange(e.target.value)}
aria-label="Select calendar view"
className="h-10 w-full appearance-none rounded-lg border border-slate-400/40 bg-slate-800 px-4 pr-10 text-[14px] font-semibold text-slate-100 transition hover:border-slate-300 hover:bg-slate-700 focus:outline-none focus:ring-2 focus:ring-cyan-400"
>
{MONTHS.map((monthName, index) => (
<option key={monthName} value={index} className="bg-slate-900">
{monthName}
</option>
))}
</select>
</div>

<div className="relative w-24">
<select
value={year}
onChange={onYearChange}
aria-label="Select year"
className="h-10 w-full appearance-none rounded-lg border border-slate-400/40 bg-slate-800 px-4 pr-10 text-[14px] font-semibold text-slate-100 transition hover:border-slate-300 hover:bg-slate-700 focus:outline-none focus:ring-2 focus:ring-cyan-400"
>
{YEAR_RANGE.map((yearOption) => (
<option key={yearOption} value={yearOption} className="bg-slate-900">
{yearOption}
{VIEW_MODES.map(({ label, value }) => (
<option key={value} value={value} className="bg-slate-900">
{label}
</option>
))}
</select>
<div className="pointer-events-none absolute inset-y-0 right-4 flex items-center text-slate-300">
<ChevronDown />
</div>
</div>

<button
Expand All @@ -56,11 +44,11 @@ export function CalendarHeader({
</div>

<p className="text-sm font-medium tracking-wide text-slate-300">
{monthEventCount} event{monthEventCount !== 1 ? 's' : ''} this month
{eventCount} event{eventCount !== 1 ? 's' : ''} {countLabel}
</p>
</div>

<div className="flex items-center gap-2">
<div className="flex items-center gap-[6px]">
{canCreateEvent && (
<Link
to="/events/create"
Expand All @@ -71,17 +59,21 @@ export function CalendarHeader({
</Link>
)}

<span className="mr-[6px] text-[14px] font-semibold text-slate-100 whitespace-nowrap">
{title}
</span>

<button
onClick={onPreviousMonth}
aria-label="Previous month"
onClick={onPrevious}
aria-label={`Previous ${view}`}
className="flex items-center justify-center w-10 h-10 transition border rounded-lg border-slate-400/40 bg-slate-800 text-slate-100 hover:border-slate-300 hover:bg-slate-700 hover:text-white"
>
<ChevronLeft />
</button>

<button
onClick={onNextMonth}
aria-label="Next month"
onClick={onNext}
aria-label={`Next ${view}`}
className="flex items-center justify-center w-10 h-10 transition border rounded-lg border-slate-400/40 bg-slate-800 text-slate-100 hover:border-slate-300 hover:bg-slate-700 hover:text-white"
>
<ChevronRight />
Expand Down
108 changes: 76 additions & 32 deletions src/Pages/Events/Calendar/CalendarView.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
import { useState, useMemo } from 'react';
import { toDateKey } from '../eventUtils';
import { eventDateKey, sortEventsForDay } from './calendarUtils';
import {
countLabel,
eventDateKey,
sortEventsForDay,
stepCursor,
viewTitle,
visibleRange,
} from './calendarUtils';
import { EventPopup } from './EventPopup';
import { CalendarHeader } from './CalendarHeader';
import { CalendarGrid } from './CalendarGrid';
import { MobileMonthAgenda } from './MobileMonthAgenda';
import { CalendarLegend } from './CalendarLegend';
import { TimeGrid } from './TimeGrid';
import { YearView } from './YearView';

export default function CalendarView({
events,
Expand All @@ -14,6 +23,9 @@ export default function CalendarView({
canCreateEvent = false,
cursor,
setCursor,
view,
onViewChange,
onYearMonthSelect,
}) {
const today = useMemo(() => new Date(), []);
const [selectedEvent, setSelectedEvent] = useState(null);
Expand All @@ -35,16 +47,6 @@ export default function CalendarView({
const year = cursor.getFullYear();
const month = cursor.getMonth();

function handleMonthChange(e) {
const nextMonth = Number(e.target.value);
setCursor(new Date(year, nextMonth, 1));
}

function handleYearChange(e) {
const nextYear = Number(e.target.value);
setCursor(new Date(nextYear, month, 1));
}

const firstDayOfMonth = new Date(year, month, 1).getDay();
const daysInMonth = new Date(year, month + 1, 0).getDate();
const totalCells = Math.ceil((firstDayOfMonth + daysInMonth) / 7) * 7;
Expand All @@ -63,9 +65,27 @@ export default function CalendarView({
};
});

const monthEventCount = cells
.filter((c) => c.isCurrentMonth)
.reduce((sum, c) => sum + c.events.length, 0);
const { startDate, endDate } = visibleRange(view, cursor);
const eventCount = Object.entries(eventsByDate).reduce((sum, [key, dayEvents]) => {
return key >= startDate && key <= endDate ? sum + dayEvents.length : sum;
}, 0);
const cursorKey = toDateKey(cursor);
const dayViewDays = [{
date: cursor,
key: cursorKey,
isToday: cursorKey === todayKey,
events: eventsByDate[cursorKey] || [],
}];
const weekViewDays = Array.from({ length: 7 }, (_, dayOffset) => {
const date = new Date(year, month, cursor.getDate() - cursor.getDay() + dayOffset);
const key = toDateKey(date);
return {
date,
key,
isToday: key === todayKey,
events: eventsByDate[key] || [],
};
});

const monthEvents = useMemo(() => {
return cells
Expand All @@ -89,32 +109,56 @@ export default function CalendarView({
/>
)}

<div className="flex h-full max-h-full flex-col overflow-hidden rounded-xl border border-slate-500/50 bg-slate-800/85 shadow-[0_0_0_1px_rgba(148,163,184,0.05)] sm:h-auto sm:max-h-none">
<div className={view === 'month'
? 'flex h-full max-h-full flex-col overflow-hidden rounded-xl border border-slate-500/50 bg-slate-800/85 shadow-[0_0_0_1px_rgba(148,163,184,0.05)] sm:h-auto sm:max-h-none'
: 'flex h-full max-h-full flex-col overflow-hidden rounded-xl border border-slate-500/50 bg-slate-800/85 shadow-[0_0_0_1px_rgba(148,163,184,0.05)]'}>
<CalendarHeader
month={month}
year={year}
monthEventCount={monthEventCount}
view={view}
onViewChange={onViewChange}
title={viewTitle(view, cursor)}
eventCount={eventCount}
countLabel={countLabel(view)}
canCreateEvent={canCreateEvent}
onMonthChange={handleMonthChange}
onYearChange={handleYearChange}
onTodayClick={() => setCursor(new Date(today.getFullYear(), today.getMonth(), 1))}
onPreviousMonth={() => setCursor(new Date(year, month - 1, 1))}
onNextMonth={() => setCursor(new Date(year, month + 1, 1))}
onTodayClick={() => {
const day = view === 'day' || view === 'week' ? today.getDate() : 1;
setCursor(new Date(today.getFullYear(), today.getMonth(), day));
}}
onPrevious={() => setCursor(stepCursor(view, cursor, -1))}
onNext={() => setCursor(stepCursor(view, cursor, 1))}
/>

<div className="flex-1 min-h-0 overflow-y-auto sm:hidden">
<MobileMonthAgenda
monthEvents={monthEvents}
{view === 'day' || view === 'week' ? (
<TimeGrid
days={view === 'day' ? dayViewDays : weekViewDays}
onSelectEvent={setSelectedEvent}
isAdminView={isAdminView}
/>
</div>
) : view === 'year' ? (
<YearView
year={year}
activeMonth={month}
eventsByDate={eventsByDate}
onSelectEvent={setSelectedEvent}
onSelectMonth={onYearMonthSelect}
isAdminView={isAdminView}
/>
) : (
<>
<div className="flex-1 min-h-0 overflow-y-auto sm:hidden">
<MobileMonthAgenda
monthEvents={monthEvents}
onSelectEvent={setSelectedEvent}
isAdminView={isAdminView}
/>
</div>

<CalendarGrid
cells={cells}
onSelectEvent={setSelectedEvent}
isAdminView={isAdminView}
/>
<CalendarGrid
cells={cells}
onSelectEvent={setSelectedEvent}
isAdminView={isAdminView}
/>
</>
)}

<CalendarLegend isAdminView={isAdminView} />
</div>
Expand Down
Loading