- 사이드바·헤더·홈·업그레이드·데모·정기결제/내역 화면 라벨 통일 - 백업 엑셀 시트명(backup.js)은 기존 백업 복원 호환 위해 '고정지출' 유지 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -19,7 +19,7 @@ const TITLES = {
|
||||
'demo-locked': '둘러보기',
|
||||
'account-entries': '가계부 내역',
|
||||
'account-stats': '통계',
|
||||
'account-recurrings': '고정 지출',
|
||||
'account-recurrings': '정기결제',
|
||||
'account-wallets': '계좌 관리',
|
||||
'account-categories': '분류 관리',
|
||||
'account-budget': '예산 설정',
|
||||
|
||||
@@ -52,7 +52,7 @@ const icons = {
|
||||
</RouterLink>
|
||||
<RouterLink to="/account/recurrings" class="menu-item">
|
||||
<svg class="menu-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" v-html="icons.recurrings" />
|
||||
<span>고정 지출</span>
|
||||
<span>정기결제</span>
|
||||
<!-- 무료도 2건까지 사용 가능 → 🔒 미표시 -->
|
||||
</RouterLink>
|
||||
<RouterLink to="/account/wallets" class="menu-item">
|
||||
|
||||
+1
-1
@@ -161,7 +161,7 @@ const router = createRouter({
|
||||
path: '/account/recurrings',
|
||||
name: 'account-recurrings',
|
||||
component: () => import('../views/account/RecurringView.vue'),
|
||||
// 고정지출은 무료도 2건까지 체험(초과 시 업셀) — 유료 게이트 해제
|
||||
// 정기결제은 무료도 2건까지 체험(초과 시 업셀) — 유료 게이트 해제
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
// 설정 (앱 하단 내비게이션 → 설정)
|
||||
|
||||
@@ -26,7 +26,7 @@ function goLogin() {
|
||||
<div class="locked">
|
||||
<div class="lock-big">🔒</div>
|
||||
<h2>{{ label }}는 로그인 후 이용할 수 있어요</h2>
|
||||
<p class="desc">둘러보기에서는 가계부 내역 · 고정 지출 · 계좌 관리 · 분류 관리만 미리 볼 수 있습니다.<br />로그인하면 통계 · 예산 · 태그 · 게시판까지 모두 사용할 수 있어요.</p>
|
||||
<p class="desc">둘러보기에서는 가계부 내역 · 정기결제 · 계좌 관리 · 분류 관리만 미리 볼 수 있습니다.<br />로그인하면 통계 · 예산 · 태그 · 게시판까지 모두 사용할 수 있어요.</p>
|
||||
<button type="button" class="cta-btn" @click="goLogin">로그인 / 시작하기</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -133,7 +133,7 @@ const shortcuts = [
|
||||
{ to: '/account/stats', icon: '📊', label: '통계', desc: '차트로 보기' },
|
||||
{ to: '/account/wallets', icon: '🏦', label: '계좌 관리', desc: '자산·부채' },
|
||||
{ to: '/account/budget', icon: '🎯', label: '예산 설정', desc: '예산 대비 지출' },
|
||||
{ to: '/account/recurrings', icon: '🔁', label: '고정 지출', desc: '매월·매년 반복' },
|
||||
{ to: '/account/recurrings', icon: '🔁', label: '정기결제', desc: '매월·매년 반복' },
|
||||
{ to: '/account/categories', icon: '🗂️', label: '분류 관리', desc: '카테고리' },
|
||||
]
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ async function buy(product) {
|
||||
const freeFeatures = [
|
||||
'가계부 내역 기록 · 조회',
|
||||
'계좌 관리 (종류별 2개)',
|
||||
'고정 지출 (2개까지 자동 반영)',
|
||||
'정기결제 (2개까지 자동 반영)',
|
||||
'자주 쓰는 내역',
|
||||
'월별 수입/지출 요약',
|
||||
'기본 분류',
|
||||
@@ -68,7 +68,7 @@ const freeFeatures = [
|
||||
const premiumFeatures = [
|
||||
'전체 통계 (분류별·추이·자산)',
|
||||
'예산 설정',
|
||||
'계좌 · 고정 지출 무제한',
|
||||
'계좌 · 정기결제 무제한',
|
||||
'영수증 OCR 인식',
|
||||
'카드 알림 자동 인식',
|
||||
'투자 · 자산 관리',
|
||||
|
||||
@@ -118,7 +118,7 @@ onBeforeUnmount(() => sortable?.destroy())
|
||||
<p v-if="reorderMode" class="hint sm">⠿ 손잡이를 끌어 순서를 바꾸세요. 끝나면 <b>완료</b>.</p>
|
||||
|
||||
<form class="new-tag" @submit.prevent="addTag">
|
||||
<input v-model="newName" type="text" placeholder="새 태그 이름 (예: 식비, 고정지출)" />
|
||||
<input v-model="newName" type="text" placeholder="새 태그 이름 (예: 식비, 정기결제)" />
|
||||
<IconBtn icon="plus" title="추가" variant="primary" type="submit" />
|
||||
</form>
|
||||
|
||||
|
||||
@@ -712,7 +712,7 @@ async function remove(e) {
|
||||
}
|
||||
}
|
||||
|
||||
// 고정지출 등록 확인 모달 상태 (네이티브 confirm 대신 — PC 팝업 제목 'sb_pt' 노출 방지)
|
||||
// 정기결제 등록 확인 모달 상태 (네이티브 confirm 대신 — PC 팝업 제목 'sb_pt' 노출 방지)
|
||||
const recurConfirm = reactive({ open: false, title: '', dom: 1, amount: 0, payload: null })
|
||||
const flashMsg = ref('')
|
||||
let flashTimer = null
|
||||
@@ -722,9 +722,9 @@ function flash(msg) {
|
||||
flashTimer = setTimeout(() => (flashMsg.value = ''), 2800)
|
||||
}
|
||||
|
||||
// 현재 수정 중인 내역을 '매월' 고정지출로 등록 — 확인 모달을 띄운다
|
||||
// 현재 수정 중인 내역을 '매월' 정기결제로 등록 — 확인 모달을 띄운다
|
||||
function registerAsRecurring() {
|
||||
if (form.type === 'REPAYMENT') return // 고정지출은 수입/지출/이체만
|
||||
if (form.type === 'REPAYMENT') return // 정기결제은 수입/지출/이체만
|
||||
const amount = Number(form.amount)
|
||||
if (!amount || amount <= 0) {
|
||||
formError.value = '금액을 올바르게 입력하세요.'
|
||||
@@ -733,7 +733,7 @@ function registerAsRecurring() {
|
||||
const title =
|
||||
(form.memo || '').trim() ||
|
||||
(form.category || '').trim() ||
|
||||
(form.type === 'INCOME' ? '정기 수입' : form.type === 'TRANSFER' ? '정기 이체' : '고정 지출')
|
||||
(form.type === 'INCOME' ? '정기 수입' : form.type === 'TRANSFER' ? '정기 이체' : '정기결제')
|
||||
const base = form.entryDate || todayStr()
|
||||
const [y, m, d] = base.split('-').map(Number)
|
||||
const dom = d
|
||||
@@ -766,9 +766,9 @@ async function doRegisterRecurring() {
|
||||
await accountApi.createRecurring(recurConfirm.payload)
|
||||
recurConfirm.open = false
|
||||
formOpen.value = false
|
||||
flash('고정지출로 등록했습니다. 고정지출 화면에서 주기를 변경할 수 있어요.')
|
||||
flash('정기결제로 등록했습니다. 정기결제 화면에서 주기를 변경할 수 있어요.')
|
||||
} catch (e) {
|
||||
formError.value = e.response?.data?.message || '고정지출 등록에 실패했습니다.'
|
||||
formError.value = e.response?.data?.message || '정기결제 등록에 실패했습니다.'
|
||||
recurConfirm.open = false
|
||||
} finally {
|
||||
submitting.value = false
|
||||
@@ -941,7 +941,7 @@ async function analyzePasted() {
|
||||
|
||||
onMounted(async () => {
|
||||
// 진입 시 밀린 정기 거래를 먼저 반영한 뒤 목록을 불러온다 (중복 없이)
|
||||
// 고정지출은 무료도 2건까지 사용 가능 → 모든 로그인 사용자에 대해 실행
|
||||
// 정기결제은 무료도 2건까지 사용 가능 → 모든 로그인 사용자에 대해 실행
|
||||
try {
|
||||
await accountApi.runRecurrings()
|
||||
} catch {
|
||||
@@ -1302,7 +1302,7 @@ onMounted(async () => {
|
||||
class="to-recurring"
|
||||
:disabled="submitting"
|
||||
@click="registerAsRecurring"
|
||||
>🔁 이 내역을 고정지출로 등록</button>
|
||||
>🔁 이 내역을 정기결제로 등록</button>
|
||||
|
||||
<button
|
||||
v-if="editId && (form.type === 'INCOME' || form.type === 'EXPENSE')"
|
||||
@@ -1346,17 +1346,17 @@ onMounted(async () => {
|
||||
</Transition>
|
||||
</Teleport>
|
||||
|
||||
<!-- 고정지출 등록 확인 (네이티브 confirm 대체 — 제목 'sb_pt' 노출 방지) -->
|
||||
<!-- 정기결제 등록 확인 (네이티브 confirm 대체 — 제목 'sb_pt' 노출 방지) -->
|
||||
<Teleport to="body">
|
||||
<Transition name="fade">
|
||||
<div v-if="recurConfirm.open" class="modal-backdrop" @click.self="recurConfirm.open = false">
|
||||
<div class="modal confirm-modal" role="dialog" aria-modal="true">
|
||||
<h2>고정지출 등록</h2>
|
||||
<h2>정기결제 등록</h2>
|
||||
<p class="confirm-body">
|
||||
<b>{{ recurConfirm.title }}</b><br />
|
||||
매월 <b>{{ recurConfirm.dom }}일</b> · <b>{{ won(recurConfirm.amount) }}</b> 으로 등록할까요?
|
||||
</p>
|
||||
<p class="confirm-sub">주기·시작일은 고정지출 화면에서 변경할 수 있어요.</p>
|
||||
<p class="confirm-sub">주기·시작일은 정기결제 화면에서 변경할 수 있어요.</p>
|
||||
<div class="buttons">
|
||||
<IconBtn icon="close" title="취소" @click="recurConfirm.open = false" />
|
||||
<IconBtn icon="save" title="등록" variant="primary" :disabled="submitting" @click="doRegisterRecurring" />
|
||||
|
||||
@@ -16,7 +16,7 @@ const router = useRouter()
|
||||
const recurrings = ref([])
|
||||
const wallets = ref([])
|
||||
|
||||
// 무료 회원 고정지출 등록 한도(써보고 → 업셀). 유료는 무제한. 백엔드에서도 강제.
|
||||
// 무료 회원 정기결제 등록 한도(써보고 → 업셀). 유료는 무제한. 백엔드에서도 강제.
|
||||
const FREE_RECURRING_LIMIT = 2
|
||||
const atRecurringLimit = computed(
|
||||
() => !auth.isPremium && recurrings.value.length >= FREE_RECURRING_LIMIT,
|
||||
@@ -158,7 +158,7 @@ async function load() {
|
||||
async function runNow() {
|
||||
try {
|
||||
const res = await accountApi.runRecurrings()
|
||||
alert(`${res.generated}건의 고정 지출가 반영되었습니다.`)
|
||||
alert(`${res.generated}건의 정기결제가 반영되었습니다.`)
|
||||
await load()
|
||||
} catch (e) {
|
||||
alert(e.response?.data?.message || '반영에 실패했습니다.')
|
||||
@@ -168,7 +168,7 @@ async function runNow() {
|
||||
async function openCreate() {
|
||||
if (atRecurringLimit.value) {
|
||||
const go = await dialog.confirm(
|
||||
`무료 회원은 고정 지출을 ${FREE_RECURRING_LIMIT}개까지 등록할 수 있어요.\n유료로 업그레이드하면 무제한이에요.`,
|
||||
`무료 회원은 정기결제를 ${FREE_RECURRING_LIMIT}개까지 등록할 수 있어요.\n유료로 업그레이드하면 무제한이에요.`,
|
||||
{ title: '등록 한도', okText: '업그레이드', cancelText: '닫기' },
|
||||
)
|
||||
if (go) router.push('/upgrade')
|
||||
@@ -237,7 +237,7 @@ async function submit() {
|
||||
}
|
||||
|
||||
async function remove(r) {
|
||||
if (!(await dialog.confirm(`'${r.title}' 고정 지출를 삭제할까요?\n이미 생성된 내역은 유지됩니다.`, { title: '고정지출 삭제', danger: true }))) return
|
||||
if (!(await dialog.confirm(`'${r.title}' 정기결제를 삭제할까요?\n이미 생성된 내역은 유지됩니다.`, { title: '정기결제 삭제', danger: true }))) return
|
||||
try {
|
||||
await accountApi.removeRecurring(r.id)
|
||||
await load()
|
||||
@@ -254,7 +254,7 @@ onMounted(load)
|
||||
<p class="hint">등록한 주기에 맞춰 가계부 진입 시 자동으로 <b>'확인 필요'</b> 내역이 생성됩니다(중복 없이). 가계부에서 실제 결제·이체를 확인한 뒤 <b>'확인'</b>을 눌러 확정하세요.</p>
|
||||
<div class="recur-actions">
|
||||
<IconBtn icon="refresh" title="지금 반영" @click="runNow" />
|
||||
<IconBtn icon="plus" title="고정 지출 추가" variant="primary" @click="openCreate" />
|
||||
<IconBtn icon="plus" title="정기결제 추가" variant="primary" @click="openCreate" />
|
||||
</div>
|
||||
<p v-if="atRecurringLimit" class="msg recur-limit">
|
||||
무료 {{ FREE_RECURRING_LIMIT }}개까지 등록 완료 ·
|
||||
@@ -295,10 +295,10 @@ onMounted(load)
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<p v-else-if="!loading" class="msg">등록된 고정 지출가 없습니다.</p>
|
||||
<p v-else-if="!loading" class="msg">등록된 정기결제가 없습니다.</p>
|
||||
|
||||
<!-- 추가/수정 모달 -->
|
||||
<AppModal v-model="formOpen" :title="`고정 지출 ${editId ? '수정' : '추가'}`">
|
||||
<AppModal v-model="formOpen" :title="`정기결제 ${editId ? '수정' : '추가'}`">
|
||||
<form class="recur-form" @submit.prevent="submit">
|
||||
<label>이름<input v-model="form.title" type="text" placeholder="예: 월세, 급여" :disabled="submitting" /></label>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user