From 633aa54274bd10100bdee6ccb14850eedb3c86fb Mon Sep 17 00:00:00 2001 From: ByungCheol Date: Wed, 3 Jun 2026 18:51:43 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=B9=B4=EB=93=9C=20=EC=95=8C=EB=A6=BC?= =?UTF-8?q?=20=EC=9E=90=EB=8F=99=EC=9D=B8=EC=8B=9D=20-=20=ED=99=95?= =?UTF-8?q?=EC=9D=B8=20=ED=95=84=EC=9A=94=20=EB=B0=B0=EC=A7=80/=ED=99=95?= =?UTF-8?q?=EC=A0=95=20UI/=EC=B9=B4=EC=9A=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 미확인(pending) 내역에 '확인필요' 배지 + 행 강조 + '확인' 버튼(즉시 확정) - 수정 저장 시 미확인 건은 확정 처리 - 가계부 헤더에 '확인 필요 N건' 카운트 배지 - accountApi: pendingCount/confirmEntry Co-Authored-By: Claude Opus 4.8 --- src/api/accountApi.js | 8 ++++ src/views/account/AccountView.vue | 74 +++++++++++++++++++++++++++++-- 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/api/accountApi.js b/src/api/accountApi.js index 69da852..8e0dd63 100644 --- a/src/api/accountApi.js +++ b/src/api/accountApi.js @@ -153,6 +153,14 @@ export const accountApi = { return http.delete(`/account/tags/${id}`) }, + // 카드 결제 알림 자동인식 (미확인 내역) + pendingCount() { + return http.get('/account/entries/pending/count') + }, + confirmEntry(id, payload) { + return http.post(`/account/entries/${id}/confirm`, payload || {}) + }, + // 영수증 OCR (백엔드가 Google Vision 호출 → 전체 텍스트 반환) ocrReceipt(blob) { const fd = new FormData() diff --git a/src/views/account/AccountView.vue b/src/views/account/AccountView.vue index d2ae2a6..7fa2399 100644 --- a/src/views/account/AccountView.vue +++ b/src/views/account/AccountView.vue @@ -14,6 +14,26 @@ const entries = ref([]) const summary = ref({ totalIncome: 0, totalExpense: 0, balance: 0 }) const loading = ref(false) const error = ref(null) +const pendingCount = ref(0) // 확인 필요(카드 알림 자동인식) 건수 +const editingPending = ref(false) // 수정 중인 항목이 미확인 건인지 + +async function loadPendingCount() { + try { + pendingCount.value = (await accountApi.pendingCount()).count || 0 + } catch { + pendingCount.value = 0 + } +} +// 미확인 내역 즉시 확정(분류 미지정으로 수락) +async function confirmRow(e) { + try { + await accountApi.confirmEntry(e.id, {}) + await load() + await loadPendingCount() + } catch (err) { + alert(err.response?.data?.message || '확인 처리에 실패했습니다.') + } +} // 검색·필터 const filterOpen = ref(false) @@ -331,6 +351,7 @@ function todayStr() { function openCreate() { editId.value = null + editingPending.value = false Object.assign(form, { entryDate: todayStr(), type: 'EXPENSE', category: '', amount: null, memo: '', walletKind: '', walletId: '', toWalletKind: '', toWalletId: '', principal: null, interest: null, installmentMonths: '' }) selectedTagIds.value = [] cancelAddCategory() @@ -339,6 +360,7 @@ function openCreate() { } function openEdit(e) { editId.value = e.id + editingPending.value = !!e.pending Object.assign(form, { entryDate: e.entryDate, type: e.type, @@ -428,10 +450,18 @@ async function submit() { tagIds: selectedTagIds.value, } try { - if (editId.value) await accountApi.update(editId.value, payload) - else await accountApi.create(payload) + if (editId.value) { + await accountApi.update(editId.value, payload) + // 미확인(카드 알림) 건이면 수정 저장 후 확정 처리 + if (editingPending.value) { + await accountApi.confirmEntry(editId.value, {}) + } + } else { + await accountApi.create(payload) + } formOpen.value = false await load() + await loadPendingCount() } catch (e) { formError.value = e.response?.data?.message || '저장에 실패했습니다.' } finally { @@ -460,13 +490,14 @@ onMounted(async () => { loadTagOptions() loadWallets() loadCategories() + loadPendingCount() })