fix: update calendar agenda view to auto-scroll to today's events and ensure today's date is included in the groups
This commit is contained in:
@@ -7,7 +7,7 @@ import { Plus } from "lucide-react";
|
|||||||
import {
|
import {
|
||||||
startOfMonth, endOfMonth, startOfWeek, endOfWeek,
|
startOfMonth, endOfMonth, startOfWeek, endOfWeek,
|
||||||
addMonths, subMonths, addWeeks, subWeeks, addDays, subDays,
|
addMonths, subMonths, addWeeks, subWeeks, addDays, subDays,
|
||||||
format, parseISO,
|
startOfDay, format, parseISO,
|
||||||
} from "date-fns";
|
} from "date-fns";
|
||||||
import { useCalendarStore } from "@/stores/calendar-store";
|
import { useCalendarStore } from "@/stores/calendar-store";
|
||||||
import { isCalendarViewMode } from "@/stores/calendar-store";
|
import { isCalendarViewMode } from "@/stores/calendar-store";
|
||||||
@@ -156,11 +156,15 @@ export default function CalendarPage() {
|
|||||||
start: format(d, "yyyy-MM-dd'T'00:00:00"),
|
start: format(d, "yyyy-MM-dd'T'00:00:00"),
|
||||||
end: format(d, "yyyy-MM-dd'T'23:59:59"),
|
end: format(d, "yyyy-MM-dd'T'23:59:59"),
|
||||||
};
|
};
|
||||||
case "agenda":
|
case "agenda": {
|
||||||
|
// Agenda always starts from today at the earliest
|
||||||
|
const today = startOfDay(new Date());
|
||||||
|
const agendaStart = d >= today ? d : today;
|
||||||
return {
|
return {
|
||||||
start: format(d, "yyyy-MM-dd'T'00:00:00"),
|
start: format(agendaStart, "yyyy-MM-dd'T'00:00:00"),
|
||||||
end: format(addDays(d, 30), "yyyy-MM-dd'T'23:59:59"),
|
end: format(addDays(agendaStart, 30), "yyyy-MM-dd'T'23:59:59"),
|
||||||
};
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [selectedDate, normalizedViewMode, firstDayOfWeek]);
|
}, [selectedDate, normalizedViewMode, firstDayOfWeek]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useMemo } from "react";
|
import { useMemo, useRef, useEffect, useCallback } from "react";
|
||||||
import { useTranslations, useFormatter } from "next-intl";
|
import { useTranslations, useFormatter } from "next-intl";
|
||||||
import { format, parseISO, isToday, isTomorrow } from "date-fns";
|
import { format, parseISO, isToday, isTomorrow, startOfDay } from "date-fns";
|
||||||
import { Calendar as CalendarIcon, MapPin, Users } from "lucide-react";
|
import { Calendar as CalendarIcon, MapPin, Users } from "lucide-react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { parseDuration, getEventColor } from "./event-card";
|
import { parseDuration, getEventColor } from "./event-card";
|
||||||
@@ -27,6 +27,7 @@ interface DayGroup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function CalendarAgendaView({
|
export function CalendarAgendaView({
|
||||||
|
selectedDate,
|
||||||
events,
|
events,
|
||||||
calendars,
|
calendars,
|
||||||
onSelectEvent,
|
onSelectEvent,
|
||||||
@@ -43,6 +44,9 @@ export function CalendarAgendaView({
|
|||||||
return map;
|
return map;
|
||||||
}, [calendars]);
|
}, [calendars]);
|
||||||
|
|
||||||
|
const todayRef = useRef<HTMLDivElement>(null);
|
||||||
|
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const grouped = useMemo(() => {
|
const grouped = useMemo(() => {
|
||||||
const sorted = [...events].sort((a, b) =>
|
const sorted = [...events].sort((a, b) =>
|
||||||
new Date(a.start).getTime() - new Date(b.start).getTime()
|
new Date(a.start).getTime() - new Date(b.start).getTime()
|
||||||
@@ -69,10 +73,38 @@ export function CalendarAgendaView({
|
|||||||
} catch { /* skip invalid dates */ }
|
} catch { /* skip invalid dates */ }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Always include today's date in the groups so the view has a "Today" anchor
|
||||||
|
const todayKey = format(new Date(), "yyyy-MM-dd");
|
||||||
|
if (!groupMap.has(todayKey)) {
|
||||||
|
const todayGroup = { date: startOfDay(new Date()), dateKey: todayKey, events: [] as CalendarEvent[] };
|
||||||
|
groupMap.set(todayKey, todayGroup);
|
||||||
|
groups.push(todayGroup);
|
||||||
|
}
|
||||||
|
|
||||||
groups.sort((a, b) => a.date.getTime() - b.date.getTime());
|
groups.sort((a, b) => a.date.getTime() - b.date.getTime());
|
||||||
return groups;
|
return groups;
|
||||||
}, [events]);
|
}, [events]);
|
||||||
|
|
||||||
|
// Auto-scroll to today's section on mount and when selectedDate changes to today
|
||||||
|
const scrollToToday = useCallback(() => {
|
||||||
|
if (todayRef.current) {
|
||||||
|
todayRef.current.scrollIntoView({ block: "start" });
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Scroll to today on mount
|
||||||
|
const frame = requestAnimationFrame(scrollToToday);
|
||||||
|
return () => cancelAnimationFrame(frame);
|
||||||
|
}, [scrollToToday]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Scroll to today when selectedDate changes to today
|
||||||
|
if (isToday(selectedDate)) {
|
||||||
|
scrollToToday();
|
||||||
|
}
|
||||||
|
}, [selectedDate, scrollToToday]);
|
||||||
|
|
||||||
const formatDateHeader = (date: Date): string => {
|
const formatDateHeader = (date: Date): string => {
|
||||||
if (isToday(date)) return t("events.today_header");
|
if (isToday(date)) return t("events.today_header");
|
||||||
if (isTomorrow(date)) return t("events.tomorrow_header");
|
if (isTomorrow(date)) return t("events.tomorrow_header");
|
||||||
@@ -86,19 +118,10 @@ export function CalendarAgendaView({
|
|||||||
return format(date, "HH:mm");
|
return format(date, "HH:mm");
|
||||||
};
|
};
|
||||||
|
|
||||||
if (grouped.length === 0) {
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col items-center justify-center flex-1 text-muted-foreground">
|
|
||||||
<CalendarIcon className="w-12 h-12 mb-3 opacity-30" />
|
|
||||||
<p className="text-sm">{t("events.no_events")}</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex-1 overflow-y-auto">
|
<div className="flex-1 overflow-y-auto" ref={scrollContainerRef}>
|
||||||
{grouped.map((group) => (
|
{grouped.map((group) => (
|
||||||
<div key={group.dateKey}>
|
<div key={group.dateKey} ref={isToday(group.date) ? todayRef : undefined}>
|
||||||
<div className="sticky top-0 bg-muted/80 backdrop-blur-sm px-4 py-2 border-b border-border">
|
<div className="sticky top-0 bg-muted/80 backdrop-blur-sm px-4 py-2 border-b border-border">
|
||||||
<span className={cn(
|
<span className={cn(
|
||||||
"text-sm font-medium",
|
"text-sm font-medium",
|
||||||
@@ -111,6 +134,11 @@ export function CalendarAgendaView({
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{group.events.length === 0 ? (
|
||||||
|
<div className="px-4 py-6 text-center text-sm text-muted-foreground">
|
||||||
|
{t("events.no_events")}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
<div className="divide-y divide-border">
|
<div className="divide-y divide-border">
|
||||||
{group.events.map((ev) => {
|
{group.events.map((ev) => {
|
||||||
const calId = getPrimaryCalendarId(ev);
|
const calId = getPrimaryCalendarId(ev);
|
||||||
@@ -176,6 +204,7 @@ export function CalendarAgendaView({
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -655,6 +655,7 @@ export const useCalendarStore = create<CalendarStore>()(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...mergedState,
|
...mergedState,
|
||||||
|
selectedDate: new Date(),
|
||||||
viewMode: getSafeCalendarViewMode(mergedState.viewMode),
|
viewMode: getSafeCalendarViewMode(mergedState.viewMode),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user