Files
sori.studio/components/admin/AdminCommentList.vue

70 lines
4.0 KiB
Vue

<script setup>
const props = defineProps({
memberId: { type: String, default: '' },
recent: { type: Boolean, default: false }
})
const page = ref(1)
const query = computed(() => ({
...(props.memberId ? { memberId: props.memberId } : {}),
page: page.value,
pageSize: props.recent ? 5 : 10
}))
const { data, status, error, refresh } = await useFetch('/admin/api/comments', {
query,
default: () => ({ items: [], pagination: { page: 1, pageCount: 1, total: 0 } })
})
watch(() => props.memberId, () => { page.value = 1 })
let refreshTimer = null
onMounted(() => {
if (props.recent) refreshTimer = window.setInterval(() => refresh(), 30000)
})
onUnmounted(() => {
if (refreshTimer) window.clearInterval(refreshTimer)
})
/**
* 댓글 작성 시각을 한국어 날짜로 표시한다.
* @param {string} value - 작성 시각
* @returns {string} 표시 날짜
*/
const formatCreatedAt = (value) => new Intl.DateTimeFormat('ko-KR', {
dateStyle: 'medium', timeStyle: 'short', timeZone: 'Asia/Seoul'
}).format(new Date(value))
</script>
<template>
<section class="admin-comment-list rounded-xl border border-line bg-white p-5 md:p-6" :aria-busy="status === 'pending'">
<h2 class="admin-comment-list__heading text-lg font-semibold">
{{ recent ? '최근 댓글' : '작성한 댓글' }}
<span class="text-sm font-normal text-muted">전체 {{ data?.pagination.total || 0 }}개 · 답글 포함</span>
</h2>
<div v-if="error" class="admin-comment-list__error mt-4 text-sm text-red-700" role="alert">
댓글을 불러오지 못했습니다.
<button type="button" class="underline" @click="refresh()">다시 시도</button>
</div>
<p v-else-if="status === 'pending'" class="admin-comment-list__loading py-6 text-sm text-muted">댓글을 불러오는 중입니다.</p>
<p v-else-if="!data?.items.length" class="admin-comment-list__empty py-6 text-sm text-muted">작성한 댓글이 없습니다.</p>
<ul v-else class="admin-comment-list__items mt-3 divide-y divide-line">
<li v-for="comment in data.items" :key="comment.id" class="admin-comment-list__item py-4">
<div class="admin-comment-list__meta flex flex-wrap items-center gap-2 text-xs text-muted">
<NuxtLink :to="`/admin/members/${comment.userId}`" class="font-semibold hover:underline">{{ comment.username }}</NuxtLink>
<time :datetime="comment.createdAt">{{ formatCreatedAt(comment.createdAt) }}</time>
<span v-if="comment.parentId">답글</span>
<span v-if="comment.status !== 'published'">{{ comment.status === 'pending' ? '승인 대기' : '차단됨' }}</span>
</div>
<p class="admin-comment-list__body my-2 whitespace-pre-wrap break-words text-sm">{{ comment.body }}</p>
<div class="admin-comment-list__post flex flex-wrap items-center gap-3 text-sm">
<NuxtLink :to="`/admin/posts/${comment.postId}`" class="min-w-0 break-words font-medium hover:underline">{{ comment.postTitle || '(제목 없음)' }}</NuxtLink>
<NuxtLink v-if="['published', 'members'].includes(comment.postStatus)" :to="`/post/${encodeURIComponent(comment.postSlug)}#comments`" target="_blank" rel="noopener noreferrer" class="shrink-0 text-muted underline">게시물 보기</NuxtLink>
<span v-else class="text-xs text-muted">미공개 게시물</span>
</div>
</li>
</ul>
<nav v-if="!recent && data?.pagination.pageCount > 1" class="admin-comment-list__pagination mt-4 flex items-center justify-between gap-3 text-sm" aria-label="댓글 페이지">
<button type="button" class="rounded border border-line px-3 py-2 disabled:opacity-40" :disabled="status === 'pending' || data.pagination.page <= 1" @click="page = data.pagination.page - 1">이전</button>
<span>{{ data.pagination.page }} / {{ data.pagination.pageCount }} 페이지</span>
<button type="button" class="rounded border border-line px-3 py-2 disabled:opacity-40" :disabled="status === 'pending' || data.pagination.page >= data.pagination.pageCount" @click="page = data.pagination.page + 1">다음</button>
</nav>
</section>
</template>