Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 7 additions & 2 deletions src/api/admin/customerService/inquiry.api.ts
Original file line number Diff line number Diff line change
@@ -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<ApiAdminInquiry>(`/inquiry`);
const response = await httpClient.get<ApiAdminInquiry>(`/inquiry`, {
params: childSearchParams,
});
return response.data.data;
} catch (e) {
console.error('전체 문의 조회 에러', e);
Expand Down
10 changes: 10 additions & 0 deletions src/api/mypage.api.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { ApiCommonBasicType } from '../models/apiCommon';
import type {
ApiUserInfo,
ApiUserInfoImg,
Expand Down Expand Up @@ -53,6 +54,15 @@ export const patchMyProfileImg = async (file: File) => {
}
};

export const patchGithubLink = async (githubUrl: string) => {
try {
await httpClient.patch<ApiCommonBasicType>('/user/github', { githubUrl });
} catch (error) {
console.error('프로필 깃허브 업데이트: ', error);
throw error;
}
};

export const getMyJoinedProjectList = async () => {
try {
const response = await httpClient.get<ApiJoinedProject>(
Expand Down
28 changes: 27 additions & 1 deletion src/components/admin/adminInquiry/AdminInquiry.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
24 changes: 23 additions & 1 deletion src/components/admin/adminInquiry/AdminInquiry.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -8,12 +9,33 @@ interface AdminInquiryProps {
}

export default function AdminInquiry({ list }: AdminInquiryProps) {
const [searchParams, setSearchParams] = useSearchParams();

const handleClickLookupUser = (e: React.MouseEvent<HTMLButtonElement>) => {
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 (
<S.AdminInquiryContainer to={`${ADMIN_ROUTE.detail}/${list.id}`}>
<S.AdminInquiryWrapper>
<S.AdminInquiryCategory>[{list.category}]</S.AdminInquiryCategory>
<S.AdminInquiryTitle>{list.title}</S.AdminInquiryTitle>
<S.AdminInquiryUser>{list.user.nickname}</S.AdminInquiryUser>
<S.AdminInquiryUserWrapper>
<S.AdminInquiryUser onClick={handleClickLookupUser}>
{list.user.nickname}
</S.AdminInquiryUser>
</S.AdminInquiryUserWrapper>
<S.AdminInquiryDate>{list.createdAt}</S.AdminInquiryDate>
<S.AdminInquiryState $hasAnswer={list.state}>
{list.state ? '답변완료' : '확인중'}
Expand Down
3 changes: 2 additions & 1 deletion src/components/admin/adminInquiry/AdminInquiryList.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
15 changes: 14 additions & 1 deletion src/components/admin/adminInquiry/AdminInquiryList.tsx
Original file line number Diff line number Diff line change
@@ -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 (
Expand All @@ -18,6 +30,7 @@ export default function AdminInquiryList() {

return (
<S.AdminInquiryListContainer>
<AdminInquiryListLookup />
<S.AdminInquiryListWrapper>
{allInquiriesData.map((list) => (
<AdminInquiry key={list.id} list={list} />
Expand Down
67 changes: 67 additions & 0 deletions src/components/admin/adminInquiry/AdminInquiryListLookup.styled.ts
Original file line number Diff line number Diff line change
@@ -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};
`;
128 changes: 128 additions & 0 deletions src/components/admin/adminInquiry/AdminInquiryListLookup.tsx
Original file line number Diff line number Diff line change
@@ -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<AdminInquiryChangeSearchParams, 'userId'>
>({
startDate,
endDate,
});

useEffect(() => {
const startDate = searchParams.get('startDate') || '';
const endDate = searchParams.get('endDate') || '';
setSearchFilters({
startDate,
endDate,
});
}, [searchParams, setSearchFilters]);

const handleSubmitChangeParams = (e: React.FormEvent<HTMLFormElement>) => {
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<HTMLInputElement>,
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 (
<S.LookupContainer>
<S.LookupWrapper onSubmit={handleSubmitChangeParams}>
<S.LookupUser
type='text'
value={nickname}
placeholder='닉네임을 클릭하세요.'
readOnly
/>
<S.LookupDateWrapper>
<S.LookupStartDate
type='date'
value={searchFilters.startDate}
onChange={(e) => handleChangeDate(e, 'startDate')}
/>
<S.LookupJoinDate> ~ </S.LookupJoinDate>
<S.LookupEndDate
type='date'
value={searchFilters.endDate}
onChange={(e) => handleChangeDate(e, 'endDate')}
/>
</S.LookupDateWrapper>
<S.LookupIconWrapper>
{(userId || startDate || endDate) && (
<S.IconDefault type='button' onClick={handleClickInit}>
<XMarkIcon />
</S.IconDefault>
)}
<S.IconSearch type='submit'>
<MagnifyingGlassIcon />
</S.IconSearch>
</S.LookupIconWrapper>
</S.LookupWrapper>
<Modal isOpen={isOpen} onClose={handleModalClose}>
{message}
</Modal>
</S.LookupContainer>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
Loading