Ledger오프라인 고정비 · 저축 가계부
서론
월세, 관리비, 통신비, 구독, 보험, 적금처럼 매달 정해진 날 빠져나가는 돈은 가계부 앱에 넣기엔 너무 반복적이고, 엑셀로 관리하기엔 “이번 달엔 뭐가 나가고 언제 나가는지”가 한눈에 안 보였습니다. 은행 · 카드 연동도 필요 없고, 내 PC에서만 가볍게 열어 보는 고정비 전용 장부가 있으면 좋겠다 싶어서 만들었습니다.
Ledger는 Windows Electron + React + Vite + TypeScript 기반의 오프라인 고정비 · 저축 가계부입니다. 항목을 주기별로 등록하고 세트로 묶어 달마다 적용하면, 이번 달 총액 · 수입 대비 · 남는 돈과 결제 일정을 한 화면에서 보여 줍니다. 데이터는 서버 없이 %APPDATA%의 JSON 파일 하나에만 저장됩니다.
1. 왜 만들었나
- 고정비만 따로: 변동 지출까지 기록하는 가계부는 오래 못 썼습니다. 매달 반복되는 돈만 정확히 보고 싶었습니다.
- 달마다 다른 구성: 이사, 약정 종료, 구독 해지처럼 달에 따라 나가는 항목이 바뀝니다. 그래서 항목을 세트로 묶고, 세트를 달마다 켜고 끄는 구조로 만들었습니다.
- 저축은 지출과 다르게: 적금 · 청약은 매달 빠져나가지만 쓰는 돈이 아닙니다. 총액에는 넣되 고정 지출과 저축 · 투자를 나눠 보고 싶었습니다.
- 오프라인 · 내 PC만: 금융 정보라 서버나 계정 없이 로컬 파일 하나로 끝나길 바랐습니다. 대신 파일이 깨지거나 날아가지 않게 저장만큼은 안전하게 만들었습니다.
2. 구조 및 아키텍처
Electron main이 JSON 저장소와 파일 대화상자를 맡고, renderer(React)는 window.ledger 브리지로만 데이터를 읽고 씁니다. 정규화 · 마이그레이션 · 세트 규칙은 src/ledgerData.ts에 두고 main의 저장소와 브라우저 목업이 같은 코드를 씁니다.
MINI_Ledger/
┣━━ 📂 electron/
┃ ┣━━ main.ts # 창 · IPC · 내보내기/가져오기 · 확인 대화상자
┃ ┣━━ preload.ts # window.ledger / windowControls 브리지
┃ ┗━━ store.ts # JSON 저장소 (원자적 쓰기 · .bak · 날짜별 백업)
┣━━ 📂 src/
┃ ┣━━ Overview.tsx # 개요
┃ ┣━━ LedgerPage.tsx # 장부 (작업대 · 세트 · 월 적용)
┃ ┣━━ IncomePage.tsx # 수입 · 월별 흐름 차트
┃ ┣━━ 📂 overview/ # 날짜 눈금 · 분류 · 결제 리듬 · 다가오는 결제
┃ ┣━━ 📂 ledger/ # 입력 폼 · 납부 시점 선택 · 세트 작업대 훅
┃ ┣━━ schedule.ts # 주기별 납부일 · 월 합계 · 지출/저축 집계
┃ ┣━━ ledgerData.ts # 정규화 · 마이그레이션 · 세트 규칙 (store와 공용)
┃ ┗━━ devMock.ts # 브라우저 개발용 샘플 데이터
┗━━ 📄 package.json
장부 파일 하나에 네 가지가 들어갑니다.
ledger.json
┣━━ items # 항목: 이름 · 금액 · 주기 · 납부일/요일/월 · 분류 · 메모
┣━━ sets # 세트: 이름 · 항목 id 목록
┣━━ assignments # "2026-10" → 그 달에 적용된 항목 id 목록
┗━━ income # 기본 월수입 + 달별 덮어쓰기
데이터 흐름은 대략 다음과 같습니다.
React UI ──window.ledger.*──▶ preload ──ipcRenderer.invoke──▶ main (store.ts)
store.ts ──read → 정규화(ledgerData) → 수정 → .bak · 백업 → 원자적 쓰기──▶ ledger.json
브라우저(Vite만) ──window.ledger 없음──▶ devMock (같은 ledgerData 규칙, 메모리 샘플)
3. 핵심 구현 디테일
① 주기 → 그 달의 납부일
schedule.ts의 occurrencesInMonth가 항목 하나를 그 달에 실제로 나가는 날짜 목록으로 바꿉니다. 매월은 납부일(말일 보정), 매주는 그 달의 해당 요일 전부, 분기는 3 · 6 · 9 · 12월처럼 기준 월에서 3개월 간격, 연간은 지정한 달 하나, 매일은 그 달 전체입니다. 그래서 같은 매주 항목도 달에 따라 4번 또는 5번 잡히고, 개요의 합계 · 날짜 눈금 · 다가오는 결제가 모두 이 목록에서 나옵니다. 장부 표의 Amount / mo는 비교용 월 환산 금액입니다.
② 세트와 월 적용
항목은 반드시 하나 이상의 세트에 들어가고, 세트를 달마다 켜면 그 달의 assignments에 항목 id가 들어갑니다. 적용한 달만 개요 · 수입 합계에 반영됩니다.
| 동작 | 규칙 |
|---|---|
| 세트 켜기 | 세트 항목을 그 달 목록에 추가 |
| 세트 끄기 | 다른 세트가 그 달에 완전히 적용돼 있고 그 항목도 갖고 있으면 남김 |
| 세트 구성 변경 | 이미 적용된 달에 새 항목을 넣고, 빠진 항목은 위 규칙대로 정리 |
| 세트 삭제 | 그 세트에만 있던 항목은 함께 삭제 (확인 창에서 개수 안내) |
| 세트 없는 항목 | 가져오기 · 옛 데이터에서 생기면 기본 / 미저장 세트로 묶음 |
③ 고정 지출 · 저축 · 투자 분리
분류는 두 묶음으로 나뉩니다. 저축 · 투자 · 연금 · 청약은 저축으로, 대출 · 헌금 · 가족 용돈을 포함한 나머지는 고정 지출로 셉니다. 개요의 큰 숫자는 둘을 합친 이번 달 총액이고, 옆 막대가 수입을 고정 지출 / 저축 · 투자 / 남는 돈으로 나눠 보여 줍니다. 분류 도넛은 고정 지출만, 분류 목록은 두 묶음을 나눠 보여 줍니다.
④ 안전한 저장
로컬 파일 하나가 전부라, 저장이 실패해도 데이터가 사라지지 않게 하는 데 신경을 썼습니다.
| 장치 | 내용 |
|---|---|
| 원자적 쓰기 | 임시 파일에 쓰고 fsync 후 rename으로 교체 — 쓰다가 꺼져도 반쯤 쓴 파일이 남지 않음 |
| 직전본 | 저장할 때마다 이전 내용을 ledger.json.bak으로 |
| 날짜별 백업 | 그날 첫 저장 전 상태를 backups/ledger-YYYY-MM-DD.json으로, 최근 14개 유지 |
| 손상 파일 | 읽을 수 없으면 ledger.corrupt-<시각>.json으로 보관하고 이후 저장을 막음 (빈 장부로 덮어쓰지 않음) |
| 시작 시 복구 | 본 파일이 깨졌고 .bak이 정상이면 .bak으로 복구 |
| 가져오기 | 막힌 상태에서도 Import로 장부를 통째로 바꿔 복구, 가져오기 창은 백업 폴더에서 열림 |
⑤ 브라우저 목업으로 개발
npm run dev는 Vite와 Electron을 같이 띄우지만, 화면 작업은 브라우저에서 하는 게 빠릅니다. Vite 개발 모드에서 window.ledger가 없으면 devMock.ts가 샘플 항목 · 세트 · 수입을 메모리에 만들어 같은 API를 흉내 냅니다. 세트 규칙은 ledgerData.ts를 그대로 써서 목업과 실제 저장소가 같은 결과를 냅니다. 이 글의 스크린샷도 모두 이 샘플 데이터입니다.
4. 화면으로 보는 기능
4.1 이번 달 총액 · 수입 흐름
- Monthly total: 고정 지출 + 저축 · 투자를 합친 이번 달 총액. 화살표로 다른 달로 이동합니다.
- 수입 대비 · 남는 돈 · 적용 항목: 그 달 수입 기준 비율과 남는 금액.
- Where income goes: 수입을 고정 지출 / 저축 · 투자(저축률) / 남는 돈으로 나눈 막대.
4.2 날짜 눈금
- Passed / Remaining: 오늘 기준 이미 나간 금액과 남은 금액.
- 점 크기 = 금액: 같은 날은 위로 쌓이고, 저축 항목은 파란 점입니다. 점에 올리면 이름 · 금액 · 주기.
- 매일 결제: 눈금에 찍지 않고 합계에만 넣습니다.
4.3 분류
- Spending share: 고정 지출만 분류별 도넛으로. 조각에 올리면 그 분류의 항목들이 보입니다.
- By category: 고정 지출과 저축 · 투자를 나눠 분류별 금액 · 비율.
4.4 결제 리듬 · 다가오는 결제
- Payment rhythm: 주차별 결제 금액, 다음 7일 · 이번 달 남은 결제 · 결제가 몰린 날.
- Upcoming: 이번 달 남은 결제와 다음 달 첫 주. 매일 결제는 기간 · 횟수 · 합계 한 줄로 묶습니다.
4.5 장부 작업대
- 작업대: 새로 추가한 항목이나 수정 중인 세트의 항목을 표로 보고 정렬 · 수정 · 삭제합니다.
- 월 환산: 주간 · 분기 · 연간 항목도
Amount / mo로 비교하고, 아래에 실제 금액과 납부 시점을 함께 씁니다.
4.6 항목 입력 · 분류
- 입력: 이름 · 금액 · 주기 · 납부 시점(날짜 / 요일 / 월) · 분류 · 메모.
- 분류: 고정 지출 분류가 먼저, 저축 · 투자 · 연금 · 청약은 파란 표시로 따로 모여 있습니다.
4.7 세트 · 월 적용
- 월 칸 클릭: 그 달에 세트를 켜고 끕니다. 개요 · 수입에 바로 반영됩니다.
- 세트 요약: 항목 수와 고정 지출 · 저축 · 투자의 월 환산 금액.
- 수정 / 삭제: 수정은 항목을 작업대로 불러오고, 삭제는 함께 지워질 항목 수를 확인 창으로 알려 줍니다.
4.8 수입
- 기본 월수입 + 달별 덮어쓰기: 다른 달만 칸을 눌러 금액을 바꿉니다 (점 표시).
- 달별 막대: 수입 대비 고정 지출 · 저축 비율. 세트를 적용하지 않은 달은
세트 미적용. - 연간 합계: 수입 · 고정 지출 · 저축 · 투자(저축률) · 남는 돈 — 적용한 달만 셉니다.
- Monthly flow: 수입 · 고정 지출 · 저축 · 투자 · 남는 돈 선 그래프, 달에 올리면 상세.
5. 한계 · 설치 · 사용 흐름
한계 (중요)
- 고정비 장부입니다. 카드 · 은행 내역 연동이나 변동 지출 기록은 없습니다.
- 세트를 적용한 달만 합계에 들어갑니다. 새 달이 되면 장부에서 세트를 켜 줘야 합니다.
- 저장소는 이 PC 한 곳입니다. 동기화가 없고 백업도 같은 디스크에 남으니, 다른 곳에 두려면 Export를 쓰세요.
- Windows 기준으로 빌드 · 확인했습니다. 코드 사이닝이 없어 SmartScreen 경고가 날 수 있습니다.
사전 요구
| 항목 | 설명 |
|---|---|
| OS | Windows 10 / 11 |
| Node.js | 개발 · 빌드용 |
설치 · 실행
git clone https://github.com/Hyeonseok93/MINI_Ledger.git
cd MINI_Ledger
npm install
npm run dev
실행 파일 빌드: npm run build:exe → release/win-unpacked/Ledger.exe. 폴더 전체를 배포하세요. .exe만 복사하면 Electron 런타임이 없어 실행되지 않습니다.
저장 경로: %APPDATA%/ledger/store/ (ledger.json, ledger.json.bak, backups/).
사용 흐름
- 장부에서 고정비 · 저축 항목을 주기 · 납부 시점 · 분류와 함께 추가합니다.
- 작업대 항목을 이름 붙여 세트로 저장합니다.
- 세트를 눌러 적용할 달을 켭니다.
- 수입에서 기본 월수입과 다른 달 금액을 맞춥니다.
- 개요에서 이번 달 총액 · 남는 돈 · 남은 결제를 확인합니다.
- 가끔 Export로 장부를 다른 곳에 보관합니다.