מהן הקשיים הנפוצים בפיתוח לוח שנה?
יצירת לוח שנה אינטראקטיבי עם שילוב FullCalendar נראית טריוויאלית במבט ראשון: שליפת אירועים ממסד הנתונים והצגתם בגריד חודשי. בפועל, הפרויקט גדל עם ניואנסים טכניים שיכולים לפוצץ את התקציב ולוחות הזמנים. יישמנו למעלה מ-20 פרויקטים כאלה ב-10 שנים, מרשימה פשוטה ועד מתכנן ארגוני עם גרירה ושחרור, אירועים חוזרים ושילוב Google Calendar. האתגרים משמעותיים: אירועים חוזרים דורשים תמיכה בחריגים (ביטול מופעים ספציפיים) וסדרות אינסופיות באמצעות חוקים דמויי cron, טיפול באזורי זמן דורש אחסון ב-UTC עם המרה בצד הלקוח, ו-UX למובייל על מסכי 375×667 px דורש החלקות, גריד אדפטיבי ובקרות ידידותיות למגע. התעלמות מאלה מובילה לשיעורי נטישה גבוהים בסמארטפונים.
איך לבחור את ספריית לוח השנה הנכונה?
בחירת ספריית לוח שנה היא פשרה בין פונקציונליות לגודל החבילה. FullCalendar טוב פי 2 מ-React Big Calendar מבחינת תכונות (20+ תוספים לעומת מעטים). אנו משתמשים ב-FullCalendar ל-90% מהפרויקטים. הוא מכסה תצוגות חודשיות, שבועיות, יומיות ורשימה, גרירה ושחרור, אירועים חוזרים, לוקליזציה. React Big Calendar הוא חצי מהגודל (20-30 KB דחוס לעומת 40-60 KB) אך חסר תמיכה מובנית באירועים חוזרים וייצוא. 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 (בטא) | כן | 30-50 KB |
לתצוגת רשימה פשוטה ללא אינטראקטיביות, CSS Grid טהור עשוי להספיק. אך לגרירה ושחרור, מודאלים וייצוא 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 מחזיר אירועים עם שדות 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> ) } , start. המיפוי בצד הלקוח נעשה באמצעות פונקציית end. אנו משתמשים בתבנית Repository כדי להפריד את לוגיקת השאילתות.
יישום אירועים חוזרים
לאירועים חוזרים, אנו משתמשים בספריית RRule — יישום JavaScript של iCalendar RFC 5545. FullCalendar קולט את הכלל ומחשב את כל המופעים תוך כדי תנועה. חריגים מוגדרים באמצעות מערך backgroundColor:
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 עם ציון אזור זמן מפורש. אזורי זמן מטופלים על ידי אחסון ב-UTC והמרה באמצעות mapToFullCalendarEvent בצד הלקוח. FullCalendar תומך ב-exdate או באזור זמן IANA ספציפי.
ייצוא ואינטגרציות
ייצוא אירועים ל-iCal מיושם בצד השרת (Laravel/Nest.js). עבור Google Calendar, אנו משתמשים בתוסף npm install @fullcalendar/rrule rrule —只需提供日历 ID 和 API 密钥。按日历进行颜色编码、背景高亮 — 全部内置。
| אינטגרציה | תוסף FullCalendar | מורכבות | הערה |
|---|---|---|---|
| Google Calendar | @fullcalendar/google-calendar | נמוכה | ID + מפתח API |
| iCal (ייצוא) | צד שרת | בינונית | יצירת .ics |
| גרירה ושחרור | @fullcalendar/interaction | נמוכה | מובנה |
תוצרי הפרויקט
- תיעוד: ארכיטקטורת API, סכימת אירועים, הוראות עריכה.
- גישה: הגדרת סביבה, CI/CD, משתני סביבה.
- הדרכה: הדגמה של שעתיים לצוות.
- תמיכה: 30 ימי עריכות חינם לאחר הפריסה.
- רספונסיביות: תמיכה מלאה במובייל (החלקה, תצוגה קומפקטית).
- SEO: אירועים נגישים דרך כתובות URL ישירות, נוספו ל-sitemap.
תהליך העבודה
- ניתוח (2–3 ימים): בחינת מקורות אירועים, סוגי חזרות, דרישות מובייל.
- עיצוב (3–5 ימים): הגדרת מחסנית, אב טיפוס API, מודל נתונים.
- פיתוח (10–20 ימים): צד שרת (Laravel/Node.js), צד לקוח (React/Vue), אינטגרציות.
- בדיקות (3–5 ימים): בדיקות יחידה + אינטגרציה, בדיקות מובייל ידניות.
- פריסה (יום אחד): הגדרת מטמון, Cloudflare, ניטור.
לוחות זמנים ועלות
לוח שנה סטטי (הצגת אירועים מ-API) — יום אחד (החל מ-$500). לוח שנה מלא עם תצוגת חודש/שבוע/רשימה, מודאל, סינון קטגוריות — 3–4 ימים (החל מ-$2,000). עם גרירה ושחרור, אירועים חוזרים, ייצוא iCal ו-Google Calendar — החל משבועיים (החל מ-$8,000). לדוגמה, לוח שנה מלא עם כל התכונות עולה החל מ-$8,000, וחוסך לכם עד $4,000 בהשוואה לבנייה מאפס. העלות מחושבת באופן אישי: צרו קשר, נעריך את הפרויקט שלכם תוך יום אחד. קבלו ייעוץ — נדון בפרטי האינטגרציה. תמיכה מובטחת ל-30 יום וניסיון של 10+ שנים מבטיחים אספקה אמינה.
שירות פיתוח לוח השנה שלנו מתמחה בשילוב FullCalendar ללוחות שנה אינטראקטיביים. אנו ממליצים על שילוב FullCalendar מכיוון שהוא מהיר פי 3 ליישום מאשר בנייה מאפס. עם פיתוח לוח השנה שלנו, אתם מקבלים שילוב FullCalendar שהוא גם חזק וגם חסכוני.







