Skip to content
Merged
45 changes: 45 additions & 0 deletions src/api/admin/report.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { ApiReportDetail } from '../../models/admin/userDetail/reportDetail';
import { ApiAllReports } from '../../models/report';
import { SearchType } from '../../models/search';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

type

import { httpClient } from '../http.api';

export const postDeleteReport = async (id: number) => {
try {
await httpClient.delete(`/report/${id}`);
} catch (e) {
console.error(e);
throw e;
}
};

export const getReportDetail = async (reportId: number) => {
try {
const response = await httpClient.get<ApiReportDetail>(
`/report/${reportId}`
);
return response.data.data;
} catch (e) {
console.error(e);
throw e;
}
};

export const getAllReports = async (params: SearchType) => {
try {
const response = await httpClient.get<ApiAllReports>(`/report`, { params });
return response.data;
} catch (e) {
console.error(e);
throw e;
}
};

export const getAllReportsPreview = async () => {
try {
const response = await httpClient.get<ApiAllReports>(`/report`);
return response.data.data;
} catch (e) {
console.error(e);
throw e;
}
};
4 changes: 2 additions & 2 deletions src/api/admin/user.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ApiUserApplicantsData } from '../../models/admin/userDetail/userDetail.
import { ApiUserProjectDataResponse } from '../../models/admin/userDetail/userProjectData';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

type

import { httpClient } from '../http.api';

export const postBanUser = async (id: string) => {
export const postBanUser = async (id: number) => {
try {
await httpClient.post(`/ban/${id}`);
} catch (e) {
Expand All @@ -11,7 +11,7 @@ export const postBanUser = async (id: string) => {
}
};

export const postWarningUser = async (id: string) => {
export const postWarningUser = async (id: number) => {
try {
await httpClient.post(`/warning/${id}`);
} catch (e) {
Expand Down
12 changes: 1 addition & 11 deletions src/api/report.api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type ApiAllReports, type ApiPostContent } from '../models/report';
import type { ApiPostContent } from '../models/report';
import { httpClient } from './http.api';

export const postReport = async (formData: ApiPostContent) => {
Expand All @@ -13,13 +13,3 @@ export const postReport = async (formData: ApiPostContent) => {
throw error;
}
};

export const getAllReports = async () => {
try {
const response = await httpClient.get<ApiAllReports>(`/reports`);
return response.data.data;
} catch (e) {
console.error(e);
throw e;
}
};
144 changes: 144 additions & 0 deletions src/components/admin/adminUserReport/AdminReportDetail.styled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import styled from 'styled-components';
import { ScrollArea } from '../../common/header/Notification/Notification.styled';
import { SpinnerContainer } from '../../user/mypage/Spinner.styled';
import { Link } from 'react-router-dom';
import Button from '../../common/Button/Button';

export const Container = styled.div`
display: flex;
flex-direction: column;
margin: 8rem 0 auto;
`;

export const Spinner = styled(SpinnerContainer)``;

export const Wrapper = styled.div`
display: flex;
flex-direction: column;
align-items: center;
`;

export const HeaderContainer = styled.div`
width: 80%;
height: 100px;
display: flex;
justify-content: space-around;
align-items: center;
border: 1px solid ${({ theme }) => theme.color.border};
border-radius: ${({ theme }) => theme.borderRadius.primary};
`;

export const UserContainer = styled.div`
display: flex;
flex-direction: column;
align-items: center;
`;

export const NickName = styled.p`
font-size: 0.8rem;
opacity: 40%;
`;

export const Category = styled.p``;

export const Arrow = styled.div`
width: 50px;
height: 50px;
position: relative;
margin-bottom: 30px;

&::before,
&::after {
content: '';
position: absolute;
}

&::before {
width: 45%;
height: 45%;
top: 59%;
right: -10rem;
border: 1.5px solid ${({ theme }) => theme.color.primary};
border-right: 0;
border-bottom: 0;
transform: rotate(130deg);
}

&::after {
width: 24.3rem;
height: 1px;
top: 78%;
left: -11rem;
background-color: ${({ theme }) => theme.color.primary};
transform-origin: 0 100%;
transform: rotate(0deg);

}
}
`;

export const Date = styled.p`
opacity: 60%;
`;
Comment on lines +80 to +82
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

전역 Date 객체와 이름 충돌

Date라는 이름이 JavaScript의 전역 Date 객체와 충돌합니다. 다른 이름으로 변경해야 합니다.

-export const Date = styled.p`
+export const DateText = styled.p`
  opacity: 60%;
`;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const Date = styled.p`
opacity: 60%;
`;
export const DateText = styled.p`
opacity: 60%;
`;
🧰 Tools
🪛 Biome (1.9.4)

[error] 80-80: Do not shadow the global "Date" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)

🤖 Prompt for AI Agents
In src/components/admin/adminUserReport/AdminReportDetail.styled.ts around lines
80 to 82, the styled component is named Date, which conflicts with the global
JavaScript Date object. Rename this styled component to a different,
non-conflicting name such as StyledDate or DateText to avoid naming collisions.


export const ContentContainer = styled.div`
width: 80%;
height: 550px;
display: flex;
flex-direction: column;
align-items: center;
border: 1px solid ${({ theme }) => theme.color.border};
border-radius: ${({ theme }) => theme.borderRadius.primary};
margin-top: 30px;
padding: 40px;
`;

export const ContentHeader = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
`;

export const Divider = styled.hr`
width: 400px;
height: 1px;
margin: 20px 0 20px 0;
opacity: 40%;
`;

export const Scroll = styled(ScrollArea)`
padding: 20px;
`;

export const Content = styled.p`
white-space: pre-wrap;
`;

export const ConfirmContainer = styled.div`
width: 80%;
display: flex;
justify-content: space-between;
margin-top: 30px;
`;

export const ConfirmArea = styled(Link)`
display: flex;
justify-content: flex-end;
align-items: center;
`;

export const ButtonArea = styled.div`
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 10px;
`;

export const WarningButton = styled(Button)`
background-color: ${({ theme }) => theme.color.red};
`;

export const BanButton = styled(Button)``;

export const ConfirmButton = styled(Button)``;
167 changes: 167 additions & 0 deletions src/components/admin/adminUserReport/AdminReportDetail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import React from 'react';
import * as S from './AdminReportDetail.styled';
import AdminTitle from '../../common/admin/title/AdminTitle';
import Avatar from '../../common/avatar/Avatar';
import defaultImg from '../../../assets/defaultImg.png';
import ReportCheckBox from '../../common/reportCheckBox/ReportCheckBox';
import ScrollPreventor from '../../common/modal/ScrollPreventor';
import { Link, useParams } from 'react-router-dom';
import { useGetReportDetail } from '../../../hooks/admin/useAdminReportDetail';
import { Spinner } from '../../common/loadingSpinner/LoadingSpinner.styled';
import { formatDate } from '../../../util/formatDate';
import { useHandleUser } from '../../../hooks/admin/useHandleUser';
import { useModal } from '../../../hooks/useModal';
import Modal from '../../common/modal/Modal';

export default function AdminReportDetail() {
const { id: reportId } = useParams();

const { reportDetailData, isLoading, isFetching } = useGetReportDetail(
Number(reportId)
);

const { isOpen, message, handleModalOpen, handleModalClose, handleConfirm } =
useModal();

const { handleClickBanButton, handleClickWarningButton } = useHandleUser({
handleModalOpen,
handleConfirm,
});

if (!reportDetailData) {
return <S.Spinner>신고 상세 정보를 찾을 수 없습니다.</S.Spinner>;
}

if (isLoading || isFetching) {
return (
<S.Spinner>
<Spinner />
</S.Spinner>
);
}

const linkUrl = (
id: number,
location: 'USER' | 'PROJECT' | 'COMMENT' | 'RECOMMENT'
) => {
if (
location === 'PROJECT' ||
location === 'COMMENT' ||
location === 'RECOMMENT'
) {
return `/project-detail/${id}`;
} else {
return `/user/${id}`;
}
};

return (
<ScrollPreventor>
<AdminTitle title='신고 검토 상세' />
<S.Container>
<S.Wrapper>
<S.HeaderContainer>
<S.UserContainer>
<Link to={`/admin/users/${reportDetailData.reporter.userId}`}>
<Avatar
image={
reportDetailData.reporter.img
? reportDetailData.reporter.img
: defaultImg
}
size='50px'
/>
<S.NickName>{reportDetailData.reporter.nickname}</S.NickName>
</Link>
</S.UserContainer>
<S.Arrow>
<S.Category>신고</S.Category>
</S.Arrow>
<S.UserContainer>
<Link to={`/admin/users/${reportDetailData.reportedUser.userId}`}>
<Avatar
image={
reportDetailData.reportedUser.img
? reportDetailData.reportedUser.img
: defaultImg
}
size='47px'
/>
<S.NickName>
{reportDetailData.reportedUser.nickname}
</S.NickName>
</Link>
</S.UserContainer>
</S.HeaderContainer>
<S.ContentContainer>
<S.ContentHeader>
<ReportCheckBox
isAdmin={true}
selectedCheckbox={[
'욕설/비속어',
'성적내용/음란물',
'광고/스팸',
'저작권 침해',
'기타',
]}
/>
<S.Date>{formatDate(reportDetailData.reportedAt)}</S.Date>
</S.ContentHeader>
<S.Divider />
<S.Scroll>
<S.Content>{reportDetailData.reason}</S.Content>
</S.Scroll>
</S.ContentContainer>
<S.ConfirmContainer>
<S.ConfirmArea
to={linkUrl(
reportDetailData.locationId,
reportDetailData.location
)}
target='_blank'
rel='noopener noreferrer'
>
<S.ConfirmButton radius='primary' schema='primary' size='primary'>
검토 하기
</S.ConfirmButton>
</S.ConfirmArea>
<S.ButtonArea>
<S.WarningButton
radius='primary'
schema='primary'
size='primary'
//userId
onClick={(e) =>
handleClickWarningButton(
e,
reportDetailData.reportedUser.userId
)
}
>
경고
</S.WarningButton>
<S.BanButton
radius='primary'
schema='primary'
size='primary'
//userId
onClick={(e) =>
handleClickBanButton(e, reportDetailData.reportedUser.userId)
}
>
강퇴
</S.BanButton>
</S.ButtonArea>
</S.ConfirmContainer>
</S.Wrapper>
</S.Container>
<Modal
isOpen={isOpen}
onClose={handleModalClose}
onConfirm={handleConfirm}
>
{message}
</Modal>
</ScrollPreventor>
);
}
Loading