본문 바로가기

React

[React] 캘린더 구현 with FullCalendar 라이브러리

728x90

캘린더

fullCalendar 라이브러리를 이용한 일정 구현

캘린더 라이브러리를 설치해줍니다.

npm i @fullcalendar/core
npm i @fullcalendar/daygrid
npm i @fullcalendar/interaction
npm i @fullcalendar/react
npm i @fullcalendar/timegrid
// 스타일적용을 위한 bootstrap
npm i react-bootstrap

그룹 상세 페이지에 캘린더를 띄울 곳을 만들어줍니다.

import Calendar from '../components/group/calendar.jsx';

<div id="calendar-container">
    <Card id="group-calendar">
        <Calendar />
    </Card>
</div>

이제 본격적으로 calendar.jsx 파일을 만들어봅시다.
우선 달력, 일정 을 위한 라이브러리와 css파일을 불러와줍니다.

import React, { useState } from 'react';
import FullCalendar from '@fullcalendar/react';
import interactionPlugin from "@fullcalendar/interaction";
import timeGridPlugin from "@fullcalendar/timegrid";
import dayGridPlugin from '@fullcalendar/daygrid';
import '../../css/group/calendar.css';
import { Modal, Button, Form } from 'react-bootstrap';

기본설정을 해줍니다.

function Calendar() {
    return (
        <></>
    )
}

export default Calendar

앞으로의 모든 코드는 calendar함수의 중괄호 {} 내부에서 작성됩니다.

  1. 달력 그리기
    기본적인 달력을 불러오는 코드입니다.이렇게만 하면 달력이 생성되어 나타납니다.
return ( <FullCalendar plugin = dayGridPlugin initialView = "dayGridmonth" /> )
  1. 달력 세부 설정하기

불러온 플러그인들 설정하기

const plugins = [dayGridPlugin, timeGridPlugin, interactionPlugin];
return ( <FullCalendar plugins = {plugins} initialView = "dayGridmonth" />

헤더 툴바 설정하기

const plugins = [dayGridPlugin, timeGridPlugin, interactionPlugin]; 
return ( 
    <FullCalendar plugins = {plugins} 
    initialView = "dayGridmonth" 
    headerToolbar = {{ 
        left: "title", // 왼쪽에는 제목 
        right: "dayGridMonth, timeGridWeek, timeGridDay today" // 오른쪽에는 보기 전환 표시 
    }} 
    >

버튼 텍스트 설정하기

const plugins = [dayGridPlugin, timeGridPlugin, interactionPlugin]; 
return ( 
    <FullCalendar 
        plugins = {plugins} 
        initialView = "dayGridmonth" 
        headerToolbar = {{ 
            left: "title", // 왼쪽에는 제목 
            right: "dayGridMonth, timeGridWeek, timeGridDay today" // 오른쪽에는 보기 전환 표시 
        }} 
        buttonText={{ 
            today: "오늘", // 오늘 버튼의 텍스트 
            month: "월별", // 월 버튼의 텍스트 
            week: "주별", // 주 버튼의 텍스트 
            day: "일별", // 일 버튼의 텍스트 
            list: "리스트" // 리스트 보기 버튼의 텍스트 
        }} 
    />

푸터 툴바 설정하기

footerToolbar={{ // 풋터 툴바 설정 
    left: "prev", // 왼쪽에는 이전 버튼을 표시 
    center: "", // 중앙에는 아무것도 표시하지 않음 
    right: "next" // 오른쪽에는 다음 버튼을 표시 
}}
  1. 일정 설정하기
    이벤트의 상태를 표시할 변수들을 만들어줍니다.
// 상태 훅을 사용하여 이벤트, 모달 표시 여부, 새 이벤트 정보, 편집 상태, 현재 이벤트 ID를 관리
const [events, setEvents] = useState([]); // 캘린더에 표시할 이벤트 목록
const [showModal, setShowModal] = useState(false); // 모달의 표시 여부
const [newEvent, setNewEvent] = useState({ title: '', start: '', end: '', description: '' }); // 새 이벤트 정보
const [isEditing, setIsEditing] = useState(false); // 편집 모드 여부
const [currentEventId, setCurrentEventId] = useState(null); // 현재 편집 중인 이벤트의 ID

날짜를 클릭했을 때 이벤트 설정

  • 이미 설정된 이벤트를 클릭했을 때 이벤트 수정
  • 이 두가지의 함수와 UI를 제공해야 합니다.

날짜 클릭

// 날짜를 클릭했을 때 호출되는 핸들러  
const handleDateClick = (date) => {  
setNewEvent({  
title: '',  
start: date.dateStr,  
end: date.dateStr,  
description: ''  
});  
// 새 이벤트 초기화  
setIsEditing(false); // 편집 모드 해제  
setShowModal(true); // 모달 표시 };

이벤트 클릭

// 이벤트를 클릭했을 때 호출되는 핸들러  
const handleEventClick = (clickInfo) => {  
const event = clickInfo.event; // 클릭한 이벤트 정보 가져오기  
setNewEvent({  
title: event.title, // 이벤트 제목 설정  
start: event.startStr, // 이벤트 시작 시간 설정  
end: event.endStr, // 이벤트 종료 시간 설정  
description: event.extendedProps.description || '' // 이벤트 설명 설정  
});  
setCurrentEventId(event.id); // 현재 이벤트 ID 설정  
setIsEditing(true); // 편집 모드 활성화  
setShowModal(true); // 모달 표시 };

이벤트 저장

// 이벤트 저장 버튼을 클릭했을 때 호출되는 핸들러  
const handleSaveEvent = () => {  
if (isEditing) {  
// 편집 모드인 경우, 기존 이벤트를 업데이트  
setEvents(events.map(event => event.id === currentEventId ? { ...newEvent, id: currentEventId } : event ));  
} else { // 새 이벤트를 추가 setEvents(\[...events, { ...newEvent, id: Date.now().toString() }\]); }  
setShowModal(false); // 모달 닫기  
setNewEvent({ title: '', start: '', end: '', description: '' }); // 새 이벤트 정보 초기화 };

이벤트 커스터마이징

// 이벤트 콘텐츠를 커스터마이즈하는 함수  
const renderEventContent = (eventInfo) => {  
  return (
    **{eventInfo.timeText}** {/\* 이벤트의 시간 정보를 굵게 표시 _/}  
    {eventInfo.view.type !== 'dayGridMonth' && _{eventInfo.event.title}_}  
    {/_ 월 보기에서는 제목 숨김 \*/}
  );  
};

이제부턴 return 내부의 코드입니다.

추가버튼을 만들어줍니다.이 버튼을 사용하기 위해 헤더 툴바를 수정해줍니다.

headerToolbar={{  
  left: "title",  
  right: "dayGridMonth,timeGridWeek,timeGridDay today,addEventButton"  
  }}  
  customButtons={{  
  addEventButton: {  
    text: '+',  
    click: () => {  
    setNewEvent({ 
      title: '', 
      start: '', 
      end: '', 
      description: '' 
    });  
    setIsEditing(false);  
    setShowModal(true);  
  }  
  }  
}}
=

클릭이벤트에 대한 핸들러 설정


dateClick={handleDateClick}  
eventClick={handleEventClick}  
eventContent={renderEventContent} // 이벤트 콘텐츠 커스터마이즈 함수 추가  
expandRows={true} // 행 확장을 활성화하여 모든 이벤트 표시  
dayMaxEventRows={false} // 최대 이벤트 행 수를 비활성화  
dayMaxEvents={false} // 최대 이벤트 수를 비활성화

일정을 추가, 수정하는 창은 모달로 띄웁니다.
전체 모달 설정

<Modal show={showModal} onHide={() => setShowModal(false)}>  
<Modal.Header closeButton>  
<Modal.Title>{isEditing ? '일정 수정' : '새 일정 추가'}</Modal.Title>  
</Modal.Header>  
<Modal.Body>
<Form.Group controlId="formEventTitle">  
<Form.Label>제목</Form.Label>  
<Form.Control  
type="text"  
value={newEvent.title}  
onChange={(e) => setNewEvent({ ...newEvent, title: e.target.value })}  
/>  
</Form.Group>  
<Form.Group controlId="formEventDescription">  
<Form.Label>내용</Form.Label>  
<Form.Control  
type="text"  
value={newEvent.description}  
onChange={(e) => setNewEvent({ ...newEvent, description: e.target.value })}  
/>  
</Form.Group>  
<Form.Group controlId="formEventStart">  
<Form.Label>시작 시간</Form.Label>  
<Form.Control  
type="datetime-local"  
value={newEvent.start}  
onChange={(e) => setNewEvent({ ...newEvent, start: e.target.value })}  
/>  
</Form.Group>  
<Form.Group controlId="formEventEnd">  
<Form.Label>종료 시간</Form.Label>  
<Form.Control  
type="datetime-local"  
value={newEvent.end}  
onChange={(e) => setNewEvent({ ...newEvent, end: e.target.value })}  
/>  
</Form.Group>
</Modal.Body>  
<Modal.Footer>  
<Button variant="secondary" onClick={() => setShowModal(false)}>  
취소  
저장 
</Modal.Footer>

전체 코드

// calendar.jsx  
import React, { useState } from 'react';
import FullCalendar from '@fullcalendar/react';
import interactionPlugin from "@fullcalendar/interaction";
import timeGridPlugin from "@fullcalendar/timegrid";
import dayGridPlugin from '@fullcalendar/daygrid';
import '../../css/group/calendar.css';
import { Modal, Button, Form } from 'react-bootstrap';

function Calendar() {
  const [events, setEvents] = useState([]);
  const [showModal, setShowModal] = useState(false);
  const [newEvent, setNewEvent] = useState({ title: '', start: '', end: '', description: '' });
  const [isEditing, setIsEditing] = useState(false);
  const [currentEventId, setCurrentEventId] = useState(null);

  const handleDateClick = (date) => {
    setNewEvent({ title: '', start: date.dateStr, end: date.dateStr, description: '' });
    setIsEditing(false);
    setShowModal(true);
  };

  const handleEventClick = (clickInfo) => {
    const event = clickInfo.event;
    setNewEvent({
      title: event.title,
      start: event.startStr,
      end: event.endStr,
      description: event.extendedProps.description || ''
    });
    setCurrentEventId(event.id);
    setIsEditing(true);
    setShowModal(true);
  };

  const handleSaveEvent = () => {
    if (isEditing) {
      setEvents(events.map(event => 
        event.id === currentEventId ? { ...newEvent, id: currentEventId } : event
      ));
    } else {
      setEvents([...events, { ...newEvent, id: Date.now().toString() }]);
    }
    setShowModal(false);
    setNewEvent({ title: '', start: '', end: '', description: '' });
  };

  const plugins = [dayGridPlugin, timeGridPlugin, interactionPlugin];

  const renderEventContent = (eventInfo) => {
    return (
      <div>
        <b>{eventInfo.timeText}</b>
        {eventInfo.view.type !== 'dayGridMonth' && <i>{eventInfo.event.title}</i>}
      </div>
    );
  };

  return (
    <div className="fullcalendar-wrapper">
      <FullCalendar
        plugins={plugins}
        initialView="dayGridMonth"
        events={events}
        headerToolbar={{ 
          left: "title",
          right: "dayGridMonth,timeGridWeek,timeGridDay today,addEventButton"
        }}
        customButtons={{
          addEventButton: {
            text: '+',
            click: () => {
              setNewEvent({ title: '', start: '', end: '', description: '' });
              setIsEditing(false);
              setShowModal(true);
            }
          }
        }}
        footerToolbar={{
          left: "prev",
          center: "",
          right: "next"
        }}
        buttonText={{
          today: "오늘",
          month: "월별",
          week: "주별",
          day: "일별",
          list: "리스트"
        }}
        dateClick={handleDateClick}
        eventClick={handleEventClick}
        eventContent={renderEventContent} // 이벤트 콘텐츠 커스터마이즈 함수 추가
        expandRows={true} // 행 확장을 활성화하여 모든 이벤트 표시
        dayMaxEventRows={false} // 최대 이벤트 행 수를 비활성화
        dayMaxEvents={false} // 최대 이벤트 수를 비활성화
      />

      <Modal show={showModal} onHide={() => setShowModal(false)}>
        <Modal.Header closeButton>
          <Modal.Title>{isEditing ? '일정 수정' : '새 일정 추가'}</Modal.Title>
        </Modal.Header>
        <Modal.Body>
          <Form>
            <Form.Group controlId="formEventTitle">
              <Form.Label>제목</Form.Label>
              <Form.Control
                type="text"
                value={newEvent.title}
                onChange={(e) => setNewEvent({ ...newEvent, title: e.target.value })}
              />
            </Form.Group>
            <Form.Group controlId="formEventDescription">
              <Form.Label>내용</Form.Label>
              <Form.Control
                type="text"
                value={newEvent.description}
                onChange={(e) => setNewEvent({ ...newEvent, description: e.target.value })}
              />
            </Form.Group>
            <Form.Group controlId="formEventStart">
              <Form.Label>시작 시간</Form.Label>
              <Form.Control
                type="datetime-local"
                value={newEvent.start}
                onChange={(e) => setNewEvent({ ...newEvent, start: e.target.value })}
              />
            </Form.Group>
            <Form.Group controlId="formEventEnd">
              <Form.Label>종료 시간</Form.Label>
              <Form.Control
                type="datetime-local"
                value={newEvent.end}
                onChange={(e) => setNewEvent({ ...newEvent, end: e.target.value })}
              />
            </Form.Group>
          </Form>
        </Modal.Body>
        <Modal.Footer>
          <Button variant="secondary" onClick={() => setShowModal(false)}>
            취소
          </Button>
          <Button variant="primary" onClick={handleSaveEvent}>
            저장
          </Button>
        </Modal.Footer>
      </Modal>
    </div>
  );
}

export default Calendar;

css도 추가해봤습니다!

.fullcalendar-wrapper {
    width: 100%;
    border: 1px solid #2F95DC;
    border-radius: 5%;
    padding: 10px 10px 10px 10px;
    box-sizing: border-box;
    background-color: transparent;
}

.fc-day a {
    color: white;
}

.fc-day-sun a {
    color: red;
    text-decoration: none;
}

.fc-day-sat a {
    color: blue;
    text-decoration: none;
}

.fc-toolbar-title {
    color: white;
}

반응형