- 분류 관리: 대분류 선택·소분류 들여쓰기(드래그 정렬 유지) - 입력/예산/고정지출 분류 드롭다운 대분류 optgroup 그룹핑(값은 소분류명) - 대시보드: 파이는 소분류 기준 + 대분류 합계 롤업 표시 - 릴리스 노트 33 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,7 @@ const barData = ref([]) // [{bucket, budget, expense}]
|
||||
const monthlyStats = ref([]) // MONTH unit of year (income/expense)
|
||||
const catType = ref('EXPENSE') // 분류별 파이차트 구분
|
||||
const catData = ref([]) // [{category, total}]
|
||||
const categoryList = ref([]) // 분류 목록(대/소분류 매핑용)
|
||||
const trendData = ref([]) // [{month, netWorth}]
|
||||
|
||||
const periodLabel = computed(() => `${year.value}년 ${String(month.value).padStart(2, '0')}월`)
|
||||
@@ -188,6 +189,33 @@ async function loadCat() {
|
||||
catData.value = []
|
||||
}
|
||||
}
|
||||
async function loadCategoryList() {
|
||||
try {
|
||||
categoryList.value = await accountApi.categories()
|
||||
} catch {
|
||||
categoryList.value = []
|
||||
}
|
||||
}
|
||||
// 소분류명 → 대분류명 매핑(현재 구분). 소분류가 아니면 자기 자신.
|
||||
const majorRollup = computed(() => {
|
||||
const list = categoryList.value.filter((c) => c.type === catType.value)
|
||||
const byId = {}
|
||||
list.forEach((c) => (byId[c.id] = c))
|
||||
const toMajor = (name) => {
|
||||
const c = list.find((x) => x.name === name)
|
||||
if (c && c.parentId != null && byId[c.parentId]) return byId[c.parentId].name
|
||||
return name
|
||||
}
|
||||
const map = {}
|
||||
for (const d of catData.value) {
|
||||
const major = toMajor(d.category)
|
||||
map[major] = (map[major] || 0) + d.total
|
||||
}
|
||||
const rows = Object.entries(map).map(([category, total]) => ({ category, total })).sort((a, b) => b.total - a.total)
|
||||
// 실제로 묶인 게 있을 때만(소분류 존재) 노출
|
||||
const hasSub = list.some((c) => c.parentId != null) && rows.length < catData.value.length
|
||||
return hasSub ? rows : []
|
||||
})
|
||||
function setCatType(t) {
|
||||
catType.value = t
|
||||
loadCat()
|
||||
@@ -290,6 +318,7 @@ onMounted(async () => {
|
||||
load()
|
||||
loadMonthly()
|
||||
loadTrend()
|
||||
loadCategoryList()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -393,6 +422,15 @@ onMounted(async () => {
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-if="majorRollup.length" class="major-rollup">
|
||||
<div class="mr-head">대분류 합계</div>
|
||||
<ul class="mr-list">
|
||||
<li v-for="m in majorRollup" :key="m.category">
|
||||
<span class="mr-cat">{{ m.category }}</span>
|
||||
<span class="mr-amt">{{ won(m.total) }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<p v-else class="empty">{{ catType === 'EXPENSE' ? '지출' : '수입' }} 내역이 없습니다.</p>
|
||||
</div>
|
||||
|
||||
@@ -760,6 +798,33 @@ h2 {
|
||||
fill: var(--color-text);
|
||||
opacity: 0.6;
|
||||
}
|
||||
.major-rollup {
|
||||
margin-top: 0.8rem;
|
||||
padding-top: 0.6rem;
|
||||
border-top: 1px dashed var(--color-border);
|
||||
}
|
||||
.mr-head {
|
||||
font-size: 0.78rem;
|
||||
opacity: 0.6;
|
||||
margin-bottom: 0.3rem;
|
||||
}
|
||||
.mr-list {
|
||||
list-style: none;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
||||
gap: 0.1rem 1rem;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.mr-list li {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 0.85rem;
|
||||
padding: 0.1rem 0;
|
||||
}
|
||||
.mr-cat {
|
||||
font-weight: 600;
|
||||
}
|
||||
.pie-legend {
|
||||
list-style: none;
|
||||
width: 100%;
|
||||
|
||||
Reference in New Issue
Block a user