feat(account): 내역추가 분류 — 행 삽입형 아코디언 + 대/소분류 추가 버튼 분리
CI / build (push) Failing after 12m46s

- 대분류를 4개씩 행으로 렌더링(majorRows computed)
- 선택된 대분류가 속한 행 바로 아래 소분류 칩 삽입(rowContainsSelected)
- + 소분류 버튼: 소분류 행 내부, 선택된 대분류 하위로 추가
- + 대분류 버튼: 맨 아래, 선택 초기화 후 대분류로 추가
- 입력 placeholder에 추가 맥락 표시(대분류명 > 새 소분류)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ByungCheol
2026-07-01 00:32:59 +09:00
parent b2f3b156b2
commit 1e9ff0408c
+49 -23
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,32 +1146,34 @@ 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개씩 --> <!-- 대분류 행별 렌더링: 선택된 대분류가 바로 아래 소분류 삽입 -->
<div class="acc-major-grid"> <template v-for="(row, rowIdx) in majorRows" :key="rowIdx">
<button <div class="acc-major-grid">
v-for="m in majorOptionsData" :key="m.id" <button
type="button" class="acc-chip major" v-for="m in row" :key="m.id"
:class="{ active: categoryMajor === m.name }" type="button" class="acc-chip major"
:disabled="submitting" :class="{ active: categoryMajor === m.name }"
@click="onMajorClick(m)" :disabled="submitting"
>{{ m.name }}</button> @click="onMajorClick(m)"
<button type="button" class="acc-chip add-chip" :disabled="submitting" @click="startAddCategory">+ 추가</button> >{{ m.name }}</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" :class="{ active: form.category === s.name }"
:class="{ active: form.category === s.name }" :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;