Skip to content

Commit 1dd2248

Browse files
committed
feat : 관리자 신고 검토 페이지 구현
1 parent c785fa4 commit 1dd2248

File tree

21 files changed

+855
-51
lines changed

21 files changed

+855
-51
lines changed

src/api/admin/report.api.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { ApiReportDetail } from '../../models/admin/userDetail/reportDetail';
2+
import { ApiAllReports } from '../../models/report';
3+
import { SearchType } from '../../models/search';
4+
import { httpClient } from '../http.api';
5+
6+
export const postDeleteReport = async (id: number) => {
7+
try {
8+
await httpClient.delete(`/report/${id}`);
9+
} catch (e) {
10+
console.error(e);
11+
throw e;
12+
}
13+
};
14+
15+
export const getReportDetail = async (reportId: number) => {
16+
try {
17+
const response = await httpClient.get<ApiReportDetail>(
18+
`/report/${reportId}`
19+
);
20+
return response.data.data;
21+
} catch (e) {
22+
console.error(e);
23+
throw e;
24+
}
25+
};
26+
27+
export const getAllReports = async (params: SearchType) => {
28+
try {
29+
const response = await httpClient.get<ApiAllReports>(`/report`, { params });
30+
return response.data.data;
31+
} catch (e) {
32+
console.error(e);
33+
throw e;
34+
}
35+
};
36+
37+
export const getAllReportsPreview = async () => {
38+
try {
39+
const response = await httpClient.get<ApiAllReports>(`/report`);
40+
return response.data.data;
41+
} catch (e) {
42+
console.error(e);
43+
throw e;
44+
}
45+
};

src/api/admin/user.api.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { ApiUserApplicantsData } from '../../models/admin/userDetail/userDetail.
22
import { ApiUserProjectDataResponse } from '../../models/admin/userDetail/userProjectData';
33
import { httpClient } from '../http.api';
44

5-
export const postBanUser = async (id: string) => {
5+
export const postBanUser = async (id: number) => {
66
try {
77
await httpClient.post(`/ban/${id}`);
88
} catch (e) {
@@ -11,7 +11,7 @@ export const postBanUser = async (id: string) => {
1111
}
1212
};
1313

14-
export const postWarningUser = async (id: string) => {
14+
export const postWarningUser = async (id: number) => {
1515
try {
1616
await httpClient.post(`/warning/${id}`);
1717
} catch (e) {

src/api/report.api.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { type ApiAllReports, type ApiPostContent } from '../models/report';
1+
import type { ApiPostContent } from '../models/report';
22
import { httpClient } from './http.api';
33

44
export const postReport = async (formData: ApiPostContent) => {
@@ -13,13 +13,3 @@ export const postReport = async (formData: ApiPostContent) => {
1313
throw error;
1414
}
1515
};
16-
17-
export const getAllReports = async () => {
18-
try {
19-
const response = await httpClient.get<ApiAllReports>(`/reports`);
20-
return response.data.data;
21-
} catch (e) {
22-
console.error(e);
23-
throw e;
24-
}
25-
};
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import styled from 'styled-components';
2+
import { ScrollArea } from '../../common/header/Notification/Notification.styled';
3+
import { SpinnerContainer } from '../../user/mypage/Spinner.styled';
4+
import { Link } from 'react-router-dom';
5+
import Button from '../../common/Button/Button';
6+
7+
export const Container = styled.div`
8+
display: flex;
9+
flex-direction: column;
10+
margin: 8rem 0 auto;
11+
`;
12+
13+
export const Spinner = styled(SpinnerContainer)``;
14+
15+
export const Wrapper = styled.div`
16+
display: flex;
17+
flex-direction: column;
18+
align-items: center;
19+
`;
20+
21+
export const HeaderContainer = styled.div`
22+
width: 80%;
23+
height: 100px;
24+
display: flex;
25+
justify-content: space-around;
26+
align-items: center;
27+
border: 1px solid ${({ theme }) => theme.color.border};
28+
border-radius: ${({ theme }) => theme.borderRadius.primary};
29+
`;
30+
31+
export const UserContainer = styled.div`
32+
display: flex;
33+
flex-direction: column;
34+
align-items: center;
35+
`;
36+
37+
export const NickName = styled.p`
38+
font-size: 0.8rem;
39+
opacity: 40%;
40+
`;
41+
42+
export const Category = styled.p``;
43+
44+
export const Arrow = styled.div`
45+
width: 50px;
46+
height: 50px;
47+
position: relative;
48+
margin-bottom: 30px;
49+
50+
&::before,
51+
&::after {
52+
content: '';
53+
position: absolute;
54+
}
55+
56+
&::before {
57+
width: 45%;
58+
height: 45%;
59+
top: 59%;
60+
right: -10rem;
61+
border: 1.5px solid ${({ theme }) => theme.color.primary};
62+
border-right: 0;
63+
border-bottom: 0;
64+
transform: rotate(130deg);
65+
}
66+
67+
&::after {
68+
width: 24.3rem;
69+
height: 1px;
70+
top: 78%;
71+
left: -11rem;
72+
background-color: ${({ theme }) => theme.color.primary};
73+
transform-origin: 0 100%;
74+
transform: rotate(0deg);
75+
76+
}
77+
}
78+
`;
79+
80+
export const Date = styled.p`
81+
opacity: 60%;
82+
`;
83+
84+
export const ContentContainer = styled.div`
85+
width: 80%;
86+
height: 550px;
87+
display: flex;
88+
flex-direction: column;
89+
align-items: center;
90+
border: 1px solid ${({ theme }) => theme.color.border};
91+
border-radius: ${({ theme }) => theme.borderRadius.primary};
92+
margin-top: 30px;
93+
padding: 40px;
94+
`;
95+
96+
export const ContentHeader = styled.div`
97+
display: flex;
98+
flex-direction: column;
99+
justify-content: center;
100+
align-items: center;
101+
`;
102+
103+
export const Divider = styled.hr`
104+
width: 400px;
105+
height: 1px;
106+
margin: 20px 0 20px 0;
107+
opacity: 40%;
108+
`;
109+
110+
export const Scroll = styled(ScrollArea)`
111+
padding: 20px;
112+
`;
113+
114+
export const Content = styled.p`
115+
white-space: pre-wrap;
116+
`;
117+
118+
export const ConfirmContainer = styled.div`
119+
width: 80%;
120+
display: flex;
121+
justify-content: space-between;
122+
margin-top: 30px;
123+
`;
124+
125+
export const ConfirmArea = styled(Link)`
126+
display: flex;
127+
justify-content: flex-end;
128+
align-items: center;
129+
`;
130+
131+
export const ButtonArea = styled.div`
132+
display: flex;
133+
align-items: center;
134+
gap: 10px;
135+
margin-bottom: 10px;
136+
`;
137+
138+
export const WarningButton = styled(Button)`
139+
background-color: ${({ theme }) => theme.color.red};
140+
`;
141+
142+
export const BanButton = styled(Button)``;
143+
144+
export const ConfirmButton = styled(Button)``;
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
import React from 'react';
2+
import * as S from './AdminReportDetail.styled';
3+
import AdminTitle from '../../common/admin/title/AdminTitle';
4+
import Avatar from '../../common/avatar/Avatar';
5+
import defaultImg from '../../../assets/defaultImg.png';
6+
import ReportCheckBox from '../../common/reportCheckBox/ReportCheckBox';
7+
import ScrollPreventor from '../../common/modal/ScrollPreventor';
8+
import { Link, useParams } from 'react-router-dom';
9+
import { useGetReportDetail } from '../../../hooks/admin/useAdminReportDetail';
10+
import { Spinner } from '../../common/loadingSpinner/LoadingSpinner.styled';
11+
import { formatDate } from '../../../util/formatDate';
12+
import { useHandleUser } from '../../../hooks/admin/useHandleUser';
13+
import { useModal } from '../../../hooks/useModal';
14+
import Modal from '../../common/modal/Modal';
15+
16+
export default function AdminReportDetail() {
17+
const { id: reportId } = useParams();
18+
19+
const { reportDetailData, isLoading, isFetching } = useGetReportDetail(
20+
Number(reportId)
21+
);
22+
23+
const { isOpen, message, handleModalOpen, handleModalClose, handleConfirm } =
24+
useModal();
25+
26+
const { handleClickBanButton, handleClickWarningButton } = useHandleUser({
27+
handleModalOpen,
28+
handleConfirm,
29+
});
30+
31+
if (!reportDetailData) {
32+
return <S.Spinner>신고 상세 정보를 찾을 수 없습니다.</S.Spinner>;
33+
}
34+
35+
if (isLoading || isFetching) {
36+
return (
37+
<S.Spinner>
38+
<Spinner />
39+
</S.Spinner>
40+
);
41+
}
42+
43+
const linkUrl = (
44+
id: number,
45+
location: 'USER' | 'PROJECT' | 'COMMENT' | 'RECOMMENT'
46+
) => {
47+
if (
48+
location === 'PROJECT' ||
49+
location === 'COMMENT' ||
50+
location === 'RECOMMENT'
51+
) {
52+
return `/project-detail/${id}`;
53+
} else {
54+
return `/user/${id}`;
55+
}
56+
};
57+
58+
return (
59+
<ScrollPreventor>
60+
<AdminTitle title='신고 검토 상세' />
61+
<S.Container>
62+
<S.Wrapper>
63+
<S.HeaderContainer>
64+
<S.UserContainer>
65+
<Link to={`/admin/users/${reportDetailData.reporter.userId}`}>
66+
<Avatar
67+
image={
68+
reportDetailData.reporter.img
69+
? reportDetailData.reporter.img
70+
: defaultImg
71+
}
72+
size='50px'
73+
/>
74+
<S.NickName>{reportDetailData.reporter.nickname}</S.NickName>
75+
</Link>
76+
</S.UserContainer>
77+
<S.Arrow>
78+
<S.Category>신고</S.Category>
79+
</S.Arrow>
80+
<S.UserContainer>
81+
<Link to={`/admin/users/${reportDetailData.reportedUser.userId}`}>
82+
<Avatar
83+
image={
84+
reportDetailData.reportedUser.img
85+
? reportDetailData.reportedUser.img
86+
: defaultImg
87+
}
88+
size='47px'
89+
/>
90+
<S.NickName>
91+
{reportDetailData.reportedUser.nickname}
92+
</S.NickName>
93+
</Link>
94+
</S.UserContainer>
95+
</S.HeaderContainer>
96+
<S.ContentContainer>
97+
<S.ContentHeader>
98+
<ReportCheckBox
99+
isAdmin={true}
100+
selectedCheckbox={[
101+
'욕설/비속어',
102+
'성적내용/음란물',
103+
'광고/스팸',
104+
'저작권 침해',
105+
'기타',
106+
]}
107+
/>
108+
<S.Date>{formatDate(reportDetailData.reportedAt)}</S.Date>
109+
</S.ContentHeader>
110+
<S.Divider />
111+
<S.Scroll>
112+
<S.Content>{reportDetailData.reason}</S.Content>
113+
</S.Scroll>
114+
</S.ContentContainer>
115+
<S.ConfirmContainer>
116+
<S.ConfirmArea
117+
to={linkUrl(
118+
reportDetailData.locationId,
119+
reportDetailData.location
120+
)}
121+
target='_blank'
122+
rel='noopener noreferrer'
123+
>
124+
<S.ConfirmButton radius='primary' schema='primary' size='primary'>
125+
검토 하기
126+
</S.ConfirmButton>
127+
</S.ConfirmArea>
128+
<S.ButtonArea>
129+
<S.WarningButton
130+
radius='primary'
131+
schema='primary'
132+
size='primary'
133+
//userId
134+
onClick={(e) =>
135+
handleClickWarningButton(
136+
e,
137+
reportDetailData.reportedUser.userId
138+
)
139+
}
140+
>
141+
경고
142+
</S.WarningButton>
143+
<S.BanButton
144+
radius='primary'
145+
schema='primary'
146+
size='primary'
147+
//userId
148+
onClick={(e) =>
149+
handleClickBanButton(e, reportDetailData.reportedUser.userId)
150+
}
151+
>
152+
강퇴
153+
</S.BanButton>
154+
</S.ButtonArea>
155+
</S.ConfirmContainer>
156+
</S.Wrapper>
157+
</S.Container>
158+
<Modal
159+
isOpen={isOpen}
160+
onClose={handleModalClose}
161+
onConfirm={handleConfirm}
162+
>
163+
{message}
164+
</Modal>
165+
</ScrollPreventor>
166+
);
167+
}

0 commit comments

Comments
 (0)