diff --git a/src/views/account/AccountView.vue b/src/views/account/AccountView.vue index 862410a..ecb52dd 100644 --- a/src/views/account/AccountView.vue +++ b/src/views/account/AccountView.vue @@ -477,7 +477,8 @@ function openEdit(e) { syncCategoryMajor() cancelAddCategory() pasteDetected.value = null - pasteOpen.value = false + pasteModalOpen.value = false + pasteText.value = '' formError.value = null formInfo.value = '' formOpen.value = true @@ -737,10 +738,11 @@ async function saveAsQuick() { } } -// ===== 문자/푸시 붙여넣기 → 파싱 자동채움 ===== -const pasteDetected = ref(null) // 클립보드 자동감지 결과(배너) -const pasteOpen = ref(false) // 수동 붙여넣기 입력칸 +// ===== 문자/푸시 붙여넣기 → 파싱 자동채움 (모달) ===== +const pasteDetected = ref(null) // 자동감지 결과(버튼 배지 + 모달 배너) +const pasteModalOpen = ref(false) // 붙여넣기 모달 const pasteText = ref('') +const pasteError = ref('') function applyParsed(p) { if (!p || !p.recognized) return form.type = p.type === 'INCOME' ? 'INCOME' : 'EXPENSE' @@ -749,8 +751,9 @@ function applyParsed(p) { if (p.date) form.entryDate = p.date categoryMajor.value = '' pasteDetected.value = null - pasteOpen.value = false + pasteModalOpen.value = false pasteText.value = '' + pasteError.value = '' } async function parseAndApply(text, { silent } = {}) { const t = (text || '').trim() @@ -767,10 +770,9 @@ async function parseAndApply(text, { silent } = {}) { } return false } -// 추가 팝업 열 때 클립보드 자동 감지(권한 막히면 조용히 무시) +// 추가 팝업 열 때 클립보드 자동 감지(권한 막히면 조용히 무시) → 버튼 배지로 안내 async function tryClipboardOnOpen() { pasteDetected.value = null - pasteOpen.value = false pasteText.value = '' try { const txt = await navigator.clipboard.readText() @@ -779,19 +781,27 @@ async function tryClipboardOnOpen() { // 클립보드 접근 불가 — 버튼으로 수동 } } -// '붙여넣기' 버튼: 클립보드 읽어 바로 적용, 실패 시 수동 입력칸 -async function pasteFromClipboard() { - try { - const txt = await navigator.clipboard.readText() - const ok = await parseAndApply(txt, { silent: false }) - if (!ok) pasteOpen.value = true - } catch { - pasteOpen.value = true +// '문자·푸시' 버튼 → 모달 열기 + 클립보드 자동 읽기 +async function openPasteModal() { + pasteError.value = '' + pasteModalOpen.value = true + if (!pasteText.value) { + try { + const txt = await navigator.clipboard.readText() + if (txt) { + pasteText.value = txt + await parseAndApply(txt, { silent: true }) + } + } catch { + // 클립보드 접근 불가 — 수동 붙여넣기 + } } } +// 모달에서 분석·채우기 async function analyzePasted() { + pasteError.value = '' const ok = await parseAndApply(pasteText.value, { silent: false }) - if (!ok) formError.value = '거래 내용을 인식하지 못했습니다.' + if (!ok) pasteError.value = '거래 내용을 인식하지 못했습니다. 금액과 승인/입금 등이 포함됐는지 확인하세요.' } onMounted(async () => { @@ -947,17 +957,23 @@ onMounted(async () => {

{{ editId ? '내역 수정' : '내역 추가' }}

- +
- +
+ + +
영수증 인식 중… @@ -967,26 +983,9 @@ onMounted(async () => { 자동 입력됨 사진에서 금액·날짜·상호를 자동 입력 - -
- -
-
- 감지: {{ pasteDetected.type === 'INCOME' ? '수입' : '지출' }} {{ won(pasteDetected.amount) }} - -
-
- - -
-
- +

문자·푸시 붙여넣기

+

문자/푸시 내용을 붙여넣고 분석하면 구분·금액·메모·날짜가 자동 입력됩니다.

+ +
+ 감지: {{ pasteDetected.type === 'INCOME' ? '수입' : '지출' }} {{ won(pasteDetected.amount) }} +
+

{{ pasteError }}

+
+ + +
+ + + + @@ -1582,14 +1603,19 @@ button.primary { } .receipt-box { display: flex; - align-items: center; - flex-wrap: wrap; + flex-direction: column; + align-items: flex-start; gap: 0.5rem; padding: 0.5rem 0.6rem; border: 1px dashed var(--color-border); border-radius: 6px; background: var(--color-background-soft); } +.rb-actions { + display: flex; + gap: 0.5rem; + flex-wrap: wrap; +} .receipt-input { display: none; } @@ -1848,15 +1874,12 @@ button.primary { cursor: pointer; } /* 문자/푸시 붙여넣기 */ -.paste-extra { - display: flex; - flex-direction: column; - gap: 0.4rem; -} .paste-btn { - margin-left: auto; /* 영수증 행 우측으로 */ - padding: 0.4rem 0.7rem; - font-size: 0.8rem; + display: inline-flex; + align-items: center; + gap: 0.25rem; + padding: 0.45rem 0.85rem; + font-size: 0.85rem; border: 1px solid var(--color-border); border-radius: 6px; background: var(--color-background); @@ -1864,23 +1887,27 @@ button.primary { cursor: pointer; white-space: nowrap; } +.paste-btn.detected { + border-color: hsla(160, 100%, 37%, 0.7); +} +.paste-btn .pb-dot { + color: hsla(160, 100%, 37%, 1); + font-size: 0.6rem; +} .paste-detected { font-size: 0.82rem; background: hsla(160, 100%, 37%, 0.1); border: 1px solid hsla(160, 100%, 37%, 0.4); border-radius: 6px; padding: 0.4rem 0.6rem; - display: flex; - align-items: center; - gap: 0.5rem; - flex-wrap: wrap; + margin: 0.5rem 0; } -.paste-manual { - display: flex; - flex-direction: column; - gap: 0.3rem; +.paste-modal .pm-hint { + font-size: 0.82rem; + opacity: 0.7; + margin: 0 0 0.6rem; } -.paste-manual textarea { +.paste-modal .pm-text { width: 100%; box-sizing: border-box; padding: 0.5rem; @@ -1891,17 +1918,6 @@ button.primary { font: inherit; resize: vertical; } -.pd-apply { - padding: 0.25rem 0.7rem; - font-size: 0.8rem; - font-weight: 600; - border: 1px solid hsla(160, 100%, 37%, 1); - border-radius: 6px; - background: hsla(160, 100%, 37%, 1); - color: #fff; - cursor: pointer; - align-self: flex-start; -} .to-recurring { display: block; width: 100%;