reworked dashboard with table summary

This commit is contained in:
Louis Heredero 2025-02-03 12:06:59 +01:00
parent aeed1f5fbe
commit 6777207f4e
Signed by: HEL
GPG Key ID: 8D83DE470F8544E7
4 changed files with 154 additions and 38 deletions

View File

@ -34,6 +34,7 @@
background-color: var(--dark4); background-color: var(--dark4);
} }
/*
.by-range #list { .by-range #list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -51,4 +52,44 @@
font-style: italic; font-style: italic;
padding: 0.4em 0.8em; padding: 0.4em 0.8em;
background-color: var(--dark2); background-color: var(--dark2);
}
*/
.by-range .tables {
display: flex;
}
.by-range .tables table {
border-collapse: collapse;
}
.by-range .tables tr {
height: 2em;
}
.by-range .tables td {
padding: 0 0.4em;
min-width: 3em;
}
#headers-table th {
text-align: right;
padding: 0 0.8em;
}
#projects-table th {
text-align: center;
padding: 0 0.4em;
}
#projects-table td {
text-align: right;
}
.by-range tr.project-nums {
border-bottom: solid var(--light1) 2px;
}
#projects-table th, #projects-table td {
border-left: solid var(--light4) 1px;
} }

View File

@ -10,7 +10,7 @@ function prevMonth() {
curMonth += 12 curMonth += 12
curYear -= 1 curYear -= 1
} }
updateListMonthly() updateTableMonthly()
} }
function nextMonth() { function nextMonth() {
curMonth = curMonth + 1 curMonth = curMonth + 1
@ -18,18 +18,18 @@ function nextMonth() {
curMonth -= 12 curMonth -= 12
curYear += 1 curYear += 1
} }
updateListMonthly() updateTableMonthly()
} }
function prevWeek() { function prevWeek() {
curWeekDate = new Date(curWeekDate.valueOf() - 7 * DAY_MS) curWeekDate = new Date(curWeekDate.valueOf() - 7 * DAY_MS)
updateListWeekly() updateTableWeekly()
} }
function nextWeek() { function nextWeek() {
curWeekDate = new Date(curWeekDate.valueOf() + 7 * DAY_MS) curWeekDate = new Date(curWeekDate.valueOf() + 7 * DAY_MS)
updateListWeekly() updateTableWeekly()
} }
function updateListMonthly() { function updateTableMonthly() {
let year = curYear.toString().padStart(4, "0") let year = curYear.toString().padStart(4, "0")
let month = (curMonth + 1).toString().padStart(2, "0") let month = (curMonth + 1).toString().padStart(2, "0")
let today = new Date() let today = new Date()
@ -45,11 +45,11 @@ function updateListMonthly() {
if (res.status !== "success") { if (res.status !== "success") {
return return
} }
setListElements(res.data.parents) updateTable(res.data)
}) })
} }
function updateListWeekly() { function updateTableWeekly() {
let weekNum = curWeekDate.getWeek() let weekNum = curWeekDate.getWeek()
let weekDay = (curWeekDate.getDay() + 6) % 7 let weekDay = (curWeekDate.getDay() + 6) % 7
let startDate = new Date(curWeekDate.valueOf() - weekDay * DAY_MS) let startDate = new Date(curWeekDate.valueOf() - weekDay * DAY_MS)
@ -68,28 +68,53 @@ function updateListWeekly() {
if (res.status !== "success") { if (res.status !== "success") {
return return
} }
setListElements(res.data.parents) updateTable(res.data)
}) })
} }
function setListElements(data) { function updateTable(data) {
data = data.filter(parent => parent.duration !== null) let totalWorked = data.clockings.map(c => c.total).reduce((a, b) => a + b, 0)
let list = document.getElementById("list")
let template = document.querySelector(".by-range .template.row").cloneNode(true) let parents = data.parents.filter(parent => parent.duration !== null && parent.is_productive)
template.classList.remove("template")
list.innerHTML = "" let headers = document.getElementById("headers-table")
if (data.length === 0) { let table = document.getElementById("projects-table")
let noData = document.querySelector(".by-range .template.no-data").cloneNode(true) let projNames = table.querySelector(".project-names")
noData.classList.remove("template") let projNums = table.querySelector(".project-nums")
list.appendChild(noData) let totDurations = table.querySelector(".total-durations")
return let workingRatios = table.querySelector(".working-time-ratios")
} let imputedRatios = table.querySelector(".imputed-time-ratios")
data.forEach(parent => { let sagexHours = table.querySelector(".sagex-hours")
let row = template.cloneNode(true) let realSagexHours = table.querySelector(".real-sagex-hours")
row.querySelector(".name").innerText = `${parent.name} (${parent.project_num})` table.querySelectorAll("tr").forEach(row => row.innerHTML = "")
row.querySelector(".duration").innerText = formatDuration(parent.duration)
list.appendChild(row) let totalImputed = 0
parents.forEach(parent => {
totalImputed += +parent.duration
}) })
headers.querySelector(".total-clocking").innerText = formatDuration(totalWorked)
headers.querySelector(".total-duration").innerText = formatDuration(totalImputed)
let totalSagex = 0
parents.forEach(parent => {
let duration = +parent.duration
let name = document.createElement("th")
name.innerText = parent.name
projNames.appendChild(name)
projNums.insertCell(-1).innerText = parent.project_num
table.querySelector(".clocking").insertCell(-1)
totDurations.insertCell(-1).innerText = formatDuration(duration)
let ratioWorking = duration / totalWorked
let ratioImputed = duration / totalImputed
workingRatios.insertCell(-1).innerText = formatPercentage(ratioWorking)
imputedRatios.insertCell(-1).innerText = formatPercentage(ratioImputed)
let sagexDuration = duration * totalWorked / totalImputed
totalSagex += sagexDuration
sagexHours.insertCell(-1).innerText = formatDuration(sagexDuration)
realSagexHours.insertCell(-1)
})
headers.querySelector(".sagex-hours-total").innerText = formatDuration(totalSagex)
} }
window.addEventListener("load", () => { window.addEventListener("load", () => {
@ -115,13 +140,13 @@ window.addEventListener("load", () => {
if (mode === "weekly") { if (mode === "weekly") {
monthGrp.classList.add("hidden") monthGrp.classList.add("hidden")
weekGrp.classList.remove("hidden") weekGrp.classList.remove("hidden")
updateListWeekly() updateTableWeekly()
} else { } else {
monthGrp.classList.remove("hidden") monthGrp.classList.remove("hidden")
weekGrp.classList.add("hidden") weekGrp.classList.add("hidden")
updateListMonthly() updateTableMonthly()
} }
}) })
updateListMonthly() updateTableMonthly()
}) })

View File

@ -156,7 +156,11 @@ def get_stats_by_month(request, year: int, month: int):
) )
) )
) )
return JsonResponse({"status": "success", "data": format_stats(parents, projects)}) clockings = Clocking.objects.filter(
date__year=year,
date__month=month
)
return JsonResponse({"status": "success", "data": format_stats(parents, projects, clockings)})
def get_stats_between(request, start_date: datetime.date, end_date: datetime.date): def get_stats_between(request, start_date: datetime.date, end_date: datetime.date):
parents = Parent.objects.annotate( parents = Parent.objects.annotate(
@ -177,15 +181,20 @@ def get_stats_between(request, start_date: datetime.date, end_date: datetime.dat
) )
) )
) )
return JsonResponse({"status": "success", "data": format_stats(parents, projects)}) clockings = Clocking.objects.filter(
date__gte=start_date,
date__lt=end_date + timedelta(days=1)
)
return JsonResponse({"status": "success", "data": format_stats(parents, projects, clockings)})
def format_stats(parents: QuerySet[Parent], projects: QuerySet[Project]): def format_stats(parents: QuerySet[Parent], projects: QuerySet[Project], clockings: QuerySet[Clocking]):
data = { data = {
"parents": [ "parents": [
{ {
"id": parent.id, "id": parent.id,
"name": parent.name, "name": parent.name,
"project_num": parent.project_num, "project_num": parent.project_num,
"is_productive": parent.is_productive,
"duration": parent.total_duration "duration": parent.total_duration
} }
for parent in parents for parent in parents
@ -197,8 +206,10 @@ def format_stats(parents: QuerySet[Parent], projects: QuerySet[Project]):
"duration": project.total_duration "duration": project.total_duration
} }
for project in projects for project in projects
] ],
"clockings": ClockingSerializer(clockings, many=True).data
} }
Clocking.objects.filter()
return data return data
class ParentsView(generic.ListView): class ParentsView(generic.ListView):
@ -210,6 +221,7 @@ class ProjectsView(generic.ListView):
model = Project model = Project
template_name = "projects.html" template_name = "projects.html"
context_object_name = "elements" context_object_name = "elements"
ordering = ["parent"]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)

View File

@ -8,11 +8,6 @@
{% block footer %}{% endblock %} {% block footer %}{% endblock %}
{% block main %} {% block main %}
<div class="by-range"> <div class="by-range">
<div class="template row">
<div class="name"></div>
<div class="duration"></div>
</div>
<div class="template no-data">No Data</div>
<div class="controls"> <div class="controls">
<div id="month-sel" class="group"> <div id="month-sel" class="group">
<button id="prev-month"><</button> <button id="prev-month"><</button>
@ -29,6 +24,49 @@
<option value="weekly">Weekly</option> <option value="weekly">Weekly</option>
</select> </select>
</div> </div>
<div id="list"></div> <div class="tables">
<table id="headers-table">
<tr class="project-names">
<th colspan="2"></th>
</tr>
<tr class="project-nums">
<th colspan="2">N° SageX</th>
</tr>
<tr class="clocking">
<th>Total imputed</th>
<td class="total-clocking"></td>
</tr>
<tr class="total-durations">
<th>Total</th>
<td class="total-duration"></td>
</tr>
<tr class="working-time-ratios">
<th>% working time</th>
<td class="working-time-ratio"></td>
</tr>
<tr class="imputed-time-ratios">
<th>% imputed time</th>
<td></td>
</tr>
<tr class="sagex-hours">
<th>Hours on SageX (theoretical)</th>
<td class="sagex-hours-total"></td>
</tr>
<tr class="real-sagex-hours">
<th>Hours on SageX (real)</th>
<td class="sagex-hours-diff"></td>
</tr>
</table>
<table id="projects-table">
<tr class="project-names"></tr>
<tr class="project-nums"></tr>
<tr class="clocking"></tr>
<tr class="total-durations"></tr>
<tr class="working-time-ratios"></tr>
<tr class="imputed-time-ratios"></tr>
<tr class="sagex-hours"></tr>
<tr class="real-sagex-hours"></tr>
</table>
</div>
</div> </div>
{% endblock %} {% endblock %}