Manage your team’s schedule with an interactive calendar featuring event management, automatic birthday reminders, and celebration animations.
Overview
The calendar system provides:
- Interactive Calendar - Full calendar view with day, week, and month views powered by FullCalendar
- Event Management - Create, edit, and delete events with color coding
- Drag-and-Drop - Reschedule events by dragging them to new dates
- Birthday Tracking - Automatic employee birthday events with celebration animations
- Event Details - Rich event descriptions and time management
- Spanish Localization - Full Spanish language support
- Real-time Updates - Instant feedback with toast notifications
Calendar View
The main calendar component (src/pages/Calendar/Calendar.tsx:17) uses FullCalendar for a professional scheduling experience:
Calendar Configuration
import FullCalendar from '@fullcalendar/react';
import dayGridPlugin from '@fullcalendar/daygrid';
import timeGridPlugin from '@fullcalendar/timegrid';
import interactionPlugin from '@fullcalendar/interaction';
import esLocale from '@fullcalendar/core/locales/es';
<FullCalendar
ref={calendarRef}
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]}
headerToolbar={{
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,timeGridWeek,timeGridDay'
}}
initialView="dayGridMonth"
editable={true}
selectable={true}
selectMirror={true}
dayMaxEvents={true}
weekends={true}
events={events}
select={handleDateSelect}
eventClick={handleEventClick}
eventDrop={handleEventDrop}
locales={[esLocale]}
locale="es"
height="100%"
/>
View Options
Month View
Week View
Day View
Display entire month with all events. Default view showing high-level overview.
Detailed week view with time slots. Perfect for detailed scheduling.
Hourly breakdown of a single day. Best for intensive daily planning.
Creating Events
Date Selection
Create events by selecting dates on the calendar (src/pages/Calendar/Calendar.tsx:62-70):
const handleDateSelect = (selectInfo) => {
setSelectedEvent({
title: '',
start: selectInfo.start,
end: selectInfo.end,
allDay: selectInfo.allDay
});
setModalOpen(true);
};
Quick access to create events (src/pages/Calendar/Calendar.tsx:181-192):
<Button
variant="contained"
startIcon={<AddIcon />}
onClick={() => {
setSelectedEvent(null);
setModalOpen(true);
}}
>
Nuevo Evento
</Button>
Event Modal
The event modal (src/pages/Calendar/EventModal.tsx) provides comprehensive event editing:
<EventModal
open={modalOpen}
onClose={() => setModalOpen(false)}
onSave={handleSaveEvent}
onDelete={handleDeleteEvent}
event={selectedEvent}
/>
Form includes:
- Title (required) - Event name
- Description - Additional details
- Start Date/Time - When the event begins
- End Date/Time - When the event ends
- All Day - Toggle for all-day events
- Color - Color coding for visual organization
All-day events span the entire day without specific times. Perfect for birthdays, holidays, or deadlines.
Saving Events
Event creation and updates with API integration (src/pages/Calendar/Calendar.tsx:109-124):
const handleSaveEvent = async (data) => {
try {
if (data.id) {
// Update existing event
await updateEvent(data.id, data);
setSnackbar({
open: true,
message: 'Cambios guardados correctamente',
severity: 'success'
});
} else {
// Create new event
await createEvent(data);
setSnackbar({
open: true,
message: 'Evento creado correctamente',
severity: 'success'
});
}
setModalOpen(false);
fetchEvents(); // Reload calendar
} catch (error) {
console.error('Error saving event:', error);
Response(error.response?.status, error.response?.data);
}
};
Editing Events
Click to Edit
Click any event to open the edit modal (src/pages/Calendar/Calendar.tsx:72-104):
const handleEventClick = (clickInfo) => {
const eventObj = clickInfo.event;
// Check if it's a birthday event
if (eventObj.extendedProps.isBirthday || eventObj.id.startsWith('birthday-')) {
// Show birthday celebration modal
setSelectedBirthday({
name: eventObj.extendedProps.employeeName || eventObj.title,
img: eventObj.extendedProps.employeeImg,
jobTitle: eventObj.extendedProps.jobTitle,
department: eventObj.extendedProps.department
});
setBirthdayModalOpen(true);
// Trigger confetti animation
confetti({
particleCount: 150,
spread: 70,
origin: { y: 0.6 },
zIndex: 9999
});
return;
}
// Regular event - open edit modal
setSelectedEvent({
id: eventObj.id,
title: eventObj.title,
description: eventObj.extendedProps.description,
start: eventObj.start,
end: eventObj.end,
allDay: eventObj.allDay,
color: eventObj.backgroundColor
});
setModalOpen(true);
};
Drag-and-Drop Rescheduling
Move events to new dates by dragging (src/pages/Calendar/Calendar.tsx:151-177):
const handleEventDrop = async (dropInfo) => {
const { event } = dropInfo;
// Prevent moving birthday events
if (event.id.startsWith('birthday-')) {
dropInfo.revert();
return;
}
try {
const updatedEvent = {
id: event.id,
title: event.title,
start: event.start,
end: event.end,
allDay: event.allDay,
color: event.backgroundColor,
description: event.extendedProps.description
};
await updateEvent(event.id, updatedEvent);
setSnackbar({
open: true,
message: 'Evento actualizado correctamente',
severity: 'success'
});
} catch (error) {
console.error('Error updating event drop:', error);
dropInfo.revert(); // Revert if save fails
Response(error.response?.status, error.response?.data);
}
};
Birthday events are automatically generated and cannot be moved or deleted. They are locked to the employee’s birth date.
Deleting Events
Delete events with confirmation (src/pages/Calendar/Calendar.tsx:126-149):
const handleDeleteEvent = async (id) => {
Swal.fire({
title: '¿Estás seguro?',
text: "No podrás revertir esto",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: 'Sí, eliminar',
cancelButtonText: 'Cancelar'
}).then(async (result) => {
if (result.isConfirmed) {
try {
await deleteEvent(id);
Swal.fire('Eliminado', 'El evento ha sido eliminado', 'success');
setModalOpen(false);
fetchEvents();
} catch (error) {
console.error('Error deleting event:', error);
Response(error.response?.status, error.response?.data);
}
}
});
};
Birthday Celebrations
Automatic birthday tracking with festive animations:
Birthday Modal
Special celebration modal for employee birthdays (src/pages/Calendar/Calendar.tsx:228-253):
<Dialog
open={birthdayModalOpen}
onClose={() => setBirthdayModalOpen(false)}
maxWidth="xs"
fullWidth
>
<DialogTitle sx={{ textAlign: 'center', fontWeight: 'bold' }}>
¡Feliz Cumpleaños! 🎉
</DialogTitle>
<DialogContent sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
{/* Employee Photo */}
{selectedBirthday?.img ? (
<Avatar
src={selectedBirthday.img}
sx={{ width: 100, height: 100, mb: 2, border: '3px solid #FF4081' }}
/>
) : (
<Avatar sx={{ width: 100, height: 100, mb: 2, bgcolor: '#FF4081', fontSize: '3rem' }}>
{selectedBirthday?.name?.charAt(0) || '🎈'}
</Avatar>
)}
{/* Employee Name */}
<Typography variant="h5" align="center" gutterBottom>
{selectedBirthday?.name}
</Typography>
{/* Job Title and Department */}
<Typography variant="body1" color="text.secondary" align="center">
{selectedBirthday?.jobTitle}
{selectedBirthday?.department ? `- ${selectedBirthday.department}` : ''}
</Typography>
{/* Birthday Message */}
<Typography variant="body2" sx={{ mt: 2, fontStyle: 'italic', color: 'primary.main' }}>
¡Le deseamos un día increíble!
</Typography>
</DialogContent>
</Dialog>
Confetti Animation
Celebration animation when clicking birthday events:
import confetti from 'canvas-confetti';
// Trigger confetti on birthday click
confetti({
particleCount: 150,
spread: 70,
origin: { y: 0.6 },
zIndex: 9999
});
Loading Events
Fetch events from the API on component mount (src/pages/Calendar/Calendar.tsx:33-56):
const fetchEvents = async () => {
setLoading(true);
try {
const response = await getEvents();
const formattedEvents = response.data.map((event) => ({
...event,
id: String(event.id)
}));
setEvents(formattedEvents);
} catch (error) {
console.error('Error fetching events:', error);
Swal.fire('Error', 'No se pudieron cargar los eventos', 'error');
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchEvents();
}, []);
API Service Methods
Event API endpoints (src/services/event.service.ts):
import axios from 'axios';
const apiUrl = import.meta.env.VITE_BASE_URL;
export interface CalendarEvent {
id?: string | number;
title: string;
description?: string;
start: Date;
end: Date;
allDay?: boolean;
color?: string;
}
// Get all events
export const getEvents = async (start?: string, end?: string) => {
const params = new URLSearchParams();
if (start) params.append('start', start);
if (end) params.append('end', end);
return await axios.get(`${apiUrl}/events?${params.toString()}`);
};
// Create event
export const createEvent = async (event: CalendarEvent) => {
return await axios.post(`${apiUrl}/events`, event);
};
// Update event
export const updateEvent = async (id: string | number, event: CalendarEvent) => {
return await axios.put(`${apiUrl}/events/${id}`, event);
};
// Delete event
export const deleteEvent = async (id: string | number) => {
return await axios.delete(`${apiUrl}/events/${id}`);
};
User Feedback
Real-time notifications using Snackbar (src/pages/Calendar/Calendar.tsx:255-259):
<Snackbar
open={snackbar.open}
autoHideDuration={3000}
onClose={handleCloseSnackbar}
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
>
<Alert
onClose={handleCloseSnackbar}
severity={snackbar.severity}
sx={{ width: '100%' }}
>
{snackbar.message}
</Alert>
</Snackbar>
Notification Examples
- Success: “Evento creado correctamente” (Event created successfully)
- Success: “Cambios guardados correctamente” (Changes saved successfully)
- Success: “Evento actualizado correctamente” (Event updated successfully)
- Error: “No se pudieron cargar los eventos” (Could not load events)
Calendar Features
Visual Organization
Color Coding
Assign custom colors to events for visual categorization
All-Day Events
Mark events that span entire days without specific times
Event Descriptions
Add detailed notes and information to events
Multiple Views
Switch between month, week, and day views
Interaction Features
Click to Select
Click and drag on the calendar to select a date range for new events.
Click to View
Click existing events to view details and edit.
Drag to Reschedule
Drag and drop events to new dates or times.
Navigate Views
Use toolbar buttons to switch between month, week, and day views.
Best Practices
Use color coding to categorize events (e.g., blue for meetings, green for deadlines, pink for birthdays).
The calendar automatically displays employee birthdays as recurring all-day events. These are read-only and updated when employee birth dates change.
When dragging events to reschedule, ensure the new time slot doesn’t conflict with existing events.
Keyboard Navigation
- Arrow Keys - Navigate between dates
- Enter - Open event details
- Escape - Close modal
- Tab - Navigate form fields
Mobile Responsiveness
The calendar adapts to mobile screens:
- Simplified header toolbar
- Touch-friendly event interactions
- Swipe gestures for navigation
- Responsive modal dialogs
Integration with Employee Management
Birthday events are automatically created from employee data:
// Birthday events are generated server-side from employee birth_date field
// and marked with isBirthday: true
{
id: 'birthday-{employeeId}',
title: 'Cumpleaños: {employeeName}',
start: '{birthDate}',
allDay: true,
color: '#FF4081',
extendedProps: {
isBirthday: true,
employeeName: 'John Doe',
employeeImg: 'https://...',
jobTitle: 'Developer',
department: 'Engineering'
}
}
Styling and Customization
The calendar uses Material-UI theming for consistent styling:
<Paper elevation={3} sx={{ flex: 1, p: 2, overflow: 'hidden' }}>
<FullCalendar
// ... calendar props
height="100%"
/>
</Paper>
Custom CSS
Additional calendar styling in Calendar.css:
.fc-event {
cursor: pointer;
border-radius: 4px;
padding: 2px 5px;
}
.fc-event:hover {
opacity: 0.8;
}
.mini-product {
max-width: 70px;
max-height: 70px;
object-fit: cover;
}
Next Steps
Employee Management
Manage employee profiles and birth dates
Introduction
Return to the main documentation page