Compare commits

...

2 Commits

Author SHA1 Message Date
ByungCheol 7965706167 Merge branch 'dev'
Deploy / deploy (push) Failing after 11m16s
2026-07-01 00:33:41 +09:00
ByungCheol 1e9ff0408c feat(account): 내역추가 분류 — 행 삽입형 아코디언 + 대/소분류 추가 버튼 분리
CI / build (push) Failing after 12m46s
- 대분류를 4개씩 행으로 렌더링(majorRows computed)
- 선택된 대분류가 속한 행 바로 아래 소분류 칩 삽입(rowContainsSelected)
- + 소분류 버튼: 소분류 행 내부, 선택된 대분류 하위로 추가
- + 대분류 버튼: 맨 아래, 선택 초기화 후 대분류로 추가
- 입력 placeholder에 추가 맥락 표시(대분류명 > 새 소분류)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 00:32:59 +09:00
+33 -7
View File
@@ -282,10 +282,22 @@ const categoryMajor = ref('') // 펼쳐진 대분류 이름
const majorOptionsData = computed(() => const majorOptionsData = computed(() =>
categories.value.filter((c) => c.type === form.type && c.parentId == null) categories.value.filter((c) => c.type === form.type && c.parentId == null)
) )
// 대분류를 4개씩 행으로 묶음
const majorRows = computed(() => {
const items = majorOptionsData.value
const rows = []
for (let i = 0; i < items.length; i += 4) rows.push(items.slice(i, i + 4))
return rows
})
// 현재 펼쳐진 대분류 객체 // 현재 펼쳐진 대분류 객체
const selectedMajorObj = computed(() => const selectedMajorObj = computed(() =>
majorOptionsData.value.find((m) => m.name === categoryMajor.value) ?? null majorOptionsData.value.find((m) => m.name === categoryMajor.value) ?? null
) )
// 해당 행에 선택된 대분류가 포함되는지 확인
function rowContainsSelected(row) {
if (!selectedMajorObj.value) return false
return row.some((m) => m.id === selectedMajorObj.value.id)
}
// 특정 대분류의 소분류 목록 // 특정 대분류의 소분류 목록
function subsByMajor(majorId) { function subsByMajor(majorId) {
return categories.value.filter((c) => c.parentId === majorId) return categories.value.filter((c) => c.parentId === majorId)
@@ -322,10 +334,19 @@ const allCategoryNames = computed(() => [...new Set(categories.value.map((c) =>
// 모달 내 분류 인라인 추가 // 모달 내 분류 인라인 추가
const addingCategory = ref(false) const addingCategory = ref(false)
const addingAsMajor = ref(true) // true=대분류 추가, false=소분류 추가
const newCategoryName = ref('') const newCategoryName = ref('')
const catSubmitting = ref(false) const catSubmitting = ref(false)
function startAddCategory() { function startAddMajorCategory() {
categoryMajor.value = ''
form.category = ''
newCategoryName.value = '' newCategoryName.value = ''
addingAsMajor.value = true
addingCategory.value = true
}
function startAddSubCategory() {
newCategoryName.value = ''
addingAsMajor.value = false
addingCategory.value = true addingCategory.value = true
} }
function cancelAddCategory() { function cancelAddCategory() {
@@ -1125,19 +1146,18 @@ onMounted(async () => {
<label v-if="form.type === 'INCOME' || form.type === 'EXPENSE'">분류 <label v-if="form.type === 'INCOME' || form.type === 'EXPENSE'">분류
<div v-if="!addingCategory" class="cat-picker"> <div v-if="!addingCategory" class="cat-picker">
<!-- 대분류: 줄에 4개씩 --> <!-- 대분류 행별 렌더링: 선택된 대분류가 바로 아래 소분류 삽입 -->
<template v-for="(row, rowIdx) in majorRows" :key="rowIdx">
<div class="acc-major-grid"> <div class="acc-major-grid">
<button <button
v-for="m in majorOptionsData" :key="m.id" v-for="m in row" :key="m.id"
type="button" class="acc-chip major" type="button" class="acc-chip major"
:class="{ active: categoryMajor === m.name }" :class="{ active: categoryMajor === m.name }"
:disabled="submitting" :disabled="submitting"
@click="onMajorClick(m)" @click="onMajorClick(m)"
>{{ m.name }}</button> >{{ m.name }}</button>
<button type="button" class="acc-chip add-chip" :disabled="submitting" @click="startAddCategory">+ 추가</button>
</div> </div>
<!-- 소분류: 선택된 대분류가 있을 때만 표시 --> <div v-if="rowContainsSelected(row) && selectedMajorObj && subsByMajor(selectedMajorObj.id).length" class="acc-sub-grid">
<div v-if="selectedMajorObj && subsByMajor(selectedMajorObj.id).length" class="acc-sub-grid">
<button <button
v-for="s in subsByMajor(selectedMajorObj.id)" :key="s.id" v-for="s in subsByMajor(selectedMajorObj.id)" :key="s.id"
type="button" class="acc-chip sub" type="button" class="acc-chip sub"
@@ -1145,12 +1165,15 @@ onMounted(async () => {
:disabled="submitting" :disabled="submitting"
@click="form.category = s.name" @click="form.category = s.name"
>{{ s.name }}</button> >{{ s.name }}</button>
<button type="button" class="acc-chip add-chip sub-add" :disabled="submitting" @click="startAddSubCategory">+ 소분류</button>
</div> </div>
</template>
<button type="button" class="acc-chip add-chip" :disabled="submitting" @click="startAddMajorCategory">+ 대분류</button>
</div> </div>
<div v-else class="cat-input"> <div v-else class="cat-input">
<input <input
v-model="newCategoryName" type="text" v-model="newCategoryName" type="text"
:placeholder="form.type === 'EXPENSE' ? '새 지출 분류' : '새 수입 분류'" :placeholder="addingAsMajor ? (form.type === 'EXPENSE' ? '새 지출 분류' : '새 수입 분류') : `${categoryMajor} > 새 소분류`"
:disabled="catSubmitting" @keyup.enter.prevent="confirmAddCategory" :disabled="catSubmitting" @keyup.enter.prevent="confirmAddCategory"
/> />
<button type="button" class="cat-add-btn primary" :disabled="catSubmitting" @click="confirmAddCategory">확인</button> <button type="button" class="cat-add-btn primary" :disabled="catSubmitting" @click="confirmAddCategory">확인</button>
@@ -1951,6 +1974,9 @@ button.primary {
opacity: 0.55; opacity: 0.55;
border-style: dashed; border-style: dashed;
} }
.acc-chip.add-chip.sub-add {
flex: 0 0 auto;
}
.tag-field { .tag-field {
display: flex; display: flex;
flex-direction: column; flex-direction: column;