Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 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;
}
};
22 changes: 14 additions & 8 deletions src/api/alarm.api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { ApiAlarmList } from '../models/alarm';
import useAuthStore from '../store/authStore';
import { httpClient } from './http.api';

export const getAlarmList = async () => {
Expand Down Expand Up @@ -36,14 +37,19 @@ export const patchAlarm = async (id: number) => {
};

export const testLiveAlarm = async () => {
try {
const response = await httpClient.get<ApiAlarmList>(
`/user/send-alarm?alarmFilter=0`
);
const { accessToken } = useAuthStore.getState();
if (accessToken) {
try {
const response = await httpClient.get<ApiAlarmList>(
`/user/send-alarm?alarmFilter=0`
);

return response;
} catch (e) {
console.error(e);
throw e;
return response;
} catch (e) {
console.error(e);
throw e;
}
} else {
return;
}
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

함수의 반환 타입 일관성 개선 필요

인증 토큰 확인 로직 추가는 좋은 보안 관행입니다. 하지만 몇 가지 개선이 필요합니다:

  1. 반환 타입 불일치: 토큰이 있을 때는 response 객체를, 없을 때는 undefined를 반환합니다.
  2. 함수명 개선: testLiveAlarm이라는 이름이 테스트용으로 보이지만 실제 프로덕션 코드에서 사용되고 있습니다.

다음과 같이 개선을 권장합니다:

-export const testLiveAlarm = async () => {
+export const sendUserAlarm = async () => {
   const { accessToken } = useAuthStore.getState();
-  if (accessToken) {
+  if (!accessToken) {
+    throw new Error('인증 토큰이 없습니다.');
+  }
+  
-    try {
-      const response = await httpClient.get<ApiAlarmList>(
-        `/user/send-alarm?alarmFilter=0`
-      );
+  try {
+    const response = await httpClient.get<ApiAlarmList>(
+      `/user/send-alarm?alarmFilter=0`
+    );

-      return response;
-    } catch (e) {
-      console.error(e);
-      throw e;
-    }
-  } else {
-    return;
+    return response;
+  } catch (e) {
+    console.error(e);
+    throw e;
   }
 };
📝 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
const { accessToken } = useAuthStore.getState();
if (accessToken) {
try {
const response = await httpClient.get<ApiAlarmList>(
`/user/send-alarm?alarmFilter=0`
);
return response;
} catch (e) {
console.error(e);
throw e;
return response;
} catch (e) {
console.error(e);
throw e;
}
} else {
return;
}
};
export const sendUserAlarm = async () => {
const { accessToken } = useAuthStore.getState();
if (!accessToken) {
throw new Error('인증 토큰이 없습니다.');
}
try {
const response = await httpClient.get<ApiAlarmList>(
`/user/send-alarm?alarmFilter=0`
);
return response;
} catch (e) {
console.error(e);
throw e;
}
};
🤖 Prompt for AI Agents
In src/api/alarm.api.ts between lines 40 and 55, the function returns a response
object when the access token exists but returns undefined otherwise, causing
inconsistent return types. To fix this, ensure the function always returns a
consistent type, such as a Promise that resolves to the response or null/empty
result when no token is present. Also, rename the function from testLiveAlarm to
a more descriptive name reflecting its purpose in production, like
fetchUserAlarms or getUserAlarms.

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;
`;
Loading