-
Notifications
You must be signed in to change notification settings - Fork 0
관리자 신고 검토 페이지 구현 ( Feat/#327 ) #351
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
1dd2248
028cb0a
3707476
e35773a
26ca2a0
4dcf410
bdf5e66
76e991c
da050aa
55a760c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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'; | ||
| 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; | ||
| } | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,7 +2,7 @@ import { ApiUserApplicantsData } from '../../models/admin/userDetail/userDetail. | |
| import { ApiUserProjectDataResponse } from '../../models/admin/userDetail/userProjectData'; | ||
|
||
| 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) { | ||
|
|
@@ -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) { | ||
|
|
||
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 전역 Date 객체와 이름 충돌
-export const Date = styled.p`
+export const DateText = styled.p`
opacity: 60%;
`;📝 Committable suggestion
Suggested change
🧰 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 |
||||||||||||||
|
|
||||||||||||||
| 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)``; | ||||||||||||||
| 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={[ | ||
| '욕설/비속어', | ||
| '성적내용/음란물', | ||
| '광고/스팸', | ||
| '저작권 침해', | ||
| '기타', | ||
| ]} | ||
| /> | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| <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> | ||
| ); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
type