diff --git a/frontend/src/components/calendar/CalendarPage.tsx b/frontend/src/components/calendar/CalendarPage.tsx index dc58d13..dff063a 100644 --- a/frontend/src/components/calendar/CalendarPage.tsx +++ b/frontend/src/components/calendar/CalendarPage.tsx @@ -187,12 +187,13 @@ export default function CalendarPage() { return () => cancelAnimationFrame(rafId); }, [panelOpen]); - // Scroll wheel navigation in month view + // Scroll wheel navigation in month view (disabled when detail panel is open) useEffect(() => { const el = calendarContainerRef.current; if (!el) return; let debounceTimer: ReturnType | null = null; const handleWheel = (e: WheelEvent) => { + if (panelOpen) return; // Skip wheel navigation on touch devices (let them scroll normally) if ('ontouchstart' in window) return; const api = calendarRef.current?.getApi(); @@ -207,7 +208,7 @@ export default function CalendarPage() { }; el.addEventListener('wheel', handleWheel, { passive: false }); return () => el.removeEventListener('wheel', handleWheel); - }, []); + }, [panelOpen]); // AW-2: Track visible date range for scoped event fetching // W-02 fix: Initialize from current month to avoid unscoped first fetch diff --git a/frontend/src/components/calendar/EventDetailPanel.tsx b/frontend/src/components/calendar/EventDetailPanel.tsx index b8e08e8..541c7ed 100644 --- a/frontend/src/components/calendar/EventDetailPanel.tsx +++ b/frontend/src/components/calendar/EventDetailPanel.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useCallback, useMemo } from 'react'; +import { useState, useEffect, useCallback, useMemo, useRef } from 'react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { toast } from 'sonner'; import { format, parseISO } from 'date-fns'; @@ -284,6 +284,17 @@ export default function EventDetailPanel({ const [scopeStep, setScopeStep] = useState<'edit' | 'delete' | null>(null); const [editScope, setEditScope] = useState<'this' | 'this_and_future' | null>(null); const [locationSearch, setLocationSearch] = useState(''); + const descRef = useRef(null); + + // Auto-resize description textarea to fit content + useEffect(() => { + const el = descRef.current; + if (!el) return; + requestAnimationFrame(() => { + el.style.height = 'auto'; + el.style.height = `${el.scrollHeight}px`; + }); + }, [editState.description, isEditing]); // Poll lock status in view mode for shared events (Stream A: real-time lock awareness) // lockInfo is only set from the 423 error path; poll data (viewLockQuery.data) is used directly. @@ -367,11 +378,11 @@ export default function EventDetailPanel({ end_datetime: endDt, all_day: data.all_day, location_id: data.location_id ? parseInt(data.location_id) : null, - is_starred: data.is_starred, - recurrence_rule: rule, }; - // Invited editors cannot change calendars — omit calendar_id from payload + // Invited editors are restricted to the backend allowlist — omit fields they cannot modify if (!canModifyAsInvitee) { + payload.is_starred = data.is_starred; + payload.recurrence_rule = rule; payload.calendar_id = data.calendar_id ? parseInt(data.calendar_id) : null; } @@ -539,7 +550,8 @@ export default function EventDetailPanel({ : event?.title || ''; return ( -
+ // onWheel stopPropagation: defence-in-depth with CalendarPage's panelOpen guard to prevent month-scroll bleed +
e.stopPropagation()}> {/* Header */}
@@ -721,7 +733,7 @@ export default function EventDetailPanel({
) : (isEditing || isCreating) ? ( /* Edit / Create mode */ -
+
{/* Title (only shown in body for create mode; edit mode has it in header) */} {isCreating && (
@@ -737,58 +749,49 @@ export default function EventDetailPanel({
)} -
- -