Polish empty states

This commit is contained in:
2026-08-14 13:27:57 +09:00
parent 45d2b74775
commit cbd43e16c2
9 changed files with 82 additions and 29 deletions

25
components/EmptyState.vue Normal file
View File

@@ -0,0 +1,25 @@
<template>
<section class="mx-auto flex max-w-sm flex-col items-center px-6 py-12 text-center">
<div class="flex h-12 w-12 items-center justify-center rounded-full bg-paper text-lg font-extrabold text-muted">
{{ mark }}
</div>
<h2 class="mt-4 text-base font-bold text-ink">{{ title }}</h2>
<p v-if="description" class="mt-2 text-sm leading-6 text-muted">
{{ description }}
</p>
<div v-if="$slots.default" class="mt-5">
<slot />
</div>
</section>
</template>
<script setup lang="ts">
withDefaults(defineProps<{
title: string
description?: string
mark?: string
}>(), {
description: '',
mark: '-'
})
</script>

View File

@@ -85,3 +85,4 @@
- PC 중간 폭 화면에서 콘텐츠 오른쪽이 잘리지 않도록 전역 레이아웃을 margin 기반에서 padding 기반으로 변경했다.
- 내역 헤더의 검색과 필터 버튼을 `assets/icons/search.svg`, `assets/icons/tune.svg` 인라인 SVG로 교체했다.
- CSV 내보내기 컬럼을 한글 컬럼명과 앱의 거래 흐름에 맞는 순서로 정리하고 Excel 호환 BOM을 추가했다.
- 빈 데이터 상태 화면을 공통 컴포넌트로 분리하고 내역, 통계, 자산, 설정, 공유 화면에 적용했다.

View File

@@ -30,6 +30,7 @@
- 검색과 필터 같은 헤더 액션도 텍스트 기호 대신 `assets/icons`의 SVG 아이콘을 사용한다.
- 거래 목록의 카테고리 표시는 하위 카테고리가 있으면 하위 이름만, 없으면 상위 이름만 보여준다.
- 카테고리 아이콘과 색상은 카테고리 관리 상세 화면에서 수정하고 거래 목록 아이콘에 즉시 반영한다.
- 빈 데이터 화면은 단순 텍스트만 노출하지 않고 공통 EmptyState 형태로 제목, 짧은 설명, 다음 행동의 힌트를 제공한다.
## 데이터 기준

View File

@@ -73,9 +73,13 @@
</span>
<span class="shrink-0 text-base font-bold">{{ formatWon(asset.balance || 0) }}</span>
</button>
<p v-if="!assets.length" class="px-4 py-12 text-center text-muted md:col-span-full md:rounded-md md:border md:border-line md:bg-white">
등록된 자산이 없습니다.
</p>
<EmptyState
v-if="!assets.length"
class="md:col-span-full md:rounded-md md:border md:border-line md:bg-white"
title="등록된 자산이 없습니다."
description="설정의 자산 관리에서 사용할 현금, 카드, 계좌를 먼저 추가해 주세요."
mark="₩"
/>
</div>
<div v-else class="md:grid md:grid-cols-[20rem_1fr] md:gap-4 md:p-5">
@@ -120,9 +124,12 @@
/>
</section>
<p v-if="!assetTransactions.length" class="px-4 py-12 text-center text-muted">
이 자산의 {{ displayMonth }} 거래가 없습니다.
</p>
<EmptyState
v-if="!assetTransactions.length"
:title="`이 자산의 ${displayMonth} 거래가 없습니다.`"
description="월을 바꾸거나 새 거래를 추가하면 이곳에 자산별 내역이 표시됩니다."
mark="-"
/>
</div>
<div v-else-if="detailView === 'monthly'" key="monthly" class="divide-y divide-line bg-white md:overflow-hidden md:rounded-md md:border md:border-line md:shadow-sm">

View File

@@ -106,9 +106,12 @@
</section>
</div>
<div v-if="!data?.transactions.length" class="px-4 py-16 text-center text-muted">
아직 거래가 없습니다.
</div>
<EmptyState
v-if="!data?.transactions.length"
title="아직 거래가 없습니다."
description="오른쪽 아래 추가 버튼으로 첫 수입이나 지출을 기록해 보세요."
mark="₩"
/>
<Teleport to="body">
<Transition enter-active-class="transition duration-200 ease-out" enter-from-class="opacity-0" enter-to-class="opacity-100" leave-active-class="transition duration-150 ease-in" leave-from-class="opacity-100" leave-to-class="opacity-0">

View File

@@ -155,9 +155,12 @@
</div>
</div>
<p v-if="!shareLinks.length" class="px-4 py-12 text-center text-muted">
만든 공유 링크가 없습니다.
</p>
<EmptyState
v-if="!shareLinks.length"
title="만든 공유 링크가 없습니다."
description="기간을 정해 읽기 전용으로 보여줄 링크를 만들 수 있습니다."
mark="↗"
/>
</div>
</div>
@@ -269,9 +272,12 @@
<img class="h-6 w-6 opacity-60" src="~/assets/icons/keyboard_arrow_right.svg" alt="">
</button>
<p v-if="!assets.length" class="px-4 py-12 text-center text-muted">
등록된 자산이 없습니다.
</p>
<EmptyState
v-if="!assets.length"
title="등록된 자산이 없습니다."
description="오른쪽 위 추가 버튼으로 자산을 등록해 주세요."
mark="₩"
/>
</div>
</div>
@@ -398,9 +404,12 @@
<img class="h-6 w-6 opacity-60" src="~/assets/icons/keyboard_arrow_right.svg" alt="">
</button>
<p v-if="!visibleCategories.length" class="px-4 py-12 text-center text-muted">
{{ selectedParentCategory ? '하위 카테고리가 없습니다.' : `${categoryTypeLabel} 상위 카테고리가 없습니다.` }}
</p>
<EmptyState
v-if="!visibleCategories.length"
:title="selectedParentCategory ? '하위 카테고리가 없습니다.' : `${categoryTypeLabel} 상위 카테고리가 없습니다.`"
description="오른쪽 위 추가 버튼으로 필요한 분류를 추가해 주세요."
mark="+"
/>
</div>
</div>

View File

@@ -8,10 +8,12 @@
</p>
</header>
<div v-if="error" class="px-4 py-20 text-center">
<h2 class="text-lg font-bold">공유 링크를 볼 수 없습니다.</h2>
<p class="mt-2 text-sm text-muted">링크가 만료되었거나 삭제되었을 수 있습니다.</p>
</div>
<EmptyState
v-if="error"
title="공유 링크를 볼 수 없습니다."
description="링크가 만료되었거나 삭제되었을 수 있습니다."
mark="!"
/>
<template v-else>
<section class="border-b border-line bg-white px-4 py-4">
@@ -55,9 +57,12 @@
</section>
</div>
<p v-if="!data?.transactions.length" class="px-4 py-16 text-center text-muted">
공유된 {{ displayMonth }} 거래가 없습니다.
</p>
<EmptyState
v-if="!data?.transactions.length"
:title="`공유된 ${displayMonth} 거래가 없습니다.`"
description="읽기 전용 공유 화면이라 이곳에서는 내용을 추가하거나 수정할 수 없습니다."
mark="-"
/>
</template>
<BottomSheetMonthPicker v-model="selectedMonth" v-model:open="monthPickerOpen" />

View File

@@ -61,9 +61,12 @@
<span class="shrink-0 text-sm font-semibold">{{ formatWon(item.total) }}</span>
</button>
<p v-if="!categoryTotals.length" class="px-4 py-12 text-center text-muted">
{{ selectedType === 'income' ? '집계할 수입이 없습니다.' : '집계할 지출이 없습니다.' }}
</p>
<EmptyState
v-if="!categoryTotals.length"
:title="selectedType === 'income' ? '집계할 수입이 없습니다.' : '집계할 지출이 없습니다.'"
description="해당 월에 거래를 추가하면 분류별 합계를 바로 볼 수 있습니다."
mark="%"
/>
</div>
<Teleport to="body">

View File

@@ -36,7 +36,6 @@
- 회원 탈퇴 또는 데이터 삭제 정책을 정한다.
- 로그인 실패, 세션 만료, 네트워크 오류 상태를 화면에서 더 명확히 보여준다.
- 빈 데이터 상태 화면을 페이지별로 다듬는다.
## 8. 공유