- 검색 필터(구분·분류·계좌·태그) select → SheetSelect(바텀시트) - 내역 추가/수정 폼: 계좌·상환대상 ChipSelect → SheetSelect, 분류 CategoryPicker → 트리거 → 바텀시트(선택 시 자동 닫힘) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,8 @@ import { useDialog } from '@/composables/dialog'
|
||||
import { appLock } from '@/composables/appLock'
|
||||
import IconBtn from '@/components/ui/IconBtn.vue'
|
||||
import CategoryPicker from '@/components/ui/CategoryPicker.vue'
|
||||
import ChipSelect from '@/components/ui/ChipSelect.vue'
|
||||
import SheetSelect from '@/components/ui/SheetSelect.vue'
|
||||
import BottomSheet from '@/components/ui/BottomSheet.vue'
|
||||
import { imageToBlob, parseReceiptText } from '@/utils/receiptOcr'
|
||||
import { cardNotif } from '@/native/cardNotif'
|
||||
import { CARD_NOTIF_ENABLED } from '@/config/features'
|
||||
@@ -87,6 +88,9 @@ function resetFilters() {
|
||||
const formOpen = ref(false)
|
||||
const editId = ref(null)
|
||||
const form = reactive({ entryDate: '', type: 'EXPENSE', category: '', amount: null, memo: '', walletKind: '', walletId: '', toWalletKind: '', toWalletId: '', principal: null, interest: null, annualFee: null, installmentMonths: '', currency: 'KRW', foreignAmount: null, rate: null })
|
||||
// 분류 선택 바텀시트 — 최종 분류가 정해지면 자동 닫힘
|
||||
const catSheet = ref(false)
|
||||
watch(() => form.category, (v) => { if (v) catSheet.value = false })
|
||||
|
||||
// ===== 외화 결제 =====
|
||||
const CURRENCIES = ['KRW', 'USD', 'JPY', 'EUR', 'CNY', 'GBP', 'AUD', 'CAD', 'HKD', 'SGD', 'THB', 'VND', 'TWD', 'PHP', 'MYR']
|
||||
@@ -441,6 +445,26 @@ function onTypeChange() {
|
||||
}
|
||||
}
|
||||
|
||||
// 검색·필터 바텀시트 옵션(맨 앞 '전체'로 해제 가능)
|
||||
const filterTypeOptions = [
|
||||
{ value: '', label: '구분 전체' },
|
||||
{ value: 'INCOME', label: '수입' },
|
||||
{ value: 'EXPENSE', label: '지출' },
|
||||
{ value: 'TRANSFER', label: '이체' },
|
||||
]
|
||||
const filterCategoryOptions = computed(() => [
|
||||
{ value: '', label: '분류 전체' },
|
||||
...allCategoryNames.value.map((c) => ({ value: c, label: c })),
|
||||
])
|
||||
const filterWalletOptions = computed(() => [
|
||||
{ value: '', label: '계좌 전체' },
|
||||
...wallets.value.map((w) => ({ value: w.id, label: w.name })),
|
||||
])
|
||||
const filterTagOptions = computed(() => [
|
||||
{ value: '', label: '태그 전체' },
|
||||
...tagOptions.value.map((t) => ({ value: t.id, label: t.name })),
|
||||
])
|
||||
|
||||
// 태그 (태그 관리에 등록된 태그 선택)
|
||||
const tagOptions = ref([]) // [{ id, name }]
|
||||
const selectedTagIds = ref([])
|
||||
@@ -1021,24 +1045,10 @@ onMounted(async () => {
|
||||
v-model="filters.keyword" type="text" class="f-keyword"
|
||||
placeholder="메모·분류 검색" @keyup.enter="applyFilters"
|
||||
/>
|
||||
<select v-model="filters.type">
|
||||
<option value="">구분 전체</option>
|
||||
<option value="INCOME">수입</option>
|
||||
<option value="EXPENSE">지출</option>
|
||||
<option value="TRANSFER">이체</option>
|
||||
</select>
|
||||
<select v-model="filters.category">
|
||||
<option value="">분류 전체</option>
|
||||
<option v-for="c in allCategoryNames" :key="c" :value="c">{{ c }}</option>
|
||||
</select>
|
||||
<select v-model="filters.walletId">
|
||||
<option value="">계좌 전체</option>
|
||||
<option v-for="w in wallets" :key="w.id" :value="w.id">{{ w.name }}</option>
|
||||
</select>
|
||||
<select v-model="filters.tagId">
|
||||
<option value="">태그 전체</option>
|
||||
<option v-for="t in tagOptions" :key="t.id" :value="t.id">{{ t.name }}</option>
|
||||
</select>
|
||||
<div class="f-sel"><SheetSelect v-model="filters.type" :options="filterTypeOptions" title="구분" placeholder="구분 전체" /></div>
|
||||
<div class="f-sel"><SheetSelect v-model="filters.category" :options="filterCategoryOptions" title="분류" placeholder="분류 전체" /></div>
|
||||
<div class="f-sel"><SheetSelect v-model="filters.walletId" :options="filterWalletOptions" title="계좌" placeholder="계좌 전체" /></div>
|
||||
<div class="f-sel"><SheetSelect v-model="filters.tagId" :options="filterTagOptions" title="태그" placeholder="태그 전체" /></div>
|
||||
<IconBtn icon="search" title="적용" variant="primary" @click="applyFilters" />
|
||||
<IconBtn icon="refresh" title="초기화" @click="resetFilters" />
|
||||
</div>
|
||||
@@ -1186,11 +1196,12 @@ onMounted(async () => {
|
||||
{{ k.label }}
|
||||
</button>
|
||||
</div>
|
||||
<ChipSelect
|
||||
<SheetSelect
|
||||
v-if="form.walletKind"
|
||||
v-model="form.walletId"
|
||||
:options="fromWalletOptions"
|
||||
:disabled="submitting"
|
||||
title="계좌 선택" placeholder="계좌를 선택하세요"
|
||||
:empty-text="`등록된 ${walletKindLabel}이(가) 없습니다`"
|
||||
/>
|
||||
</div>
|
||||
@@ -1212,11 +1223,12 @@ onMounted(async () => {
|
||||
{{ k.label }}
|
||||
</button>
|
||||
</div>
|
||||
<ChipSelect
|
||||
<SheetSelect
|
||||
v-if="form.toWalletKind"
|
||||
v-model="form.toWalletId"
|
||||
:options="toWalletOptions"
|
||||
:disabled="submitting"
|
||||
title="입금 계좌 선택" placeholder="계좌를 선택하세요"
|
||||
empty-text="등록된 계좌가 없습니다"
|
||||
/>
|
||||
</div>
|
||||
@@ -1226,10 +1238,11 @@ onMounted(async () => {
|
||||
<template v-if="isRepayment">
|
||||
<div class="field">
|
||||
<span class="field-label">대상(대출/카드)</span>
|
||||
<ChipSelect
|
||||
<SheetSelect
|
||||
v-model="form.toWalletId"
|
||||
:options="repayTargetOptions"
|
||||
:disabled="submitting"
|
||||
title="상환 대상 선택" placeholder="대출/카드를 선택하세요"
|
||||
empty-text="등록된 대출/카드가 없습니다"
|
||||
/>
|
||||
</div>
|
||||
@@ -1264,7 +1277,17 @@ onMounted(async () => {
|
||||
<label v-if="repayTargetIsCard">연회비<input v-model.number="form.annualFee" type="number" min="0" placeholder="원 (지출·분류 연회비)" :disabled="submitting" /></label>
|
||||
</template>
|
||||
|
||||
<label v-if="form.type === 'INCOME' || form.type === 'EXPENSE'">분류
|
||||
<div v-if="form.type === 'INCOME' || form.type === 'EXPENSE'" class="field">
|
||||
<span class="field-label">분류</span>
|
||||
<button
|
||||
type="button" class="sheet-trigger" :class="{ empty: !form.category }"
|
||||
:disabled="submitting" @click="catSheet = true"
|
||||
>
|
||||
<span class="st-label">{{ form.category || '분류를 선택하세요' }}</span>
|
||||
<span class="st-caret">▾</span>
|
||||
</button>
|
||||
</div>
|
||||
<BottomSheet v-model="catSheet" title="분류 선택">
|
||||
<CategoryPicker
|
||||
v-model="form.category"
|
||||
:type="form.type"
|
||||
@@ -1272,7 +1295,7 @@ onMounted(async () => {
|
||||
:disabled="submitting"
|
||||
@category-added="loadCategories"
|
||||
/>
|
||||
</label>
|
||||
</BottomSheet>
|
||||
<label v-if="!isRepayment" class="amount-label">
|
||||
금액
|
||||
<div class="amount-row">
|
||||
@@ -1502,6 +1525,43 @@ button.primary {
|
||||
flex: 1;
|
||||
min-width: 8rem;
|
||||
}
|
||||
.filter-bar .f-sel {
|
||||
flex: 1 1 7rem;
|
||||
min-width: 6.5rem;
|
||||
}
|
||||
/* 시트 트리거(필드형) — 분류 선택 등 */
|
||||
.sheet-trigger {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
width: 100%;
|
||||
padding: 0.5rem 0.7rem;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 4px;
|
||||
background: var(--color-background-soft);
|
||||
color: var(--color-text);
|
||||
font-size: 0.9rem;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
}
|
||||
.sheet-trigger:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.sheet-trigger.empty .st-label {
|
||||
opacity: 0.5;
|
||||
}
|
||||
.st-label {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.st-caret {
|
||||
opacity: 0.5;
|
||||
font-size: 0.8rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.summary {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
|
||||
Reference in New Issue
Block a user