Compare commits

..

No commits in common. "99f70f3a41b797be4b1ff5b96b7cffa498a14f4f" and "652be41da4c988ab5b9cbf1808b62406d88b5496" have entirely different histories.

3 changed files with 21 additions and 182 deletions

View File

@ -35,12 +35,8 @@ async def get_dashboard(
today = client_date or date.today()
upcoming_cutoff = today + timedelta(days=current_settings.upcoming_days)
# Fetch calendar IDs once as a plain list — avoids repeating the subquery
# expression in each of the 3+ queries below and makes intent clearer.
calendar_id_result = await db.execute(
select(Calendar.id).where(Calendar.user_id == current_user.id)
)
user_calendar_ids = [row[0] for row in calendar_id_result.all()]
# Subquery: calendar IDs belonging to this user (for event scoping)
user_calendar_ids = select(Calendar.id).where(Calendar.user_id == current_user.id)
# Today's events (exclude parent templates — they are hidden, children are shown)
today_start = datetime.combine(today, datetime.min.time())
@ -99,13 +95,11 @@ async def get_dashboard(
total_todos = todo_row.total
total_incomplete_todos = todo_row.incomplete
# Starred events (within upcoming_days window, ordered by date, scoped to user's calendars)
upcoming_cutoff_dt = datetime.combine(upcoming_cutoff, datetime.max.time())
# Starred events (upcoming, ordered by date, scoped to user's calendars)
starred_query = select(CalendarEvent).where(
CalendarEvent.calendar_id.in_(user_calendar_ids),
CalendarEvent.is_starred == True,
CalendarEvent.start_datetime > today_start,
CalendarEvent.start_datetime <= upcoming_cutoff_dt,
_not_parent_template,
).order_by(CalendarEvent.start_datetime.asc()).limit(5)
starred_result = await db.execute(starred_query)
@ -177,11 +171,8 @@ async def get_upcoming(
overdue_floor = today - timedelta(days=30)
overdue_floor_dt = datetime.combine(overdue_floor, datetime.min.time())
# Fetch calendar IDs once as a plain list (same rationale as /dashboard handler)
calendar_id_result = await db.execute(
select(Calendar.id).where(Calendar.user_id == current_user.id)
)
user_calendar_ids = [row[0] for row in calendar_id_result.all()]
# Subquery: calendar IDs belonging to this user
user_calendar_ids = select(Calendar.id).where(Calendar.user_id == current_user.id)
# Build queries — include overdue todos (up to 30 days back) and snoozed reminders
todos_query = select(Todo).where(

View File

@ -8,9 +8,8 @@ import FullCalendar from '@fullcalendar/react';
import dayGridPlugin from '@fullcalendar/daygrid';
import timeGridPlugin from '@fullcalendar/timegrid';
import interactionPlugin from '@fullcalendar/interaction';
import enAuLocale from '@fullcalendar/core/locales/en-au';
import type { EventClickArg, DateSelectArg, EventDropArg, DatesSetArg, EventContentArg } from '@fullcalendar/core';
import { ChevronLeft, ChevronRight, PanelLeft, Plus, Search, Repeat } from 'lucide-react';
import type { EventClickArg, DateSelectArg, EventDropArg, DatesSetArg } from '@fullcalendar/core';
import { ChevronLeft, ChevronRight, PanelLeft, Plus, Search } from 'lucide-react';
import api, { getErrorMessage } from '@/lib/api';
import axios from 'axios';
import type { CalendarEvent, EventTemplate, Location as LocationType, CalendarPermission } from '@/types';
@ -33,8 +32,6 @@ const viewLabels: Record<CalendarView, string> = {
timeGridDay: 'Day',
};
const UMBRA_EVENT_CLASSES = ['umbra-event'];
export default function CalendarPage() {
const queryClient = useQueryClient();
const location = useLocation();
@ -366,14 +363,14 @@ export default function CalendarPage() {
start: event.start_datetime,
end: event.end_datetime || undefined,
allDay: event.all_day,
color: 'transparent',
backgroundColor: event.calendar_color || 'hsl(var(--accent-color))',
borderColor: event.calendar_color || 'hsl(var(--accent-color))',
editable: permissionMap.get(event.calendar_id) !== 'read_only',
extendedProps: {
is_virtual: event.is_virtual,
is_recurring: event.is_recurring,
parent_event_id: event.parent_event_id,
calendar_id: event.calendar_id,
calendarColor: event.calendar_color || 'hsl(var(--accent-color))',
},
}));
@ -503,59 +500,6 @@ export default function CalendarPage() {
const navigateToday = () => calendarRef.current?.getApi().today();
const changeView = (view: CalendarView) => calendarRef.current?.getApi().changeView(view);
// Set --event-color CSS var via eventDidMount (write-only, no reads)
const handleEventDidMount = useCallback((info: { el: HTMLElement; event: { extendedProps: Record<string, unknown> } }) => {
const color = info.event.extendedProps.calendarColor as string;
if (color) {
info.el.style.setProperty('--event-color', color);
}
}, []);
const renderEventContent = useCallback((arg: EventContentArg) => {
const isMonth = arg.view.type === 'dayGridMonth';
const isAllDay = arg.event.allDay;
const isRecurring = arg.event.extendedProps.is_recurring || arg.event.extendedProps.parent_event_id;
const repeatIcon = isRecurring ? (
<Repeat className="h-2.5 w-2.5 shrink-0 opacity-50" />
) : null;
if (isMonth) {
if (isAllDay) {
return (
<div className="flex items-center gap-1 truncate px-1">
<span className="text-[11px] font-medium truncate">{arg.event.title}</span>
{repeatIcon}
</div>
);
}
// Timed events in month: dot + title + time right-aligned
return (
<div className="flex items-center gap-1.5 truncate w-full">
<span
className="fc-daygrid-event-dot"
style={{ borderColor: 'var(--event-color)' }}
/>
<span className="text-[11px] font-medium truncate">{arg.event.title}</span>
{repeatIcon}
<span className="umbra-event-time text-[10px] opacity-50 shrink-0 ml-auto tabular-nums">{arg.timeText}</span>
</div>
);
}
// Week/day view — title on top, time underneath
return (
<div className="flex flex-col overflow-hidden h-full">
<div className="flex items-center gap-1">
<span className="text-[12px] font-medium truncate">{arg.event.title}</span>
{repeatIcon}
</div>
<span className="text-[10px] opacity-50 leading-tight tabular-nums">{arg.timeText}</span>
</div>
);
}, []);
return (
<div className="flex h-full overflow-hidden animate-fade-in">
<div className="hidden lg:flex lg:flex-row shrink-0">
@ -700,18 +644,6 @@ export default function CalendarPage() {
select={handleDateSelect}
datesSet={handleDatesSet}
height="100%"
locale={enAuLocale}
views={{
dayGridMonth: { dayHeaderFormat: { weekday: 'short' } },
timeGridWeek: { dayHeaderFormat: { weekday: 'short', day: 'numeric', month: 'numeric' } },
timeGridDay: { dayHeaderFormat: { weekday: 'long', day: 'numeric', month: 'long' } },
}}
eventTimeFormat={{ hour: 'numeric', minute: '2-digit', meridiem: 'short' }}
slotLabelFormat={{ hour: 'numeric', minute: '2-digit', meridiem: 'short' }}
slotEventOverlap={false}
eventDidMount={handleEventDidMount}
eventClassNames={UMBRA_EVENT_CLASSES}
eventContent={renderEventContent}
/>
</div>
</div>

View File

@ -91,9 +91,9 @@
--fc-button-hover-border-color: hsl(0 0% 20%);
--fc-button-active-bg-color: hsl(var(--accent-color));
--fc-button-active-border-color: hsl(var(--accent-color));
--fc-event-bg-color: transparent;
--fc-event-border-color: transparent;
--fc-event-text-color: inherit;
--fc-event-bg-color: hsl(var(--accent-color));
--fc-event-border-color: hsl(var(--accent-color));
--fc-event-text-color: hsl(0 0% 98%);
--fc-page-bg-color: transparent;
--fc-neutral-bg-color: hsl(0 0% 8% / 0.65);
--fc-neutral-text-color: hsl(0 0% 98%);
@ -144,44 +144,10 @@
border-bottom-color: hsl(var(--accent-color));
}
/* Now indicator pulse dot */
.fc .fc-timegrid-now-indicator-line::before {
content: '';
position: absolute;
left: -3px;
top: -3px;
width: 6px;
height: 6px;
border-radius: 50%;
background: hsl(var(--accent-color));
animation: pulse-dot 2s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
.fc .fc-timegrid-now-indicator-line::before {
animation: none;
opacity: 1;
}
}
/* Weekend columns: neutralise FullCalendar's built-in weekend td background.
The frame inherits --fc-neutral-bg-color identically to weekdays.
Weekend tint removed after 10+ attempts cross-browser compositing divergence
(Firefox produces teal artifact from semi-transparent HSL on near-black bg). */
.fc .fc-day-sat,
.fc .fc-day-sun {
background-color: transparent !important;
}
.fc .fc-col-header-cell {
background-color: hsl(0 0% 8% / 0.65);
border-color: var(--fc-border-color);
}
.fc .fc-col-header-cell.fc-day-sat,
.fc .fc-col-header-cell.fc-day-sun {
background-color: hsl(0 0% 8% / 0.65) !important;
}
.fc-theme-standard td,
.fc-theme-standard th {
@ -205,62 +171,21 @@
color: hsl(0 0% 63.9%);
}
/* ── Translucent event styling ── */
.fc .umbra-event {
border: none !important;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.15s ease, box-shadow 0.15s ease, opacity 0.15s ease;
}
.fc .umbra-event .fc-event-main {
color: var(--event-color) !important;
}
/* ── Month view: all-day events get translucent fill ── */
.fc .fc-daygrid-block-event.umbra-event {
background: color-mix(in srgb, var(--event-color) 12%, transparent) !important;
}
.fc .fc-daygrid-block-event.umbra-event:hover {
background: color-mix(in srgb, var(--event-color) 22%, transparent) !important;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
}
/* ── Month view: timed events — dot only, card on hover ── */
.fc .fc-daygrid-dot-event.umbra-event {
background: transparent !important;
}
.fc .fc-daygrid-dot-event.umbra-event .fc-daygrid-event-dot {
border-color: var(--event-color) !important;
margin: 0 2px 0 0;
}
.fc .fc-daygrid-dot-event.umbra-event:hover {
background: color-mix(in srgb, var(--event-color) 12%, transparent) !important;
}
.fc .fc-daygrid-event.umbra-event {
/* Event pills — compact rounded style */
.fc .fc-daygrid-event {
border-radius: 4px;
font-size: 0.75rem;
padding: 1px 2px;
padding: 0px 4px;
line-height: 1.4;
}
/* ── Time grid (week/day view) — translucent fill ── */
.fc .fc-timegrid-event.umbra-event {
background: color-mix(in srgb, var(--event-color) 12%, transparent) !important;
border-radius: 6px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
.fc .fc-timegrid-event {
border-radius: 4px;
font-size: 0.75rem;
}
.fc .fc-timegrid-event.umbra-event:hover {
background: color-mix(in srgb, var(--event-color) 22%, transparent) !important;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
}
.fc .fc-timegrid-event.umbra-event .fc-event-main {
padding: 3px 6px;
.fc .fc-timegrid-event .fc-event-main {
padding: 2px 4px;
}
/* Day number styling for today */
@ -337,10 +262,6 @@
padding: 2px 6px;
margin-bottom: 2px;
}
/* Ensure translucent events in popover inherit styling */
.fc .fc-more-popover .umbra-event {
margin-bottom: 2px;
}
}
/* ── Chromium native date picker icon fix (safety net) ── */
@ -437,11 +358,6 @@ form[data-submitted] input:invalid + button {
display: none;
}
/* Hide time spans in custom eventContent on mobile month view */
.fc .fc-daygrid-event .umbra-event-time {
display: none;
}
.fc .fc-timegrid-event {
font-size: 0.6rem;
}