diff --git a/dispatcher/static/dashboard.css b/dispatcher/static/dashboard.css index 0394c06..1e94192 100644 --- a/dispatcher/static/dashboard.css +++ b/dispatcher/static/dashboard.css @@ -34,6 +34,7 @@ background-color: var(--dark4); } +/* .by-range #list { display: flex; flex-direction: column; @@ -51,4 +52,44 @@ font-style: italic; padding: 0.4em 0.8em; 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; } \ No newline at end of file diff --git a/dispatcher/static/dashboard.js b/dispatcher/static/dashboard.js index d7e19f6..bf79aa2 100644 --- a/dispatcher/static/dashboard.js +++ b/dispatcher/static/dashboard.js @@ -10,7 +10,7 @@ function prevMonth() { curMonth += 12 curYear -= 1 } - updateListMonthly() + updateTableMonthly() } function nextMonth() { curMonth = curMonth + 1 @@ -18,18 +18,18 @@ function nextMonth() { curMonth -= 12 curYear += 1 } - updateListMonthly() + updateTableMonthly() } function prevWeek() { curWeekDate = new Date(curWeekDate.valueOf() - 7 * DAY_MS) - updateListWeekly() + updateTableWeekly() } function nextWeek() { curWeekDate = new Date(curWeekDate.valueOf() + 7 * DAY_MS) - updateListWeekly() + updateTableWeekly() } -function updateListMonthly() { +function updateTableMonthly() { let year = curYear.toString().padStart(4, "0") let month = (curMonth + 1).toString().padStart(2, "0") let today = new Date() @@ -45,11 +45,11 @@ function updateListMonthly() { if (res.status !== "success") { return } - setListElements(res.data.parents) + updateTable(res.data) }) } -function updateListWeekly() { +function updateTableWeekly() { let weekNum = curWeekDate.getWeek() let weekDay = (curWeekDate.getDay() + 6) % 7 let startDate = new Date(curWeekDate.valueOf() - weekDay * DAY_MS) @@ -68,28 +68,53 @@ function updateListWeekly() { if (res.status !== "success") { return } - setListElements(res.data.parents) + updateTable(res.data) }) } -function setListElements(data) { - data = data.filter(parent => parent.duration !== null) - let list = document.getElementById("list") - let template = document.querySelector(".by-range .template.row").cloneNode(true) - template.classList.remove("template") - list.innerHTML = "" - if (data.length === 0) { - let noData = document.querySelector(".by-range .template.no-data").cloneNode(true) - noData.classList.remove("template") - list.appendChild(noData) - return - } - data.forEach(parent => { - let row = template.cloneNode(true) - row.querySelector(".name").innerText = `${parent.name} (${parent.project_num})` - row.querySelector(".duration").innerText = formatDuration(parent.duration) - list.appendChild(row) +function updateTable(data) { + let totalWorked = data.clockings.map(c => c.total).reduce((a, b) => a + b, 0) + + let parents = data.parents.filter(parent => parent.duration !== null && parent.is_productive) + + let headers = document.getElementById("headers-table") + let table = document.getElementById("projects-table") + let projNames = table.querySelector(".project-names") + let projNums = table.querySelector(".project-nums") + let totDurations = table.querySelector(".total-durations") + let workingRatios = table.querySelector(".working-time-ratios") + let imputedRatios = table.querySelector(".imputed-time-ratios") + let sagexHours = table.querySelector(".sagex-hours") + let realSagexHours = table.querySelector(".real-sagex-hours") + table.querySelectorAll("tr").forEach(row => row.innerHTML = "") + + 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", () => { @@ -115,13 +140,13 @@ window.addEventListener("load", () => { if (mode === "weekly") { monthGrp.classList.add("hidden") weekGrp.classList.remove("hidden") - updateListWeekly() + updateTableWeekly() } else { monthGrp.classList.remove("hidden") weekGrp.classList.add("hidden") - updateListMonthly() + updateTableMonthly() } }) - updateListMonthly() + updateTableMonthly() }) \ No newline at end of file diff --git a/dispatcher/views.py b/dispatcher/views.py index 597180c..cc4b46e 100644 --- a/dispatcher/views.py +++ b/dispatcher/views.py @@ -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): 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 = { "parents": [ { "id": parent.id, "name": parent.name, "project_num": parent.project_num, + "is_productive": parent.is_productive, "duration": parent.total_duration } for parent in parents @@ -197,8 +206,10 @@ def format_stats(parents: QuerySet[Parent], projects: QuerySet[Project]): "duration": project.total_duration } for project in projects - ] + ], + "clockings": ClockingSerializer(clockings, many=True).data } + Clocking.objects.filter() return data class ParentsView(generic.ListView): @@ -210,6 +221,7 @@ class ProjectsView(generic.ListView): model = Project template_name = "projects.html" context_object_name = "elements" + ordering = ["parent"] def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) diff --git a/templates/dashboard.html b/templates/dashboard.html index a29ea05..f9ec4cf 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -8,11 +8,6 @@ {% block footer %}{% endblock %} {% block main %}
+ | |
---|---|
N° SageX | +|
Total imputed | ++ |
Total | ++ |
% working time | ++ |
% imputed time | ++ |
Hours on SageX (theoretical) | ++ |
Hours on SageX (real) | ++ |