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 기반으로 변경했다.
|
- PC 중간 폭 화면에서 콘텐츠 오른쪽이 잘리지 않도록 전역 레이아웃을 margin 기반에서 padding 기반으로 변경했다.
|
||||||
- 내역 헤더의 검색과 필터 버튼을 `assets/icons/search.svg`, `assets/icons/tune.svg` 인라인 SVG로 교체했다.
|
- 내역 헤더의 검색과 필터 버튼을 `assets/icons/search.svg`, `assets/icons/tune.svg` 인라인 SVG로 교체했다.
|
||||||
- CSV 내보내기 컬럼을 한글 컬럼명과 앱의 거래 흐름에 맞는 순서로 정리하고 Excel 호환 BOM을 추가했다.
|
- CSV 내보내기 컬럼을 한글 컬럼명과 앱의 거래 흐름에 맞는 순서로 정리하고 Excel 호환 BOM을 추가했다.
|
||||||
|
- 빈 데이터 상태 화면을 공통 컴포넌트로 분리하고 내역, 통계, 자산, 설정, 공유 화면에 적용했다.
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
- 검색과 필터 같은 헤더 액션도 텍스트 기호 대신 `assets/icons`의 SVG 아이콘을 사용한다.
|
- 검색과 필터 같은 헤더 액션도 텍스트 기호 대신 `assets/icons`의 SVG 아이콘을 사용한다.
|
||||||
- 거래 목록의 카테고리 표시는 하위 카테고리가 있으면 하위 이름만, 없으면 상위 이름만 보여준다.
|
- 거래 목록의 카테고리 표시는 하위 카테고리가 있으면 하위 이름만, 없으면 상위 이름만 보여준다.
|
||||||
- 카테고리 아이콘과 색상은 카테고리 관리 상세 화면에서 수정하고 거래 목록 아이콘에 즉시 반영한다.
|
- 카테고리 아이콘과 색상은 카테고리 관리 상세 화면에서 수정하고 거래 목록 아이콘에 즉시 반영한다.
|
||||||
|
- 빈 데이터 화면은 단순 텍스트만 노출하지 않고 공통 EmptyState 형태로 제목, 짧은 설명, 다음 행동의 힌트를 제공한다.
|
||||||
|
|
||||||
## 데이터 기준
|
## 데이터 기준
|
||||||
|
|
||||||
|
|||||||
@@ -73,9 +73,13 @@
|
|||||||
</span>
|
</span>
|
||||||
<span class="shrink-0 text-base font-bold">{{ formatWon(asset.balance || 0) }}</span>
|
<span class="shrink-0 text-base font-bold">{{ formatWon(asset.balance || 0) }}</span>
|
||||||
</button>
|
</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">
|
<EmptyState
|
||||||
등록된 자산이 없습니다.
|
v-if="!assets.length"
|
||||||
</p>
|
class="md:col-span-full md:rounded-md md:border md:border-line md:bg-white"
|
||||||
|
title="등록된 자산이 없습니다."
|
||||||
|
description="설정의 자산 관리에서 사용할 현금, 카드, 계좌를 먼저 추가해 주세요."
|
||||||
|
mark="₩"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="md:grid md:grid-cols-[20rem_1fr] md:gap-4 md:p-5">
|
<div v-else class="md:grid md:grid-cols-[20rem_1fr] md:gap-4 md:p-5">
|
||||||
@@ -120,9 +124,12 @@
|
|||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<p v-if="!assetTransactions.length" class="px-4 py-12 text-center text-muted">
|
<EmptyState
|
||||||
이 자산의 {{ displayMonth }} 거래가 없습니다.
|
v-if="!assetTransactions.length"
|
||||||
</p>
|
:title="`이 자산의 ${displayMonth} 거래가 없습니다.`"
|
||||||
|
description="월을 바꾸거나 새 거래를 추가하면 이곳에 자산별 내역이 표시됩니다."
|
||||||
|
mark="-"
|
||||||
|
/>
|
||||||
</div>
|
</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">
|
<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>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="!data?.transactions.length" class="px-4 py-16 text-center text-muted">
|
<EmptyState
|
||||||
아직 거래가 없습니다.
|
v-if="!data?.transactions.length"
|
||||||
</div>
|
title="아직 거래가 없습니다."
|
||||||
|
description="오른쪽 아래 추가 버튼으로 첫 수입이나 지출을 기록해 보세요."
|
||||||
|
mark="₩"
|
||||||
|
/>
|
||||||
|
|
||||||
<Teleport to="body">
|
<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">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p v-if="!shareLinks.length" class="px-4 py-12 text-center text-muted">
|
<EmptyState
|
||||||
만든 공유 링크가 없습니다.
|
v-if="!shareLinks.length"
|
||||||
</p>
|
title="만든 공유 링크가 없습니다."
|
||||||
|
description="기간을 정해 읽기 전용으로 보여줄 링크를 만들 수 있습니다."
|
||||||
|
mark="↗"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -269,9 +272,12 @@
|
|||||||
<img class="h-6 w-6 opacity-60" src="~/assets/icons/keyboard_arrow_right.svg" alt="">
|
<img class="h-6 w-6 opacity-60" src="~/assets/icons/keyboard_arrow_right.svg" alt="">
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<p v-if="!assets.length" class="px-4 py-12 text-center text-muted">
|
<EmptyState
|
||||||
등록된 자산이 없습니다.
|
v-if="!assets.length"
|
||||||
</p>
|
title="등록된 자산이 없습니다."
|
||||||
|
description="오른쪽 위 추가 버튼으로 자산을 등록해 주세요."
|
||||||
|
mark="₩"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -398,9 +404,12 @@
|
|||||||
<img class="h-6 w-6 opacity-60" src="~/assets/icons/keyboard_arrow_right.svg" alt="">
|
<img class="h-6 w-6 opacity-60" src="~/assets/icons/keyboard_arrow_right.svg" alt="">
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<p v-if="!visibleCategories.length" class="px-4 py-12 text-center text-muted">
|
<EmptyState
|
||||||
{{ selectedParentCategory ? '하위 카테고리가 없습니다.' : `${categoryTypeLabel} 상위 카테고리가 없습니다.` }}
|
v-if="!visibleCategories.length"
|
||||||
</p>
|
:title="selectedParentCategory ? '하위 카테고리가 없습니다.' : `${categoryTypeLabel} 상위 카테고리가 없습니다.`"
|
||||||
|
description="오른쪽 위 추가 버튼으로 필요한 분류를 추가해 주세요."
|
||||||
|
mark="+"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,12 @@
|
|||||||
</p>
|
</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div v-if="error" class="px-4 py-20 text-center">
|
<EmptyState
|
||||||
<h2 class="text-lg font-bold">공유 링크를 볼 수 없습니다.</h2>
|
v-if="error"
|
||||||
<p class="mt-2 text-sm text-muted">링크가 만료되었거나 삭제되었을 수 있습니다.</p>
|
title="공유 링크를 볼 수 없습니다."
|
||||||
</div>
|
description="링크가 만료되었거나 삭제되었을 수 있습니다."
|
||||||
|
mark="!"
|
||||||
|
/>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<section class="border-b border-line bg-white px-4 py-4">
|
<section class="border-b border-line bg-white px-4 py-4">
|
||||||
@@ -55,9 +57,12 @@
|
|||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p v-if="!data?.transactions.length" class="px-4 py-16 text-center text-muted">
|
<EmptyState
|
||||||
공유된 {{ displayMonth }} 거래가 없습니다.
|
v-if="!data?.transactions.length"
|
||||||
</p>
|
:title="`공유된 ${displayMonth} 거래가 없습니다.`"
|
||||||
|
description="읽기 전용 공유 화면이라 이곳에서는 내용을 추가하거나 수정할 수 없습니다."
|
||||||
|
mark="-"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<BottomSheetMonthPicker v-model="selectedMonth" v-model:open="monthPickerOpen" />
|
<BottomSheetMonthPicker v-model="selectedMonth" v-model:open="monthPickerOpen" />
|
||||||
|
|||||||
@@ -61,9 +61,12 @@
|
|||||||
<span class="shrink-0 text-sm font-semibold">{{ formatWon(item.total) }}</span>
|
<span class="shrink-0 text-sm font-semibold">{{ formatWon(item.total) }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<p v-if="!categoryTotals.length" class="px-4 py-12 text-center text-muted">
|
<EmptyState
|
||||||
{{ selectedType === 'income' ? '집계할 수입이 없습니다.' : '집계할 지출이 없습니다.' }}
|
v-if="!categoryTotals.length"
|
||||||
</p>
|
:title="selectedType === 'income' ? '집계할 수입이 없습니다.' : '집계할 지출이 없습니다.'"
|
||||||
|
description="해당 월에 거래를 추가하면 분류별 합계를 바로 볼 수 있습니다."
|
||||||
|
mark="%"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Teleport to="body">
|
<Teleport to="body">
|
||||||
|
|||||||
Reference in New Issue
Block a user