reworked dashboard with table summary
This commit is contained in:
parent
aeed1f5fbe
commit
6777207f4e
@ -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;
|
||||||
}
|
}
|
@ -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()
|
||||||
})
|
})
|
@ -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)
|
||||||
|
@ -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 %}
|
Loading…
x
Reference in New Issue
Block a user