Recurring events
Recurring events
@mantine/schedule에서 반복 이벤트를 정의하고 뷰가 시리즈를 확장하는 방식에 대한 문서예요. RFC 5545 rrule 패턴과 오버라이드 이벤트, 확장 동작을 설명해요.
출처: 문서
본문
일반적인 rrule 패턴 (Common rrule patterns)
FREQ=DAILY;COUNT=10– 매일, 10회 발생FREQ=WEEKLY;BYDAY=MO,WE,FR– 매주 월, 수, 금요일FREQ=MONTHLY;BYMONTHDAY=15– 매월 15일FREQ=YEARLY;COUNT=5– 매년 1회, 5회
오버라이드 이벤트 (Override event)
오버라이드 이벤트는 시리즈에서 생성된 단일 발생을 대체해요. recurringEventId로 시리즈를 가리키고 recurrenceId로 대체할 발생을 식별해요(YYYY-MM-DD HH:mm:ss 형식, 원래 시작 시간과 일치):
const override = {
id: 'weekly-series-override',
title: 'Weekly planning (moved)',
start: '2024-01-17 16:00:00',
end: '2024-01-17 17:00:00',
color: 'grape',
recurringEventId: 'weekly-series',
recurrenceId: '2024-01-17 10:00:00',
};
렌더링이 동작하는 방식 (How rendering works)
각 뷰는 표시된 날짜 범위에 대해서만 반복 이벤트를 확장해요:
rrule문자열을 파싱하고 범위 내의 발생 시작 시간을 생성해요exdate항목과 일치하는 발생을 제거해요- 일치하는 발생을 오버라이드 이벤트로 대체해요
- 일회성 이벤트와 병합해 렌더링해요
생성된 인스턴스는 recurringInstance 메타데이터 객체를 포함해요:
isRecurringInstance– 생성된 이벤트에 대해 항상truerecurringEventId– 부모 시리즈 idrecurrenceId– 발생 식별자originalStart/originalEnd– 오버라이드 전의 발생 날짜
expandRecurringEvents 유틸리티
커스텀 로직에 expandRecurringEvents 유틸리티를 직접 사용할 수 있어요:
import { expandRecurringEvents } from '@mantine/schedule';
const expanded = expandRecurringEvents({
events,
rangeStart: '2024-01-15 00:00:00',
rangeEnd: '2024-01-21 23:59:59',
expansionLimit: 2000, // optional, default 2000
});
recurrenceExpansionLimit prop
모든 뷰는 recurrenceExpansionLimit prop(기본값 2000)을 받아 반복 시리즈당 생성되는 인스턴스 수를 제한해요. 이는 무제한 반복 규칙(예: COUNT나 UNTIL이 없는 FREQ=DAILY)의 성능 문제를 방지해요. 제한은 각 시리즈에 독립적으로 적용되므로, 무제한 시리즈가 여러 개인 일정은 최대 limit × 시리즈 수의 총 인스턴스를 생성할 수 있다는 점에 주의하세요.
Schedule 데모 (Schedule demo)
import { useState } from 'react';
import { Schedule } from '@mantine/schedule';
const events = [
{
id: 'schedule-series',
title: 'Weekly planning (series)',
start: '2024-01-15 10:00:00',
end: '2024-01-15 11:00:00',
color: 'blue',
recurrence: {
rrule: 'FREQ=WEEKLY;BYDAY=MO,WE;COUNT=16',
exdate: ['2024-01-17 10:00:00'],
},
},
{
id: 'schedule-series-override',
title: 'Weekly planning (moved one occurrence)',
start: '2024-01-17 16:00:00',
end: '2024-01-17 17:00:00',
color: 'grape',
recurringEventId: 'schedule-series',
recurrenceId: '2024-01-17 10:00:00',
},
{
id: 'schedule-one-off',
title: 'One-off roadmap review',
start: '2024-01-18 12:00:00',
end: '2024-01-18 13:00:00',
color: 'green',
},
];
function Demo() {
const [date, setDate] = useState('2024-01-15');
return <Schedule date={date} onDateChange={setDate} events={events} />;
}
DayView 데모 (DayView demo)
import dayjs from 'dayjs';
import { useState } from 'react';
import { DayView } from '@mantine/schedule';
const today = dayjs().format('YYYY-MM-DD');
const events = [
{
id: 'daily-sync-series',
title: 'Daily sync (series)',
start: `${dayjs(today).subtract(2, 'day').format('YYYY-MM-DD')} 09:00:00`,
end: `${dayjs(today).subtract(2, 'day').format('YYYY-MM-DD')} 09:30:00`,
color: 'blue',
recurrence: {
rrule: 'FREQ=DAILY;COUNT=10',
exdate: [`${today} 09:00:00`],
},
},
{
id: 'daily-sync-override',
title: 'Daily sync (moved today)',
start: `${today} 14:00:00`,
end: `${today} 14:30:00`,
color: 'grape',
recurringEventId: 'daily-sync-series',
recurrenceId: `${today} 09:00:00`,
},
{
id: 'one-off-day',
title: 'One-off planning',
start: `${today} 11:00:00`,
end: `${today} 12:00:00`,
color: 'green',
},
];
function Demo() {
const [date, setDate] = useState(today);
return (
<DayView date={date} onDateChange={setDate} events={events} />
);
}
WeekView 데모 (WeekView demo)
import dayjs from 'dayjs';
import { useState } from 'react';
import { WeekView, getStartOfWeek } from '@mantine/schedule';
const weekStart = dayjs(getStartOfWeek({ date: new Date(), firstDayOfWeek: 1 })).format('YYYY-MM-DD');
const events = [
{
id: 'weekly-sync-series',
title: 'Weekly sync (series)',
start: `${weekStart} 10:00:00`,
end: `${weekStart} 11:00:00`,
color: 'blue',
recurrence: {
rrule: 'FREQ=WEEKLY;BYDAY=MO,WE;COUNT=12',
exdate: [`${dayjs(weekStart).add(2, 'day').format('YYYY-MM-DD')} 10:00:00`],
},
},
{
id: 'weekly-sync-override',
title: 'Weekly sync (moved this week)',
start: `${dayjs(weekStart).add(2, 'day').format('YYYY-MM-DD')} 15:00:00`,
end: `${dayjs(weekStart).add(2, 'day').format('YYYY-MM-DD')} 16:00:00`,
color: 'grape',
recurringEventId: 'weekly-sync-series',
recurrenceId: `${dayjs(weekStart).add(2, 'day').format('YYYY-MM-DD')} 10:00:00`,
},
];
function Demo() {
const [date, setDate] = useState(weekStart);
return (
<WeekView date={date} onDateChange={setDate} events={events} />
);
}
MonthView 데모 (MonthView demo)
import { useState } from 'react';
import { MonthView } from '@mantine/schedule';
const month = '2025-11-01';
const events = [
{
id: 'monthly-planning-series',
title: 'Weekly planning (series)',
start: '2025-11-03 10:00:00',
end: '2025-11-03 11:00:00',
color: 'blue',
recurrence: {
rrule: 'FREQ=WEEKLY;BYDAY=MO;COUNT=10',
exdate: ['2025-11-17 10:00:00'],
},
},
{
id: 'monthly-planning-override',
title: 'Weekly planning (moved occurrence)',
start: '2025-11-17 15:00:00',
end: '2025-11-17 16:00:00',
color: 'grape',
recurringEventId: 'monthly-planning-series',
recurrenceId: '2025-11-17 10:00:00',
},
];
function Demo() {
const [date, setDate] = useState(month);
return <MonthView date={date} onDateChange={setDate} events={events} />;
}
YearView 데모 (YearView demo)
import dayjs from 'dayjs';
import { useState } from 'react';
import { YearView } from '@mantine/schedule';
const year = dayjs().year();
const events = [
{
id: 'yearly-series',
title: 'Monthly architecture review (series)',
start: `${year}-01-10 09:00:00`,
end: `${year}-01-10 10:00:00`,
color: 'blue',
recurrence: {
rrule: 'FREQ=MONTHLY;COUNT=12',
exdate: [`${year}-11-10 09:00:00`],
},
},
{
id: 'yearly-series-override',
title: 'Monthly architecture review (moved in Nov)',
start: `${year}-11-10 13:00:00`,
end: `${year}-11-10 14:00:00`,
color: 'grape',
recurringEventId: 'yearly-series',
recurrenceId: `${year}-11-10 09:00:00`,
},
];
function Demo() {
const [date, setDate] = useState(`${year}-01-01`);
return <YearView date={date} onDateChange={setDate} events={events} />;
}