Skip to main content
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

Display entire month with all events. Default view showing high-level overview.

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);
};

New Event Button

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:

Event Form Fields

<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

1

Click to Select

Click and drag on the calendar to select a date range for new events.
2

Click to View

Click existing events to view details and edit.
3

Drag to Reschedule

Drag and drop events to new dates or times.
4

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

Build docs developers (and LLMs) love