Skip to content

Commit

Permalink
Added Dashboard with Topic and Skill Statistic
Browse files Browse the repository at this point in the history
  • Loading branch information
linxiaoxin committed Oct 1, 2024
1 parent da72589 commit 813e44c
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 117 deletions.
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -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
180 changes: 66 additions & 114 deletions app/(main)/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -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<any>([]);
const menu1 = useRef<Menu>(null);
const menu2 = useRef<Menu>(null);
const [lineOptions, setLineOptions] = useState<ChartOptions>({});
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<ChartOptions>({});
const [topicSkillStats, setTopicSkillStats] = useState<ChartData>();

const { layoutConfig } = useContext(LayoutContext);
const appMsg = useRef<AppMessage>(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.');
Expand All @@ -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;
Expand All @@ -132,48 +118,14 @@ const Dashboard = () => {
}, 500);
}, [layoutConfig.colorScheme]);

const formatCurrency = (value: number) => {
return value?.toLocaleString('en-US', {
style: 'currency',
currency: 'USD'
});
};

return (
<div className="grid">
<AppMessages ref={appMsg}></AppMessages>
<div className="col-12 lg:col-6 xl:col-3">
<div className="card mb-0">
<div className="flex justify-content-between mb-3">
<div>
<span className="block text-500 font-medium mb-3">Questions Attempted</span>
<div className="text-900 font-medium text-xl">152</div>
</div>
</div>
<span className="text-green-500 font-medium">24 new </span>
<span className="text-500">since last visit</span>
</div>
</div>
<div className="col-12 lg:col-6 xl:col-3">
<div className="card mb-0">
<div className="flex justify-content-between mb-3">
<div>
<span className="block text-500 font-medium mb-3">Comments</span>
<div className="text-900 font-medium text-xl">5 Unread</div>
</div>
<div className="flex align-items-center justify-content-center bg-purple-100 border-round" style={{ width: '2.5rem', height: '2.5rem' }}>
<i className="pi pi-comment text-purple-500 text-xl" />
</div>
</div>
<span className="text-green-500 font-medium">10 </span>
<span className="text-500">responded</span>
</div>
</div>

<div className="col-12 xl:col-6">
<div className="card">
<h5>Progress</h5>
<Chart type="line" data={lineData} options={lineOptions} />
<h5>Top most difficult topics-skills</h5>
<Chart type="bar" data={topicSkillStats} options={chartOptions} />
</div>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion app/(main)/quiz/results/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
5 changes: 3 additions & 2 deletions layout/AppMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const AppMenu = () => {
//customise list of menu items for quemistry
const model: AppMenuItem[] = [
{
label: 'Home',
label: 'Workdesk',
items: [
{
label: 'Practice',
Expand All @@ -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']) }
]
},
{
Expand Down
15 changes: 15 additions & 0 deletions service/StatisticService.tsx
Original file line number Diff line number Diff line change
@@ -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<Statistics.TopicSkillStatistics[]>);
}
}
17 changes: 17 additions & 0 deletions types/stats.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
declare namespace Statistics {
interface StatisticsResponse<Type> {
data: Type;
pageNo: number;
pageSize: number;
totalRecords: number;
}

interface TopicSkillStatistics {
topicId: number;
topicName: string;
skillId: number;
skillName: string;
cntAttempt: number;
cntCorrectAnswer: number;
}
}

0 comments on commit 813e44c

Please sign in to comment.