Fix QA findings: firstDay reactivity, state revert, helper extraction

- W1: Add key prop to FullCalendar so firstDay change triggers remount
- W2: Revert firstDayOfWeek toggle state on API failure
- S1: Extract _rule_int helper in recurrence service to reduce duplication

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Kyle 2026-02-22 02:02:15 +08:00
parent 3b63d18f63
commit e22cad1d86
3 changed files with 14 additions and 5 deletions

View File

@ -28,6 +28,12 @@ def _nth_weekday_of_month(year: int, month: int, weekday: int, week: int) -> Opt
return target return target
def _rule_int(rule: dict, key: str, default: int) -> int:
"""Get an int from the rule dict, falling back to default if missing or None."""
val = rule.get(key)
return int(val) if val is not None else default
def generate_occurrences( def generate_occurrences(
parent: CalendarEvent, parent: CalendarEvent,
horizon_days: int = 365, horizon_days: int = 365,
@ -90,7 +96,7 @@ def generate_occurrences(
occurrences.append(_make_child(parent_start)) occurrences.append(_make_child(parent_start))
if rule_type == "every_n_days": if rule_type == "every_n_days":
interval: int = int(rule.get("interval") or 1) interval: int = _rule_int(rule, "interval", 1)
if interval < 1: if interval < 1:
interval = 1 interval = 1
current = parent_start + timedelta(days=interval) current = parent_start + timedelta(days=interval)
@ -99,7 +105,7 @@ def generate_occurrences(
current += timedelta(days=interval) current += timedelta(days=interval)
elif rule_type == "weekly": elif rule_type == "weekly":
weekday: int = int(rule.get("weekday") if rule.get("weekday") is not None else parent_start.weekday()) weekday: int = _rule_int(rule, "weekday", parent_start.weekday())
# Start from the next week after the parent # Start from the next week after the parent
days_ahead = (weekday - parent_start.weekday()) % 7 days_ahead = (weekday - parent_start.weekday()) % 7
if days_ahead == 0: if days_ahead == 0:
@ -110,8 +116,8 @@ def generate_occurrences(
current += timedelta(weeks=1) current += timedelta(weeks=1)
elif rule_type == "monthly_nth_weekday": elif rule_type == "monthly_nth_weekday":
week: int = int(rule.get("week") or 1) week: int = _rule_int(rule, "week", 1)
weekday = int(rule.get("weekday") if rule.get("weekday") is not None else parent_start.weekday()) weekday = _rule_int(rule, "weekday", parent_start.weekday())
# Advance month by month # Advance month by month
year, month = parent_start.year, parent_start.month year, month = parent_start.year, parent_start.month
# Move to the next month from the parent # Move to the next month from the parent
@ -144,7 +150,7 @@ def generate_occurrences(
break break
elif rule_type == "monthly_date": elif rule_type == "monthly_date":
day: int = int(rule.get("day") or parent_start.day) day: int = _rule_int(rule, "day", parent_start.day)
year, month = parent_start.year, parent_start.month year, month = parent_start.year, parent_start.month
month += 1 month += 1
if month > 12: if month > 12:

View File

@ -322,6 +322,7 @@ export default function CalendarPage() {
<div className="flex-1 overflow-y-auto"> <div className="flex-1 overflow-y-auto">
<div className="h-full"> <div className="h-full">
<FullCalendar <FullCalendar
key={`fc-${settings?.first_day_of_week ?? 0}`}
ref={calendarRef} ref={calendarRef}
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]} plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]}
initialView="dayGridMonth" initialView="dayGridMonth"

View File

@ -139,12 +139,14 @@ export default function SettingsPage() {
}; };
const handleFirstDayChange = async (value: number) => { const handleFirstDayChange = async (value: number) => {
const previous = firstDayOfWeek;
setFirstDayOfWeek(value); setFirstDayOfWeek(value);
try { try {
await updateSettings({ first_day_of_week: value }); await updateSettings({ first_day_of_week: value });
queryClient.invalidateQueries({ queryKey: ['calendar-events'] }); queryClient.invalidateQueries({ queryKey: ['calendar-events'] });
toast.success(value === 0 ? 'Week starts on Sunday' : 'Week starts on Monday'); toast.success(value === 0 ? 'Week starts on Sunday' : 'Week starts on Monday');
} catch { } catch {
setFirstDayOfWeek(previous);
toast.error('Failed to update first day of week'); toast.error('Failed to update first day of week');
} }
}; };