diff --git a/src/api/activityLog.api.ts b/src/api/activityLog.api.ts index b16adef5..e5a2c5fa 100644 --- a/src/api/activityLog.api.ts +++ b/src/api/activityLog.api.ts @@ -1,3 +1,4 @@ +import { ApiAllInquiries } from '../models/admin/mainPreview'; import type { ApiMyComments, ApiMyInquiries } from './../models/activityLog'; import { httpClient } from './http.api'; @@ -22,3 +23,13 @@ export const getMyInquiries = async () => { throw e; } }; + +export const getAllInquiries = async () => { + try { + const response = await httpClient.get(`/inquiry`); + return response.data.data; + } catch (e) { + console.error('전체 문의 조회 에러', e); + throw e; + } +}; diff --git a/src/api/alarm.api.ts b/src/api/alarm.api.ts index 3ec22180..55e3b1a5 100644 --- a/src/api/alarm.api.ts +++ b/src/api/alarm.api.ts @@ -1,4 +1,5 @@ import type { ApiAlarmList } from '../models/alarm'; +import useAuthStore from '../store/authStore'; import { httpClient } from './http.api'; export const getAlarmList = async () => { @@ -36,14 +37,19 @@ export const patchAlarm = async (id: number) => { }; export const testLiveAlarm = async () => { - try { - const response = await httpClient.get( - `/user/send-alarm?alarmFilter=0` - ); + const { accessToken } = useAuthStore.getState(); + if (accessToken) { + try { + const response = await httpClient.get( + `/user/send-alarm?alarmFilter=0` + ); - return response; - } catch (e) { - console.error(e); - throw e; + return response; + } catch (e) { + console.error(e); + throw e; + } + } else { + throw new Error('인증 토큰이 없습니다.'); } }; diff --git a/src/api/auth.api.ts b/src/api/auth.api.ts index 0bc320ea..b866b37c 100644 --- a/src/api/auth.api.ts +++ b/src/api/auth.api.ts @@ -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'; @@ -100,3 +105,13 @@ export const getOauthLogin = async (oauthAccessToken: string) => { throw e; } }; + +export const getAllUsers = async () => { + try { + const response = await httpClient.get(`/users`); + return response.data.data; + } catch (e) { + console.error(e); + throw e; + } +}; diff --git a/src/api/report.api.ts b/src/api/report.api.ts index 111ef186..0b0d24dd 100644 --- a/src/api/report.api.ts +++ b/src/api/report.api.ts @@ -1,4 +1,4 @@ -import type { ApiPostContent } from '../models/report'; +import { type ApiAllReports, type ApiPostContent } from '../models/report'; import { httpClient } from './http.api'; export const postReport = async (formData: ApiPostContent) => { @@ -13,3 +13,13 @@ export const postReport = async (formData: ApiPostContent) => { throw error; } }; + +export const getAllReports = async () => { + try { + const response = await httpClient.get(`/reports`); + return response.data.data; + } catch (e) { + console.error(e); + throw e; + } +}; diff --git a/src/components/admin/mainCard/graphCard/GraphCard.styled.ts b/src/components/admin/mainCard/graphCard/GraphCard.styled.ts index c3389834..bd91c4e2 100644 --- a/src/components/admin/mainCard/graphCard/GraphCard.styled.ts +++ b/src/components/admin/mainCard/graphCard/GraphCard.styled.ts @@ -1,3 +1,5 @@ import styled from 'styled-components'; -export const Container = styled.div``; +export const Container = styled.div` + height: 350px; +`; diff --git a/src/components/admin/mainCard/graphCard/GraphCard.tsx b/src/components/admin/mainCard/graphCard/GraphCard.tsx index 0ceef919..576a1cab 100644 --- a/src/components/admin/mainCard/graphCard/GraphCard.tsx +++ b/src/components/admin/mainCard/graphCard/GraphCard.tsx @@ -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 GraphCard Component; + return ( + + + + ); +}; + +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; diff --git a/src/components/admin/previewComponent/allUserPreview/AllUserPreview.styled.ts b/src/components/admin/previewComponent/allUserPreview/AllUserPreview.styled.ts index c3389834..a68e5081 100644 --- a/src/components/admin/previewComponent/allUserPreview/AllUserPreview.styled.ts +++ b/src/components/admin/previewComponent/allUserPreview/AllUserPreview.styled.ts @@ -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: 0.5; +`; + +export const MoveToUsersArea = styled(Link)` + display: flex; + align-items: center; +`; + +export const Text = styled.p` + font-size: 9px; +`; + +export const Arrow = styled.img` + width: 11px; + height: 11px; +`; diff --git a/src/components/admin/previewComponent/allUserPreview/AllUserPreview.tsx b/src/components/admin/previewComponent/allUserPreview/AllUserPreview.tsx index fba6c2f3..9131fd10 100644 --- a/src/components/admin/previewComponent/allUserPreview/AllUserPreview.tsx +++ b/src/components/admin/previewComponent/allUserPreview/AllUserPreview.tsx @@ -1,8 +1,47 @@ 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'; +import LoadingSpinner from '../../../common/loadingSpinner/LoadingSpinner'; const AllUserPreview = () => { - return AllUserPreview Component; + const { allUserData, isLoading, isFetching } = useGetAllUsers(); + + if (isLoading || isFetching) { + return ; + } + + if (!allUserData || allUserData.length === 0) { + return 가입된 회원이 없습니다.; + } + + const previewList = allUserData + ? allUserData.length > 6 + ? allUserData.slice(0, 4) + : allUserData + : []; + + return ( + + {previewList?.map((user) => ( + + + + + {user.user.nickname} + {user.email} + + + + 상세 보기 + + + + ))} + + ); }; export default AllUserPreview; diff --git a/src/components/admin/previewComponent/inquiresPreview/InquiresPreview.styled.ts b/src/components/admin/previewComponent/inquiresPreview/InquiresPreview.styled.ts index c3389834..0fa5c181 100644 --- a/src/components/admin/previewComponent/inquiresPreview/InquiresPreview.styled.ts +++ b/src/components/admin/previewComponent/inquiresPreview/InquiresPreview.styled.ts @@ -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: 0.5; +`; + +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 InquiriesDate = styled.p` + font-size: 9px; + opacity: 0.5; +`; + +export const Divider = styled.p` + font-size: 9px; + opacity: 0.2; + 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; +`; diff --git a/src/components/admin/previewComponent/inquiresPreview/InquiresPreview.tsx b/src/components/admin/previewComponent/inquiresPreview/InquiresPreview.tsx index 25aba453..3b4968b4 100644 --- a/src/components/admin/previewComponent/inquiresPreview/InquiresPreview.tsx +++ b/src/components/admin/previewComponent/inquiresPreview/InquiresPreview.tsx @@ -1,8 +1,54 @@ -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'; +import LoadingSpinner from '../../../common/loadingSpinner/LoadingSpinner'; const InquiresPreview = () => { - return InquiresPreview Component; + const { allInquiriesData, isLoading, isFetching } = useGetAllInquiries(); + + if (isLoading || isFetching) { + return ; + } + + if (!allInquiriesData || allInquiriesData.length === 0) { + return 등록된 문의가 없습니다.; + } + + const previewList = allInquiriesData + ? allInquiriesData.length > 6 + ? allInquiriesData.slice(0, 4) + : allInquiriesData + : []; + + return ( + + {previewList?.map((inquiry) => ( + + + {/* + + {inquiry.category} + {inquiry.title} + + {inquiry.createdAt} + | + + {inquiry.state ? '답변 완료' : '답변 대기 중'} + + + + + + 상세 보기 + + + + ))} + + ); }; export default InquiresPreview; diff --git a/src/components/admin/previewComponent/noticePreview/NoticePreview.tsx b/src/components/admin/previewComponent/noticePreview/NoticePreview.tsx index a5efc202..8337563d 100644 --- a/src/components/admin/previewComponent/noticePreview/NoticePreview.tsx +++ b/src/components/admin/previewComponent/noticePreview/NoticePreview.tsx @@ -2,13 +2,25 @@ 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'; const NoticePreview = () => { - const { noticeData } = useGetNotice({ keyword: '', page: 1 }); + const { noticeData, isLoading, isFetching } = useGetNotice({ + keyword: '', + page: 1, + }); + + if (isLoading || isFetching) { + return ; + } + + if (!noticeData?.notices || noticeData.notices.length === 0) { + return 공지사힝이 없습니다.; + } return ( - {noticeData?.notices.map((notice) => ( + {noticeData.notices.map((notice) => ( {notice.title} diff --git a/src/components/admin/previewComponent/reportsPreview/ReportsPreview.styled.ts b/src/components/admin/previewComponent/reportsPreview/ReportsPreview.styled.ts index c3389834..5514ca39 100644 --- a/src/components/admin/previewComponent/reportsPreview/ReportsPreview.styled.ts +++ b/src/components/admin/previewComponent/reportsPreview/ReportsPreview.styled.ts @@ -1,3 +1,68 @@ +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 ReportArea = styled(Link)` + display: flex; +`; + +export const ContentArea = styled.div` + margin-left: 16px; +`; + +export const ImposedCount = styled.div` + font-size: 9px; + opacity: 0.5; +`; + +export const Category = styled.p` + font-size: 13px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +`; + +export const StateArea = styled.div` + display: flex; +`; + +export const ReportDate = styled.p` + font-size: 9px; + opacity: 0.5; +`; + +export const Divider = styled.p` + font-size: 9px; + opacity: 0.2; + margin-left: 3px; + margin-right: 3px; +`; + +export const IsImposed = styled.p<{ $isImposed: boolean }>` + font-size: 9px; + color: ${({ $isImposed }) => ($isImposed ? `#07DE00` : `#DE1A00`)}; +`; + +export const MoveToReportsArea = styled(Link)` + display: flex; +`; + +export const Text = styled.p` + font-size: 9px; +`; + +export const Arrow = styled.img` + width: 11px; + height: 11px; +`; diff --git a/src/components/admin/previewComponent/reportsPreview/ReportsPreview.tsx b/src/components/admin/previewComponent/reportsPreview/ReportsPreview.tsx index d3964ed7..0f7f4d21 100644 --- a/src/components/admin/previewComponent/reportsPreview/ReportsPreview.tsx +++ b/src/components/admin/previewComponent/reportsPreview/ReportsPreview.tsx @@ -1,8 +1,44 @@ -import React from 'react'; import * as S from './ReportsPreview.styled'; +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'; const ReportsPreview = () => { - return ReportsPreview Component; + const { allReportsData } = useGetAllReports(); + + const previewList = allReportsData + ? allReportsData.length > 6 + ? allReportsData.slice(0, 4) + : allReportsData + : []; + + return ( + + {previewList?.map((report) => ( + + + + + {report.imposedCount} 번 + {report.category} + + {report.createdAt} + | + + {report.isImposed ? '검토 완료' : '검토 미완료'} + + + + + + 신고 상세 보기 + + + + ))} + + ); }; export default ReportsPreview; diff --git a/src/constants/admin/mainItems.ts b/src/constants/admin/mainItems.ts index d83a2a41..74c17c8d 100644 --- a/src/constants/admin/mainItems.ts +++ b/src/constants/admin/mainItems.ts @@ -14,13 +14,13 @@ export interface CardItem { export const cardList: CardItem[] = [ { - key: 'notice', - title: '공지사항', - link: `${ADMIN_ROUTE.notice}`, - Component: NoticePreview, + key: 'allUsers', + title: '전체 회원 조회', + link: `${ADMIN_ROUTE.allUser}`, + Component: AllUserPreview, }, { - key: 'inquires', + key: 'inquiries', title: '문의 확인', link: `${ADMIN_ROUTE.inquiries}`, Component: InquiresPreview, @@ -31,15 +31,15 @@ export const cardList: CardItem[] = [ link: `${ADMIN_ROUTE.reports}`, Component: ReportsPreview, }, - { - key: 'allUsers', - title: '전체 회원 조회', - link: `${ADMIN_ROUTE.allUser}`, - Component: AllUserPreview, - }, { key: 'Graph', title: '방문자 현황', Component: GraphCard, }, + { + key: 'notice', + title: '공지사항', + link: `${ADMIN_ROUTE.notice}`, + Component: NoticePreview, + }, ]; diff --git a/src/hooks/admin/useGetAllInquiries.ts b/src/hooks/admin/useGetAllInquiries.ts new file mode 100644 index 00000000..b4b7b797 --- /dev/null +++ b/src/hooks/admin/useGetAllInquiries.ts @@ -0,0 +1,16 @@ +import { useQuery } from '@tanstack/react-query'; +import { getAllInquiries } from '../../api/activityLog.api'; +import { Inquiries } from '../queries/user/keys'; + +export const useGetAllInquiries = () => { + const { + data: allInquiriesData, + isLoading, + isFetching, + } = useQuery({ + queryKey: [Inquiries.allInquiries], + queryFn: () => getAllInquiries(), + }); + + return { allInquiriesData, isLoading, isFetching }; +}; diff --git a/src/hooks/admin/useGetAllReports.ts b/src/hooks/admin/useGetAllReports.ts new file mode 100644 index 00000000..ab7c1f31 --- /dev/null +++ b/src/hooks/admin/useGetAllReports.ts @@ -0,0 +1,12 @@ +import { useQuery } from '@tanstack/react-query'; +import { ReportData } from '../queries/user/keys'; +import { getAllReports } from '../../api/report.api'; + +export const useGetAllReports = () => { + const { data: allReportsData, isLoading } = useQuery({ + queryKey: [ReportData.allReports], + queryFn: () => getAllReports(), + }); + + return { allReportsData, isLoading }; +}; diff --git a/src/hooks/admin/useGetAllUsers.ts b/src/hooks/admin/useGetAllUsers.ts new file mode 100644 index 00000000..769cfea7 --- /dev/null +++ b/src/hooks/admin/useGetAllUsers.ts @@ -0,0 +1,16 @@ +import { useQuery } from '@tanstack/react-query'; +import { UserData } from '../queries/user/keys'; +import { getAllUsers } from '../../api/auth.api'; + +export const useGetAllUsers = () => { + const { + data: allUserData, + isLoading, + isFetching, + } = useQuery({ + queryKey: [UserData.allUser], + queryFn: () => getAllUsers(), + }); + + return { allUserData, isLoading, isFetching }; +}; diff --git a/src/hooks/queries/user/keys.ts b/src/hooks/queries/user/keys.ts index 60a3acbd..ae2f244a 100644 --- a/src/hooks/queries/user/keys.ts +++ b/src/hooks/queries/user/keys.ts @@ -49,8 +49,20 @@ export const ActivityLog = { myInquiries: ['MyInquiries'], }; +export const Inquiries = { + allInquiries: ['AllInquiries'], +}; + export const CustomerService = { faq: 'faq', notice: 'notice', noticeDetail: 'noticeDetail', }; + +export const ReportData = { + allReports: ['AllReports'], +}; + +export const UserData = { + allUser: ['AllUser'], +}; diff --git a/src/hooks/user/useGetNotice.ts b/src/hooks/user/useGetNotice.ts index 16cd3df3..f367e266 100644 --- a/src/hooks/user/useGetNotice.ts +++ b/src/hooks/user/useGetNotice.ts @@ -6,12 +6,16 @@ import { CustomerService } from '../queries/user/keys'; export const useGetNotice = (searchProperty: NoticeSearch) => { const { keyword, page } = searchProperty; - const { data: noticeData, isLoading } = useQuery({ + const { + data: noticeData, + isLoading, + isFetching, + } = useQuery({ queryKey: [CustomerService.notice, keyword, page], queryFn: () => getNotice(searchProperty), staleTime: Infinity, gcTime: Infinity, }); - return { noticeData, isLoading }; + return { noticeData, isLoading, isFetching }; }; diff --git a/src/mock/applicant.ts b/src/mock/applicant.ts index c8e922b3..72fe2301 100644 --- a/src/mock/applicant.ts +++ b/src/mock/applicant.ts @@ -4,7 +4,7 @@ import mockApplicantData from './mockApplicantData.json'; import mockPassNonPassListData from './mockPassNonPassListData.json'; export const applicantList = http.get( - `${import.meta.env.VITE_API_BASE_URL}/project/:projectId/applicants`, + `${import.meta.env.VITE_APP_API_BASE_URL}project/:projectId/applicants`, () => { return HttpResponse.json(mockApplicantsData, { status: 200, @@ -13,7 +13,9 @@ export const applicantList = http.get( ); export const applicantInfo = http.get( - `${import.meta.env.VITE_API_BASE_URL}/project/:projectId/applicants/:userId`, + `${ + import.meta.env.VITE_APP_API_BASE_URL + }project/:projectId/applicants/:userId`, () => { return HttpResponse.json(mockApplicantData, { status: 200, @@ -22,7 +24,9 @@ export const applicantInfo = http.get( ); export const passNonPassList = http.get( - `${import.meta.env.VITE_API_BASE_URL}/project/:projectId/applicants/results`, + `${ + import.meta.env.VITE_APP_API_BASE_URL + }project/:projectId/applicants/results`, () => { return HttpResponse.json(mockPassNonPassListData, { status: 200, @@ -31,7 +35,7 @@ export const passNonPassList = http.get( ); export const passNonPass = http.put( - `${import.meta.env.VITE_API_BASE_URL}/project/:projectId/applicant`, + `${import.meta.env.VITE_APP_API_BASE_URL}project/:projectId/applicant`, () => { return HttpResponse.json( { message: '합/불합 상태가 수정되었습니다.' }, @@ -43,7 +47,7 @@ export const passNonPass = http.put( ); export const createApplicant = http.post( - `${import.meta.env.VITE_API_BASE_URL}/project/:projectId/applicant`, + `${import.meta.env.VITE_APP_API_BASE_URL}project/:projectId/applicant`, () => { return HttpResponse.json({ status: 200, diff --git a/src/mock/auth.ts b/src/mock/auth.ts index f47564ac..ce549ea7 100644 --- a/src/mock/auth.ts +++ b/src/mock/auth.ts @@ -1,7 +1,7 @@ import { http, HttpResponse } from 'msw'; export const login = http.post( - `${import.meta.env.VITE_API_BASE_URL}/auth/login`, + `${import.meta.env.VITE_APP_API_BASE_URL}auth/login`, () => { return HttpResponse.json({ status: 200, diff --git a/src/mock/browser.ts b/src/mock/browser.ts index ea43a146..2c4da947 100644 --- a/src/mock/browser.ts +++ b/src/mock/browser.ts @@ -7,7 +7,7 @@ import { passNonPass, passNonPassList, } from './applicant'; -import { projectDetail } from './projectDetail'; +import { projectDetail, reportsAll } from './projectDetail'; import { myPageAppliedProjectList, mypageEditProfile, @@ -16,7 +16,11 @@ import { myPageProfile, myPageSkillTag, } from './mypage'; -import { userPageAppliedProjectList, userPageProfile } from './userpage'; +import { + userAll, + userPageAppliedProjectList, + userPageProfile, +} from './userpage'; import { login } from './auth'; import { fetchProjectLists, fetchProjectStatistic } from './projectLists'; import { @@ -51,6 +55,8 @@ export const handlers = [ login, createApplicant, createProject, + reportsAll, + userAll, ]; export const worker = setupWorker(...handlers); diff --git a/src/mock/createProject.ts b/src/mock/createProject.ts index 1ab0fc72..d1fee78f 100644 --- a/src/mock/createProject.ts +++ b/src/mock/createProject.ts @@ -1,7 +1,7 @@ import { http, HttpResponse } from 'msw'; export const createProject = http.post( - `${import.meta.env.VITE_API_BASE_URL}/project`, + `${import.meta.env.VITE_APP_API_BASE_URL}project`, () => { return HttpResponse.json({ status: 200, diff --git a/src/mock/manageProjectList.ts b/src/mock/manageProjectList.ts index 44554fc7..a5a81006 100644 --- a/src/mock/manageProjectList.ts +++ b/src/mock/manageProjectList.ts @@ -2,7 +2,7 @@ import { HttpResponse, http } from 'msw'; import mockManageMyprojectList from './mockProjectList.json'; export const myProjectList = http.get( - `${import.meta.env.VITE_API_BASE_URL}/project/my`, + `${import.meta.env.VITE_APP_API_BASE_URL}project/my`, () => { return HttpResponse.json(mockManageMyprojectList, { status: 200, @@ -11,7 +11,7 @@ export const myProjectList = http.get( ); export const sendResult = http.put( - `${import.meta.env.VITE_API_BASE_URL}/project/:projectId/close`, + `${import.meta.env.VITE_APP_API_BASE_URL}project/:projectId/close`, () => { return HttpResponse.json( { message: '지원자들에게 결과를 전송했어요' }, diff --git a/src/mock/mockReports.json b/src/mock/mockReports.json new file mode 100644 index 00000000..5d9e4f00 --- /dev/null +++ b/src/mock/mockReports.json @@ -0,0 +1,128 @@ +{ + "success": true, + "message": "성공적으로 처리되었습니다.", + "data": [ + { + "id": 1, + "imposedCount": 3, + "category": "스팸 신고", + "user": { + "id": 301, + "nickname": "alice", + "img": "https://example.com/images/alice.png" + }, + "isImposed": false, + "createdAt": "2025-05-01" + }, + { + "id": 2, + "imposedCount": 5, + "category": "욕설/비방", + "user": { + "id": 303, + "nickname": "charlie", + "img": "https://example.com/images/charlie.jpeg" + }, + "isImposed": true, + "createdAt": "2025-05-03" + }, + { + "id": 3, + "imposedCount": 2, + "category": "저작권 침해", + "user": { + "id": 304, + "nickname": "daisy", + "img": "https://example.com/images/daisy.png" + }, + "isImposed": false, + "createdAt": "2025-05-05" + }, + { + "id": 4, + "imposedCount": 7, + "category": "개인정보 노출", + "user": { + "id": 305, + "nickname": "eve", + "img": "https://example.com/images/eve.jpg" + }, + "isImposed": true, + "createdAt": "2025-05-07" + }, + { + "id": 5, + "imposedCount": 1, + "category": "음란물 게시물", + "user": { + "id": 306, + "nickname": "frank", + "img": "https://example.com/images/frank.png" + }, + + "isImposed": false, + "createdAt": "2025-05-09" + }, + { + "id": 6, + "imposedCount": 4, + "category": "사칭 계정", + "user": { + "id": 308, + "nickname": "henry", + "img": "https://example.com/images/henry.jpg" + }, + "isImposed": true, + "createdAt": "2025-05-11" + }, + { + "id": 7, + "imposedCount": 6, + "category": "허위 정보 유포", + "user": { + "id": 309, + "nickname": "iris", + "img": "https://example.com/images/iris.png" + }, + + "isImposed": false, + "createdAt": "2025-05-13" + }, + { + "id": 8, + "imposedCount": 2, + "category": "불법 광고", + "user": { + "id": 311, + "nickname": "kate", + "img": "https://example.com/images/kate.jpeg" + }, + "isImposed": true, + "createdAt": "2025-05-15" + }, + { + "id": 9, + "imposedCount": 8, + "category": "명예 훼손", + "user": { + "id": 312, + "nickname": "leo", + "img": "https://example.com/images/leo.png" + }, + "isImposed": true, + "createdAt": "2025-05-17" + }, + { + "id": 10, + "imposedCount": 3, + "category": "폭력/위협", + "user": { + "id": 314, + "nickname": "nate", + "img": "https://example.com/images/nate.png" + }, + "isImposed": false, + "createdAt": "2025-05-19" + } + ] +} diff --git a/src/mock/mockUsers.json b/src/mock/mockUsers.json new file mode 100644 index 00000000..29036373 --- /dev/null +++ b/src/mock/mockUsers.json @@ -0,0 +1,117 @@ +{ + "success": true, + "message": "성공적으로 처리되었습니다.", + "data": [ + { + "id": 1, + "email": "alice@example.com", + "name": "Alice Kim", + "user": { + "id": 201, + "nickname": "alice", + "img": "" + }, + "createdAt": "2025-05-15" + }, + { + "id": 2, + "email": "bob@example.com", + "name": "Bob Lee", + "user": { + "id": 202, + "nickname": "bob", + "img": "" + }, + "createdAt": "2025-05-16" + }, + { + "id": 3, + "email": "charlie@example.com", + "name": "Charlie Park", + "user": { + "id": 204, + "nickname": "daisy", + "img": "" + }, + "createdAt": "2025-05-17" + }, + { + "id": 4, + "email": "daisy@example.com", + "name": "Daisy Choi", + "user": { + "id": 205, + "nickname": "daisy", + "img": "" + }, + "createdAt": "2025-05-18" + }, + { + "id": 5, + "email": "eve@example.com", + "name": "Eve Jung", + "user": { + "id": 206, + "nickname": "eve", + "img": "" + }, + "createdAt": "2025-05-19" + }, + { + "id": 6, + "email": "frank@example.com", + "name": "Frank Han", + "user": { + "id": 208, + "nickname": "grace", + "img": "" + }, + "createdAt": "2025-05-20" + }, + { + "id": 7, + "email": "grace@example.com", + "name": "Grace Yoon", + "user": { + "id": 209, + "nickname": "grace", + "img": "" + }, + "createdAt": "2025-05-21" + }, + { + "id": 8, + "email": "henry@example.com", + "name": "Henry Shin", + "user": { + "id": 210, + "nickname": "henry", + "img": "" + }, + "createdAt": "2025-05-22" + }, + { + "id": 9, + "email": "iris@example.com", + "name": "Iris Lim", + "user": { + "id": 211, + "nickname": "iris", + "img": "" + }, + "createdAt": "2025-05-23" + }, + { + "id": 10, + "email": "jack@example.com", + "name": "Jack Han", + "user": { + "id": 212, + "nickname": "jack", + "img": "" + }, + + "createdAt": "2025-05-24" + } + ] +} diff --git a/src/mock/mypage.ts b/src/mock/mypage.ts index 74c04ed0..6adaf4f2 100644 --- a/src/mock/mypage.ts +++ b/src/mock/mypage.ts @@ -6,7 +6,7 @@ import mockMypageJoinedProjectListData from './mockMypageJoinedProjectListData.j import mockMypageAppliedProjectListData from './mockMypageAppliedProjectListData.json'; export const myPageProfile = http.get( - `${import.meta.env.VITE_API_BASE_URL}/user/me`, + `${import.meta.env.VITE_APP_API_BASE_URL}user/me`, () => { return HttpResponse.json(mockMypageProfileData, { status: 200, @@ -15,7 +15,7 @@ export const myPageProfile = http.get( ); export const myPagePositionTag = http.get( - `${import.meta.env.VITE_API_BASE_URL}/position-tag`, + `${import.meta.env.VITE_APP_API_BASE_URL}position-tag`, () => { return HttpResponse.json(mockPositionTagData, { status: 200, @@ -24,7 +24,7 @@ export const myPagePositionTag = http.get( ); export const myPageSkillTag = http.get( - `${import.meta.env.VITE_API_BASE_URL}/skill-tag`, + `${import.meta.env.VITE_APP_API_BASE_URL}skill-tag`, () => { return HttpResponse.json(mockSkillTagData, { status: 200, @@ -33,7 +33,7 @@ export const myPageSkillTag = http.get( ); export const myPageJoinedProjectList = http.get( - `${import.meta.env.VITE_API_BASE_URL}/user/me/project`, + `${import.meta.env.VITE_APP_API_BASE_URL}user/me/project`, () => { return HttpResponse.json(mockMypageJoinedProjectListData, { status: 200, @@ -42,7 +42,7 @@ export const myPageJoinedProjectList = http.get( ); export const myPageAppliedProjectList = http.get( - `${import.meta.env.VITE_API_BASE_URL}/user/me/applications`, + `${import.meta.env.VITE_APP_API_BASE_URL}user/me/applications`, () => { return HttpResponse.json(mockMypageAppliedProjectListData, { status: 200, @@ -51,7 +51,7 @@ export const myPageAppliedProjectList = http.get( ); export const mypageEditProfile = http.put( - `${import.meta.env.VITE_API_BASE_URL}/user/me`, + `${import.meta.env.VITE_APP_API_BASE_URL}user/me`, () => { return HttpResponse.json({ status: 200, diff --git a/src/mock/projectDetail.ts b/src/mock/projectDetail.ts index 9ac8c983..bf6c51ea 100644 --- a/src/mock/projectDetail.ts +++ b/src/mock/projectDetail.ts @@ -1,11 +1,19 @@ import { http, HttpResponse } from 'msw'; import mockPrjectDetail from './mockProjectDetail.json'; +import mockReports from './mockReports.json'; export const projectDetail = http.get( - `${import.meta.env.VITE_API_BASE_URL}/project/:projectId`, + `${import.meta.env.VITE_APP_API_BASE_URL}project/:projectId`, () => { return HttpResponse.json(mockPrjectDetail, { status: 200, }); } ); + +export const reportsAll = http.get( + `${import.meta.env.VITE_APP_API_BASE_URL}reports`, + () => { + return HttpResponse.json(mockReports, { status: 200 }); + } +); diff --git a/src/mock/projectLists.ts b/src/mock/projectLists.ts index 394b7cf6..c9db9c1c 100644 --- a/src/mock/projectLists.ts +++ b/src/mock/projectLists.ts @@ -3,7 +3,7 @@ import count from './mockCount.json'; import project from './mockProject.json'; export const fetchProjectLists = http.get( - `${import.meta.env.VITE_API_BASE_URL}/project`, + `${import.meta.env.VITE_APP_API_BASE_URL}project`, () => { return HttpResponse.json(project, { status: 200, @@ -12,7 +12,7 @@ export const fetchProjectLists = http.get( ); export const fetchProjectStatistic = http.get( - `${import.meta.env.VITE_API_BASE_URL}/project/count`, + `${import.meta.env.VITE_APP_API_BASE_URL}project/count`, () => { return HttpResponse.json(count, { status: 200, diff --git a/src/mock/projectSearchFiltering.ts b/src/mock/projectSearchFiltering.ts index e97bda84..1a86df73 100644 --- a/src/mock/projectSearchFiltering.ts +++ b/src/mock/projectSearchFiltering.ts @@ -4,7 +4,7 @@ import position from './mockPosition.json'; import method from './mockMethod.json'; export const fetchSkillTag = http.get( - `${import.meta.env.VITE_API_BASE_URL}/skill-tag`, + `${import.meta.env.VITE_APP_API_BASE_URL}skill-tag`, () => { return HttpResponse.json(skill, { status: 200, @@ -12,7 +12,7 @@ export const fetchSkillTag = http.get( } ); export const fetchPositionTag = http.get( - `${import.meta.env.VITE_API_BASE_URL}/position-tag`, + `${import.meta.env.VITE_APP_API_BASE_URL}position-tag`, () => { return HttpResponse.json(position, { status: 200, @@ -21,7 +21,7 @@ export const fetchPositionTag = http.get( ); export const fetchMethodTag = http.get( - `${import.meta.env.VITE_API_BASE_URL}/method`, + `${import.meta.env.VITE_APP_API_BASE_URL}method`, () => { return HttpResponse.json(method, { status: 200, diff --git a/src/mock/userpage.ts b/src/mock/userpage.ts index 65f443d1..21090fff 100644 --- a/src/mock/userpage.ts +++ b/src/mock/userpage.ts @@ -1,9 +1,10 @@ import { http, HttpResponse } from 'msw'; import mockUserpageProfileData from './mockUserpageProfileData.json'; import mockUserpageAppliedProjectListData from './mockUserpageAppliedProjectListData.json'; +import mockUsers from './mockUsers.json'; export const userPageProfile = http.get( - `${import.meta.env.VITE_API_BASE_URL}/user/:id`, + `${import.meta.env.VITE_APP_API_BASE_URL}user/:id`, () => { return HttpResponse.json(mockUserpageProfileData, { status: 200, @@ -12,10 +13,17 @@ export const userPageProfile = http.get( ); export const userPageAppliedProjectList = http.get( - `${import.meta.env.VITE_API_BASE_URL}/user/:id/project`, + `${import.meta.env.VITE_APP_API_BASE_URL}user/:id/project`, () => { return HttpResponse.json(mockUserpageAppliedProjectListData, { status: 200, }); } ); + +export const userAll = http.get( + `${import.meta.env.VITE_APP_API_BASE_URL}users`, + () => { + return HttpResponse.json(mockUsers, { status: 200 }); + } +); diff --git a/src/models/activityLog.ts b/src/models/activityLog.ts index 1dd69529..d264daa1 100644 --- a/src/models/activityLog.ts +++ b/src/models/activityLog.ts @@ -1,6 +1,7 @@ import type { ApiCommonType } from './apiCommon'; export interface MyInquiries { + id: number; title: string; content: string; category: string; diff --git a/src/models/admin/mainPreview.ts b/src/models/admin/mainPreview.ts new file mode 100644 index 00000000..892b1ee9 --- /dev/null +++ b/src/models/admin/mainPreview.ts @@ -0,0 +1,11 @@ +import type { MyInquiries } from '../activityLog'; +import type { ApiCommonType, User } from '../apiCommon'; + +export interface AllInquiries extends MyInquiries { + user: User; + createdAt: string; +} + +export interface ApiAllInquiries extends ApiCommonType { + data: AllInquiries[]; +} diff --git a/src/models/auth.ts b/src/models/auth.ts index bd5787a2..f663c1aa 100644 --- a/src/models/auth.ts +++ b/src/models/auth.ts @@ -1,5 +1,4 @@ -//model -import { ApiCommonType } from './apiCommon'; +import { type ApiCommonType, type User } from './apiCommon'; export interface VerifyEmail { email: string; @@ -27,3 +26,14 @@ export interface ApiOauth extends ApiCommonType { data: Pick; user: UserData; } + +export interface ApiGetAllUsers extends ApiCommonType { + data: AllUser[]; +} + +export interface AllUser { + id: number; + email: string; + user: User; + createdAt: string; +} diff --git a/src/models/report.ts b/src/models/report.ts index 16fa8720..0c24868e 100644 --- a/src/models/report.ts +++ b/src/models/report.ts @@ -1,6 +1,21 @@ +import { type ApiCommonType, type User } from './apiCommon'; + export interface ApiPostContent { reportTargetId: number; reportFilter: number; reason: string[]; detail: string; } + +export interface ApiAllReports extends ApiCommonType { + data: AllReports[]; +} + +export interface AllReports { + id: number; + imposedCount: number; + category: string; + user: User; + isImposed: boolean; + createdAt: string; +} diff --git a/src/pages/admin/adminMain/AdminMain.styled.ts b/src/pages/admin/adminMain/AdminMain.styled.ts index 39d65d55..2c311a07 100644 --- a/src/pages/admin/adminMain/AdminMain.styled.ts +++ b/src/pages/admin/adminMain/AdminMain.styled.ts @@ -1,7 +1,7 @@ import styled from 'styled-components'; export const Container = styled.div` - padding: 50px; + padding: 10px; min-height: 100vh; `; @@ -9,7 +9,17 @@ export const Wrapper = styled.div` display: grid; grid-template-columns: repeat(3, 1fr); grid-auto-rows: minmax(350px, auto); - gap: 20px; + gap: 10px; + + & > * { + width: 100%; + } + + & > *:nth-child(4) { + grid-column: 1 / 3; + width: 300px; + width: 100%; + } @media (max-width: 768px) { grid-template-columns: 1fr; diff --git a/src/routes/AppRoutes.tsx b/src/routes/AppRoutes.tsx index 35cc2f98..b65e2c7a 100644 --- a/src/routes/AppRoutes.tsx +++ b/src/routes/AppRoutes.tsx @@ -5,10 +5,7 @@ import useAuthStore from '../store/authStore'; import ProtectRoute from '../components/common/ProtectRoute'; import NotFoundPage from '../pages/notFoundPage/NotFoundPage'; import QueryErrorBoundary from '../components/common/error/QueryErrorBoundary'; -import { ToastProvider } from '../components/common/Toast/ToastProvider'; -import NotificationInitializer from '../components/user/notificationLive/NotificationInitializer'; -import { NotificationProvider } from '../components/user/notificationLive/NotificationProvider'; -import { ADMIN_ROUTE, ROUTES } from '../constants/routes'; +import { ROUTES } from '../constants/routes'; const Login = lazy(() => import('../pages/login/Login')); const LoginSuccess = lazy(() => import('../pages/login/LoginSuccess')); const LoginApi = lazy(() => import('../pages/login/LoginApi')); @@ -379,22 +376,7 @@ export const AppRoutes = () => { }; }); - const router = createBrowserRouter([ - { - element: ( - - - - - - - ), - - children: [...newRouteList, { path: '*', element: }], - }, - ]); - - return ; + return newRouteList; }; export default AppRoutes; diff --git a/src/routes/MergeRoutes.tsx b/src/routes/MergeRoutes.tsx index dd084465..421c2895 100644 --- a/src/routes/MergeRoutes.tsx +++ b/src/routes/MergeRoutes.tsx @@ -4,14 +4,19 @@ import AppRoutes from './AppRoutes'; import NotFoundPage from '../pages/notFoundPage/NotFoundPage'; import { ToastProvider } from '../components/common/Toast/ToastProvider'; import ProtectAdminRoute from './ProtectAdminRoute'; +import { NotificationProvider } from '../components/user/notificationLive/NotificationProvider'; +import NotificationInitializer from '../components/user/notificationLive/NotificationInitializer'; export default function MergeRoutes() { const router = createBrowserRouter([ { element: ( - - - + + + + + + ), children: [...AppRoutes()], },