Skip to content
This repository has been archived by the owner on Sep 20, 2024. It is now read-only.

Commit

Permalink
feat(Quiz) : quiz update & delete from professor
Browse files Browse the repository at this point in the history
  • Loading branch information
kasterra committed Jun 2, 2024
1 parent c570bb2 commit bb26252
Show file tree
Hide file tree
Showing 4 changed files with 611 additions and 7 deletions.
186 changes: 185 additions & 1 deletion app/API/practice.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { API_SERVER_URL } from "~/util/constant";
import { PracticeDetailResponse } from "~/types/APIResponse";
import {
EmptyResponse,
PracticeDetailResponse,
QuizRowResponse,
QuizsResponse,
} from "~/types/APIResponse";
import { handle401 } from "~/util";
import {
BadRequestError,
Expand Down Expand Up @@ -162,3 +167,182 @@ export async function deletePractice(
}
return await response.json();
}

export async function getAllQuizes(
practiceId: string,
token: string
): Promise<QuizsResponse> {
const response = await fetch(
`${API_SERVER_URL}/practice/${practiceId}/quizs`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
}
);
switch (response.status) {
case 400:
throw new BadRequestError("JWT 토큰이 없거나 입력값 검증 실패");
case 401:
handle401();
break;
case 403:
throw new ForbiddenError("강의 소유 권한이 없습니다. 다시 확인해 주세요");
case 404:
throw new NotFoundError("강의 ID가 없거나 실습 ID 가 없습니다");
case 500:
throw new InternalServerError(
"서버 에러가 발생했습니다. 관리자에게 문의해 주세요"
);
}
return await response.json();
}

export async function createNewQuiz(
practiceId: string,
data: {
max_score: number;
scores: { score: number | null; user_id: string }[];
title: string;
}[],
token: string
): Promise<QuizsResponse> {
const response = await fetch(
`${API_SERVER_URL}/practice/${practiceId}/quizs`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ data }),
}
);
switch (response.status) {
case 400:
throw new BadRequestError("JWT 토큰이 없거나 입력값 검증 실패");
case 401:
handle401();
break;
case 403:
throw new ForbiddenError("강의 소유 권한이 없습니다. 다시 확인해 주세요");
case 404:
throw new NotFoundError("강의 ID가 없거나 실습 ID 가 없습니다");
case 500:
throw new InternalServerError(
"서버 에러가 발생했습니다. 관리자에게 문의해 주세요"
);
}
return await response.json();
}

export async function updateQuiz(
practiceId: string,
data: {
max_score: number;
quiz_id: number;
scores: { score: number | null; user_id: string }[];
title: string;
}[],
token: string
): Promise<QuizsResponse> {
const response = await fetch(
`${API_SERVER_URL}/practice/${practiceId}/quizs`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ data }),
}
);
switch (response.status) {
case 400:
throw new BadRequestError("JWT 토큰이 없거나 입력값 검증 실패");
case 401:
handle401();
break;
case 403:
throw new ForbiddenError(
"소속되어있지 않은 강의의 퀴즈 접근, 학생 권한으로 다른 학생의 퀴즈 접근"
);
case 404:
throw new NotFoundError("강의 ID가 없거나 실습 ID 가 없습니다");
case 500:
throw new InternalServerError(
"서버 에러가 발생했습니다. 관리자에게 문의해 주세요"
);
}
return await response.json();
}

export async function deleteQuiz(
practiceId: string,
token: string
): Promise<{ status: number }> {
const response = await fetch(
`${API_SERVER_URL}/practice/${practiceId}/quizs`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
}
);
switch (response.status) {
case 400:
throw new BadRequestError("JWT 토큰이 없거나 입력값 검증 실패");
case 401:
handle401();
break;
case 403:
throw new ForbiddenError("강의 소유 권한이 없습니다. 다시 확인해 주세요");
case 404:
throw new NotFoundError("강의 ID가 없거나 실습 ID 가 없습니다");
case 500:
throw new InternalServerError(
"서버 에러가 발생했습니다. 관리자에게 문의해 주세요"
);
}
if (response.status === 204) {
return { status: 204 };
}
return await response.json();
}

export async function getQuizWithUserId(
practice_id: string,
user_id: string,
token: string
): Promise<QuizRowResponse> {
const response = await fetch(
`${API_SERVER_URL}/quiz/${practice_id}/${user_id}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
}
);
switch (response.status) {
case 400:
throw new BadRequestError("JWT 토큰이 없거나 입력값 검증 실패");
case 401:
handle401();
break;
case 403:
throw new ForbiddenError("강의 소유 권한이 없습니다. 다시 확인해 주세요");
case 404:
throw new NotFoundError("강의 ID가 없거나 실습 ID 가 없습니다");
case 500:
throw new InternalServerError(
"서버 에러가 발생했습니다. 관리자에게 문의해 주세요"
);
}
return await response.json();
}
70 changes: 64 additions & 6 deletions app/routes/_procted+/lectures+/$lectureId+/_layout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,13 @@ import trashSVG from "~/assets/trash.svg";
import NewPracticeModal from "./NewPracticeModal";
import ImportPracticeModal from "./ImportPracticeModal";
import PracticeEditModal from "./PracticeEditModal";
import { deletePractice, getPracticeWithPracticeId } from "~/API/practice";
import {
deletePractice,
deleteQuiz,
getAllQuizes,
getPracticeWithPracticeId,
getQuizWithUserId,
} from "~/API/practice";
import toast from "react-hot-toast";
import ProblemAddModal from "./ProblemAddModal";
import ProblemEditModal from "./ProblemEditModal";
Expand Down Expand Up @@ -239,6 +245,7 @@ const PracticeDetail = ({
const [isPracticeEditModalOpen, setIsPracticeEditModalOpen] = useState(false);
const [isProblemAddModalOpen, setIsProblemAddModalOpen] = useState(false);
const [isFoldableOpen, setIsFoldableOpen] = useState(false);
const [hasQuiz, setHasQuiz] = useState(false);
const { lectureId } = useParams();
useEffect(() => {
async function getData() {
Expand All @@ -254,6 +261,24 @@ const PracticeDetail = ({
getData();
}, [isPracticeEditModalOpen, isProblemAddModalOpen, loading]);

useEffect(() => {
async function getData() {
if (auth.role === "professor") {
const response = await getAllQuizes(id + "", auth.token);
setHasQuiz(response.data.length > 0);
} else {
const response = await getQuizWithUserId(
id + "",
auth.userId,
auth.token
);
setHasQuiz(response.data.length > 0);
}
}

getData();
});

return loading ? (
<h3>loading...</h3>
) : (
Expand Down Expand Up @@ -296,11 +321,44 @@ const PracticeDetail = ({
}}
iconSrcList={[plusSVG]}
/>
<LinkElement
title="퀴즈 추가하기"
iconSrcList={[plusSVG]}
link={`/lectures/quiz/new?lecture_id=${lectureId}&practice_id=${id}`}
/>
{hasQuiz ? (
<>
<LinkElement
title="퀴즈 수정하기"
iconSrcList={[pencilSVG]}
link={`/lectures/quiz/edit?lecture_id=${lectureId}&practice_id=${id}`}
/>
<ButtonElement
title="퀴즈 삭제하기"
onButtonClick={async () => {
if (
confirm(
`정말로 ${
practiceDetail!.title
} 퀴즈을 삭제 하시겠습니까?`
)
) {
await toast.promise(deleteQuiz(id + "", auth.token), {
loading: "퀴즈 삭제...",
success: () => {
setSuperIsLoading!(true);
return "성공적으로 삭제되었습니다";
},
error: (error) =>
`Error: ${error.message} - ${error.responseMessage}`,
});
}
}}
iconSrcList={[trashSVG]}
/>
</>
) : (
<LinkElement
title="퀴즈 추가하기"
iconSrcList={[plusSVG]}
link={`/lectures/quiz/new?lecture_id=${lectureId}&practice_id=${id}`}
/>
)}
</>
) : null}
</FoldableSuperButtonElement>
Expand Down
Loading

0 comments on commit bb26252

Please sign in to comment.