added monthly summary on dashboard

This commit is contained in:
Louis Heredero 2025-01-26 02:01:38 +01:00
parent cd604e39a0
commit 029502d887
Signed by: HEL
GPG Key ID: 8D83DE470F8544E7
8 changed files with 217 additions and 1 deletions

View File

@ -25,4 +25,6 @@ urlpatterns = [
path("projects/", views.projects_view, name="projects"),
path("parents/", views.ParentsView.as_view(), name="parents"),
path("projects/<int:id>/", views.project_view, name="project"),
path("projects/<int:id>/set_parent", views.set_parent, name="set_parent"),
path("stats/by-month/<int:year>/<int:month>/", views.get_stats_by_month, name="stats_by_month"),
]

View File

@ -79,4 +79,8 @@ button:hover, select:hover {
button, select {
cursor: pointer;
}
.template {
display: none !important;
}

View File

@ -0,0 +1,44 @@
.monthly {
display: flex;
flex-direction: column;
padding: 1.2em;
border: solid var(--light4) 1px;
gap: 1.2em;
}
.monthly .controls {
display: flex;
gap: 1.2em;
align-items: center;
}
.monthly .controls button {
background-color: var(--dark3);
color: var(--light1);
padding: 0.4em 0.8em;
border: none;
cursor: pointer;
}
.monthly .controls button:hover {
background-color: var(--dark4);
}
.monthly #list {
display: flex;
flex-direction: column;
gap: 0.8em;
}
.monthly #list .row {
display: flex;
gap: 1.2em;
padding: 0.4em 0.8em;
background-color: var(--dark3);
}
.monthly #list .no-data {
font-style: italic;
padding: 0.4em 0.8em;
background-color: var(--dark2);
}

View File

@ -0,0 +1,94 @@
let prevBtn, nextBtn, month
let curYear = new Date().getFullYear()
let curMonth = new Date().getMonth()
const MONTHS = [
"Janvier",
"Février",
"Mars",
"Avril",
"Mai",
"Juin",
"Juillet",
"Août",
"Septembre",
"Octobre",
"Novembre",
"Décembre"
]
function formatDuration(duration) {
let hours = Math.floor(duration / 60)
duration -= hours * 60
let res = ""
if (hours > 0) {
res += hours.toString() + "h"
res += duration.toString().padStart(2, "0")
} else {
res += duration.toString() + "min"
}
return res
}
function prevMonth() {
curMonth = curMonth - 1
if (curMonth < 0) {
curMonth += 12
curYear -= 1
}
updateList()
}
function nextMonth() {
curMonth = curMonth + 1
if (curMonth >= 12) {
curMonth -= 12
curYear += 1
}
updateList()
}
function updateList() {
let year = curYear.toString().padStart(4, "0")
let month = (curMonth + 1).toString().padStart(2, "0")
let today = new Date()
let txt = MONTHS[curMonth]
if (today.getFullYear() !== curYear) {
txt += " " + year.toString().padStart(4, "0")
}
document.getElementById("month").innerText = txt
fetch(`stats/by-month/${year}/${month}/`).then(res => {
return res.json()
}).then(res => {
if (res.status !== "success") {
return
}
let data = res.data.filter(parent => parent.duration !== null)
let list = document.getElementById("list")
let template = document.querySelector(".monthly .template.row").cloneNode(true)
template.classList.remove("template")
list.innerHTML = ""
if (data.length === 0) {
let noData = document.querySelector(".monthly .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)
})
})
}
window.addEventListener("load", () => {
prevBtn = document.getElementById("prev")
nextBtn = document.getElementById("next")
month = document.getElementById("month")
prevBtn.addEventListener("click", () => prevMonth())
nextBtn.addEventListener("click", () => nextMonth())
updateList()
})

View File

@ -1,6 +1,20 @@
window.addEventListener("load", () => {
document.querySelectorAll(".projects tbody tr").forEach(row => {
let id = row.dataset.id
let selector = row.querySelector(".parent-sel")
selector.addEventListener("change", () => {
let fd = new FormData()
fd.set("parent_id", selector.value)
let csrftoken = document.querySelector("input[name='csrfmiddlewaretoken']").value
fetch(`${id}/set_parent`, {
method: "POST",
body: fd,
headers: {
"X-CSRFToken": csrftoken
}
})
})
row.querySelector("button.see").addEventListener("click", () => {
window.location.href = `${id}/`
})

View File

@ -1,10 +1,13 @@
from django.core.files.uploadedfile import UploadedFile
from django.db.models import Sum, Q
from django.http import JsonResponse
from django.shortcuts import render, get_object_or_404, redirect
from django.views import generic
from django.views.decorators.http import require_POST
from dispatcher.core import import_tasks
from dispatcher.forms import ProjectForm, ImportForm
from dispatcher.models import Project, Parent
from dispatcher.models import Project, Parent, Task
def dashboard_view(request):
@ -30,6 +33,21 @@ def project_view(request, id):
context["form"] = ProjectForm(instance=project)
return render(request, "project.html", context)
@require_POST
def set_parent(request, id):
project = get_object_or_404(Project, id=id)
parent_id = request.POST.get("parent_id")
try:
parent = Parent.objects.get(id=parent_id)
except Parent.DoesNotExist:
parent = None
project.parent = parent
project.save()
return JsonResponse({"status": "success"})
def import_view(request):
if request.method == "POST":
form = ImportForm(request.POST, request.FILES)
@ -40,6 +58,31 @@ def import_view(request):
return render(request, "import.html")
def get_stats_by_month(request, year: int, month: int):
if month < 1 or month > 12:
return JsonResponse({"status": "error", "error": f"Invalid month {month}"})
parents = Parent.objects.annotate(
total_duration=Sum(
"project__task__duration",
filter=Q(
project__task__date__year=year,
project__task__date__month=month
)
)
)
data = [
{
"id": parent.id,
"name": parent.name,
"project_num": parent.project_num,
"duration": parent.total_duration
}
for parent in parents
]
return JsonResponse({"status": "success", "data": data})
class ParentsView(generic.ListView):
model = Parent
template_name = "parents.html"

View File

@ -3,7 +3,21 @@
{% block title %}Dashboard{% endblock %}
{% block head %}
<link rel="stylesheet" href="{% static "dashboard.css" %}">
<script src="{% static "dashboard.js" %}"></script>
{% endblock %}
{% block footer %}{% endblock %}
{% block main %}
<div class="monthly">
<div class="template row">
<div class="name"></div>
<div class="duration"></div>
</div>
<div class="template no-data">No Data</div>
<div class="controls">
<button id="prev"><</button>
<div id="month">Mois</div>
<button id="next">></button>
</div>
<div id="list"></div>
</div>
{% endblock %}

View File

@ -7,6 +7,7 @@
{% endblock %}
{% block footer %}{% endblock %}
{% block main %}
{% csrf_token %}
<table class="projects">
<thead>
<tr>