티스토리 뷰

현재 프로젝트에서 달력을 만들고, 해당 날짜에 할 일을 보여주는 작업을 맡았습니다. 달력 라이브러리를 사용할까 고민했었지만, 추후 커스터마이징하기 편하고 라이브러리의 업데이트로 인해 버전 호환성 문제를 피하기 위해 직접 구현하기로 결심했습니다.

 

먼저, 아래는 작업한 최종 결과물입니다.

달력을 만들기 위해 필요한 요구사항을 정리하자면 다음과 같습니다.

1. 매달 42개의 날짜를 보여주기 (이전달의 후반날짜 + 현재달의 전체날짜 + 다음달의 초반날짜)

2. 1주의 시작은 일요일로 설정하기

3. 매주 일요일의 날짜는 빨간색으로 지정하기

4. 이전달과 다음달의 날짜는 투명도 지정하기

5. 포커싱된 날짜 배경색 지정하기 (처음에는 오늘 날짜를 자동으로 포커싱 및 배경색 지정)

6. 달력의 날짜를 완성했다면, API로 부터 받은 데이터들을 해당 날짜에 맞게 보여주기

7. 해당 날짜에 있는 각각의 데이터들의 배경색 지정하기

8. 클릭한 달력의 해당 날짜 데이터를 달력의 아랫부분에 보여주기

9. 달력의 해당 날짜를 클릭하면 달력의 아랫부분으로 이동시키기 (a태그)

 

이제 코드로 설명을 시작해보려고 합니다.

1. 가장 먼저 '현재 달'인 selectedMonth 설정하고, 이전달, 다음달로 이동할 수 있는 버튼을 만들었습니다.

const Calendar = () => {
  const [selectedYear, setSelectedYear] = useState(new Date().getFullYear());
  const [selectedMonth, setSelectedMonth] = useState(new Date().getMonth() + 1);
  
  const onPrevSelectedMonth = () => {
    if (selectedMonth === 1) {
      setSelectedYear(selectedYear - 1);
      setSelectedMonth(12);
    } else {
      setSelectedMonth(selectedMonth - 1);
    }
  };

  const onNextSelectedMonth = () => {
    if (selectedMonth === 12) {
      setSelectedYear(selectedYear + 1);
      setSelectedMonth(1);
    } else {
      setSelectedMonth(selectedMonth + 1);
    }
  };
  
  return (
  	<>
           <div>
            <button onClick={onPrevSelectedMonth}>&lt;</button>
            <p>{selectedYear}년 {selectedMonth}월</p>
            <button onClick={onNextSelectedMonth}>&gt;</button>
           </div>
    </>
  )

selectedYear는 현재 년도를 의미하며, selectedMonth는 현재 달을 의미합니다. (selectedMonth 초기값의 경우 0~11을 반환하므로 +1을 했습니다.) 이전 버튼을 클릭했을 때는 현재 달(selectedMonth)을 1씩 감소하며 1월이 됐을 때는 현재 년도(selectedYear)를 1씩 감소했습니다. 반대로 다음 버튼을 클릭했을 때는 현재 달(selectedMonth)을 1씩 증가하며 12월이 됐을 때는 현재 년도(selectedYear)를 1씩 증가했습니다.

 

2. 이제 현재 달에 맞게 날짜들을 보여줘야 합니다. 여기서 사용한 변수명은 "year: 년, month: 달, date: 일, dayoftheweek: 요일"을 의미합니다.

const Calendar = () => {
  const [selectedYear, setSelectedYear] = useState(new Date().getFullYear());
  const [selectedMonth, setSelectedMonth] = useState(new Date().getMonth() + 1);

  // 1. 선택한 달의 이전 달
  let selectedPrevYear;
  let selectedPrevMonth;
  if (selectedMonth === 1) {
    selectedPrevYear = selectedYear - 1;
    selectedPrevMonth = 12;
  } else {
    selectedPrevYear = selectedYear;
    selectedPrevMonth = selectedMonth - 1;
  }
  // 2. 선택한 달의 이전 달의 마지막 날짜의 일수 // 30,31
  const selectedPrevMonth_lastDate = new Date(selectedPrevYear,selectedPrevMonth,0).getDate();

  // 3. 선택한 달의 1일 요일 //일0 월1 화2 수3 목4 금5 토6 일7
  const selectedMonth_firstDate_DayOfTheWeek = new Date(selectedYear, selectedMonth - 1, 1).getDay();

  // 4. 선택한 달의 마지막 날짜의 일수 // 30,31
  const selectedMonth_lastDate = new Date(selectedYear,selectedMonth,0).getDate();

  // 5. 선택한 달의 다음 달
  let selectedNextYear;
  let selectedNextMonth;
  if (selectedMonth === 12) {
    selectedNextYear = selectedYear + 1;
    selectedNextMonth = 1;
  } else {
    selectedNextYear = selectedYear;
    selectedNextMonth = selectedMonth + 1;
  }
  
  // 6. 요일을 넣기 위해 배열 선언
  const dayOfTheWeekArr = ['일', '월', '화', '수', '목', '금', '토'];

  /* 7. 달력 날짜 넣기 시작!!! */
  // 7-1. 배열 42개 초기화 (1달에 날짜는 42개)
  const initArr = [];
  for (let i = 0; i < 42; i++) {
    initArr.push({ year: 0, month: 0, date: 0 });
  }

  // 7-2. 선택한 달의 이전 달 마지막 날짜 넣기
  // 42개의 배열 인덱스0부터 ~ 선택한 달의 1일의 요일까지
  for (let i = 0; i < selectedMonth_firstDate_DayOfTheWeek; i++) {
    initArr[i] = {
      year: selectedPrevYear,
      month: selectedPrevMonth,
      date:
        selectedPrevMonth_lastDate -
        selectedMonth_firstDate_DayOfTheWeek +
        i +
        1,
      dayOfTheWeek:
        dayOfTheWeekArr[
          new Date(
            selectedPrevYear,
            selectedPrevMonth - 1,
            selectedPrevMonth_lastDate -
              selectedMonth_firstDate_DayOfTheWeek +
              i +
              1
          ).getDay()
        ],
    };
  }

  // 7-3. 선택한 달의 1일부터 마지막 날짜 넣기
  // 선택한 달의 1일의 요일부터 ~ 선택한 달의 마지막 날짜의 갯수만큼
  let count = 1;
  for (
    let i = selectedMonth_firstDate_DayOfTheWeek;
    i < selectedMonth_lastDate + selectedMonth_firstDate_DayOfTheWeek;
    i++
  ) {
    initArr[i] = {
      year: selectedYear,
      month: selectedMonth,
      date: count,
      dayOfTheWeek:
        dayOfTheWeekArr[
          new Date(selectedYear, selectedMonth - 1, count).getDay()
        ],
    };
    count++;
  }

  // 7-4. 선택한 달의 다음 달 첫번째 날짜 넣기
  // 나머지 0이 있는 칸 ~ 달력의 마지막(인덱스41)까지
  count = 1;
  for (
    let i = selectedMonth_lastDate + selectedMonth_firstDate_DayOfTheWeek;
    i < 42;
    i++
  ) {
    initArr[i] = {
      year: selectedNextYear,
      month: selectedNextMonth,
      date: count,
      dayOfTheWeek:
        dayOfTheWeekArr[
          new Date(selectedNextYear, selectedNextMonth - 1, count).getDay()
        ],
    };
    count++;
  }

  const onPrevSelectedMonth = () => {
    if (selectedMonth === 1) {
      setSelectedYear(selectedYear - 1);
      setSelectedMonth(12);
    } else {
      setSelectedMonth(selectedMonth - 1);
    }
  };

  const onNextSelectedMonth = () => {
    if (selectedMonth === 12) {
      setSelectedYear(selectedYear + 1);
      setSelectedMonth(1);
    } else {
      setSelectedMonth(selectedMonth + 1);
    }
  };

  return (
	<>
           <div>
            <button onClick={onPrevSelectedMonth}>&lt;</button>
            <p>{selectedYear}년 {selectedMonth}월</p>
            <button onClick={onNextSelectedMonth}>&gt;</button>
           </div>
 	</>
  );
};

export default Calendar;

7-1 ~ 7-4번 과정을 중점적으로 살펴보겠습니다. 

7-1. 해당 달의 날짜를 넣기 위해 배열을 초기화 합니다. (배열의 인덱스는 42개)

7-2. 이전 달의 후반 날짜를 넣기 위한 작업을 진행해야 합니다. 배열의 인덱스 0부터 현재 달의 1일의 요일에 해당하는 인덱스까지 넣어야하며, 요일은 위의 과정 3번을 통해 구할 수 있습니다. 여기서 일요일을 시작으로 '일0 월1 화2 수3 목4 금5 토6 일7'을 의미합니다. 말 그대로 현재 달의 1일이 일요일이면 0을 반환, 월요일이면 1을 반환해서 배열의 인덱스0에만, 화요일이면 2를 반환해서 배열의 인덱스0~1까지만 이전 달의 후반 날짜를 넣어주면 되는 것입니다. 

7-3. 이제 배열에 현재 달의 날짜를 넣어줘야 합니다. 이어서 현재 달의 1일의 요일에 해당하는 값부터 현재 달의 마지막 일수까지 갯수만큼 값을 넣습니다.

7-4. 마지막으로 다음 달의 초반 날짜를 넣어줘야 합니다. 배열의 나머지 빈 부분부터 인덱스 41까지 다음 달의 초반 날짜를 넣어줍니다.

추가적으로 .getDay()는 해당 날짜의 요일을 숫자(일0 월1 화2 수3 목4 금5 토6 일7)로 반환해줍니다. 그래서 6번 과정에서 배열을 선언해주어 숫자를 활용해 문자를 추출했습니다.

 

결과적으로, console.log(initArr)를 찍어보면 1차원 배열에 해당 달과 관련있는 42개의 날짜가 알맞게 나왔습니다. 아래는 2022년 8월 달력 기준입니다. 

만약 이전 달 버튼과 다음 달 버튼을 누른다면, 현재 달이 바뀌면서 1차원 배열에 있는 42개의 날짜도 알맞게 변화될 것입니다.

 

3. 앞에서 구한 42개의 날짜를 담은 배열(initArr)을 달력 1행에 날짜 7개씩 보여주기 위해, 기존 1차원 배열(initArr)을 7개씩 담아 2차원 배열(resultArr)로 변환하여 table태그를 통해 뿌려주었습니다.

const Calendar = () => {
  // ...생략

  // 위에서 구한 42개의 날짜를 담은 배열(initArr)을 이용하여 7개씩 담아 2차원 배열(resultArr)로 변환
  const resultArr = [];
  for (let i = 0; i < 42; i++) {
    let newData = {
      year: initArr[i].year,
      month: initArr[i].month,
      date: initArr[i].date,
      dayOfTheWeek: initArr[i].dayOfTheWeek,
    };

    rowArr.push(newData);
    if (rowArr.length === 7) {
      resultArr.push(rowArr);
      rowArr = [];
    }
  }

  return (
    <StyledCalendar>
      ...생략
      
      <table className="calendar-table">
        <thead className="calendar-table__thead">
          <tr>
            {['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'].map(
              (columnTitle) => (
                <th className="calendar-table__columnTitle" key={columnTitle}>
                  {columnTitle}
                </th>
              )
            )}
          </tr>
        </thead>
        <tbody className="calendar-table__tbody">
          {resultArr.map((row, rowIndex) => (
            <tr key={rowIndex}>
              {row.map((day, dayIndex) => (
                  <td className="calendar-table__day">
                    <div
                      className="calendar-table__date"
                      style={{
                        color: `${day.dateColor}`,
                        opacity: `${day.dateOpacity}`,
                      }}
                    >
                      {day.date}
                    </div>                    
                  </td>
              ))}
            </tr>
          ))}
        </tbody>
      </table>
    </StyledCalendar>
  );
};

export default Calendar;

 

4. 이제 달력의 큰 틀은 구현하였으며, 스타일적인 부분을 추가하면 됩니다. 아래는 달력 작업에 대한 전체 코드입니다.

- Calendar/index.tsx - (위에서 설명한 부분으로 가장 핵심적인 부분입니다.)  

더보기
import Link from 'next/link';
import { useState } from 'react';
import { mockCalendarData } from '../../../mocks/mockCalendarData';
import { StyledCalendar } from './styles';
import {
  CalendarPropsType,
  initArrType,
  newDataType,
  resultArrType,
  rowArrType,
  selectedMonthType,
  selectedMonth_firstDate_dayOfTheWeekType,
  selectedMonth_lastDateType,
  selectedNextMonthType,
  selectedNextYearType,
  selectedPrevMonthType,
  selectedPrevMonth_lastDateType,
  selectedPrevYearType,
  selectedYearType,
} from './types';

const Calendar = ({ focusDay, onClickFocusDay }: CalendarPropsType) => {
  const [selectedYear, setSelectedYear] = useState<selectedYearType>(
    new Date().getFullYear()
  );
  const [selectedMonth, setSelectedMonth] = useState<selectedMonthType>(
    new Date().getMonth() + 1
  );

  // 선택한 달의 이전 달
  let selectedPrevYear: selectedPrevYearType;
  let selectedPrevMonth: selectedPrevMonthType;
  if (selectedMonth === 1) {
    selectedPrevYear = selectedYear - 1;
    selectedPrevMonth = 12;
  } else {
    selectedPrevYear = selectedYear;
    selectedPrevMonth = selectedMonth - 1;
  }
  //선택한 달의 이전 달의 마지막 날짜의 일수
  const selectedPrevMonth_lastDate: selectedPrevMonth_lastDateType = new Date(
    selectedPrevYear,
    selectedPrevMonth,
    0
  ).getDate();

  // 선택한 달의 1일 요일 //일0 월1 화2 수3 목4 금5 토6 일7
  const selectedMonth_firstDate_DayOfTheWeek: selectedMonth_firstDate_dayOfTheWeekType =
    new Date(selectedYear, selectedMonth - 1, 1).getDay();

  // 선택한 달의 마지막 날짜의 일수
  const selectedMonth_lastDate: selectedMonth_lastDateType = new Date(
    selectedYear,
    selectedMonth,
    0
  ).getDate();

  // 선택한 달의 다음 달
  let selectedNextYear: selectedNextYearType;
  let selectedNextMonth: selectedNextMonthType;
  if (selectedMonth === 12) {
    selectedNextYear = selectedYear + 1;
    selectedNextMonth = 1;
  } else {
    selectedNextYear = selectedYear;
    selectedNextMonth = selectedMonth + 1;
  }

  // 요일을 넣기 위해 배열 선언
  const dayOfTheWeekArr = ['일', '월', '화', '수', '목', '금', '토'];

  /* 1. 달력 날짜 넣기 시작 */
  // 1-1. 배열 42개 초기화 (1달에 날짜는 42개)
  const initArr: initArrType = [];
  for (let i = 0; i < 42; i++) {
    initArr.push({ year: 0, month: 0, date: 0, dayOfTheWeek: '' });
  }

  // 1-2. 선택한 달의 이전 달 마지막 날짜 넣기
  // 42개의 배열 인덱스0부터 ~ 선택한 달의 1일의 요일까지
  for (let i = 0; i < selectedMonth_firstDate_DayOfTheWeek; i++) {
    initArr[i] = {
      year: selectedPrevYear,
      month: selectedPrevMonth,
      date:
        selectedPrevMonth_lastDate -
        selectedMonth_firstDate_DayOfTheWeek +
        i +
        1,
      dayOfTheWeek:
        dayOfTheWeekArr[
          new Date(
            selectedPrevYear,
            selectedPrevMonth - 1,
            selectedPrevMonth_lastDate -
              selectedMonth_firstDate_DayOfTheWeek +
              i +
              1
          ).getDay()
        ],
    };
  }

  // 1-3. 선택한 달의 1일부터 마지막 날짜 넣기
  // 선택한 달의 1일의 요일부터 ~ 선택한 달의 마지막 날짜의 갯수만큼
  let count = 1;
  for (
    let i = selectedMonth_firstDate_DayOfTheWeek;
    i < selectedMonth_lastDate + selectedMonth_firstDate_DayOfTheWeek;
    i++
  ) {
    initArr[i] = {
      year: selectedYear,
      month: selectedMonth,
      date: count,
      dayOfTheWeek:
        dayOfTheWeekArr[
          new Date(selectedYear, selectedMonth - 1, count).getDay()
        ],
    };
    count++;
  }

  // 1-4. 선택한 달의 다음 달 첫번째 날짜 넣기
  // 나머지 0이 있는 칸 ~ 달력의 마지막(인덱스41)까지
  count = 1;
  for (
    let i = selectedMonth_lastDate + selectedMonth_firstDate_DayOfTheWeek;
    i < 42;
    i++
  ) {
    initArr[i] = {
      year: selectedNextYear,
      month: selectedNextMonth,
      date: count,
      dayOfTheWeek:
        dayOfTheWeekArr[
          new Date(selectedNextYear, selectedNextMonth - 1, count).getDay()
        ],
    };
    count++;
  }

  /* 2. 기존에 날짜만 들어있는 배열 -> 조건 비교하면서 데이터 넣기 */
  let rowArr: rowArrType = [];
  const resultArr: resultArrType = [];
  // 배열 42개 모두 비교하기
  for (let i = 0; i < 42; i++) {
    // 2-1. 초기화
    let newData: newDataType = {
      year: initArr[i].year,
      month: initArr[i].month,
      date: initArr[i].date,
      dayOfTheWeek: initArr[i].dayOfTheWeek,
    };

    // 2-2. 해당 날짜와 focus 날짜가 동일한지 비교
    if (
      initArr[i].year === focusDay.year &&
      initArr[i].month === focusDay.month &&
      initArr[i].date === focusDay.date
    ) {
      newData = {
        ...newData,
        focusDayBackgroundColor: '#82b0ac31',
      };
    } else {
      newData = {
        ...newData,
        focusDayBackgroundColor: 'white',
      };
    }

    // 2-3. 매주 일요일 (인덱스 7의 배수) 색깔 빨간색 지정
    if (i % 7 === 0) {
      newData = {
        ...newData,
        dateColor: 'red',
      };
    } else {
      newData = {
        ...newData,
        dateColor: 'black',
      };
    }

    // 2-4. 선택한 달이 아닌 날짜 투명도 지정
    if (initArr[i].month !== selectedMonth) {
      newData = {
        ...newData,
        dateOpacity: '0.3',
      };
    } else {
      newData = {
        ...newData,
        dateOpacity: '1',
      };
    }

    // 2-5. API로 받은 data 비교하기 (해당 data가 있는 날짜만 data 넣기)
    for (let k = 0; k < mockCalendarData.length; k++) {
      if (
        String(initArr[i].year) === mockCalendarData[k].year &&
        String(initArr[i].month) === mockCalendarData[k].month &&
        String(initArr[i].date) === mockCalendarData[k].date
      ) {
        newData = {
          ...newData,
          data: mockCalendarData[k].data,
        };
      }
    }
    if (!newData.data) {
      newData = {
        ...newData,
        data: [],
      };
    }

    rowArr.push(newData);

    // 2-6. rowArr 배열을 이용해서 한 행에 7개 날짜만 넣는 2차원 배열로 변환하기
    if (rowArr.length === 7) {
      resultArr.push(rowArr);
      rowArr = [];
    }
  }

  const onPrevSelectedMonth = () => {
    if (selectedMonth === 1) {
      setSelectedYear(selectedYear - 1);
      setSelectedMonth(12);
    } else {
      setSelectedMonth(selectedMonth - 1);
    }
  };

  const onNextSelectedMonth = () => {
    if (selectedMonth === 12) {
      setSelectedYear(selectedYear + 1);
      setSelectedMonth(1);
    } else {
      setSelectedMonth(selectedMonth + 1);
    }
  };

  return (
    <StyledCalendar>
      <div className="calendar-title">
        <button
          className="calendar-title__button"
          onClick={onPrevSelectedMonth}
        >
          &lt;
        </button>
        <p className="calendar-title__content">
          {selectedYear}년 {selectedMonth}월
        </p>
        <button
          className="calendar-title__button"
          onClick={onNextSelectedMonth}
        >
          &gt;
        </button>
      </div>
      <table className="calendar-table">
        <thead className="calendar-table__thead">
          <tr>
            {['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'].map(
              (columnTitle) => (
                <th className="calendar-table__columnTitle" key={columnTitle}>
                  {columnTitle}
                </th>
              )
            )}
          </tr>
        </thead>
        <tbody className="calendar-table__tbody">
          {resultArr.map((row, rowIndex) => (
            <tr key={rowIndex}>
              {row.map((day, dayIndex) => (
                <Link
                  href={`#${day.year}-${day.month}-${day.date}`}
                  key={dayIndex}
                >
                  <td
                    className="calendar-table__day"
                    onClick={() =>
                      onClickFocusDay({
                        clickFocusYear: day.year,
                        clickFocusMonth: day.month,
                        clickFocusDate: day.date,
                        clickFocusDayOfTheWeek: day.dayOfTheWeek,
                        clickFocusData: day.data,
                      })
                    }
                    style={{
                      backgroundColor: `${day.focusDayBackgroundColor}`,
                    }}
                  >
                    <div
                      className="calendar-table__date"
                      style={{
                        color: `${day.dateColor}`,
                        opacity: `${day.dateOpacity}`,
                      }}
                    >
                      {day.date}
                    </div>
                    <div className="calendar-table__schedule">
                      {day.data &&
                        day.data.map(
                          (scheduleItem: any, scheduleItemIndex: number) => (
                            <div
                              className="calendar-table__scheduleItem"
                              key={scheduleItemIndex}
                              style={{
                                backgroundColor: `${scheduleItem.backgroundColor}`,
                                opacity: `${day.dateOpacity}`,
                              }}
                            >
                              {scheduleItem.title.length > 14
                                ? scheduleItem.title.slice(0, 14) + ' ...'
                                : scheduleItem.title}
                            </div>
                          )
                        )}
                    </div>
                  </td>
                </Link>
              ))}
            </tr>
          ))}
        </tbody>
      </table>
    </StyledCalendar>
  );
};

export default Calendar;

 

- FocusDay/index.tsx - (달력 아래의 해당 날짜에 데이터를 보여주는 곳에 해당합니다.)

더보기
import { StyledFocusDay } from './styles';
import { FocusDayPropsType } from './types';
import { IoAddCircle } from 'react-icons/io5';
import { HiOutlineLink } from 'react-icons/hi';
import { AiOutlineClose } from 'react-icons/ai';
import { MdOutlineModeEditOutline } from 'react-icons/md';
import Link from 'next/link';

const FocusDay = ({ focusDay }: FocusDayPropsType) => {
  return (
    <StyledFocusDay
      className="focusday"
      id={`${focusDay.year}-${focusDay.month}-${focusDay.date}`}
    >
      <div className="focusday__header">
        <span className="focusday__date">
          {focusDay.year}.{focusDay.month}.{focusDay.date}{' '}
          {focusDay.dayOfTheWeek}
        </span>
        <button className="focusday__button focusday__addBtn">
          <IoAddCircle />
        </button>
      </div>
      <div>
        {focusDay.data.map((scheduleItem: any, scheduleItemIndex: number) => (
          <div
            className="focusday__scheduleItem"
            key={scheduleItemIndex}
            style={{
              backgroundColor: `${scheduleItem.backgroundColor}`,
            }}
          >
            <div className="focusday__title">{scheduleItem.title}</div>
            <div className="focusday__content">{scheduleItem.content}</div>
            {scheduleItem.url && (
              <Link href={scheduleItem.url}>
                <button className="focusday__button focusday__urlBtn">
                  <HiOutlineLink />
                </button>
              </Link>
            )}
            <button className="focusday__button focusday__editBtn">
              <MdOutlineModeEditOutline />
            </button>
            <button className="focusday__button focusday__deleteBtn">
              <AiOutlineClose />
            </button>
          </div>
        ))}
      </div>
    </StyledFocusDay>
  );
};

export default FocusDay;

 

- Schedule/index.tsx -

더보기
import { useEffect, useState } from 'react';
import Calendar from '../common/Calendar';
import FocusDay from '../common/FocusDay';
import { StyledSchedule } from './styles';
import { FocusDayType, onClickFocusDayType } from './types';

const Schedule = () => {
  const [focusDay, setFocusDay] = useState<FocusDayType>({
    year: 0,
    month: 0,
    date: 0,
    dayOfTheWeek: '',
    data: [],
  });

  useEffect(() => {
    //(API 요청) 처음에 오늘 날짜의 데이터 받아오기
    const dayOfTheWeekArr = ['일', '월', '화', '수', '목', '금', '토'];

    setFocusDay({
      year: new Date().getFullYear(),
      month: new Date().getMonth() + 1,
      date: new Date().getDate(),
      dayOfTheWeek: dayOfTheWeekArr[new Date().getDay()],
      data: [],
    });
  }, []);

  const onClickFocusDay: onClickFocusDayType = ({
    clickFocusYear,
    clickFocusMonth,
    clickFocusDate,
    clickFocusDayOfTheWeek,
    clickFocusData,
  }) => {
    setFocusDay({
      year: clickFocusYear,
      month: clickFocusMonth,
      date: clickFocusDate,
      dayOfTheWeek: clickFocusDayOfTheWeek,
      data: clickFocusData,
    });
  };

  return (
    <StyledSchedule>
      <Calendar focusDay={focusDay} onClickFocusDay={onClickFocusDay} />
      <FocusDay focusDay={focusDay} />
    </StyledSchedule>
  );
};

export default Schedule;

 

- Schedule/types.ts -

더보기
export type FocusDayType = {
  year: number;
  month: number;
  date: number;
  dayOfTheWeek: string;
  data: any;
};

export type onClickFocusDayType = ({
  clickFocusYear,
  clickFocusMonth,
  clickFocusDate,
  clickFocusDayOfTheWeek,
  clickFocusData,
}: {
  clickFocusYear: number;
  clickFocusMonth: number;
  clickFocusDate: number;
  clickFocusDayOfTheWeek: string;
  clickFocusData: any;
}) => void;

 

- Calendar/styles.tsx - 

더보기
import styled from '@emotion/styled';

export const StyledCalendar = styled.div`
  padding-bottom: 30px;
  .calendar-title {
    margin: 20px 0 10px;
    display: flex;
    justify-content: center;
    align-items: center;
    &__button {
      border: 1px solid lightgray;
      cursor: pointer;
      background-color: white;
      color: lightgray;
      font-size: 15px;
      border-radius: 50%;
      margin: 0 30px;
      height: 25px;
      width: 25px;
    }
    &__content {
      font-size: 17px;
      font-weight: 600;
    }
  }
  .calendar-table {
    margin: 0 auto;
    border-collapse: collapse;
    &__thead {
      border: 1px solid #d3d3d35c;
      padding: 30px;
    }
    &__columnTitle {
      width: 130px;
      text-align: start;
      padding-left: 3px;
      font-size: 14px;
    }
    &__day {
      width: 130px;
      height: 100px;
      overflow: auto;
      border: 1px solid #d3d3d35c;
      position: relative;
      cursor: pointer;
    }
    &__date {
      position: absolute;
      left: 3px;
      top: 3px;
      font-size: 13px;
    }
    &__schedule {
      position: absolute;
      left: 0;
      right: 0;
      top: 25px;
    }
    &__scheduleItem {
      color: white;
      border-radius: 3px;
      margin: 2px 1px;
      font-size: 11px;
      font-weight: 700;
      padding: 5px 3px;
    }
  }

  @media screen and (max-width: 768px) {
    .calendar-title {
      margin: 10px 0 0;
      &__button {
        border: none;
        font-size: 18px;
      }
      &__content {
        font-size: 14px;
      }
    }
    .calendar-table {
      &__columnTitle {
        font-size: 10px;
      }
      &__date {
        font-size: 10px;
      }
      &__scheduleItem {
        font-size: 8px;
      }
    }
  }
`;

 

- Calendar/types.ts - 

더보기
import { FocusDayType, onClickFocusDayType } from '../../Schedule/types';

export type CalendarPropsType = {
  focusDay: FocusDayType;
  onClickFocusDay: onClickFocusDayType;
};

export type selectedPrevYearType = number;
export type selectedPrevMonthType = number;
export type selectedYearType = number;
export type selectedMonthType = number;
export type selectedNextYearType = number;
export type selectedNextMonthType = number;

export type selectedPrevMonth_lastDateType = number;
export type selectedMonth_firstDate_dayOfTheWeekType = number;
export type selectedMonth_lastDateType = number;

export type initArrType = Array<{
  year: number;
  month: number;
  date: number;
  dayOfTheWeek: string;
}>;

export type newDataType = {
  year: number;
  month: number;
  date: number;
  dayOfTheWeek: string;
  focusDayBackgroundColor?: string;
  dateColor?: string;
  dateOpacity?: string;
  data?: any;
};

export type rowArrType = Array<newDataType>;
export type resultArrType = Array<rowArrType>;

 

- FocusDay/styles.tsx - 

더보기
import styled from '@emotion/styled';

export const StyledFocusDay = styled.div`
  width: 910px;
  margin: 0 auto;
  border-radius: 10px;
  padding: 20px;
  background-color: #e4f2e579;
  .focusday {
    &__header {
      margin-bottom: 20px;
      position: relative;
      text-align: center;
    }
    &__date {
      font-size: 17px;
      font-weight: 700;
    }
    &__button {
      border: none;
      background-color: inherit;
      cursor: pointer;
    }
    &__addBtn {
      font-size: 22px;
      color: gray;
      position: absolute;
      right: 0px;
      top: 0px;
    }
    &__urlBtn {
      position: absolute;
      top: 20px;
      right: 62px;
      font-size: 16px;
      color: white;
      height: 23px;
      width: 30px;
    }
    &__editBtn {
      position: absolute;
      top: 20px;
      right: 35px;
      font-size: 16px;
      color: white;
      height: 23px;
      width: 30px;
    }
    &__deleteBtn {
      position: absolute;
      top: 20px;
      right: 10px;
      font-size: 16px;
      color: white;
      height: 23px;
      width: 30px;
    }
    &__scheduleItem {
      margin: 10px 0;
      border-radius: 10px;
      color: white;
      font-weight: 700;
      padding: 10px 95px 10px 10px;
      position: relative;
    }
    &__title {
      font-size: 14px;
      margin-bottom: 4px;
    }
    &__content {
      font-size: 13px;
    }
  }

  @media screen and (max-width: 950px) {
    width: 100%;
    border-radius: 20px;
  }

  @media screen and (max-width: 768px) {
    .focusday {
      &__date {
        font-size: 14px;
      }
      &__addBtn {
        font-size: 17px;
      }
      &__urlBtn {
        font-size: 14px;
        right: 54px;
        width: 26px;
      }
      &__editBtn {
        font-size: 14px;
        right: 28px;
        width: 26px;
      }
      &__deleteBtn {
        font-size: 14px;
        right: 3px;
        width: 26px;
      }
      &__scheduleItem {
        margin: 5px 0;
        padding: 10px 85px 10px 10px;
      }
      &__title {
        font-size: 13px;
      }
      &__content {
        font-size: 11px;
      }
    }
  }
`;

 

- FocusDay/types.ts - 

더보기
import { FocusDayType } from '../../Schedule/types';

export type FocusDayPropsType = {
  focusDay: FocusDayType;
};

작업을 진행하면서 마치 알고리즘 문제를 푸는 느낌이 들었습니다. 예를 들어 1달을 기준으로 1주일의 시작은 일요일로 설정하는데 날짜를 어떻게 순서대로 배치할지, 일요일마다 빨간색 설정을 어떻게 할지, 매달 마지막 날짜 계산하기 등등 달력을 만들기 위해 많은 문제들을 직면하며 하나씩 해결해가는 과정, 예외상황에 대한 생각 등 '생각 -> 문제발견 -> 해결' 과정을 반복적으로 진행했습니다. 

 

댓글
Total
Today
Yesterday