Skip to content

Commit 56c2eff

Browse files
committed
feat : 신고기능 구현 + 신고상세 데이터바인딩 오류 수정
1 parent 775a265 commit 56c2eff

File tree

8 files changed

+117
-34
lines changed

8 files changed

+117
-34
lines changed

src/apis/admin.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
import client from './client';
22

3+
const postReports = async (postReportRequest: PostReportRequest) => {
4+
try {
5+
const res = await client.post(`/api/reports`, postReportRequest);
6+
if (res.status === 200) {
7+
return res;
8+
}
9+
} catch (error) {
10+
console.error(error);
11+
}
12+
};
13+
314
const getReports = async (reportQueryString: ReportQueryString) => {
415
try {
516
const queryParams = new URLSearchParams();
@@ -19,10 +30,10 @@ const getReports = async (reportQueryString: ReportQueryString) => {
1930
}
2031
};
2132

22-
const patchReport = async (reportId: number, reportRequest: ReportRequest) => {
33+
const patchReport = async (reportId: number, patchReportRequest: PatchReportRequest) => {
2334
try {
24-
console.log(`/api/reports/${reportId}`, reportRequest);
25-
const res = await client.patch(`/api/reports/${reportId}`, reportRequest);
35+
console.log(`/api/reports/${reportId}`, patchReportRequest);
36+
const res = await client.patch(`/api/reports/${reportId}`, patchReportRequest);
2637
console.log(res);
2738
} catch (error) {
2839
console.error(error);
@@ -65,4 +76,4 @@ const patchBadWords = async (
6576
}
6677
};
6778

68-
export { getReports, patchReport, getBadWords, postBadWords, patchBadWords };
79+
export { postReports, getReports, patchReport, getBadWords, postBadWords, patchBadWords };

src/components/ReportModal.tsx

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,91 @@
1-
import { useState } from 'react';
1+
import { useEffect, useState } from 'react';
22
import { twMerge } from 'tailwind-merge';
33

4+
import { postReports } from '@/apis/admin';
5+
46
import ConfirmModal from './ConfirmModal';
57
import TextareaField from './TextareaField';
68

79
interface ReportModalProps {
10+
reportType: ReportType;
11+
letterId: number | null;
812
onClose: () => void;
913
}
1014

11-
const REPORT_REASON = ['욕설', '비방', '폭언', '성희롱', '기타'];
15+
interface ReportReason {
16+
name: string;
17+
type: Reason;
18+
}
19+
const REPORT_REASON: ReportReason[] = [
20+
{ name: '욕설', type: 'ABUSE' },
21+
{ name: '비방', type: 'DEFAMATION' },
22+
{ name: '폭언', type: 'THREATS' },
23+
{ name: '성희롱', type: 'HARASSMENT' },
24+
{ name: '기타', type: 'ETC' },
25+
];
1226

13-
const ReportModal = ({ onClose }: ReportModalProps) => {
14-
const [selected, setSelected] = useState('');
15-
const [additionalReason, setAdditionalReason] = useState('');
27+
const ReportModal = ({ reportType, letterId, onClose }: ReportModalProps) => {
28+
const [postReportRequest, setPostReportRequest] = useState<PostReportRequest>({
29+
reportType: reportType,
30+
reasonType: '',
31+
reason: '',
32+
letterId: letterId,
33+
});
1634

17-
const handleReasonClick = (reason: string) => {
18-
if (selected === reason) setSelected('');
19-
else setSelected(reason);
35+
const handleReasonClick = (reason: Reason) => {
36+
if (postReportRequest.reasonType === reason)
37+
setPostReportRequest((cur) => ({ ...cur, reasonType: '' }));
38+
else setPostReportRequest((cur) => ({ ...cur, reasonType: reason }));
2039
};
2140

22-
const handleSubmit = () => {
23-
onClose();
41+
const handleSubmit = async () => {
42+
const res = await postReports(postReportRequest);
43+
if (res?.status === 200) {
44+
alert('신고 처리되었습니다.');
45+
onClose();
46+
} else if (res?.status === 409) {
47+
alert('신고한 이력이 있습니다.');
48+
onClose();
49+
}
2450
};
2551

52+
useEffect(() => {
53+
if (!postReportRequest.letterId) {
54+
alert('신고 모달을 여는 과정에서 오류가 발생했습니다. 새로고침을 눌러주세요');
55+
onClose();
56+
}
57+
});
58+
2659
return (
2760
<ConfirmModal
2861
title="신고 사유를 선택해주세요"
2962
description="신고한 게시물은 관리자 검토 후 처리됩니다."
3063
cancelText="취소하기"
3164
confirmText="제출하기"
32-
confirmDisabled={selected === ''}
65+
confirmDisabled={postReportRequest.reasonType === ''}
3366
onCancel={onClose}
3467
onConfirm={handleSubmit}
3568
>
3669
<section className="my-6 flex flex-wrap gap-x-2.5 gap-y-2">
37-
{REPORT_REASON.map((reason) => (
70+
{REPORT_REASON.map((reason, idx) => (
3871
<button
72+
key={idx}
3973
type="button"
4074
className={twMerge(
4175
'body-m rounded-full bg-white px-5 py-1.5 text-black',
42-
selected === reason && 'bg-primary-2',
76+
postReportRequest.reasonType === reason.type && 'bg-primary-2',
4377
)}
44-
onClick={() => handleReasonClick(reason)}
78+
onClick={() => handleReasonClick(reason.type)}
4579
>
46-
{reason}
80+
{reason.name}
4781
</button>
4882
))}
4983
</section>
5084
<TextareaField
5185
rows={3}
5286
placeholder="이곳을 눌러 추가 사유를 작성해주세요"
53-
value={additionalReason}
54-
onChange={(e) => setAdditionalReason(e.target.value)}
87+
value={postReportRequest.reason}
88+
onChange={(e) => setPostReportRequest((cur) => ({ ...cur, reason: e.target.value }))}
5589
/>
5690
</ConfirmModal>
5791
);

src/pages/Admin/Report.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,14 @@ export default function ReportManage() {
2626

2727
const [reportQueryString, setReportQueryString] = useState<ReportQueryString>({
2828
reportType: null,
29-
status: null,
29+
status: 'PENDING',
3030
page: '1',
31-
size: null,
31+
size: '3',
3232
});
3333
const handleGetReports = async (reportQueryString: ReportQueryString) => {
3434
const res = await getReports(reportQueryString);
3535
if (res?.status === 200) {
36+
console.log(res.data.data.content);
3637
setReports(res.data.data.content);
3738
setReportPages(() => ({
3839
currentPage: res.data.data.currentPage,
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { ReactNode } from 'react';
2+
import { twMerge } from 'tailwind-merge';
3+
4+
interface DetailSmallBoxFrame {
5+
children: ReactNode;
6+
className?: string;
7+
}
8+
export default function DetailSmallBoxFrame({ children, className }: DetailSmallBoxFrame) {
9+
const frameStyle = twMerge(`w-full rounded-lg border border-[#D6D6D6] px-3 py-4`, className);
10+
return <div className={frameStyle}>{children}</div>;
11+
}

src/pages/Admin/components/ReportDetailModal.tsx

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import DetailFrame from './DetailFrame';
2+
import DetailSmallBoxFrame from './DetailSmallBoxFrame';
23

34
export default function ReportDetailModal({
45
selectedReport,
@@ -11,12 +12,20 @@ export default function ReportDetailModal({
1112
<DetailFrame closeEvent={closeEvent}>
1213
<>
1314
<span className="h2-b">제보 편지 상세 조회</span>
14-
<div className="mt-2.5 w-full rounded-lg border border-[#D6D6D6] px-3 py-4">
15-
<span>{selectedReport?.reasonDetail}</span>
15+
16+
<div className="mt-5 flex flex-col gap-2">
17+
<span className="body-l-sb">신고사유</span>
18+
<DetailSmallBoxFrame className="flex flex-col gap-5">
19+
<span>{selectedReport?.reason}</span>
20+
</DetailSmallBoxFrame>
1621
</div>
17-
<div className="mt-2.5 flex w-full flex-col gap-5 rounded-lg border border-[#D6D6D6] px-3 py-4">
18-
<span className="body-l-b">{selectedReport?.letterDetail.title}</span>
19-
<span>{selectedReport?.letterDetail.content}</span>
22+
23+
<div className="mt-5 flex flex-col gap-2">
24+
<span className="body-l-sb">편지 상세</span>
25+
<DetailSmallBoxFrame className="flex flex-col gap-5">
26+
<span className="body-l-b">{selectedReport?.contentDetail.title}</span>
27+
<span>{selectedReport?.contentDetail.content}</span>
28+
</DetailSmallBoxFrame>
2029
</div>
2130
</>
2231
</DetailFrame>

src/pages/Admin/components/ReportListItem.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export default function ReportListItem({
6262
<span className="admin-list-set basis-2/10">{report.reporterEmail}</span>
6363
<span className="admin-list-set basis-2/10">{report.targetEmail}</span>
6464
<span className="admin-list-set basis-2/10">{`${formattedDate} ${formattedTime}`}</span>
65-
<span className="admin-list-set basis-3/10">{reasonList[report.reason]}</span>
65+
<span className="admin-list-set basis-3/10">{reasonList[report.reasonType]}</span>
6666
</div>
6767
<button
6868
onClick={(e) => {

src/pages/LetterDetail/index.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,13 @@ const LetterDetailPage = () => {
7373
}, [params.id, navigate]);
7474
return (
7575
<>
76-
{reportModalOpen && <ReportModal onClose={() => setReportModalOpen(false)} />}
76+
{reportModalOpen && (
77+
<ReportModal
78+
reportType="LETTER"
79+
letterId={letterDetail ? letterDetail.letterId : null}
80+
onClose={() => setReportModalOpen(false)}
81+
/>
82+
)}
7783
<div
7884
className={twMerge(
7985
`flex grow flex-col gap-3 px-5 pb-7.5`,

src/types/admin.d.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,33 @@ interface ReportPages {
1717
interface Report {
1818
id: number;
1919
reporterEmail: string;
20+
targetId: number;
2021
targetEmail: string;
2122
reportedAt: Date;
23+
reporterId: number;
24+
updatedAt: Date;
2225
letterId: number | null;
2326
comment: string | null;
2427
sharePostId: number | null;
2528
reportType: ReportType;
26-
reason: Reason;
27-
reasonDetail: string | null;
29+
reasonType: Reason;
30+
reason: string;
2831
status: Status;
29-
letterDetail: {
32+
eventCommentId: number;
33+
contentDetail: {
3034
title: string | null;
3135
content: string;
3236
};
3337
}
3438

35-
interface ReportRequest {
39+
interface PostReportRequest {
40+
reportType: ReportType;
41+
reasonType: Reason | '';
42+
reason: string;
43+
letterId: number | null;
44+
}
45+
46+
interface PatchReportRequest {
3647
status: 'RESOLVED' | 'REJECTED';
3748
adminMemo: string;
3849
}

0 commit comments

Comments
 (0)