Polish empty states
This commit is contained in:
25
components/EmptyState.vue
Normal file
25
components/EmptyState.vue
Normal 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>
|
||||
@@ -85,3 +85,4 @@
|
||||
- PC 중간 폭 화면에서 콘텐츠 오른쪽이 잘리지 않도록 전역 레이아웃을 margin 기반에서 padding 기반으로 변경했다.
|
||||
- 내역 헤더의 검색과 필터 버튼을 `assets/icons/search.svg`, `assets/icons/tune.svg` 인라인 SVG로 교체했다.
|
||||
- CSV 내보내기 컬럼을 한글 컬럼명과 앱의 거래 흐름에 맞는 순서로 정리하고 Excel 호환 BOM을 추가했다.
|
||||
- 빈 데이터 상태 화면을 공통 컴포넌트로 분리하고 내역, 통계, 자산, 설정, 공유 화면에 적용했다.
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
- 검색과 필터 같은 헤더 액션도 텍스트 기호 대신 `assets/icons`의 SVG 아이콘을 사용한다.
|
||||
- 거래 목록의 카테고리 표시는 하위 카테고리가 있으면 하위 이름만, 없으면 상위 이름만 보여준다.
|
||||
- 카테고리 아이콘과 색상은 카테고리 관리 상세 화면에서 수정하고 거래 목록 아이콘에 즉시 반영한다.
|
||||
- 빈 데이터 화면은 단순 텍스트만 노출하지 않고 공통 EmptyState 형태로 제목, 짧은 설명, 다음 행동의 힌트를 제공한다.
|
||||
|
||||
## 데이터 기준
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user