fix: 고정지출 등록 확인 인앱 모달 + 분류 관리 계층 드래그
CI / build (push) Successful in 36s

1. PC 네이티브 confirm 제목('sb_pt') 노출 → '고정지출 등록' 인앱 확인 모달로 교체
   (성공 시 토스트 안내). electron app.setName('Slim Budget')로 다른 네이티브
   대화상자 제목도 개선(다음 Electron 빌드 시 반영).
2. 분류 관리 드래그를 계층형으로: 대분류는 블록 단위(소분류 함께) 이동,
   소분류는 그 대분류 안에서만 이동(고유 group). 로컬 즉시반영으로 스냅백 방지.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ByungCheol
2026-06-23 21:02:09 +09:00
parent cad1aba0ca
commit 8add41d94f
3 changed files with 213 additions and 62 deletions
+78 -17
View File
@@ -579,8 +579,18 @@ async function remove(e) {
}
}
// 현재 수정 중인 내역을 '매월' 고정지출로 바로 등록 (주기·시작일은 고정지출 화면에서 변경)
async function registerAsRecurring() {
// 고정지출 등록 확인 모달 상태 (네이티브 confirm 대신 — PC 팝업 제목 'sb_pt' 노출 방지)
const recurConfirm = reactive({ open: false, title: '', dom: 1, amount: 0, payload: null })
const flashMsg = ref('')
let flashTimer = null
function flash(msg) {
flashMsg.value = msg
if (flashTimer) clearTimeout(flashTimer)
flashTimer = setTimeout(() => (flashMsg.value = ''), 2800)
}
// 현재 수정 중인 내역을 '매월' 고정지출로 등록 — 확인 모달을 띄운다
function registerAsRecurring() {
if (form.type === 'REPAYMENT') return // 고정지출은 수입/지출/이체만
const amount = Number(form.amount)
if (!amount || amount <= 0) {
@@ -597,26 +607,36 @@ async function registerAsRecurring() {
// 이번 회차 중복 방지: 시작일을 내역 다음날로 → 다음 발생부터 생성
const start = new Date(y, m - 1, d + 1)
const startStr = `${start.getFullYear()}-${String(start.getMonth() + 1).padStart(2, '0')}-${String(start.getDate()).padStart(2, '0')}`
if (!confirm(`'${title}'\n매월 ${dom}일 · ${won(amount)} 고정지출로 등록할까요?\n(주기·시작일은 고정지출 화면에서 변경할 수 있어요)`)) return
recurConfirm.title = title
recurConfirm.dom = dom
recurConfirm.amount = amount
recurConfirm.payload = {
title,
type: form.type,
amount,
category: form.type === 'TRANSFER' ? null : form.category || null,
memo: form.memo || null,
walletId: form.walletId || null,
toWalletId: form.type === 'TRANSFER' ? form.toWalletId || null : null,
frequency: 'MONTHLY',
dayOfMonth: dom,
startDate: startStr,
active: true,
}
recurConfirm.open = true
}
async function doRegisterRecurring() {
if (!recurConfirm.payload) return
submitting.value = true
try {
await accountApi.createRecurring({
title,
type: form.type,
amount,
category: form.type === 'TRANSFER' ? null : form.category || null,
memo: form.memo || null,
walletId: form.walletId || null,
toWalletId: form.type === 'TRANSFER' ? form.toWalletId || null : null,
frequency: 'MONTHLY',
dayOfMonth: dom,
startDate: startStr,
active: true,
})
await accountApi.createRecurring(recurConfirm.payload)
recurConfirm.open = false
formOpen.value = false
alert('고정지출로 등록했습니다. 고정지출 화면에서 주기를 변경할 수 있어요.')
flash('고정지출로 등록했습니다. 고정지출 화면에서 주기를 변경할 수 있어요.')
} catch (e) {
formError.value = e.response?.data?.message || '고정지출 등록에 실패했습니다.'
recurConfirm.open = false
} finally {
submitting.value = false
}
@@ -644,6 +664,7 @@ onMounted(async () => {
<h1>가계부<span v-if="pendingCount" class="pending-count">확인 필요 {{ pendingCount }}</span></h1>
<IconBtn icon="plus" title="내역 추가" variant="primary" @click="openCreate" />
</header>
<Transition name="fade"><p v-if="flashMsg" class="flash">{{ flashMsg }}</p></Transition>
<!-- 카드 결제 알림 자동인식 권한 안내 (네이티브, 미허용 ) -->
<div v-if="notifNative && !notifEnabled" class="notif-banner">
@@ -936,6 +957,26 @@ onMounted(async () => {
</div>
</Transition>
</Teleport>
<!-- 고정지출 등록 확인 (네이티브 confirm 대체 제목 'sb_pt' 노출 방지) -->
<Teleport to="body">
<Transition name="fade">
<div v-if="recurConfirm.open" class="modal-backdrop" @click.self="recurConfirm.open = false">
<div class="modal confirm-modal" role="dialog" aria-modal="true">
<h2>고정지출 등록</h2>
<p class="confirm-body">
<b>{{ recurConfirm.title }}</b><br />
매월 <b>{{ recurConfirm.dom }}</b> · <b>{{ won(recurConfirm.amount) }}</b> 으로 등록할까요?
</p>
<p class="confirm-sub">주기·시작일은 고정지출 화면에서 변경할 있어요.</p>
<div class="buttons">
<IconBtn icon="close" title="취소" @click="recurConfirm.open = false" />
<IconBtn icon="save" title="등록" variant="primary" :disabled="submitting" @click="doRegisterRecurring" />
</div>
</div>
</div>
</Transition>
</Teleport>
</section>
</template>
@@ -1256,6 +1297,26 @@ button.primary {
background: rgba(0, 0, 0, 0.5);
padding: 1rem;
}
.flash {
margin: 0.5rem 0 0;
padding: 0.6rem 0.9rem;
background: hsla(160, 100%, 37%, 0.12);
border: 1px solid hsla(160, 100%, 37%, 0.5);
border-radius: 8px;
font-size: 0.85rem;
}
.confirm-modal {
max-width: 340px;
}
.confirm-body {
margin: 0.5rem 0;
line-height: 1.5;
}
.confirm-sub {
margin: 0 0 0.5rem;
font-size: 0.8rem;
opacity: 0.6;
}
.modal {
position: relative;
width: 100%;