Skip to content

Commit

Permalink
Merge pull request #167 from GDSC-Hongik/feature/student-statistics
Browse files Browse the repository at this point in the history
[Feature] 멘토 페이지 스터디 통계 조회 기능 생성
  • Loading branch information
SeieunYoo authored Jan 5, 2025
2 parents 402a744 + 3b6c513 commit 9008481
Show file tree
Hide file tree
Showing 15 changed files with 1,374 additions and 5 deletions.
15 changes: 14 additions & 1 deletion apps/admin/apis/study/studyApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ import type {
import type { AttendanceApiResponseDto } from "types/dtos/attendance";
import type { CurriculumApiResponseDto } from "types/dtos/curriculumList";
import type { StudyBasicInfoApiResponseDto } from "types/dtos/studyBasicInfo";
import type { PaginatedStudyStudentResponseDto } from "types/dtos/studyStudent";
import type { StudyStatisticsApiResponseDto } from "types/dtos/studyStatistics";
import type {
PaginatedStudyStudentResponseDto,
StudyStudentApiResponseDto,
} from "types/dtos/studyStudent";
import type { PageableType } from "types/entities/page";
import type { StudyAnnouncementType } from "types/entities/study";

Expand Down Expand Up @@ -172,4 +176,13 @@ export const studyApi = {

return response.data;
},
getStudyStatistics: async (studyId: number) => {
const response = await fetcher.get<StudyStatisticsApiResponseDto>(
`/mentor/study-details/statistics?studyId=${studyId}`,
{
next: { tags: [tags.statistics] },
}
);
return response.data;
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { css } from "@styled-system/css";
import { Flex } from "@styled-system/jsx";
import { Text } from "@wow-class/ui";
import type { StudyWeekStatisticsApiResponseDto } from "types/dtos/studyStatistics";

import BarGraph from "./graph/BarGraph";

const StudyAnalyticsGraph = ({
graphTitle,
averageRate = 0,
totalStudent,
studyWeekStatisticsResponses,
}: {
graphTitle: string;
averageRate?: number;
totalStudent?: number;
studyWeekStatisticsResponses?: StudyWeekStatisticsApiResponseDto[];
}) => {
return (
<Flex direction="column" gap="md">
<Text className={staticsTitleStyle} typo="h3">
{graphTitle}
</Text>
<Flex direction="column" gap="xs">
<Flex alignItems="flex-start" direction="column" gap="md">
{studyWeekStatisticsResponses?.map((data) => (
<Flex direction="row" gap="lg" key={data.week} minWidth="340px">
<Text
as="div"
className={studyWeekStyle}
color="sub"
typo="body1"
>
{data.week}주차
</Text>
<BarGraph
isCurriculumCanceled={data.isCurriculumCanceled}
percent={data.attendanceRate}
totalStudent={totalStudent}
/>
</Flex>
))}
<Flex direction="row" gap="lg">
<Text className={studyWeekStyle} color="sub" typo="body1">
평균
</Text>
<BarGraph
barColor="average"
isToolTipActive={false}
percent={averageRate}
/>
</Flex>
</Flex>
</Flex>
</Flex>
);
};

export default StudyAnalyticsGraph;

const studyWeekStyle = css({
minWidth: "45px",
});

const staticsTitleStyle = css({
marginBottom: "10px",
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"use client";
import { Flex } from "@styled-system/jsx";
import { Text, Tooltip } from "@wow-class/ui";
import { Help } from "wowds-icons";

import CircleGraph from "./graph/CircleGraph";

const StudyCompleteRate = ({
studyCompleteCount,
studyCompleteRate,
}: {
studyCompleteCount?: number;
studyCompleteRate?: number;
}) => {
return (
<Flex direction="column" gap="md" minWidth="300px">
<Flex alignItems="center" gap="xs" marginLeft="8px">
<Text typo="h3">수료율</Text>
<Tooltip
content={
<Text color="white" typo="body1">
출석율과 과제제출률의 합이
<br />
70% 이상인 학생 비율
</Text>
}
>
<Help fill="textBlack" height={20} stroke="textBlack" width={20} />
</Tooltip>
</Flex>
<Flex alignItems="center" direction="column" gap="lg">
<CircleGraph percentage={studyCompleteRate} />
<Text as="span" color="sub" typo="label1">
수료 가능한 인원{" "}
<Text as="span" color="primary">
{studyCompleteCount}
</Text>
</Text>
</Flex>
</Flex>
);
};

export default StudyCompleteRate;
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Flex } from "@styled-system/jsx";
import { Space, Text } from "@wow-class/ui";
import { studyApi } from "apis/study/studyApi";

import StudyAnalyticsGraph from "./StudyAnalyticsGraph";
import StudyCompleteRate from "./StudyCompleteRate";

const StudyStatics = async ({ studyId }: { studyId: string }) => {
const studyStatistics = await studyApi.getStudyStatistics(
parseInt(studyId, 10)
);

return (
<section aria-label="study-statics">
<Flex direction="column" gap="sm">
<Text typo="h2">스터디 통계</Text>
<Text color="sub" typo="label2">
전체 {studyStatistics?.totalStudentCount}명, 수료 인원{" "}
{studyStatistics?.completeStudentCount}
</Text>
</Flex>
<Space height={33} />
<Flex alignItems="flex-start" gap="xl">
<StudyAnalyticsGraph
averageRate={studyStatistics?.averageAttendanceRate}
graphTitle="출석률"
totalStudent={studyStatistics?.totalStudentCount}
studyWeekStatisticsResponses={
studyStatistics?.studyWeekStatisticsResponses
}
/>
<StudyAnalyticsGraph
averageRate={studyStatistics?.averageAssignmentSubmissionRate}
graphTitle="과제 제출률"
totalStudent={studyStatistics?.totalStudentCount}
studyWeekStatisticsResponses={
studyStatistics?.studyWeekStatisticsResponses
}
/>
<StudyCompleteRate
studyCompleteCount={studyStatistics?.completeStudentCount}
studyCompleteRate={studyStatistics?.studyCompleteRate}
/>
</Flex>
<Space height={33} />
</section>
);
};

export default StudyStatics;
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
"use client";
import { css, cva } from "@styled-system/css";
import { Flex } from "@styled-system/jsx";
import { Text } from "@wow-class/ui";
import clsx from "clsx";

import GraphToolTip from "./GraphToolTip";

const BarGraph = ({
barColor = "default",
totalStudent = 0,
percent = 0,
isCurriculumCanceled,
isToolTipActive = true,
}: {
barColor?: "default" | "average";
totalStudent?: number;
isToolTipActive?: boolean;
percent?: number;
isCurriculumCanceled?: boolean;
}) => {
return (
<Flex
alignItems="center"
flexShrink="0"
gap="sm"
justifyContent="flex-start"
>
<div
className={BarGraphBackgroundStyle({
type: isCurriculumCanceled ? "canceled" : "default",
})}
>
{percent > 0 ? (
<div
style={{ width: `${50 + (232 - 50) * (percent / 100)}px` }}
className={clsx(
barGraphStyle({
type: barColor,
}),
isToolTipActive &&
css({
"&:hover .tooltip": {
visibility: "visible !important",
},
})
)}
>
<div className={barGraphInnerStyle}>
<Text className={percentLabelStyle} color="white" typo="label2">
{percent}%
</Text>
<GraphToolTip
studentCount={Math.ceil((percent / 100) * totalStudent)}
/>
</div>
</div>
) : (
<Text className={zeroPercentLabelStyle} color="sub" typo="label2">
0%
</Text>
)}
</div>
{isCurriculumCanceled && (
<Text as="div" color="sub" typo="label2">
휴강
</Text>
)}
</Flex>
);
};

export default BarGraph;

const BarGraphBackgroundStyle = cva({
base: {
position: "relative",
minWidth: "232px",
width: "232px",
height: "32px",
display: "flex",
alignItems: "center",
gap: "4px",
flex: 2,
zIndex: "5",
borderRadius: "4px",
},
variants: {
type: {
default: {
backgroundColor: "monoBackgroundPressed",
},
canceled: {
backgroundColor: "lightDisabled",
},
},
},
});

const barGraphStyle = cva({
base: {
position: "absolute",
zIndex: 10,
top: 0,
left: 0,
height: "32px",
padding: "4px 8px",
display: "flex",
alignItems: "center",
borderRadius: "4px",
},

variants: {
type: {
default: {
backgroundColor: "primary",
},
average: {
backgroundColor: "secondaryYellow",
},
},
},
});

const percentLabelStyle = css({
height: "100%",
display: "flex",
alignItems: "center",
});

const barGraphInnerStyle = css({
position: "relative",
width: "100%",
height: "100%",
});

const zeroPercentLabelStyle = css({
marginLeft: "8px",
});
Loading

0 comments on commit 9008481

Please sign in to comment.