14 Commits

Author SHA1 Message Date
d96d2c590e Merge pull request 'v0.1.4: minor fix' (#7) from dev into main
Reviewed-on: #7
2025-04-23 22:00:44 +00:00
da9be74050 Merge pull request 'v0.1.4: minor fix' (#6) from fix/5-edit-orphan-project into dev
Reviewed-on: #6
2025-04-23 21:59:32 +00:00
7d3f13657f bumped to version 0.1.4 2025-04-23 22:39:58 +02:00
0be774531c made project parent blank 2025-04-23 22:10:46 +02:00
8393855514 Merge pull request 'v0.1.3: minor fixes' (#4) from dev into main
Reviewed-on: #4
2025-03-02 15:10:42 +00:00
756b51309a bumped to version 0.1.3 2025-03-02 16:09:09 +01:00
cef66f2bd4 made parent empty lines hidden 2025-03-02 16:08:14 +01:00
2aa80e094a fixed daily totals only for productive parents 2025-03-02 14:46:24 +01:00
f48cbdb03a Merge pull request 'v0.1.2: Minor ui improvements' (#3) from dev into main
Reviewed-on: #3
2025-03-01 13:25:13 +00:00
57231c0d34 Merge pull request 'minor ui improvements' (#2) from feat/ui-improvements-1 into dev
Reviewed-on: #2
2025-03-01 13:23:52 +00:00
842d0ff11e bumped version to 0.1.2 2025-03-01 14:21:22 +01:00
87005efcf5 added weekly total + highlight clocking inconsistencies 2025-03-01 14:20:16 +01:00
7f845dcb1d made empty project lines hidden 2025-03-01 13:40:24 +01:00
9e2566ba03 updated app version to 0.1.1 2025-03-01 13:13:02 +01:00
6 changed files with 88 additions and 14 deletions

View File

@ -17,7 +17,7 @@ from dotenv import load_dotenv
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
APP_VERSION = "0.1.0"
APP_VERSION = "0.1.4"
load_dotenv(BASE_DIR / ".env")

View File

@ -0,0 +1,19 @@
# Generated by Django 5.1.5 on 2025-04-23 20:09
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dispatcher', '0010_realsagexhours_unique_monthly_sagex'),
]
operations = [
migrations.AlterField(
model_name='project',
name='parent',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='dispatcher.parent'),
),
]

View File

@ -15,7 +15,8 @@ class Project(models.Model):
parent = models.ForeignKey(
Parent,
on_delete=models.CASCADE,
null=True
null=True,
blank=True
)
name = models.CharField(max_length=256)

View File

@ -1,3 +1,7 @@
:root {
--invalid-col: #f1232394;
}
#table {
border-collapse: collapse;
font-size: 80%;
@ -49,6 +53,11 @@
border-top: solid #71717185 1px;
td {
padding: 0.4em;
font-style: italic;
&.invalid {
background-color: var(--invalid-col);
font-weight: bold;
}
}
}
@ -85,6 +94,17 @@
border-bottom: var(--border);
}
.day-totals {
td {
text-align: right;
font-style: italic;
&.invalid {
background-color: var(--invalid-col);
font-weight: bold;
}
}
}
col.day-5, col.day-6 {
background-color: var(--dark3);
}

View File

@ -9,6 +9,7 @@ class Table {
this.weekNames = elmt.querySelector(".week-names")
this.dayNames = elmt.querySelector(".day-names")
this.dayDates = elmt.querySelector(".day-dates")
this.dayTotals = elmt.querySelector(".day-totals")
this.columns = elmt.querySelector(".columns")
this.timeInputTemplate = getTemplate("time-input")
@ -22,6 +23,7 @@ class Table {
this.endDate = today
this.totalProjects = 0
this.dailyTotals = []
this.clockingTotals = []
this.clockingRemotes = []
@ -67,6 +69,7 @@ class Table {
this.weekNames.querySelectorAll("td").forEach(c => c.remove())
this.dayNames.querySelectorAll("td").forEach(c => c.remove())
this.dayDates.innerHTML = ""
this.dayTotals.querySelectorAll("td").forEach(c => c.remove())
this.times.querySelectorAll("tr.project").forEach(r => r.remove())
this.nDays = 0
}
@ -112,6 +115,7 @@ class Table {
this.addClockings(date)
let dayName = this.dayNames.insertCell(this.nDays + 2)
let dayDate = this.dayDates.insertCell(this.nDays)
this.dayTotals.insertCell(this.nDays + 1)
let weekDay = (date.getDay() + 6) % 7
dayName.innerText = DAYS_SHORT[weekDay]
dayDate.innerText = `${dayPadded}/${monthPadded}`
@ -211,37 +215,46 @@ class Table {
displayData(data) {
this.displayClockings(data.clockings)
this.totalProjects = 0
this.dailyTotals = Array(this.nDays).fill(0)
data.parents.forEach(parent => {
let parentDurations = Array(this.nDays).fill(0)
let projects = parent.projects.map(project => {
let durations = Array(this.nDays).fill("")
let total = 0
project.tasks.forEach(task => {
let date = new Date(task.date)
let i = Math.floor((date.valueOf() - this.startDate.valueOf()) / DAY_MS)
let hours = task.duration / 60
durations[i] = hours
total += hours
parentDurations[i] += hours
if (parent.is_productive) {
this.dailyTotals[i] += hours
}
})
return {
id: project.id,
name: project.name,
durations: durations
durations: durations,
total: total
}
})
this.addProject(
parent.id,
parent.name,
parent.project_num,
parentDurations.map(v => v === 0 ? "" : v),
true,
parent.is_productive
)
if (parentDurations.reduce((a, b) => a + b, 0) !== 0) {
this.addProject(
parent.id,
parent.name,
parent.project_num,
parentDurations.map(v => v === 0 ? "" : v),
true,
parent.is_productive
)
projects.forEach(project => {
this.addProject(project.id, project.name, "", project.durations)
})
projects.filter(p => p.total !== 0).forEach(project => {
this.addProject(project.id, project.name, "", project.durations)
})
}
})
this.updateTotals()
}
@ -334,6 +347,24 @@ class Table {
let sagexTime = imputedTimeRatio * totalClockings
parent.cells[this.nDays + 5].innerText = Math.round(sagexTime * 100) / 100
})
for (let i = 0; i < this.nDays; i++) {
let total = this.dailyTotals[i]
let cell = this.dayTotals.cells[i + 1]
if (total === 0) {
cell.innerText = ""
} else {
cell.innerText = Math.round(total * 100) / 100
}
let clocking = this.clockings.querySelector(".clocking.total").cells[i + 1]
if (total > this.clockingTotals[i]) {
clocking.classList.add("invalid")
cell.classList.add("invalid")
} else {
clocking.classList.remove("invalid")
cell.classList.remove("invalid")
}
}
}
post(endpoint, data) {

View File

@ -63,6 +63,9 @@
<th rowspan="2">N° sagex</th>
</tr>
<tr class="day-dates"></tr>
<tr class="day-totals">
<th colspan="2">Total Productive</th>
</tr>
</tbody>
</table>
{% endblock %}