Merge branch 'dev'
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user