Skip to content
Merged
19 changes: 19 additions & 0 deletions frontend/api/quizzes/fetchQuizDetailStat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import axios from "axios";
import { axiosInstance } from "@/api/axiosInstance";
import { ENDPOINTS } from "@/constants/endpoints";
import { ApiResponse } from "@/types/apiResponseTypes";
import { fetchQuizDetailStatResult } from "@/types/quizzes/fetchQuizDetailStatTypes";

export async function fetchQuizDetailStat(lectureId: string) {
try {
const response = await axiosInstance.get<
ApiResponse<fetchQuizDetailStatResult>
>(ENDPOINTS.QUIZZES.GET_DETAIL_STAT(lectureId));
return response.data;
} catch (error: unknown) {
if (axios.isAxiosError(error) && error.response) {
return error.response.data as ApiResponse<fetchQuizDetailStatResult>;
}
throw error;
}
}
19 changes: 19 additions & 0 deletions frontend/api/quizzes/fetchQuizForDashboard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import axios from "axios";
import { axiosInstance } from "@/api/axiosInstance";
import { ENDPOINTS } from "@/constants/endpoints";
import { ApiResponse } from "@/types/apiResponseTypes";
import { fetchQuizForDashboardResult } from "@/types/quizzes/fetchQuizForDashboardTypes";

export async function fetchQuizForDashboard(lectureId: string) {
try {
const response = await axiosInstance.get<
ApiResponse<fetchQuizForDashboardResult>
>(ENDPOINTS.QUIZZES.GET_FOR_DASHBOARD(lectureId));
return response.data;
} catch (error: unknown) {
if (axios.isAxiosError(error) && error.response) {
return error.response.data as ApiResponse<fetchQuizForDashboardResult>;
}
throw error;
}
}
19 changes: 19 additions & 0 deletions frontend/api/quizzes/fetchQuizInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import axios from "axios";
import { axiosInstance } from "@/api/axiosInstance";
import { ENDPOINTS } from "@/constants/endpoints";
import { ApiResponse } from "@/types/apiResponseTypes";
import { fetchQuizInfoResult } from "@/types/quizzes/fetchQuizInfoTypes";

export async function fetchQuizInfo(lectureId: string) {
try {
const response = await axiosInstance.get<ApiResponse<fetchQuizInfoResult>>(
ENDPOINTS.QUIZZES.GET_INFO(lectureId)
);
return response.data;
} catch (error: unknown) {
if (axios.isAxiosError(error) && error.response) {
return error.response.data as ApiResponse<fetchQuizInfoResult>;
}
throw error;
}
}
19 changes: 19 additions & 0 deletions frontend/api/quizzes/fetchSubmitList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import axios from "axios";
import { axiosInstance } from "@/api/axiosInstance";
import { ENDPOINTS } from "@/constants/endpoints";
import { ApiResponse } from "@/types/apiResponseTypes";
import { fetchQuizSubmitListResult } from "@/types/quizzes/fetchSubmitListTypes";

export async function fetchSubmitList(lectureId: string) {
try {
const response = await axiosInstance.get<
ApiResponse<fetchQuizSubmitListResult>
>(ENDPOINTS.QUIZZES.GET_SUBMIT_LIST(lectureId));
return response.data;
} catch (error: unknown) {
if (axios.isAxiosError(error) && error.response) {
return error.response.data as ApiResponse<fetchQuizSubmitListResult>;
}
throw error;
}
}
55 changes: 36 additions & 19 deletions frontend/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,40 @@
import "./globals.scss";
import type { Metadata } from "next";

export const metadata: Metadata = {
title: "ClassLog",
manifest: "./manifest.webmanifest",
themeColor: "#ffffff",
appleWebApp: {
capable: true,
title: "ClassLog",
statusBarStyle: "default",
},
icons: {
apple: [
{
url: "/favicon/apple-touch-icon.png",
sizes: "180x180",
type: "image/png",
},
],
icon: [
{
url: "/favicon/favicon-96x96.png",
sizes: "96x96",
type: "image/png",
},
{
url: "/favicon/favicon.svg",
type: "image/svg+xml",
},
{
url: "/favicon/favicon.ico",
type: "image/x-icon",
},
],
},
};

export default function RootLayout({
children,
Expand All @@ -7,25 +43,6 @@ export default function RootLayout({
}>) {
return (
<html lang="ko">
<head>
<link rel="manifest" href="./manifest.webmanifest" />
<title>ClassLog</title>
<meta name="theme-color" content="#ffffff" />
<meta name="apple-mobile-web-app-title" content="ClassLog" />
<link
rel="apple-touch-icon"
href="/favicon/apple-touch-icon.png"
sizes="180x180"
/>
<link
rel="icon"
type="image/png"
href="/favicon/favicon-96x96.png"
sizes="96x96"
/>
<link rel="icon" type="image/svg+xml" href="/favicon/favicon.svg" />
<link rel="shortcut icon" href="/favicon/favicon.ico" />
</head>
{children}
</html>
);
Expand Down
14 changes: 9 additions & 5 deletions frontend/app/teacher/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,16 @@ export default function TeacherLayout({
const showSidebar = currentRoute?.sidebarType === SiderbarType.DEFAULT;
const showHeader = currentRoute?.headerType !== TeacherHeaderType.NONE;

const bodyClassName = [
"teacher-body",
showSidebar && "show-sidebar",
showHeader && "show-header",
]
.filter(Boolean)
.join(" ");

return (
<body
className={`teacher-body ${showSidebar ? "show-sidebar" : ""} ${
showHeader ? "show-header" : ""
}`}
>
<body className={bodyClassName}>
{showSidebar && <SideBar />}
{renderHeader()}
<div className="content">{children}</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,125 +1,52 @@
"use client";
import React from "react";

import React, { useEffect, useState } from "react";
import styles from "./DashboardContainer.module.scss";
import QuizInfo from "../QuizInfo/QuizInfo";
import QuizSubmitList from "../QuizSubmitList/QuizSubmitList";
import QuizList from "../QuizList/QuizList";
import StatisticsContainer from "../StatisticsContainer/StatisticsContainer";

type Quiz =
| {
quizId: string;
quizOrder: number;
type: "multipleChoice";
quizBody: string;
correctRate: number;
solution: string;
options: Array<{ optionOrder: number; option: string; count: number }>;
}
| {
quizId: string;
quizOrder: number;
type: "trueFalse";
quizBody: string;
correctRate: number;
solution: string;
options: Array<{ optionOrder: null; option: string; count: number }>;
}
| {
quizId: string;
quizOrder: number;
type: "shortAnswer";
quizBody: string;
correctRate: number;
solution: string;
count: number;
};
import { fetchQuizForDashboardResult } from "@/types/quizzes/fetchQuizForDashboardTypes";
import { fetchQuizForDashboard } from "@/api/quizzes/fetchQuizForDashboard";
import { useParams } from "next/navigation";

export default function DashboardContainer() {
const statData: {
totalQuizCount: number;
averageCorrectRate: number;
quizList: Quiz[];
} = {
totalQuizCount: 4,
averageCorrectRate: 57.5,
quizList: [
{
quizId: "qz-001",
quizOrder: 1,
type: "multipleChoice",
quizBody: "์•™์ƒ๋ธ” ํ•™์Šต์˜ ์ฃผ์š” ๋ชฉ์  ์ค‘ ํ•˜๋‚˜๋กœ ์˜ฌ๋ฐ”๋ฅธ ์„ค๋ช…์„ ๊ณ ๋ฅด์„ธ์š”.",
correctRate: 70.0,
solution: "์—ฌ๋Ÿฌ ๋ชจ๋ธ์„ ๊ฒฐํ•ฉํ•ด ์˜ค๋ฅ˜๋ฅผ ์ค„์ด๊ธฐ ์œ„ํ•ด",
options: [
{
optionOrder: 1,
option: "์—ฌ๋Ÿฌ ๋ชจ๋ธ์„ ๊ฒฐํ•ฉํ•ด ์˜ค๋ฅ˜๋ฅผ ์ค„์ด๊ธฐ ์œ„ํ•ด",
count: 7,
},
{
optionOrder: 2,
option: "ํ•˜๋‚˜์˜ ๋ชจ๋ธ ์„ฑ๋Šฅ์„ ๊ทน๋‹จ์ ์œผ๋กœ ํ–ฅ์ƒ์‹œํ‚ค๊ธฐ ์œ„ํ•ด",
count: 2,
},
{
optionOrder: 3,
option: "๋ชจ๋ธ์˜ ํ•™์Šต ์†๋„๋ฅผ ๋†’์ด๊ธฐ ์œ„ํ•ด",
count: 0,
},
{
optionOrder: 4,
option: "๋ฐ์ดํ„ฐ๋ฅผ ์ค„์—ฌ ๋ชจ๋ธ์„ ๊ฐ„์†Œํ™”ํ•˜๊ธฐ ์œ„ํ•ด",
count: 1,
},
],
},
{
quizId: "qz-002",
quizOrder: 2,
type: "trueFalse",
quizBody: "Random Forest๋Š” ๊ฐœ๋ณ„ ํŠธ๋ฆฌ์˜ ๊ฐ€์ง€์น˜๊ธฐ๋ฅผ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š๋Š”๋‹ค.",
correctRate: 80.0,
solution: "O",
options: [
{ optionOrder: null, option: "O", count: 8 },
{ optionOrder: null, option: "X", count: 2 },
],
},
{
quizId: "qz-003",
quizOrder: 3,
type: "shortAnswer",
quizBody:
"์•™์ƒ๋ธ” ๊ธฐ๋ฒ• ์ค‘ ์—ฌ๋Ÿฌ ๋ชจ๋ธ์ด ๊ฐ์ž ์˜ˆ์ธกํ•œ ๊ฒฐ๊ณผ๋ฅผ ๋‹ค์ˆ˜๊ฒฐ๋กœ ๊ฒฐ์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋ฌด์—‡์ธ๊ฐ€์š”?",
correctRate: 50.0,
solution: "๋ฐฐ๊น…",
count: 5,
},
{
quizId: "qz-004",
quizOrder: 4,
type: "shortAnswer",
quizBody:
"Random Forest์—์„œ ๊ฐœ๋ณ„ ๋ณ€์ˆ˜์˜ ์ค‘์š”๋„๋ฅผ ํ‰๊ฐ€ํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๋ฐ์ดํ„ฐ ์ƒ˜ํ”Œ๋ง ๊ธฐ๋ฒ•์€ ๋ฌด์—‡์ธ๊ฐ€์š”?",
correctRate: 30.0,
solution: "๋ณต์› ์ถ”์ถœ",
count: 3,
},
],
};
const [statData, setStatData] = useState<fetchQuizForDashboardResult | null>(
null
);
const { lectureId } = useParams<{ lectureId: string }>();

useEffect(() => {
if (!lectureId) return;
fetchQuizForDashboard(lectureId).then((res) => {
if (res.isSuccess && res.result) {
setStatData(res.result);
} else {
setStatData(null);
}
});
}, [lectureId]);

return (
<div className={styles.dashboardContainer}>
<QuizInfo />
<div className={styles.dashboardContainerInner}>
<section className={styles.leftSection}>
<QuizSubmitList />
<QuizList
quizList={statData.quizList}
totalQuizCount={statData.totalQuizCount}
quizList={statData?.quizList || []}
totalQuizCount={statData?.totalQuizCount || 0}
/>
</section>
<StatisticsContainer statData={statData} />
<StatisticsContainer
statData={
statData || {
averageCorrectRate: 0,
totalQuizCount: 0,
quizList: [],
}
}
/>
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,43 @@
"use client";
import React from "react";
import React, { useEffect, useState } from "react";
import styles from "./QuizInfo.module.scss";

const data = {
title: "Ensemble 1",
quizDate: "2025-06-03",
quizDay: "ํ™”",
};
import { fetchQuizInfo } from "@/api/quizzes/fetchQuizInfo";
import { useParams } from "next/navigation";
import { fetchQuizInfoResult } from "@/types/quizzes/fetchQuizInfoTypes";

function formatDate(date: string, day: string) {
const [yyyy, mm, dd] = date.split("-");
return `${yyyy}.${mm}.${dd} (${day})`;
}

export default function QuizInfo() {
const { lectureId } = useParams<{ lectureId: string }>();
const [data, setData] = useState<fetchQuizInfoResult | null>(null);

useEffect(() => {
if (!lectureId) return;
fetchQuizInfo(lectureId).then((res) => {
if (res.isSuccess && res.result) {
setData(res.result);
} else {
setData(null);
}
});
}, [lectureId]);

return (
<div className={styles.infoRow}>
<div className={styles.title}>
[{data.title}] <span className={styles.dashboard}>ํ€ด์ฆˆ ๋Œ€์‹œ๋ณด๋“œ</span>
</div>
<div className={styles.date}>
{formatDate(data.quizDate, data.quizDay)}
</div>
{data && (
<>
<div className={styles.title}>
[{data.title}]
<span className={styles.dashboard}>ํ€ด์ฆˆ ๋Œ€์‹œ๋ณด๋“œ</span>
</div>
<div className={styles.date}>
{formatDate(data.quizDate, data.quizDay)}
</div>
</>
)}
</div>
);
}
Loading