style(13-03): apply Prettier formatting across repo
Mechanical reformat — no logic changes. 398 files changed, 19125 insertions(+), 16457 deletions(-). Prettier 3.8.4 with .prettierrc (singleQuote:true, semi:true, tabWidth:2, trailingComma:all, printWidth:100). Isolated per D-13-08 for reviewability.
This commit is contained in:
@@ -22,26 +22,26 @@
|
||||
* - aria-modal="true", role="dialog"
|
||||
*/
|
||||
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { MapPin, Edit2, Trash2 } from 'lucide-react'
|
||||
import { useCalendarStore } from '../store/calendarStore.js'
|
||||
import type { CalendarOccurrence } from '../api/client.js'
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { MapPin, Edit2, Trash2 } from 'lucide-react';
|
||||
import { useCalendarStore } from '../store/calendarStore.js';
|
||||
import type { CalendarOccurrence } from '../api/client.js';
|
||||
|
||||
// ── Types ──────────────────────────────────────────────────────────────────
|
||||
|
||||
/** Shape of props passed by Schedule-X customComponents.eventModal */
|
||||
interface ScheduleXEventModalProps {
|
||||
calendarEvent?: {
|
||||
id?: string | number
|
||||
title?: string
|
||||
start?: unknown
|
||||
end?: unknown
|
||||
calendarId?: string
|
||||
location?: string
|
||||
description?: string
|
||||
_familySync?: { uid: string; color: string; isShared: boolean }
|
||||
}
|
||||
id?: string | number;
|
||||
title?: string;
|
||||
start?: unknown;
|
||||
end?: unknown;
|
||||
calendarId?: string;
|
||||
location?: string;
|
||||
description?: string;
|
||||
_familySync?: { uid: string; color: string; isShared: boolean };
|
||||
};
|
||||
}
|
||||
|
||||
// ── Helpers ────────────────────────────────────────────────────────────────
|
||||
@@ -58,41 +58,41 @@ function formatDateTime(start: string, end: string, allDay: boolean): string {
|
||||
if (allDay) {
|
||||
// YYYY-MM-DD — format as a date without time
|
||||
try {
|
||||
const d = new Date(start + 'T00:00:00')
|
||||
const d = new Date(start + 'T00:00:00');
|
||||
return d.toLocaleDateString(undefined, {
|
||||
weekday: 'short',
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
})
|
||||
});
|
||||
} catch {
|
||||
return start
|
||||
return start;
|
||||
}
|
||||
}
|
||||
// Timed — parse offset-aware ISO string.
|
||||
// Strip trailing IANA bracket e.g. '[America/Toronto]' before passing to new Date():
|
||||
// new Date() cannot parse the bracket notation and returns Invalid Date.
|
||||
try {
|
||||
const cleanStart = start.replace(/\[[^\]]*\]$/, '')
|
||||
const cleanEnd = end.replace(/\[[^\]]*\]$/, '')
|
||||
const startDate = new Date(cleanStart)
|
||||
const endDate = new Date(cleanEnd)
|
||||
const cleanStart = start.replace(/\[[^\]]*\]$/, '');
|
||||
const cleanEnd = end.replace(/\[[^\]]*\]$/, '');
|
||||
const startDate = new Date(cleanStart);
|
||||
const endDate = new Date(cleanEnd);
|
||||
const dateStr = startDate.toLocaleDateString(undefined, {
|
||||
weekday: 'short',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
})
|
||||
});
|
||||
const startTime = startDate.toLocaleTimeString(undefined, {
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
})
|
||||
});
|
||||
const endTime = endDate.toLocaleTimeString(undefined, {
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
})
|
||||
return `${dateStr}, ${startTime} – ${endTime}`
|
||||
});
|
||||
return `${dateStr}, ${startTime} – ${endTime}`;
|
||||
} catch {
|
||||
return start
|
||||
return start;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,65 +106,65 @@ function formatDateTime(start: string, end: string, allDay: boolean): string {
|
||||
* In standalone mode it resolves the event from TanStack Query cache.
|
||||
*/
|
||||
export function EventDetailPopover(props: ScheduleXEventModalProps = {}) {
|
||||
const { openEventId, setOpenEventId } = useCalendarStore()
|
||||
const setEventForm = useCalendarStore((s) => s.setEventForm)
|
||||
const setDeleteDialog = useCalendarStore((s) => s.setDeleteDialog)
|
||||
const queryClient = useQueryClient()
|
||||
const dialogRef = useRef<HTMLDivElement>(null)
|
||||
const { openEventId, setOpenEventId } = useCalendarStore();
|
||||
const setEventForm = useCalendarStore((s) => s.setEventForm);
|
||||
const setDeleteDialog = useCalendarStore((s) => s.setDeleteDialog);
|
||||
const queryClient = useQueryClient();
|
||||
const dialogRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Resolve the event to display:
|
||||
// 1. If Schedule-X passed a calendarEvent prop, use it to get the id
|
||||
// 2. Otherwise use Zustand openEventId
|
||||
const activeId: string | null = (() => {
|
||||
if (props.calendarEvent?.id != null) {
|
||||
return String(props.calendarEvent.id)
|
||||
return String(props.calendarEvent.id);
|
||||
}
|
||||
return openEventId
|
||||
})()
|
||||
return openEventId;
|
||||
})();
|
||||
|
||||
// Lookup the occurrence in TanStack Query cache.
|
||||
// We search all 'events' query entries for a matching id.
|
||||
const occurrence: CalendarOccurrence | null = (() => {
|
||||
if (!activeId) return null
|
||||
if (!activeId) return null;
|
||||
// queryClient.getQueriesData returns [{queryKey, data}] entries
|
||||
const allEntries = queryClient.getQueriesData<{ occurrences: CalendarOccurrence[] }>({
|
||||
queryKey: ['events'],
|
||||
})
|
||||
});
|
||||
for (const [, data] of allEntries) {
|
||||
if (!data?.occurrences) continue
|
||||
const found = data.occurrences.find((o) => o.id === activeId)
|
||||
if (found) return found
|
||||
if (!data?.occurrences) continue;
|
||||
const found = data.occurrences.find((o) => o.id === activeId);
|
||||
if (found) return found;
|
||||
}
|
||||
return null
|
||||
})()
|
||||
return null;
|
||||
})();
|
||||
|
||||
// Close handler
|
||||
const handleClose = () => setOpenEventId(null)
|
||||
const handleClose = () => setOpenEventId(null);
|
||||
|
||||
// Escape key listener — add to document so it works even when focus is trapped
|
||||
useEffect(() => {
|
||||
if (!activeId) return
|
||||
if (!activeId) return;
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
handleClose()
|
||||
handleClose();
|
||||
}
|
||||
}
|
||||
document.addEventListener('keydown', onKeyDown)
|
||||
return () => document.removeEventListener('keydown', onKeyDown)
|
||||
}, [activeId]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||
};
|
||||
document.addEventListener('keydown', onKeyDown);
|
||||
return () => document.removeEventListener('keydown', onKeyDown);
|
||||
}, [activeId]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
// Focus trap — when popover opens, focus the dialog
|
||||
useEffect(() => {
|
||||
if (activeId && dialogRef.current) {
|
||||
dialogRef.current.focus()
|
||||
dialogRef.current.focus();
|
||||
}
|
||||
}, [activeId])
|
||||
}, [activeId]);
|
||||
|
||||
// Nothing to show
|
||||
if (!activeId || !occurrence) return null
|
||||
if (!activeId || !occurrence) return null;
|
||||
|
||||
// Responsive: detect phone breakpoint
|
||||
const isPhone = typeof window !== 'undefined' && window.matchMedia('(max-width: 767px)').matches
|
||||
const isPhone = typeof window !== 'undefined' && window.matchMedia('(max-width: 767px)').matches;
|
||||
|
||||
const dialogStyle: React.CSSProperties = isPhone
|
||||
? {
|
||||
@@ -198,7 +198,7 @@ export function EventDetailPopover(props: ScheduleXEventModalProps = {}) {
|
||||
overflowY: 'auto',
|
||||
zIndex: 200,
|
||||
fontFamily: 'var(--font-family-base)',
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -316,11 +316,7 @@ export function EventDetailPopover(props: ScheduleXEventModalProps = {}) {
|
||||
fontFamily: 'var(--font-family-base)',
|
||||
}}
|
||||
>
|
||||
<MapPin
|
||||
size={14}
|
||||
style={{ flexShrink: 0, marginTop: '2px' }}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<MapPin size={14} style={{ flexShrink: 0, marginTop: '2px' }} aria-hidden="true" />
|
||||
{/* Plain text — XSS guard */}
|
||||
<span>{occurrence.location}</span>
|
||||
</div>
|
||||
@@ -374,9 +370,7 @@ export function EventDetailPopover(props: ScheduleXEventModalProps = {}) {
|
||||
{/* Plain text — XSS guard (T-02e-01).
|
||||
Show 'Family' for shared-calendar events; owner display name for personal
|
||||
events; fall back to calendarName when ownerName is null. */}
|
||||
{occurrence.isShared
|
||||
? 'Family'
|
||||
: (occurrence.ownerName ?? occurrence.calendarName)}
|
||||
{occurrence.isShared ? 'Family' : (occurrence.ownerName ?? occurrence.calendarName)}
|
||||
</div>
|
||||
|
||||
{/* Phase 3 footer: Edit / Delete actions (D-10) */}
|
||||
@@ -394,8 +388,8 @@ export function EventDetailPopover(props: ScheduleXEventModalProps = {}) {
|
||||
<button
|
||||
aria-label="Edit event"
|
||||
onClick={() => {
|
||||
setEventForm(true, 'edit', occurrence.uid)
|
||||
setOpenEventId(null)
|
||||
setEventForm(true, 'edit', occurrence.uid);
|
||||
setOpenEventId(null);
|
||||
}}
|
||||
style={{
|
||||
background: 'none',
|
||||
@@ -424,7 +418,7 @@ export function EventDetailPopover(props: ScheduleXEventModalProps = {}) {
|
||||
<button
|
||||
aria-label="Delete event"
|
||||
onClick={() => {
|
||||
setDeleteDialog(true, occurrence.uid)
|
||||
setDeleteDialog(true, occurrence.uid);
|
||||
}}
|
||||
style={{
|
||||
background: 'none',
|
||||
@@ -451,5 +445,5 @@ export function EventDetailPopover(props: ScheduleXEventModalProps = {}) {
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user