added weekly view
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
.monthly {
|
||||
.by-range {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 1.2em;
|
||||
@ -6,13 +6,23 @@
|
||||
gap: 1.2em;
|
||||
}
|
||||
|
||||
.monthly .controls {
|
||||
.by-range .controls {
|
||||
display: flex;
|
||||
gap: 1.2em;
|
||||
gap: 2.4em;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.monthly .controls button {
|
||||
.by-range .controls .group {
|
||||
display: flex;
|
||||
gap: 0.8em;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.by-range .controls .group.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.by-range .controls button, .by-range .controls select {
|
||||
background-color: var(--dark3);
|
||||
color: var(--light1);
|
||||
padding: 0.4em 0.8em;
|
||||
@ -20,24 +30,24 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.monthly .controls button:hover {
|
||||
.by-range .controls button:hover, .by-range .controls select:hover {
|
||||
background-color: var(--dark4);
|
||||
}
|
||||
|
||||
.monthly #list {
|
||||
.by-range #list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.8em;
|
||||
}
|
||||
|
||||
.monthly #list .row {
|
||||
.by-range #list .row {
|
||||
display: flex;
|
||||
gap: 1.2em;
|
||||
padding: 0.4em 0.8em;
|
||||
background-color: var(--dark3);
|
||||
}
|
||||
|
||||
.monthly #list .no-data {
|
||||
.by-range #list .no-data {
|
||||
font-style: italic;
|
||||
padding: 0.4em 0.8em;
|
||||
background-color: var(--dark2);
|
||||
|
@ -1,22 +1,36 @@
|
||||
let prevBtn, nextBtn, month
|
||||
let prevMonthBtn, nextMonthBtn, month
|
||||
let prevWeekBtn, nextWeekBtn, week
|
||||
let curYear = new Date().getFullYear()
|
||||
let curMonth = new Date().getMonth()
|
||||
let curWeekDate = new Date()
|
||||
|
||||
const SEC_MS = 1000
|
||||
const MIN_MS = SEC_MS * 60
|
||||
const HOUR_MS = MIN_MS * 60
|
||||
const DAY_MS = HOUR_MS * 24
|
||||
|
||||
const MONTHS = [
|
||||
"Janvier",
|
||||
"Février",
|
||||
"Mars",
|
||||
"Avril",
|
||||
"Mai",
|
||||
"Juin",
|
||||
"Juillet",
|
||||
"Août",
|
||||
"Septembre",
|
||||
"Octobre",
|
||||
"Novembre",
|
||||
"Décembre"
|
||||
"January",
|
||||
"February",
|
||||
"March",
|
||||
"April",
|
||||
"May",
|
||||
"June",
|
||||
"July",
|
||||
"August",
|
||||
"September",
|
||||
"October",
|
||||
"November",
|
||||
"December"
|
||||
]
|
||||
|
||||
function formatDate(date) {
|
||||
let year = date.getFullYear().toString().padStart(4, "0")
|
||||
let month = (date.getMonth() + 1).toString().padStart(2, "0")
|
||||
let day = date.getDate().toString().padStart(2, "0")
|
||||
return `${year}-${month}-${day}`
|
||||
}
|
||||
|
||||
function formatDuration(duration) {
|
||||
let hours = Math.floor(duration / 60)
|
||||
duration -= hours * 60
|
||||
@ -36,7 +50,7 @@ function prevMonth() {
|
||||
curMonth += 12
|
||||
curYear -= 1
|
||||
}
|
||||
updateList()
|
||||
updateListMonthly()
|
||||
}
|
||||
function nextMonth() {
|
||||
curMonth = curMonth + 1
|
||||
@ -44,10 +58,18 @@ function nextMonth() {
|
||||
curMonth -= 12
|
||||
curYear += 1
|
||||
}
|
||||
updateList()
|
||||
updateListMonthly()
|
||||
}
|
||||
function prevWeek() {
|
||||
curWeekDate = new Date(curWeekDate.valueOf() - 7 * DAY_MS)
|
||||
updateListWeekly()
|
||||
}
|
||||
function nextWeek() {
|
||||
curWeekDate = new Date(curWeekDate.valueOf() + 7 * DAY_MS)
|
||||
updateListWeekly()
|
||||
}
|
||||
|
||||
function updateList() {
|
||||
function updateListMonthly() {
|
||||
let year = curYear.toString().padStart(4, "0")
|
||||
let month = (curMonth + 1).toString().padStart(2, "0")
|
||||
let today = new Date()
|
||||
@ -63,32 +85,82 @@ function updateList() {
|
||||
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)
|
||||
setListElements(res.data.parents)
|
||||
})
|
||||
}
|
||||
|
||||
function updateListWeekly() {
|
||||
let weekDay = (curWeekDate.getDay() + 6) % 7
|
||||
let startDate = new Date(curWeekDate.valueOf() - weekDay * DAY_MS)
|
||||
let endDate = new Date(startDate.valueOf() + 6 * DAY_MS)
|
||||
|
||||
let today = new Date()
|
||||
let txt = `Week of ${MONTHS[startDate.getMonth()]} ${startDate.getDate()}`
|
||||
if (startDate.getFullYear() !== today.getFullYear()) {
|
||||
txt += " " + startDate.getFullYear().toString().padStart(4, "0")
|
||||
}
|
||||
document.getElementById("week").innerText = txt
|
||||
|
||||
fetch(`stats/between/${formatDate(startDate)}/${formatDate(endDate)}/`).then(res => {
|
||||
return res.json()
|
||||
}).then(res => {
|
||||
if (res.status !== "success") {
|
||||
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)
|
||||
})
|
||||
setListElements(res.data.parents)
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
window.addEventListener("load", () => {
|
||||
prevBtn = document.getElementById("prev")
|
||||
nextBtn = document.getElementById("next")
|
||||
prevMonthBtn = document.getElementById("prev-month")
|
||||
nextMonthBtn = document.getElementById("next-month")
|
||||
month = document.getElementById("month")
|
||||
|
||||
prevBtn.addEventListener("click", () => prevMonth())
|
||||
nextBtn.addEventListener("click", () => nextMonth())
|
||||
updateList()
|
||||
prevWeekBtn = document.getElementById("prev-week")
|
||||
nextWeekBtn = document.getElementById("next-week")
|
||||
week = document.getElementById("week")
|
||||
|
||||
prevMonthBtn.addEventListener("click", () => prevMonth())
|
||||
nextMonthBtn.addEventListener("click", () => nextMonth())
|
||||
prevWeekBtn.addEventListener("click", () => prevWeek())
|
||||
nextWeekBtn.addEventListener("click", () => nextWeek())
|
||||
|
||||
let monthGrp = document.getElementById("month-sel")
|
||||
let weekGrp = document.getElementById("week-sel")
|
||||
|
||||
rangeSel = document.getElementById("range-sel")
|
||||
rangeSel.addEventListener("change", () => {
|
||||
let mode = rangeSel.value
|
||||
if (mode === "weekly") {
|
||||
monthGrp.classList.add("hidden")
|
||||
weekGrp.classList.remove("hidden")
|
||||
updateListWeekly()
|
||||
} else {
|
||||
monthGrp.classList.remove("hidden")
|
||||
weekGrp.classList.add("hidden")
|
||||
updateListMonthly()
|
||||
}
|
||||
})
|
||||
|
||||
updateListMonthly()
|
||||
})
|
@ -1,5 +1,7 @@
|
||||
from datetime import timedelta
|
||||
|
||||
from django.core.files.uploadedfile import UploadedFile
|
||||
from django.db.models import Sum, Q
|
||||
from django.db.models import Sum, Q, QuerySet
|
||||
from django.http import JsonResponse
|
||||
from django.shortcuts import render, get_object_or_404, redirect
|
||||
from django.views import generic
|
||||
@ -71,17 +73,59 @@ def get_stats_by_month(request, year: int, month: int):
|
||||
)
|
||||
)
|
||||
)
|
||||
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})
|
||||
projects = Project.objects.annotate(
|
||||
total_duration=Sum(
|
||||
"task__duration",
|
||||
filter=Q(
|
||||
task__date__year=year,
|
||||
task__date__month=month
|
||||
)
|
||||
)
|
||||
)
|
||||
return JsonResponse({"status": "success", "data": format_stats(parents, projects)})
|
||||
|
||||
def get_stats_between(request, start_date, end_date):
|
||||
parents = Parent.objects.annotate(
|
||||
total_duration=Sum(
|
||||
"project__task__duration",
|
||||
filter=Q(
|
||||
project__task__date__gte=start_date,
|
||||
project__task__date__lt=end_date + timedelta(days=1)
|
||||
)
|
||||
)
|
||||
)
|
||||
projects = Project.objects.annotate(
|
||||
total_duration=Sum(
|
||||
"task__duration",
|
||||
filter=Q(
|
||||
task__date__gte=start_date,
|
||||
task__date__lt=end_date + timedelta(days=1)
|
||||
)
|
||||
)
|
||||
)
|
||||
return JsonResponse({"status": "success", "data": format_stats(parents, projects)})
|
||||
|
||||
def format_stats(parents: QuerySet[Parent], projects: QuerySet[Project]):
|
||||
data = {
|
||||
"parents": [
|
||||
{
|
||||
"id": parent.id,
|
||||
"name": parent.name,
|
||||
"project_num": parent.project_num,
|
||||
"duration": parent.total_duration
|
||||
}
|
||||
for parent in parents
|
||||
],
|
||||
"projects": [
|
||||
{
|
||||
"id": project.id,
|
||||
"name": project.name,
|
||||
"duration": project.total_duration
|
||||
}
|
||||
for project in projects
|
||||
]
|
||||
}
|
||||
return data
|
||||
|
||||
class ParentsView(generic.ListView):
|
||||
model = Parent
|
||||
|
Reference in New Issue
Block a user