Skip to content

Commit fbc17f6

Browse files
barnesc3mbusch3
authored andcommitted
add admin report dashboard page
1 parent 9db8c02 commit fbc17f6

File tree

3 files changed

+236
-3
lines changed

3 files changed

+236
-3
lines changed

assets/js/Components/Admin/AdminApp.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { useState, useEffect, useCallback } from 'react'
22
import AdminHeader from './AdminHeader'
3+
import AdminDashboard from './AdminDashboard'
34
import CoursesPage from './CoursesPage'
45
import ReportsPage from './ReportsPage'
56
import UsersPage from './UsersPage'
@@ -33,7 +34,7 @@ export default function AdminApp(initialData) {
3334
const [filters, setFilters] = useState({...initialFilters})
3435
const [searchTerm, setSearchTerm] = useState('')
3536
const [accountData, setAccountData] = useState([])
36-
const [navigation, setNavigation] = useState('courses')
37+
const [navigation, setNavigation] = useState('dashboard')
3738
const [modal, setModal] = useState(null)
3839
const [loadingCourses, setLoadingCourses] = useState(true)
3940
const [trayOpen, setTrayOpen] = useState(false)
@@ -129,7 +130,7 @@ export default function AdminApp(initialData) {
129130
/>
130131

131132
<main role="main" className="pt-2">
132-
{ (navigation !== 'reports') &&
133+
{ (navigation !== 'reports' && navigation !== 'dashboard') &&
133134
<AdminFilters
134135
t={t}
135136
settings={settings}
@@ -155,6 +156,15 @@ export default function AdminApp(initialData) {
155156
{ !loadingCourses && (
156157
<div className="mt-3">
157158

159+
{('dashboard' === navigation) &&
160+
<AdminDashboard
161+
t={t}
162+
settings={settings}
163+
courses={courses}
164+
handleNavigation={handleNavigation}
165+
addMessage={addMessage}
166+
/>
167+
}
158168
{('courses' === navigation) &&
159169
<CoursesPage
160170
t={t}
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
import React, { useState, useEffect } from 'react'
2+
3+
export default function AdminDashboard({
4+
t,
5+
settings,
6+
courses,
7+
handleNavigation,
8+
addMessage
9+
}) {
10+
const [dashboardStats, setDashboardStats] = useState({
11+
loading: true,
12+
totalCourses: 0,
13+
scannedCourses: 0,
14+
totalInstructors: 0,
15+
uniqueInstructorsUsingUdoit: 0,
16+
totalErrors: 0,
17+
totalSuggestions: 0,
18+
totalFixed: 0,
19+
totalResolved: 0,
20+
totalFilesReviewed: 0,
21+
recentScans: 0,
22+
accountBreakdown: {},
23+
oldestScan: null,
24+
newestScan: null
25+
})
26+
27+
useEffect(() => {
28+
calculateStats()
29+
}, [courses])
30+
31+
const calculateStats = () => {
32+
if (!courses || Object.keys(courses).length === 0) {
33+
setDashboardStats(prev => ({ ...prev, loading: false }))
34+
return
35+
}
36+
37+
const stats = {
38+
loading: false,
39+
totalCourses: 0,
40+
scannedCourses: 0,
41+
totalInstructors: 0,
42+
uniqueInstructorsUsingUdoit: 0,
43+
totalErrors: 0,
44+
totalSuggestions: 0,
45+
totalFixed: 0,
46+
totalResolved: 0,
47+
totalFilesReviewed: 0,
48+
recentScans: 0,
49+
accountBreakdown: {},
50+
oldestScan: null,
51+
newestScan: null
52+
}
53+
54+
const allInstructors = new Set()
55+
const udoitInstructors = new Set()
56+
const accountStats = {}
57+
const scanDates = []
58+
const thirtyDaysAgo = new Date()
59+
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30)
60+
61+
Object.keys(courses).forEach(key => {
62+
const course = courses[key]
63+
stats.totalCourses++
64+
65+
// Track instructors
66+
if (Array.isArray(course.instructors)) {
67+
course.instructors.forEach(instructor => {
68+
allInstructors.add(instructor)
69+
if (course.hasReport) {
70+
udoitInstructors.add(instructor)
71+
}
72+
})
73+
}
74+
75+
// Account breakdown
76+
const accountName = course.accountName || 'Unknown'
77+
if (!accountStats[accountName]) {
78+
accountStats[accountName] = { total: 0, scanned: 0, errors: 0 }
79+
}
80+
accountStats[accountName].total++
81+
82+
if (course.hasReport) {
83+
stats.scannedCourses++
84+
accountStats[accountName].scanned++
85+
86+
// Aggregate report data
87+
if (course.report) {
88+
stats.totalErrors += parseInt(course.report.errors) || 0
89+
stats.totalSuggestions += parseInt(course.report.suggestions) || 0
90+
stats.totalFixed += parseInt(course.report.contentFixed) || 0
91+
stats.totalResolved += parseInt(course.report.contentResolved) || 0
92+
stats.totalFilesReviewed += parseInt(course.report.filesReviewed) || 0
93+
accountStats[accountName].errors += parseInt(course.report.errors) || 0
94+
}
95+
96+
// Track scan dates
97+
if (course.lastUpdated && course.lastUpdated !== '---') {
98+
const scanDate = new Date(course.lastUpdated)
99+
scanDates.push(scanDate)
100+
101+
if (scanDate > thirtyDaysAgo) {
102+
stats.recentScans++
103+
}
104+
}
105+
}
106+
})
107+
108+
stats.totalInstructors = allInstructors.size
109+
stats.uniqueInstructorsUsingUdoit = udoitInstructors.size
110+
stats.accountBreakdown = accountStats
111+
112+
// Find oldest and newest scans
113+
if (scanDates.length > 0) {
114+
scanDates.sort((a, b) => a - b)
115+
stats.oldestScan = scanDates[0]
116+
stats.newestScan = scanDates[scanDates.length - 1]
117+
}
118+
119+
setDashboardStats(stats)
120+
}
121+
122+
if (dashboardStats.loading) {
123+
return <div className="p-3">Loading dashboard...</div>
124+
}
125+
126+
const scanPercentage = dashboardStats.totalCourses > 0
127+
? ((dashboardStats.scannedCourses / dashboardStats.totalCourses) * 100).toFixed(1)
128+
: 0
129+
130+
const instructorAdoption = dashboardStats.totalInstructors > 0
131+
? ((dashboardStats.uniqueInstructorsUsingUdoit / dashboardStats.totalInstructors) * 100).toFixed(1)
132+
: 0
133+
134+
return (
135+
<div className="p-3">
136+
<div className="flex-row justify-content-center mb-3">
137+
<h1 className="mt-0 mb-0 primary-dark">UDOIT Admin Dashboard</h1>
138+
</div>
139+
140+
<div>
141+
<div>
142+
<h2>Course Statistics</h2>
143+
<p>Total Courses: {dashboardStats.totalCourses}</p>
144+
<p>Scanned Courses: {dashboardStats.scannedCourses}</p>
145+
<p>Unscanned Courses: {dashboardStats.totalCourses - dashboardStats.scannedCourses}</p>
146+
<p>Scan Adoption Rate: {scanPercentage}%</p>
147+
<p>Courses Scanned in Last 30 Days: {dashboardStats.recentScans}</p>
148+
</div>
149+
150+
<div>
151+
<h2>Instructor Statistics</h2>
152+
<p>Total Instructors: {dashboardStats.totalInstructors}</p>
153+
<p>Instructors Using UDOIT: {dashboardStats.uniqueInstructorsUsingUdoit}</p>
154+
<p>Instructor Adoption Rate: {instructorAdoption}%</p>
155+
</div>
156+
157+
<div>
158+
<h2>Accessibility Statistics</h2>
159+
<p>Total Accessibility Issues Found: {dashboardStats.totalErrors}</p>
160+
<p>Total Suggestions Made: {dashboardStats.totalSuggestions}</p>
161+
<p>Total Items Fixed: {dashboardStats.totalFixed}</p>
162+
<p>Total Items Resolved: {dashboardStats.totalResolved}</p>
163+
<p>Total Files Reviewed: {dashboardStats.totalFilesReviewed}</p>
164+
165+
{dashboardStats.scannedCourses > 0 && (
166+
<>
167+
<p>Average Issues per Scanned Course: {(dashboardStats.totalErrors / dashboardStats.scannedCourses).toFixed(1)}</p>
168+
<p>Average Files Reviewed per Course: {(dashboardStats.totalFilesReviewed / dashboardStats.scannedCourses).toFixed(1)}</p>
169+
</>
170+
)}
171+
</div>
172+
173+
<div>
174+
<h2>Scan Activity</h2>
175+
{dashboardStats.oldestScan && (
176+
<p>Oldest Scan: {dashboardStats.oldestScan.toLocaleDateString()}</p>
177+
)}
178+
{dashboardStats.newestScan && (
179+
<p>Most Recent Scan: {dashboardStats.newestScan.toLocaleDateString()}</p>
180+
)}
181+
</div>
182+
183+
<div>
184+
<h2>Department/Account Breakdown</h2>
185+
{Object.entries(dashboardStats.accountBreakdown).map(([account, stats]) => (
186+
<div key={account}>
187+
<p><strong>{account}:</strong></p>
188+
<p style={{ marginLeft: '20px' }}>Total Courses: {stats.total}</p>
189+
<p style={{ marginLeft: '20px' }}>Scanned: {stats.scanned} ({stats.total > 0 ? ((stats.scanned / stats.total) * 100).toFixed(1) : 0}%)</p>
190+
<p style={{ marginLeft: '20px' }}>Total Issues: {stats.errors}</p>
191+
</div>
192+
))}
193+
</div>
194+
</div>
195+
196+
<div>
197+
<button
198+
className="btn btn-primary"
199+
onClick={() => handleNavigation('courses')}
200+
>
201+
View Courses
202+
</button>
203+
</div>
204+
</div>
205+
)
206+
}

assets/js/Components/Admin/AdminHeader.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from 'react'
22
import UDOITLogo from '../../../mediaAssets/udoit-logo.svg'
3+
import HomeIcon from '../Icons/HomeIcon'
34
import ContentAssignmentIcon from '../Icons/ContentAssignmentIcon'
45
import UserIcon from '../Icons/UserIcon'
56
import '../Header.css'
@@ -13,7 +14,7 @@ export default function AdminHeader({
1314
return (
1415
<header role="banner">
1516
<nav aria-label={t('menu.nav.label')}>
16-
<div className="flex-row justify-content-start gap-2" onClick={() => handleNavigation('courses')}>
17+
<div className="flex-row justify-content-start gap-2" onClick={() => handleNavigation('dashboard')}>
1718
<div className="flex-column justify-content-center" style={{ width: 'min-content' }}>
1819
<img alt={t('alt.UDOIT')} src={UDOITLogo}></img>
1920
</div>
@@ -23,6 +24,22 @@ export default function AdminHeader({
2324
</div>
2425
<div>
2526
<ul>
27+
<li
28+
className={`flex-row ${navigation === 'dashboard' ? ' active-link' : ''}`}
29+
onClick={() => handleNavigation('dashboard')}
30+
onKeyDown={(e) => {
31+
if(e.key === 'Enter' || e.key === ' ') {
32+
handleNavigation('dashboard')
33+
}
34+
}}
35+
tabIndex="0">
36+
<div className='flex-column justify-content-center'>
37+
<HomeIcon className='icon-md pr-1'/>
38+
</div>
39+
<div className='flex-column justify-content-center'>
40+
Dashboard
41+
</div>
42+
</li>
2643
<li
2744
className={`flex-row ${navigation === 'courses' ? ' active-link' : ''}`}
2845
onClick={() => handleNavigation('courses')}

0 commit comments

Comments
 (0)