From 813e44ce69573b3d2842ee88f8b7680458e60ba2 Mon Sep 17 00:00:00 2001 From: linxiaoxin Date: Tue, 1 Oct 2024 23:09:08 +0800 Subject: [PATCH] Added Dashboard with Topic and Skill Statistic --- .env | 1 + app/(main)/dashboard/page.tsx | 180 ++++++++++++------------------- app/(main)/quiz/results/page.tsx | 2 +- layout/AppMenu.tsx | 5 +- service/StatisticService.tsx | 15 +++ types/stats.ts | 17 +++ 6 files changed, 103 insertions(+), 117 deletions(-) create mode 100644 service/StatisticService.tsx create mode 100644 types/stats.ts diff --git a/.env b/.env index 015870b..2cdc191 100644 --- a/.env +++ b/.env @@ -12,3 +12,4 @@ NEXT_PUBLIC_QUEMISTRY_GENAI_URL = $NEXT_PUBLIC_QUEMISTRY_GATEWAY_URL/genai NEXT_PUBLIC_QUEMISTRY_STUDENTS_URL=$NEXT_PUBLIC_QUEMISTRY_GATEWAY_URL/student NEXT_PUBLIC_QUEMISTRY_STUDENTS_INVITATION_URL=$NEXT_PUBLIC_QUEMISTRY_GATEWAY_URL/student NEXT_PUBLIC_QUEMISTRY_TUTOR_URL=$NEXT_PUBLIC_QUEMISTRY_GATEWAY_URL/users/tutor +NEXT_PUBLIC_QUEMISTRY_STATS_URL=$NEXT_PUBLIC_QUEMISTRY_GATEWAY_URL/stats diff --git a/app/(main)/dashboard/page.tsx b/app/(main)/dashboard/page.tsx index 8e6629e..3ff5bf1 100644 --- a/app/(main)/dashboard/page.tsx +++ b/app/(main)/dashboard/page.tsx @@ -1,113 +1,105 @@ /* eslint-disable @next/next/no-img-element */ 'use client'; -import { Button } from 'primereact/button'; import { Chart } from 'primereact/chart'; -import { Column } from 'primereact/column'; -import { DataTable } from 'primereact/datatable'; -import { Menu } from 'primereact/menu'; import React, { useContext, useEffect, useRef, useState } from 'react'; -//import { ProductService } from '../../../demo/service/ProductService'; import { LayoutContext } from '../../../layout/context/layoutcontext'; -//import Link from 'next/link'; -//import { Demo } from '@/types'; import { ChartData, ChartOptions } from 'chart.js'; -import { Toast } from 'primereact/toast'; import AppMessages, { AppMessage } from '@/components/AppMessages'; +import { StatisticService } from '@/service/StatisticService'; -const lineData: ChartData = { - labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], - datasets: [ - { - label: 'First Dataset', - data: [65, 59, 80, 81, 56, 55, 40], - fill: false, - backgroundColor: '#2f4860', - borderColor: '#2f4860', - tension: 0.4 - }, - { - label: 'Second Dataset', - data: [28, 48, 40, 19, 86, 27, 90], - fill: false, - backgroundColor: '#00bb7e', - borderColor: '#00bb7e', - tension: 0.4 - } - ] -}; +const TOTAL_ATTEMPTS_BAR_COLOR = '#673AB7'; +const CORRECT_ANSWER_BAR_COLOR = '#157be8'; const Dashboard = () => { - const [products, setProducts] = useState([]); - const menu1 = useRef(null); - const menu2 = useRef(null); - const [lineOptions, setLineOptions] = useState({}); + const documentStyle = getComputedStyle(document.documentElement); + const surfaceBorder = documentStyle.getPropertyValue('--surface-border'); + const textColor = documentStyle.getPropertyValue('--text-color'); + const textColorSecondary = documentStyle.getPropertyValue('--text-color-secondary'); + + const [chartOptions, setChartOptions] = useState({}); + const [topicSkillStats, setTopicSkillStats] = useState(); + const { layoutConfig } = useContext(LayoutContext); const appMsg = useRef(null); - const applyLightTheme = () => { - const lineOptions: ChartOptions = { + useEffect(() => { + const chartOptions = { + maintainAspectRatio: false, + aspectRatio: 0.8, plugins: { + tooltips: { + mode: 'index', + intersect: false + }, legend: { labels: { - color: '#495057' + color: textColor } } }, scales: { x: { + stacked: false, ticks: { - color: '#495057' + color: textColorSecondary }, grid: { - color: '#ebedef' + color: surfaceBorder } }, y: { + stacked: false, ticks: { - color: '#495057' + color: textColorSecondary }, grid: { - color: '#ebedef' + color: surfaceBorder } } } }; - setLineOptions(lineOptions); - }; + setChartOptions(chartOptions); + }, []); - const applyDarkTheme = () => { - const lineOptions = { - plugins: { - legend: { - labels: { - color: '#ebedef' - } - } - }, - scales: { - x: { - ticks: { - color: '#ebedef' - }, - grid: { - color: 'rgba(160, 167, 181, .3)' - } - }, - y: { - ticks: { - color: '#ebedef' + useEffect(() => { + StatisticService.getTopicSkillStatistics(0, 10).then((statsResponse) => { + let topicSkillStat = statsResponse.data; + let chartLabels: string[] = []; + let totalAttempts: number[] = []; + let correctAnswers: number[] = []; + + //labels + //array of data set + topicSkillStat.map((item) => { + chartLabels.push(item.topicName + ' - ' + item.skillName); + totalAttempts.push(item.cntAttempt); + correctAnswers.push(item.cntCorrectAnswer); + }); + let chartData: ChartData = { + labels: chartLabels, + datasets: [ + { + label: 'Total Attempts', + data: totalAttempts, + fill: true, + backgroundColor: TOTAL_ATTEMPTS_BAR_COLOR, + borderColor: TOTAL_ATTEMPTS_BAR_COLOR, + tension: 0.4 }, - grid: { - color: 'rgba(160, 167, 181, .3)' + { + label: 'Correct Answers', + data: correctAnswers, + fill: true, + backgroundColor: CORRECT_ANSWER_BAR_COLOR, + borderColor: CORRECT_ANSWER_BAR_COLOR, + tension: 0.4 } - } - } - }; - - setLineOptions(lineOptions); - }; - + ] + }; + setTopicSkillStats(chartData); + }); + }, []); const invitationResponse = (isSucceeded: boolean) => { if (!isSucceeded) { appMsg.current?.showError('Error enrolling to the class. Please contact customer support for more info.'); @@ -117,12 +109,6 @@ const Dashboard = () => { }; useEffect(() => { - if (layoutConfig.colorScheme === 'light') { - applyLightTheme(); - } else { - applyDarkTheme(); - } - setTimeout(() => { if (typeof sessionStorage !== 'undefined' && sessionStorage.getItem('invitation_result') !== null) { const invitationResult = sessionStorage.getItem('invitation_result') === 'true' || false; @@ -132,48 +118,14 @@ const Dashboard = () => { }, 500); }, [layoutConfig.colorScheme]); - const formatCurrency = (value: number) => { - return value?.toLocaleString('en-US', { - style: 'currency', - currency: 'USD' - }); - }; - return (
-
-
-
-
- Questions Attempted -
152
-
-
- 24 new - since last visit -
-
-
-
-
-
- Comments -
5 Unread
-
-
- -
-
- 10 - responded -
-
-
Progress
- +
Top most difficult topics-skills
+
diff --git a/app/(main)/quiz/results/page.tsx b/app/(main)/quiz/results/page.tsx index dfdce9d..00c0302 100644 --- a/app/(main)/quiz/results/page.tsx +++ b/app/(main)/quiz/results/page.tsx @@ -28,7 +28,7 @@ const ResultsPage: React.FC = () => { if (!quizId) { throw new Error('Quiz ID is undefined'); } - const data = await QuizService.fetchQuizById(Number(quizId));; + const data = await QuizService.fetchQuizById(Number(quizId)); setQuiz(data); } catch (error) { console.error('Error fetching quiz:', error); diff --git a/layout/AppMenu.tsx b/layout/AppMenu.tsx index 7c4bdcb..3d965b6 100644 --- a/layout/AppMenu.tsx +++ b/layout/AppMenu.tsx @@ -32,7 +32,7 @@ const AppMenu = () => { //customise list of menu items for quemistry const model: AppMenuItem[] = [ { - label: 'Home', + label: 'Workdesk', items: [ { label: 'Practice', @@ -44,7 +44,8 @@ const AppMenu = () => { // { label: 'Results', icon: 'pi pi-fw pi-chart-bar', to: '/quiz/results', visible: accessibleBy(['student']) } ] }, - { label: 'Take Test', icon: 'pi pi-fw pi-graduation-cap', to: '/test', visible: accessibleBy(['student']) } + { label: 'Take Test', icon: 'pi pi-fw pi-graduation-cap', to: '/test', visible: accessibleBy(['student']) }, + { label: 'Dashboard', icon: 'pi pi-fw pi-chart-line', to: '/dashboard', visible: accessibleBy(['admin', 'tutor', 'student']) } ] }, { diff --git a/service/StatisticService.tsx b/service/StatisticService.tsx new file mode 100644 index 0000000..4ac5fb6 --- /dev/null +++ b/service/StatisticService.tsx @@ -0,0 +1,15 @@ +const topicSkillStatsUrl = `${process.env.NEXT_PUBLIC_QUEMISTRY_STATS_URL}/topic-skill` + + +export const StatisticService = { + getTopicSkillStatistics(pageNo: number, pageSize: number) { + const params = new URLSearchParams({ pageno: pageNo.toString(), pagesize: pageSize.toString() }); + //console.log("API URL: " + topicSkillStatsUrl + params.toString()); + + return fetch(`${topicSkillStatsUrl}?${params.toString()}`,{ + credentials: 'include' + }) + .then(res => res.json()) + .then(data => data as Statistics.StatisticsResponse); + } +} \ No newline at end of file diff --git a/types/stats.ts b/types/stats.ts new file mode 100644 index 0000000..211598d --- /dev/null +++ b/types/stats.ts @@ -0,0 +1,17 @@ +declare namespace Statistics { + interface StatisticsResponse { + data: Type; + pageNo: number; + pageSize: number; + totalRecords: number; + } + + interface TopicSkillStatistics { + topicId: number; + topicName: string; + skillId: number; + skillName: string; + cntAttempt: number; + cntCorrectAnswer: number; + } +}