Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
16 changes: 13 additions & 3 deletions src/components/admin/mainCard/MainCard.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,18 @@ export const ShowAllButton = styled.span`

export const ArrowRight = styled.img``;

export const Line = styled.hr``;
export const Line = styled.hr`
Copy link
Collaborator

Choose a reason for hiding this comment

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

<ContentBorder /> 를 사용할 수 있습니당 ~!

color: ${({ theme }) => theme.color.grey};
`;

export const Wrapper = styled.div``;
export const Wrapper = styled.div`
flex: 1;
display: flex;
`;

export const MainContent = styled.div``;
export const MainContent = styled.div`
display: flex;
flex: 1;
justify-content: center;
align-items: center;
`;
3 changes: 2 additions & 1 deletion src/components/admin/mainCard/MainCard.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as S from './MainCard.styled';
import arrowRight from '../../../assets/ArrowRight.svg';
import React from 'react';
import ContentBorder from '../../common/contentBorder/ContentBorder';

interface MainCardProps {
title: string;
Expand All @@ -20,7 +21,7 @@ const MainCard = ({ title, moreLink, children }: MainCardProps) => {
</S.ShowAllArea>
)}
</S.CardHeader>
<S.Line />
<ContentBorder />
<S.Wrapper>
<S.MainContent>{children}</S.MainContent>
</S.Wrapper>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@ import { Link } from 'react-router-dom';
import styled from 'styled-components';

export const Container = styled.div`
width: 100%;
`;

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

export const Wrapper = styled.div`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@ import * as S from './AllUserPreview.styled';
import Avatar from '../../../common/avatar/Avatar';
import { ADMIN_ROUTE } from '../../../../constants/routes';
import arrow_right from '../../../../assets/ArrowRight.svg';
import LoadingSpinner from '../../../common/loadingSpinner/LoadingSpinner';
import { useGetAllUsersPreview } from '../../../../hooks/admin/useGetAllUsersPreview';
import { Spinner } from '../../../common/loadingSpinner/LoadingSpinner.styled';

const AllUserPreview = () => {
const { allUserData, isLoading, isFetching } = useGetAllUsersPreview();

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

if (!allUserData || allUserData.length === 0) {
Expand All @@ -22,9 +26,9 @@ const AllUserPreview = () => {
{allUserData?.map((user) => (
<S.Wrapper key={user.id}>
<S.UserArea>
<Avatar image={user.user.img} size='40px' />
<Avatar image={user.profileImg} size='40px' />
<S.ContentArea to={`${ADMIN_ROUTE.allUser}/${user.id}`}>
<S.NickName>{user.user.nickname}</S.NickName>
<S.NickName>{user.nickname}</S.NickName>
<S.Email>{user.email}</S.Email>
</S.ContentArea>
</S.UserArea>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { Link } from 'react-router-dom';
import styled from 'styled-components';
import { SpinnerContainer } from '../allUserPreview/AllUserPreview.styled';

export const Container = styled.div`
display: flex;
flex-direction: column;
width: 100%;
`;

export const SpinnerWrapper = styled(SpinnerContainer)``;

export const Wrapper = styled.div`
display: flex;
justify-content: space-between;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,26 @@ import { useGetAllInquiries } from '../../../../hooks/admin/useGetAllInquiries';
import Avatar from '../../../common/avatar/Avatar';
import { ADMIN_ROUTE } from '../../../../constants/routes';
import arrow_right from '../../../../assets/ArrowRight.svg';
import LoadingSpinner from '../../../common/loadingSpinner/LoadingSpinner';
import Spinner from '../../../user/mypage/Spinner';
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Spinner import 경로 개선 필요

관리자 컴포넌트에서 사용자 마이페이지(user/mypage) 디렉토리의 Spinner를 import하고 있습니다. 공통 컴포넌트는 common 디렉토리에서 import하거나, 관리자 전용 Spinner를 사용하는 것이 더 적절합니다.

-import Spinner from '../../../user/mypage/Spinner';
+import { Spinner } from '../../../common/loadingSpinner/LoadingSpinner.styled';

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/components/admin/previewComponent/inquiresPreview/InquiresPreview.tsx at
line 6, the Spinner component is imported from the user/mypage directory, which
is inappropriate for an admin component. Change the import path to use the
Spinner from the common components directory or replace it with an
admin-specific Spinner component if available, ensuring the import reflects
proper component ownership and reuse.


const InquiresPreview = () => {
const { allInquiriesData, isLoading, isFetching } = useGetAllInquiries();

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

if (!allInquiriesData || allInquiriesData.length === 0) {
return <S.Container>등록된 문의가 없습니다.</S.Container>;
}

const previewList = allInquiriesData
? allInquiriesData.length > 6
? allInquiriesData.slice(0, 4)
: allInquiriesData
: [];

return (
<S.Container>
{previewList?.map((inquiry) => (
{allInquiriesData?.map((inquiry) => (
<S.Wrapper key={inquiry.id}>
<S.Content>
{/* <Link to={`${ADMIN_ROUTE.}`} */}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import styled from 'styled-components';
import { SpinnerContainer } from '../allUserPreview/AllUserPreview.styled';

export const Container = styled.div`
display: flex;
flex-direction: column;
padding: 20px;
width: 100%;
`;

export const SpinnerWrapper = styled(SpinnerContainer)``;

export const Wrapper = styled.div`
display: flex;
`;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import React from 'react';
import * as S from './NoticePreview.styled';
import { useGetNotice } from '../../../../hooks/user/useGetNotice';
import line from '../../../../assets/line.svg';
import LoadingSpinner from '../../../common/loadingSpinner/LoadingSpinner';
import { Spinner } from '../../../common/loadingSpinner/LoadingSpinner.styled';

const NoticePreview = () => {
const { noticeData, isLoading, isFetching } = useGetNotice({
Expand All @@ -11,7 +10,11 @@ const NoticePreview = () => {
});

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

if (!noticeData?.notices || noticeData.notices.length === 0) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { Link } from 'react-router-dom';
import styled from 'styled-components';
import { SpinnerContainer } from '../allUserPreview/AllUserPreview.styled';

export const Container = styled.div`
display: flex;
flex-direction: column;
width: 100%;
`;

export const SpinnerWrapper = styled(SpinnerContainer)``;

export const Wrapper = styled.div`
display: flex;
justify-content: space-between;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,22 @@ import { useGetAllReports } from '../../../../hooks/admin/useGetAllReports';
import Avatar from '../../../common/avatar/Avatar';
import arrow_right from '../../../../assets/ArrowRight.svg';
import { ADMIN_ROUTE } from '../../../../constants/routes';
import { Spinner } from '../../../common/loadingSpinner/LoadingSpinner.styled';

const ReportsPreview = () => {
const { allReportsData } = useGetAllReports();
const { allReportsData, isLoading, isFetching } = useGetAllReports();

const previewList = allReportsData
? allReportsData.length > 6
? allReportsData.slice(0, 4)
: allReportsData
: [];
if (isLoading || isFetching) {
return (
<S.SpinnerWrapper>
<Spinner />
</S.SpinnerWrapper>
);
}

return (
<S.Container>
{previewList?.map((report) => (
{allReportsData?.map((report) => (
<S.Wrapper key={report.id}>
<S.ReportArea to={`${ADMIN_ROUTE.reports}/${report.id}`}>
<Avatar image={report.user.img} size='40px' />
Expand Down
4 changes: 2 additions & 2 deletions src/components/admin/userCard/UserCard.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,15 @@ export const TextLabel = styled.label`
export const TextContent = styled.p<{
$userState?: UserState;
}>`
font-size: 14px;
font-size: 13px;
color: ${({ theme, $userState }) =>
$userState === UserState.ONLINE
? theme.color.green
: $userState === UserState.OFFLINE
? theme.color.blue
: $userState === UserState.SUSPENDED
? theme.color.red
: theme.color.white};
: theme.color.primary};
margin-left: 15px;
`;

Expand Down
33 changes: 19 additions & 14 deletions src/components/admin/userCard/UserCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react';
import * as S from './UserCard.styled';
import Avatar from '../../common/avatar/Avatar';
import { type AllUser } from '../../../models/auth';
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 { formatDate } from '../../../util/formatDate';

interface UserCardProps {
userData: AllUser;
Expand All @@ -11,34 +12,38 @@ const UserCard = ({ userData }: UserCardProps) => {
return (
<S.Container>
<S.ProfileHeader>
<Avatar image={userData.user.img} size='46px' />
<S.NickName>{userData.user.nickname}</S.NickName>
<Avatar image={userData.profileImg} size='46px' />
<S.NickName>{userData.nickname}</S.NickName>
</S.ProfileHeader>
<S.MainContentArea>
<S.TextLabel>이메일</S.TextLabel>
<S.TextContent>{userData.email}</S.TextContent>
<S.TextLabel>회원 상태</S.TextLabel>
<S.TextContent $userState={userData.userState}>
{/* <S.TextContent $userState={userData.userState}>
{userData.userState}
</S.TextContent>
</S.TextContent> */}
<S.TextLabel>경고 횟수</S.TextLabel>
<S.TextContent>
{userData.reportedCount === 0
? '없음'
: `${userData.reportedCount}번`}
{userData.warning === 0 ? '없음' : `${userData.warning}번`}
</S.TextContent>
<S.TextLabel>포지션</S.TextLabel>
<S.TextContent>
{userData.position.map((position) => position.name).join(', ')}
{userData.position
? userData.position.map((position) => position.name).join(', ')
: '선택된 포지션이 없습니다.'}
</S.TextContent>
<S.TextLabel>대표 스킬</S.TextLabel>
<S.SkillTagArea>
{userData.skill.map((skillTag) => (
<S.SkillTag key={skillTag.id} src={skillTag.img} />
))}
</S.SkillTagArea>
{userData.skill ? (
<S.SkillTagArea>
{userData.skill.map((skillTag) => (
<S.SkillTag key={skillTag.id} src={skillTag.img} />
))}
</S.SkillTagArea>
) : (
<S.TextContent>선택된 스킬이 없습니다.</S.TextContent>
)}
<S.TextLabel>계정 생성 날짜</S.TextLabel>
<S.TextContent>{userData.createdAt}</S.TextContent>
<S.TextContent>{formatDate(userData.createdAt)}</S.TextContent>
</S.MainContentArea>
</S.Container>
);
Expand Down
1 change: 1 addition & 0 deletions src/hooks/admin/useGetAllInquiries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const useGetAllInquiries = () => {
} = useQuery({
queryKey: [Inquiries.allInquiries],
queryFn: () => getAllInquiries(),
select: (allInquiries) => allInquiries.slice(0, 5),
Copy link
Collaborator

Choose a reason for hiding this comment

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

옹 처음 보는거네요 ✨

});

return { allInquiriesData, isLoading, isFetching };
Expand Down
9 changes: 7 additions & 2 deletions src/hooks/admin/useGetAllReports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@ import { ReportData } from '../queries/user/keys';
import { getAllReports } from '../../api/report.api';

export const useGetAllReports = () => {
const { data: allReportsData, isLoading } = useQuery({
const {
data: allReportsData,
isLoading,
isFetching,
} = useQuery({
queryKey: [ReportData.allReports],
queryFn: () => getAllReports(),
select: (allReports) => allReports.slice(0, 5),
});

return { allReportsData, isLoading };
return { allReportsData, isLoading, isFetching };
};
33 changes: 33 additions & 0 deletions src/mock/adminMock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { http, HttpResponse, passthrough } from 'msw';
import mockReports from './mockReports.json';
import mockUsers from './mockUsers.json';
import mockAllUsers from './mockAllUser.json';

export const reportsAll = http.get(
`${import.meta.env.VITE_APP_API_BASE_URL}reports`,
() => {
return HttpResponse.json(mockReports, { status: 200 });
}
);

export const userAllPreview = http.get(
`${import.meta.env.VITE_APP_API_BASE_URL}users/preview`,
() => {
return HttpResponse.json(mockUsers, { status: 200 });
}
);

export const userAll = http.get(
`${import.meta.env.VITE_APP_API_BASE_URL}users`,
() => {
return HttpResponse.json(mockAllUsers, { status: 200 });
}
);
Comment on lines +6 to +25
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

API 엔드포인트 mock 구현 승인

세 가지 주요 API 엔드포인트에 대한 mock 핸들러가 올바르게 구현되었습니다:

  • /reports: 신고 전체 조회
  • /users/preview: 회원 미리보기
  • /users: 회원 전체 조회

환경변수를 사용한 base URL 설정과 적절한 HTTP 상태 코드(200) 반환이 좋습니다.

Mock 데이터가 업데이트된 인터페이스와 일치하는지 확인이 필요합니다.


🏁 Script executed:

#!/bin/bash
# Mock 데이터 파일 구조 확인
echo "=== mockUsers.json 구조 확인 ==="
cat src/mock/mockUsers.json | jq '.' | head -20

echo "=== mockAllUser.json 구조 확인 ==="
cat src/mock/mockAllUser.json | jq '.data.users[0]' 2>/dev/null || cat src/mock/mockAllUser.json | jq '.data' | head -10

Length of output: 618


🏁 Script executed:

#!/bin/bash
echo "=== src/mock 디렉터리 내 JSON 파일 목록 ==="
find src/mock -maxdepth 1 -type f -name '*.json' -printf "%f\n"

echo ""
echo "=== mockAllUsers.json 최상위 키 확인 ==="
jq 'keys' src/mock/mockAllUsers.json

echo ""
echo "=== mockAllUsers.json data 필드 구조 확인 ==="
jq '.data' src/mock/mockAllUsers.json | head -20

Length of output: 1058


🏁 Script executed:

#!/bin/bash
echo "=== mockAllUsers 참조 확인 ==="
grep -R "mockAllUsers" -n src/mock

echo ""
echo "=== mockAllUser 참조 확인 ==="
grep -R "mockAllUser" -n src/mock

echo ""
echo "=== src/mock/adminMock.ts 전체 내용 ==="
sed -n '1,200p' src/mock/adminMock.ts

Length of output: 1512


#!/bin/bash
echo "=== mockAllUser.json 구조 확인 (최상위 50줄) ==="
cat src/mock/mockAllUser.json | jq '.' | head -50

echo ""
echo "=== mockAllUser.json 최상위 키 ==="
jq 'keys' src/mock/mockAllUser.json

echo ""
echo "=== data 필드 구조 ==="
jq '.data' src/mock/mockAllUser.json | head -20

🏁 Script executed:

#!/bin/bash
echo "=== mockAllUser.json 구조 확인 (상위 50줄) ==="
cat src/mock/mockAllUser.json | jq '.' | head -50

Length of output: 1339


API 엔드포인트 mock 구현 승인 및 mockAllUser.json 구조 검증 요청

세 가지 주요 API 엔드포인트에 대한 mock 핸들러가 올바르게 구현되었습니다.

  • /reports: 신고 전체 조회
  • /users/preview: 회원 미리보기
  • /users: 회원 전체 조회
    환경변수를 사용한 base URL 설정과 HTTP 200 상태 코드 반환 역시 적절합니다.

하지만 src/mock/mockAllUser.json의 응답 형태가 아래와 같이

{
  "success": true,
  "message": "성공",
  "data": {
    "allUsers": [ /* 사용자 배열 */ ]
  }
}

로, data가 객체로 래핑되고 allUsers 키를 사용하고 있습니다.
클라이언트 측 타입 정의 및 실제 API 인터페이스가 이 구조와 일치하는지 확인이 필요합니다.

  • 예상 인터페이스가 data에 바로 사용자 배열을 담는 형태라면 mockAllUser.json

    - "data": { "allUsers": [ … ] }
    + "data": [ … ]

    와 같이 수정하거나,

  • 타입 정의를 { data: { allUsers: User[] } } 형태로 업데이트해야 합니다.

  • 위치: src/mock/mockAllUser.json

🤖 Prompt for AI Agents
In src/mock/adminMock.ts around lines 6 to 25, the mock handler for the /users
endpoint returns mockAllUsers which currently has a structure where the user
array is nested inside data.allUsers. To fix this, either update the
mockAllUsers data to directly be the user array without the wrapping object, or
adjust the client-side type definitions and API interface to expect an object
with a data property containing an allUsers array. Ensure the mock response and
client types are consistent with each other.


export const passthroughAllGet = http.get(`*`, () => {
return passthrough();
});

export const passthroughAllPost = http.post(`*`, () => {
return passthrough();
});
Loading