diff --git a/src/api/admin/customerService/inquiry.api.ts b/src/api/admin/customerService/inquiry.api.ts index a6536a9c..c3228c55 100644 --- a/src/api/admin/customerService/inquiry.api.ts +++ b/src/api/admin/customerService/inquiry.api.ts @@ -1,14 +1,19 @@ import type { ApiCommonBasicType } from '../../../models/apiCommon'; import type { + AdminInquiryChangeSearchParams, ApiAdminInquiry, ApiAdminInquiryDetail, InquiryAnswerBody, } from '../../../models/inquiry'; import { httpClient } from '../../http.api'; -export const getAllInquiries = async () => { +export const getAllInquiries = async ( + childSearchParams: AdminInquiryChangeSearchParams +) => { try { - const response = await httpClient.get(`/inquiry`); + const response = await httpClient.get(`/inquiry`, { + params: childSearchParams, + }); return response.data.data; } catch (e) { console.error('전체 문의 조회 에러', e); diff --git a/src/api/mypage.api.ts b/src/api/mypage.api.ts index c3778531..e600ac7b 100644 --- a/src/api/mypage.api.ts +++ b/src/api/mypage.api.ts @@ -1,3 +1,4 @@ +import type { ApiCommonBasicType } from '../models/apiCommon'; import type { ApiUserInfo, ApiUserInfoImg, @@ -53,6 +54,15 @@ export const patchMyProfileImg = async (file: File) => { } }; +export const patchGithubLink = async (githubUrl: string) => { + try { + await httpClient.patch('/user/github', { githubUrl }); + } catch (error) { + console.error('프로필 깃허브 업데이트: ', error); + throw error; + } +}; + export const getMyJoinedProjectList = async () => { try { const response = await httpClient.get( diff --git a/src/components/admin/adminInquiry/AdminInquiry.styled.ts b/src/components/admin/adminInquiry/AdminInquiry.styled.ts index 7e9f145f..e0eea5bc 100644 --- a/src/components/admin/adminInquiry/AdminInquiry.styled.ts +++ b/src/components/admin/adminInquiry/AdminInquiry.styled.ts @@ -19,7 +19,33 @@ export const AdminInquiryCategory = styled.span` font-weight: 500; `; -export const AdminInquiryUser = styled.div``; +export const AdminInquiryUserWrapper = styled.div` + position: relative; +`; + +export const AdminInquiryUser = styled.button` + width: fit-content; + font-size: 1.1rem; + z-index: 1; + transition: color 0.1s ease-in-out; + &:hover { + color: ${({ theme }) => theme.color.lightnavy}; + } +`; + +export const AdminInquiryUserCheckDropdown = styled.div` + position: absolute; + top: 0.8rem; + right: 0; + background: ${({ theme }) => theme.color.white}; + border-radius: ${({ theme }) => theme.borderRadius.primary}; + border: 1px solid ${({ theme }) => theme.color.lightgrey}; + padding: 0.3rem 0.5rem; + z-index: 100000; + box-shadow: 3px 2px 19px -6px rgba(0, 0, 0, 0.75); + -webkit-box-shadow: 3px 2px 19px -6px rgba(0, 0, 0, 0.75); + -moz-box-shadow: 3px 2px 19px -6px rgba(0, 0, 0, 0.75); +`; export const AdminInquiryDate = styled.span` font-size: 0.9rem; diff --git a/src/components/admin/adminInquiry/AdminInquiry.tsx b/src/components/admin/adminInquiry/AdminInquiry.tsx index da9933b5..61a9edd0 100644 --- a/src/components/admin/adminInquiry/AdminInquiry.tsx +++ b/src/components/admin/adminInquiry/AdminInquiry.tsx @@ -1,3 +1,4 @@ +import { useSearchParams } from 'react-router-dom'; import { ADMIN_ROUTE } from '../../../constants/routes'; import type { AdminInquiry as TAdminInquiry } from '../../../models/inquiry'; import ContentBorder from '../../common/contentBorder/ContentBorder'; @@ -8,12 +9,33 @@ interface AdminInquiryProps { } export default function AdminInquiry({ list }: AdminInquiryProps) { + const [searchParams, setSearchParams] = useSearchParams(); + + const handleClickLookupUser = (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + + const id = String(list.user.id); + const userId = id || ''; + const nickname = list.user.nickname; + + const newParams = new URLSearchParams(searchParams); + newParams.set('userId', userId); + newParams.set('nickname', nickname); + + setSearchParams(newParams); + }; + return ( [{list.category}] {list.title} - {list.user.nickname} + + + {list.user.nickname} + + {list.createdAt} {list.state ? '답변완료' : '확인중'} diff --git a/src/components/admin/adminInquiry/AdminInquiryList.styled.ts b/src/components/admin/adminInquiry/AdminInquiryList.styled.ts index 152ad450..1185b650 100644 --- a/src/components/admin/adminInquiry/AdminInquiryList.styled.ts +++ b/src/components/admin/adminInquiry/AdminInquiryList.styled.ts @@ -6,7 +6,8 @@ export const SpinnerWrapper = styled(SpinnerWrapperStyled)``; export const AdminInquiryListContainer = styled.section` width: 100%; display: flex; - justify-content: center; + flex-direction: column; + align-items: center; `; export const AdminInquiryListWrapper = styled.div` diff --git a/src/components/admin/adminInquiry/AdminInquiryList.tsx b/src/components/admin/adminInquiry/AdminInquiryList.tsx index 18ae34bc..8804b253 100644 --- a/src/components/admin/adminInquiry/AdminInquiryList.tsx +++ b/src/components/admin/adminInquiry/AdminInquiryList.tsx @@ -1,10 +1,22 @@ +import { useSearchParams } from 'react-router-dom'; import { useGetAllInquiries } from '../../../hooks/admin/useGetAllInquiries'; import Spinner from '../../user/mypage/Spinner'; import AdminInquiry from './AdminInquiry'; import * as S from './AdminInquiryList.styled'; +import AdminInquiryListLookup from './AdminInquiryListLookup'; +import type { AdminInquiryChangeSearchParams } from '../../../models/inquiry'; +export type SearchParamsInquiryKeyType = keyof AdminInquiryChangeSearchParams; export default function AdminInquiryList() { - const { allInquiriesData, isLoading } = useGetAllInquiries(); + const [searchParams, setSearchParams] = useSearchParams(); + const userId = searchParams.get('userId') || ''; + const startDate = searchParams.get('startDate') || ''; + const endDate = searchParams.get('endDate') || ''; + const { allInquiriesData, isLoading } = useGetAllInquiries({ + userId, + startDate, + endDate, + }); if (isLoading) { return ( @@ -18,6 +30,7 @@ export default function AdminInquiryList() { return ( + {allInquiriesData.map((list) => ( diff --git a/src/components/admin/adminInquiry/AdminInquiryListLookup.styled.ts b/src/components/admin/adminInquiry/AdminInquiryListLookup.styled.ts new file mode 100644 index 00000000..c530d158 --- /dev/null +++ b/src/components/admin/adminInquiry/AdminInquiryListLookup.styled.ts @@ -0,0 +1,67 @@ +import styled from 'styled-components'; + +export const LookupContainer = styled.nav` + width: 80%; + margin-bottom: 1rem; + + input[type='date'] { + position: relative; + padding: 14px; + width: 150px; + height: 30px; + font-size: 14px; + color: ${({ theme }) => theme.color.placeholder}; + border: none; + border-bottom: 1px solid ${({ theme }) => theme.color.grey}; + } + input[type='date']::-webkit-calendar-picker-indicator { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + width: auto; + height: auto; + color: transparent; + background: transparent; + } +`; + +export const LookupWrapper = styled.form` + display: flex; + justify-content: space-between; +`; + +export const LookupUser = styled.input` + border-bottom: 1px solid ${({ theme }) => theme.color.placeholder}; + width: fit-content; +`; + +export const LookupDateWrapper = styled.div` + display: flex; + gap: 1rem; +`; + +export const LookupStartDate = styled.input``; + +export const LookupJoinDate = styled.span``; + +export const LookupEndDate = styled.input``; + +export const LookupIconWrapper = styled.div` + display: flex; + gap: 2rem; + + svg { + width: 1.5rem; + height: 1.5rem; + } +`; + +export const IconDefault = styled.button` + color: ${({ theme }) => theme.color.deepGrey}; +`; + +export const IconSearch = styled.button` + color: ${({ theme }) => theme.color.deepGrey}; +`; diff --git a/src/components/admin/adminInquiry/AdminInquiryListLookup.tsx b/src/components/admin/adminInquiry/AdminInquiryListLookup.tsx new file mode 100644 index 00000000..f5984ef9 --- /dev/null +++ b/src/components/admin/adminInquiry/AdminInquiryListLookup.tsx @@ -0,0 +1,128 @@ +import React, { useEffect, useState } from 'react'; +import type { SearchParamsInquiryKeyType } from './AdminInquiryList'; +import * as S from './AdminInquiryListLookup.styled'; +import { MagnifyingGlassIcon, XMarkIcon } from '@heroicons/react/24/outline'; +import { useSearchParams } from 'react-router-dom'; +import { AdminInquiryChangeSearchParams } from '../../../models/inquiry'; +import Modal from '../../common/modal/Modal'; +import { useModal } from '../../../hooks/useModal'; +import { MODAL_MESSAGE } from '../../../constants/user/modalMessage'; + +export default function AdminInquiryListLookup() { + const [searchParams, setSearchParams] = useSearchParams(); + const userId = searchParams.get('userId') || ''; + const startDate = searchParams.get('startDate') || ''; + const endDate = searchParams.get('endDate') || ''; + const nickname = searchParams.get('nickname') || ''; + const { isOpen, message, handleModalOpen, handleModalClose } = useModal(); + const [searchFilters, setSearchFilters] = useState< + Omit + >({ + startDate, + endDate, + }); + + useEffect(() => { + const startDate = searchParams.get('startDate') || ''; + const endDate = searchParams.get('endDate') || ''; + setSearchFilters({ + startDate, + endDate, + }); + }, [searchParams, setSearchFilters]); + + const handleSubmitChangeParams = (e: React.FormEvent) => { + e.preventDefault(); + + const { startDate, endDate } = searchFilters; + + const newParams = new URLSearchParams(searchParams); + + if (startDate && !endDate) { + return handleModalOpen(MODAL_MESSAGE.endDateEmpty); + } else if (!startDate && endDate) { + return handleModalOpen(MODAL_MESSAGE.startDateEmpty); + } else if (startDate && endDate) { + newParams.set('startDate', startDate); + newParams.set('endDate', endDate); + } else { + newParams.delete('startDate'); + newParams.delete('endDate'); + } + + setSearchParams(newParams); + }; + + const handleChangeDate = ( + e: React.ChangeEvent, + key: SearchParamsInquiryKeyType + ) => { + const value = e.currentTarget.value; + + setSearchFilters((prev) => { + switch (key) { + case 'startDate': { + if (prev.endDate !== '' && prev.endDate < value) { + handleModalOpen(MODAL_MESSAGE.startDateInvalid); + return prev; + } + return { ...prev, startDate: value }; + } + case 'endDate': { + if (prev.startDate !== '' && prev.startDate > value) { + handleModalOpen(MODAL_MESSAGE.endDateInvalid); + return prev; + } + return { ...prev, endDate: value }; + } + default: { + return prev; + } + } + }); + }; + + const handleClickInit = () => { + setSearchParams({}); + setSearchFilters({ startDate: '', endDate: '' }); + }; + + return ( + + + + + handleChangeDate(e, 'startDate')} + /> + ~ + handleChangeDate(e, 'endDate')} + /> + + + {(userId || startDate || endDate) && ( + + + + )} + + + + + + + {message} + + + ); +} diff --git a/src/components/admin/previewComponent/inquiresPreview/InquiresPreview.tsx b/src/components/admin/previewComponent/inquiresPreview/InquiresPreview.tsx index 57cea21c..030a8177 100644 --- a/src/components/admin/previewComponent/inquiresPreview/InquiresPreview.tsx +++ b/src/components/admin/previewComponent/inquiresPreview/InquiresPreview.tsx @@ -4,9 +4,16 @@ import Avatar from '../../../common/avatar/Avatar'; import { ADMIN_ROUTE } from '../../../../constants/routes'; import arrow_right from '../../../../assets/ArrowRight.svg'; import Spinner from '../../../user/mypage/Spinner'; +import { AdminInquiryChangeSearchParams } from '../../../../models/inquiry'; const InquiresPreview = () => { - const { allInquiriesData, isLoading, isFetching } = useGetAllInquiries(); + const childSearchParams: AdminInquiryChangeSearchParams = { + userId: '', + startDate: '', + endDate: '', + }; + const { allInquiriesData, isLoading, isFetching } = + useGetAllInquiries(childSearchParams); if (isLoading || isFetching) { return ( diff --git a/src/components/user/mypage/myProfile/editProfile/ProfileGithubSuccess.tsx b/src/components/user/mypage/myProfile/editProfile/ProfileGithubSuccess.tsx index 05436891..eb856cd0 100644 --- a/src/components/user/mypage/myProfile/editProfile/ProfileGithubSuccess.tsx +++ b/src/components/user/mypage/myProfile/editProfile/ProfileGithubSuccess.tsx @@ -4,31 +4,23 @@ import { ROUTES } from '../../../../../constants/routes'; import { useModal } from '../../../../../hooks/useModal'; import Modal from '../../../../common/modal/Modal'; import { MODAL_MESSAGE } from '../../../../../constants/user/modalMessage'; +import { useGithubLink } from '../../../../../hooks/user/useMyInfo'; export default function ProfileGithubSuccess() { const navigate = useNavigate(); const [searchParams] = useSearchParams(); const { isOpen, message, handleModalOpen, handleModalClose } = useModal(); + const { patchGithubLinkMutate } = useGithubLink(handleModalOpen); useEffect(() => { (async () => { const githubUrl = searchParams.get('githubUrl'); - console.log(githubUrl); + if (githubUrl) { - handleModalOpen(MODAL_MESSAGE.githubProfileSuccess); - setTimeout(() => { - navigate(`${ROUTES.mypage}/${ROUTES.mypageEdit}`, { - state: { githubUrl }, - }); - }, 1000); - } else { - handleModalOpen(MODAL_MESSAGE.githubProfileFail); - setTimeout(() => { - navigate(`${ROUTES.mypage}/${ROUTES.mypageEdit}`); - }, 1000); + patchGithubLinkMutate(githubUrl); } })(); - }, [searchParams, handleModalOpen, navigate]); + }, [searchParams, handleModalOpen, navigate, patchGithubLinkMutate]); return ( diff --git a/src/constants/user/modalMessage.ts b/src/constants/user/modalMessage.ts index 09d59fe1..bd355ecc 100644 --- a/src/constants/user/modalMessage.ts +++ b/src/constants/user/modalMessage.ts @@ -32,4 +32,8 @@ export const MODAL_MESSAGE = { emptySkillImg: '스킬 이미지를 추가하세요.', githubProfileFail: '깃허브 프로필 등록에 실패했습니다. 다시 시도해주세요.', githubProfileSuccess: '깃허브 프로필이 성공적으로 등록되었습니다!', + startDateEmpty: '시작일을 선택해주세요.', + startDateInvalid: '시작일은 종료일 이전으로 설정해주세요.', + endDateEmpty: '종료일을 선택해주세요.', + endDateInvalid: '종료일은 시작일 이후로 설정해주세요.', } as const; diff --git a/src/hooks/admin/useGetAllInquiries.ts b/src/hooks/admin/useGetAllInquiries.ts index 5f7fc046..bcd9b5ef 100644 --- a/src/hooks/admin/useGetAllInquiries.ts +++ b/src/hooks/admin/useGetAllInquiries.ts @@ -1,15 +1,18 @@ import { useQuery } from '@tanstack/react-query'; import { Inquiries } from '../queries/keys'; import { getAllInquiries } from '../../api/admin/customerService/inquiry.api'; +import { AdminInquiryChangeSearchParams } from '../../models/inquiry'; -export const useGetAllInquiries = () => { +export const useGetAllInquiries = ( + childSearchParams: AdminInquiryChangeSearchParams +) => { const { data: allInquiriesData, isLoading, isFetching, } = useQuery({ - queryKey: [Inquiries.allInquiries], - queryFn: () => getAllInquiries(), + queryKey: [Inquiries.allInquiries, childSearchParams], + queryFn: () => getAllInquiries(childSearchParams), }); return { allInquiriesData, isLoading, isFetching }; diff --git a/src/hooks/user/useMyInfo.ts b/src/hooks/user/useMyInfo.ts index 74279aa0..9860023f 100644 --- a/src/hooks/user/useMyInfo.ts +++ b/src/hooks/user/useMyInfo.ts @@ -8,6 +8,7 @@ import { getMyAppliedStatusList, getMyInfo, getMyJoinedProjectList, + patchGithubLink, patchMyProfileImg, putMyInfo, } from '../../api/mypage.api'; @@ -88,6 +89,32 @@ export const useUploadProfileImg = ( return { uploadProfileImg }; }; +export const useGithubLink = (onModalOpen: (message: string) => void) => { + const queryClient = useQueryClient(); + const navigate = useNavigate(); + const isLoggedIn = useAuthStore.getState().isLoggedIn; + + const { mutate: patchGithubLinkMutate } = useMutation< + void, + AxiosError, + string + >({ + mutationFn: (githubUrl: string) => patchGithubLink(githubUrl), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: myInfoKey.myProfile }); + onModalOpen(MODAL_MESSAGE.githubProfileSuccess); + }, + onError: () => { + onModalOpen(MODAL_MESSAGE.githubProfileFail); + setTimeout(() => { + navigate(`${ROUTES.mypage}/${ROUTES.mypageEdit}`); + }, 1000); + }, + }); + + return { patchGithubLinkMutate, isLoggedIn }; +}; + export const useMyJoinedProjectList = () => { const isLoggedIn = useAuthStore.getState().isLoggedIn; diff --git a/src/models/inquiry.ts b/src/models/inquiry.ts index 5b2e46e1..6bb4b4fd 100644 --- a/src/models/inquiry.ts +++ b/src/models/inquiry.ts @@ -7,6 +7,12 @@ export interface InquiryFormData { content: string; } +export interface AdminInquiryChangeSearchParams { + userId: string; + startDate: string; + endDate: string; +} + export interface AdminInquiry { id: number; title: string; diff --git a/src/routes/AdminRoutes.tsx b/src/routes/AdminRoutes.tsx index 725eb2f0..2ec80a06 100644 --- a/src/routes/AdminRoutes.tsx +++ b/src/routes/AdminRoutes.tsx @@ -18,9 +18,9 @@ const ActivityLog = lazy( const Notifications = lazy( () => import('../components/user/mypage/notifications/Notifications') ); -const AdminReportDetail = lazy( - () => import('../components/admin/adminUserReport/AdminReportDetail') -); +// const AdminReportDetail = lazy( +// () => import('../components/admin/adminUserReport/AdminReportDetail') +// ); const Sidebar = lazy( () => import('../components/common/admin/sidebar/AdminSidebar') ); @@ -225,10 +225,10 @@ export const AdminRoutes = () => { path: ADMIN_ROUTE.reports, element: , }, - { - path: `${ADMIN_ROUTE.reports}/:id`, - element: , - }, + // { + // path: `${ADMIN_ROUTE.reports}/:id`, + // element: , + // }, { path: ADMIN_ROUTE.inquiries, element: ,