Які складнощі виникають при створенні календаря?
Створення інтерактивного календаря подій — завдання, яке на перший погляд здається тривіальним: витягти події з бази та відобразити в місячній сітці. На практиці проект обростає технічними нюансами, здатними поховати бюджет і терміни. Ми реалізували понад 20 таких проектів за 10+ років (на ринку з 2014 року, гарантія 12 місяців), від простого списку до корпоративного планувальника з drag-and-drop, повторюваними подіями та інтеграцією Google Calendar. Розберемо ключові підводні камені.
Перша складність — повторювані події. Простий event loop не підходить: потрібна підтримка винятків, перенесень, нескінченних серій (cron-подібні правила). Друга — часові пояси. Зберігати дати в UTC, відображати в локальному часі — класика, але якщо подія створюється в одному TZ, а переглядається в іншому, дати з’їжджають. Третя — мобільний UX. Повноцінний календар на екрані 375×667 px — окреме завдання: потрібні свайпи, адаптивна сітка, скрол і touch-інтерфейс. Ігнорування цих аспектів призводить до високого bounce rate на смартфонах.
Яку бібліотеку обрати?
Вибір бібліотеки для календаря — компроміс між функціональністю та бандлом. FullCalendar у 2 рази функціональніший за React Big Calendar, а rrrule дозволяє обробляти повторювані події в 3 рази швидше за ручне парсування. Ми використовуємо 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 день від $2000. Повноцінний з місяць/тиждень/список, модалкою, фільтром за категоріями — 3–4 дні від $4000. З drag-and-drop, повторюваними подіями, iCal-експортом та Google Calendar — від 2 тижнів від $6000. Вартість розраховується індивідуально: напишіть нам, оцінимо ваш проект за 1 день. Отримайте консультацію — обговоримо деталі інтеграції.







