Improve transaction entry flow
This commit is contained in:
@@ -66,3 +66,5 @@
|
||||
- 내역, 통계, 자산 화면의 월 이동 UI를 공통 `MonthNavigator` 컴포넌트로 통일했다.
|
||||
- 통계와 자산 화면의 상단 헤더와 월 이동 영역을 분리해 페이지 간 날짜 조작 위치를 맞췄다.
|
||||
- 거래 입력/수정 날짜 선택 바텀시트에 `오늘`, `이번 달`, `선택일` 빠른 이동 버튼을 추가했다.
|
||||
- 거래 입력/수정에서 자산 선택 후 분류 선택 바텀시트로 자연스럽게 이어지도록 했다.
|
||||
- 거래 입력/수정의 수입/지출 타입 변경 시 카테고리 선택을 초기화하고 저장/삭제/복사 실패 토스트를 추가했다.
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
placeholder=""
|
||||
button-class="transaction-form-select"
|
||||
:options="assetOptions"
|
||||
@update:model-value="handleAssetSelected"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -48,6 +49,7 @@
|
||||
placeholder=""
|
||||
button-class="transaction-form-select"
|
||||
:options="categoryOptions"
|
||||
@update:model-value="invalidField = ''"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -161,8 +163,23 @@ watch(selectableCategories, (items: any[]) => {
|
||||
}, { immediate: true })
|
||||
|
||||
function setType(type: TransactionType) {
|
||||
if (form.type === type) return
|
||||
form.type = type
|
||||
form.categoryId = ''
|
||||
invalidField.value = ''
|
||||
if (form.assetId) openCategorySelect()
|
||||
}
|
||||
|
||||
function handleAssetSelected() {
|
||||
invalidField.value = ''
|
||||
if (!form.categoryId) openCategorySelect()
|
||||
}
|
||||
|
||||
function openCategorySelect() {
|
||||
if (!categoryOptions.value.length) return
|
||||
window.setTimeout(() => {
|
||||
categorySelectRef.value?.focus()
|
||||
}, 220)
|
||||
}
|
||||
|
||||
function captureLocation() {
|
||||
@@ -200,6 +217,8 @@ async function submit() {
|
||||
longitude: null
|
||||
})
|
||||
await navigateTo('/')
|
||||
} catch {
|
||||
showToast('거래를 저장하지 못했습니다.')
|
||||
} finally {
|
||||
pending.value = false
|
||||
}
|
||||
@@ -228,4 +247,12 @@ function showValidation(field: string, message: string, focus: () => void) {
|
||||
nextTick(() => focus())
|
||||
return false
|
||||
}
|
||||
|
||||
function showToast(message: string) {
|
||||
toastMessage.value = message
|
||||
if (toastTimer) clearTimeout(toastTimer)
|
||||
toastTimer = setTimeout(() => {
|
||||
toastMessage.value = ''
|
||||
}, 2200)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
placeholder=""
|
||||
button-class="transaction-form-select"
|
||||
:options="assetOptions"
|
||||
@update:model-value="handleAssetSelected"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -48,6 +49,7 @@
|
||||
placeholder=""
|
||||
button-class="transaction-form-select"
|
||||
:options="categoryOptions"
|
||||
@update:model-value="invalidField = ''"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -184,8 +186,23 @@ watch(selectableCategories, (items: any[]) => {
|
||||
})
|
||||
|
||||
function setType(type: TransactionType) {
|
||||
if (form.type === type) return
|
||||
form.type = type
|
||||
form.categoryId = ''
|
||||
invalidField.value = ''
|
||||
if (form.assetId) openCategorySelect()
|
||||
}
|
||||
|
||||
function handleAssetSelected() {
|
||||
invalidField.value = ''
|
||||
if (!form.categoryId) openCategorySelect()
|
||||
}
|
||||
|
||||
function openCategorySelect() {
|
||||
if (!categoryOptions.value.length) return
|
||||
window.setTimeout(() => {
|
||||
categorySelectRef.value?.focus()
|
||||
}, 220)
|
||||
}
|
||||
|
||||
function clearLocation() {
|
||||
@@ -210,28 +227,46 @@ function payload() {
|
||||
async function save() {
|
||||
if (!validateForm()) return
|
||||
pending.value = true
|
||||
await $fetch(`/api/transactions/${id.value}`, {
|
||||
method: 'PATCH',
|
||||
body: payload()
|
||||
})
|
||||
pending.value = false
|
||||
await router.back()
|
||||
try {
|
||||
await $fetch(`/api/transactions/${id.value}`, {
|
||||
method: 'PATCH',
|
||||
body: payload()
|
||||
})
|
||||
await router.back()
|
||||
} catch {
|
||||
showToast('거래를 저장하지 못했습니다.')
|
||||
} finally {
|
||||
pending.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function remove() {
|
||||
if (!confirm('이 거래를 삭제할까요?')) return
|
||||
await $fetch(`/api/transactions/${id.value}`, { method: 'DELETE' })
|
||||
await navigateTo('/')
|
||||
pending.value = true
|
||||
try {
|
||||
await $fetch(`/api/transactions/${id.value}`, { method: 'DELETE' })
|
||||
await navigateTo('/')
|
||||
} catch {
|
||||
showToast('거래를 삭제하지 못했습니다.')
|
||||
} finally {
|
||||
pending.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function copy() {
|
||||
if (!validateForm()) return
|
||||
pending.value = true
|
||||
const created = await $fetch<any>('/api/transactions', {
|
||||
method: 'POST',
|
||||
body: { ...payload(), occurredAt: dateOnlyToNoon(formatDateOnly()) }
|
||||
})
|
||||
pending.value = false
|
||||
await navigateTo(`/transactions/${created.id}`)
|
||||
try {
|
||||
const created = await $fetch<any>('/api/transactions', {
|
||||
method: 'POST',
|
||||
body: { ...payload(), occurredAt: dateOnlyToNoon(formatDateOnly()) }
|
||||
})
|
||||
await navigateTo(`/transactions/${created.id}`)
|
||||
} catch {
|
||||
showToast('거래를 복사하지 못했습니다.')
|
||||
} finally {
|
||||
pending.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
@@ -257,4 +292,12 @@ function showValidation(field: string, message: string, focus: () => void) {
|
||||
nextTick(() => focus())
|
||||
return false
|
||||
}
|
||||
|
||||
function showToast(message: string) {
|
||||
toastMessage.value = message
|
||||
if (toastTimer) clearTimeout(toastTimer)
|
||||
toastTimer = setTimeout(() => {
|
||||
toastMessage.value = ''
|
||||
}, 2200)
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user