Files
money.sori.studio/pages/share/[token].vue
2026-08-14 13:27:57 +09:00

112 lines
4.4 KiB
Vue

<template>
<section class="min-h-dvh bg-paper">
<header class="sticky top-0 z-10 border-b border-line bg-white px-4 py-4">
<p class="text-xs font-bold text-red-500">읽기 전용 공유</p>
<h1 class="mt-1 text-lg font-extrabold">{{ data?.name || '공유 가계부' }}</h1>
<p v-if="data" class="mt-1 text-xs text-muted">
{{ data.owner }}님의 {{ displayMonth }} 내역 · {{ expiresLabel }}까지
</p>
</header>
<EmptyState
v-if="error"
title="공유 링크를 볼 수 없습니다."
description="링크가 만료되었거나 삭제되었을 수 있습니다."
mark="!"
/>
<template v-else>
<section class="border-b border-line bg-white px-4 py-4">
<div class="flex items-center justify-between">
<button type="button" class="flex h-9 w-9 items-center justify-center rounded-md active:bg-paper" aria-label="이전 달" @click="moveMonth(-1)">
<img class="h-6 w-6 opacity-70" src="~/assets/icons/keyboard_arrow_left.svg" alt="">
</button>
<button type="button" class="text-lg font-bold" @click="monthPickerOpen = true">{{ displayMonth }}</button>
<button type="button" class="flex h-9 w-9 items-center justify-center rounded-md active:bg-paper" aria-label="다음 달" @click="moveMonth(1)">
<img class="h-6 w-6 opacity-70" src="~/assets/icons/keyboard_arrow_right.svg" alt="">
</button>
</div>
<div class="mt-4 grid grid-cols-3 gap-2 text-center text-sm">
<div>
<p class="text-muted">수입</p>
<p class="mt-1 font-bold text-brand-700">{{ formatWon(data?.summary.income || 0) }}</p>
</div>
<div>
<p class="text-muted">지출</p>
<p class="mt-1 font-bold text-red-600">{{ formatWon(data?.summary.expense || 0) }}</p>
</div>
<div>
<p class="text-muted">차액</p>
<p class="mt-1 font-bold">{{ formatWon(data?.summary.diff || 0) }}</p>
</div>
</div>
</section>
<div class="divide-y divide-line bg-white">
<section v-for="group in groupedTransactions" :key="group.date" class="py-2">
<h2 class="px-4 py-3 text-sm font-semibold text-muted">
{{ group.label }}
</h2>
<TransactionListRow
v-for="tx in group.items"
:key="tx.id"
:transaction="tx"
readonly
/>
</section>
</div>
<EmptyState
v-if="!data?.transactions.length"
:title="`공유된 ${displayMonth} 거래가 없습니다.`"
description="읽기 전용 공유 화면이라 이곳에서는 내용을 추가하거나 수정할 수 없습니다."
mark="-"
/>
</template>
<BottomSheetMonthPicker v-model="selectedMonth" v-model:open="monthPickerOpen" />
</section>
</template>
<script setup lang="ts">
import { addMonths, format, parse } from 'date-fns'
import type { Transaction } from '~/composables/useMoney'
definePageMeta({ layout: false })
const route = useRoute()
const token = computed(() => String(route.params.token))
const selectedMonth = ref(monthKey())
const monthPickerOpen = ref(false)
const { data, error } = await useFetch<any>(() => `/api/shared/${token.value}`, {
query: { month: selectedMonth }
})
const displayMonth = computed(() => {
const date = parse(selectedMonth.value, 'yyyy-MM', new Date())
return format(date, 'yyyy년 M월')
})
const expiresLabel = computed(() => {
if (!data.value?.expiresAt) return ''
return format(new Date(data.value.expiresAt), 'yyyy.MM.dd')
})
const groupedTransactions = computed(() => {
const groups = new Map<string, { date: string; label: string; items: Transaction[] }>()
for (const tx of data.value?.transactions || []) {
const date = new Date(tx.occurredAt)
const key = format(date, 'yyyy-MM-dd')
const weekday = new Intl.DateTimeFormat('ko-KR', { weekday: 'short' }).format(date)
const group = groups.get(key) || { date: key, label: `${format(date, 'M. d')}(${weekday})`, items: [] }
group.items.push(tx)
groups.set(key, group)
}
return Array.from(groups.values()).sort((a, b) => b.date.localeCompare(a.date))
})
function moveMonth(amount: number) {
const date = parse(selectedMonth.value, 'yyyy-MM', new Date())
selectedMonth.value = format(addMonths(date, amount), 'yyyy-MM')
}
</script>