Skip to content

Commit 81853ef

Browse files
authored
Merge pull request ucfopen#1051 from ewainberg/issue/1047-multi-line-graph
Adds UDOIT Reports Super Overhaul
2 parents bd36867 + 3c8c79f commit 81853ef

File tree

15 files changed

+1023
-264
lines changed

15 files changed

+1023
-264
lines changed

assets/js/Components/Admin/AdminApp.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ export default function AdminApp(initialData) {
132132
}
133133

134134
{ !loadingCourses && (
135-
<div className="non-scrollable">
135+
<div className="mt-3">
136136

137137
{('courses' === navigation) &&
138138
<CoursesPage

assets/js/Components/Admin/CoursesPage.js

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,16 @@ export default function CoursePage({
1717

1818
const [filteredCourses, setFilteredCourses] = useState([])
1919
const [tableSettings, setTableSettings] = useState({
20-
sortBy: 'errors',
20+
sortBy: 'lastUpdated',
2121
ascending: false,
2222
pageNum: 0,
2323
rowsPerPage: (localStorage.getItem('rowsPerPage')) ? localStorage.getItem('rowsPerPage') : '10'
2424
})
25-
2625
const headers = [
2726
{ id: "courseName", text: t('report.header.course_name') },
2827
{ id: "accountName", text: t('report.header.account_name') },
2928
{ id: "lastUpdated", text: t('report.header.last_scanned') },
30-
{ id: "errors", text: t('report.header.issues'), alignText: "center" },
29+
{ id: "barriers", text: t('report.header.issues'), alignText: "center" },
3130
{ id: "suggestions", text: t('report.header.suggestions'), alignText: "center" },
3231
{ id: "contentFixed", text: t('report.header.items_fixed'), alignText: "center" },
3332
{ id: "contentResolved", text: t('report.header.items_resolved'), alignText: "center" },
@@ -59,15 +58,19 @@ export default function CoursePage({
5958
}
6059

6160
if (!excludeCourse) {
62-
// The Course data from the database is stored in the `course` object.
63-
// The data for the table is converted to the `row` object.
61+
const scanCounts = course.report?.scanCounts || {};
62+
const barriers = scanCounts.errors || 0;
63+
const suggestions = scanCounts.suggestions || 0;
6464
let row = {
6565
id: course.id,
6666
course,
6767
courseName: <a href={course.publicUrl} target="_blank" rel="noopener noreferrer">{course.title}</a>,
6868
courseTitle: course.title, // Used for sorting, not displayed outside of courseName element
6969
accountName: course.accountName,
70+
barriers,
71+
suggestions,
7072
lastUpdated: course.lastUpdated,
73+
7174
action: <div class="flex-row gap-1">
7275
<button key={`reportButton${course.id}`}
7376
onClick={() => { !course.loading && handleReportClick(course) }}
@@ -92,7 +95,7 @@ export default function CoursePage({
9295
</div>
9396

9497
}
95-
tempFilteredCourses.push({...row, ...course.report})
98+
tempFilteredCourses.push({...course.report, ...row,})
9699
}
97100
})
98101

@@ -102,6 +105,9 @@ export default function CoursePage({
102105
if (sortBy === 'courseName') {
103106
return (a['courseTitle'].toLowerCase() < b['courseTitle'].toLowerCase()) ? -1 : 1
104107
}
108+
if (sortBy === 'lastUpdated') {
109+
return new Date(a.lastUpdated) < new Date(b.lastUpdated) ? -1 : 1
110+
}
105111
if (isNaN(a[sortBy]) || isNaN(b[sortBy])) {
106112
return (a[sortBy].toLowerCase() < b[sortBy].toLowerCase()) ? -1 : 1
107113
}
@@ -177,7 +183,7 @@ export default function CoursePage({
177183
}
178184

179185
return (
180-
<div className="p-0 scrollable h-100">
186+
<div className="report-page-container scrollable">
181187
<div className="flex-row justify-content-center mt-3 mb-3">
182188
<h1 className="mt-0 mb-0 primary-dark">{t('report.header.courses')}</h1>
183189
</div>

assets/js/Components/Admin/ReportsPage.js

Lines changed: 70 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -6,71 +6,87 @@ import ReportsTable from '../Reports/ReportsTable'
66
import IssuesTable from '../Reports/IssuesTable'
77
import ProgressIcon from '../Icons/ProgressIcon'
88
import '../ReportsPage.css'
9+
import { analyzeReport } from '../../Services/Report'
910

1011
export default function ReportsPage({
1112
t,
1213
settings,
1314
filters,
1415
selectedCourse
1516
}) {
17+
const [groupedReports, setGroupedReports] = useState(null)
18+
const [issues, setIssues] = useState(null)
1619

17-
const [reports, setReports] = useState(null)
18-
const [issues, setIssues] = useState(null)
20+
const ISSUE_STATE = {
21+
UNCHANGED: 0,
22+
SAVING: 1,
23+
RESOLVING: 2,
24+
SAVED: 3,
25+
RESOLVED: 4,
26+
ERROR: 5,
27+
}
1928

20-
const getReportHistory = () => {
21-
const api = new Api(settings)
22-
if(selectedCourse === null) {
23-
api.getAdminReportHistory(filters)
24-
.then((responseStr) => responseStr.json())
25-
.then((response) => {
26-
if (!Array.isArray(response.data)) {
27-
let tempReports = Object.values(response.data.reports)
28-
for (let report of tempReports) {
29-
report.id = report.created
30-
}
31-
setReports(tempReports)
32-
setIssues(response.data.issues)
33-
}
34-
else {
35-
setReports(null)
36-
setIssues(null)
37-
}
38-
39-
})
40-
}
41-
else {
42-
api.getCourseReport(selectedCourse.id)
43-
.then((responseStr) => responseStr.json())
44-
.then((response) => {
45-
if (response.data) {
46-
let tempReports = Object.values(response.data.reports)
47-
for (let report of tempReports) {
48-
report.id = report.created
49-
}
50-
setReports(tempReports)
51-
setIssues(response.data.issues)
52-
}
53-
else {
54-
setReports(null)
55-
setIssues(null)
56-
}
57-
})
58-
}
29+
const getReportHistory = () => {
30+
const api = new Api(settings);
31+
api.getAdminReportHistory(filters)
32+
.then((responseStr) => responseStr.json())
33+
.then((response) => {
34+
if (!Array.isArray(response.data)) {
35+
36+
const groupedReports = {}; // Initialize groupedReports
37+
// Iterate through each course
38+
Object.entries(response.data.reports).forEach(([courseName, courseDates]) => {
39+
groupedReports[courseName] = {}; // Initialize course in groupedReports
40+
41+
// Iterate through each date in this course
42+
Object.entries(courseDates).forEach(([date, reportData]) => {
43+
const analyzedReport = analyzeReport(reportData, ISSUE_STATE);
44+
45+
if (date.match(/^\d{4}-\d{2}-\d{2}$/) || date.match(/^\d{2}\/\d{2}\/\d{4}$/)) {
46+
// Update groupedReports with the analyzed report
47+
groupedReports[courseName][date] = {
48+
...analyzedReport,
49+
};
50+
}
51+
});
52+
});
5953

60-
}
54+
// Update state with analyzed reports
55+
setGroupedReports(groupedReports);
56+
setIssues(response.data.issues);
57+
} else {
58+
setGroupedReports(null);
59+
setIssues(null);
60+
}
61+
})
62+
.catch((error) => {
63+
console.error("Error fetching reports:", error);
64+
});
65+
};
6166

6267
useEffect(() => {
63-
if (reports === null) {
68+
if (groupedReports === null) {
6469
getReportHistory()
6570
}
6671
}, [])
6772

6873
return (
69-
<div className="p-0 scrollable h-100">
74+
<div className="report-page-container scrollable">
7075
<div className="flex-row justify-content-center mt-3">
71-
<h1 className="mt-0 mb-0 primary-dark">{selectedCourse?.title || t('report.header.all_courses')}</h1>
76+
<div className="flex-column w-100">
77+
<h1 className="mt-0 mb-0 primary-dark" style={{ textAlign: "center" }}>
78+
{selectedCourse?.title || t('report.header.all_courses')}
79+
</h1>
80+
<hr
81+
style={{
82+
margin: "8px auto 0 auto",
83+
borderTop: "1px solid",
84+
width: "90%"
85+
}}
86+
/>
87+
</div>
7288
</div>
73-
{ (reports === null) ? (
89+
{ (groupedReports === null) ? (
7490
<div className="mt-3 mb-3 flex-row justify-content-center">
7591
<div className="flex-column justify-content-center me-3">
7692
<ProgressIcon className="icon-lg udoit-suggestion spinner" />
@@ -81,31 +97,33 @@ export default function ReportsPage({
8197
</div>
8298
) : (
8399
<div>
84-
{(reports.length === 0) ?
100+
{(groupedReports.length === 0) ?
85101
<div className="flex-row justify-content-center mt-3">
86102
<div>{t('report.label.no_results')}</div>
87103
</div>
88104
:
89105
<div className="flex-column">
90106
<div className="mt-4">
91-
<div className="flex-row w-100 justify-content-center">
92-
<h2 className="primary-dark mt-0 mb-2">{t('report.title.barriers_remaining')}</h2>
93-
</div>
94107
<div id="resolutionsReport" className="graph-container">
95-
<ResolutionsReport t={t} reports={reports}/>
108+
<ResolutionsReport
109+
t={t}
110+
reports={groupedReports}
111+
selectedCourse={selectedCourse}
112+
/>
96113
</div>
97114
</div>
98115
<div className="mt-3">
99116
<IssuesTable
100117
t={t}
101118
issues={issues}
102119
isAdmin={true}
120+
selectedCourse={selectedCourse}
103121
/>
104122
</div>
105123
<div className="mt-3">
106124
<ReportsTable
107125
t={t}
108-
reports={reports}
126+
reports={groupedReports}
109127
isAdmin={true}
110128
/>
111129
</div>
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
.course-browser-container {
2+
padding: 0;
3+
}
4+
5+
.course-browser-header {
6+
background-color: #fff;
7+
z-index: 1;
8+
padding: 10px;
9+
border-bottom: 1px solid #ccc;
10+
}
11+
12+
.course-browser-search {
13+
width: 85%;
14+
padding: 7px;
15+
}
16+
17+
.course-browser-limit {
18+
display: block;
19+
margin-top: 5px;
20+
color: #666;
21+
}
22+
23+
.course-browser-deselect {
24+
margin-top: 10px;
25+
padding: 5px 10px;
26+
background-color: #f5f5f5;
27+
border: 1px solid #ccc;
28+
border-radius: 4px;
29+
cursor: pointer;
30+
font-size: 14px;
31+
}
32+
33+
.course-browser-list {
34+
padding: 10px;
35+
}
36+
37+
.course-browser-label {
38+
display: block;
39+
margin-bottom: 10px;
40+
}
41+
42+
.course-browser-label.disabled {
43+
color: #aaa;
44+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import React from "react";
2+
import "./CourseBrowser.css";
3+
4+
const COURSE_LIMIT = 5;
5+
6+
export default function CourseBrowser({
7+
filteredCourseNames,
8+
selectedCourses,
9+
setSelectedCourses,
10+
searchTerm,
11+
setSearchTerm,
12+
selectedCount,
13+
t,
14+
}) {
15+
const handleCheckbox = (name) => {
16+
// Prevent selecting more than COURSE_LIMIT
17+
if (!selectedCourses[name] && selectedCount >= COURSE_LIMIT) return;
18+
setSelectedCourses((prev) => ({
19+
...prev,
20+
[name]: !prev[name],
21+
}));
22+
};
23+
24+
return (
25+
<div className="course-browser-container">
26+
{/* Search + actions */}
27+
<div className="course-browser-header">
28+
<input
29+
type="text"
30+
placeholder={t("report.label.search_courses")}
31+
value={searchTerm}
32+
onChange={(e) => setSearchTerm(e.target.value)}
33+
className="course-browser-search"
34+
/>
35+
<small className="course-browser-limit">
36+
{t("report.label.course_limit")}
37+
</small>
38+
<button
39+
onClick={() => setSelectedCourses({})}
40+
className="course-browser-deselect"
41+
style={{ textTransform: "none" }}
42+
>
43+
{t("report.button.deselect_all")}
44+
</button>
45+
</div>
46+
{/* Course list */}
47+
<div className="course-browser-list">
48+
{filteredCourseNames.map((name) => {
49+
const checked = !!selectedCourses[name];
50+
const disabled =
51+
!checked && selectedCount >= COURSE_LIMIT;
52+
return (
53+
<label
54+
key={name}
55+
className={`course-browser-label${disabled ? " disabled" : ""}`}
56+
>
57+
<input
58+
type="checkbox"
59+
checked={checked}
60+
disabled={disabled}
61+
onChange={() => handleCheckbox(name)}
62+
/>{" "}
63+
{name}
64+
</label>
65+
);
66+
})}
67+
</div>
68+
</div>
69+
);
70+
}

assets/js/Components/Reports/IssuesTable.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ export default function IssuesTable({
88
t,
99
issues,
1010
quickSearchTerm = null,
11-
isAdmin
11+
isAdmin,
12+
selectedCourse
1213
}) {
1314

1415
const headers = [
@@ -19,8 +20,8 @@ export default function IssuesTable({
1920
{ id: "resolved", text: t('report.header.resolved'), alignText: 'center' },
2021
]
2122

22-
if (isAdmin) {
23-
headers.push({ id: "courses", text: t('report.header.courses') })
23+
if (isAdmin && (selectedCourse == null)) {
24+
headers.push({ id: "courses", text: t('report.header.courses'), alignText: 'center' })
2425
}
2526

2627
headers.push({ id: "total", text: t('report.header.total'), alignText: 'center' })

0 commit comments

Comments
 (0)