일정 컴포넌트 인터페이스 개발

Kim Young Jae·2024년 6월 20일
0

안녕하세요, 여러분!
현재 공유 달력 앱을 개발하고 있습니다. 일정 컴포넌트를 개발하면서 겪었던 경험을 공유해보고자 합니다.

분석

  1. 일정은 몇 시간 단위거나 며칠 단위입니다. 같은 날 일정이라면 "일정1"과 같이 하루 안에 UI를 그리면 되지만 "일정2"처럼 며칠인 경우 가로로 길게 이어진 UI를 가집니다.
  1. 컴포넌트의 순서도 시간이 빠른 일정이 먼저 렌더링 됩니다.

설계

  1. 시작하는 시간이 제일 빠른 일정을 먼저 그리고 그 다음 먼저 끝나는 일정을 그리도록 정렬해야합니다.

  1. 단순히 그 날짜에 일정 컴포넌트를 렌더링하는게 아니라 21일에 "일정2"처럼 한칸을 띄우고 단계를 맞추어 주어야합니다.

저는 null값을 넣어서 한칸을 띄우는 방식을 채택했습니다.

  1. 만약 20일 컴포넌트를 렌더링할 순서라면 20일 컴포넌트를 그리고 그 날짜에 일정이 있으면 일정 컴포넌트를 렌더링하는 방식입니다. 이 과정을 반복하면서 그리게 됩니다. 해당하는 데이터만 있으면 되기때문에 데이터 검색 속도를 위해 Map 자료형으로 가공하여 사용합니다.

구현

먼저 일정 데이터를 API 조회를 하면 일정이 배열 형태로 응답받습니다.

[
	{
    	"scheduleId":114,
        "name":"미용실",
        "startDate":"2024-05-24T03:35:34.385Z",
        "endDate":"2024-05-24T03:35:34.385Z",
        ...
	}
    ...
]

로직 순서
1. 데이터가 없으면 빈배열로 초기화
2. 시작 날짜와 종료 날짜가 동일하면 한개의 데이터만 저장후 바로 종료
3. 시작 날짜의 배열 Length와 종료 날짜의 배열 Length를 비교해서 큰 숫자와 동일하게 null값을 추가하여 높이를 맞춥니다
4. 시작 날짜의 startingDay와 종료 날짜의 endingDay를 true로 표시해줍니다.
5. 시작 날짜와 종료 날짜사이에 배열들도 3번과 같은 의미로 null값을 추가하여 높이를 맞춰줍니다.

const periodsData : { [date: string]: (ISchedule | null)[] } = {}

events.forEach((event) => {
      const startDate = format(event.startDate, 'yyyy-MM-dd');
      const endDate = format(event.endDate, 'yyyy-MM-dd');

      //1. 시작 날짜가 periodsData에 없으면 생성
      if (!periodsData[startDate]) {
        periodsData[startDate] = [];
      }
      //1. 종료 날짜가 periodsData에 없으면 생성
      if (!periodsData[endDate]) {
        periodsData[endDate] = [];
      }

      //2. 시작 날짜와 종료 날짜가 동일한 경우
      if (startDate === endDate) {
        periodsData[startDate].push({
          ...event,
          startingDay: true, // 일정의 시작
          endingDay: true, // 일정의 종료
        });
      } else {
        const startLength = periodsData[startDate].length;
        const endLength = periodsData[endDate].length;

        //3. 시작 날짜와 종료 날짜를 비교해서 같은 높이로 맞추기
        if (startLength > endLength) {
          while (periodsData[endDate].length < startLength) {
            periodsData[endDate].push(null);
          }
        } else if (endLength > startLength) {
          while (periodsData[startDate].length < endLength) {
            periodsData[startDate].push(null);
          }
        }

        //4. 시작 날짜에 시작 표시
        periodsData[startDate].push({
          ...event,
          startingDay: true,
          endingDay: false,
        });
        //4. 종료 날짜에 끝 표시
        periodsData[endDate].push({
          ...event,
          startingDay: false,
          endingDay: true,
        });

        //5. 시작 날짜와 종료 날짜 사이의 날짜들에 대한 표시
        let currentDate = new Date(startDate);
        currentDate.setDate(currentDate.getDate() + 1);
        while (currentDate < new Date(endDate)) {
          const middleDate = format(currentDate, 'yyyy-MM-dd');
          if (!periodsData[middleDate]) {
            periodsData[middleDate] = [];
          }

          const middleLength = periodsData[middleDate].length;

          // 시작 날짜와 중간 날짜를 비교해서 같은 높이로 맞추기
          if (startLength > middleLength) {
            while (periodsData[middleDate].length < startLength) {
              periodsData[middleDate].push(null);
            }
          }

          periodsData[middleDate].push({
            ...event,
            startingDay: false,
            endingDay: false,
          });
          currentDate.setDate(currentDate.getDate() + 1);
        }
      }
    });

결과

{
"2024-06-22":[
	null,
    {
    "scheduleId":122,
    "name":"일정2",
    "startDate":"2024-06-20T07:36:29.196Z",
    "endDate":"2024-06-22T07:36:29Z",
    "startingDay":false,
    "endingDay":true
    },
	],
	...
}

Day 컴포넌트에서 해당하는 날짜를 검색하고 null일 경우 한칸 띄우는 View를 구성하는 것으로 해결했습니다.

마무리

"공달이" 프로젝트를 하면서 가장 어려웠던 부분이라 생각합니다. 최대한 단순하게 구현을 해야 성능이라든지 유지 비용에 효율성을 높일 수 있다고 생각했습니다.
초반에 분석하고 설계를 잘 해놓으면 개발하는데 속도가 붙는다고 느끼게 되었습니다.

profile
프론트엔드 뭐시기 주로 하는 사람

0개의 댓글