added table view

This commit is contained in:
2025-01-28 21:14:15 +01:00
parent 01db4285c2
commit 0380d18cd7
7 changed files with 520 additions and 4 deletions

140
dispatcher/static/table.css Normal file
View File

@ -0,0 +1,140 @@
#table {
border-collapse: collapse;
font-size: 80%;
table-layout: auto;
width: 100%;
--border: solid var(--light1) 1px;
th {
font-weight: bold;
padding: 0.4em 0.8em;
}
.clockings {
.dates {
th {
text-align: center;
}
}
.clocking {
th:first-child {
text-align: right;
}
.total-header {
writing-mode: vertical-lr;
transform: rotate(180deg);
padding: 0.8em 0.4em;
}
.time-input {
max-width: 4em; /* not perfect */
input {
width: 100%;
text-align: right;
}
input[type="time"]::-webkit-calendar-picker-indicator {
display: none;
}
}
}
}
.week-names {
text-align: center;
font-weight: bold;
td {
padding: 0.4em;
&:not(:first-child) {
border: var(--border);
}
}
}
.day-names, .day-dates {
text-align: center;
td {
padding: 0.2em;
}
}
.day-dates {
border-bottom: var(--border);
}
col.day-5, col.day-6 {
background-color: var(--dark3);
}
colgroup.tailers, col.day-0 {
border-left: var(--border);
}
colgroup.headers, col.day-6 {
border-right: solid var(--light1) 1px;
}
.times .project td:nth-child(2) {
text-align: center;
}
.times {
td {
padding: 0.4em;
}
tr {
&.project {
border-top: solid #71717185 1px;
td {
text-align: right;
&:first-child {
text-align: left;
}
&:nth-child(2) {
text-align: center;
}
}
}
&.parent {
border-top: var(--border);
td {
font-weight: bold;
}
}
}
}
.separator {
height: 2em;
}
}
.controls {
display: flex;
gap: 0.8em;
align-items: center;
margin-bottom: 1em;
button {
background-color: var(--dark3);
color: var(--light1);
padding: 0.4em 0.8em;
border: none;
cursor: pointer;
&:hover {
background-color: var(--dark4);
}
}
}

272
dispatcher/static/table.js Normal file
View File

@ -0,0 +1,272 @@
let table
let prevMonthBtn, nextMonthBtn, month
let curMonth = new Date().getMonth()
const DAYS_SHORT = [
"Mon",
"Tue",
"Wed",
"Thu",
"Fri",
"Sat",
"Sun"
]
const MONTHS = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
]
const SEC_MS = 1000
const MIN_MS = SEC_MS * 60
const HOUR_MS = MIN_MS * 60
const DAY_MS = HOUR_MS * 24
//////////////////////////////////////////////////////////
// //
// Taken from: https://weeknumber.com/how-to/javascript //
// //
//////////////////////////////////////////////////////////
Date.prototype.getWeek = function() {
let date = new Date(this.getTime())
date.setHours(0, 0, 0, 0)
// Thursday in current week decides the year.
date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7)
// January 4 is always in week 1.
let week1 = new Date(date.getFullYear(), 0, 4)
// Adjust to Thursday in week 1 and count number of weeks from date to week1.
return 1 + Math.round(((date.getTime() - week1.getTime()) / 86400000 - 3 + (week1.getDay() + 6) % 7) / 7)
}
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 getTemplate(cls) {
let elmt = document.querySelector(".template." + cls).cloneNode(true)
elmt.classList.remove("template")
return elmt
}
class Table {
constructor(elmt) {
this.table = elmt
this.clockings = elmt.querySelector(".clockings")
this.times = elmt.querySelector(".times")
this.weekNames = elmt.querySelector(".week-names")
this.dayNames = elmt.querySelector(".day-names")
this.dayDates = elmt.querySelector(".day-dates")
this.columns = elmt.querySelector(".columns")
this.timeInputTemplate = getTemplate("time-input")
this.nDays = 0
let today = new Date()
this.curMonth = today.getMonth()
this.curYear = today.getFullYear()
this.startDate = today
this.endDate = today
this.update()
}
update() {
let year = this.curYear.toString().padStart(4, "0")
let month = (this.curMonth + 1).toString().padStart(2, "0")
let date = new Date(`${year}-${month}-01`)
let txt = MONTHS[this.curMonth]
if (new Date().getFullYear() !== this.curYear) {
txt += " " + year
}
document.getElementById("month").innerText = txt
this.clear()
this.addMonth(date)
this.fetchData()
}
prevMonth() {
this.curMonth--
if (this.curMonth < 0) {
this.curMonth += 12
this.curYear -= 1
}
this.update()
}
nextMonth() {
this.curMonth++
if (this.curMonth >= 12) {
this.curMonth -= 12
this.curYear += 1
}
this.update()
}
clear() {
this.columns.innerHTML = ""
this.clockings.querySelectorAll(".dates th").forEach(c => c.remove())
this.clockings.querySelectorAll(".clocking td").forEach(c => c.remove())
this.weekNames.querySelectorAll("td").forEach(c => c.remove())
this.dayNames.querySelectorAll("td").forEach(c => c.remove())
this.dayDates.innerHTML = ""
this.times.querySelectorAll("tr.project").forEach(r => r.remove())
this.nDays = 0
}
addClockings(date) {
let dates = this.clockings.querySelector(".dates")
let dateCell = document.createElement("th")
let weekDay = DAYS_SHORT[(date.getDay() + 1) % 7]
let monthDay = date.getDate().toString().padStart(2, "0")
dateCell.innerText = `${weekDay} ${monthDay}`
dates.appendChild(dateCell)
let clockings = this.clockings.querySelectorAll(".clocking")
clockings.forEach((clocking, i) => {
let cell = clocking.insertCell(this.nDays + 1)
if (i === 5) {
return
}
cell.replaceWith(this.timeInputTemplate.cloneNode(true))
})
}
addWeek(startDate, length=7) {
let weekNum = startDate.getWeek()
let weekName = this.weekNames.insertCell(-1)
weekName.innerText = `Week n° ${weekNum}`
weekName.colSpan = length
for (let i = 0; i < length; i++) {
let date = new Date(startDate.valueOf() + i * DAY_MS)
let dayPadded = date.getDate().toString().padStart(2, "0")
let monthPadded = (date.getMonth() + 1).toString().padStart(2, "0")
this.addClockings(date)
let dayName = this.dayNames.insertCell(this.nDays + 2)
let dayDate = this.dayDates.insertCell(this.nDays)
let weekDay = (date.getDay() + 6) % 7
dayName.innerText = DAYS_SHORT[weekDay]
dayDate.innerText = `${dayPadded}/${monthPadded}`
let col = document.createElement("col")
col.classList.add(`day-${weekDay}`)
this.columns.appendChild(col)
this.nDays++
}
}
addProject(id, name, sagexNum, times, isParent=false) {
let row = this.times.insertRow(-1)
row.classList.add("project")
if (isParent) {
row.classList.add("parent")
}
row.dataset.id = id
row.insertCell(-1).innerText = name
row.insertCell(-1).innerText = sagexNum
times.forEach(time => {
let t = time.toString().split(".")
if (t.length > 1 ){
t[1] = t[1].slice(0, 2)
}
row.insertCell(-1).innerText = t.join(".")
})
}
addMonth(anchorDate) {
this.startDate = new Date(anchorDate)
this.startDate.setDate(1)
let startDay = (this.startDate.getDay() + 6) % 7
let length = 7 - startDay
this.addWeek(this.startDate, length)
let monday = new Date(this.startDate.valueOf() + length * DAY_MS)
let nextMonday
let startMonth = this.startDate.getMonth()
while (monday.getMonth() === startMonth) {
nextMonday = new Date(monday.valueOf() + 7 * DAY_MS)
let len = 7
if (nextMonday.getMonth() !== startMonth) {
len -= nextMonday.getDate() - 1
}
this.addWeek(monday, len)
monday = nextMonday
}
this.endDate = new Date(monday.valueOf() - monday.getDate() * DAY_MS)
}
fetchData() {
let startDate = formatDate(this.startDate)
let endDate = formatDate(this.endDate)
fetch(`/table/${startDate}/${endDate}/`).then(res => {
if (res.ok) {
return res.json()
}
}).then(res => {
if (res && res.status === "success") {
this.displayData(res.data)
}
})
}
displayData(data) {
data.parents.forEach(parent => {
this.addProject(
parent.id,
parent.name,
parent.project_num,
Array(this.nDays).fill(""),
true
)
parent.projects.forEach(project => {
let durations = Array(this.nDays).fill("")
project.tasks.forEach(task => {
let date = new Date(task.date)
let i = Math.floor((date.valueOf() - this.startDate.valueOf()) / DAY_MS)
durations[i] = task.duration / 60
})
this.addProject(
project.id,
project.name,
"",
durations
)
})
})
}
}
window.addEventListener("load", () => {
prevMonthBtn = document.getElementById("prev-month")
nextMonthBtn = document.getElementById("next-month")
month = document.getElementById("month")
table = new Table(document.getElementById("table"))
prevMonthBtn.addEventListener("click", () => table.prevMonth())
nextMonthBtn.addEventListener("click", () => table.nextMonth())
})