Compute total hours per project for the selected month

This commit is contained in:
Niko Reunanen 2025-03-01 10:54:18 +02:00
parent a883a556be
commit fea2964395
Signed by: nreunane
GPG key ID: D192625387DB0F16
2 changed files with 85 additions and 17 deletions

View file

@ -15,8 +15,14 @@ import (
)
type Entry struct {
Start string
End string
Project string
Start string
End string
}
type Project struct {
Id string
Name string
}
func main() {
@ -28,6 +34,7 @@ func main() {
month := c.PathParam("month")
organisation := c.PathParam("organisation")
projects := []Project{}
entries := []Entry{}
record, _ := c.Get(apis.ContextAuthRecordKey).(*models.Record)
@ -37,6 +44,20 @@ func main() {
}
err := app.Dao().DB().
NewQuery("SELECT * FROM projects WHERE organisation = {:organisation}").
Bind(dbx.Params{
"start": fmt.Sprintf("%s-%s-01 00:00:00.000Z", year, month),
"end": fmt.Sprintf("%s-%s-31 23:59:59.999Z", year, month),
"organisation": organisation,
"user": record.Id,
}).
All(&projects)
if err != nil {
return c.JSON(http.StatusOK, map[string]any{"hours": nil, "error": err})
}
err = app.Dao().DB().
NewQuery("SELECT * FROM entries WHERE user = {:user} AND start >= {:start} AND end <= {:end} AND end != '' AND project IN (SELECT id FROM projects WHERE organisation = {:organisation})").
Bind(dbx.Params{
"start": fmt.Sprintf("%s-%s-01 00:00:00.000Z", year, month),
@ -47,25 +68,45 @@ func main() {
All(&entries)
if err != nil {
return c.JSON(http.StatusOK, map[string]any{"hours": 0, "error": err})
return c.JSON(http.StatusOK, map[string]any{"hours": nil, "error": err})
}
hours := 0.0
var projects_map map[string]string
projects_map = make(map[string]string)
for _, e := range projects {
projects_map[e.Id] = e.Name
}
var hours map[string]float64
hours = make(map[string]float64)
for _, e := range entries {
start, err := time.Parse("2006-01-02 15:04:05.000Z", e.Start)
if err != nil {
return c.JSON(http.StatusOK, map[string]any{"hours": 0, "error": fmt.Sprintf("time.Parse(%s)", e.Start)})
return c.JSON(http.StatusOK, map[string]any{"hours": nil, "error": fmt.Sprintf("time.Parse(%s)", e.Start)})
}
end, err := time.Parse("2006-01-02 15:04:05.000Z", e.End)
if err != nil {
return c.JSON(http.StatusOK, map[string]any{"hours": 0, "error": fmt.Sprintf("time.Parse(%s)", e.End)})
return c.JSON(http.StatusOK, map[string]any{"hours": nil, "error": fmt.Sprintf("time.Parse(%s)", e.End)})
}
hours = hours + end.Sub(start).Hours()
var entry_hours = end.Sub(start).Hours()
project, project_exists := projects_map[e.Project]
if !project_exists {
return c.JSON(http.StatusOK, map[string]any{"hours": nil, "error": fmt.Sprintf("time.Parse(%s)", e.End)})
}
if value, total_entry_exists := hours[project]; total_entry_exists {
hours[project] = value + entry_hours
} else {
hours[project] = entry_hours
}
}
return c.JSON(http.StatusOK, map[string]any{"hours": hours, "error": nil})

View file

@ -6,8 +6,13 @@ import DatePicker from "primevue/datepicker";
import Dialog from "primevue/dialog";
import { inject, ref } from "vue";
const modalTotalHours = ref({
hours: 0,
const modalTotalHours = ref<{
hours: { [project: string]: number };
total: number;
visible: boolean;
}>({
hours: {},
total: 0,
visible: false,
});
const selectedDate = ref(new Date());
@ -30,8 +35,16 @@ async function showModalTotalHours() {
const response = await pb.send(`/hours/${organisation}/${year}/${monthDoubleDigit}`, {});
console.log(response);
if (response.error === null) {
modalTotalHours.value.hours = Math.round(response.hours * 100) / 100;
let total = 0.0;
modalTotalHours.value.hours = {};
for (const project of Object.keys(response.hours)) {
modalTotalHours.value.hours[project] = Math.round(response.hours[project] * 100.0) / 100.0;
total = total + modalTotalHours.value.hours[project];
}
modalTotalHours.value.total = total;
modalTotalHours.value.visible = true;
} else {
console.error(response.error);
@ -40,11 +53,25 @@ async function showModalTotalHours() {
</script>
<template>
<Button label="<" @click="setDate(-1)" style="margin-left: 20px;" />
<DatePicker v-model="selectedDate" style="width: 150px; margin-left: 3px;" />
<Button label=">" @click="setDate(1)" style="margin-left: 3px;" />
<Button label="total hours" @click="showModalTotalHours" style="margin-left: 20px;" />
<Dialog v-model:visible="modalTotalHours.visible" modal header="Total hours" :style="{ width: '25rem' }">
<p>Hours: {{ modalTotalHours.hours }}</p>
</Dialog>
<Button label="<" @click="setDate(-1)" style="margin-left: 20px;" />
<DatePicker v-model="selectedDate" style="width: 150px; margin-left: 3px;" />
<Button label=">" @click="setDate(1)" style="margin-left: 3px;" />
<Button label="total hours" @click="showModalTotalHours" style="margin-left: 20px;" />
<Dialog v-model:visible="modalTotalHours.visible" modal header="Total hours">
<table>
<tbody>
<tr v-for="project in Object.keys(modalTotalHours.hours)">
<td>{{ project }}</td>
<td>{{ modalTotalHours.hours[project] }}</td>
</tr>
<tr v-if="modalTotalHours.total > 0">
<td colspan="2"><hr /></td>
</tr>
<tr>
<td>Total hours</td>
<td>{{ modalTotalHours.total }}</td>
</tr>
</tbody>
</table>
</Dialog>
</template>