Adjust UI density and spacing
This commit is contained in:
@@ -11,6 +11,7 @@ body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
font-feature-settings: "tnum";
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
input,
|
||||
@@ -22,19 +23,19 @@ button {
|
||||
|
||||
@layer components {
|
||||
.field {
|
||||
@apply w-full rounded-md border border-line bg-white px-3 py-3 text-base outline-none transition focus:border-brand-600 focus:ring-2 focus:ring-brand-100;
|
||||
@apply w-full rounded-md border border-line bg-white px-4 py-2.5 text-sm outline-none transition focus:border-brand-600 focus:ring-2 focus:ring-brand-100;
|
||||
}
|
||||
|
||||
.label {
|
||||
@apply mb-1 block text-sm font-medium text-muted;
|
||||
@apply mb-1 block text-xs font-medium text-muted;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
@apply inline-flex min-h-12 items-center justify-center rounded-md bg-brand-700 px-4 py-3 text-base font-semibold text-white shadow-soft transition active:scale-[0.99];
|
||||
@apply inline-flex min-h-11 items-center justify-center rounded-md bg-brand-700 px-4 py-2.5 text-sm font-semibold text-white shadow-soft transition active:scale-[0.99];
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
@apply inline-flex min-h-11 items-center justify-center rounded-md border border-line bg-white px-3 py-2 text-sm font-semibold text-ink transition active:scale-[0.99];
|
||||
@apply inline-flex min-h-10 items-center justify-center rounded-md border border-line bg-white px-3 py-2 text-xs font-semibold text-ink transition active:scale-[0.99];
|
||||
}
|
||||
|
||||
.segmented {
|
||||
@@ -42,7 +43,7 @@ button {
|
||||
}
|
||||
|
||||
.segmented-button {
|
||||
@apply rounded px-3 py-2 text-sm font-semibold text-muted transition;
|
||||
@apply rounded px-3 py-2 text-xs font-semibold text-muted transition;
|
||||
}
|
||||
|
||||
.segmented-button-active {
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
@click="open = true"
|
||||
>
|
||||
<span :class="selected ? 'text-ink' : 'text-muted'">{{ selected?.label || placeholder }}</span>
|
||||
<span class="text-xl leading-none text-muted">⌄</span>
|
||||
</button>
|
||||
|
||||
<Teleport to="body">
|
||||
@@ -40,7 +39,7 @@
|
||||
<div class="sticky top-0 border-b border-line bg-white px-4 pb-3 pt-2">
|
||||
<div class="mx-auto mb-3 h-1 w-10 rounded-full bg-line" />
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-lg font-bold">{{ title }}</h2>
|
||||
<h2 class="text-base font-bold">{{ title }}</h2>
|
||||
<button type="button" class="rounded-md px-2 py-1 text-sm font-semibold text-muted" @click="open = false">닫기</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -50,12 +49,12 @@
|
||||
v-for="option in options"
|
||||
:key="option.value"
|
||||
type="button"
|
||||
class="flex w-full items-center justify-between rounded-md px-3 py-4 text-left active:bg-paper"
|
||||
class="flex w-full items-center justify-between rounded-md px-3 py-3 text-left active:bg-paper"
|
||||
@click="select(option.value)"
|
||||
>
|
||||
<span>
|
||||
<span class="block font-semibold text-ink">{{ option.label }}</span>
|
||||
<span v-if="option.description" class="mt-1 block text-sm text-muted">{{ option.description }}</span>
|
||||
<span class="block text-sm font-semibold text-ink">{{ option.label }}</span>
|
||||
<span v-if="option.description" class="mt-1 block text-xs text-muted">{{ option.description }}</span>
|
||||
</span>
|
||||
<span
|
||||
class="ml-3 flex h-6 w-6 shrink-0 items-center justify-center rounded-full border text-sm"
|
||||
@@ -99,12 +98,4 @@ function select(value: string) {
|
||||
open.value = false
|
||||
}
|
||||
|
||||
watch(open, (value) => {
|
||||
if (import.meta.server) return
|
||||
document.body.style.overflow = value ? 'hidden' : ''
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (import.meta.client) document.body.style.overflow = ''
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -15,3 +15,7 @@
|
||||
- 카테고리 관리를 수입/지출별 상위 카테고리 목록과 하위 카테고리 상세 목록 구조로 변경했다.
|
||||
- 카테고리 추가는 상시 노출 입력 폼 대신 헤더의 `+` 버튼과 바텀 시트 입력으로 변경했다.
|
||||
- 카테고리 관리 화면에서 활성/비활성 토글 표시를 제거했다.
|
||||
- 전역 UI 밀도를 낮추고 입력 필드 좌우 여백을 정리했다.
|
||||
- 바텀 시트 선택 버튼에서 하단 화살표를 제거했다.
|
||||
- 로그인 화면이 하단 탭 여백 때문에 스크롤되는 문제를 수정했다.
|
||||
- 입력 중 스크롤이 튀는 현상을 줄이기 위해 바텀 시트 body overflow 변경과 추가 화면 sticky 저장 버튼을 제거했다.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="min-h-screen bg-paper text-ink">
|
||||
<div class="min-h-dvh bg-paper text-ink">
|
||||
<aside
|
||||
v-if="showNav"
|
||||
class="fixed inset-y-0 left-0 z-20 hidden w-64 border-r border-line bg-white md:block"
|
||||
@@ -22,8 +22,8 @@
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<main class="min-h-screen w-full bg-white pb-24 md:ml-64 md:bg-paper md:pb-0">
|
||||
<div class="mx-auto min-h-screen w-full md:max-w-7xl md:bg-transparent">
|
||||
<main class="min-h-dvh w-full bg-white md:ml-64 md:bg-paper" :class="showNav ? 'pb-20 md:pb-0' : ''">
|
||||
<div class="mx-auto min-h-dvh w-full md:max-w-7xl md:bg-transparent">
|
||||
<slot />
|
||||
</div>
|
||||
</main>
|
||||
@@ -36,10 +36,10 @@
|
||||
v-for="item in items"
|
||||
:key="item.to"
|
||||
:to="item.to"
|
||||
class="flex min-h-16 flex-col items-center justify-center gap-1 text-xs font-medium text-muted"
|
||||
class="flex min-h-14 flex-col items-center justify-center gap-0.5 text-[11px] font-medium text-muted"
|
||||
active-class="text-brand-700"
|
||||
>
|
||||
<span class="text-xl leading-none">{{ item.icon }}</span>
|
||||
<span class="text-lg leading-none">{{ item.icon }}</span>
|
||||
<span>{{ item.label }}</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
@@ -47,7 +47,7 @@
|
||||
<NuxtLink
|
||||
v-if="showFab"
|
||||
to="/add"
|
||||
class="fixed bottom-24 right-4 z-30 flex h-16 w-16 items-center justify-center rounded-full bg-red-500 text-4xl leading-none text-white shadow-soft active:scale-95 md:bottom-10 md:right-10"
|
||||
class="fixed bottom-20 right-4 z-30 flex h-14 w-14 items-center justify-center rounded-full bg-red-500 text-3xl leading-none text-white shadow-soft active:scale-95 md:bottom-10 md:right-10"
|
||||
aria-label="거래 추가"
|
||||
>
|
||||
+
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<section class="px-4 py-4">
|
||||
<h1 class="mb-4 text-xl font-bold">거래 추가</h1>
|
||||
<h1 class="mb-4 text-lg font-bold">거래 추가</h1>
|
||||
|
||||
<form class="space-y-4" @submit.prevent="submit">
|
||||
<form class="space-y-3" @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>
|
||||
@@ -15,7 +15,7 @@
|
||||
</div>
|
||||
<div>
|
||||
<span class="label">반복</span>
|
||||
<button type="button" class="btn-secondary h-[50px]" disabled>없음</button>
|
||||
<button type="button" class="btn-secondary h-[42px]" disabled>없음</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
|
||||
<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>
|
||||
<input id="amount" v-model.number="form.amount" class="field text-right text-lg font-bold" inputmode="numeric" min="1" type="number" required>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@@ -57,17 +57,17 @@
|
||||
<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">
|
||||
<p class="rounded-md border border-line bg-paper px-4 py-2.5 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>
|
||||
<textarea id="memo" v-model="form.memo" class="field min-h-24 resize-none" placeholder="자유롭게 기록"></textarea>
|
||||
</div>
|
||||
|
||||
<button class="btn-primary sticky bottom-20 w-full" type="submit" :disabled="pending">
|
||||
<button class="btn-primary mt-5 w-full" type="submit" :disabled="pending">
|
||||
{{ pending ? '저장 중...' : '저장하기' }}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
<template>
|
||||
<section class="min-h-screen bg-paper">
|
||||
<section class="min-h-dvh bg-paper">
|
||||
<header class="sticky top-0 z-10 border-b border-line bg-white">
|
||||
<div class="flex h-16 items-center justify-between px-4">
|
||||
<div class="flex h-14 items-center justify-between px-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
v-if="selectedAsset"
|
||||
type="button"
|
||||
class="flex h-10 w-10 items-center justify-center rounded-md text-3xl leading-none active:bg-paper"
|
||||
class="flex h-9 w-9 items-center justify-center rounded-md text-2xl leading-none active:bg-paper"
|
||||
aria-label="자산 목록으로"
|
||||
@click="selectedAssetId = ''"
|
||||
>
|
||||
‹
|
||||
</button>
|
||||
<h1 class="text-xl font-bold">{{ selectedAsset?.name || '자산' }}</h1>
|
||||
<h1 class="text-lg font-bold">{{ selectedAsset?.name || '자산' }}</h1>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<button class="flex h-10 w-9 items-center justify-center rounded-md text-3xl leading-none active:bg-paper" type="button" @click="moveMonth(-1)">‹</button>
|
||||
<button class="rounded-md px-2 py-2 text-lg font-semibold active:bg-paper" type="button" @click="monthPickerOpen = true">
|
||||
<button class="flex h-9 w-8 items-center justify-center rounded-md text-2xl leading-none active:bg-paper" type="button" @click="moveMonth(-1)">‹</button>
|
||||
<button class="rounded-md px-2 py-2 text-base font-semibold active:bg-paper" type="button" @click="monthPickerOpen = true">
|
||||
{{ displayMonth }}
|
||||
</button>
|
||||
<button class="flex h-10 w-9 items-center justify-center rounded-md text-3xl leading-none active:bg-paper" type="button" @click="moveMonth(1)">›</button>
|
||||
<button class="flex h-9 w-8 items-center justify-center rounded-md text-2xl leading-none active:bg-paper" type="button" @click="moveMonth(1)">›</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!selectedAsset" class="grid grid-cols-3 border-t border-line px-4 py-4 text-center">
|
||||
<div v-if="!selectedAsset" class="grid grid-cols-3 border-t border-line px-4 py-3 text-center">
|
||||
<div>
|
||||
<p class="text-sm font-semibold text-muted">누적형</p>
|
||||
<p class="text-xs font-semibold text-muted">누적형</p>
|
||||
<p class="mt-1 font-bold text-brand-700">{{ formatWon(cumulativeTotal) }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm font-semibold text-muted">월별</p>
|
||||
<p class="text-xs font-semibold text-muted">월별</p>
|
||||
<p class="mt-1 font-bold text-red-600">{{ formatWon(monthlyTotal) }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm font-semibold text-muted">합계</p>
|
||||
<p class="text-xs font-semibold text-muted">합계</p>
|
||||
<p class="mt-1 font-bold">{{ formatWon(totalBalance) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -44,22 +44,22 @@
|
||||
v-for="asset in assets"
|
||||
:key="asset.id"
|
||||
type="button"
|
||||
class="flex w-full items-center justify-between px-4 py-5 text-left active:bg-paper"
|
||||
class="flex w-full items-center justify-between px-4 py-4 text-left active:bg-paper"
|
||||
@click="selectedAssetId = asset.id"
|
||||
>
|
||||
<span>
|
||||
<span class="block text-lg font-semibold text-ink">{{ asset.name }}</span>
|
||||
<span class="mt-1 block text-sm text-muted">{{ asset.balanceType === 'cumulative' ? '계속 누적' : `${displayMonth}만 계산` }}</span>
|
||||
<span class="block text-base font-semibold text-ink">{{ asset.name }}</span>
|
||||
<span class="mt-1 block text-xs text-muted">{{ asset.balanceType === 'cumulative' ? '계속 누적' : `${displayMonth}만 계산` }}</span>
|
||||
</span>
|
||||
<span class="text-xl font-bold">{{ formatWon(asset.balance || 0) }}</span>
|
||||
<span class="text-base font-bold">{{ formatWon(asset.balance || 0) }}</span>
|
||||
</button>
|
||||
<p v-if="!assets.length" class="px-4 py-12 text-center text-muted">등록된 자산이 없습니다.</p>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<section class="border-b border-line bg-white px-4 py-5">
|
||||
<section class="border-b border-line bg-white px-4 py-4">
|
||||
<p class="text-sm font-semibold text-muted">현재 표시 잔액</p>
|
||||
<p class="mt-1 text-3xl font-bold">{{ formatWon(selectedAsset.balance || 0) }}</p>
|
||||
<p class="mt-1 text-2xl font-bold">{{ formatWon(selectedAsset.balance || 0) }}</p>
|
||||
<div class="mt-5 grid grid-cols-3 text-center">
|
||||
<div>
|
||||
<p class="text-sm text-muted">입금</p>
|
||||
@@ -80,8 +80,8 @@
|
||||
<section v-for="group in groupedTransactions" :key="group.date" class="py-2">
|
||||
<div class="flex items-center justify-between px-4 py-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<strong class="text-3xl">{{ group.day }}</strong>
|
||||
<span class="rounded bg-slate-200 px-2 py-1 text-sm font-semibold text-muted">{{ group.weekday }}</span>
|
||||
<strong class="text-2xl">{{ group.day }}</strong>
|
||||
<span class="rounded bg-slate-200 px-2 py-1 text-xs font-semibold text-muted">{{ group.weekday }}</span>
|
||||
<span class="text-sm text-muted">{{ group.monthLabel }}</span>
|
||||
</div>
|
||||
<div class="flex gap-4 text-sm font-bold">
|
||||
@@ -129,16 +129,16 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between px-4 py-4">
|
||||
<button class="text-3xl" type="button" @click="pickerYear--">‹</button>
|
||||
<strong class="text-2xl">{{ pickerYear }}</strong>
|
||||
<button class="text-3xl" type="button" @click="pickerYear++">›</button>
|
||||
<button class="text-2xl" type="button" @click="pickerYear--">‹</button>
|
||||
<strong class="text-xl">{{ pickerYear }}</strong>
|
||||
<button class="text-2xl" type="button" @click="pickerYear++">›</button>
|
||||
</div>
|
||||
<div class="grid grid-cols-4 gap-y-2 px-4 pb-6">
|
||||
<button
|
||||
v-for="month in 12"
|
||||
:key="month"
|
||||
type="button"
|
||||
class="rounded-md py-4 text-lg font-bold"
|
||||
class="rounded-md py-3 text-base font-bold"
|
||||
:class="isSelectedPickerMonth(month) ? 'bg-red-50 text-red-500' : 'active:bg-paper'"
|
||||
@click="selectPickerMonth(month)"
|
||||
>
|
||||
@@ -206,15 +206,6 @@ watch(monthPickerOpen, (open) => {
|
||||
if (open) pickerYear.value = Number(selectedMonth.value.slice(0, 4))
|
||||
})
|
||||
|
||||
watch(monthPickerOpen, (open) => {
|
||||
if (import.meta.server) return
|
||||
document.body.style.overflow = open ? 'hidden' : ''
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (import.meta.client) document.body.style.overflow = ''
|
||||
})
|
||||
|
||||
function moveMonth(amount: number) {
|
||||
const date = parse(selectedMonth.value, 'yyyy-MM', new Date())
|
||||
selectedMonth.value = format(addMonths(date, amount), 'yyyy-MM')
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<section class="flex min-h-screen flex-col justify-center px-5 py-10">
|
||||
<div class="mb-8">
|
||||
<section class="flex min-h-dvh flex-col justify-center px-5 py-6 md:mx-auto md:max-w-sm">
|
||||
<div class="mb-6">
|
||||
<p class="text-sm font-semibold text-brand-700">Money</p>
|
||||
<h1 class="mt-2 text-3xl font-bold tracking-normal text-ink">빠르게 기록하는 개인 가계부</h1>
|
||||
<p class="mt-3 text-sm leading-6 text-muted">홈 화면에 설치해서 앱처럼 쓰고, 로그인 상태를 유지할 수 있게 구성했습니다.</p>
|
||||
<h1 class="mt-2 text-2xl font-bold tracking-normal text-ink">빠르게 기록하는 개인 가계부</h1>
|
||||
<p class="mt-2 text-sm leading-6 text-muted">홈 화면에 설치해서 앱처럼 쓰고, 로그인 상태를 유지할 수 있게 구성했습니다.</p>
|
||||
</div>
|
||||
|
||||
<form class="space-y-4" @submit.prevent="submit">
|
||||
<form class="space-y-3" @submit.prevent="submit">
|
||||
<div>
|
||||
<label class="label" for="email">이메일</label>
|
||||
<input id="email" v-model="email" class="field" type="email" autocomplete="email" required>
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
<template>
|
||||
<section class="min-h-screen bg-paper">
|
||||
<header class="sticky top-0 z-10 flex h-16 items-center justify-between border-b border-line bg-white px-4">
|
||||
<section class="min-h-dvh bg-paper">
|
||||
<header class="sticky top-0 z-10 flex h-14 items-center justify-between border-b border-line bg-white px-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<button
|
||||
v-if="view !== 'home'"
|
||||
type="button"
|
||||
class="flex h-10 w-10 items-center justify-center rounded-md text-3xl leading-none active:bg-paper"
|
||||
class="flex h-9 w-9 items-center justify-center rounded-md text-2xl leading-none active:bg-paper"
|
||||
aria-label="뒤로"
|
||||
@click="goBack"
|
||||
>
|
||||
‹
|
||||
</button>
|
||||
<h1 class="text-xl font-bold">{{ title }}</h1>
|
||||
<h1 class="text-lg font-bold">{{ title }}</h1>
|
||||
</div>
|
||||
|
||||
<button
|
||||
v-if="showAddButton"
|
||||
type="button"
|
||||
class="flex h-10 w-10 items-center justify-center rounded-md text-3xl leading-none text-brand-700 active:bg-paper"
|
||||
class="flex h-9 w-9 items-center justify-center rounded-md text-2xl leading-none text-brand-700 active:bg-paper"
|
||||
aria-label="추가"
|
||||
@click="openAddSheet"
|
||||
>
|
||||
@@ -26,40 +26,40 @@
|
||||
</header>
|
||||
|
||||
<div v-if="view === 'home'">
|
||||
<div class="border-b border-line bg-paper px-4 py-4 text-sm font-semibold text-muted">관리</div>
|
||||
<div class="border-b border-line bg-paper px-4 py-3 text-xs font-semibold text-muted">관리</div>
|
||||
<div class="divide-y divide-line bg-white">
|
||||
<button
|
||||
v-for="item in menuItems"
|
||||
:key="item.id"
|
||||
type="button"
|
||||
class="flex w-full items-center justify-between px-4 py-5 text-left active:bg-paper"
|
||||
class="flex w-full items-center justify-between px-4 py-4 text-left active:bg-paper"
|
||||
@click="open(item.id)"
|
||||
>
|
||||
<span>
|
||||
<span class="block text-lg font-semibold text-ink">{{ item.label }}</span>
|
||||
<span class="mt-1 block text-sm text-muted">{{ item.description }}</span>
|
||||
<span class="block text-base font-semibold text-ink">{{ item.label }}</span>
|
||||
<span class="mt-1 block text-xs text-muted">{{ item.description }}</span>
|
||||
</span>
|
||||
<span class="text-2xl text-muted">›</span>
|
||||
<span class="text-xl text-muted">›</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 border-y border-line bg-white">
|
||||
<a
|
||||
class="flex items-center justify-between px-4 py-5 active:bg-paper"
|
||||
class="flex items-center justify-between px-4 py-4 active:bg-paper"
|
||||
href="/api/export/transactions.csv"
|
||||
>
|
||||
<span>
|
||||
<span class="block text-lg font-semibold text-ink">CSV 내보내기</span>
|
||||
<span class="mt-1 block text-sm text-muted">전체 거래 데이터를 파일로 저장합니다.</span>
|
||||
<span class="block text-base font-semibold text-ink">CSV 내보내기</span>
|
||||
<span class="mt-1 block text-xs text-muted">전체 거래 데이터를 파일로 저장합니다.</span>
|
||||
</span>
|
||||
<span class="text-2xl text-muted">↓</span>
|
||||
<span class="text-xl text-muted">↓</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="view === 'assets'" class="px-4 py-4">
|
||||
<form class="space-y-3 border-b border-line pb-5" @submit.prevent="createAsset">
|
||||
<input v-model="assetForm.name" class="field border-x-0 border-t-0 px-0" placeholder="자산 이름" required>
|
||||
<input v-model="assetForm.name" class="field" placeholder="자산 이름" required>
|
||||
<div>
|
||||
<span class="label">잔액 계산 방식</span>
|
||||
<div class="space-y-2">
|
||||
@@ -79,12 +79,12 @@
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<input v-model.number="assetForm.initialBalance" class="field border-x-0 border-t-0 px-0 text-right" inputmode="numeric" type="number" placeholder="최초 잔액">
|
||||
<input v-model.number="assetForm.initialBalance" class="field text-right" inputmode="numeric" type="number" placeholder="최초 잔액">
|
||||
<button class="btn-primary w-full" type="submit">자산 추가</button>
|
||||
</form>
|
||||
|
||||
<div class="mt-4 divide-y divide-line border-y border-line bg-white">
|
||||
<div v-for="asset in assets" :key="asset.id" class="flex items-center justify-between gap-3 py-4">
|
||||
<div v-for="asset in assets" :key="asset.id" class="flex items-center justify-between gap-3 py-3">
|
||||
<div>
|
||||
<p class="font-semibold">{{ asset.name }}</p>
|
||||
<p class="mt-1 text-xs text-muted">
|
||||
@@ -123,14 +123,14 @@
|
||||
v-for="category in visibleCategories"
|
||||
:key="category.id"
|
||||
type="button"
|
||||
class="flex w-full items-center justify-between px-4 py-5 text-left active:bg-paper"
|
||||
class="flex w-full items-center justify-between px-4 py-4 text-left active:bg-paper"
|
||||
@click="selectCategory(category)"
|
||||
>
|
||||
<span>
|
||||
<span class="block text-lg font-semibold text-ink">{{ category.name }}</span>
|
||||
<span class="mt-1 block text-sm text-muted">{{ categoryDescription(category) }}</span>
|
||||
<span class="block text-base font-semibold text-ink">{{ category.name }}</span>
|
||||
<span class="mt-1 block text-xs text-muted">{{ categoryDescription(category) }}</span>
|
||||
</span>
|
||||
<span v-if="!selectedParentCategory" class="text-2xl text-muted">›</span>
|
||||
<span v-if="!selectedParentCategory" class="text-xl text-muted">›</span>
|
||||
</button>
|
||||
|
||||
<p v-if="!visibleCategories.length" class="px-4 py-12 text-center text-muted">
|
||||
@@ -153,10 +153,9 @@
|
||||
</div>
|
||||
<input
|
||||
v-model="categoryName"
|
||||
class="field border-x-0 border-t-0 px-0"
|
||||
class="field"
|
||||
:placeholder="selectedParentCategory ? '하위 카테고리 이름' : '상위 카테고리 이름'"
|
||||
required
|
||||
autofocus
|
||||
>
|
||||
<button class="btn-primary mt-5 w-full" type="submit">추가하기</button>
|
||||
</form>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<template>
|
||||
<section class="min-h-screen bg-paper">
|
||||
<section class="min-h-dvh bg-paper">
|
||||
<header class="sticky top-0 z-10 border-b border-line bg-white">
|
||||
<div class="flex h-16 items-center justify-between px-4">
|
||||
<div class="flex h-14 items-center justify-between px-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<button class="flex h-10 w-10 items-center justify-center rounded-md text-3xl leading-none active:bg-paper" type="button" @click="moveMonth(-1)">‹</button>
|
||||
<button class="rounded-md px-1 py-2 text-left text-2xl font-semibold active:bg-paper" type="button" @click="monthPickerOpen = true">
|
||||
<button class="flex h-9 w-9 items-center justify-center rounded-md text-2xl leading-none active:bg-paper" type="button" @click="moveMonth(-1)">‹</button>
|
||||
<button class="rounded-md px-1 py-2 text-left text-lg font-semibold active:bg-paper" type="button" @click="monthPickerOpen = true">
|
||||
{{ displayMonth }}
|
||||
</button>
|
||||
<button class="flex h-10 w-10 items-center justify-center rounded-md text-3xl leading-none active:bg-paper" type="button" @click="moveMonth(1)">›</button>
|
||||
<button class="flex h-9 w-9 items-center justify-center rounded-md text-2xl leading-none active:bg-paper" type="button" @click="moveMonth(1)">›</button>
|
||||
</div>
|
||||
<button class="btn-secondary min-h-10 px-3 text-base" type="button" @click="periodSheetOpen = true">
|
||||
월간 ⌄
|
||||
@@ -17,7 +17,7 @@
|
||||
<div class="grid grid-cols-2 border-t border-line">
|
||||
<button
|
||||
type="button"
|
||||
class="relative px-4 py-4 text-center text-lg font-bold"
|
||||
class="relative px-4 py-3 text-center text-sm font-bold"
|
||||
:class="selectedType === 'income' ? 'text-brand-700' : 'text-muted'"
|
||||
@click="selectedType = 'income'"
|
||||
>
|
||||
@@ -26,7 +26,7 @@
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="relative px-4 py-4 text-center text-lg font-bold"
|
||||
class="relative px-4 py-3 text-center text-sm font-bold"
|
||||
:class="selectedType === 'expense' ? 'text-red-600' : 'text-muted'"
|
||||
@click="selectedType = 'expense'"
|
||||
>
|
||||
@@ -36,11 +36,11 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="bg-white px-4 py-5">
|
||||
<div class="bg-white px-4 py-4">
|
||||
<div class="flex items-end justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-semibold text-muted">{{ selectedType === 'income' ? '수입 합계' : '지출 합계' }}</p>
|
||||
<p class="mt-1 text-3xl font-bold">{{ formatWon(selectedTotal) }}</p>
|
||||
<p class="mt-1 text-2xl font-bold">{{ formatWon(selectedTotal) }}</p>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<p class="text-sm font-semibold text-muted">차액</p>
|
||||
@@ -56,21 +56,21 @@
|
||||
v-for="item in categoryTotals"
|
||||
:key="item.id"
|
||||
type="button"
|
||||
class="flex w-full items-center gap-3 px-4 py-4 text-left active:bg-paper"
|
||||
class="flex w-full items-center gap-3 px-4 py-3 text-left active:bg-paper"
|
||||
>
|
||||
<span
|
||||
class="flex h-9 w-16 shrink-0 items-center justify-center rounded-md text-sm font-bold text-white"
|
||||
class="flex h-8 w-14 shrink-0 items-center justify-center rounded-md text-xs font-bold text-white"
|
||||
:class="selectedType === 'income' ? 'bg-brand-600' : 'bg-red-500'"
|
||||
>
|
||||
{{ item.percent }}%
|
||||
</span>
|
||||
<span class="min-w-0 flex-1">
|
||||
<span class="block truncate text-lg font-semibold">{{ item.name }}</span>
|
||||
<span class="block truncate text-base font-semibold">{{ item.name }}</span>
|
||||
<span v-if="item.children.length" class="mt-1 block truncate text-sm text-muted">
|
||||
{{ item.children.map((child: any) => child.name).join(' · ') }}
|
||||
</span>
|
||||
</span>
|
||||
<span class="shrink-0 text-lg font-semibold">{{ formatWon(item.total) }}</span>
|
||||
<span class="shrink-0 text-base font-semibold">{{ formatWon(item.total) }}</span>
|
||||
</button>
|
||||
|
||||
<p v-if="!categoryTotals.length" class="px-4 py-12 text-center text-muted">
|
||||
@@ -93,16 +93,16 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between px-4 py-4">
|
||||
<button class="text-3xl" type="button" @click="pickerYear--">‹</button>
|
||||
<strong class="text-2xl">{{ pickerYear }}</strong>
|
||||
<button class="text-3xl" type="button" @click="pickerYear++">›</button>
|
||||
<button class="text-2xl" type="button" @click="pickerYear--">‹</button>
|
||||
<strong class="text-xl">{{ pickerYear }}</strong>
|
||||
<button class="text-2xl" type="button" @click="pickerYear++">›</button>
|
||||
</div>
|
||||
<div class="grid grid-cols-4 gap-y-2 px-4 pb-6">
|
||||
<button
|
||||
v-for="month in 12"
|
||||
:key="month"
|
||||
type="button"
|
||||
class="rounded-md py-4 text-lg font-bold"
|
||||
class="rounded-md py-3 text-base font-bold"
|
||||
:class="isSelectedPickerMonth(month) ? 'bg-red-50 text-red-500' : 'active:bg-paper'"
|
||||
@click="selectPickerMonth(month)"
|
||||
>
|
||||
@@ -119,7 +119,7 @@
|
||||
<h2 class="text-lg font-bold">조회 단위</h2>
|
||||
</div>
|
||||
<div class="px-2 py-2">
|
||||
<button class="flex w-full items-center justify-between rounded-md px-3 py-4 text-left active:bg-paper" type="button" @click="periodSheetOpen = false">
|
||||
<button class="flex w-full items-center justify-between rounded-md px-3 py-3 text-left active:bg-paper" type="button" @click="periodSheetOpen = false">
|
||||
<span>
|
||||
<span class="block font-semibold">월간</span>
|
||||
<span class="mt-1 block text-sm text-muted">현재 구현된 조회 단위입니다.</span>
|
||||
@@ -180,15 +180,6 @@ watch(monthPickerOpen, (open) => {
|
||||
if (open) pickerYear.value = Number(selectedMonth.value.slice(0, 4))
|
||||
})
|
||||
|
||||
watch([monthPickerOpen, periodSheetOpen], ([monthOpen, periodOpen]) => {
|
||||
if (import.meta.server) return
|
||||
document.body.style.overflow = monthOpen || periodOpen ? 'hidden' : ''
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (import.meta.client) document.body.style.overflow = ''
|
||||
})
|
||||
|
||||
function moveMonth(amount: number) {
|
||||
const date = parse(selectedMonth.value, 'yyyy-MM', new Date())
|
||||
selectedMonth.value = format(addMonths(date, amount), 'yyyy-MM')
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
<template>
|
||||
<section class="min-h-screen bg-paper">
|
||||
<header class="sticky top-0 z-10 flex h-16 items-center justify-between border-b border-line bg-white px-4">
|
||||
<section class="min-h-dvh bg-paper">
|
||||
<header class="sticky top-0 z-10 flex h-14 items-center justify-between border-b border-line bg-white px-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<button
|
||||
type="button"
|
||||
class="flex h-10 w-10 items-center justify-center rounded-md text-3xl leading-none active:bg-paper"
|
||||
class="flex h-9 w-9 items-center justify-center rounded-md text-2xl leading-none active:bg-paper"
|
||||
aria-label="뒤로"
|
||||
@click="goBack"
|
||||
>
|
||||
‹
|
||||
</button>
|
||||
<h1 class="text-xl font-bold">{{ form.type === 'income' ? '수입' : '지출' }}</h1>
|
||||
<h1 class="text-lg font-bold">{{ form.type === 'income' ? '수입' : '지출' }}</h1>
|
||||
</div>
|
||||
<button type="button" class="text-sm font-bold text-brand-700" :disabled="pending" @click="save">
|
||||
저장
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<form class="bg-white px-4 py-5" @submit.prevent="save">
|
||||
<form class="bg-white px-4 py-4" @submit.prevent="save">
|
||||
<div class="mb-5 grid grid-cols-2 gap-2">
|
||||
<button type="button" class="segmented-button border border-line py-3" :class="{ 'border-brand-700 text-brand-700': form.type === 'income' }" @click="setType('income')">수입</button>
|
||||
<button type="button" class="segmented-button border border-line py-3" :class="{ 'border-red-500 text-red-500': form.type === 'expense' }" @click="setType('expense')">지출</button>
|
||||
<button type="button" class="segmented-button border border-line py-2.5" :class="{ 'border-brand-700 text-brand-700': form.type === 'income' }" @click="setType('income')">수입</button>
|
||||
<button type="button" class="segmented-button border border-line py-2.5" :class="{ 'border-red-500 text-red-500': form.type === 'expense' }" @click="setType('expense')">지출</button>
|
||||
</div>
|
||||
|
||||
<div class="divide-y divide-line border-y border-line">
|
||||
<div class="grid grid-cols-[5rem_1fr] items-center gap-3 py-4">
|
||||
<label class="text-lg font-semibold text-muted" for="occurredAt">날짜</label>
|
||||
<input id="occurredAt" v-model="form.occurredAt" class="w-full bg-transparent py-2 text-lg font-semibold outline-none" type="datetime-local" required>
|
||||
<div class="grid grid-cols-[4.25rem_1fr] items-center gap-3 py-3">
|
||||
<label class="text-sm font-semibold text-muted" for="occurredAt">날짜</label>
|
||||
<input id="occurredAt" v-model="form.occurredAt" class="field border-0 bg-transparent shadow-none focus:ring-0" type="datetime-local" required>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-[5rem_1fr] items-center gap-3 py-4">
|
||||
<span class="text-lg font-semibold text-muted">자산</span>
|
||||
<div class="grid grid-cols-[4.25rem_1fr] items-center gap-3 py-3">
|
||||
<span class="text-sm font-semibold text-muted">자산</span>
|
||||
<BottomSheetSelect
|
||||
v-model="form.assetId"
|
||||
title="자산 선택"
|
||||
@@ -39,8 +39,8 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-[5rem_1fr] items-center gap-3 py-4">
|
||||
<span class="text-lg font-semibold text-muted">분류</span>
|
||||
<div class="grid grid-cols-[4.25rem_1fr] items-center gap-3 py-3">
|
||||
<span class="text-sm font-semibold text-muted">분류</span>
|
||||
<BottomSheetSelect
|
||||
v-model="form.categoryId"
|
||||
title="분류 선택"
|
||||
@@ -49,20 +49,20 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-[5rem_1fr] items-center gap-3 py-4">
|
||||
<label class="text-lg font-semibold text-muted" for="amount">금액</label>
|
||||
<input id="amount" v-model.number="form.amount" class="w-full bg-transparent py-2 text-right text-2xl font-bold outline-none" inputmode="numeric" min="1" type="number" required>
|
||||
<div class="grid grid-cols-[4.25rem_1fr] items-center gap-3 py-3">
|
||||
<label class="text-sm font-semibold text-muted" for="amount">금액</label>
|
||||
<input id="amount" v-model.number="form.amount" class="field border-0 bg-transparent text-right text-lg font-bold shadow-none focus:ring-0" inputmode="numeric" min="1" type="number" required>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-[5rem_1fr] items-center gap-3 py-4">
|
||||
<label class="text-lg font-semibold text-muted" for="title">내용</label>
|
||||
<input id="title" v-model="form.title" class="w-full bg-transparent py-2 text-lg font-semibold outline-none" required>
|
||||
<div class="grid grid-cols-[4.25rem_1fr] items-center gap-3 py-3">
|
||||
<label class="text-sm font-semibold text-muted" for="title">내용</label>
|
||||
<input id="title" v-model="form.title" class="field border-0 bg-transparent font-semibold shadow-none focus:ring-0" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 border-y border-line py-4">
|
||||
<label class="mb-2 block text-lg font-semibold text-muted" for="memo">메모</label>
|
||||
<textarea id="memo" v-model="form.memo" class="min-h-24 w-full resize-none bg-transparent text-lg outline-none" placeholder="메모"></textarea>
|
||||
<label class="mb-2 block text-sm font-semibold text-muted" for="memo">메모</label>
|
||||
<textarea id="memo" v-model="form.memo" class="field min-h-24 resize-none" placeholder="메모"></textarea>
|
||||
</div>
|
||||
|
||||
<div v-if="locationLabel" class="mt-3 flex items-center justify-between border-b border-line py-4">
|
||||
|
||||
Reference in New Issue
Block a user