added monthly summary on dashboard
This commit is contained in:
parent
cd604e39a0
commit
029502d887
@ -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"),
|
||||
]
|
||||
|
@ -80,3 +80,7 @@ button:hover, select:hover {
|
||||
button, select {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.template {
|
||||
display: none !important;
|
||||
}
|
@ -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);
|
||||
}
|
94
dispatcher/static/dashboard.js
Normal file
94
dispatcher/static/dashboard.js
Normal 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()
|
||||
})
|
@ -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}/`
|
||||
})
|
||||
|
@ -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"
|
||||
|
@ -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 %}
|
@ -7,6 +7,7 @@
|
||||
{% endblock %}
|
||||
{% block footer %}{% endblock %}
|
||||
{% block main %}
|
||||
{% csrf_token %}
|
||||
<table class="projects">
|
||||
<thead>
|
||||
<tr>
|
||||
|
Loading…
x
Reference in New Issue
Block a user