Skip to content

Commit

Permalink
feat(member): 활동 그룹 관리 및 과제 제출 조회 및 피드백 생성 (#201)
Browse files Browse the repository at this point in the history
* refactor(member): 활동 그룹 카테고리 상수화, 타입 수정 (#39)

* refactor(member): 반복되는 요소 컴포넌트화 (#39)

* feat(member): 활동 그룹 공지 관리 컴포넌트 생성 및 api 연동 (#39)

* refactor(member): 게시판 관리 테이블 모든 카테고리에서 사용 가능하도록 변경 (#39)

* feat(member): 주차별 활동 관리 api 연동 (#39)

* feat(member): 과제 추가 컴포넌트 생성 (#39)

* fix(member): 각 주차별 활동에 맞는 과제만 나타나도록 수정 (#39)

* feat(member): 과제 첨부파일 조회 및 레이아웃 수정 (#39)

* feat(member): 과제 제출 조회 확인 section 생성 (#39)

* feat(member): 피드백 입력 모달 추가 (#39)

* refactor(member): 멤버 활동 신청 컴포넌트 및 연동 리팩토링 (#39)

* chore(member): change @clab/- to @clab-platforms (#39)

* chore(member): change @clab/- to @clab-platforms (#39)

* chore(member): change @clab/- to @clab-platforms (#39)

* Delete .changeset/friendly-countries-love.md

* Create early-crews-fly.md

* refactor(member): props 이름 변경 및 Options Object Pattern으로 변경

* refactor(member): 활동 멤버 role, 활동 status 상수화

* refactor(member): 배열 생성 키워드 수정

* refactor(member): 멤버 상태 상수화 및 관련 상수명 수정

* feat(member): 활동 그룹 정보 수정 api 연동

* refactor(member): 신규 데이터 조회 관련 쿼리키 개선

* refactor(member): 사용 데이터 변경

* refactor(member): 게시판 타입 상수화

* refactor(member): 데이터 반환 수정, 쿼리키 수정

---------

Co-authored-by: GwanSik Kim <[email protected]>
  • Loading branch information
Jeong-Ag and gwansikk authored Aug 17, 2024
1 parent 4dbe105 commit 5259e47
Show file tree
Hide file tree
Showing 36 changed files with 1,399 additions and 214 deletions.
5 changes: 5 additions & 0 deletions .changeset/early-crews-fly.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@clab-platforms/member": minor
---

feat(member): 활동 그룹 관리 및 과제 제출 조회 및 피드백 생성
162 changes: 140 additions & 22 deletions apps/member/src/api/activity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ import { groupBoardParser } from '@utils/group';
import type {
ActivityApplyMemberType,
ActivityBoardType,
ActivityGroupBoardCategoryType,
ActivityGroupBoardParserType,
ActivityGroupCategoryType,
ActivityGroupCreateItem,
ActivityGroupItem,
ActivityGroupMemberMyType,
ActivityGroupMemberType,
ActivityGroupStatusType,
ActivityPhotoItem,
ActivityPhotosBody,
Expand Down Expand Up @@ -48,8 +49,8 @@ export interface PostActivityBoardParams {

export interface PatchActivityBoardParams {
activityGroupBoardId: number;
groupId: number;
groupBoardId: number;
groupId?: number;
groupBoardId?: number;
body: SubmitBoardType;
files?: FormData;
}
Expand All @@ -60,24 +61,34 @@ export interface PostActivityPhotoParams {
file: File;
}

export interface PostActivityGroupParams {
category: ActivityGroupCategoryType;
subject: string;
name: string;
content: string;
imageUrl?: string;
curriculum?: string;
startDate?: string;
endDate?: string;
techStack?: string;
githubUrl?: string;
export interface GetActivityBoardByCategoryParams {
activityGroupId: number;
category: ActivityGroupBoardCategoryType;
page: number;
size: number;
}

export interface GetActivityBoardByParentParams {
parentId: number;
page: number;
size: number;
}
export interface GetActivityGroupMemberParams {
activityGroupId: number;
page: number;
size: number;
}

export interface PatchActivityGroupParams {
activityGroupId: number;
activityGroupStatus: ActivityGroupStatusType;
}

export interface PatchActivityGroupAdminParams {
activityGroupId: number;
body: ActivityGroupCreateItem;
}

/**
* 활동 사진 조회
*/
Expand Down Expand Up @@ -118,11 +129,19 @@ export async function getActivityGroupDetail(id: number) {
},
);

const { notices, activities } = groupBoardParser(data.activityGroupBoards); // 게시판 분류 파싱
const { notices, activities, assignments } = groupBoardParser(
data.activityGroupBoards,
); // 게시판 분류 파싱
data.notices = notices;
data.activities = activities;
data.assignments = assignments;

return data;
return {
...data,
notices,
activities,
assignments,
};
}

/**
Expand All @@ -145,7 +164,7 @@ export async function postActivityGroupMemberApply({
}

/**
* 상태별 활동 멤버 조회
* 활동 신청자 및 지원서 조회
*/
export async function getActivityGroupApplyByStatus(
activityGroupId: number,
Expand Down Expand Up @@ -189,7 +208,7 @@ export async function patchActivityGroupMemberApply({
memberId,
status,
}: PatchActivityGroupMemberApplyParams) {
const { data } = await server.patch<never, BaseResponse<string>>({
const { data } = await server.patch<never, BaseResponse<number>>({
url: createPagination(END_POINT.ACTIVITY_GROUP_ADMIN_ACCEPT, {
activityGroupId,
memberId,
Expand Down Expand Up @@ -257,7 +276,7 @@ export async function postActivityBoard({

const { data } = await server.post<
SubmitBoardType,
BaseResponse<{ id: number; parentId: number }>
BaseResponse<{ id: number; groupId: number; parentId: number }>
>({
url: createPagination(END_POINT.ACTIVITY_GROUP_BOARD, params),
body: {
Expand Down Expand Up @@ -294,7 +313,7 @@ export async function patchActivityBoard({

const { data } = await server.patch<
SubmitBoardType,
BaseResponse<{ id: number; parentId: number }>
BaseResponse<{ id: number; groupId: number; parentId: number }>
>({
url: createPagination(END_POINT.ACTIVITY_GROUP_BOARDS, {
activityGroupBoardId,
Expand Down Expand Up @@ -325,6 +344,68 @@ export async function postActivityPhoto(body: PostActivityPhotoParams) {

return data;
}

/**
* 활동 내 카테고리 게시판 조회
*/
export async function getActivityBoardByCategory({
activityGroupId,
category,
page,
size,
}: GetActivityBoardByCategoryParams) {
const { data } = await server.get<ResponsePagination<ActivityBoardType>>({
url: createPagination(END_POINT.ACTIVITY_GROUP_BOARD_BY_CATEGORY, {
activityGroupId,
category,
page,
size,
}),
});

return data;
}

/**
* 활동 그룹 게시판 계층 구조적 조회
*/
export async function getActivityBoardByParent({
parentId,
page,
size,
}: GetActivityBoardByParentParams) {
const { data } = await server.get<ResponsePagination<ActivityBoardType>>({
url: createPagination(END_POINT.ACTIVITY_GROUP_BOARD_BY_PARENT, {
parentId,
page,
size,
}),
});

return data;
}

/**
* 활동 멤버 조회
*/
export async function getActivityGroupMember({
activityGroupId,
page,
size,
}: GetActivityGroupMemberParams) {
const { data } = await server.get<
ResponsePagination<ActivityGroupMemberType>
>({
url: createPagination(END_POINT.ACTIVITY_GROUP_MEMBER_MEMBERS, {
activityGroupId,
page,
size,
}),
});

return data;
}

/**
* 키워드 사진 검색
*/
Expand Down Expand Up @@ -355,7 +436,7 @@ export async function getSearchImage(keyword: string) {
/**
* 활동 생성
*/
export async function postActivityGroup(body: PostActivityGroupParams) {
export async function postActivityGroup(body: ActivityGroupCreateItem) {
const { data } = await server.post<
ActivityGroupCreateItem,
BaseResponse<number>
Expand Down Expand Up @@ -385,7 +466,10 @@ export async function patchActivityGroup({
activityGroupId,
activityGroupStatus,
}: PatchActivityGroupParams) {
const { data } = await server.patch<never, BaseResponse<number>>({
const { data } = await server.patch<
never,
BaseResponse<{ id: number; status: ActivityGroupStatusType }>
>({
url: createPagination(
END_POINT.ACTIVITY_GROUP_ADMIN_MANAGE(activityGroupId),
{
Expand All @@ -396,3 +480,37 @@ export async function patchActivityGroup({

return data;
}

/**
* 활동 수정
*/
export async function patchActivityGroupAdmin({
activityGroupId,
body,
}: PatchActivityGroupAdminParams) {
const { data } = await server.patch<
ActivityGroupCreateItem,
BaseResponse<number>
>({
url: createURL(END_POINT.ACTIVITY_GROUP_ADMIN_DETAIL(activityGroupId)),
body,
});

return data;
}

/**
* 활동 게시판 삭제
*/
export async function deleteActivityGroupBoards(activityGroupBoardId: number) {
const { data } = await server.del<
never,
BaseResponse<{ id: number; groupId: number; parentId: number }>
>({
url: createPagination(END_POINT.ACTIVITY_GROUP_BOARDS, {
activityGroupBoardId,
}),
});

return data;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { useRef, useState } from 'react';

import { Button, Grid, Input } from '@clab-platforms/design-system';

import { Section } from '@components/common/Section';
import Textarea from '@components/common/Textarea/Textarea';

import { FORM_DATA_KEY } from '@constants/api';
import { ACTIVITY_BOARD_CATEGORY_STATE } from '@constants/state';
import useToast from '@hooks/common/useToast';
import { useActivityGroupBoardMutation, useMyProfile } from '@hooks/queries';

interface Props {
parentId: number;
activityGroupId: number;
}

const ActivityAssignmentEditor = ({ parentId, activityGroupId }: Props) => {
const toast = useToast();
const [board, setBoard] = useState({
title: '',
content: '',
dueDateTime: '',
fileUrls: [],
});

const { data: myProfile } = useMyProfile();

const uploaderRef = useRef<HTMLInputElement>(null);
const { activityGroupBoardMutate } = useActivityGroupBoardMutation();

const handlePostChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) => {
const { name, value } = e.target;
setBoard((prev) => ({ ...prev, [name]: value }));
};
const handleAddAssignmentClick = () => {
const formData = new FormData();
const file = uploaderRef.current?.files?.[0];

if (!board.title || !board.content || !board.dueDateTime)
return toast({
state: 'error',
message: '제목, 내용, 종료 일시는 필수 입력 요소입니다.',
});
if (file) {
formData.append(FORM_DATA_KEY, file);
}

activityGroupBoardMutate({
parentId: parentId,
activityGroupId: activityGroupId,
memberId: myProfile.id,
body: {
category: ACTIVITY_BOARD_CATEGORY_STATE.ASSIGNMENT,
...board,
},
files: file ? formData : undefined,
});
};

return (
<Section>
<Section.Header title="과제 관리">
<div className="space-x-2">
<Button size="sm" onClick={handleAddAssignmentClick}>
추가
</Button>
</div>
</Section.Header>
<Section.Body className="space-y-4">
<div className="space-y-2">
<Input
id="title"
name="title"
label="제목"
placeholder="제목을 입력해주세요."
value={board.title}
onChange={handlePostChange}
/>
<Textarea
id="content"
name="content"
label="내용"
placeholder="내용을 입력해주세요."
className="w-full"
maxLength={200}
value={board.content}
onChange={handlePostChange}
/>
<Grid col="2" gap="md" className="items-center">
<Input
label="종료 일시"
type="datetime-local"
id="dueDateTime"
name="dueDateTime"
value={board.dueDateTime}
onChange={handlePostChange}
/>
<div className="flex flex-col">
<label htmlFor="fileUpload" className="mb-1 ml-1 text-xs">
첨부 파일
</label>
<input ref={uploaderRef} id="fileUpload" type="file" />
</div>
</Grid>
</div>
</Section.Body>
</Section>
);
};

export default ActivityAssignmentEditor;
Loading

0 comments on commit 5259e47

Please sign in to comment.