Skip to content

Commit 1bf7e22

Browse files
authored
scevents month mobile ui view (#2108)
1 parent 666d754 commit 1bf7e22

2 files changed

Lines changed: 183 additions & 32 deletions

File tree

src/Pages/Events/CalendarView.js

Lines changed: 175 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,21 @@ function eventDateKey(event) {
2727
return toDateKey(d);
2828
}
2929

30+
function toTimeSortValue(event) {
31+
if (!event?.time) return Number.POSITIVE_INFINITY;
32+
const [hour, minute] = String(event.time).split(':').map(Number);
33+
if (Number.isNaN(hour) || Number.isNaN(minute)) return Number.POSITIVE_INFINITY;
34+
return hour * 60 + minute;
35+
}
36+
37+
function sortEventsForDay(events) {
38+
return [...events].sort((a, b) => {
39+
const timeDelta = toTimeSortValue(a) - toTimeSortValue(b);
40+
if (timeDelta !== 0) return timeDelta;
41+
return String(a.name || '').localeCompare(String(b.name || ''));
42+
});
43+
}
44+
3045
function formatDate(dateStr) {
3146
if (!dateStr) return '';
3247
const d = new Date(`${dateStr}T12:00:00`);
@@ -432,17 +447,18 @@ function EventPopup({ event, onClose, isAdminView, user }) {
432447

433448
// ─── Event pill ───────────────────────────────────────────────────────────────
434449

435-
function EventPill({ event, onSelect, isAdminView }) {
450+
function EventRow({ event, onSelect, isAdminView }) {
436451
const colors = pillColors(event, isAdminView);
437452
const badgeText = getBadgeText(event, isAdminView);
453+
const timeLabel = formatTime(event.time);
438454

439455
return (
440456
<button
441457
type="button"
442458
onClick={() => onSelect(event)}
443459
className={[
444-
'flex w-full items-start gap-1.5 rounded-md border px-1.5 py-1',
445-
'text-left text-[12px] sm:text-[13px] font-semibold leading-tight',
460+
'flex w-full items-center gap-1.5 rounded border px-1.5 py-1',
461+
'text-left text-[11px] font-medium leading-tight',
446462
'transition-all duration-150 hover:border-white/30 hover:brightness-110',
447463
colors.bg,
448464
colors.text,
@@ -451,16 +467,21 @@ function EventPill({ event, onSelect, isAdminView }) {
451467
title={event.name}
452468
>
453469
<span className={['mt-1 h-1.5 w-1.5 shrink-0 rounded-full', colors.dot].join(' ')} />
454-
<div className="min-w-0 flex-1">
455-
<div className="truncate">
456-
{event.name || 'Untitled'}
457-
</div>
458-
{badgeText && (
459-
<div className="truncate pt-0.5 text-[10px] opacity-80">
460-
{badgeText}
461-
</div>
470+
<div className="min-w-0 flex-1 truncate">
471+
{timeLabel && (
472+
<span className="mr-1.5 font-semibold text-white/90">
473+
{timeLabel}
474+
</span>
462475
)}
476+
<span className="truncate">
477+
{event.name || 'Untitled'}
478+
</span>
463479
</div>
480+
{badgeText && (
481+
<span className="hidden rounded border border-current/30 px-1 py-0.5 text-[9px] uppercase tracking-wide opacity-75 sm:inline">
482+
{badgeText}
483+
</span>
484+
)}
464485
</button>
465486
);
466487
}
@@ -485,18 +506,23 @@ function DayLabels() {
485506
// ─── Calendar cell ────────────────────────────────────────────────────────────
486507

487508
function CalCell({ date, isCurrentMonth, isToday, events, onSelectEvent, isAdminView }) {
509+
const sortedEvents = sortEventsForDay(events);
510+
const mobileRows = sortedEvents.slice(0, 2);
511+
const desktopOnlyRows = sortedEvents.slice(2, 4);
512+
const hiddenCount = Math.max(sortedEvents.length - 4, 0);
513+
488514
return (
489515
<div
490516
className={[
491-
'min-h-[120px] sm:min-h-[130px] border-b border-r border-slate-700/60 p-1.5 transition-colors duration-150',
517+
'min-h-[106px] sm:min-h-[124px] border-b border-r border-slate-700/60 p-1.5 transition-colors duration-150',
492518
isCurrentMonth ? '' : 'opacity-30',
493519
isToday ? 'bg-cyan-500/[0.12]' : 'hover:bg-slate-700/35',
494520
].join(' ')}
495521
>
496-
<div className="mb-1 flex justify-end">
522+
<div className="mb-1 flex justify-start">
497523
<span
498524
className={[
499-
'flex h-6 w-6 items-center justify-center rounded-full text-sm font-semibold',
525+
'flex h-6 min-w-6 items-center justify-center rounded-full px-1.5 text-xs font-semibold',
500526
isToday ? 'bg-cyan-400 text-slate-950' : isCurrentMonth ? 'text-slate-200' : 'text-slate-600',
501527
].join(' ')}
502528
>
@@ -505,25 +531,120 @@ function CalCell({ date, isCurrentMonth, isToday, events, onSelectEvent, isAdmin
505531
</div>
506532

507533
<div className="flex flex-col gap-0.5">
508-
{events.slice(0, 3).map((ev) => (
509-
<EventPill
534+
{mobileRows.map((ev) => (
535+
<EventRow
510536
key={ev.id}
511537
event={ev}
512538
onSelect={onSelectEvent}
513539
isAdminView={isAdminView}
514540
/>
515541
))}
516542

517-
{events.length > 3 && (
518-
<span className="pl-1 text-[10px] text-slate-400">
519-
+{events.length - 3} more
543+
{desktopOnlyRows.map((ev) => (
544+
<div key={ev.id} className="hidden sm:block">
545+
<EventRow
546+
event={ev}
547+
onSelect={onSelectEvent}
548+
isAdminView={isAdminView}
549+
/>
550+
</div>
551+
))}
552+
553+
{hiddenCount > 0 && (
554+
<span className="pl-1 text-[10px] font-medium text-slate-400">
555+
+{hiddenCount} more
520556
</span>
521557
)}
522558
</div>
523559
</div>
524560
);
525561
}
526562

563+
function MobileMonthAgenda({ monthEvents, onSelectEvent, isAdminView }) {
564+
if (monthEvents.length === 0) {
565+
return (
566+
<div className="px-4 py-8 text-center text-sm text-slate-400">
567+
No events scheduled this month.
568+
</div>
569+
);
570+
}
571+
572+
const groupedEvents = monthEvents.reduce((groups, item) => {
573+
const lastGroup = groups[groups.length - 1];
574+
if (!lastGroup || lastGroup.dateKey !== item.dateKey) {
575+
groups.push({ dateKey: item.dateKey, events: [item.event] });
576+
return groups;
577+
}
578+
lastGroup.events.push(item.event);
579+
return groups;
580+
}, []);
581+
582+
return (
583+
<div className="space-y-3 px-3 pb-4 pt-2">
584+
{groupedEvents.map(({ dateKey, events }) => {
585+
const [year, month, day] = dateKey.split('-');
586+
const date = new Date(Number(year), Number(month) - 1, Number(day));
587+
const weekday = date.toLocaleDateString('en-US', { weekday: 'short' }).toUpperCase();
588+
const dayOfMonth = String(date.getDate());
589+
590+
return (
591+
<div key={dateKey} className="flex items-start gap-2">
592+
<div className="w-[44px] shrink-0 pt-1 text-center">
593+
<div className="text-[10px] font-bold uppercase tracking-wider text-slate-400">
594+
{weekday}
595+
</div>
596+
<div className="text-xl font-semibold leading-6 text-slate-100">
597+
{dayOfMonth}
598+
</div>
599+
</div>
600+
601+
<div className="min-w-0 flex-1 space-y-1.5">
602+
{events.map((event) => {
603+
const colors = pillColors(event, isAdminView);
604+
const badgeText = getBadgeText(event, isAdminView);
605+
const timeLabel = formatTime(event.time) || 'All day';
606+
607+
return (
608+
<button
609+
key={`${dateKey}-${event.id || event._id || event.name}`}
610+
type="button"
611+
onClick={() => onSelectEvent(event)}
612+
className={[
613+
'flex w-full items-end justify-between gap-3 rounded-lg border px-3 py-2.5 text-left transition-all',
614+
'hover:border-white/30 hover:brightness-110',
615+
colors.bg,
616+
colors.text,
617+
colors.border,
618+
].join(' ')}
619+
>
620+
<div className="min-w-0 flex-1">
621+
<div className="whitespace-normal break-words text-sm font-semibold leading-snug">
622+
{event.name || 'Untitled Event'}
623+
</div>
624+
<div className="pt-1 text-xs font-semibold text-white/85">
625+
{timeLabel}
626+
</div>
627+
</div>
628+
629+
<div className="min-w-[92px] text-right">
630+
<div className="whitespace-normal break-words text-xs opacity-85">
631+
{event.location || '\u00A0'}
632+
</div>
633+
<div className="pt-1 text-[10px] uppercase tracking-wide opacity-80">
634+
{badgeText || '\u00A0'}
635+
</div>
636+
</div>
637+
</button>
638+
);
639+
})}
640+
</div>
641+
</div>
642+
);
643+
})}
644+
</div>
645+
);
646+
}
647+
527648
// ─── Main export ──────────────────────────────────────────────────────────────
528649

529650
export default function CalendarView({ events, isAdminView = false, user, canCreateEvent = false }) {
@@ -539,6 +660,9 @@ export default function CalendarView({ events, isAdminView = false, user, canCre
539660
if (!map[key]) map[key] = [];
540661
map[key].push(ev);
541662
});
663+
Object.keys(map).forEach((key) => {
664+
map[key] = sortEventsForDay(map[key]);
665+
});
542666
return map;
543667
}, [events]);
544668

@@ -577,6 +701,17 @@ export default function CalendarView({ events, isAdminView = false, user, canCre
577701
.filter((c) => c.isCurrentMonth)
578702
.reduce((sum, c) => sum + c.events.length, 0);
579703

704+
const monthEvents = useMemo(() => {
705+
return cells
706+
.filter((c) => c.isCurrentMonth && c.events.length > 0)
707+
.flatMap((c) =>
708+
c.events.map((event) => ({
709+
event,
710+
dateKey: c.key,
711+
}))
712+
);
713+
}, [cells]);
714+
580715
return (
581716
<>
582717
{selectedEvent && (
@@ -588,7 +723,7 @@ export default function CalendarView({ events, isAdminView = false, user, canCre
588723
/>
589724
)}
590725

591-
<div className="overflow-hidden rounded-xl border border-slate-500/50 bg-slate-800/85 shadow-[0_0_0_1px_rgba(148,163,184,0.05)]">
726+
<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">
592727
<div className="flex flex-wrap items-start justify-between gap-3 border-b border-slate-600/50 px-5 py-4">
593728
<div className="space-y-3">
594729
<div className="flex flex-wrap items-center gap-3">
@@ -675,17 +810,27 @@ export default function CalendarView({ events, isAdminView = false, user, canCre
675810
</div>
676811
</div>
677812

678-
<DayLabels />
813+
<div className="min-h-0 flex-1 overflow-y-auto sm:hidden">
814+
<MobileMonthAgenda
815+
monthEvents={monthEvents}
816+
onSelectEvent={setSelectedEvent}
817+
isAdminView={isAdminView}
818+
/>
819+
</div>
679820

680-
<div className="grid grid-cols-7 [&>*:nth-child(7n)]:border-r-0">
681-
{cells.map((cell) => (
682-
<CalCell
683-
key={cell.key}
684-
{...cell}
685-
onSelectEvent={setSelectedEvent}
686-
isAdminView={isAdminView}
687-
/>
688-
))}
821+
<div className="hidden sm:block">
822+
<DayLabels />
823+
824+
<div className="grid grid-cols-7 [&>*:nth-child(7n)]:border-r-0">
825+
{cells.map((cell) => (
826+
<CalCell
827+
key={cell.key}
828+
{...cell}
829+
onSelectEvent={setSelectedEvent}
830+
isAdminView={isAdminView}
831+
/>
832+
))}
833+
</div>
689834
</div>
690835

691836
{isAdminView ? (

src/Pages/Events/Events.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,12 @@ export default function EventsPage() {
283283
const canCreateEvent = user?.accessLevel >= membershipState.OFFICER;
284284
const visibleEvents = events.filter((event) => canUserSeeEvent(event, user));
285285
const isAdminView = canCreateEvent;
286+
const pageContainerClass = isAdminView
287+
? 'relative h-dvh overflow-hidden bg-gradient-to-r from-gray-800 to-gray-600 text-white'
288+
: 'relative h-[calc(100dvh-4rem)] overflow-hidden bg-gradient-to-r from-gray-800 to-gray-600 text-white';
289+
const calendarContainerClass = isAdminView
290+
? 'relative h-full w-full overflow-hidden px-3 py-4 sm:px-4 sm:py-5 lg:px-5'
291+
: 'relative mx-auto h-full max-w-[120rem] overflow-y-auto px-4 py-6 sm:px-6 sm:py-8 lg:px-10';
286292

287293
useEffect(() => {
288294
async function fetchEvents() {
@@ -304,15 +310,15 @@ export default function EventsPage() {
304310
}, []);
305311

306312
return (
307-
<div className="relative min-h-screen overflow-hidden bg-gradient-to-r from-gray-800 to-gray-600 text-white">
313+
<div className={pageContainerClass}>
308314
{/* Ambient blobs — unchanged from original */}
309315
<div className="pointer-events-none absolute inset-0">
310316
<div className="absolute -top-24 left-[-8rem] h-[22rem] w-[22rem] rounded-full bg-sky-400/10 blur-3xl" />
311317
<div className="absolute right-[-8rem] top-[10rem] h-[24rem] w-[24rem] rounded-full bg-indigo-500/10 blur-3xl" />
312318
</div>
313319

314320
{/* ── Calendar area ── */}
315-
<div className="relative mx-auto max-w-[120rem] px-4 py-8 sm:px-6 lg:px-10">
321+
<div className={calendarContainerClass}>
316322
{isLoading && (
317323
<div className="py-16 text-center text-lg text-gray-300">
318324
Loading events...

0 commit comments

Comments
 (0)