Web Frontend

This commit is contained in:
Jesper 2023-06-22 18:31:06 +02:00
parent 5d97286a77
commit 7c2206b785
5 changed files with 114 additions and 16 deletions

0
data.json Normal file
View File

57
index.html Normal file
View File

@ -0,0 +1,57 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PunctualityTracker</title>
</head>
<style>
html {
background-color: #131516;
color: #d8d4cf;
}
body {
text-align: center;
}
</style>
<script>
function populate_table(data) {
let table = document.getElementById("student_table");
data.forEach(student => {
var tr = document.createElement('tr');
tr.innerHTML = '<td>' + student.name + '</td>' +
'<td>' + student.total_times + '</td>' +
'<td>' + student.total_minutes + '</td>' +
'<td>' + student.total_days + '</td>' +
'<td>' + ((student.total_times / student.total_days) * 100).toFixed(2) + " %" + '</td>' +
'<td>' + student.average_time + '</td>'
table.appendChild(tr);
});
const el = document.getElementById('percent_late')
if (el) {
el.click()
}
}
fetch("data.json")
.then(res => res.json())
.then(data => populate_table(data))
</script>
<body>
<table class="sortable" align="center">
<thead>
<tr>
<th>Elev</th>
<th>Forseelser</th>
<th>Minuter forsent</th>
<th>Dage mødt</th>
<th id="percent_late">% af dage forsent</th>
<th>Gnm møde tidspunkt</th>
</tr>
<tbody id="student_table">
</tbody>
</thead>
</table>
<link href="sortable.min.css" rel="stylesheet" />
<script src="sortable.min.js"></script>
</body>
</html>

70
main.py
View File

@ -1,22 +1,39 @@
import bs4 as bs import bs4 as bs
import requests import requests
import json
import urllib3 import urllib3
from datetime import datetime from datetime import datetime, timedelta, time
urllib3.disable_warnings() urllib3.disable_warnings()
class Student: class Student:
def __init__(self, name, total_times, total_minutes, total_days): def __init__(self, name, total_times, total_minutes, total_days, average_time):
self.name = name self.name = name
self.total_times = total_times self.total_times = total_times
self.total_minutes = total_minutes self.total_minutes = total_minutes
self.total_days = total_days self.total_days = total_days
self.average_time = average_time
class StudentEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, Student):
return {
"name": obj.name,
"total_times": obj.total_times,
"total_minutes": obj.total_minutes,
"total_days": obj.total_days,
"average_time": str(obj.average_time)
}
return super().default(obj)
def get_all_student_data(): def get_all_student_data():
student_ids = get_all_student_ids() student_ids = get_all_student_ids()
students = [] students = []
for student_id in student_ids: for student_id in student_ids:
students.append(get_student_data(student_id)) student = get_student_data(student_id)
if student.name == "Null":
continue
students.append(student)
return students return students
@ -71,23 +88,44 @@ def get_student_data(student_string):
if too_late: if too_late:
total_times += 1 total_times += 1
minutes_total += minutes_difference minutes_total += minutes_difference
if len(times) == 0:
return Student("Null", 0, 0, 0, 0)
# Convert time objects to time durations in seconds
durations = [t.hour * 3600 + t.minute * 60 + t.second for t in times]
# Calculate the average time duration
average_duration = sum(durations) / len(durations)
# Convert the average time duration back to the time format
average_time = time(
int(average_duration // 3600),
int((average_duration % 3600) // 60),
int(average_duration % 60)
)
return Student(student_name, total_times, minutes_total, total_days) return Student(student_name, total_times, minutes_total, total_days, average_time)
students = get_all_student_data() students = get_all_student_data()
file_content = json.dumps(students, cls=StudentEncoder)
# Sort the array based on total_minutes in descending order f = open("data.json", "w")
sorted_array = sorted(students, key=lambda x: x.total_times / x.total_days if x.total_days != 0 else 0, reverse=True) f.write(file_content)
# sorted_array = sorted(students, key=lambda x: x.total_minutes, reverse=True) f.close()
i = 0 # # Sort the array based on total_minutes in descending order
# Print the sorted array # sorted_array = sorted(students, key=lambda x: x.total_times / x.total_days if x.total_days != 0 else 0, reverse=True)
for item in sorted_array: # # sorted_array = sorted(students, key=lambda x: x.total_minutes, reverse=True)
if item.total_times == 0:
continue # i = 0
i += 1 # # Print the sorted array
hours = round(item.total_minutes / 50, 2) # for item in sorted_array:
percent_late = round((item.total_times / item.total_days) * 100, 2) # if item.total_times == 0:
print(f"{str(i)}) Name: {item.name}, Total Times: {item.total_times} / {item.total_days} = {percent_late}%, Total Minutes: {item.total_minutes}, Total Hours: {hours}") # continue
# i += 1
# hours = round(item.total_minutes / 50, 2)
# percent_late = round((item.total_times / item.total_days) * 100, 2)
# print(f"{str(i)}) Name: {item.name}, Total Times: {item.total_times} / {item.total_days} = {percent_late}%, Total Minutes: {item.total_minutes}, Total Hours: {hours}, Average time: {item.average_time}")

1
sortable.min.css vendored Normal file
View File

@ -0,0 +1 @@
.sortable th{cursor:pointer}.sortable th.no-sort{pointer-events:none}.sortable th::after,.sortable th::before{transition:color .1s ease-in-out;font-size:1.2em;color:rgba(0,0,0,0)}.sortable th::after{margin-left:3px;content:"▸"}.sortable th:hover::after{color:inherit}.sortable th.dir-d::after{color:inherit;content:"▾"}.sortable th.dir-u::after{color:inherit;content:"▴"}.sortable th.indicator-left::after{content:""}.sortable th.indicator-left::before{margin-right:3px;content:"▸"}.sortable th.indicator-left:hover::before{color:inherit}.sortable th.indicator-left.dir-d::before{color:inherit;content:"▾"}.sortable th.indicator-left.dir-u::before{color:inherit;content:"▴"}.sortable{--stripe-color: #272b2c;--th-color: #d8d4cf;--th-bg: #181a1b;--td-color: #d8d4cf;--td-on-stripe-color: #000;border-spacing:0}.sortable tbody tr:nth-child(odd){background-color:var(--stripe-color);color:var(--td-on-stripe-color)}.sortable th{background:var(--th-bg);color:var(--th-color);font-weight:normal;text-align:left;text-transform:capitalize;vertical-align:baseline;white-space:nowrap}.sortable td{color:var(--td-color)}.sortable td,.sortable th{padding:10px}.sortable td:first-child,.sortable th:first-child{border-top-left-radius:4px}.sortable td:last-child,.sortable th:last-child{border-top-right-radius:4px}/*# sourceMappingURL=sortable.min.css.map */

2
sortable.min.js vendored Normal file
View File

@ -0,0 +1,2 @@
document.addEventListener("click",function(c){try{function f(a,b){return a.nodeName===b?a:f(a.parentNode,b)}var x=c.shiftKey||c.altKey,d=f(c.target,"TH"),m=f(d,"TR"),g=f(m,"TABLE");function n(a,b){a.classList.remove("dir-d");a.classList.remove("dir-u");b&&a.classList.add(b)}function p(a){return x&&a.dataset.sortAlt||a.dataset.sort||a.textContent}if(g.classList.contains("sortable")){var q,e=m.cells,r=parseInt(d.dataset.sortTbr);for(c=0;c<e.length;c++)e[c]===d?q=parseInt(d.dataset.sortCol)||c:n(e[c],
"");e="dir-d";if(d.classList.contains("dir-d")||g.classList.contains("asc")&&!d.classList.contains("dir-u"))e="dir-u";n(d,e);var t="dir-u"===e,v=function(a,b,h){var u=p((t?a:b).cells[h]);a=p((t?b:a).cells[h]);b=parseFloat(u)-parseFloat(a);return isNaN(b)?u.localeCompare(a):b};for(c=0;c<g.tBodies.length;c++){var k=g.tBodies[c],w=[].slice.call(k.rows,0);w.sort(function(a,b){var h=v(a,b,q);return 0!==h||isNaN(r)?h:v(a,b,r)});var l=k.cloneNode();l.append.apply(l,w);g.replaceChild(l,k)}}}catch(f){}});