- AppModal 제거, 목록/폼 v-if·v-else 전환(헤더·하단바 유지 + 뒤로가기) - ?budget 라우트 바인딩으로 뒤로가기 시 닫힘 - 분류 선택은 트리거 → 바텀시트(CategoryPicker) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,12 +1,15 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { accountApi } from '@/api/accountApi'
|
||||
import { useDialog } from '@/composables/dialog'
|
||||
import IconBtn from '@/components/ui/IconBtn.vue'
|
||||
import AppModal from '@/components/ui/AppModal.vue'
|
||||
import CategoryPicker from '@/components/ui/CategoryPicker.vue'
|
||||
import BottomSheet from '@/components/ui/BottomSheet.vue'
|
||||
|
||||
const dialog = useDialog()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const now = new Date()
|
||||
const year = ref(now.getFullYear())
|
||||
@@ -17,7 +20,18 @@ const budgetsRaw = ref([])
|
||||
const loading = ref(false)
|
||||
const error = ref(null)
|
||||
|
||||
// 폼 — 라우트(?budget) 바인딩으로 뒤로가기 시 닫힘(페이지 전환처럼)
|
||||
const formOpen = ref(false)
|
||||
function openForm() {
|
||||
formOpen.value = true
|
||||
if (route.query.budget == null) router.push({ query: { ...route.query, budget: '1' } })
|
||||
}
|
||||
function closeForm() {
|
||||
formOpen.value = false
|
||||
if (route.query.budget != null) router.back()
|
||||
}
|
||||
watch(() => route.query.budget, (v) => { if (v == null) formOpen.value = false })
|
||||
|
||||
const editId = ref(null)
|
||||
const form = reactive({
|
||||
category: '',
|
||||
@@ -25,6 +39,9 @@ const form = reactive({
|
||||
})
|
||||
const submitting = ref(false)
|
||||
const formError = ref(null)
|
||||
// 분류 선택 바텀시트 — 최종 분류 정해지면 닫힘
|
||||
const catSheet = ref(false)
|
||||
watch(() => form.category, (v) => { if (v) catSheet.value = false })
|
||||
|
||||
// 분류(지출) 드롭다운
|
||||
const categories = ref([])
|
||||
@@ -152,7 +169,7 @@ function openCreate() {
|
||||
editId.value = null
|
||||
Object.assign(form, { category: '', monthly: null })
|
||||
formError.value = null
|
||||
formOpen.value = true
|
||||
openForm()
|
||||
}
|
||||
function openEdit(s) {
|
||||
const b = budgetsRaw.value.find((x) => x.id === s.id)
|
||||
@@ -160,7 +177,7 @@ function openEdit(s) {
|
||||
editId.value = b.id
|
||||
Object.assign(form, { category: b.category, monthly: b.monthly })
|
||||
formError.value = null
|
||||
formOpen.value = true
|
||||
openForm()
|
||||
}
|
||||
|
||||
async function submit() {
|
||||
@@ -185,7 +202,7 @@ async function submit() {
|
||||
try {
|
||||
if (editId.value) await accountApi.updateBudget(editId.value, payload)
|
||||
else await accountApi.createBudget(payload, { year: year.value, month: month.value })
|
||||
formOpen.value = false
|
||||
closeForm()
|
||||
await load()
|
||||
} catch (e) {
|
||||
formError.value = e.response?.data?.message || '저장에 실패했습니다.'
|
||||
@@ -213,6 +230,7 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<section class="budget">
|
||||
<template v-if="!formOpen">
|
||||
<div class="month-nav">
|
||||
<span class="mn-spacer"></span>
|
||||
<IconBtn icon="chevronLeft" title="이전 달" size="sm" @click="prevMonth" />
|
||||
@@ -279,12 +297,26 @@ onMounted(() => {
|
||||
</li>
|
||||
</ul>
|
||||
<p v-else-if="!loading" class="msg">설정된 예산이 없습니다. "예산 추가"로 시작하세요.</p>
|
||||
</template>
|
||||
|
||||
<!-- 추가/수정 모달 (월별) -->
|
||||
<AppModal v-model="formOpen" :title="`예산 ${editId ? '수정' : '추가'}`">
|
||||
<!-- 예산 추가/수정 (인라인 페이지 — 헤더·하단바 유지, 뒤로가기로 닫힘) -->
|
||||
<div v-else class="budget-page">
|
||||
<header class="ep-head">
|
||||
<IconBtn icon="back" title="뒤로" @click="closeForm()" />
|
||||
<span class="ep-title">예산 {{ editId ? '수정' : '추가' }}</span>
|
||||
</header>
|
||||
<form class="budget-form" @submit.prevent="submit">
|
||||
<div 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="EXPENSE"
|
||||
@@ -292,7 +324,7 @@ onMounted(() => {
|
||||
:disabled="submitting"
|
||||
@category-added="loadCategories"
|
||||
/>
|
||||
</div>
|
||||
</BottomSheet>
|
||||
|
||||
<label>월 예산<input v-model.number="form.monthly" type="number" min="0" placeholder="원" :disabled="submitting" /></label>
|
||||
<p class="note">설정한 월 예산 기준으로 이번 달 지출과 비교됩니다.</p>
|
||||
@@ -300,11 +332,11 @@ onMounted(() => {
|
||||
<p v-if="formError" class="msg error">{{ formError }}</p>
|
||||
|
||||
<div class="buttons">
|
||||
<IconBtn icon="close" title="취소" @click="formOpen = false" />
|
||||
<IconBtn icon="close" title="취소" @click="closeForm()" />
|
||||
<IconBtn icon="save" :title="editId ? '수정' : '등록'" variant="primary" type="submit" :disabled="submitting" />
|
||||
</div>
|
||||
</form>
|
||||
</AppModal>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -313,6 +345,52 @@ onMounted(() => {
|
||||
max-width: 640px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.budget-page .budget-form {
|
||||
max-width: 460px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.ep-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1.2rem;
|
||||
}
|
||||
.ep-title {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
.sheet-trigger {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
width: 100%;
|
||||
padding: 0.55rem 0.75rem;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 6px;
|
||||
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;
|
||||
}
|
||||
.head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
Reference in New Issue
Block a user