유피테르
유피테르
🧙 AI 위자드
🏅 베스트오브베스트

[병원 휴가관리 시스템 #3] 베타테스트를 통해 부딪히며 발견한 버그들을 클로드 코드와 함께 수정하기

이전 병원 휴가관리 시스템 #1 병원 직원들의 연차, 휴일관리 시스템 구축, 병원 휴가관리 시스템 #2 # Github에 올리고 Vercel에 배포하는 자동 파이프라인 구축기에 이은 3번째 글입니다.


실제 데이터로 부딪히며 발견한 버그들

첫 가동에 성공했지만, 베타테스트를 해보니 예상치 못한 문제들이 연달아 나왔습니다. 여기서 Claude Code의 진가가 발휘됐습니다.

문제 1: 캘린더에 반차(및 휴가, 휴일)가 표시되지 않는 문제

"3월 31일에 오전 반차를 신청했는데 캘린더에 아무것도 안 나와."

원인을 파고들어 보니 Airtable의 날짜 필드 비교 연산자 문제였습니다. Airtable 수식에서 {Date}='2025-03-31'처럼 날짜를 직접 비교하면 조건이 맞아도 빈 결과를 반환하는 경우가 있었습니다. >=, <= 연산자도 마찬가지였습니다.

해결 방법: Airtable에서는 상태(status)만 필터링하고, 날짜 필터링은 JavaScript에서 처리하도록 전면 교체.

// 변경 전 (Airtable 수식에 날짜 조건 포함 → 오작동)
filterByFormula: `AND({Date}='${date}',{Status}='confirmed')`

// 변경 후 (JS에서 날짜 필터)
filterByFormula: `{Status}='confirmed'`
// → 결과를 받은 뒤 .filter((l) => l.date === date) 적용

같은 패턴의 문제가 getLeaveRequestsByDate, getDayOffByDate, getLeaveRequestsByDateRange, getDayOffByDateRange 등 여러 함수에 퍼져 있었고, 모두 동일한 방식으로 클로드 코드가 수정했 줬습니다.

문제 2: 직원별 연차 조회가 항상 빈 결과

"내 연차현황 페이지에 신청한 연차가 하나도 안 떠."

이번엔 Airtable의 ARRAYJOIN 버그였습니다. 연차 테이블에서 직원 ID로 필터링하기 위해 다음 수식을 썼는데:

FIND('rec직원ID', ARRAYJOIN({Employee}))

Airtable이 ARRAYJOIN({Employee})의 결과로 레코드 ID(recXXX) 대신 표시 이름(홍길동)을 반환하는 것이었습니다. 그러니 ID로 아무리 찾아도 매칭이 안 됐죠.

해결 방법: ARRAYJOIN 수식을 완전히 제거하고 전체 레코드를 불러온 뒤 JavaScript에서 필터링.

// 변경 전
filterByFormula: `FIND('${employeeId}', ARRAYJOIN({Employee}))`

// 변경 후
// 전체 레코드 fetch → JS에서 필터
.filter((l) => l.employeeId === employeeId)

이 패턴이 getLeaveRequestsByEmployee, getDayOffByEmployee, getDayOffByWeek 등 여러 함수에 반복됐고 클로드 코드는 이를 알아서 일괄 수정했습니다.

문제 3: 팀 충돌 체크가 작동하지 않는다

"같은 팀인 두 직원이 같은 날 연차를 신청했는데 아무 오류 없이 통과됐어."

날짜 필터가 제대로 동작하지 않으니, 팀 충돌 체크 로직도 당연히 빈 목록을 기준으로 비교하고 있었습니다. 날짜 필터 수정 후 팀 충돌 체크도 정상 작동하게 됐습니다.

추가로, 오류 메시지도 개선했습니다. 기존에는 "오전 시간대에 이미 휴가 신청 중입니다"처럼 슬롯 기준으로 메시지가 나왔는데, 사용자 입장에서는 혼란스러웠습니다.

"관리자가 전일 연차를 신청한 상태인데, 이수진이 오전 연차를 신청하면 '같은 팀원이 이미 연차(오전)을 신청했습니다'라고 나와. 관리자가 전일이니까 (전일)이라고 나와야 하는 거 아닌가?"

해결: 메시지를 슬롯 기준이 아닌 기존 신청의 타입 기준으로 변경.

const typeLabel: Record<string, string> = { full: '전일', am: '오전', pm: '오후' };

// 기존 연차의 타입으로 메시지 생성
const label = typeLabel[leave.type]; // '전일', '오전', '오후'
const suffix = label === '오후' ? '를' : '을';
return validationErrorResponse(`같은 팀원이 이미 연차(${label})${suffix} 신청했습니다`);

3단계: UI 세부 조정 — 캘린더 표시 개선

캘린더에 이름만 표시되니 전일인지 반차인지 구분이 안 됐습니다.

컴퓨터 화면에 표시된 달력의 스크린샷

"캘린더에 이름 옆에 (오전), (오후), (전일)을 붙여줘. 띄어쓰기 없이 붙여서 '홍길동(오전)'처럼."

CalendarView 컴포넌트에서 이벤트 타입별로 suffix를 계산해 이름에 붙이도록 수정했습니다.

const suffix =
  evt.type === 'leave_am' ? '(오전)' :
  evt.type === 'leave_pm' ? '(오후)' :
  evt.type === 'leave_full' ? '(전일)' :
  evt.type === 'dayoff' ? (
    evt.dayoffType === 'am' ? '(오전)' :
    evt.dayoffType === 'pm' ? '(오후)' : '(전일)'
  ) : '';

return `${name}${suffix}`; // 예: 홍길동(오전)

휴일 신청 화면에서도 "반차"라는 표현 대신 "오전 휴일", "오후 휴일"로 자연스러운 언어로 바꿨습니다.

컴퓨터 화면에 표시된 달력의 스크린샷

4단계: 관리자 기능 고도화

기본 시스템이 동작하게 된 이후, 관리자 편의 기능을 요청했습니다.

항목 목록을 보여주는 컴퓨터 화면의 스크린샷

"관리자가 직원의 연차를 직접 부여하거나 취소할 수 있어야 해. 직원 목록에서 각 직원의 연차·휴일 현황을 보고 조작할 수 있으면 좋겠어."

Claude Code가 설계한 관리자 직원 관리 페이지 구조:

직원 목록 테이블
└── 직원 행마다 [연차·휴일] 버튼
    └── 클릭 시 모달 오픈
        ├── 연차 요약 (전체/사용/잔여)
        ├── 탭: [연차 내역] / [휴일 내역]
        │   ├── 날짜순 정렬 목록
        │   └── 각 항목에 [취소] 버튼
        └── [연차 부여] 버튼 → 서브 모달
            ├── 날짜 선택
            ├── 종류 선택 (전일/오전/오후)
            └── 저장

아래와 같이 직원 관리 페이지에서 관리자가 직접 연차, 휴일을 편집할 수 있도록 했습니다.

한국어 숫자 목록이 표시된 컴퓨터 화면의 스크린샷


팀 관리 페이지도 개선했습니다.

"팀 카드를 클릭하면 그 팀 팀원들의 연차·휴일 현황이 나오면 좋겠어."

팀 카드 클릭 시 모달이 열리고, 팀원별로 연차 전체/사용/잔여와 확정된 연차·휴일 건수를 테이블로 표시합니다. 3개 API(/api/admin/employees, /api/admin/leaves, /api/admin/dayoffs)를 병렬로 호출해 JavaScript에서 집계하는 방식입니다.

Google 검색 페이지의 스크린샷

✅ 결과 (After)

항목

이전

이후

연차 관리 방식

엑셀 수작업

직원 자체 신청·조회

팀 충돌 방지

관리자가 직접 확인

시스템 자동 차단

캘린더 현황 파악

불가

실시간 통합 캘린더

관리자 권한

없음

연차 부여·취소·직원 승인

개발 기간

—

약 2주 (비개발자)

배포 방법

—

Vercel CLI (vercel --prod)


느낀 점

처음엔 "코딩을 모르는 내가 이걸 만들 수 있을까?" 반신반의했습니다. 결론적으로 가능했지만, 단순히 "만들어줘" 한 마디로 완성된 건 아니었습니다. 하지만 그렇다고 해서 제가 직접 손으로 짠 코드는 단 1줄도 없었습니다. 정말 저는 느낌적인 느낌으로 클로드 코드에게 지시만 했고, 클로드가 알아서 코드를 짜 주었습니다.

잘 된 것: 초기 설계, 반복적인 기능 추가, 코드 리팩토링은 정말 빠릅니다. "이런 기능 넣어줘"라고 하면 관련 파일을 찾아 수정하고 API까지 만들어줍니다.

어려웠던 것: 외부 서비스(Airtable 등)의 버그는 Claude도 바로 알지 못합니다. "왜 안 되지?"를 반복하다 보니 결국 현상을 최대한 구체적으로 설명하는 것이 핵심이었습니다. "캘린더에 안 나와"보다 "3월 31일 오전 반차를 신청했는데 해당 날짜를 눌러도 일정이 없다고 나와"처럼 정확하게 설명할수록 해결이 빨랐습니다.

가장 인상적인 것: 버그 하나가 수정되면 연쇄적으로 다른 문제도 같이 고쳐지는 경우가 많았습니다. Airtable 날짜 필터 문제를 패턴으로 파악하고 나니, 비슷한 함수 10여 개를 한꺼번에 고쳐줬습니다. 사람 개발자가 했다면 하나씩 찾아야 했을 텐데요. 제가 생각지도 못했던 부분까지 찾아서 일괄적으로, 그리고 연쇄적으로 수정해 주었습니다.

비개발자도 Claude Code를 잘 활용하면 실무에서 쓸 수 있는 수준의 웹 시스템을 충분히 만들 수 있습니다. 핵심은 업무 흐름을 구체적으로 설명하고, 실제로 써보면서 나오는 문제를 정확히 전달하는 것입니다.


참고

지피터스 뉴스레터

이번 주 AI, 딱 쓸 것만

매주 월요일 아침,
읽고 바로 써먹을 AI 소식만