Skip to content
Merged
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
14 changes: 14 additions & 0 deletions src/api/inquiry.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,18 @@ export const getInquiry = async ({ page, size, inquiryType, inquiryStatus }) =>
console.error(error);
throw error;
}
}

export const patchInquiryStatus = async ({ inquiryId, answer, status }) => {
try {
const response = await client.patch(`/api/v1/admin/inquiry/${inquiryId}`, {
answer: answer,
status: status,
});
return response;
}
catch (error) {
console.error(error);
throw error;
}
}
15 changes: 9 additions & 6 deletions src/api/report.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,15 @@ export const getReport = async ({ postType, startDate, endDate, nickname, pageab

export const patchReport = async ({ reportId, reportStatus }) => {
try {
const response = await client.patch(`/api/v1/admin/report/${reportId}`, null, {
params: {
reportStatus: reportStatus,
},
});
return response.data;
const response = await client.patch(`/api/v1/admin/report/${reportId}`,
null,
{
params: {
reportStatus: reportStatus,
},
}
);
return response;
} catch (error) {
console.error(error);
throw error;
Expand Down
14 changes: 12 additions & 2 deletions src/components/common/table.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ export default function Table({ columns, data, renderAction, onRowClick, origina
<thead className="bg-blue-200 border">
<tr>
{columns.map((col) => (
<th key={col.key} className="border p-2 text-left">{col.value}</th>
<th
key={col.key}
className="border p-2 text-left"
style={col.width ? { width: col.width, maxWidth: col.width } : {}}
>
{col.value}
</th>
))}
</tr>
</thead>
Expand All @@ -17,7 +23,11 @@ export default function Table({ columns, data, renderAction, onRowClick, origina
onClick={() => onRowClick && onRowClick(row, originalData && originalData[idx])}
>
{columns.map((col) => (
<td key={col.key} className="border p-2">
<td
key={col.key}
className="border p-2"
style={col.width ? { width: col.width, maxWidth: col.width } : {}}
>
{col.render ? col.render(row[col.key], row) : row[col.key]}
</td>
))}
Expand Down
203 changes: 199 additions & 4 deletions src/pages/inquiry.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getInquiry } from "../api/inquiry";
import { getInquiry, patchInquiryStatus } from "../api/inquiry";
import AdminLayout from "../components/layout/adminLayout";
import Table from "../components/common/table";
import Pagination from "../components/common/pagination";
Expand All @@ -11,12 +11,29 @@ export default function Inquiry() {
const pageSize = 10;
const [inquiryType, setInquiryType] = useState("ALL");
const [inquiryStatus, setInquiryStatus] = useState("ALL");
const [selectedInquiry, setSelectedInquiry] = useState(null);
const [showModal, setShowModal] = useState(false);
const [selectedStatus, setSelectedStatus] = useState(null);
const [answer, setAnswer] = useState("");


const columns = [
{ key: "문의 유형", value: "문의 유형" },
{ key: "문의 제목", value: "문의 제목" },
{ key: "문의 내용", value: "문의 내용" },
{
key: "문의 내용",
value: "문의 내용",
maxWidth: "600px",
render: (content) => {
if (!content) return "";
const truncated = content.length > 40 ? content.substring(0, 40) + "..." : content;
return (
<div className="truncate" title={content}>
{truncated}
</div>
);
}
},
{ key: "문의 상태", value: "문의 상태" },
{ key: "작성자", value: "작성자" },
{ key: "문의 작성일", value: "문의 작성일" },
Expand All @@ -31,7 +48,8 @@ export default function Inquiry() {
setInquiryStatus(value);
setCurrentPage(0);
};



const getInquiryData = async () => {
try {
const params = {
Expand Down Expand Up @@ -121,6 +139,54 @@ useEffect(() => {
getInquiryData();
}, [currentPage, inquiryType, inquiryStatus]);

const handleRowClick = (inquiry, originalData) => {
const inquiryData = originalData || inquiry;
setSelectedInquiry(inquiry);
setShowModal(true);
setSelectedStatus(null);
setAnswer("");
};


const handleProcessingChange = (value) => {
setSelectedStatus(value);
};

const handleProcessComplete = async () => {
if (!selectedInquiry || !selectedStatus) {
alert('상태를 선택해주세요.');
return;
}

try {
const inquiryId = selectedInquiry.inquiryId || (selectedInquiry.originalData && selectedInquiry.originalData.inquiryId);
if (!inquiryId) {
alert('문의 ID를 찾을 수 없습니다.');
return;
}

await patchInquiryStatus({
inquiryId: inquiryId,
answer: answer,
status: selectedStatus,
});

alert('문의 답변이 작성되었습니다.');
handleCloseModal();
getInquiryData();
} catch (error) {
console.error('문의 답변 실패:', error);
alert('문의 답변에 실패했습니다.');
}
};

const handleCloseModal = () => {
setShowModal(false);
setSelectedInquiry(null);
setSelectedStatus(null);
setAnswer("");
};

return (
<AdminLayout title="문의"
filters={[
Expand Down Expand Up @@ -152,9 +218,138 @@ useEffect(() => {
]}
>
<div className="bg-white min-h-[550px] flex flex-col justify-between mx-4">
<Table columns={columns} data={paginationData} />
<Table
columns={columns}
data={paginationData}
onRowClick={handleRowClick}
originalData={paginationData.map(item => item.originalData)}
/>
<Pagination currentPage={currentPage} totalPages={totalPages} onPageChange={setCurrentPage} />
</div>

{/* 문의 상세 모달 */}
{showModal && selectedInquiry && (
<div className="fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center" onClick={handleCloseModal}>
<div
className="bg-white rounded-lg shadow-xl w-full max-w-5xl p-6 max-h-[90vh] overflow-y-auto"
onClick={(e) => e.stopPropagation()}
>
<div className="flex justify-between items-center mb-6">
<h2 className="text-2xl font-bold text-gray-800">문의 상세</h2>
<button
onClick={handleCloseModal}
className="text-gray-500 hover:text-gray-700 text-2xl"
>
×
</button>
</div>

<div className="flex gap-6">
{/* 왼쪽: 문의 정보 */}
<div className="flex-1 space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">문의 유형</label>
<div className="p-3 bg-gray-50 rounded border">{selectedInquiry["문의 유형"]}</div>
</div>

<div>
<label className="block text-sm font-medium text-gray-700 mb-2">문의 제목</label>
<div className="p-3 bg-gray-50 rounded border">{selectedInquiry["문의 제목"]}</div>
</div>

<div>
<label className="block text-sm font-medium text-gray-700 mb-2">문의 내용</label>
<div className="p-3 bg-gray-50 rounded border min-h-[100px] whitespace-pre-wrap">
{selectedInquiry["문의 내용"]}
</div>
</div>

<div>
<label className="block text-sm font-medium text-gray-700 mb-2">작성자</label>
<div className="p-3 bg-gray-50 rounded border">{selectedInquiry["작성자"]}</div>
</div>

<div>
<label className="block text-sm font-medium text-gray-700 mb-2">문의 작성일</label>
<div className="p-3 bg-gray-50 rounded border">{selectedInquiry["문의 작성일"]}</div>
</div>

<div>
<label className="block text-sm font-medium text-gray-700 mb-2">문의 상태</label>
<div className="p-3 bg-gray-50 rounded border">{selectedInquiry["문의 상태"]}</div>
</div>

<h3 className="text-lg font-semibold text-gray-800 mb-4">이미지</h3>
<div className="text-gray-500 py-8 text-center">
이미지가 없습니다.
</div>


</div>

{/* 오른쪽: 답변 처리 (답변 대기 상태일 때만 표시) */}
<div className="flex-1 border-l pl-6">
{selectedInquiry["문의 상태"] === "답변 대기" ? (
<>
<h3 className="text-lg font-semibold text-gray-800 mb-4">문의 상태 처리</h3>
<div className="flex flex-col gap-2">
<button
className={`w-full p-3 rounded font-medium transition-colors border focus:ring-2 focus:ring-green-300 ${
selectedStatus === 'PENDING'
? 'bg-gray-400 text-white font-bold border-none'
: 'bg-white text-gray-400 border-gray-400 hover:bg-gray-400 hover:text-white hover:font-bold'
}`}
onClick={() => handleProcessingChange('PENDING')}
>답변 대기</button>
<button
className={`w-full p-3 rounded font-medium transition-colors border focus:ring-2 focus:ring-green-300 ${
selectedStatus === 'RESOLVED'
? 'bg-green-400 text-white font-bold border-none'
: 'bg-white text-green-500 border-green-500 hover:bg-green-300 hover:text-white hover:font-bold'
}`}
onClick={() => handleProcessingChange('RESOLVED')}
>답변 완료</button>
<button
className={`w-full p-3 rounded font-medium transition-colors border focus:ring-2 focus:ring-red-300 ${
selectedStatus === 'REJECTED'
? 'bg-red-400 text-white font-bold border-none'
: 'bg-white text-red-500 border-red-500 hover:bg-red-300 hover:text-white hover:font-bold'
}`}
onClick={() => handleProcessingChange('REJECTED')}
>답변 거절</button>
</div>

<h3 className="text-lg font-semibold text-gray-800 mb-4 mt-6">답변 작성</h3>
<textarea
className="w-full p-3 bg-gray-50 min-h-[200px] rounded border min-h-[100px] whitespace-pre-wrap"
placeholder="답변을 입력해주세요."
value={answer}
onChange={(e) => setAnswer(e.target.value)}
/>
<button
className="bg-blue-main w-full mt-4 text-white font-semibold py-3 px-4 rounded hover:bg-blue-point transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
onClick={handleProcessComplete}
disabled={!selectedStatus}
>
답변 작성
</button>
</>
) : (
<div>
<h3 className="text-lg font-semibold text-gray-800 mb-4">문의 상태</h3>
<div className="p-3 bg-gray-50 rounded border">
{selectedInquiry["문의 상태"]}
</div>
</div>
)}
</div>


</div>

</div>
</div>
)}
</AdminLayout>
);
}
Loading