feat: 자주 쓰는 내역 불러오기 모달 피커 + 붙여넣기 버튼 영수증 행으로
CI / build (push) Failing after 13m52s

- 불러오기: 셀렉트 → 모달 리스트(시인성↑), 항목 탭하면 폼 채움
- 문자·푸시 붙여넣기 버튼을 영수증 스캔과 같은 행(우측)으로 이동, 감지/수동입력은 아래로 분리

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ByungCheol
2026-06-27 15:19:13 +09:00
parent eeea19e20b
commit 5a5959a26e
+98 -21
View File
@@ -653,6 +653,7 @@ async function doRegisterRecurring() {
// ===== 자주 쓰는 내역(빠른 등록 템플릿) ===== // ===== 자주 쓰는 내역(빠른 등록 템플릿) =====
const quickEntries = ref([]) const quickEntries = ref([])
const quickEditMode = ref(false) const quickEditMode = ref(false)
const quickPickerOpen = ref(false) // 불러오기 모달 피커
async function loadQuick() { async function loadQuick() {
try { try {
quickEntries.value = await accountApi.quickEntries() quickEntries.value = await accountApi.quickEntries()
@@ -706,11 +707,9 @@ function applyQuick(q) {
} }
syncCategoryMajor() syncCategoryMajor()
} }
function onQuickPick(e) { function pickQuick(q) {
const id = Number(e.target.value) applyQuick(q)
e.target.value = '' // 다시 선택 가능하게 초기화 quickPickerOpen.value = false
const q = quickEntries.value.find((x) => x.id === id)
if (q) applyQuick(q)
} }
// 현재 폼 값을 자주 쓰는 내역으로 저장 // 현재 폼 값을 자주 쓰는 내역으로 저장
async function saveAsQuick() { async function saveAsQuick() {
@@ -968,12 +967,16 @@ onMounted(async () => {
자동 입력됨 자동 입력됨
</span> </span>
<span v-else class="receipt-hint">사진에서 금액·날짜·상호를 자동 입력</span> <span v-else class="receipt-hint">사진에서 금액·날짜·상호를 자동 입력</span>
<!-- 문자/푸시 붙여넣기 (영수증 스캔과 같은 , 우측) -->
<button
type="button" class="paste-btn"
:disabled="submitting" @click="pasteFromClipboard"
>📋 문자·푸시</button>
<div v-if="ocrRunning" class="receipt-progress"><div class="bar"></div></div> <div v-if="ocrRunning" class="receipt-progress"><div class="bar"></div></div>
</div> </div>
<!-- 문자/푸시 붙여넣기 자동 채움 (iPhone 알림 자동수집 불가 대비) --> <!-- 붙여넣기 감지 결과 / 수동 입력 -->
<div v-if="!isRepayment" class="paste-box"> <div v-if="!isRepayment && (pasteDetected || pasteOpen)" class="paste-extra">
<button type="button" class="paste-btn" :disabled="submitting" @click="pasteFromClipboard">📋 문자·푸시 붙여넣기</button>
<div v-if="pasteDetected" class="paste-detected"> <div v-if="pasteDetected" class="paste-detected">
감지: {{ pasteDetected.type === 'INCOME' ? '수입' : '지출' }} {{ won(pasteDetected.amount) }}<template v-if="pasteDetected.merchant"> · {{ pasteDetected.merchant }}</template> 감지: {{ pasteDetected.type === 'INCOME' ? '수입' : '지출' }} {{ won(pasteDetected.amount) }}<template v-if="pasteDetected.merchant"> · {{ pasteDetected.merchant }}</template>
<button type="button" class="pd-apply" @click="applyParsed(pasteDetected)">채우기</button> <button type="button" class="pd-apply" @click="applyParsed(pasteDetected)">채우기</button>
@@ -984,15 +987,12 @@ onMounted(async () => {
</div> </div>
</div> </div>
<!-- 신규 추가 : 자주 쓰는 내역 불러오기( 채움) --> <!-- 신규 추가 : 자주 쓰는 내역 불러오기(모달 피커) -->
<label v-if="!editId && quickEntries.length" class="quick-load"> 자주 쓰는 내역 불러오기 <button
<select :disabled="submitting" @change="onQuickPick"> v-if="!editId && quickEntries.length"
<option value="">선택해서 불러오기</option> type="button" class="to-recurring to-quick"
<option v-for="q in quickEntries" :key="q.id" :value="q.id"> :disabled="submitting" @click="quickPickerOpen = true"
{{ quickLabel(q) }} · {{ q.type === 'INCOME' ? '+' : '-' }}{{ won(q.amount) }} > 자주 쓰는 내역 불러오기</button>
</option>
</select>
</label>
<label>거래일<input v-model="form.entryDate" type="date" :disabled="submitting" /></label> <label>거래일<input v-model="form.entryDate" type="date" :disabled="submitting" /></label>
<label>구분 <label>구분
@@ -1176,6 +1176,30 @@ onMounted(async () => {
</div> </div>
</Transition> </Transition>
</Teleport> </Teleport>
<!-- 자주 쓰는 내역 불러오기 (모달 피커) -->
<Teleport to="body">
<Transition name="fade">
<div v-if="quickPickerOpen" class="modal-backdrop quick-picker-backdrop" @click.self="quickPickerOpen = false">
<div class="modal quick-picker" role="dialog" aria-modal="true">
<button class="close" type="button" @click="quickPickerOpen = false">×</button>
<h2>자주 쓰는 내역 불러오기</h2>
<ul v-if="quickEntries.length" class="qp-list">
<li v-for="q in quickEntries" :key="q.id">
<button type="button" class="qp-item" @click="pickQuick(q)">
<span class="qp-label">{{ quickLabel(q) }}</span>
<span class="qp-meta">
<span v-if="q.category" class="qp-cat">{{ q.category }}</span>
<span class="qp-amt" :class="q.type === 'INCOME' ? 'income' : 'expense'">{{ q.type === 'INCOME' ? '+' : '-' }}{{ won(q.amount) }}</span>
</span>
</button>
</li>
</ul>
<p v-else class="msg">저장된 자주 쓰는 내역이 없습니다.</p>
</div>
</div>
</Transition>
</Teleport>
</section> </section>
</template> </template>
@@ -1824,20 +1848,21 @@ button.primary {
cursor: pointer; cursor: pointer;
} }
/* 문자/푸시 붙여넣기 */ /* 문자/푸시 붙여넣기 */
.paste-box { .paste-extra {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.4rem; gap: 0.4rem;
} }
.paste-btn { .paste-btn {
align-self: flex-start; margin-left: auto; /* 영수증 행 우측으로 */
padding: 0.4rem 0.7rem; padding: 0.4rem 0.7rem;
font-size: 0.82rem; font-size: 0.8rem;
border: 1px solid var(--color-border); border: 1px solid var(--color-border);
border-radius: 6px; border-radius: 6px;
background: var(--color-background-soft); background: var(--color-background);
color: var(--color-text); color: var(--color-text);
cursor: pointer; cursor: pointer;
white-space: nowrap;
} }
.paste-detected { .paste-detected {
font-size: 0.82rem; font-size: 0.82rem;
@@ -1894,6 +1919,58 @@ button.primary {
color: #b8860b; color: #b8860b;
border-color: rgba(184, 134, 11, 0.6); border-color: rgba(184, 134, 11, 0.6);
} }
/* 자주 쓰는 내역 불러오기 피커 모달 */
.quick-picker-backdrop {
z-index: 1300; /* 내역 모달 위 */
}
.qp-list {
list-style: none;
margin: 0;
padding: 0;
max-height: 60vh;
overflow-y: auto;
}
.qp-list li {
border-bottom: 1px solid var(--color-border);
}
.qp-item {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
width: 100%;
padding: 0.7rem 0.4rem;
background: transparent;
border: 0;
color: var(--color-text);
font: inherit;
cursor: pointer;
text-align: left;
}
.qp-item:hover {
background: var(--color-background-soft);
}
.qp-label {
font-weight: 600;
}
.qp-meta {
display: flex;
align-items: center;
gap: 0.5rem;
flex: 0 0 auto;
}
.qp-cat {
font-size: 0.78rem;
opacity: 0.6;
}
.qp-amt.income {
color: #2e7d32;
font-weight: 600;
}
.qp-amt.expense {
color: #c0392b;
font-weight: 600;
}
.to-recurring:disabled { .to-recurring:disabled {
opacity: 0.5; opacity: 0.5;
cursor: default; cursor: default;