grep

Engineering

카카오톡 예약하기에서 그려 본 캘린더

joy.hwan카카오

2026년 4월 23일

원문에서 보기 ↗

안녕하세요 저는 카카오톡 예약하기라는 서비스에서 FE 개발을 담당하고 있는 Joy 라고 합니다.

이 글에서는 우리에게 친근한 캘린더를 직접 만들어보면서 경험한 이야기를 해보자 합니다.

WHY ?

판매자들을 위한 예약하기 파트너센터 서비스에는 들어온 예약을 모아볼 수 있는 예약 관리라는 탭이 존재합니다.

이 예약 관리 탭에서는 무수히 많은 수의 예약을 카드 형태로 보여주고 있는데요.

이러한 카드 목록 형태의 UI는 한눈에 재고와 예약 건을 파악하기 어려워 판매자가 이용하기에 불편하다는 의견이 나왔고 이러한 정보들을 캘린더의 형태로 보여주면 좋겠다는 니즈가 있었습니다.

이런 니즈 속에 재고와 예약을 한 눈에 관리할 수 있는 예약 캘린더를 만들자는 과제가 시작됐습니다.

요구사항

기획자가 생각했던 캘린더의 형태는 타임 블록형이라고 불리는 UI의 캘린더였습니다.

쉽게 접할 수 있는 타임 블록 캘린더는 구글 캘린더 애플 캘린더가 있는데요.

출처 : https://workspace.google.com/products/calendar/?from=gafb-calendar-global_nav-ko

기획의 요구사항은 심플하게 두 가지였습니다.

  1. 한 시간대에는 최대 10개의 예약이 들어올 수 있고 한 예약 건은 최대 6시간의 소요 시간을 가질 수 있다.

  2. 예약 블록들이 비어 있는 공간 없이 최대한 잘 보이게 배치되어야 한다.

이 간단해 보이는 요구사항이 고난 길의 시작이었을 줄은 개발하기 전까지 깨닫지 못했었습니다.

이 요구사항과 함께 예시로 전달받은 이미지는 아주 이쁜 블록들의 나열이었습니다.

하지만 내려오는 데이터의 형태는 아래와 같았는데요.

{
// ...예약 정보
"startDateTime": "2026-02-05T17:00:00",
"endDateTime": "2026-02-05T18:00:00"
}

예약 건들의 시작 시간과 끝 시간만을 가지고 위의 요구사항을 충족해야 했습니다.

고민의 시작

고민은 2번의 요구사항이었습니다. 비어 있는 공간 없이 최대한 잘 보이게 하려면 배치에 대한 고민이 필요했는데요.

예약 건들이 기획자분이 작성해 준 대로만 들어온다면 얼마나 행복한 일일까요 ?

하지만 개발자는 늘 최악의 케이스에 대해 고민하고 대응해야 하는 직업입니다

어떤 날의 캘린더에는 가장 긴 시간의 예약이 가운데에 오게 될 수도 있고

어떤 날의 캘린더에는 예약이 계단의 형태로 오게 될 수도 있고

블록의 배치 와 확장의 관점에서 캘린더를 바라봐야 했습니다.

배치 전략

1. 이용 시간이 이른 순으로 정렬한다.

가장 먼저 고민했던 내용은 많고 복잡한 순서의 예약 건들이 들어왔을 때 어떤 순서로 배치해야 이상적일까를 고민했는데요.

우선 가장 먼저 판매자의 관점에서 “가게를 열고 어떤 예약 건을 가장 먼저 확인하고 싶을까?”를 고민해 봤습니다.

저는 하루를 시작하는 첫 예약이 판매자의 관점에서 가장 먼저 확인하고 싶은 예약 건일 거라 생각했는데요.

기대하지 않는 케이스기대하는 케이스

예약이 들어오는 순서대로 배치하기보단 이용 시간이 이른 순서대로 배치하는 것이 판매자의 관점에서 더 편한 배치일 것이라 생각했습니다.

출처 : https://uxdesign.cc/6-visual-design-principles-that-ux-designers-should-be-aware-of-f609f9293703

블록을 배치를 할 때 시각적 우선순위에 대해서도 고민했었는데요.

사람의 시선은 왼쪽 위부터 시작해서 오른쪽 아래로 내려가는 것이 익숙하다고 합니다. 이런 우선순위가 있기 때문에 가장 먼저 확인해야 할 정보를 왼쪽 위에 배치하고 싶었습니다.

그래서 예약하기 캘린더의 첫 번째 규칙인 ‘이용 시간이 이른 순으로 정렬한다’ 를 결정했습니다.

2. 같은 시간 내 소요 시간이 긴 순으로 정렬한다.

두 번째 고민 포인트는 “어떻게 하면 최대한 잘 보이게 확장할 수 있을까?”에 대한 고민이었습니다.

하나의 시간대에 예약 건이 3개가 들어왔다고 가정해 봅시다.

확장 전확장 후

위 경우에는 모든 예약 건이 사이좋게 균등 확장해 영역을 채울 수 있습니다.

이런 사이좋은 상황만 있다면 얼마나 캘린더 그리기가 간단했을까요 ?

들어올 수 있는 예약 건은 최소 1칸 최대 6칸을 차지할 수 있기 때문에 이전에 발생한 예약으로 인해 블록의 확장이 막힐 수도 있게 됩니다.

이런 케이스를 방지하기 위해 확장을 가로막을 수 있는 긴 예약 건들을 최대한 앞쪽에 배치하기로 결정했습니다.

그렇게 되면 다른 예약 건들은 그 이후에 배치되게 되며 나만의 공간을 확보할 수 있게 됩니다.

기대하지 않는 케이스기대하는 케이스

이렇게 두 번째 규칙인 ‘같은 시간 내 소요 시간이 긴 순으로 정렬한다’ 를 결정했습니다.

확장 전략

이제는 나열된 예약 건들을 어떻게 하면 사이좋게 확장할 수 있을까에 대해 고민해야 할 차례입니다.

우선 확장은 그래프라는 자료구조를 사용해 확장하고자 결정했습니다.

아래와 같은 형태의 예약 건을 예시로 설명해 보도록 하겠습니다.

예시 그림

이 그림에서 UI를 모두 지우고 예약 건들을 연결하면 아래와 같은 그래프의 그림으로 변경할 수 있습니다.

불필요한 UI 제거예약 건을 그래프로 구성

그래프의 각 노드는 아래와 같은 데이터들을 포함할 예정입니다.

class  BookingNode {
private  depth:  number  =  0; // 그래프의 Depth

private  maxLength:  number  =  0; // 끝 예약까지 도달하는 최대 거리

private  nextBooking:  BookingNode[]  =  []; // 인접한 다음 예약 포인터
private  prevBooking:  BookingNode[]  =  []; // 인접한 이전 예약 포인터
}

들어오는 예약 건들을 nodes라는 배열에 하나씩 넣으며 우선 데이터를 구성해 보도록 하겠습니다.

prevBooking, nextBooking 데이터의 구성

그다음 DFS를 통해 각 노드마다 레벨과 끝 예약까지 도달하는 최대 거리를 구했습니다.

DFS를 통한 거리 계산완성된 데이터의 구성

이제 확장을 위한 준비를 모두 마쳤습니다.

이제 위 값을 가지고 left 값을 구해 위치시키고 width 값을 통해 확장해 보도록 하겠습니다.

그래프를 기준으로 가장 왼쪽에 붙어있는 노드(depth = 0)를 먼저 기준으로 잡고 확장해야 하는데요.

아래가 left와 width를 구하는 공식입니다.

// left : max(prevBooking 의 left 값 + prevBooking 의 width 값)
this.left  =  this.prevBooking.reduce((acc, n) =>  Math.max(acc, n.left  +  n.width), 0);

// width : ((100 - left 값) / (1 + 끝 예약까지 도달하는 최대 거리)) / 100
this.width  = ((100  -  this.left) / (1  +  this.maxLegnth) *  100) /  100;

이 공식을 기반으로 각 노드의 left와 width를 계산해 보도록 하겠습니다.

노드의 left와 width 계산

이 값들을 기준으로 노드를 그려본다면?

확보된 노드들의 공간

이렇게 나열된 예약 건들을 사이좋게 확장할 수 있었습니다.

예외 케이스

이렇게 완성된 줄 알았던 캘린더에 이런저런 형태의 데이터를 넣어보다가 예외 케이스를 발견하게 됐는데요.

문제가 발생하는 케이스위 공식을 통해 완성된 그림

상위에 위치하는 루트 노드로 인해 이미 너비가 결정된 그래프의 경우 하위의 루트 노드가 DFS를 돌아도 끝까지 채워지지 않고 공간이 남아있는 경우가 존재합니다.

즉, 노드들이 모두 연결되어 있지만 위 루트 노드의 maxLength가 더 길고

아래 루트 노드의 maxLength가 더 짧은 경우 문제가 발생하게 됩니다.

이 경우 확장할 수 있는 남은 공간을 찾고 남은 공간만큼 연결된 노드들을 확장해 주는 작업을 거쳐야 합니다.

이때 남은 공간이 여러 군데라면 최소 공간만큼 확장이 필요한 노드들을 넓혀주는 방식을 택했습니다.

다음 노드의 LEFT 값이 현재 노드의 LEFT + WIDTH 값보다 큰 경우 인접한 노드와 떨어져 있다고 판단했습니다.

F 를 기준으로 확보 공간 계산F 와 연결된 모든 노드 최소 공간만큼 확장
I 를 기준으로 확보 공간 계산I 와 연결된 모든 노드 최소 공간만큼 확장

이런 확장의 과정을 거치며 기획 의도에 맞는 예약 캘린더를 만들어낼 수 있었습니다.

맺으며

이번 타임 블록형 캘린더를 제작하며 참 많은 우여곡절이 있었던 것 같습니다.

처음에 해당 UI 캘린더를 봤을 때는 “이쁘고 심플하네 간단하겠는데?”라는 생각을 하고 부딪혔는데 참 많은 고민을 하게 됐고, 동료들과 티타임 때 회의실에 모여 “더 좋은 알고리즘이 없을까?” 고민도 많이 했었습니다.

캘린더를 만들 당시 취미생활로 러닝을 자주 했었는데 뛰는 중에 “아! 이 케이스 이 알고리즘으로 안 되겠는데 ?..” 이런 생각이 참 많이 스쳐 지나갔고, 저를 좌절시켰던 기억이 납니다.

FE 개발자는 사용자와 가장 맞닿아 있는 개발자인데요. 이 말은 “데이터가 어떻게 보여야 하는가?”에 대한 책임과 권리가 있는 개발자라는 말이 아닐까 싶습니다.

이번 캘린더를 제작하며 이러한 책임과 권리에 대해 참 많이 배우고 성장할 수 있는 계기가 됐던 것 같습니다.

제가 예약하기에서 만든 캘린더는 완성된 형태가 아닌 이제 막 알을 깨고 나온 형태라고 생각이 듭니다.

앞으로 서비스를 진행하며 버그가 발견되면 수정하고 더 좋은 알고리즘이 생각나면 고치고 하며 애정을 가지고 완성된 형태의 캘린더로 나아가게 할 생각입니다.

이 글이 타임 블록 UI 캘린더를 만들려고 하는 분들 또는 “이런 알고리즘 배워서 어디다 쓰나?”하는 분들께 조금이나마 도움이나 위안이 됐으면 합니다.

함께 서비스를 운영하고 고민했던 동료들에게 감사의 인사를 드립니다