Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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: 15 additions & 1 deletion src/api/activityLog.api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import type { ApiMyComments, ApiMyInquiries } from './../models/activityLog';
import type {
ApiAllInquiries,
Copy link
Collaborator

Choose a reason for hiding this comment

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

ApiAllInquiries 같은 경우는 activityLog가 아니기 때문에 디렉토리 분리를 하는게 더 좋지 않을까 싶습니당,

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

그럼 admin으로 하나를 더 만들게요

ApiMyComments,
ApiMyInquiries,
} from './../models/activityLog';
import { httpClient } from './http.api';

export const getMyComments = async () => {
Expand All @@ -22,3 +26,13 @@ export const getMyInquiries = async () => {
throw e;
}
};

export const getAllInquiries = async () => {
try {
const response = await httpClient.get<ApiAllInquiries>(`/inquiry`);
return response.data.data;
} catch (e) {
console.error('전체 문의 조회 에러', e);
throw e;
}
};
17 changes: 16 additions & 1 deletion src/api/auth.api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import type { ApiOauth, ApiVerifyNickname, VerifyEmail } from '../models/auth';
import {
ApiGetAllUsers,
type ApiOauth,
type ApiVerifyNickname,
type VerifyEmail,
} from '../models/auth';
import { httpClient } from './http.api';
import { loginFormValues } from '../pages/login/Login';
import { registerFormValues } from '../pages/user/register/Register';
Expand Down Expand Up @@ -100,3 +105,13 @@ export const getOauthLogin = async (oauthAccessToken: string) => {
throw e;
}
};

export const getAllUsers = async () => {
try {
const response = await httpClient.get<ApiGetAllUsers>(`/users`);
return response.data.data;
} catch (e) {
console.error(e);
throw e;
}
};
12 changes: 11 additions & 1 deletion src/api/report.api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ApiPostContent } from '../models/report';
import { ApiAllReports, type ApiPostContent } from '../models/report';
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 postReport = async (formData: ApiPostContent) => {
Expand All @@ -13,3 +13,13 @@ 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;
}
};
4 changes: 3 additions & 1 deletion src/components/admin/mainCard/graphCard/GraphCard.styled.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import styled from 'styled-components';

export const Container = styled.div``;
export const Container = styled.div`
height: 350px;
`;
91 changes: 90 additions & 1 deletion src/components/admin/mainCard/graphCard/GraphCard.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,97 @@
import React from 'react';
import * as S from './GraphCard.styled';
import { Line } from 'react-chartjs-2';
import 'chart.js/auto';
import { ChartData, ChartOptions } from 'chart.js';

const GraphCard = () => {
return <S.Container>GraphCard Component</S.Container>;
return (
<S.Container>
<Line data={data} options={options} />
</S.Container>
);
};

const data: ChartData<'line'> = {
labels: [
'월요일',
'화요일',
'수요일',
'목요일',
'금요일',
'토요일',
'일요일',
],
datasets: [
{
label: '방문자 수',
data: [8, 6, 4, 0, 7, 5, 3],
fill: true,
backgroundColor: 'rgba(54, 162, 235, 0.2)',
borderColor: 'rgba(54, 162, 235, 1)',
borderWidth: 2,
tension: 0.3,
pointRadius: 5,
pointBackgroundColor: '#ffffff',
pointBorderColor: 'rgba(54, 162, 235, 1)',
pointBorderWidth: 2,
pointHoverRadius: 6,
},
],
};

const options: ChartOptions<'line'> = {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false,
},
tooltip: {
enabled: true,
},
title: {
display: false,
},
},
scales: {
x: {
grid: {
display: true,
borderDash: [5, 5],
color: 'rgba(0,0,0,0.1)',
},
ticks: {
font: {
size: 14,
},
color: '#333',
},
},
y: {
grid: {
display: true,
drawBorder: false,

borderDash: [5, 5],
color: 'rgba(0,0,0,0.1)',
},
ticks: {
stepSize: 2,
font: {
size: 14,
},
color: '#333',
callback: (value) => `${value}`,
},
beginAtZero: true,
suggestedMax: 8,
},
},
animation: {
duration: 800,
easing: 'easeOutQuart',
},
};

export default GraphCard;
Original file line number Diff line number Diff line change
@@ -1,3 +1,44 @@
import { Link } from 'react-router-dom';
import styled from 'styled-components';

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

export const Wrapper = styled.div`
display: flex;
justify-content: space-between;
padding: 10px;
`;

export const UserArea = styled.div`
display: flex;
`;

export const ContentArea = styled(Link)`
margin-left: 16px;
`;

export const NickName = styled.p`
font-size: 14px;
`;

export const Email = styled.p`
font-size: 9px;
opacity: 50%;
`;

export const MoveToUsersArea = styled(Link)`
display: flex;
align-items: center;
`;

export const Text = styled.p`
font-size: 9px;
`;

export const Arrow = styled.img`
width: 10px;
height: 10px;
`;
Original file line number Diff line number Diff line change
@@ -1,8 +1,38 @@
import React from 'react';
import * as S from './AllUserPreview.styled';
import { useGetAllUsers } from '../../../../hooks/admin/useGetAllUsers';
import Avatar from '../../../common/avatar/Avatar';
import { ADMIN_ROUTE } from '../../../../constants/routes';
import arrow_right from '../../../../assets/ArrowRight.svg';

const AllUserPreview = () => {
return <S.Container>AllUserPreview Component</S.Container>;
const { allUserData, isLoading } = useGetAllUsers();
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

쿼리 키 불일치 문제 확인 필요

useGetAllUsers 훅에서 잘못된 쿼리 키를 사용하고 있는 것으로 보입니다. 관련 코드 스니펫(src/hooks/admin/useGetAllUsers.ts)에서 ReportData.allReports를 쿼리 키로 사용하고 있는데, 이는 사용자 데이터를 가져오는 훅에서는 적절하지 않습니다.

다음 스크립트를 실행하여 쿼리 키가 올바른지 확인해주세요:


🏁 Script executed:

#!/bin/bash
# useGetAllUsers 훅의 쿼리 키 확인
rg -A 5 -B 5 "queryKey.*ReportData" src/hooks/admin/useGetAllUsers.ts

# 올바른 쿼리 키 상수 확인
rg "allUsers\|AllUsers\|ALL_USERS" src/constants/

Length of output: 437


다음 스크립트를 실행하여 src/queries/user/keys.ts에 정의된 쿼리 키들을 확인해주세요:


🏁 Script executed:

#!/bin/bash
# src/queries/user/keys.ts의 쿼리 키 정의 확인
rg -n "export const" src/queries/user/keys.ts

Length of output: 112


다음 스크립트를 실행하여 쿼리 키가 정의된 파일을 찾아주세요:


🏁 Script executed:

#!/bin/bash
# 쿼리 키 정의가 담긴 keys 파일 위치 확인
find src -type f -iname "*keys.ts"

Length of output: 67


다음 스크립트를 실행하여 src/hooks/queries/user/keys.ts의 전체 내용을 확인해주세요:


🏁 Script executed:

#!/bin/bash
# user 쿼리 키 정의 확인
sed -n '1,200p' src/hooks/queries/user/keys.ts

Length of output: 1375


useGetAllUsers 훅의 쿼리 키 수정 필요

useGetAllUsers 훅에서 현재 리포트 전용 키인 ReportData.allReports를 사용하고 있습니다.
사용자 목록을 캐싱/식별하기 위해서는 별도의 사용자 전용 쿼리 키를 정의하고 사용해야 합니다.

조치 사항:

  • src/hooks/queries/user/keys.ts에 사용자 목록용 키를 추가
    예)
    export const userListKey = {
      allUsers: ['allUsers'],
    } as const;
  • src/hooks/admin/useGetAllUsers.ts에서 쿼리 키를 userListKey.allUsers로 변경

변경 예시:

- queryKey: [ReportData.allReports],
+ queryKey: [userListKey.allUsers],

– 대상 파일:
• src/hooks/admin/useGetAllUsers.ts
• src/hooks/queries/user/keys.ts

🤖 Prompt for AI Agents
In src/components/admin/previewComponent/allUserPreview/AllUserPreview.tsx at
line 9, the useGetAllUsers hook is using an incorrect query key intended for
report data. To fix this, define a proper user-specific query key in
src/hooks/queries/user/keys.ts, for example, export a constant userListKey with
allUsers as the key. Then update src/hooks/admin/useGetAllUsers.ts to use this
new userListKey.allUsers instead of ReportData.allReports to correctly cache and
identify user data queries.


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

return (
<S.Container>
{previewList?.map((user) => (
<S.Wrapper key={user.id}>
<S.UserArea>
<Avatar image={undefined} size='40px' />
<S.ContentArea to={`${ADMIN_ROUTE.allUser}/${user.id}`}>
<S.NickName>{user.user.nickname}</S.NickName>
<S.Email>{user.email}</S.Email>
</S.ContentArea>
</S.UserArea>
<S.MoveToUsersArea to={`${ADMIN_ROUTE.allUser}/${user.id}`}>
<S.Text>상세 보기</S.Text>
<S.Arrow src={arrow_right} />
</S.MoveToUsersArea>
</S.Wrapper>
))}
</S.Container>
);
};

export default AllUserPreview;
Original file line number Diff line number Diff line change
@@ -1,3 +1,69 @@
import { Link } from 'react-router-dom';
import styled from 'styled-components';

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

export const Wrapper = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
`;

export const Content = styled.div`
display: flex;
`;

export const Inquiry = styled(Link)`
margin-left: 16px;
`;

export const Category = styled.p`
font-size: 9px;
opacity: 50%;
`;

export const Title = styled.p`
font-size: 13px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
`;

export const StateArea = styled.div`
display: flex;
`;

export const Date = styled.p`
font-size: 9px;
opacity: 50%;
`;

export const Divider = styled.p`
font-size: 9px;
opacity: 20%;
margin-left: 3px;
margin-right: 3px;
`;

export const InquiryState = styled.p<{ $isCompleted: boolean }>`
font-size: 9px;
color: ${({ $isCompleted }) => ($isCompleted ? `#07DE00` : `#DE1A00`)};
`;

export const MoveToInquiryArea = styled(Link)`
display: flex;
font-size: 9px;
`;

export const Text = styled.p`
font-size: 9px;
`;

export const Arrow = styled.img`
width: 11px;
height: 11px;
`;
Original file line number Diff line number Diff line change
@@ -1,8 +1,45 @@
import React from 'react';
import * as S from './InquiresPreview.styled';
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';

const InquiresPreview = () => {
return <S.Container>InquiresPreview Component</S.Container>;
const { allInquiriesData } = useGetAllInquiries();

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

return (
<S.Container>
{previewList?.map((inquiry) => (
<S.Wrapper key={inquiry.id}>
<S.Content>
{/* <Link to={`${ADMIN_ROUTE.}`} */}
<Avatar image={inquiry.user.img} size='40px' />
<S.Inquiry to={`${ADMIN_ROUTE.inquiries}/${inquiry.id}`}>
<S.Category>{inquiry.category}</S.Category>
<S.Title>{inquiry.title}</S.Title>
<S.StateArea>
<S.Date>{inquiry.createdAt}</S.Date>
<S.Divider>|</S.Divider>
<S.InquiryState $isCompleted={inquiry.state}>
{inquiry.state ? '답변 완료' : '답변 대기 중'}
</S.InquiryState>
</S.StateArea>
</S.Inquiry>
</S.Content>
<S.MoveToInquiryArea to={`${ADMIN_ROUTE.inquiries}/${inquiry.id}`}>
<S.Text>상세 보기</S.Text>
<S.Arrow src={arrow_right} />
</S.MoveToInquiryArea>
</S.Wrapper>
))}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

에러 상태 처리 누락

API 요청이 실패했을 때의 에러 상태 처리가 누락되어 있습니다. 사용자에게 적절한 피드백을 제공해야 합니다.

useGetAllInquiries 훅에서 에러 상태를 가져와서 처리하거나, 빈 배열일 때 적절한 메시지를 표시해야 합니다:

      {previewList?.map((inquiry) => (
        ...
      ))}
+      {!isLoading && previewList.length === 0 && (
+        <div>표시할 문의가 없습니다.</div>
+      )}
📝 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
{previewList?.map((inquiry) => (
<S.Wrapper key={inquiry.id}>
<S.Content>
{/* <Link to={`${ADMIN_ROUTE.}`} */}
<Avatar image={inquiry.user.img} size='40px' />
<S.Inquiry to={`${ADMIN_ROUTE.inquiries}/${inquiry.id}`}>
<S.Category>{inquiry.category}</S.Category>
<S.Title>{inquiry.title}</S.Title>
<S.StateArea>
<S.Date>{inquiry.createdAt}</S.Date>
<S.Divider>|</S.Divider>
<S.InquiryState $isCompleted={inquiry.state}>
{inquiry.state ? '답변 완료' : '답변 대기 중'}
</S.InquiryState>
</S.StateArea>
</S.Inquiry>
</S.Content>
<S.MoveToInquiryArea to={`${ADMIN_ROUTE.inquiries}/${inquiry.id}`}>
<S.Text>상세 보기</S.Text>
<S.Arrow src={arrow_right} />
</S.MoveToInquiryArea>
</S.Wrapper>
))}
{previewList?.map((inquiry) => (
<S.Wrapper key={inquiry.id}>
<S.Content>
{/* <Link to={`${ADMIN_ROUTE.}`} */}
<Avatar image={inquiry.user.img} size='40px' />
<S.Inquiry to={`${ADMIN_ROUTE.inquiries}/${inquiry.id}`}>
<S.Category>{inquiry.category}</S.Category>
<S.Title>{inquiry.title}</S.Title>
<S.StateArea>
<S.Date>{inquiry.createdAt}</S.Date>
<S.Divider>|</S.Divider>
<S.InquiryState $isCompleted={inquiry.state}>
{inquiry.state ? '답변 완료' : '답변 대기 중'}
</S.InquiryState>
</S.StateArea>
</S.Inquiry>
</S.Content>
<S.MoveToInquiryArea to={`${ADMIN_ROUTE.inquiries}/${inquiry.id}`}>
<S.Text>상세 보기</S.Text>
<S.Arrow src={arrow_right} />
</S.MoveToInquiryArea>
</S.Wrapper>
))}
{!isLoading && previewList.length === 0 && (
<div>표시할 문의가 없습니다.</div>
)}
🤖 Prompt for AI Agents
In src/components/admin/previewComponent/inquiresPreview/InquiresPreview.tsx
around lines 18 to 40, the code lacks error handling for failed API requests
when fetching inquiries. Update the component to use the error state from the
useGetAllInquiries hook and conditionally render an appropriate error message or
fallback UI when an error occurs or when the inquiry list is empty, ensuring
users receive clear feedback on the data loading status.

</S.Container>
);
};

export default InquiresPreview;
Loading