Skip to content

Commit e536380

Browse files
authored
feat(member): 문의 페이지 생성 (#280)
* feat(member): 문의 페이지 UI 스타일 개발(#274) * feat(member): 문의 페이지 UI 스타일 개발(#274) * feat(member): 문의 페이지 UI 스타일 개발(#274) * docs(member): 불필요한 주석 제거 * refactor(member): head.ts로 Table.Header 상수 관리 추가(#274) * refactor(member): self-closing 가능한 태그 정리(#274) * refactor: 불필요한 태그 삭제(#274) * refactor: 불필요한 상수 삭제(#274) * refactor: 불필요한 export 삭제(#274) * refactor: 문의 한글 맵핑 적용 및 상수 관리(#274) * refactor: static 값들을 mock/data로 이동(#274) * refactor: 반복되는 데이터 구조분해 적용(#274) * refactor(member): 불필요한 중복 코드 제거(#274) * style(member): 문의 리스트 테이블 스타일 변경(#274) * refactor(member): 매직 넘버 제거(#274) * refactor(member): 반복되는 데이터 구조분해 적용(#274) * docs(member): 문의 유의사항 문구 변경(#274) * refactor(member): static 값들을 mock/data로 이동(#274) * refactor(member): 중첩 삼항연산자 제거(#274) * refactor(member): faq 카테고리 변경(#274) * docs(member): 오탈자 수정(#274) * docs(member): 오탈자 수정(#274) * docs(member): 오탈자 수정 * refactor(member): support 페이지를 membershipFee로 리네이밍 * refactor(member): support 페이지를 membershipFee로 리네이밍 * refactor(member): imquiry 페이지를 support로 리네이밍 * refactor(member): support 페이지를 membershipFee로 리네이밍 * refactor(member): imquiry 페이지를 support로 리네이밍 * refactor(member): inquiry 페이지를 support로 리네이밍 * refactor(member): 도서관 페이지에서 버튼 클릭 시 경로를 membershipFee로 변경 * refactor(member): '나의 문의' 세션 위치 변경 (문의 목록 > 마이페이지) * refactor(member): 테이블 가로 스크롤 지원 * refactor(member): 레이아웃 개선 및 버튼 정렬 수정 * docs(member): faq 문구 변경 * docs(member): 문의 안내 문구 변경 * refactor(member): 오류 이미지 업로더 항상 표시하도록 수정 * refactor(member): 문의 작성 페이지에 문의 네비게이션 추가 * feat(member): support 페이지 mock 데이터를 API 연동으로 교체 * refactor(member): supportTableRow를 컴포넌트 및 훅으로 분리 * chore(member): support 테이블 헤더 '카테고리 / 상태'를 '상태'로 수정 * chore(member): support 옵션 상수에서 'INQURIY' 오탈자를 'INQUIRY'로 수정 * feat(member): 답변 상태 및 제목/내용 길이 제한 상수 정의 * refactor(member): support 타입 중복 개선 및 통합 * refactor(member): getSupports 네이밍을 getSupportList로 변경 * refactor(member): 엔드포인트 UPLOADEDFILE_SUPPORT '/' 제거 * refactor(member): support 타입 불필요한 옵셔널 정리 * refactor(member): 에러 메시지 DEFAULT로 변경 * refactor(member): 불필요한 useMemo 제거 * refactor(member): 불필요한 useCallback 제거 및 최적화 * refactor(member): useSupportWriteMutation 무효화 최적화 * refactor(member): 쿼리스트링 함수 정의 및 HistorySection 경로 상수화 * refactor(member): import type 적용 * docs(member): support 문구 변경 * refactor(member): 시멘틱 태그 처리 * refactor(member): early return 적용 및 최적화 * refactor(member): 불필요한 주석 제거 * Revert "refactor(member): support 타입 불필요한 옵셔널 정리" This reverts commit 0aa9ed8. * refactor(member): support 타입 불필요한 옵셔널 정리 * refactor(member): support state 상수 관리 * refactor(member): useSupportListQuery, useSupportListUi 제거 및 최적화 * refactor(member): createURLWithQueryString 제거 및 createPagination으로 교체 * refactor(member): supportListSection 의존성 및 안정성 개선 * refactor(member): 답변 시간이 맞지 않았던 버그 수정 * refactor(member): 문의 수정 시에 익명 여부를 선택할 수 있었던 문제 수정
1 parent de681a8 commit e536380

59 files changed

Lines changed: 2071 additions & 83 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/member/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"eslint-plugin-react-refresh": "^0.4.5",
4949
"postcss": "^8.4.33",
5050
"prettier": "^3.1.1",
51+
"prettier-plugin-tailwindcss": "^0.5.13",
5152
"tailwindcss": "^3.4.1",
5253
"typescript": "^5.2.2",
5354
"vite": "^5.0.8"

apps/member/src/api/support.ts

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import { createPagination } from '@clab-platforms/utils';
2+
3+
import { END_POINT } from '@constants/api';
4+
5+
import type {
6+
BaseResponse,
7+
ResponsePagination,
8+
WithPaginationParams,
9+
} from '@type/api';
10+
import type {
11+
Support,
12+
SupportAnswerRequest,
13+
SupportDetail,
14+
SupportWriteRequest,
15+
} from '@type/support';
16+
17+
import { server } from './server';
18+
import {
19+
postFilesSupportImages,
20+
postUploadedFileBoard,
21+
uploadFiles,
22+
} from './uploadedFile';
23+
24+
export interface PostSupportsWriteParams extends SupportWriteRequest {
25+
file?: File;
26+
}
27+
export interface PatchSupportsParams extends SupportWriteRequest {
28+
id: number;
29+
file?: File;
30+
}
31+
export interface PostAnswersParams extends SupportAnswerRequest {
32+
id: number;
33+
}
34+
export interface PatchAnswersParams extends SupportAnswerRequest {
35+
id: number;
36+
}
37+
38+
/**
39+
* 내가 작성한 문의 조회
40+
*/
41+
export async function getMySupports({ page, size }: WithPaginationParams) {
42+
const { data } = await server.get<ResponsePagination<Support>>({
43+
url: createPagination(END_POINT.MY_SUPPORTS, { page, size }),
44+
});
45+
46+
return data;
47+
}
48+
49+
/**
50+
* 문의 목록 조회
51+
*/
52+
export async function getSupportList({ page, size }: WithPaginationParams) {
53+
const { data } = await server.get<ResponsePagination<Support>>({
54+
url: createPagination(END_POINT.SUPPORTS, { page, size }),
55+
});
56+
return data;
57+
}
58+
59+
/**
60+
* 문의 작성
61+
*/
62+
export async function postSupportsWrite(data: PostSupportsWriteParams) {
63+
if (data.file) {
64+
const files = await uploadFiles([data.file], postUploadedFileBoard);
65+
if (!files?.[0]?.fileUrl) {
66+
throw new Error('파일 업로드에 실패했습니다.');
67+
}
68+
data.imageUrl = files[0].fileUrl;
69+
}
70+
71+
return server.post<SupportWriteRequest, BaseResponse<number>>({
72+
url: END_POINT.SUPPORTS,
73+
body: data,
74+
});
75+
}
76+
77+
/**
78+
* 문의 상세 조회
79+
*/
80+
export function getSupportsDetail(id: number) {
81+
return server.get<BaseResponse<SupportDetail>>({
82+
url: END_POINT.SUPPORTS_ITEM(id),
83+
});
84+
}
85+
86+
/**
87+
* 문의 수정
88+
*/
89+
export async function patchSupports({
90+
id,
91+
file,
92+
...data
93+
}: PatchSupportsParams) {
94+
if (file) {
95+
const uploadedFile = await uploadFiles([file], postFilesSupportImages);
96+
if (!uploadedFile?.[0]?.fileUrl) {
97+
throw new Error('파일 업로드에 실패했습니다.');
98+
}
99+
data.imageUrl = uploadedFile[0].fileUrl;
100+
}
101+
102+
return server.patch<SupportWriteRequest, BaseResponse<number>>({
103+
url: END_POINT.SUPPORTS_ITEM(id),
104+
body: data,
105+
});
106+
}
107+
108+
/**
109+
* 문의 삭제
110+
*/
111+
export function deleteSupports(id: number) {
112+
return server.del<never, BaseResponse<number>>({
113+
url: END_POINT.SUPPORTS_ITEM(id),
114+
});
115+
}
116+
117+
/**
118+
* 답변 작성
119+
*/
120+
export async function postAnswersWrite({ id, ...data }: PostAnswersParams) {
121+
return server.post<SupportAnswerRequest, BaseResponse<number>>({
122+
url: END_POINT.ANSWERS_ITEM(id),
123+
body: data,
124+
});
125+
}
126+
127+
/**
128+
* 답변 수정
129+
*/
130+
export async function patchAnswers({ id, ...data }: PatchAnswersParams) {
131+
return server.patch<SupportAnswerRequest, BaseResponse<number>>({
132+
url: END_POINT.ANSWERS_ITEM(id),
133+
body: data,
134+
});
135+
}

apps/member/src/api/uploadedFile.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,3 +154,15 @@ export async function postUploadedFileSubmit({
154154

155155
return data;
156156
}
157+
158+
/**
159+
* 문의 이미지 업로드
160+
*/
161+
export async function postFilesSupportImages(multipartFile: FormData) {
162+
const { data } = await server.post<FormData, BaseResponse<ResponseFile[]>>({
163+
url: createURL(END_POINT.UPLOADEDFILE_SUPPORT, STORAGE_PERIOD(5)), // 문의 이미지는 5년간 보관
164+
body: multipartFile,
165+
});
166+
167+
return data;
168+
}

apps/member/src/components/common/Nav/Nav.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,17 @@ const Nav = () => {
7777
>
7878
도서관
7979
</Menubar.Item>
80+
<Menubar.Item
81+
selected={pathName.startsWith(PATH.MEMBERSHIP_FEE)}
82+
onClick={() => handleMenubarItemClick(PATH.MEMBERSHIP_FEE)}
83+
>
84+
회비
85+
</Menubar.Item>
8086
<Menubar.Item
8187
selected={pathName.startsWith(PATH.SUPPORT)}
8288
onClick={() => handleMenubarItemClick(PATH.SUPPORT)}
8389
>
84-
회비
90+
문의
8591
</Menubar.Item>
8692
{data.roleLevel! >= ROLE_LEVEL.ADMIN && (
8793
<>

apps/member/src/components/common/Sidebar/Sidebar.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,17 @@ const Sidebar = () => {
8686
>
8787
도서관
8888
</Menubar.Item>
89+
<Menubar.Item
90+
selected={pathName.startsWith(PATH.MEMBERSHIP_FEE)}
91+
onClick={() => handleMenubarItemClick(PATH.MEMBERSHIP_FEE)}
92+
>
93+
회비
94+
</Menubar.Item>
8995
<Menubar.Item
9096
selected={pathName.startsWith(PATH.SUPPORT)}
9197
onClick={() => handleMenubarItemClick(PATH.SUPPORT)}
9298
>
93-
회비
99+
문의
94100
</Menubar.Item>
95101
{data.roleLevel! >= ROLE_LEVEL.ADMIN && (
96102
<>

apps/member/src/components/membership/MembershipCategoryBadge/MembershipCategoryBadge.tsx renamed to apps/member/src/components/membership/membershipCategoryBadge/MembershipCategoryBadge.tsx

File renamed without changes.

apps/member/src/components/support/SupportHistorySection.tsx renamed to apps/member/src/components/membership/membershipFee/MembershipFeeHistorySection.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Table } from '@clab-platforms/design-system';
33
import ActionButton from '@components/common/ActionButton/ActionButton';
44
import Pagination from '@components/common/Pagination/Pagination';
55
import Section from '@components/common/Section/Section';
6-
import MembershipStatusBadge from '@components/membership/MembershipStatusBadge/MembershipStatusBadge';
6+
import MembershipStatusBadge from '@components/membership/membershipStatusBadge/MembershipStatusBadge';
77

88
import { ROLE_LEVEL } from '@constants/state';
99
import { usePagination } from '@hooks/common/usePagination';
@@ -23,7 +23,7 @@ interface Props {
2323
hasPermission?: boolean;
2424
}
2525

26-
export function SupportHistorySection({
26+
export function MembershipFeeHistorySection({
2727
title,
2828
size: defaultSize,
2929
withPagination,
@@ -98,7 +98,7 @@ export function SupportHistorySection({
9898
);
9999
}
100100

101-
export function SupportHistorySectionSkeleton() {
101+
export function MembershipFeeHistorySectionSkeleton() {
102102
return (
103103
<Section className="h-96 animate-pulse">
104104
<Section.Header title={TITLE} />

apps/member/src/components/membership/MembershipStatusBadge/MembershipStatusBadge.tsx renamed to apps/member/src/components/membership/membershipStatusBadge/MembershipStatusBadge.tsx

File renamed without changes.

apps/member/src/constants/api.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ export const END_POINT = {
7979
`/v1/files/notices/${groupId}`,
8080
UPLOADEDFILE_ACTIVITY_ASSIGNMENT: (groupId: number) =>
8181
`/v1/files/assignments/${groupId}`,
82+
UPLOADEDFILE_SUPPORT: '/v1/files/supports',
8283
// -- 활동그룹 멤버
8384
ACTIVITY_GROUP_MEMBER: (id: number) => `/v1/activity-group/member/${id}`,
8485
ACTIVITY_GROUP_MEMBER_MY: `/v1/activity-group/member/my`,
@@ -114,6 +115,11 @@ export const END_POINT = {
114115
RECRUITMENT: `/v1/recruitments`,
115116
// -- 해시태그
116117
HASHTAG: `/v1/hashtags`,
118+
// -- 문의
119+
SUPPORTS: `/v1/supports`,
120+
SUPPORTS_ITEM: (supportId: number) => `/v1/supports/${supportId}`,
121+
MY_SUPPORTS: `/v1/supports/my-supports`,
122+
ANSWERS_ITEM: (supportId: number) => `/v1/supports/${supportId}/answer`,
117123
} as const;
118124

119125
/**

apps/member/src/constants/head.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export const TABLE_HEAD = {
4040
'생성',
4141
] as const,
4242
MEMBER_MANAGE_TABLE: ['번호', '학번', '이름', '상태'],
43+
SUPPORT_TABLE: ['번호', '제목', '문의자', '문의일', '상태'],
4344
} as const;
4445
/**
4546
* 관리자 페이지에서 기능을 나타내는 상수

0 commit comments

Comments
 (0)