feat: sync calendar colors from all providers and add custom recurrence
- Store calendar colors (backgroundColor) from Apple and Synology during connection setup - Auto-refresh missing colors for existing Apple/Synology connections - Show color dots next to calendar names in settings - Add "Custom..." recurrence option with interval (every N days/weeks/ months/years) and day-of-week selection for weekly recurrence - Generate proper RRULE with INTERVAL and BYDAY for all providers - Generate proper Outlook Graph recurrence with custom intervals v1.61.0 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
1ab98025a6
commit
c5dc315f82
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "my-weekly-todo-list",
|
||||
"version": "1.60.0",
|
||||
"version": "1.61.0",
|
||||
"description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
||||
@ -57,6 +57,7 @@ export async function POST(req: Request) {
|
||||
id: cal.id,
|
||||
title: cal.title,
|
||||
isPrimary: cal.isPrimary,
|
||||
backgroundColor: cal.color || undefined,
|
||||
selected: true // Default to selected
|
||||
})) as any,
|
||||
updatedAt: new Date()
|
||||
|
||||
@ -4,6 +4,7 @@ import { authOptions } from "@/lib/auth";
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { refreshConnectionCache, RefreshableConnection } from '@/lib/calendar-cache';
|
||||
import { getUserCalendars as getSynologyCalendars } from '@/lib/synology-calendar';
|
||||
import { getUserCalendars as getAppleCalendars } from '@/lib/apple-calendar';
|
||||
|
||||
// Get user's calendar connections
|
||||
export async function GET(request: NextRequest) {
|
||||
@ -42,7 +43,7 @@ export async function GET(request: NextRequest) {
|
||||
);
|
||||
}
|
||||
|
||||
// For Synology connections, fire-and-forget prune of deleted calendars
|
||||
// For Synology connections, fire-and-forget prune of deleted calendars + refresh colors
|
||||
if (conn.provider === 'synology' && Array.isArray(calendars) && calendars.length > 0) {
|
||||
const connId = conn.id;
|
||||
const storedCalendars = calendars;
|
||||
@ -52,14 +53,20 @@ export async function GET(request: NextRequest) {
|
||||
const serverUrl = conn.refreshToken;
|
||||
if (username && password && serverUrl) {
|
||||
const freshCalendars = await getSynologyCalendars(serverUrl, username, password);
|
||||
const freshIds = new Set(freshCalendars.map(c => c.id));
|
||||
const staleIds = storedCalendars.filter((c: any) => !freshIds.has(c.id)).map((c: any) => c.id);
|
||||
if (staleIds.length > 0) {
|
||||
console.log('[CONNECTIONS] Synology stale calendars to prune:', staleIds);
|
||||
const pruned = storedCalendars.filter((c: any) => freshIds.has(c.id));
|
||||
const freshMap = new Map(freshCalendars.map(c => [c.id, c]));
|
||||
const staleIds = storedCalendars.filter((c: any) => !freshMap.has(c.id)).map((c: any) => c.id);
|
||||
const hasMissingColors = storedCalendars.some((c: any) => !c.backgroundColor);
|
||||
if (staleIds.length > 0 || hasMissingColors) {
|
||||
console.log('[CONNECTIONS] Synology refresh: pruning', staleIds.length, 'stale, refreshing colors:', hasMissingColors);
|
||||
const updated = storedCalendars
|
||||
.filter((c: any) => freshMap.has(c.id))
|
||||
.map((c: any) => {
|
||||
const fresh = freshMap.get(c.id);
|
||||
return { ...c, backgroundColor: c.backgroundColor || fresh?.color || undefined };
|
||||
});
|
||||
await prisma.calendarConnection.update({
|
||||
where: { id: connId },
|
||||
data: { calendars: pruned },
|
||||
data: { calendars: updated },
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -69,6 +76,35 @@ export async function GET(request: NextRequest) {
|
||||
})();
|
||||
}
|
||||
|
||||
// For Apple connections, fire-and-forget refresh of missing colors
|
||||
if (conn.provider === 'apple' && Array.isArray(calendars) && calendars.length > 0) {
|
||||
const connId = conn.id;
|
||||
const storedCalendars = calendars;
|
||||
const hasMissingColors = storedCalendars.some((c: any) => !c.backgroundColor);
|
||||
if (hasMissingColors) {
|
||||
(async () => {
|
||||
try {
|
||||
const [email, appPassword] = conn.accessToken.split(':');
|
||||
if (email && appPassword) {
|
||||
const freshCalendars = await getAppleCalendars(email, appPassword);
|
||||
const freshMap = new Map(freshCalendars.map(c => [c.id, c]));
|
||||
const updated = storedCalendars.map((c: any) => {
|
||||
const fresh = freshMap.get(c.id);
|
||||
return { ...c, backgroundColor: c.backgroundColor || fresh?.color || undefined };
|
||||
});
|
||||
await prisma.calendarConnection.update({
|
||||
where: { id: connId },
|
||||
data: { calendars: updated },
|
||||
});
|
||||
console.log('[CONNECTIONS] Apple calendars refreshed with colors');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[CONNECTIONS] Apple calendar color refresh failed:', err);
|
||||
}
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: conn.id,
|
||||
provider: conn.provider,
|
||||
|
||||
@ -39,7 +39,7 @@ export async function POST(request: NextRequest) {
|
||||
if (!session?.user?.email) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
|
||||
const body = await request.json();
|
||||
const { calendarId, title, description, start, end, location, allDay, recurrence, recurrenceEndDate, recurrenceCount, url,
|
||||
const { calendarId, title, description, start, end, location, allDay, recurrence, recurrenceEndDate, recurrenceCount, recurrenceInterval, recurrenceDays, url,
|
||||
reminders, busyStatus, visibility, attendees, attachments } = body;
|
||||
|
||||
console.log('[API] Creating event:', { calendarId, title, start, end });
|
||||
@ -65,6 +65,8 @@ export async function POST(request: NextRequest) {
|
||||
recurrence,
|
||||
recurrenceEndDate,
|
||||
recurrenceCount,
|
||||
recurrenceInterval,
|
||||
recurrenceDays,
|
||||
url,
|
||||
reminders,
|
||||
busyStatus,
|
||||
@ -94,7 +96,7 @@ export async function PATCH(request: NextRequest) {
|
||||
if (!session?.user?.email) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
|
||||
const body = await request.json();
|
||||
const { calendarId, eventId, title, description, start, end, location, allDay, recurrence, recurrenceEndDate, recurrenceCount, url,
|
||||
const { calendarId, eventId, title, description, start, end, location, allDay, recurrence, recurrenceEndDate, recurrenceCount, recurrenceInterval, recurrenceDays, url,
|
||||
reminders, busyStatus, visibility, attendees, attachments } = body;
|
||||
|
||||
console.log('[API] Updating event:', { calendarId, eventId, title });
|
||||
@ -120,6 +122,8 @@ export async function PATCH(request: NextRequest) {
|
||||
recurrence,
|
||||
recurrenceEndDate,
|
||||
recurrenceCount,
|
||||
recurrenceInterval,
|
||||
recurrenceDays,
|
||||
url,
|
||||
reminders,
|
||||
busyStatus,
|
||||
|
||||
@ -71,6 +71,7 @@ export async function POST(req: Request) {
|
||||
id: cal.id,
|
||||
title: cal.title,
|
||||
isPrimary: cal.isPrimary,
|
||||
backgroundColor: cal.color || undefined,
|
||||
selected: true
|
||||
})) as any
|
||||
}
|
||||
|
||||
@ -77,6 +77,11 @@ export default function CalendarEventModal({
|
||||
event?.recurrenceEndDate || ''
|
||||
);
|
||||
const [recurrenceCount, setRecurrenceCount] = useState(event?.recurrenceCount || 10);
|
||||
// Custom recurrence fields
|
||||
const [customInterval, setCustomInterval] = useState(event?.recurrenceInterval || 1);
|
||||
const [customUnit, setCustomUnit] = useState<'days' | 'weeks' | 'months' | 'years'>(event?.recurrenceUnit || 'weeks');
|
||||
const startDow = (() => { const d = event?.start?.dateTime ? new Date(event.start.dateTime) : new Date(); return d.getDay(); })();
|
||||
const [customDays, setCustomDays] = useState<number[]>(event?.recurrenceDays || [startDow]);
|
||||
const [calendarId, setCalendarId] = useState(event?.calendarId || (availableCalendars.length > 0 ? availableCalendars[0].id : ''));
|
||||
|
||||
// New fields
|
||||
@ -179,7 +184,10 @@ export default function CalendarEventModal({
|
||||
description,
|
||||
location,
|
||||
url: url || undefined,
|
||||
recurrence: recurrence !== 'none' ? recurrence : undefined,
|
||||
recurrence: recurrence === 'custom' ? customUnit.replace(/s$/, '') === 'day' ? 'daily' : customUnit.replace(/s$/, '') === 'week' ? 'weekly' : customUnit.replace(/s$/, '') === 'month' ? 'monthly' : 'yearly'
|
||||
: recurrence !== 'none' ? recurrence : undefined,
|
||||
recurrenceInterval: recurrence === 'custom' ? customInterval : undefined,
|
||||
recurrenceDays: recurrence === 'custom' && customUnit === 'weeks' ? customDays : undefined,
|
||||
recurrenceEndDate: recurrence !== 'none' && recurrenceEndType === 'date' && recurrenceEndDateValue ? recurrenceEndDateValue : undefined,
|
||||
recurrenceCount: recurrence !== 'none' && recurrenceEndType === 'count' && recurrenceCount > 0 ? recurrenceCount : undefined,
|
||||
calendarId,
|
||||
@ -443,9 +451,62 @@ export default function CalendarEventModal({
|
||||
<option value="weekly">Every Week</option>
|
||||
<option value="monthly">Every Month</option>
|
||||
<option value="yearly">Every Year</option>
|
||||
<option value="custom">Custom...</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Custom recurrence options */}
|
||||
{recurrence === 'custom' && (
|
||||
<>
|
||||
<div style={{ ...rowStyle, gap: '6px' }}>
|
||||
<span style={labelStyle}>Every</span>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
|
||||
<input
|
||||
type="number" min={1} max={99} value={customInterval}
|
||||
onChange={e => setCustomInterval(Math.max(1, parseInt(e.target.value) || 1))}
|
||||
style={{
|
||||
...selectStyle, width: '45px', textAlign: 'center',
|
||||
background: 'var(--weekly-bg-secondary, #f5f5f5)', borderRadius: '6px', padding: '4px 6px',
|
||||
}}
|
||||
/>
|
||||
<select value={customUnit} onChange={e => setCustomUnit(e.target.value as any)} style={selectStyle}>
|
||||
<option value="days">{customInterval === 1 ? 'Day' : 'Days'}</option>
|
||||
<option value="weeks">{customInterval === 1 ? 'Week' : 'Weeks'}</option>
|
||||
<option value="months">{customInterval === 1 ? 'Month' : 'Months'}</option>
|
||||
<option value="years">{customInterval === 1 ? 'Year' : 'Years'}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{customUnit === 'weeks' && (
|
||||
<div style={{ ...rowStyle, flexDirection: 'column', alignItems: 'flex-start', gap: '6px' }}>
|
||||
<span style={labelStyle}>Repeat on</span>
|
||||
<div style={{ display: 'flex', gap: '4px', paddingLeft: '0' }}>
|
||||
{['S', 'M', 'T', 'W', 'T', 'F', 'S'].map((day, i) => (
|
||||
<button
|
||||
key={i}
|
||||
onClick={() => {
|
||||
setCustomDays(prev =>
|
||||
prev.includes(i) ? (prev.length > 1 ? prev.filter(d => d !== i) : prev) : [...prev, i]
|
||||
);
|
||||
}}
|
||||
style={{
|
||||
width: '30px', height: '30px', borderRadius: '50%',
|
||||
border: customDays.includes(i) ? '2px solid #3b82f6' : '1px solid var(--weekly-border, #ddd)',
|
||||
backgroundColor: customDays.includes(i) ? '#3b82f6' : 'transparent',
|
||||
color: customDays.includes(i) ? 'white' : 'var(--weekly-text)',
|
||||
fontSize: '0.75rem', fontWeight: 600, cursor: 'pointer',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
{day}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Recurrence End - only shown when repeat is set */}
|
||||
{recurrence !== 'none' && (
|
||||
<>
|
||||
|
||||
@ -12405,7 +12405,7 @@ function SettingsSidebar({
|
||||
padding: "3px 0",
|
||||
}}
|
||||
>
|
||||
{/* Calendar Name */}
|
||||
{/* Calendar Color + Name */}
|
||||
<span
|
||||
style={{
|
||||
flex: 1,
|
||||
@ -12414,8 +12414,15 @@ function SettingsSidebar({
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "6px",
|
||||
}}
|
||||
>
|
||||
<span style={{
|
||||
width: "10px", height: "10px", borderRadius: "50%", flexShrink: 0,
|
||||
backgroundColor: cal.backgroundColor || cal.color || "#3b82f6",
|
||||
}} />
|
||||
{cleanTitle}
|
||||
{isShared && (
|
||||
<span title="Shared calendar" style={{ marginLeft: "4px", fontSize: "0.75rem", opacity: 0.5 }}>
|
||||
|
||||
@ -392,6 +392,8 @@ export const createEvent = async (
|
||||
recurrence?: string;
|
||||
recurrenceEndDate?: string;
|
||||
recurrenceCount?: number;
|
||||
recurrenceInterval?: number;
|
||||
recurrenceDays?: number[];
|
||||
start: { dateTime?: string; date?: string };
|
||||
end: { dateTime?: string; date?: string };
|
||||
reminders?: Array<{ method: string; minutes: number }>;
|
||||
@ -458,6 +460,13 @@ export const createEvent = async (
|
||||
};
|
||||
if (rruleMap[eventData.recurrence]) {
|
||||
let rrule = rruleMap[eventData.recurrence];
|
||||
if (eventData.recurrenceInterval && eventData.recurrenceInterval > 1 && eventData.recurrence !== 'biweekly') {
|
||||
rrule += `;INTERVAL=${eventData.recurrenceInterval}`;
|
||||
}
|
||||
if (eventData.recurrenceDays && eventData.recurrenceDays.length > 0 && eventData.recurrence === 'weekly') {
|
||||
const dayMap = ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'];
|
||||
rrule += `;BYDAY=${eventData.recurrenceDays.map(d => dayMap[d]).join(',')}`;
|
||||
}
|
||||
if (eventData.recurrenceCount && eventData.recurrenceCount > 0) {
|
||||
rrule += `;COUNT=${eventData.recurrenceCount}`;
|
||||
} else if (eventData.recurrenceEndDate) {
|
||||
|
||||
@ -43,6 +43,8 @@ export interface CalendarEvent {
|
||||
recurrence?: string;
|
||||
recurrenceEndDate?: string;
|
||||
recurrenceCount?: number;
|
||||
recurrenceInterval?: number;
|
||||
recurrenceDays?: number[];
|
||||
recurringEventId?: string;
|
||||
isRecurring?: boolean;
|
||||
source: 'google' | 'apple' | 'outlook' | 'synology' | 'notion';
|
||||
@ -60,7 +62,7 @@ export interface CalendarEvent {
|
||||
/**
|
||||
* Convert friendly recurrence name to RRULE string
|
||||
*/
|
||||
function toRRule(recurrence?: string, recurrenceEndDate?: string, recurrenceCount?: number): string | null {
|
||||
function toRRule(recurrence?: string, recurrenceEndDate?: string, recurrenceCount?: number, recurrenceInterval?: number, recurrenceDays?: number[]): string | null {
|
||||
let freq: string;
|
||||
switch (recurrence) {
|
||||
case 'daily': freq = 'FREQ=DAILY'; break;
|
||||
@ -71,10 +73,16 @@ function toRRule(recurrence?: string, recurrenceEndDate?: string, recurrenceCoun
|
||||
default: return null;
|
||||
}
|
||||
let rrule = `RRULE:${freq}`;
|
||||
if (recurrenceInterval && recurrenceInterval > 1 && recurrence !== 'biweekly') {
|
||||
rrule += `;INTERVAL=${recurrenceInterval}`;
|
||||
}
|
||||
if (recurrenceDays && recurrenceDays.length > 0 && recurrence === 'weekly') {
|
||||
const dayMap = ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'];
|
||||
rrule += `;BYDAY=${recurrenceDays.map(d => dayMap[d]).join(',')}`;
|
||||
}
|
||||
if (recurrenceCount && recurrenceCount > 0) {
|
||||
rrule += `;COUNT=${recurrenceCount}`;
|
||||
} else if (recurrenceEndDate) {
|
||||
// UNTIL format: YYYYMMDDTHHMMSSZ
|
||||
const d = new Date(recurrenceEndDate);
|
||||
d.setHours(23, 59, 59);
|
||||
rrule += `;UNTIL=${d.toISOString().replace(/[-:]/g, '').replace(/\.\d+Z$/, 'Z')}`;
|
||||
@ -85,9 +93,11 @@ function toRRule(recurrence?: string, recurrenceEndDate?: string, recurrenceCoun
|
||||
/**
|
||||
* Convert friendly recurrence name to Outlook Graph recurrence object
|
||||
*/
|
||||
function toOutlookRecurrence(recurrence?: string, startDate?: Date, recurrenceEndDate?: string, recurrenceCount?: number): any {
|
||||
function toOutlookRecurrence(recurrence?: string, startDate?: Date, recurrenceEndDate?: string, recurrenceCount?: number, recurrenceInterval?: number, recurrenceDays?: number[]): any {
|
||||
if (!recurrence || recurrence === 'none') return undefined;
|
||||
const start = startDate || new Date();
|
||||
const interval = recurrenceInterval || (recurrence === 'biweekly' ? 2 : 1);
|
||||
const dayNames = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
|
||||
let range: any = {
|
||||
type: 'noEnd',
|
||||
startDate: start.toISOString().split('T')[0],
|
||||
@ -99,15 +109,18 @@ function toOutlookRecurrence(recurrence?: string, startDate?: Date, recurrenceEn
|
||||
}
|
||||
switch (recurrence) {
|
||||
case 'daily':
|
||||
return { pattern: { type: 'daily', interval: 1 }, range };
|
||||
return { pattern: { type: 'daily', interval }, range };
|
||||
case 'weekly':
|
||||
return { pattern: { type: 'weekly', interval: 1, daysOfWeek: [['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'][start.getDay()]] }, range };
|
||||
case 'biweekly':
|
||||
return { pattern: { type: 'weekly', interval: 2, daysOfWeek: [['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'][start.getDay()]] }, range };
|
||||
case 'biweekly': {
|
||||
const days = recurrenceDays && recurrenceDays.length > 0
|
||||
? recurrenceDays.map(d => dayNames[d])
|
||||
: [dayNames[start.getDay()]];
|
||||
return { pattern: { type: 'weekly', interval, daysOfWeek: days }, range };
|
||||
}
|
||||
case 'monthly':
|
||||
return { pattern: { type: 'absoluteMonthly', interval: 1, dayOfMonth: start.getDate() }, range };
|
||||
return { pattern: { type: 'absoluteMonthly', interval, dayOfMonth: start.getDate() }, range };
|
||||
case 'yearly':
|
||||
return { pattern: { type: 'absoluteYearly', interval: 1, dayOfMonth: start.getDate(), month: start.getMonth() + 1 }, range };
|
||||
return { pattern: { type: 'absoluteYearly', interval, dayOfMonth: start.getDate(), month: start.getMonth() + 1 }, range };
|
||||
default: return undefined;
|
||||
}
|
||||
}
|
||||
@ -516,6 +529,7 @@ export const getCalendarEvents = async (
|
||||
|
||||
console.log(`[CALENDAR] Synology: fetched ${calendarEvents.length} events from calendar ${calendarId}`);
|
||||
const calendarData = calendars.find(c => c.id === calendarId);
|
||||
const freshCal = freshCalendars.find(c => c.id === calendarId);
|
||||
|
||||
events = events.concat(calendarEvents.map((event: any) => {
|
||||
const isDateOnly = (s: string) => s && !s.includes('T');
|
||||
@ -539,7 +553,7 @@ export const getCalendarEvents = async (
|
||||
source: 'synology' as const,
|
||||
calendarId,
|
||||
calendarTitle: calendarData?.title || 'Synology Calendar',
|
||||
backgroundColor: calendarData?.color || '#1b85ff',
|
||||
backgroundColor: calendarData?.backgroundColor || calendarData?.color || freshCal?.color || '#1b85ff',
|
||||
reminders: event.reminders as EventReminder[] || undefined,
|
||||
busyStatus: event.busyStatus as BusyStatus || undefined,
|
||||
visibility: event.visibility as EventVisibility || undefined,
|
||||
@ -825,7 +839,7 @@ export const createCalendarEvent = async (
|
||||
);
|
||||
|
||||
// Map to Google format
|
||||
const rrule = toRRule(event.recurrence, event.recurrenceEndDate, event.recurrenceCount);
|
||||
const rrule = toRRule(event.recurrence, event.recurrenceEndDate, event.recurrenceCount, event.recurrenceInterval, event.recurrenceDays);
|
||||
const googleEvent: any = {
|
||||
summary: event.title,
|
||||
description: event.description,
|
||||
@ -877,7 +891,7 @@ export const createCalendarEvent = async (
|
||||
end: event.end,
|
||||
location: event.location,
|
||||
allDay: event.allDay,
|
||||
recurrence: toOutlookRecurrence(event.recurrence, startDate, event.recurrenceEndDate, event.recurrenceCount),
|
||||
recurrence: toOutlookRecurrence(event.recurrence, startDate, event.recurrenceEndDate, event.recurrenceCount, event.recurrenceInterval, event.recurrenceDays),
|
||||
reminders: event.reminders,
|
||||
busyStatus: event.busyStatus,
|
||||
visibility: event.visibility,
|
||||
@ -936,6 +950,8 @@ export const createCalendarEvent = async (
|
||||
recurrence: event.recurrence,
|
||||
recurrenceEndDate: event.recurrenceEndDate,
|
||||
recurrenceCount: event.recurrenceCount,
|
||||
recurrenceInterval: event.recurrenceInterval,
|
||||
recurrenceDays: event.recurrenceDays,
|
||||
start,
|
||||
end,
|
||||
reminders: event.reminders,
|
||||
@ -974,6 +990,8 @@ export const createCalendarEvent = async (
|
||||
recurrence: event.recurrence,
|
||||
recurrenceEndDate: event.recurrenceEndDate,
|
||||
recurrenceCount: event.recurrenceCount,
|
||||
recurrenceInterval: event.recurrenceInterval,
|
||||
recurrenceDays: event.recurrenceDays,
|
||||
start: event.start!,
|
||||
end: event.end!,
|
||||
reminders: event.reminders,
|
||||
@ -1056,7 +1074,7 @@ export const updateCalendarEvent = async (
|
||||
);
|
||||
|
||||
// Map to Google format
|
||||
const rrule = toRRule(event.recurrence, event.recurrenceEndDate, event.recurrenceCount);
|
||||
const rrule = toRRule(event.recurrence, event.recurrenceEndDate, event.recurrenceCount, event.recurrenceInterval, event.recurrenceDays);
|
||||
const googleEvent: any = {};
|
||||
if (event.title !== undefined) googleEvent.summary = event.title;
|
||||
if (event.description !== undefined) googleEvent.description = event.description;
|
||||
@ -1109,7 +1127,7 @@ export const updateCalendarEvent = async (
|
||||
end: event.end,
|
||||
location: event.location,
|
||||
allDay: event.allDay,
|
||||
recurrence: toOutlookRecurrence(event.recurrence, startDate, event.recurrenceEndDate, event.recurrenceCount),
|
||||
recurrence: toOutlookRecurrence(event.recurrence, startDate, event.recurrenceEndDate, event.recurrenceCount, event.recurrenceInterval, event.recurrenceDays),
|
||||
reminders: event.reminders,
|
||||
busyStatus: event.busyStatus,
|
||||
visibility: event.visibility,
|
||||
|
||||
@ -402,6 +402,8 @@ export const createEvent = async (
|
||||
recurrence?: string;
|
||||
recurrenceEndDate?: string;
|
||||
recurrenceCount?: number;
|
||||
recurrenceInterval?: number;
|
||||
recurrenceDays?: number[];
|
||||
start: { dateTime?: string; date?: string };
|
||||
end: { dateTime?: string; date?: string };
|
||||
reminders?: Array<{ method: string; minutes: number }>;
|
||||
@ -458,6 +460,13 @@ export const createEvent = async (
|
||||
};
|
||||
if (rruleMap[eventData.recurrence]) {
|
||||
let rrule = rruleMap[eventData.recurrence];
|
||||
if (eventData.recurrenceInterval && eventData.recurrenceInterval > 1 && eventData.recurrence !== 'biweekly') {
|
||||
rrule += `;INTERVAL=${eventData.recurrenceInterval}`;
|
||||
}
|
||||
if (eventData.recurrenceDays && eventData.recurrenceDays.length > 0 && eventData.recurrence === 'weekly') {
|
||||
const dayMap = ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'];
|
||||
rrule += `;BYDAY=${eventData.recurrenceDays.map(d => dayMap[d]).join(',')}`;
|
||||
}
|
||||
if (eventData.recurrenceCount && eventData.recurrenceCount > 0) {
|
||||
rrule += `;COUNT=${eventData.recurrenceCount}`;
|
||||
} else if (eventData.recurrenceEndDate) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user