Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/api/inquiries.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ export const inquiriesApi = {
},

updateAnswer(inquiryId: string, payload: InquiryAnswerRequest) {
return client.patch<ApiResponse<InquiryDetail>>(`/inquiries/${inquiryId}/answer`, payload) // PATCH
return client.patch<ApiResponse<InquiryDetail>>(`/inquiries/${inquiryId}/answer`, payload)
},

delete(inquiryId: string) {
return client.delete(`/inquiries/${inquiryId}`)
deleteAnswer(inquiryId: string) {
return client.delete<ApiResponse<InquiryDetail>>(`/inquiries/${inquiryId}/answer`)
},
}
148 changes: 45 additions & 103 deletions src/views/inquiries/InquiriesPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,13 @@ import { useRouter } from 'vue-router'
import { toast } from 'vue-sonner'
import DashboardLayout from '@/layouts/DashboardLayout.vue'
import BaseSpinner from '@/components/common/BaseSpinner.vue'
import ConfirmDialog from '@/components/common/ConfirmDialog.vue'
import { inquiriesApi } from '@/api/inquiries.api'
import type { Inquiry } from '@/types/inquiry'
import type { ProblemDetail } from '@/types/api'

const router = useRouter()
const inquiries = ref<Inquiry[]>([])
const isLoading = ref(false)
const isDeleting = ref(false)
const selectedInquiry = ref<Inquiry | null>(null)
const showDeleteDialog = ref(false)

const typeLabel = (type: Inquiry['type']) => {
const map = { USAGE: '이용', BUG: '버그', IMPROVEMENT: '개선', ETC: '기타' }
Expand All @@ -34,36 +30,14 @@ const fetchInquiries = async () => {
}
}

const handleDelete = async () => {
if (!selectedInquiry.value) return
isDeleting.value = true
try {
await inquiriesApi.delete(selectedInquiry.value.id)
toast.success('문의가 삭제되었습니다.')
showDeleteDialog.value = false
await fetchInquiries()
} catch (error: unknown) {
const problem = (error as any)?.response?.data as ProblemDetail | undefined
toast.error(problem?.detail || '삭제에 실패했습니다.')
} finally {
isDeleting.value = false
}
}

const openDelete = (e: Event, inquiry: Inquiry) => {
e.stopPropagation()
selectedInquiry.value = inquiry
showDeleteDialog.value = true
}

const statusLabel = (status: Inquiry['status']) => {
return status === 'PENDING' ? '대기' : '답변완료'
}

const statusClass = (status: Inquiry['status']) => {
return status === 'PENDING'
? 'bg-yellow-50 text-yellow-600'
: 'bg-primary/10 text-primary'
? 'bg-yellow-50 text-yellow-600'
: 'bg-primary/10 text-primary'
}

const formatDate = (dateStr: string) => {
Expand All @@ -77,7 +51,7 @@ onMounted(fetchInquiries)

<template>
<DashboardLayout>
<BaseSpinner :show="isLoading || isDeleting" />
<BaseSpinner :show="isLoading" />

<div class="space-y-5">

Expand All @@ -91,61 +65,51 @@ onMounted(fetchInquiries)
<div class="hidden sm:block overflow-hidden rounded-2xl bg-white border border-neutral-200">
<table class="w-full text-sm table-fixed">
<colgroup>
<col class="w-[28%]" />
<col class="w-[18%]" />
<col class="w-[10%]" />
<col class="w-[35%]" />
<col class="w-[22%]" />
<col class="w-[13%]" />
<col class="w-[15%]" />
<col class="w-[15%]" />
<col class="w-[17%]" />
<col class="w-[12%]" />
</colgroup>
<thead>
<tr class="border-b border-neutral-200 bg-neutral-50">
<th class="px-5 py-3.5 text-left text-xs font-semibold text-neutral-500">내용</th>
<th class="px-5 py-3.5 text-center text-xs font-semibold text-neutral-500">작성자</th>
<th class="px-5 py-3.5 text-center text-xs font-semibold text-neutral-500">유형</th>
<th class="px-5 py-3.5 text-center text-xs font-semibold text-neutral-500">상태</th>
<th class="px-5 py-3.5 text-center text-xs font-semibold text-neutral-500">등록일</th>
<th class="px-5 py-3.5 text-center text-xs font-semibold text-neutral-500">액션</th>
</tr>
<tr class="border-b border-neutral-200 bg-neutral-50">
<th class="px-5 py-3.5 text-left text-xs font-semibold text-neutral-500">내용</th>
<th class="px-5 py-3.5 text-center text-xs font-semibold text-neutral-500">작성자</th>
<th class="px-5 py-3.5 text-center text-xs font-semibold text-neutral-500">유형</th>
<th class="px-5 py-3.5 text-center text-xs font-semibold text-neutral-500">상태</th>
<th class="px-5 py-3.5 text-center text-xs font-semibold text-neutral-500">등록일</th>
</tr>
</thead>
<tbody class="divide-y divide-neutral-100">
<tr v-if="inquiries.length === 0">
<td colspan="6" class="py-16 text-center text-sm text-neutral-400">
등록된 문의가 없습니다.
</td>
</tr>
<tr
<tr v-if="inquiries.length === 0">
<td colspan="5" class="py-16 text-center text-sm text-neutral-400">
등록된 문의가 없습니다.
</td>
</tr>
<tr
v-for="inquiry in inquiries"
:key="inquiry.id"
class="hover:bg-neutral-50 transition cursor-pointer"
@click="router.push(`/inquiries/${inquiry.id}`)"
>
<td class="px-5 py-4 text-neutral-900">
<span class="block truncate">{{ inquiry.content }}</span>
</td>
<td class="px-5 py-4 text-center text-neutral-500">
<span class="block truncate">{{ inquiry.email }}</span>
</td>
<td class="px-5 py-4 text-center text-neutral-500 whitespace-nowrap">
{{ typeLabel(inquiry.type) }}
</td>
<td class="px-5 py-4 text-center">
<span :class="['inline-flex items-center justify-center rounded-full px-2.5 py-1 text-xs font-medium whitespace-nowrap', statusClass(inquiry.status)]">
{{ statusLabel(inquiry.status) }}
</span>
</td>
<td class="px-5 py-4 text-center text-neutral-500 whitespace-nowrap">
{{ formatDate(inquiry.createdAt) }}
</td>
<td class="px-5 py-4 text-center" @click.stop>
<button
@click="openDelete($event, inquiry)"
class="rounded-lg bg-neutral-100 px-3 py-1.5 text-xs font-medium text-neutral-500 hover:bg-red-50 hover:text-red-500 transition cursor-pointer whitespace-nowrap"
>
삭제
</button>
</td>
</tr>
>
<td class="px-5 py-4 text-neutral-900">
<span class="block truncate">{{ inquiry.content }}</span>
</td>
<td class="px-5 py-4 text-center text-neutral-500">
<span class="block truncate">{{ inquiry.email }}</span>
</td>
<td class="px-5 py-4 text-center text-neutral-500 whitespace-nowrap">
{{ typeLabel(inquiry.type) }}
</td>
<td class="px-5 py-4 text-center">
<span :class="['inline-flex items-center justify-center rounded-full px-2.5 py-1 text-xs font-medium whitespace-nowrap', statusClass(inquiry.status)]">
{{ statusLabel(inquiry.status) }}
</span>
</td>
<td class="px-5 py-4 text-center text-neutral-500 whitespace-nowrap">
{{ formatDate(inquiry.createdAt) }}
</td>
</tr>
</tbody>
</table>
</div>
Expand All @@ -156,49 +120,27 @@ onMounted(fetchInquiries)
등록된 문의가 없습니다.
</p>
<div
v-for="inquiry in inquiries"
:key="inquiry.id"
class="bg-white rounded-2xl border border-neutral-200 px-4 py-4 space-y-3 cursor-pointer"
@click="router.push(`/inquiries/${inquiry.id}`)"
v-for="inquiry in inquiries"
:key="inquiry.id"
class="bg-white rounded-2xl border border-neutral-200 px-4 py-4 space-y-3 cursor-pointer"
@click="router.push(`/inquiries/${inquiry.id}`)"
>
<div class="flex items-start justify-between gap-2">
<span class="text-sm font-medium text-neutral-900 truncate">{{ inquiry.content }}</span>
<span :class="['flex-shrink-0 inline-flex items-center rounded-full px-2.5 py-1 text-xs font-medium whitespace-nowrap', statusClass(inquiry.status)]">
{{ statusLabel(inquiry.status) }}
</span>
{{ statusLabel(inquiry.status) }}
</span>
</div>
<!-- 이메일 / 유형 / 날짜를 세로로 분리 -->
<div class="flex flex-col gap-0.5 text-xs text-neutral-400">
<span class="truncate">{{ inquiry.email }}</span>
<div class="flex items-center justify-between">
<span>{{ typeLabel(inquiry.type) }}</span>
<span class="whitespace-nowrap">{{ formatDate(inquiry.createdAt) }}</span>
</div>
</div>
<div class="pt-2 border-t border-neutral-100" @click.stop>
<button
@click="openDelete($event, inquiry)"
class="w-full rounded-lg bg-neutral-100 py-1.5 text-xs font-medium text-neutral-500 hover:bg-red-50 hover:text-red-500 transition cursor-pointer"
>
삭제
</button>
</div>
</div>
</div>

</div>

<!-- 삭제 다이얼로그 -->
<ConfirmDialog
v-if="showDeleteDialog"
title="문의 삭제"
:description="`해당 문의를 삭제하시겠습니까?`"
confirm-text="삭제"
confirm-class="bg-red-500 hover:bg-red-400"
:is-loading="isDeleting"
@close="showDeleteDialog = false"
@confirm="handleDelete"
/>

</DashboardLayout>
</template>
Loading