diff --git a/client/src/api/api.ts b/client/src/api/api.ts index d8bb953fd..e7e22834c 100644 --- a/client/src/api/api.ts +++ b/client/src/api/api.ts @@ -5271,6 +5271,55 @@ export const TaskDtoTypeEnum = { export type TaskDtoTypeEnum = typeof TaskDtoTypeEnum[keyof typeof TaskDtoTypeEnum]; +/** + * + * @export + * @interface TaskPerformanceStatsDto + */ +export interface TaskPerformanceStatsDto { + /** + * Total number of students who submitted the task + * @type {number} + * @memberof TaskPerformanceStatsDto + */ + 'totalAchievement': number; + /** + * Number of students scoring between 1% and 20% of the maximum points + * @type {number} + * @memberof TaskPerformanceStatsDto + */ + 'minimalAchievement': number; + /** + * Number of students scoring between 21% and 50% of the maximum points + * @type {number} + * @memberof TaskPerformanceStatsDto + */ + 'lowAchievement': number; + /** + * Number of students scoring between 51% and 70% of the maximum points + * @type {number} + * @memberof TaskPerformanceStatsDto + */ + 'moderateAchievement': number; + /** + * Number of students scoring between 71% and 90% of the maximum points + * @type {number} + * @memberof TaskPerformanceStatsDto + */ + 'highAchievement': number; + /** + * Number of students scoring between 91% and 99% of the maximum points + * @type {number} + * @memberof TaskPerformanceStatsDto + */ + 'exceptionalAchievement': number; + /** + * Number of students achieving a perfect score of 100% + * @type {number} + * @memberof TaskPerformanceStatsDto + */ + 'perfectScores': number; +} /** * * @export @@ -8208,6 +8257,43 @@ export const CourseStatsApiAxiosParamCreator = function (configuration?: Configu + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {number} courseId + * @param {number} taskId + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getTaskPerformance: async (courseId: number, taskId: number, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'courseId' is not null or undefined + assertParamExists('getTaskPerformance', 'courseId', courseId) + // verify required parameter 'taskId' is not null or undefined + assertParamExists('getTaskPerformance', 'taskId', taskId) + const localVarPath = `/courses/{courseId}/stats/task/{taskId}/performance` + .replace(`{${"courseId"}}`, encodeURIComponent(String(courseId))) + .replace(`{${"taskId"}}`, encodeURIComponent(String(taskId))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; @@ -8267,6 +8353,17 @@ export const CourseStatsApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.getCourseStudentCountries(courseId, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @param {number} courseId + * @param {number} taskId + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getTaskPerformance(courseId: number, taskId: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getTaskPerformance(courseId, taskId, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, } }; @@ -8313,6 +8410,16 @@ export const CourseStatsApiFactory = function (configuration?: Configuration, ba getCourseStudentCountries(courseId: number, options?: any): AxiosPromise { return localVarFp.getCourseStudentCountries(courseId, options).then((request) => request(axios, basePath)); }, + /** + * + * @param {number} courseId + * @param {number} taskId + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getTaskPerformance(courseId: number, taskId: number, options?: any): AxiosPromise { + return localVarFp.getTaskPerformance(courseId, taskId, options).then((request) => request(axios, basePath)); + }, }; }; @@ -8366,6 +8473,18 @@ export class CourseStatsApi extends BaseAPI { public getCourseStudentCountries(courseId: number, options?: AxiosRequestConfig) { return CourseStatsApiFp(this.configuration).getCourseStudentCountries(courseId, options).then((request) => request(this.axios, this.basePath)); } + + /** + * + * @param {number} courseId + * @param {number} taskId + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof CourseStatsApi + */ + public getTaskPerformance(courseId: number, taskId: number, options?: AxiosRequestConfig) { + return CourseStatsApiFp(this.configuration).getTaskPerformance(courseId, taskId, options).then((request) => request(this.axios, this.basePath)); + } } diff --git a/client/src/modules/AdminDashboard/components/DonutChart/DonutChart.tsx b/client/src/modules/AdminDashboard/components/DonutChart/DonutChart.tsx new file mode 100644 index 000000000..024246cb5 --- /dev/null +++ b/client/src/modules/AdminDashboard/components/DonutChart/DonutChart.tsx @@ -0,0 +1,32 @@ +import { Pie, PieConfig } from '@ant-design/plots'; + +type Props = { + data: { + type: string; + value: number; + }[]; + config?: Partial; +}; + +const DonutChart = ({ data, config = {} }: Props) => { + const pieConfig: PieConfig = { + ...config, + data, + angleField: 'value', + colorField: 'type', + innerRadius: 0.6, + label: { + content: '', + }, + legend: { + color: { + title: false, + position: 'right', + rowPadding: 5, + }, + }, + }; + return ; +}; + +export default DonutChart; diff --git a/client/src/modules/AdminDashboard/components/TaskPerformanceCard/TaskPerformanceCard.tsx b/client/src/modules/AdminDashboard/components/TaskPerformanceCard/TaskPerformanceCard.tsx new file mode 100644 index 000000000..ea9c45c40 --- /dev/null +++ b/client/src/modules/AdminDashboard/components/TaskPerformanceCard/TaskPerformanceCard.tsx @@ -0,0 +1,92 @@ +import { Datum } from '@antv/g2plot'; +import { Card, Flex, Form, Image, Select, Typography } from 'antd'; +import { CourseStatsApi, CourseTaskDto, TaskPerformanceStatsDto } from 'api'; +import { useActiveCourseContext } from 'modules/Course/contexts'; +import dynamic from 'next/dynamic'; +import { useState } from 'react'; +import { useAsync } from 'react-use'; +import { Colors } from '../data'; + +const courseStatsApi = new CourseStatsApi(); + +type Props = { + tasks: CourseTaskDto[]; +}; + +const { Text } = Typography; + +const DonutChart = dynamic(() => import('../DonutChart/DonutChart'), { ssr: false }); + +export const TaskPerformanceCard = ({ tasks }: Props) => { + const { course } = useActiveCourseContext(); + + const [taskId, setTaskId] = useState(); + + const { value: taskPerformanceStats } = useAsync(async () => { + if (taskId) { + const { data } = await courseStatsApi.getTaskPerformance(course.id, taskId); + return data; + } + }, [taskId]); + + return ( + + +