Какие сложности возникают при создании календаря?
Создание интерактивного календаря событий — задача, которая на первый взгляд кажется тривиальной: вытащить события из базы и отобразить в месячной сетке. На практике проект обрастает техническими нюансами, способными похоронить бюджет и сроки. Мы реализовали более 20 таких проектов за 10 лет, от простого списка до корпоративного планировщика с drag-and-drop, повторяющимися событиями и интеграцией Google Calendar. Разберём ключевые подводные камни.
Первая сложность — повторяющиеся события. Простой event loop не подходит: нужна поддержка исключений, переносов, бесконечных серий (cron-подобные правила). Вторая — часовые пояса. Хранить даты в UTC, отображать в локальном времени — классика, но если событие создаётся в одном TZ, а просматривается в другом, даты съезжают. Третья — мобильный UX. Полноценный календарь на экране 375×667 px — отдельная задача: требуются свайпы, адаптивная сетка, скролл и touch-интерфейс. Игнорирование этих аспектов приводит к высокому bounce rate на смартфонах.
Какую библиотеку выбрать?
Выбор библиотеки для календаря — компромисс между функциональностью и бандлом. FullCalendar в 2 раза функциональнее React Big Calendar по числу плагинов. Мы используем FullCalendar для 90% проектов. Он покрывает месячный, недельный, дневной и списочный виды, drag-and-drop, повторяющиеся события, локализацию. React Big Calendar в два раза легче, но не имеет встроенной поддержки повторяющихся событий и экспорта. TOAST UI Calendar — достойная альтернатива с корейским корнями, но сообщество меньше.
| Библиотека | Плагины | Поддержка React/Vue | Повторяющиеся события | Бандл (min+gzip) |
|---|---|---|---|---|
| FullCalendar | 20+ | Оба | rrule | 40-60 KB |
| React Big Calendar | Мало | React | Нет | 20-30 KB |
| TOAST UI Calendar | Средне | Vue, React (beta) | Есть | 30-50 KB |
Если нужно просто вывести список событий по месяцам без интерактива — хватит чистого CSS Grid. Но для drag-and-drop, модалок и экспорта iCal библиотека обязательна.
Как мы интегрируем FullCalendar с React
Установка и настройка занимают 30 минут. Базовый компонент с переключением видов, локализацией и кастомным рендером:
Установка FullCalendar
npm install @fullcalendar/react @fullcalendar/core @fullcalendar/daygrid \
@fullcalendar/timegrid @fullcalendar/list @fullcalendar/interaction
import FullCalendar from '@fullcalendar/react'
import dayGridPlugin from '@fullcalendar/daygrid'
import timeGridPlugin from '@fullcalendar/timegrid'
import listPlugin from '@fullcalendar/list'
import interactionPlugin from '@fullcalendar/interaction'
import { EventInput, DateSelectArg, EventClickArg } from '@fullcalendar/core'
import ruLocale from '@fullcalendar/core/locales/ru'
export function EventCalendar() {
const [events, setEvents] = useState<EventInput[]>([])
const [selectedEvent, setSelectedEvent] = useState<EventClickArg | null>(null)
useEffect(() => {
fetch('/api/events')
.then(r => r.json())
.then(data => setEvents(data.map(mapToFullCalendarEvent)))
}, [])
function handleEventClick(info: EventClickArg) {
info.jsEvent.preventDefault()
setSelectedEvent(info)
}
function handleDateSelect(info: DateSelectArg) {
openCreateEventModal({ start: info.start, end: info.end, allDay: info.allDay })
}
return (
<>
<FullCalendar
plugins={[dayGridPlugin, timeGridPlugin, listPlugin, interactionPlugin]}
initialView="dayGridMonth"
locale={ruLocale}
headerToolbar={{
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek',
}}
buttonText={{
today: 'Сегодня',
month: 'Месяц',
week: 'Неделя',
day: 'День',
list: 'Список',
}}
events={events}
selectable={true}
selectMirror={true}
dayMaxEvents={3}
weekends={true}
eventClick={handleEventClick}
select={handleDateSelect}
eventTimeFormat={{ hour: '2-digit', minute: '2-digit', hour12: false }}
firstDay={1}
height="auto"
eventDisplay="block"
eventContent={renderEventContent}
/>
{selectedEvent && <EventModal event={selectedEvent} onClose={() => setSelectedEvent(null)} />}
</>
)
}
function renderEventContent(eventInfo: any) {
return (
<div className="fc-event-custom">
<span className="fc-event-dot" style={{ background: eventInfo.event.backgroundColor }} />
<span className="fc-event-title">{eventInfo.event.title}</span>
{!eventInfo.event.allDay && (
<span className="fc-event-time">{eventInfo.timeText}</span>
)}
</div>
)
}
API на Laravel возвращает события с полями start, end, backgroundColor. Маппинг на фронте происходит через функцию mapToFullCalendarEvent. Мы используем Repository pattern для разделения логики запросов.
Как реализовать повторяющиеся события?
Для повторяющихся событий применяем библиотеку RRule — JavaScript-реализацию iCalendar RFC 5545. FullCalendar подхватывает правило и рассчитывает все вхождения на лету. Исключения — массив exdate:
npm install @fullcalendar/rrule rrule
import rrulePlugin from '@fullcalendar/rrule'
const recurringEvent: EventInput = {
id: 'weekly-standup',
title: 'Еженедельный стендап',
rrule: {
freq: 'weekly',
byweekday: ['mo', 'we', 'fr'],
dtstart: new Date(),
until: new Date(new Date().setFullYear(new Date().getFullYear() + 1)),
},
duration: '00:30',
backgroundColor: '#0ea5e9',
}
const recurringWithExceptions: EventInput = {
...recurringEvent,
exdate: [new Date(2025, 4, 1, 9, 0).toISOString(), new Date(2025, 5, 12, 9, 0).toISOString()],
}
Важно: даты в exdate должны быть в формате ISO с явным указанием часового пояса (Z или смещение). Часовые пояса решаем хранением в UTC и конвертацией через date-fns-tz на клиенте. FullCalendar поддерживает timeZone="local" или конкретный IANA-таймзон.
Как организован экспорт и интеграции?
Экспорт событий в iCal реализуем на бэкенде (Laravel/Nest.js). Для Google Calendar используем плагин @fullcalendar/google-calendar — достаточно указать ID календаря и API-ключ. Цветовая кодировка событий по календарям, фоновая подсветка — всё из коробки.
| Интеграция | Плагин FullCalendar | Сложность | Примечание |
|---|---|---|---|
| Google Calendar | @fullcalendar/google-calendar | Низкая | ID + API-ключ |
| iCal (экспорт) | Бэкенд | Средняя | Генерация .ics |
| Drag-and-drop | @fullcalendar/interaction | Низкая | Встроенное |
Что входит в реализацию календаря?
- Документация: архитектура API, схема событий, инструкция для редакторов.
- Доступы: настройка окружения, CI/CD, переменные окружения.
- Обучение: 2 часа демо для команды.
- Поддержка: 30 дней бесплатных правок после деплоя.
- Адаптив: полная поддержка мобильных (swipe, compact view).
- SEO: события доступны по прямым URL, добавляются в sitemap.
Процесс работы
- Аналитика (2–3 дня): разбираем источники событий, типы повторов, требования к мобильности.
- Проектирование (3–5 дней): определяем стек, прототип API, модель данных.
- Разработка (10–20 дней): бэкенд (Laravel/Node.js), фронтенд (React/Vue), интеграции.
- Тестирование (3–5 дней): unit + integration тесты, ручное тестирование на мобильных.
- Деплой (1 день): настройка кеширования, Cloudflare, мониторинг.
Сроки и стоимость
Статичный календарь (вывод событий из API) — 1 день. Полноценный с месяц/неделя/список, модалкой, фильтром по категориям — 3–4 дня. С drag-and-drop, повторяющимися событиями, iCal-экспортом и Google Calendar — от 2 недель. Стоимость рассчитывается индивидуально: напишите нам, оценим ваш проект за 1 день. Получите консультацию — обсудим детали интеграции.







