I T H

[MySchedule project] 15. React-big-Calendar 사용하기 (1) 본문

React + Node.js

[MySchedule project] 15. React-big-Calendar 사용하기 (1)

thdev 2024. 2. 20. 11:15
이번 시간은 react big calendar 라이브러리를 사용해서 스케줄관리에 대한 코드를 작성해 보겠다.
구현에 들어가기 앞서 react-big-calendar 라이브러리를 설치한다.
추가적으로 날짜관리 라이브러리인 moment.js도 같이 설치 해준다.

 

[ 설치 ]

npm install --save react-big-calendar
npm install moment

 

[ import ] 

- "react-big-calendar" 라이브러리와 "moment.js"를 설치 했다면 아래 부분을 import 해준다.

import { Calendar, View, DateLocalizer } from "react-big-calendar";
import moment from "moment";
import { momentLocalizer } from "react-big-calendar";
import "react-big-calendar/lib/css/react-big-calendar.css";

[ 참고자료 ]

https://www.npmjs.com/package/react-big-calendar

 

react-big-calendar

Calendar! with events. Latest version: 1.10.2, last published: 12 hours ago. Start using react-big-calendar in your project by running `npm i react-big-calendar`. There are 270 other projects in the npm registry using react-big-calendar.

www.npmjs.com


Frontend

[ src / types / myscheduleType.ts ]

- 마이스케줄 페이지에서 사용할 타입들을 지정해준다.

export interface CalendarEvent {
  title: string;
  allDay: boolean;
  start: Date;
  end: Date;
  desc: string;
  resourceId?: string;
  tooltip?: string;
  userId: string;
  calenderType: number;
}

export interface CalendarModalData {
  title: string;
  start: Date;
  end: Date;
  calendarType: number;
}

 

 

[ react-big-calendar 구조 ]

 

● 캘린더 라이브러리의 중요한 포인트 4가지

 

- localizer : 현재 시간을 가져온다. 날짜와 시간관리 라이브러리인 moment.js를 사용해서 localizer에 넣어주었다.

const localizer = momentLocalizer(moment);

 

- events : 화면에 출력될 캘린더 데이터로,  db에서 데이터를 조회해오고 조회한 데이터를 이부분에 넣어주면 된다.

- onSelectSlot : 해당부분을 선택하면 데이터를 넣을 수 있다. 데이터를 db로 전달해서 저장한다.

- onSelectEvent: 화면에 출력된 데이터를 클릭하면 해당 데이터를 볼수 있다. 이부분은 modal을 사용해서 수정, 삭제과정까지 진행하였다.

 

[ src / pages / myschedule /index.tsx ]

1. 캘린더 데이터 입력

 - 데이터를 파라미터로 보낼 useState를 만들고, useEffect hook을 통해 데이터가 입력받을때(변할때) dataInsert함수가 실행되게 한다.

- axios를 이용해 백앤드로 데이터를 전달한다. 이때 필수 값은 "title, start, end" 이므로 꼭 넣도록 하자. 추가적으로 userId와 캘린더의 타입도 같이 전달했다.

 

2. 캘린더 데이터 조회

 - db에 저장된 캘린더의 데이터들을 조회해 온다.

- 데이터 조회는 캘린더 페이지에 들어왔을때 바로 눈에 보여야 하므로, useEffect()를 사용해서 조회 함수를 넣어준다.

- 사용자에 따라 데이터가 다 다르므로, userId를 파라미터로 보내 해당 사용자에 맞는 데이터를 조회해온다. 캘린더의 타입도 같이 보내주었다.

예를 들어서 "타입 0일땐 기본스케줄, 타입 1일땐 운동스케줄, 타입2일땐 식비관리" 처럼 나눌 수 있다.

 

3. modal 컴포넌트로 연결된 수정, 삭제 기능

 - 위에서 캘린더의 구조에 대해 살펴봤을때 "onSelectEvent" 기능을 통해 클릭하면 해당 데이터를 볼 수 있는데,  데이터를 modal 컴포넌트로 같이 전달해 준다.

- modal에 대한 라이브러리에 대한 설명은  "챕터 11"에서 했으므로, 아래부분을 클릭하면 자세히 볼 수 있다.

https://devth.tistory.com/110

 

[MySchedule project] 11. 관리자 - 사용자관리 로직 mui x-data-grid와 mui modal 사용

관리자용 페이지중에서 사용자 데이터를 관리하는 페이지를 만들려고 한다. 먼저 db에 저장되있는 사용자 데이터들을 조회해 오고, 조회해온 데이터를 수정, 삭제하는 기능과 사용자를 등록하

devth.tistory.com

 

import React, { useEffect, useState } from "react";
import { Calendar, View, DateLocalizer } from "react-big-calendar";
import moment from "moment";
import { momentLocalizer } from "react-big-calendar";
import "react-big-calendar/lib/css/react-big-calendar.css";
import axiosInstance from "../../utils/axios";
import { useAddSelector } from "../../store/redux";
import { CalendarEvent } from "../../types/myscheduleType";
import CalendarModalPage from "./CalendarModalPage";

const localizer = momentLocalizer(moment);

const allViews: View[] = ["agenda", "day", "week", "month"];

interface Props {
  localizer: DateLocalizer;
}

const SelectableCalendar = ({ localizer }: Props) => {
  const userId = useAddSelector((state) => state.user?.userData?.userId);

  //modal
  const [open, setOpen] = useState(false);
  const handleUpdateOpen = () => setOpen(true);
  const [calenderModalData, setCalenderModalData] = useState<CalendarEvent>(); //모달에 넣을 데이터

  //조회한 스케줄 데이터를 담을 state
  const [events, setEvents] = useState([
    {
      id: "",
      start: "",
      end: "",
      title: "",
      calenderType: 0,
    },
  ] as unknown as CalendarEvent[]);

  //스케줄 데이터를 backend에 보낼 parameter값 관리
  const [newDateInsert, setNewDateInsert] = useState<CalendarEvent>();

  //데이터 조회 start ==================================================
  useEffect(() => {
    CalenderDataSelect();
  }, []);

  const CalenderDataSelect = async () => {
    const body = {
      userId: userId,
      calenderType: 0,
    };
    try {
      const response = await axiosInstance.post(`/mySchedule/dataSelect`, body);
      for (let index in response.data) {
        setEvents(response.data[index]);
      }
      // setEvents(response.data);
    } catch (error) {
      console.log(error);
    }
  };

  //화면에 출력할 calenderData
  const calenderData = events?.map((data) => ({
    start: new Date(data.start),
    end: new Date(data.end),
    title: data.title,
    calenderType: data.calenderType,
  }));

  //데이터 조회 end ===================================================

  // 데이터 입력 start=================================================
  useEffect(() => {
    dataInsert();
  }, [newDateInsert]);

  //onSelectSlot event
  const handleSelectNewData = ({ start, end }: CalendarEvent | any) => {
    const title = window.prompt("스케줄을 입력해주세요.");
    console.log("1");

    if (title) {
      let newEvent = {} as CalendarEvent;
      newEvent.start = moment(start).toDate();
      newEvent.end = moment(end).toDate();
      //newEvent.end = moment(end).subtract(1, "days").toDate();
      newEvent.title = title;
      newEvent.calenderType = 0;
      newEvent.userId = userId;
      //console.log(newEvent);

      setNewDateInsert(newEvent);

      //dataInsert();
    }
  };

  //데이터 입력 함수
  const dataInsert = async () => {
    // backend 전달
    const body = {
      ...newDateInsert,
    };
    console.log(body);
    if (body?.start) {
      //body로 값이 들어올때만 insert
      try {
        const response = await axiosInstance.post(
          `/mySchedule/dataInsert`,
          body
        );
        console.log("OK");
        if (response.data === "OK") {
          CalenderDataSelect(); //재조회
        }
      } catch (error) {
        console.log(error);
      }
    }
  };
  // 데이터 입력 end ==============================================

  //modal open start ==============================================
  const modalHandle = (event: any) => {
    console.log(event);
    setCalenderModalData(event); //모달에 넣을 데이터
    //console.log(calenderModalData);
    handleUpdateOpen();
  };

  //modal open end ==============================================

  return (
    <>
      <div className="my-6">
        <p className="text-[20px] font-semibold text-center my-6">
          기본 스케줄 관리
        </p>
        <p className="text-sm font-md ">
          - 달력안을 클릭하면 스케줄을 등록하실 수 있습니다.
        </p>
      </div>
      <Calendar
        selectable
        localizer={localizer}
        events={calenderData}
        defaultView="month"
        views={allViews}
        defaultDate={new Date()}
        //onSelectEvent={(event) => alert(event.title)}
        onSelectEvent={modalHandle}
        onSelectSlot={handleSelectNewData}
        startAccessor="start"
        endAccessor="end"
        titleAccessor="title"
      />
      {/* modal update 호출 */}
      {calenderModalData && (
        <CalendarModalPage
          open={open}
          setOpen={setOpen}
          calenderModalData={calenderModalData}
          CalenderDataSelect={CalenderDataSelect} //데이터 조회
        />
      )}
    </>
  );
};

const MySchedule = () => {
  return (
    <div style={{ height: "100vh" }}>
      <SelectableCalendar localizer={localizer} />
    </div>
  );
};
export default MySchedule;

 

[ src / pages / myschedule / CalendarModalPage.tsx ]

1. 캘린더 데이터 수정

 - 전달받은 props 중에서 캘린더 데이터는 useEffect의 의존성배열("[ ]")에 넣어서 데이터가 바뀔때마다 setState에 넣어 상태값을 관리해준다. 전달받은 데이터를 담은 state는 input태그의 value에 넣어 modal의 화면에 출력되게 한다.

- 사용자로부터 수정된 데이터를 전달받아 axios를 통해 백앤드로 데이터를 전달한다. 이때 userId는 필수로 전달해야된다. 사용자마다 캘린더의 데이터가 다 다르기 때문이다. userId는 redux의 useSelector를 사용해서 가져왔다.

 

2. 캘린더 데이터 삭제

- userId와 삭제할 데이터를 백앤드로 보낸다.

- 데이터가 정상적으로 삭제되었다면 props로 전달받은 데이터 재조회 함수를 실행한다.

import React, { ChangeEvent, useEffect, useState } from "react";
import { Backdrop, Box, Fade, Modal, Typography } from "@mui/material";
import axiosInstance from "../../utils/axios";
import { toast } from "react-toastify";
import { MdOutlineUpdate } from "react-icons/md";
import { CalendarEvent, CalendarModalData } from "../../types/myscheduleType";
import { style } from "../UserManagement/ModalStyle";
import { useAddSelector } from "../../store/redux";

interface ownProps {
  open: boolean;
  setOpen: any;
  calenderModalData: CalendarEvent;
  CalenderDataSelect(): void;
}

const CalendarModalPage = ({
  calenderModalData,
  open,
  setOpen,
  CalenderDataSelect,
}: ownProps) => {
  // 클릭 이벤트로 받아온 캘린더 데이터
  const [calenderData, setCalenderData] = useState<CalendarModalData | any>({
    title: "",
    start: "",
    end: "",
    calenderType: "",
  });
  const userId = useAddSelector((state) => state.user?.userData?.userId);
  const handleClose = () => setOpen(false);

  useEffect(() => {
    console.log(calenderModalData);
    setCalenderData({
      title: calenderModalData.title,
      start: calenderModalData.start,
      end: calenderModalData.end,
      calenderType: calenderModalData.calenderType,
    });
  }, [calenderModalData]);

  //onChange함수
  const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
    e.preventDefault();
    // console.log(e.target);
    const { name, value } = e.target;
    setCalenderData((prevState: CalendarModalData) => ({
      ...prevState,
      [name]: value,
    }));
  };

  //데이터 업데이트
  const calenderhandleUpdate = async (
    e: React.MouseEvent<HTMLButtonElement, MouseEvent>
  ) => {
    e.preventDefault();
    const body = {
      ...calenderData,
      userId: userId,
    };
    try {
      const response = await axiosInstance.post(
        `/mySchedule/calenderDataUpdate`,
        body
      );

      //console.log(response);
      if (response.data.title !== null && response.data.title !== "") {
        toast.success("캘린더 스케줄이 수정되었습니다.");
        handleClose();
        CalenderDataSelect(); //데이터 재조회
      }
    } catch (error) {
      console.log(error);
    }
  };

  //데이터 삭제
  const calenderhandleDelete = async (
    e: React.MouseEvent<HTMLButtonElement, MouseEvent>
  ) => {
    e.preventDefault();
    const body = {
      ...calenderData,
      userId: userId,
    };
    try {
      const response = await axiosInstance.post(
        `/mySchedule/calenderDataDelete`,
        body
      );

      console.log(response);
      if (response.data.deleteYn === true) {
        toast.success("캘린더 스케줄이 삭제되었습니다.");
        handleClose();
        CalenderDataSelect(); //데이터 재조회
      }
    } catch (error) {
      console.log(error);
    }
  };
  return (
    <div>
      {/* <Button onClick={handleOpen}>Open modal</Button> */}
      <Modal
        aria-labelledby="transition-modal-title"
        aria-describedby="transition-modal-description"
        open={open}
        onClose={handleClose}
        closeAfterTransition
        slots={{ backdrop: Backdrop }}
        slotProps={{
          backdrop: {
            timeout: 500,
          },
        }}
      >
        <Fade in={open}>
          <Box sx={style} className="rounded-md border-[1px] border-gray-300 ">
            <button onClick={handleClose} className="float-right text-md ">
              X
            </button>
            <Typography
              id="transition-modal-title"
              variant="h6"
              component="h2"
              className="text-center"
            >
              스케줄 관리
            </Typography>
            {calenderData && (
              <>
                <div className="w-full my-4">
                  <div className="my-2">
                    <label className="text-left text-sm ">
                      <MdOutlineUpdate className="inline size-5 mb-1" /> 일정을
                      수정 또는 삭제할 수 있습니다.
                    </label>
                  </div>
                  <div>
                    <input
                      className="w-full border-b-[1px] h-9 border-gray-300 outline-none"
                      type="text"
                      onChange={handleChange}
                      value={calenderData.title}
                      name="title"
                    />
                  </div>
                </div>

                <div className="w-full my-3">
                  <div className="flex gap-4 justify-center">
                    <button
                      onClick={calenderhandleUpdate}
                      className="bg-blue-600 w-6/12 text-gray-50 rounded-md h-10 hover:bg-blue-700"
                    >
                      수정
                    </button>
                    <button
                      onClick={calenderhandleDelete}
                      className="bg-blue-600 w-6/12  text-gray-50 rounded-md h-10 hover:bg-blue-700"
                    >
                      삭제
                    </button>
                  </div>
                </div>
              </>
            )}
          </Box>
        </Fade>
      </Modal>
    </div>
  );
};

export default CalendarModalPage;

 

[결과화면]

 

- 다음 챕터에서는 캘린더 라이브러리를 사용해서 내스케줄관리에 대한 백앤드 로직을 작성하겠다.