사회초년생 월급관리, 첫 월급부터 이렇게 나눠보세요 [2편] 구현기
지난 [1편]에서는 첫 월급 배분기를 왜, 무엇을 담아 만들었는지 다뤘습니다. 이번 편에서는 URL 공유, 부채 카테고리 동적 추가, 인쇄 전용 체크리스트를 어떻게 구현했는지 기록합니다.
1. 프리셋에 “이유”를 붙이는 구조
처음엔 프리셋을 단순히 { save: 40, fixed: 25, ... } 같은 숫자 객체로만 관리했습니다. 그런데 “왜 이 비율인지”를 보여주려니, 숫자만으로는 부족했습니다. 그래서 각 프리셋에 출처(source)와 설명(blurb)을 함께 묶었습니다.
const PRESETS = {
'503020': {
values: { save: 20, fixed: 30, variable: 20, emergency: 5, leisure: 25, debt: 0 },
source: '엘리자베스 워런 (미국 상원의원, 하버드 로스쿨 교수)',
blurb: '저서 『맞벌이의 함정』에서 제안한 방식으로, "생활비 50%·원하는 것 30%·저축 20%"...'
},
// ...
};
function applyPreset(presetKey) {
const preset = PRESETS[presetKey];
// 슬라이더에 값 반영
CATEGORIES.forEach(cat => {
document.getElementById(`slider-${cat.key}`).value = preset.values[cat.key];
});
// 출처와 설명을 화면에 표시
document.getElementById('presetDesc').innerHTML = `<b>${preset.source}</b><br>${preset.blurb}`;
}
프리셋을 선택하는 순간 값과 설명이 동시에 갱신되니, “이 비율이 어디서 나온 건지” 궁금한 순간 바로 답이 보입니다.
2. 부채 카테고리를 켜면 비율이 자동으로 재조정되는 로직
부채상환 카테고리는 항상 존재하는 게 아니라 체크박스로 켜고 끕니다. 문제는, 켰을 때 기존 5개 항목의 비율을 그대로 두면 합이 100%를 넘어버린다는 점이었습니다. 그래서 부채 카테고리를 켜면 저축·고정지출·변동지출에서 일정량을 덜어와 부채상환에 배정하는 조정값을 따로 정의했습니다.
const DEBT_PRESET_ADJUST = { save: -10, fixed: -5, variable: -5, emergency: 0, leisure: 0, debt: 20 };
function applyPreset(presetKey) {
let values = { ...PRESETS[presetKey].values };
if (useDebt) {
Object.keys(DEBT_PRESET_ADJUST).forEach(k => {
values[k] = Math.max(0, (values[k] || 0) + DEBT_PRESET_ADJUST[k]);
});
}
// ...
}
이렇게 하면 어떤 프리셋을 고르든, 부채상환을 켜는 순간 합계가 항상 100%를 유지한 채로 자연스럽게 재배분됩니다.
3. URL에 계산 결과를 담아 공유하기
결과를 이미지가 아니라 링크로 공유하는 기능도 넣고 싶었습니다. URLSearchParams를 쓰면 현재 슬라이더 값들을 쿼리 문자열로 만들 수 있습니다.
function copyShareUrl() {
const params = new URLSearchParams();
params.set('net', document.getElementById('monthlyNet').value);
params.set('debt', useDebt ? '1' : '0');
CATEGORIES.forEach(cat => {
params.set(cat.key, document.getElementById(`slider-${cat.key}`).value);
});
const url = window.location.origin + window.location.pathname + '?' + params.toString();
navigator.clipboard.writeText(url).then(() => {
alert('링크가 복사됐어요!');
});
}
그리고 페이지가 로드될 때, URL에 쿼리 파라미터가 있으면 그 값으로 슬라이더를 미리 채워둡니다.
function loadFromUrl() {
const params = new URLSearchParams(window.location.search);
if (![...params].length) return; // 파라미터 없으면 그냥 기본값 사용
if (params.get('net')) document.getElementById('monthlyNet').value = params.get('net');
CATEGORIES.forEach(cat => {
if (params.get(cat.key) !== null) {
document.getElementById(`slider-${cat.key}`).value = params.get(cat.key);
}
});
}
navigator.clipboard.writeText는 HTTPS 환경에서만 정상 동작한다는 점도 이번에 확인했습니다. 혹시 실패할 경우를 대비해 prompt()로 링크를 직접 보여주는 대체 경로도 함께 넣어뒀습니다.
4. 화면에는 다 보이지만, 인쇄하면 체크리스트만 — @media print
3개월 실천 체크리스트를 인쇄할 때, 입력 폼이나 그래프까지 같이 출력되면 지저분해집니다. CSS의 @media print를 쓰면 인쇄할 때만 적용되는 스타일을 따로 지정할 수 있습니다.
@media print {
.top-bar, .input-card, .chart-wrap, .cat-cards, .character-card,
#compoundAccordion, .related-section, .goto-link, .print-btn { display:none !important; }
#checklistAccordion .accordion-body { display:block !important; }
}
화면에서 보일 때는 평소처럼 다 나오지만, window.print()를 호출해 인쇄 미리보기로 들어가는 순간 이 스타일이 적용되면서 체크리스트 표 하나만 깔끔하게 출력됩니다. 아코디언으로 접혀 있던 상태여도 인쇄 시엔 강제로 펼쳐지도록 display:block !important를 걸어둔 것도 포인트입니다.
5. 캐릭터 배정 — 가장 높은 비율을 찾는 단순한 로직
캐릭터는 복잡한 규칙이 필요 없었습니다. 5~6개 항목 중 가장 비율이 높은 항목 하나만 찾으면 됩니다.
let maxKey = CATEGORIES[0].key, maxPct = -1;
CATEGORIES.forEach(cat => {
const pct = Number(document.getElementById(`slider-${cat.key}`).value);
if (pct > maxPct) { maxPct = pct; maxKey = cat.key; }
});
const char = CHARACTER_TYPES[maxKey];
단순한 로직이지만, 프리셋이나 슬라이더 조정에 따라 결과가 계속 달라지니 사용자가 이것저것 조절해보며 “어? 내가 다람쥐형에서 판다형으로 바뀌었네” 하고 재미를 느낄 수 있는 지점이 됐습니다.
6. 마무리하며
이번 작업에서는 특히 “화면에 보이는 것”과 “저장·공유되는 것”을 분리해서 생각하는 연습이 됐습니다. URL 공유는 데이터를, 인쇄는 특정 화면 요소만을 선택적으로 내보내는 작업인데, 둘 다 “지금 화면 상태 전체”가 아니라 “필요한 부분만” 골라내는 접근이 핵심이었습니다.
무료로 직접 사용해보고 싶으시다면 [1편]에 링크해둔 배분기를 확인해보세요.