Skip to content

Commit 02635be

Browse files
authored
Merge pull request #328 from devpalsPlus/feat/#318
관리자 메인 페이지 구현 ( issue #318 )
2 parents 425dcba + 9b42294 commit 02635be

38 files changed

+890
-87
lines changed

src/api/activityLog.api.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { ApiAllInquiries } from '../models/admin/mainPreview';
12
import type { ApiMyComments, ApiMyInquiries } from './../models/activityLog';
23
import { httpClient } from './http.api';
34

@@ -22,3 +23,13 @@ export const getMyInquiries = async () => {
2223
throw e;
2324
}
2425
};
26+
27+
export const getAllInquiries = async () => {
28+
try {
29+
const response = await httpClient.get<ApiAllInquiries>(`/inquiry`);
30+
return response.data.data;
31+
} catch (e) {
32+
console.error('전체 문의 조회 에러', e);
33+
throw e;
34+
}
35+
};

src/api/alarm.api.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { ApiAlarmList } from '../models/alarm';
2+
import useAuthStore from '../store/authStore';
23
import { httpClient } from './http.api';
34

45
export const getAlarmList = async () => {
@@ -36,14 +37,19 @@ export const patchAlarm = async (id: number) => {
3637
};
3738

3839
export const testLiveAlarm = async () => {
39-
try {
40-
const response = await httpClient.get<ApiAlarmList>(
41-
`/user/send-alarm?alarmFilter=0`
42-
);
40+
const { accessToken } = useAuthStore.getState();
41+
if (accessToken) {
42+
try {
43+
const response = await httpClient.get<ApiAlarmList>(
44+
`/user/send-alarm?alarmFilter=0`
45+
);
4346

44-
return response;
45-
} catch (e) {
46-
console.error(e);
47-
throw e;
47+
return response;
48+
} catch (e) {
49+
console.error(e);
50+
throw e;
51+
}
52+
} else {
53+
throw new Error('인증 토큰이 없습니다.');
4854
}
4955
};

src/api/auth.api.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import type { ApiOauth, ApiVerifyNickname, VerifyEmail } from '../models/auth';
1+
import {
2+
ApiGetAllUsers,
3+
type ApiOauth,
4+
type ApiVerifyNickname,
5+
type VerifyEmail,
6+
} from '../models/auth';
27
import { httpClient } from './http.api';
38
import { loginFormValues } from '../pages/login/Login';
49
import { registerFormValues } from '../pages/user/register/Register';
@@ -100,3 +105,13 @@ export const getOauthLogin = async (oauthAccessToken: string) => {
100105
throw e;
101106
}
102107
};
108+
109+
export const getAllUsers = async () => {
110+
try {
111+
const response = await httpClient.get<ApiGetAllUsers>(`/users`);
112+
return response.data.data;
113+
} catch (e) {
114+
console.error(e);
115+
throw e;
116+
}
117+
};

src/api/report.api.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { ApiPostContent } from '../models/report';
1+
import { type ApiAllReports, type ApiPostContent } from '../models/report';
22
import { httpClient } from './http.api';
33

44
export const postReport = async (formData: ApiPostContent) => {
@@ -13,3 +13,13 @@ export const postReport = async (formData: ApiPostContent) => {
1313
throw error;
1414
}
1515
};
16+
17+
export const getAllReports = async () => {
18+
try {
19+
const response = await httpClient.get<ApiAllReports>(`/reports`);
20+
return response.data.data;
21+
} catch (e) {
22+
console.error(e);
23+
throw e;
24+
}
25+
};
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
import styled from 'styled-components';
22

3-
export const Container = styled.div``;
3+
export const Container = styled.div`
4+
height: 350px;
5+
`;
Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,97 @@
11
import React from 'react';
22
import * as S from './GraphCard.styled';
3+
import { Line } from 'react-chartjs-2';
4+
import 'chart.js/auto';
5+
import { ChartData, ChartOptions } from 'chart.js';
36

47
const GraphCard = () => {
5-
return <S.Container>GraphCard Component</S.Container>;
8+
return (
9+
<S.Container>
10+
<Line data={data} options={options} />
11+
</S.Container>
12+
);
13+
};
14+
15+
const data: ChartData<'line'> = {
16+
labels: [
17+
'월요일',
18+
'화요일',
19+
'수요일',
20+
'목요일',
21+
'금요일',
22+
'토요일',
23+
'일요일',
24+
],
25+
datasets: [
26+
{
27+
label: '방문자 수',
28+
data: [8, 6, 4, 0, 7, 5, 3],
29+
fill: true,
30+
backgroundColor: 'rgba(54, 162, 235, 0.2)',
31+
borderColor: 'rgba(54, 162, 235, 1)',
32+
borderWidth: 2,
33+
tension: 0.3,
34+
pointRadius: 5,
35+
pointBackgroundColor: '#ffffff',
36+
pointBorderColor: 'rgba(54, 162, 235, 1)',
37+
pointBorderWidth: 2,
38+
pointHoverRadius: 6,
39+
},
40+
],
41+
};
42+
43+
const options: ChartOptions<'line'> = {
44+
responsive: true,
45+
maintainAspectRatio: false,
46+
plugins: {
47+
legend: {
48+
display: false,
49+
},
50+
tooltip: {
51+
enabled: true,
52+
},
53+
title: {
54+
display: false,
55+
},
56+
},
57+
scales: {
58+
x: {
59+
grid: {
60+
display: true,
61+
borderDash: [5, 5],
62+
color: 'rgba(0,0,0,0.1)',
63+
},
64+
ticks: {
65+
font: {
66+
size: 14,
67+
},
68+
color: '#333',
69+
},
70+
},
71+
y: {
72+
grid: {
73+
display: true,
74+
drawBorder: false,
75+
76+
borderDash: [5, 5],
77+
color: 'rgba(0,0,0,0.1)',
78+
},
79+
ticks: {
80+
stepSize: 2,
81+
font: {
82+
size: 14,
83+
},
84+
color: '#333',
85+
callback: (value) => `${value}`,
86+
},
87+
beginAtZero: true,
88+
suggestedMax: 8,
89+
},
90+
},
91+
animation: {
92+
duration: 800,
93+
easing: 'easeOutQuart',
94+
},
695
};
796

897
export default GraphCard;
Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,44 @@
1+
import { Link } from 'react-router-dom';
12
import styled from 'styled-components';
23

3-
export const Container = styled.div``;
4+
export const Container = styled.div`
5+
display: flex;
6+
flex-direction: column;
7+
`;
8+
9+
export const Wrapper = styled.div`
10+
display: flex;
11+
justify-content: space-between;
12+
padding: 10px;
13+
`;
14+
15+
export const UserArea = styled.div`
16+
display: flex;
17+
`;
18+
19+
export const ContentArea = styled(Link)`
20+
margin-left: 16px;
21+
`;
22+
23+
export const NickName = styled.p`
24+
font-size: 14px;
25+
`;
26+
27+
export const Email = styled.p`
28+
font-size: 9px;
29+
opacity: 0.5;
30+
`;
31+
32+
export const MoveToUsersArea = styled(Link)`
33+
display: flex;
34+
align-items: center;
35+
`;
36+
37+
export const Text = styled.p`
38+
font-size: 9px;
39+
`;
40+
41+
export const Arrow = styled.img`
42+
width: 11px;
43+
height: 11px;
44+
`;
Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,47 @@
11
import React from 'react';
22
import * as S from './AllUserPreview.styled';
3+
import { useGetAllUsers } from '../../../../hooks/admin/useGetAllUsers';
4+
import Avatar from '../../../common/avatar/Avatar';
5+
import { ADMIN_ROUTE } from '../../../../constants/routes';
6+
import arrow_right from '../../../../assets/ArrowRight.svg';
7+
import LoadingSpinner from '../../../common/loadingSpinner/LoadingSpinner';
38

49
const AllUserPreview = () => {
5-
return <S.Container>AllUserPreview Component</S.Container>;
10+
const { allUserData, isLoading, isFetching } = useGetAllUsers();
11+
12+
if (isLoading || isFetching) {
13+
return <LoadingSpinner />;
14+
}
15+
16+
if (!allUserData || allUserData.length === 0) {
17+
return <S.Container>가입된 회원이 없습니다.</S.Container>;
18+
}
19+
20+
const previewList = allUserData
21+
? allUserData.length > 6
22+
? allUserData.slice(0, 4)
23+
: allUserData
24+
: [];
25+
26+
return (
27+
<S.Container>
28+
{previewList?.map((user) => (
29+
<S.Wrapper key={user.id}>
30+
<S.UserArea>
31+
<Avatar image={user.user.img} size='40px' />
32+
<S.ContentArea to={`${ADMIN_ROUTE.allUser}/${user.id}`}>
33+
<S.NickName>{user.user.nickname}</S.NickName>
34+
<S.Email>{user.email}</S.Email>
35+
</S.ContentArea>
36+
</S.UserArea>
37+
<S.MoveToUsersArea to={`${ADMIN_ROUTE.allUser}/${user.id}`}>
38+
<S.Text>상세 보기</S.Text>
39+
<S.Arrow src={arrow_right} />
40+
</S.MoveToUsersArea>
41+
</S.Wrapper>
42+
))}
43+
</S.Container>
44+
);
645
};
746

847
export default AllUserPreview;
Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,69 @@
1+
import { Link } from 'react-router-dom';
12
import styled from 'styled-components';
23

3-
export const Container = styled.div``;
4+
export const Container = styled.div`
5+
display: flex;
6+
flex-direction: column;
7+
`;
8+
9+
export const Wrapper = styled.div`
10+
display: flex;
11+
justify-content: space-between;
12+
align-items: center;
13+
padding: 10px;
14+
`;
15+
16+
export const Content = styled.div`
17+
display: flex;
18+
`;
19+
20+
export const Inquiry = styled(Link)`
21+
margin-left: 16px;
22+
`;
23+
24+
export const Category = styled.p`
25+
font-size: 9px;
26+
opacity: 0.5;
27+
`;
28+
29+
export const Title = styled.p`
30+
font-size: 13px;
31+
white-space: nowrap;
32+
overflow: hidden;
33+
text-overflow: ellipsis;
34+
`;
35+
36+
export const StateArea = styled.div`
37+
display: flex;
38+
`;
39+
40+
export const InquiriesDate = styled.p`
41+
font-size: 9px;
42+
opacity: 0.5;
43+
`;
44+
45+
export const Divider = styled.p`
46+
font-size: 9px;
47+
opacity: 0.2;
48+
margin-left: 3px;
49+
margin-right: 3px;
50+
`;
51+
52+
export const InquiryState = styled.p<{ $isCompleted: boolean }>`
53+
font-size: 9px;
54+
color: ${({ $isCompleted }) => ($isCompleted ? `#07DE00` : `#DE1A00`)};
55+
`;
56+
57+
export const MoveToInquiryArea = styled(Link)`
58+
display: flex;
59+
font-size: 9px;
60+
`;
61+
62+
export const Text = styled.p`
63+
font-size: 9px;
64+
`;
65+
66+
export const Arrow = styled.img`
67+
width: 11px;
68+
height: 11px;
69+
`;

0 commit comments

Comments
 (0)