Skip to content

Commit

Permalink
Merge pull request #77 from CSID-DGU/frontend/feature/add-exam
Browse files Browse the repository at this point in the history
FE: [feat] 사후 레포트, 엑셀 다운로드 완료
  • Loading branch information
hyeona01 authored Dec 1, 2024
2 parents 772947d + 1a54ace commit c40df16
Show file tree
Hide file tree
Showing 16 changed files with 193 additions and 57 deletions.
4 changes: 2 additions & 2 deletions src/frontend/eyesee-admin/src/apis/dashBoard.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { api, apiWithoutAuth } from ".";
import { api } from ".";
import { testSesstion } from "@/types/user";
import { RESTYPE } from "@/types/common";

export const getDashboardData = async (
examId: number
): Promise<RESTYPE<testSesstion>> => {
const response = await api.get(`/exams/${examId}/sessions`);
const response = await api.get(`/exams/${examId}/users`);
return response.data;
};
34 changes: 34 additions & 0 deletions src/frontend/eyesee-admin/src/apis/report.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { api } from ".";
import { ReportResponse } from "@/types/report";

export const getReportData = async (
examId: number
): Promise<ReportResponse> => {
const response = await api.get(`/exams/${examId}/report`);
return response.data;
};

// 사후 레포트 엑셀 다운로드
export const downloadReport = async (examId: number) => {
try {
const response = await api.get(`/exams/${examId}/report/download`, {
responseType: "blob",
});

const blob = new Blob([response.data], {
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
});

const url = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = "report.xlsx";
document.body.appendChild(link);

link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
} catch (error) {
console.error("엑셀 다운로드 중 에러가 발생했습니다: ", error);
}
};
2 changes: 1 addition & 1 deletion src/frontend/eyesee-admin/src/apis/userDetail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ export const getUserDetail = async (
examId: string,
userId: string
): Promise<RESTYPE<timeLineType>> => {
const response = await api.get(`/exams/${examId}/sessions/${userId}`);
const response = await api.get(`/exams/${examId}/users/${userId}`);
return response.data;
};
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,15 @@ const UserDetailPage = () => {
<div className="flex w-screen h-screen bg-[#0E1D3C]">
<TimeLine timeLineData={userDetailData} setVideoNum={setVideoNum} />
<div className="grow text-white p-10">
<TestInfo examName="융합캡스톤디자인 중간시험" examDuration={120} />
<TestInfo
examName={userDetailData.examName}
examDuration={userDetailData.examDuration}
/>
<div>
<CheatingVideo
cheatingVideo={userDetailData.cheatingVideos[vidieoNum]}
cheatingType={
userDetailData.cheatingStatistics[vidieoNum].cheatingTypeName
userDetailData.cheatingStatistics[vidieoNum].koreanTypeName
}
cheatingCounts={
userDetailData.cheatingStatistics[vidieoNum].cheatingCount
Expand Down
16 changes: 11 additions & 5 deletions src/frontend/eyesee-admin/src/app/dashboard/[examId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,28 @@
import DashBoardSection from "@/components/dashBoard/DashBoardSection";
import UserSection from "@/components/dashBoard/UserSection";
import { useDashboardData } from "@/hooks/api/useDashboard";
import { testSesstion, testSesstionData } from "@/types/user";
import { testSesstion } from "@/types/user";
import { useParams } from "next/navigation";
import React, { useEffect, useState } from "react";

const DashBoardPage = () => {
const { examId } = useParams();
const [sessionData, setSessionData] =
useState<testSesstion>(testSesstionData);
const [sessionData, setSessionData] = useState<testSesstion>();

// 시험 대시보드 데이터 호출
const { data } = useDashboardData(Number(examId));

useEffect(() => {
if (data && data.data.user.length > 0) {
if (data) {
// 좌석번호 오름차순 정렬
const sortedData = [...data.data.user].sort(
(a, b) => a.seatNum - b.seatNum
);
setSessionData({ ...sessionData, user: sortedData });

setSessionData({
...data.data,
user: sortedData,
});
}
}, [data]);

Expand Down
27 changes: 18 additions & 9 deletions src/frontend/eyesee-admin/src/app/report/[examId]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
"use client";

import DashBoardSection from "@/components/dashBoard/DashBoardSection";
import UserSection from "@/components/dashBoard/UserSection";
import ReportSection from "@/components/report/ReportSection";
import { testSesstionData } from "@/types/user";
import { useDashboarReportdData } from "@/hooks/api/useDashboard";
import { useReportdData } from "@/hooks/api/useReport";
import { useParams } from "next/navigation";

const ReportPage = () => {
// TODO: 서버 데이터 연결
const sessionData = testSesstionData;
const { examId } = useParams();
const { data: dashboard } = useDashboarReportdData(Number(examId));
const { data: report } = useReportdData(Number(examId));

return (
<div className="flex min-h-screen w-screen bg-[##0E1D3C]">
<UserSection sessionData={sessionData} />
<DashBoardSection sesstionData={sessionData}>
<ReportSection />
</DashBoardSection>
</div>
<>
{dashboard && report && (
<div className="flex min-h-screen w-screen bg-[##0E1D3C]">
<UserSection sessionData={dashboard.data} />
<DashBoardSection sesstionData={dashboard.data}>
<ReportSection reportData={report} />
</DashBoardSection>
</div>
)}
</>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,17 @@ const BeforeSection = () => {

useEffect(() => {
if (data) {
setBeforeTestData(data.data);
const sortedData = [...data.data].sort((a, b) => {
return new Date(b.examDate).getTime() - new Date(a.examDate).getTime();
});
setBeforeTestData(sortedData);
}
}, [data]);

return (
<>
{beforeTestData ? (
<div className="bg-[rgba(14,29,60,0.20)] p-4 rounded-lg">
<div className="bg-[rgba(14,29,60,0.20)] p-4 rounded-lg w-[350px]">
<div className="flex items-center gap-2.5 p-2.5 pb-5">
<span className="text-[#6f6f6f] text-lg font-normal">Before</span>
<span className="text-[#6f6f6f] text-lg font-semibold">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,22 @@ import { useEffect, useState } from "react";

const DoneSection = () => {
const { data } = useDoneExam();

const [doneData, setDoneData] = useState<testType[]>();

useEffect(() => {
if (data) {
setDoneData(data.data);
const sortedData = [...data.data].sort((a, b) => {
return new Date(b.examDate).getTime() - new Date(a.examDate).getTime();
});
setDoneData(sortedData);
}
}, [data]);

return (
<>
{doneData ? (
<div className="bg-[rgba(14,29,60,0.20)] p-4 rounded-lg">
<div className="bg-[rgba(14,29,60,0.20)] p-4 rounded-lg w-[350px]">
<div className="flex items-center gap-2.5 p-2.5 pb-5">
<span className="text-[#6f6f6f] text-lg font-normal">Done</span>
<span className="text-[#6f6f6f] text-lg font-semibold">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,17 @@ const InProgressSection = () => {

useEffect(() => {
if (data) {
setInProgressData(data.data);
const sortedData = [...data.data].sort((a, b) => {
return new Date(b.examDate).getTime() - new Date(a.examDate).getTime();
});
setInProgressData(sortedData);
}
}, [data]);

return (
<>
{inProgressData ? (
<div className="bg-[rgba(14,29,60,0.20)] p-4 rounded-lg">
<div className="bg-[rgba(14,29,60,0.20)] p-4 rounded-lg w-[350px]">
<div className="flex items-center gap-2.5 p-2.5 pb-5">
<span className="text-[#6f6f6f] text-lg font-normal">
In Progress
Expand Down
46 changes: 37 additions & 9 deletions src/frontend/eyesee-admin/src/components/report/ReportSection.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,69 @@
import { api } from "@/apis";
import { downloadReport } from "@/apis/report";
import ExcelIcon from "@/assets/images/Excel.svg";
import { ReportResponse } from "@/types/report";
import { useParams } from "next/navigation";

type ReportSectionType = {
reportData: ReportResponse;
};

const ReportSection = ({ reportData }: ReportSectionType) => {
const { examId } = useParams();

// 시험 레포트 엑셀 다운로드
const handleDownload = () => {
if (examId) {
downloadReport(Number(examId));
} else {
console.error("exam id가 존재하지 않습니다. 다시 시도해주세요.");
}
};

const ReportSection = () => {
return (
<div className="relative w-full mb-20 flex justify-between gap-20">
<div className="cursor-pointer absolute bottom-0 flex items-center gap-3">
<div
onClick={handleDownload}
className="cursor-pointer absolute bottom-0 flex items-center gap-3"
>
<ExcelIcon />
<p className="text-[20px]">엑셀 다운로드</p>
</div>
<div className="w-[50%] h-full text-[2.5rem] font-bold text-white">
<p className="">2024년 11월 27일</p>
<p className="text-[#16A34A]">융합캡스톤디자인 중간시험</p>
<p className="text-[#16A34A]">{reportData.examName}</p>
<p>부정행위 탐지 결과</p>
</div>
<div className="[w-50%] flex flex-col h-full">
<div className="w-full flex gap-20 py-3 items-center justify-between text-[20px] text-bold border-b border-white">
<div>총 탐지된 부정행위 건수</div>
<div>75건</div>
<div>{reportData.totalCheatingCount}</div>
</div>
<div className="w-full flex gap-20 py-3 items-center justify-between text-[20px] text-bold border-b border-white">
<div>부정행위 탐지 수험자 수</div>
<div>15명</div>
<div>{reportData.cheatingStudentsCount}</div>
</div>
<div className="w-full flex gap-20 py-3 items-center justify-between text-[20px] text-bold border-b border-white">
<div>평균 부정행위 탐지 건수</div>
<div>5건/수험자</div>
<div>{reportData.averageCheatingCount}/수험자</div>
</div>
<div className="w-full flex gap-20 py-3 items-center justify-between text-[20px] text-bold border-b border-white">
<div>최다 부정행위 탐지 수험자</div>
<div>학번: 2022113107, 횟수: 10건</div>
<div>{reportData.maxCheatingStudent}</div>
</div>
<div className="w-full flex gap-20 py-3 items-center justify-between text-[20px] text-bold border-b border-white">
<div>부정행위 탐지율</div>
<div>30%</div>
<div>{reportData.cheatingRate}%</div>
</div>
<div className="w-full flex gap-20 py-3 items-center justify-between text-[20px] text-bold border-b border-white">
<div>부정행위 유형별 통계</div>
<div>시선: 50건, 자리이탈: 10건</div>
{Object.entries(reportData.cheatingTypeStatistics).map(
([type, count]) => (
<span key={type}>
{type}: {count}{", "}
</span>
)
)}
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,25 @@ const CheatingVideo = ({
cheatingCounts,
}: CheatingVideoProps) => {
return (
<div className="text-white">
<div className="bg-[rgba(58,64,97,0.7)] text-xl px-6 py-4 rounded-t-lg flex justify-between items-center">
{cheatingVideo.startTime.slice(11)} ~{" "}
{cheatingVideo.endTime.slice(11)}
<button className="bg-[#EF4444] px-3 py-2 flex justify-center items-center rounded-lg text-base">
{cheatingType} {cheatingCounts}
</button>
</div>
<video
src={cheatingVideo.filepath}
controls
className="w-full max-h-[62vh] rounded-b-lg"
></video>
</div>
<>
{cheatingVideo && (
<div className="text-white">
<div className="bg-[rgba(58,64,97,0.7)] text-xl px-6 py-4 rounded-t-lg flex justify-between items-center">
{cheatingVideo.startTime.slice(11)} ~{" "}
{cheatingVideo.endTime.slice(11)}
{/* TODO: 서버 데이터 확인 후 상위 페이지로 이동해야함 */}
<button className="bg-[#EF4444] px-3 py-2 flex justify-center items-center rounded-lg text-base">
{cheatingType} {cheatingCounts}
</button>
</div>
<video
src={cheatingVideo.filepath}
controls
className="w-full max-h-[62vh] rounded-b-lg"
></video>
</div>
)}
</>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,8 @@ const TimeLine = ({ timeLineData, setVideoNum }: TimeLineProps) => {
<CircleIcon />
<div className="text-xl text-[#000]">
<div>{cheating.detectedTime}</div>
{/* <div>{cheating.DetectedTime.slice(-8)}</div> */}
<div>
{cheating.cheatingTypeName} {cheating.cheatingCount}회 감지
{cheating.koreanTypeName} {cheating.cheatingCount}회 감지
</div>
</div>
</div>
Expand Down
7 changes: 7 additions & 0 deletions src/frontend/eyesee-admin/src/hooks/api/useDashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,10 @@ export const useDashboardData = (examId: number) => {
refetchIntervalInBackground: true, // 화면이 포커스되지 않아도 백그라운드에서 리프레시
});
};

export const useDashboarReportdData = (examId: number) => {
return useQuery({
queryKey: ["dashboard", examId],
queryFn: () => getDashboardData(examId),
});
};
17 changes: 17 additions & 0 deletions src/frontend/eyesee-admin/src/hooks/api/useReport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { getDashboardData } from "@/apis/dashBoard";
import { downloadReport, getReportData } from "@/apis/report";
import { useQuery } from "@tanstack/react-query";

export const useDashboarReportdData = (examId: number) => {
return useQuery({
queryKey: ["dashboard", examId],
queryFn: () => getDashboardData(examId),
});
};

export const useReportdData = (examId: number) => {
return useQuery({
queryKey: ["report", examId],
queryFn: () => getReportData(examId),
});
};
12 changes: 12 additions & 0 deletions src/frontend/eyesee-admin/src/types/report.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export type ReportResponse = {
examName: string;
totalCheatingCount: number;
cheatingStudentsCount: number;
averageCheatingCount: number;
maxCheatingStudent: string;
cheatingRate: number;
cheatingTypeStatistics: {
[key: string]: number;
};
peakCheatingTimeRange: string;
};
Loading

0 comments on commit c40df16

Please sign in to comment.