Compare commits

..

2 Commits

Author SHA1 Message Date
ByungCheol 8d659ea956 Merge branch 'dev'
Deploy / deploy (push) Failing after 13m53s
2026-06-30 22:06:04 +09:00
ByungCheol 970e9e14ad feat: 내역 추가/수정 전체화면 모달 + 분류 대분류 아코디언
CI / build (push) Failing after 15m22s
- 내역 추가/수정 폼을 전체화면 모달로(바깥 영역 없음·× 로만 닫힘)
  → 입력 중 바깥 실수 터치로 닫히는 불편 해소. PC는 내용 폭 460px 가운데정렬
- 분류 화면: 대분류 삼각형(▶) 토글로 소분류 펼침/접힘(기본 접힘, 개수 뱃지)
  소분류 추가 시 해당 대분류 자동 펼침. 순서변경 모드에선 모두 펼침

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 22:06:02 +09:00
2 changed files with 81 additions and 4 deletions
+23 -2
View File
@@ -1012,8 +1012,8 @@ onMounted(async () => {
<!-- 추가/수정 모달 --> <!-- 추가/수정 모달 -->
<Teleport to="body"> <Teleport to="body">
<Transition name="fade"> <Transition name="fade">
<div v-if="formOpen" class="modal-backdrop" @click.self="formOpen = false"> <div v-if="formOpen" class="modal-backdrop entry-backdrop">
<div class="modal" role="dialog" aria-modal="true"> <div class="modal entry-modal" role="dialog" aria-modal="true">
<button class="close" type="button" @click="formOpen = false">×</button> <button class="close" type="button" @click="formOpen = false">×</button>
<h2>{{ editId ? '내역 수정' : '내역 추가' }}</h2> <h2>{{ editId ? '내역 수정' : '내역 추가' }}</h2>
@@ -1661,6 +1661,27 @@ button.primary {
font-size: 0.8rem; font-size: 0.8rem;
opacity: 0.6; opacity: 0.6;
} }
/* 내역 추가/수정: 전체화면 모달 — 바깥 영역이 없어 실수로 닫히지 않음(× 로만 닫힘) */
.entry-backdrop {
padding: 0;
}
.modal.entry-modal {
width: 100%;
height: 100%;
max-width: none;
max-height: 100%;
border: 0;
border-radius: 0;
box-shadow: none;
padding-top: calc(1.5rem + env(safe-area-inset-top));
}
/* 넓은 화면(PC)에서도 입력 내용은 읽기 좋은 폭으로 가운데 정렬 */
.modal.entry-modal > h2,
.modal.entry-modal .entry-form {
max-width: 460px;
margin-left: auto;
margin-right: auto;
}
.modal { .modal {
position: relative; position: relative;
width: 100%; width: 100%;
+58 -2
View File
@@ -65,6 +65,18 @@ async function reload() {
// 스크롤하다 실수로 순서가 바뀌는 것 방지: 기본은 드래그 비활성, '순서변경' 버튼으로 켠다. // 스크롤하다 실수로 순서가 바뀌는 것 방지: 기본은 드래그 비활성, '순서변경' 버튼으로 켠다.
const majorListEl = ref(null) const majorListEl = ref(null)
const reorderMode = ref(false) const reorderMode = ref(false)
// ===== 대분류 펼침(아코디언) — 삼각형 누르면 소분류 표시 =====
const expanded = ref(new Set())
function toggleExpand(id) {
const s = new Set(expanded.value)
if (s.has(id)) s.delete(id)
else s.add(id)
expanded.value = s
}
function isExpanded(id) {
return expanded.value.has(id)
}
let sortables = [] let sortables = []
function destroySortables() { function destroySortables() {
sortables.forEach((s) => s.destroy()) sortables.forEach((s) => s.destroy())
@@ -128,9 +140,16 @@ async function addCategory() {
if (!name) return if (!name) return
error.value = null error.value = null
try { try {
await accountApi.createCategory({ type: activeType.value, name, parentId: newParent.value || null }) const parentId = newParent.value || null
await accountApi.createCategory({ type: activeType.value, name, parentId })
newName.value = '' newName.value = ''
await reload() await reload()
// 소분류를 추가했으면 그 대분류를 펼쳐 바로 보이게
if (parentId) {
const s = new Set(expanded.value)
s.add(Number(parentId))
expanded.value = s
}
} catch (e) { } catch (e) {
showError(e, '추가에 실패했습니다.') showError(e, '추가에 실패했습니다.')
} }
@@ -226,12 +245,18 @@ onBeforeUnmount(destroySortables)
<div class="cat-row major-row"> <div class="cat-row major-row">
<div class="cat-main"> <div class="cat-main">
<span v-show="reorderMode" class="drag-handle major-handle" title="대분류 순서 변경(소분류 함께 이동)"></span> <span v-show="reorderMode" class="drag-handle major-handle" title="대분류 순서 변경(소분류 함께 이동)"></span>
<button
type="button" class="expand-btn" :class="{ open: isExpanded(g.major.id), empty: !g.children.length }"
:title="g.children.length ? (isExpanded(g.major.id) ? '소분류 접기' : '소분류 펼치기') : '소분류 없음'"
:disabled="!g.children.length" @click="toggleExpand(g.major.id)"
></button>
<input v-model="g.major.name" class="cat-name" @keyup.enter="saveCategory(g.major)" /> <input v-model="g.major.name" class="cat-name" @keyup.enter="saveCategory(g.major)" />
<span v-if="g.children.length && !isExpanded(g.major.id)" class="sub-count">{{ g.children.length }}</span>
<IconBtn icon="check" title="저장" size="sm" @click="saveCategory(g.major)" /> <IconBtn icon="check" title="저장" size="sm" @click="saveCategory(g.major)" />
<IconBtn icon="trash" title="삭제" variant="danger" size="sm" @click="removeCategory(g.major)" /> <IconBtn icon="trash" title="삭제" variant="danger" size="sm" @click="removeCategory(g.major)" />
</div> </div>
</div> </div>
<ul class="sub-list" :data-parent="g.major.id"> <ul v-show="isExpanded(g.major.id) || reorderMode" class="sub-list" :data-parent="g.major.id">
<li v-for="s in g.children" :key="s.id" :data-id="s.id" class="sub-item cat-row child"> <li v-for="s in g.children" :key="s.id" :data-id="s.id" class="sub-item cat-row child">
<div class="cat-main"> <div class="cat-main">
<span v-show="reorderMode" class="drag-handle sub-handle" title="소분류 순서 변경"></span> <span v-show="reorderMode" class="drag-handle sub-handle" title="소분류 순서 변경"></span>
@@ -440,6 +465,37 @@ button.danger {
.sub-mark { .sub-mark {
opacity: 0.45; opacity: 0.45;
} }
/* 대분류 펼침 삼각형 */
.expand-btn {
flex: none;
width: 1.4rem;
height: 1.4rem;
border: 0;
background: transparent;
color: var(--color-text);
font-size: 0.7rem;
cursor: pointer;
transition: transform 0.15s ease;
opacity: 0.7;
}
.expand-btn.open {
transform: rotate(90deg);
}
.expand-btn.empty {
opacity: 0.15;
cursor: default;
}
.sub-count {
flex: none;
min-width: 1.2rem;
text-align: center;
font-size: 0.72rem;
padding: 0.05rem 0.3rem;
border-radius: 999px;
background: var(--color-border);
color: var(--color-text);
opacity: 0.75;
}
.hint.sm { .hint.sm {
font-size: 0.78rem; font-size: 0.78rem;
margin: 0 0 0.75rem; margin: 0 0 0.75rem;