Skip to content

Commit

Permalink
[Feature] 우수회원/수료회원 처리 및 철회 모달 구현 (#164)
Browse files Browse the repository at this point in the history
* feat: 우수 회원 수료 처리 및 철회 드롭다운 버튼 UI

* refactor: 엑셀 다운로드 fetch 로직 hook으로 분리

* refactor: 헤더 버튼 묶음 컴포넌트 분리

* refactor: 우수회원 옵션 상수 분리

* feat: 1차/2차 우수회원 처리/철회 버튼 상태관리

* feat: 선택된 학생 목록 상태관리

* refactor: 불필요한 분기처리 삭제

* feat: AchievementType, 우수회원 API DTO 등록

* feat: fetch delete 메서드에도 body 추가

* feat: 우수회원 처리, 철회 API 등록

* feat: 우수회원 처리, 철회 API 버튼 클릭이벤트 연결

* fix: 잘못된 AchievementType import

* fix: delete 메서드 body 기본값 설정

* feat: 우수회원 처리/철회 모달 임시구현

* fix: 처리, 철회 type으로 사용 및 outstandingRoundMap으로 1차, 2차 워딩 관리

* feat: 우수 처리, 철회 로직 모달로 이동

* feat: 첫 번째 선택된 학생 이름 렌더링

* chore: CODEOWNER 수정

* fix: delete 메서드 body 필수 해제

* fix: 불필요한 코드 제거, 코드 순서 변경

* refactor: 수강생 목록 헤더, 컨텐츠 컴포넌트 분리

* fix: 스터디 변경 시 선택 학생 초기화

* fix: 페이지네이션 시에도 첫번째 학생 이름 유지하도록

* fix: pathname의 변화에 따라 fetch

* feat: 모달 닫을 때 선택된 학생 정보 초기화

* refactor: Modal Header, Footer 분리

* fix: 변경된 타입명 적용

* feat: 드롭다운 수료 타입 추가

* feat: 수강생 수료 처리, 철회 API

* chore: TODO 주석 추가

* feat: 수료 처리, 철회 API 연결

* chore: TODO 주석 추가

* refactor: 우수, 수료에 따른 분기처리

* fix: 선택한 학생 수가 1명일 때 나머지 학생 수 렌더링하지 않음

* fix: 코드오너 추가

* fix: OutstandingModalFooter -> OutstandingModalButton 네이밍 변경

* feat: 취소하기 버튼 클릭 시 selectedStudentsAtom 초기화

* fix: fragment 대신 span 태그로 변경

* chore: wowds-ui 버전업

* fix: 선택된 학생들 배열 대신 Set으로 관리, 디자인 시스템의 onChange 대신 직접 구현한 핸들러 사용

* fix: 체크박스 직접 관리, 선택된 학생 리스트 API 요청 시 배열로 변환

* fix: selectedRowsProp Set이 아닌 Array로 넘겨주기

* fix: Table.Tr 대신 styled.tr 사용, 개별 체크박스 직접 구현

* fix: 허용되지 않은 value prop 삭제

* feat: 우수 및 수료 모달 버튼 분리, 공통 로직 훅 분리, 모달 네이밍 변경

* feat: 중복처리 체크박스 막기

* fix: 불필요한 styled 태그 삭제, 클래스명 컨벤션 통일
  • Loading branch information
hamo-o authored Dec 22, 2024
1 parent 9ff073a commit 45c13e9
Show file tree
Hide file tree
Showing 36 changed files with 829 additions and 157 deletions.
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -1 +1 @@
* @ghdtjgus76 @eugene028 @hamo-o @SeieunYoo
* @eugene028 @hamo-o @SeieunYoo @soulchicken
28 changes: 28 additions & 0 deletions apps/admin/apis/study/studyAchievementApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { fetcher } from "@wow-class/utils";
import type { OutstandingStudentApiRequestDto } from "types/dtos/outstandingStudent";

const studyAchievementApi = {
postStudyAchievement: async (
studyId: number,
data: OutstandingStudentApiRequestDto
) => {
const response = await fetcher.post(
`/mentor/study-achievements?studyId=${studyId}`,
data
);
return { success: response.ok };
},

deleteStudyAchievement: async (
studyId: number,
data: OutstandingStudentApiRequestDto
) => {
const response = await fetcher.delete(
`/mentor/study-achievements?studyId=${studyId}`,
data
);
return { success: response.ok };
},
};

export default studyAchievementApi;
19 changes: 19 additions & 0 deletions apps/admin/apis/study/studyCompleteApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { fetcher } from "@wow-class/utils";
import type { StudyCompleteRequestDto } from "types/dtos/studyComplete";

const studyCompleteApi = {
postStudyComplete: async (data: StudyCompleteRequestDto) => {
const response = await fetcher.post(`/mentor/study-history/complete`, data);
return { success: response.ok };
},

postStudyCompleteWithdraw: async (data: StudyCompleteRequestDto) => {
const response = await fetcher.post(
`/mentor/study-history/withdraw-completion`,
data
);
return { success: response.ok };
},
};

export default studyCompleteApi;
6 changes: 0 additions & 6 deletions apps/admin/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,6 @@ const RootLayout = ({
return (
<html lang="ko">
<body>
<ToastContainer
hideProgressBar
autoClose={4000}
closeButton={false}
limit={1}
/>
<JotaiProvider>
<ToastContainer
hideProgressBar
Expand Down
7 changes: 7 additions & 0 deletions apps/admin/app/students/@modal/(.)status/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import StudentStatusModal from "../_components/StudentStatusModal";

const StudentStatusModalPage = () => {
return <StudentStatusModal />;
};

export default StudentStatusModalPage;
55 changes: 55 additions & 0 deletions apps/admin/app/students/@modal/_components/CompleteModalButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"use client";

import studyCompleteApi from "apis/study/studyCompleteApi";
import { useAtomValue } from "jotai";
import Button from "wowds-ui/Button";

import {
enabledOutstandingStudentsAtom,
outstandingStudentsAtom,
selectedStudentsAtom,
studyAtom,
} from "@/students/_contexts/StudyProvider";

import useCloseStudentStatusModal from "../_hooks/useCloseStudentStatusModal";

const CompleteModalButton = () => {
const study = useAtomValue(studyAtom);
const { enabled } = useAtomValue(enabledOutstandingStudentsAtom);
const { type } = useAtomValue(outstandingStudentsAtom);
const { students } = useAtomValue(selectedStudentsAtom);

const {
handleClickCloseModal,
closeModalWithSuccess,
closeModalWithFailure,
} = useCloseStudentStatusModal();

const handleClickComplete = async () => {
if (!study || !type) return;

const apiMap = {
처리: studyCompleteApi.postStudyComplete,
철회: studyCompleteApi.postStudyCompleteWithdraw,
};

const fetchApi = () => {
const fetch = apiMap[type];
return fetch({
studyId: study.studyId,
studentIds: Array.from(students),
});
};
const result = await fetchApi();
if (result.success) closeModalWithSuccess();
else closeModalWithFailure();
};

return enabled ? (
<Button onClick={handleClickComplete}>선택한 회원 수료 {type}</Button>
) : (
<Button onClick={handleClickCloseModal}>확인하기</Button>
);
};

export default CompleteModalButton;
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"use client";

import studyAchievementApi from "apis/study/studyAchievementApi";
import { useAtomValue } from "jotai";
import type { AchievementType } from "types/entities/achievement";
import Button from "wowds-ui/Button";

import {
enabledOutstandingStudentsAtom,
outstandingStudentsAtom,
selectedStudentsAtom,
studyAtom,
} from "@/students/_contexts/StudyProvider";

import useCloseStudentStatusModal from "../_hooks/useCloseStudentStatusModal";

const OutstandingModalButton = () => {
const study = useAtomValue(studyAtom);
const { enabled } = useAtomValue(enabledOutstandingStudentsAtom);
const { type, achievement } = useAtomValue(outstandingStudentsAtom);
const { students } = useAtomValue(selectedStudentsAtom);

const {
handleClickCloseModal,
closeModalWithSuccess,
closeModalWithFailure,
} = useCloseStudentStatusModal();

const handleClickOutstanding = async () => {
if (!study || !achievement || !type) return;

const apiMap = {
처리: studyAchievementApi.postStudyAchievement,
철회: studyAchievementApi.deleteStudyAchievement,
};

const fetchApi = () => {
const fetch = apiMap[type];
return fetch(study.studyId, {
studentIds: Array.from(students),
achievementType: achievement as AchievementType,
});
};
const result = await fetchApi();
if (result.success) closeModalWithSuccess();
else closeModalWithFailure();
};

return enabled ? (
<Button onClick={handleClickOutstanding}>선택한 회원 우수 {type}</Button>
) : (
<Button onClick={handleClickCloseModal}>확인하기</Button>
);
};

export default OutstandingModalButton;
50 changes: 50 additions & 0 deletions apps/admin/app/students/@modal/_components/StudentStatusModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"use client";

import { Flex, styled } from "@styled-system/jsx";
import { Modal, Text } from "@wow-class/ui";
import { useAtomValue } from "jotai";

import {
outstandingStudentsAtom,
selectedStudentsAtom,
} from "@/students/_contexts/StudyProvider";

import CompleteModalButton from "./CompleteModalButton";
import OutstandingModalButton from "./OutstandingModalButton";
import StudentStatusModalHeader from "./StudentStatusModalHeader";

const StudentStatusModal = () => {
const { firstStudentName, students } = useAtomValue(selectedStudentsAtom);
const { type, achievement } = useAtomValue(outstandingStudentsAtom);

const STUDENTS_NUM = students.size;
if (!type || !achievement) return null;
if (!STUDENTS_NUM) return <Text>선택된 수강생이 없습니다.</Text>;

const renderAdditionalStudents = () => {
if (STUDENTS_NUM === 1) return null;
return (
<styled.span>
<styled.span color="primary">{STUDENTS_NUM - 1}</styled.span>
</styled.span>
);
};

return (
<Modal>
<Flex align="center" direction="column" gap="1.5rem">
<StudentStatusModalHeader />
<Text color="sub">
{firstStudentName}{renderAdditionalStudents()}
</Text>
{achievement === "COMPLETE" ? (
<CompleteModalButton />
) : (
<OutstandingModalButton />
)}
</Flex>
</Modal>
);
};

export default StudentStatusModal;
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"use client";

import { Text } from "@wow-class/ui";
import {
outstandingRoundMap,
outstandingTypeMap,
} from "constants/status/outstandigOptions";
import { useAtomValue } from "jotai";

import {
enabledOutstandingStudentsAtom,
outstandingStudentsAtom,
} from "@/students/_contexts/StudyProvider";

const StudentStatusModalHeader = () => {
const { enabled } = useAtomValue(enabledOutstandingStudentsAtom);
const { type, achievement } = useAtomValue(outstandingStudentsAtom);

if (!type || !achievement) return null;
return enabled ? (
<Text
typo="h1"
style={{
textAlign: "center",
}}
>
선택한 수강생을 <br />
{outstandingRoundMap[achievement]} {outstandingTypeMap[type]}
하시겠어요?
</Text>
) : (
<Text typo="h1">
{outstandingRoundMap[achievement]} {outstandingTypeMap[type]}
되었어요.
</Text>
);
};
export default StudentStatusModalHeader;
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { useModalRoute } from "@wow-class/ui/hooks";
import { tags } from "constants/tags";
import { useSetAtom } from "jotai";
import { revalidateTagByName } from "utils/revalidateTagByName";

import {
enabledOutstandingStudentsAtom,
selectedStudentsAtom,
} from "@/students/_contexts/StudyProvider";

const useCloseStudentStatusModal = () => {
const setSelectedStudents = useSetAtom(selectedStudentsAtom);
const setEnabledOutstandingStudents = useSetAtom(
enabledOutstandingStudentsAtom
);
const { onClose } = useModalRoute();

const handleClickCloseModal = () => {
setSelectedStudents({
students: new Set(),
firstStudentName: "",
});
onClose();
};

const resetStudents = () => {
revalidateTagByName(tags.students);
setEnabledOutstandingStudents({
enabled: false,
});
};

const closeModalWithSuccess = () => {
resetStudents();
setTimeout(() => {
handleClickCloseModal();
}, 1000);
};

const closeModalWithFailure = () => {
resetStudents();
handleClickCloseModal();
};

return {
handleClickCloseModal,
closeModalWithSuccess,
closeModalWithFailure,
};
};
export default useCloseStudentStatusModal;
5 changes: 5 additions & 0 deletions apps/admin/app/students/@modal/default.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const Default = () => {
return null;
};

export default Default;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { OutstandingType } from "constants/status/outstandigOptions";
import Button from "wowds-ui/Button";

const DropDownTrigger = ({ type }: { type: OutstandingType }) => {
return (
<Button size="sm" variant="outline">
우수 및 수료 {type}
</Button>
);
};

export default DropDownTrigger;
Loading

0 comments on commit 45c13e9

Please sign in to comment.