Compare commits

..

No commits in common. "c5a309f4a1969fb7f112baca2c89d4110c22bc80" and "1daec977bab35fa4cef22d417909429312119c41" have entirely different histories.

3 changed files with 15 additions and 214 deletions

View File

@ -51,10 +51,7 @@ export default function CalendarPage() {
const [createDefaults, setCreateDefaults] = useState<CreateDefaults | null>(null);
const { settings } = useSettings();
const firstDayOfWeek = settings?.first_day_of_week ?? 0;
const { data: calendars = [], sharedData, allCalendarIds } = useCalendars({ pollingEnabled: true });
const [currentDate, setCurrentDate] = useState<string>(() => format(new Date(), 'yyyy-MM-dd'));
const [navKey, setNavKey] = useState(0);
const [visibleSharedIds, setVisibleSharedIds] = useState<Set<number>>(new Set());
const calendarContainerRef = useRef<HTMLDivElement>(null);
@ -110,10 +107,6 @@ export default function CalendarPage() {
localStorage.setItem(SIDEBAR_STORAGE_KEY, String(sidebarWidth));
}, [sidebarWidth]);
const handleMiniCalClick = useCallback((dateStr: string) => {
calendarRef.current?.getApi().gotoDate(dateStr);
}, []);
// Location data for event panel
const { data: locations = [] } = useQuery({
queryKey: ['locations'],
@ -520,10 +513,6 @@ export default function CalendarPage() {
setVisibleRange((prev) =>
prev.start === start && prev.end === end ? prev : { start, end }
);
// Track current date anchor for mini calendar sync
setCurrentDate(format(arg.view.currentStart, 'yyyy-MM-dd'));
// Increment nav key so mini calendar clears selection even when month doesn't change
setNavKey((k) => k + 1);
};
const navigatePrev = () => calendarRef.current?.getApi().prev();
@ -602,7 +591,7 @@ export default function CalendarPage() {
return (
<div className="flex h-full overflow-hidden animate-fade-in">
<div className="hidden lg:flex lg:flex-row shrink-0">
<CalendarSidebar ref={sidebarRef} onUseTemplate={handleUseTemplate} onSharedVisibilityChange={setVisibleSharedIds} width={sidebarWidth} onDateClick={handleMiniCalClick} currentDate={currentDate} firstDayOfWeek={firstDayOfWeek} navKey={navKey} />
<CalendarSidebar ref={sidebarRef} onUseTemplate={handleUseTemplate} onSharedVisibilityChange={setVisibleSharedIds} width={sidebarWidth} />
<div
onMouseDown={handleSidebarMouseDown}
className="w-1 shrink-0 cursor-col-resize hover:bg-accent/30 active:bg-accent/50 transition-colors duration-150"
@ -613,7 +602,7 @@ export default function CalendarPage() {
<Sheet open={mobileSidebarOpen} onOpenChange={setMobileSidebarOpen}>
<SheetContent className="w-72 p-0">
<SheetClose onClick={() => setMobileSidebarOpen(false)} />
<CalendarSidebar onUseTemplate={(tmpl) => { setMobileSidebarOpen(false); handleUseTemplate(tmpl); }} onSharedVisibilityChange={setVisibleSharedIds} width={288} onDateClick={(dateStr) => { setMobileSidebarOpen(false); handleMiniCalClick(dateStr); }} currentDate={currentDate} firstDayOfWeek={firstDayOfWeek} navKey={navKey} />
<CalendarSidebar onUseTemplate={(tmpl) => { setMobileSidebarOpen(false); handleUseTemplate(tmpl); }} onSharedVisibilityChange={setVisibleSharedIds} width={288} />
</SheetContent>
</Sheet>
)}
@ -723,12 +712,12 @@ export default function CalendarPage() {
>
<div className="h-full">
<FullCalendar
key={`fc-${firstDayOfWeek}`}
key={`fc-${settings?.first_day_of_week ?? 0}`}
ref={calendarRef}
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]}
initialView="dayGridMonth"
headerToolbar={false}
firstDay={firstDayOfWeek}
firstDay={settings?.first_day_of_week ?? 0}
events={calendarEvents}
editable={true}
selectable={true}

View File

@ -10,19 +10,14 @@ import { Checkbox } from '@/components/ui/checkbox';
import CalendarForm from './CalendarForm';
import TemplateForm from './TemplateForm';
import SharedCalendarSection, { loadVisibility, saveVisibility } from './SharedCalendarSection';
import MiniCalendar from './MiniCalendar';
interface CalendarSidebarProps {
onUseTemplate?: (template: EventTemplate) => void;
onSharedVisibilityChange?: (visibleIds: Set<number>) => void;
width: number;
onDateClick?: (dateStr: string) => void;
currentDate?: string;
firstDayOfWeek?: number;
navKey?: number;
}
const CalendarSidebar = forwardRef<HTMLDivElement, CalendarSidebarProps>(function CalendarSidebar({ onUseTemplate, onSharedVisibilityChange, width, onDateClick, currentDate, firstDayOfWeek, navKey }, ref) {
const CalendarSidebar = forwardRef<HTMLDivElement, CalendarSidebarProps>(function CalendarSidebar({ onUseTemplate, onSharedVisibilityChange, width }, ref) {
const queryClient = useQueryClient();
const { data: calendars = [], sharedData: sharedCalendars = [] } = useCalendars();
const [showForm, setShowForm] = useState(false);
@ -100,35 +95,19 @@ const CalendarSidebar = forwardRef<HTMLDivElement, CalendarSidebarProps>(functio
return (
<div ref={ref} className="shrink-0 border-r bg-card flex flex-col" style={{ width }}>
<div className="h-16 px-4 border-b flex items-center shrink-0">
<div className="h-16 px-4 border-b flex items-center justify-between shrink-0">
<span className="text-sm font-semibold font-heading text-foreground">Calendars</span>
</div>
{onDateClick && (
<div className="border-b shrink-0">
<MiniCalendar
onDateClick={onDateClick}
currentDate={currentDate}
firstDayOfWeek={firstDayOfWeek}
navKey={navKey}
/>
</div>
)}
<div className="flex-1 overflow-y-auto p-3 space-y-4">
{/* Owned calendars list (non-shared only) */}
<div className="space-y-1.5">
<div className="flex items-center justify-between px-2">
<span className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground">
My Calendars
</span>
<Button
variant="ghost"
size="icon"
className="h-5 w-5"
className="h-7 w-7"
onClick={() => { setEditingCalendar(null); setShowForm(true); }}
>
<Plus className="h-3 w-3" />
<Plus className="h-4 w-4" />
</Button>
</div>
<div className="flex-1 overflow-y-auto p-3 space-y-4">
{/* Owned calendars list (non-shared only) */}
<div className="space-y-0.5">
{calendars.filter((c) => !c.is_shared).map((cal) => (
<div
@ -159,7 +138,6 @@ const CalendarSidebar = forwardRef<HTMLDivElement, CalendarSidebarProps>(functio
</div>
))}
</div>
</div>
{/* Shared calendars section -- owned + member */}
{(calendars.some((c) => c.is_shared) || sharedCalendars.length > 0) && (

View File

@ -1,166 +0,0 @@
import { useState, useEffect, useMemo, useCallback, memo } from 'react';
import {
startOfMonth, endOfMonth, startOfWeek, endOfWeek,
eachDayOfInterval, format, isSameDay, isSameMonth, isToday,
addMonths, subMonths, parse,
} from 'date-fns';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import { Button } from '@/components/ui/button';
interface MiniCalendarProps {
onDateClick: (dateStr: string) => void;
currentDate?: string;
firstDayOfWeek?: number;
navKey?: number;
}
const DAY_LABELS = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
function buildGrid(displayed: Date, firstDay: number) {
const monthStart = startOfMonth(displayed);
const monthEnd = endOfMonth(displayed);
const gridStart = startOfWeek(monthStart, { weekStartsOn: firstDay as 0 | 1 | 2 | 3 | 4 | 5 | 6 });
const gridEnd = endOfWeek(monthEnd, { weekStartsOn: firstDay as 0 | 1 | 2 | 3 | 4 | 5 | 6 });
return eachDayOfInterval({ start: gridStart, end: gridEnd });
}
function getOrderedLabels(firstDay: number) {
return [...DAY_LABELS.slice(firstDay), ...DAY_LABELS.slice(0, firstDay)];
}
const MiniCalendar = memo(function MiniCalendar({
onDateClick,
currentDate,
firstDayOfWeek = 0,
navKey,
}: MiniCalendarProps) {
const REF_DATE = useMemo(() => new Date(), []);
const parseDate = useCallback(
(dateStr: string) => parse(dateStr, 'yyyy-MM-dd', REF_DATE),
[REF_DATE]
);
const [displayedMonth, setDisplayedMonth] = useState(() =>
currentDate ? startOfMonth(parseDate(currentDate)) : startOfMonth(new Date())
);
const [selectedDate, setSelectedDate] = useState<string | null>(null);
// Sync displayed month when main calendar navigates across months
useEffect(() => {
if (!currentDate) return;
const incoming = startOfMonth(parseDate(currentDate));
setDisplayedMonth((prev) =>
prev.getTime() === incoming.getTime() ? prev : incoming
);
}, [currentDate, parseDate]);
// Clear selection on any toolbar navigation (today/prev/next)
// navKey increments on every datesSet, even when the month doesn't change
useEffect(() => {
setSelectedDate(null);
}, [navKey]);
const days = useMemo(
() => buildGrid(displayedMonth, firstDayOfWeek),
[displayedMonth, firstDayOfWeek]
);
const orderedLabels = useMemo(
() => getOrderedLabels(firstDayOfWeek),
[firstDayOfWeek]
);
const handlePrev = useCallback(() => setDisplayedMonth((m) => subMonths(m, 1)), []);
const handleNext = useCallback(() => setDisplayedMonth((m) => addMonths(m, 1)), []);
const handleDayClick = useCallback((day: Date) => {
const dateStr = format(day, 'yyyy-MM-dd');
setSelectedDate(dateStr);
setDisplayedMonth((prev) => isSameMonth(day, prev) ? prev : startOfMonth(day));
onDateClick(dateStr);
}, [onDateClick]);
const selectedDateObj = useMemo(
() => selectedDate ? parseDate(selectedDate) : null,
[selectedDate, parseDate]
);
return (
<div className="px-3 pt-3 pb-2 max-w-[280px] mx-auto">
{/* Month header with navigation */}
<div className="flex items-center justify-between mb-1.5">
<Button
variant="ghost"
size="icon"
className="h-6 w-6"
onClick={handlePrev}
>
<ChevronLeft className="h-3.5 w-3.5" />
</Button>
<span className="text-sm font-medium font-heading select-none">
{format(displayedMonth, 'MMMM yyyy')}
</span>
<Button
variant="ghost"
size="icon"
className="h-6 w-6"
onClick={handleNext}
>
<ChevronRight className="h-3.5 w-3.5" />
</Button>
</div>
{/* Day-of-week headers */}
<div className="grid grid-cols-7 mb-0.5">
{orderedLabels.map((label) => (
<span
key={label}
className="text-[10px] text-muted-foreground text-center select-none"
>
{label}
</span>
))}
</div>
{/* Day grid */}
<div className="grid grid-cols-7">
{days.map((day) => {
const isCurrentMonth = isSameMonth(day, displayedMonth);
const today = isToday(day);
const isSelected = selectedDateObj ? isSameDay(day, selectedDateObj) : false;
return (
<button
key={format(day, 'yyyy-MM-dd')}
type="button"
aria-label={format(day, 'EEEE, MMMM d, yyyy')}
onClick={() => handleDayClick(day)}
className={[
'h-7 text-xs flex items-center justify-center rounded-md transition-colors duration-100',
isSelected
? 'font-medium'
: today
? 'font-semibold'
: isCurrentMonth
? 'text-foreground hover:bg-accent/10'
: 'text-muted-foreground/40 hover:bg-accent/10',
].join(' ')}
style={
isSelected
? { backgroundColor: 'hsl(var(--accent-color))', color: 'hsl(var(--accent-foreground))' }
: today && !isSelected
? { backgroundColor: 'hsl(var(--accent-color) / 0.2)', color: 'hsl(var(--accent-color))' }
: undefined
}
>
{format(day, 'd')}
</button>
);
})}
</div>
</div>
);
});
export default MiniCalendar;