Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] 우수회원/수료회원 처리 및 철회 모달 구현 #164

Merged
merged 47 commits into from
Dec 22, 2024
Merged
Show file tree
Hide file tree
Changes from 46 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
0d8054f
feat: 우수 회원 수료 처리 및 철회 드롭다운 버튼 UI
hamo-o Nov 10, 2024
1c5f55f
refactor: 엑셀 다운로드 fetch 로직 hook으로 분리
hamo-o Nov 10, 2024
ffa008f
refactor: 헤더 버튼 묶음 컴포넌트 분리
hamo-o Nov 10, 2024
c4187ef
refactor: 우수회원 옵션 상수 분리
hamo-o Nov 10, 2024
973a06e
feat: 1차/2차 우수회원 처리/철회 버튼 상태관리
hamo-o Nov 10, 2024
8292a70
feat: 선택된 학생 목록 상태관리
hamo-o Nov 10, 2024
e9a2a54
refactor: 불필요한 분기처리 삭제
hamo-o Nov 10, 2024
eea7b41
feat: AchievementType, 우수회원 API DTO 등록
hamo-o Nov 10, 2024
c69464d
feat: fetch delete 메서드에도 body 추가
hamo-o Nov 10, 2024
a6568b2
feat: 우수회원 처리, 철회 API 등록
hamo-o Nov 10, 2024
dd86923
feat: 우수회원 처리, 철회 API 버튼 클릭이벤트 연결
hamo-o Nov 10, 2024
192e5d2
fix: 잘못된 AchievementType import
hamo-o Nov 10, 2024
7fc7b0f
fix: delete 메서드 body 기본값 설정
hamo-o Nov 10, 2024
3bf1e64
feat: 우수회원 처리/철회 모달 임시구현
hamo-o Nov 10, 2024
7bebb45
fix: 처리, 철회 type으로 사용 및 outstandingRoundMap으로 1차, 2차 워딩 관리
hamo-o Nov 17, 2024
0e88202
feat: 우수 처리, 철회 로직 모달로 이동
hamo-o Nov 17, 2024
a1f8a0c
feat: 첫 번째 선택된 학생 이름 렌더링
hamo-o Nov 17, 2024
d9f5fee
chore: CODEOWNER 수정
hamo-o Nov 17, 2024
9f616cc
fix: delete 메서드 body 필수 해제
hamo-o Nov 23, 2024
cbafaa7
fix: 불필요한 코드 제거, 코드 순서 변경
hamo-o Nov 23, 2024
616ae52
refactor: 수강생 목록 헤더, 컨텐츠 컴포넌트 분리
hamo-o Nov 23, 2024
16d02cd
fix: 스터디 변경 시 선택 학생 초기화
hamo-o Nov 23, 2024
60fb228
fix: 페이지네이션 시에도 첫번째 학생 이름 유지하도록
hamo-o Nov 23, 2024
7ab1eb8
fix: pathname의 변화에 따라 fetch
hamo-o Nov 24, 2024
a4d8c7c
feat: 모달 닫을 때 선택된 학생 정보 초기화
hamo-o Nov 24, 2024
b331bc1
refactor: Modal Header, Footer 분리
hamo-o Nov 24, 2024
e7cff27
fix: 변경된 타입명 적용
hamo-o Nov 24, 2024
0c36142
feat: 드롭다운 수료 타입 추가
hamo-o Nov 24, 2024
dd26cf4
feat: 수강생 수료 처리, 철회 API
hamo-o Nov 24, 2024
fd908a0
chore: TODO 주석 추가
hamo-o Nov 24, 2024
76fb85a
feat: 수료 처리, 철회 API 연결
hamo-o Nov 24, 2024
a759886
chore: TODO 주석 추가
hamo-o Nov 24, 2024
66b9a1e
refactor: 우수, 수료에 따른 분기처리
hamo-o Nov 24, 2024
439c29a
fix: 선택한 학생 수가 1명일 때 나머지 학생 수 렌더링하지 않음
hamo-o Nov 24, 2024
956c4ac
fix: 코드오너 추가
hamo-o Dec 12, 2024
aa83814
fix: OutstandingModalFooter -> OutstandingModalButton 네이밍 변경
hamo-o Dec 12, 2024
b3da994
feat: 취소하기 버튼 클릭 시 selectedStudentsAtom 초기화
hamo-o Dec 12, 2024
509431b
fix: fragment 대신 span 태그로 변경
hamo-o Dec 16, 2024
6fdfdf6
chore: wowds-ui 버전업
hamo-o Dec 16, 2024
1e64d83
fix: 선택된 학생들 배열 대신 Set으로 관리, 디자인 시스템의 onChange 대신 직접 구현한 핸들러 사용
hamo-o Dec 16, 2024
f4fd40e
fix: 체크박스 직접 관리, 선택된 학생 리스트 API 요청 시 배열로 변환
hamo-o Dec 16, 2024
f40faab
fix: selectedRowsProp Set이 아닌 Array로 넘겨주기
hamo-o Dec 16, 2024
410868d
fix: Table.Tr 대신 styled.tr 사용, 개별 체크박스 직접 구현
hamo-o Dec 16, 2024
621f229
fix: 허용되지 않은 value prop 삭제
hamo-o Dec 16, 2024
8aad894
feat: 우수 및 수료 모달 버튼 분리, 공통 로직 훅 분리, 모달 네이밍 변경
hamo-o Dec 16, 2024
9b805b5
feat: 중복처리 체크박스 막기
hamo-o Dec 16, 2024
a23cc1a
fix: 불필요한 styled 태그 삭제, 클래스명 컨벤션 통일
hamo-o Dec 22, 2024
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
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
hamo-o marked this conversation as resolved.
Show resolved Hide resolved
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;
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;
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
Loading