151 lines
5.0 KiB
Vue
151 lines
5.0 KiB
Vue
<template>
|
|
<section class="px-4 py-4">
|
|
<h1 class="mb-4 text-xl font-bold">거래 추가</h1>
|
|
|
|
<form class="space-y-4" @submit.prevent="submit">
|
|
<div class="segmented">
|
|
<button type="button" class="segmented-button" :class="{ 'segmented-button-active': form.type === 'income' }" @click="form.type = 'income'">수입</button>
|
|
<button type="button" class="segmented-button" :class="{ 'segmented-button-active': form.type === 'expense' }" @click="form.type = 'expense'">지출</button>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-[1fr_auto] gap-2">
|
|
<div>
|
|
<label class="label" for="occurredAt">날짜</label>
|
|
<input id="occurredAt" v-model="form.occurredAt" class="field" type="datetime-local" required>
|
|
</div>
|
|
<div>
|
|
<span class="label">반복</span>
|
|
<button type="button" class="btn-secondary h-[50px]" disabled>없음</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="label">자산</label>
|
|
<BottomSheetSelect
|
|
v-model="form.assetId"
|
|
title="자산 선택"
|
|
placeholder="자산 선택"
|
|
:options="assetOptions"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="label">분류</label>
|
|
<BottomSheetSelect
|
|
v-model="form.categoryId"
|
|
title="분류 선택"
|
|
placeholder="분류 선택"
|
|
:options="categoryOptions"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="label" for="amount">금액</label>
|
|
<input id="amount" v-model.number="form.amount" class="field text-right text-xl font-bold" inputmode="numeric" min="1" type="number" required>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="label" for="title">내용</label>
|
|
<input id="title" v-model="form.title" class="field" placeholder="이마트칠성점" required>
|
|
</div>
|
|
|
|
<div>
|
|
<div class="mb-1 flex items-center justify-between">
|
|
<span class="label mb-0">위치</span>
|
|
<div class="flex gap-2">
|
|
<button type="button" class="text-sm font-semibold text-brand-700" @click="captureLocation">가져오기</button>
|
|
<button v-if="form.latitude" type="button" class="text-sm font-semibold text-muted" @click="clearLocation">삭제</button>
|
|
</div>
|
|
</div>
|
|
<p class="rounded-md border border-line bg-paper px-3 py-3 text-sm text-muted">
|
|
{{ form.latitude ? `${form.latitude}, ${form.longitude}` : '선택 사항입니다.' }}
|
|
</p>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="label" for="memo">메모</label>
|
|
<textarea id="memo" v-model="form.memo" class="field min-h-28 resize-none" placeholder="자유롭게 기록"></textarea>
|
|
</div>
|
|
|
|
<button class="btn-primary sticky bottom-20 w-full" type="submit" :disabled="pending">
|
|
{{ pending ? '저장 중...' : '저장하기' }}
|
|
</button>
|
|
</form>
|
|
</section>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
const pending = ref(false)
|
|
const { data: assetsData } = await useFetch('/api/assets')
|
|
const { data: categoriesData } = await useFetch('/api/categories')
|
|
|
|
const form = reactive({
|
|
type: 'expense',
|
|
occurredAt: formatDateInput(),
|
|
assetId: '',
|
|
categoryId: '',
|
|
amount: 0,
|
|
title: '',
|
|
memo: '',
|
|
latitude: null as number | null,
|
|
longitude: null as number | null
|
|
})
|
|
|
|
const assets = computed(() => assetsData.value || [])
|
|
const categories = computed(() => categoriesData.value || [])
|
|
const filteredCategories = computed(() => categories.value.filter((category: any) => category.type === form.type && category.active))
|
|
const assetOptions = computed(() => assets.value.map((asset: any) => ({
|
|
value: asset.id,
|
|
label: asset.name,
|
|
description: asset.balanceType === 'cumulative' ? '계속 누적' : '매월 새로 시작'
|
|
})))
|
|
const categoryOptions = computed(() => filteredCategories.value.map((category: any) => ({
|
|
value: category.id,
|
|
label: category.parent ? `${category.parent.name} > ${category.name}` : category.name,
|
|
description: category.type === 'income' ? '수입' : '지출'
|
|
})))
|
|
|
|
watch(assets, (items: any[]) => {
|
|
if (!form.assetId && items[0]) form.assetId = items[0].id
|
|
}, { immediate: true })
|
|
|
|
watch(filteredCategories, (items: any[]) => {
|
|
if (!items.some(item => item.id === form.categoryId)) form.categoryId = items[0]?.id || ''
|
|
}, { immediate: true })
|
|
|
|
function captureLocation() {
|
|
if (!navigator.geolocation) return
|
|
navigator.geolocation.getCurrentPosition((position) => {
|
|
form.latitude = Number(position.coords.latitude.toFixed(7))
|
|
form.longitude = Number(position.coords.longitude.toFixed(7))
|
|
})
|
|
}
|
|
|
|
function clearLocation() {
|
|
form.latitude = null
|
|
form.longitude = null
|
|
}
|
|
|
|
async function submit() {
|
|
pending.value = true
|
|
await $fetch('/api/transactions', {
|
|
method: 'POST',
|
|
body: form
|
|
})
|
|
const keepAssetId = form.assetId
|
|
Object.assign(form, {
|
|
type: form.type,
|
|
occurredAt: formatDateInput(),
|
|
assetId: keepAssetId,
|
|
categoryId: form.categoryId,
|
|
amount: 0,
|
|
title: '',
|
|
memo: '',
|
|
latitude: null,
|
|
longitude: null
|
|
})
|
|
pending.value = false
|
|
await navigateTo('/')
|
|
}
|
|
</script>
|