[바이브코딩 도전기] 카드 내역을 편하게 관리하려고 추가한 기능들

가계부를 만들어 쓰다 보면 처음 설계할 때는 몰랐던 불편함이 하나씩 튀어나온다. 카드 내역 화면이 딱 그랬다. 내역을 불러오는 것 자체는 됐는데, 막상 쓰려니 “이 달치만 보고 싶다”, “비슷한 내역 여러 개 한 번에 수정하고 싶다” 같은 요구가 계속 생겼다. 그래서 이번에는 카드 내역 화면에 기간 필터, 일괄 수정, 드래그 선택 같은 기능들을 붙인 과정을 정리해봤다.

기간 필터부터 넣었다

처음에는 카드 내역을 월 단위로만 볼 수 있었다. 그런데 월초에 지난달 말 결제가 섞이거나, 특정 기간만 따로 보고 싶을 때가 생각보다 자주 있었다. 그래서 필터에 시작일·종료일을 직접 입력하는 기간 조건을 추가했다.

구현 자체는 어렵지 않았다. 기존에 월 파라미터만 받던 API 쪽에 date_from, date_to 파라미터를 추가하고, SQL WHERE 절에 조건을 붙이는 식이었다. 문제는 “조건별 삭제” 기능이었다. 삭제 요청을 보낼 때 현재 필터 상태를 그대로 반영해야 하는데, 처음에는 삭제 API가 필터를 전혀 안 받고 있었다. 그러다 보니 필터를 걸어서 보이는 내역만 지우려 해도 전체 삭제가 돼버리는 상황이 생겼다.

결국 삭제 API도 필터 파라미터를 받도록 수정하고, 프론트에서 삭제 버튼을 누를 때 현재 적용된 필터 값을 함께 넘기도록 맞췄다. 단순한 것 같아도 이런 부분에서 한 번씩 삽질이 생긴다.

여러 내역 한 번에 수정하기

카드 내역에는 태그, 메모, 국내/해외 여부 같은 필드가 있다. 비슷한 가게에서 반복적으로 결제한 내역이 쌓이면, 하나씩 열어서 태그를 붙이는 게 꽤 번거롭다. 그래서 여러 건을 선택해서 한 번에 수정하는 일괄 수정 기능을 만들기로 했다.

API 쪽에는 update_bulk라는 엔드포인트를 따로 뒀다. 선택된 내역들의 ID 배열과 함께 바꿀 필드 값을 POST로 넘기면, 루프 돌면서 일괄 업데이트하는 구조다. 간단하게 보면 이런 흐름이다.

// 요청 예시 (실제 값은 아님)
{
  "ids": [101, 102, 105],
  "tag": "식비",
  "memo": "점심",
  "is_domestic": 1
}

UI 쪽에서는 체크박스로 내역을 선택하면 화면 하단에 일괄 수정 패널이 올라오게 했다. 태그, 메모, 국내/해외 여부를 한 화면에서 수정하고 저장하면 선택한 내역 전체에 반영된다. 빈 값으로 넘기면 해당 필드는 건드리지 않도록 처리해서, 태그만 바꾸고 싶을 때 메모가 지워지는 일은 없게 했다.

드래그 선택이 생각보다 까다로웠다

체크박스를 하나씩 클릭해서 선택하는 건 내역이 많을 때 불편하다. 그래서 마우스를 드래그해서 여러 행을 한 번에 선택하는 UX를 넣었다.

드래그 선택 자체는 mousedown, mousemove, mouseup 이벤트 조합으로 구현했다. 드래그 시작 시점의 체크 상태를 기준으로, 드래그하면서 지나가는 행들을 같은 상태로 맞추는 방식이다.

그런데 문제가 있었다. 드래그를 시작한 첫 번째 행이 토글이 안 됐다. 정확히는 됐다가 안 됐다가, 타이밍 문제처럼 보이는 증상이었다. 이벤트 순서를 따라가 보니 mousedown에서 상태를 저장하고, 이후 click 이벤트가 또 발생하면서 첫 행이 다시 토글되는 게 문제였다.

해결은 드래그 시작 시점에 첫 행을 직접 토글하고, 이후 드래그 중에는 첫 행 상태를 기준으로만 나머지를 맞추도록 바꿨다. 그리고 드래그 완료 후 발생하는 click 이벤트는 플래그로 막았다. 실제로 쓰면서 “이게 왜 안 되지?” 하고 한참 들여다봤던 부분이라 기억에 남는다.

카드사별 청구월 재분류

카드 내역을 관리하다 보면 “이게 몇 월 사용분이지?”가 헷갈릴 때가 있다. 카드사마다 결제일과 사용기간 기준이 다르기 때문이다. 예를 들어 어떤 카드는 매월 15일 결제인데 전월 16일~당월 15일 사용분이 청구되는 식이다.

그래서 카드사별로 결제일과 사용기간 오프셋을 설정해두고, 거래일을 기준으로 실제 사용월을 자동으로 재분류하는 로직을 넣었다. 카드사 설정에 결제일과 오프셋을 저장해두면, 내역을 불러올 때 청구월이 아니라 사용월 기준으로 분류해서 보여준다.

다만 이 로직을 처음 적용할 때 기존에 쌓인 내역들이 자동으로 재분류될 수 있다. 예를 들어 6월로 잡혀 있던 거래가 5월 사용분으로 바뀌는 경우다. 데이터를 건드리기 전에 어떻게 바뀌는지 미리 확인하고 적용하는 게 좋다.

그 외 자잘한 수정들

카드 화면만 건드린 건 아니었다. 작업하면서 같이 고친 것들도 몇 가지 있다.

연도·월 이동 버튼 안정화

대시보드, 월급, 카드 화면 모두 연도와 월을 이동하는 버튼이 있다. 그런데 화면 초기화 타이밍에 따라 버튼이 동작 안 하는 경우가 간헐적으로 있었다. 모듈이 초기화되기 전에 이벤트가 걸리는 타이밍 문제였다.

이걸 이벤트 위임 방식으로 바꿔서 해결했다. 버튼 각각에 이벤트를 거는 대신, 상위 컨테이너에 이벤트를 하나 걸고 클릭된 요소를 판별하는 방식이다. 모듈 초기화 여부와 무관하게 안정적으로 동작한다.

튜토리얼 툴팁 z-index 문제

가계부에 처음 접속하면 기능 설명 툴팁이 뜨는 튜토리얼을 넣어뒀는데, 하이라이트 처리한 요소 뒤로 툴팁이 숨어버리는 문제가 있었다. z-index를 올려도 안 됐는데, 원인은 stacking context였다.

특정 요소에 transform이나 opacity가 걸리면 그 요소 내부에 새로운 stacking context가 생긴다. 아무리 z-index를 높여도 그 context 밖을 벗어날 수 없다. 해결책은 툴팁 요소를 아예 body 바로 아래로 옮기고, 위치를 JavaScript로 계산해서 잡는 방식이었다. CSS에서 z-index 값도 다시 정리했다.

iOS 홈 화면 아이콘

iPhone에서 홈 화면에 가계부를 추가했더니 아이콘이 안 나왔다. apple-touch-icon은 SVG를 지원하지 않고 PNG만 받는다는 걸 그때 알았다. sips 명령어로 PNG를 만들고 manifest.webmanifest도 추가했다.

# sips로 PNG 변환 예시
sips -z 180 180 icon.png --out apple-touch-icon.png

크기는 180×180, 192×192, 512×512, 32×32 정도를 만들어뒀다. 홈 화면 아이콘이 제대로 나오니까 앱처럼 쓰는 느낌이 확실히 달라졌다.

지금 카드 화면 상태

이것저것 붙이고 나니 카드 내역 화면이 처음보다 훨씬 쓸 만해졌다. 기간 필터로 원하는 구간만 볼 수 있고, 드래그로 여러 내역 선택해서 태그 한 번에 붙이고, 청구월 기준도 카드사별로 자동 계산된다.

물론 아직 부족한 부분은 있다. 카드 내역을 카테고리별로 묶어서 보여주는 통계 뷰가 없고, 반복 결제를 자동으로 감지하는 기능도 없다. 쓰면서 필요한 게 생기면 그때그때 추가하는 식으로 계속 다듬고 있다.

개인 NAS에 올려두고 혼자 쓰는 도구라 완성도보다는 실제로 편한지가 더 중요하다. 만들면서 불편한 점을 바로 고칠 수 있다는 게 직접 만든 도구의 장점이기도 하고.


NAS에 직접 만든 개인 가계부 만들기 시리즈의 다른 글도 위 카테고리에서 볼 수 있습니다.


시리즈 글 모아보기

댓글 남기기