Compute total hours per project for the selected month
This commit is contained in:
parent
a883a556be
commit
fea2964395
2 changed files with 85 additions and 17 deletions
|
|
@ -15,10 +15,16 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Entry struct {
|
type Entry struct {
|
||||||
|
Project string
|
||||||
Start string
|
Start string
|
||||||
End string
|
End string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Project struct {
|
||||||
|
Id string
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
app := pocketbase.New()
|
app := pocketbase.New()
|
||||||
|
|
||||||
|
|
@ -28,6 +34,7 @@ func main() {
|
||||||
month := c.PathParam("month")
|
month := c.PathParam("month")
|
||||||
organisation := c.PathParam("organisation")
|
organisation := c.PathParam("organisation")
|
||||||
|
|
||||||
|
projects := []Project{}
|
||||||
entries := []Entry{}
|
entries := []Entry{}
|
||||||
|
|
||||||
record, _ := c.Get(apis.ContextAuthRecordKey).(*models.Record)
|
record, _ := c.Get(apis.ContextAuthRecordKey).(*models.Record)
|
||||||
|
|
@ -37,6 +44,20 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
err := app.Dao().DB().
|
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})").
|
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{
|
Bind(dbx.Params{
|
||||||
"start": fmt.Sprintf("%s-%s-01 00:00:00.000Z", year, month),
|
"start": fmt.Sprintf("%s-%s-01 00:00:00.000Z", year, month),
|
||||||
|
|
@ -47,25 +68,45 @@ func main() {
|
||||||
All(&entries)
|
All(&entries)
|
||||||
|
|
||||||
if err != nil {
|
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 {
|
for _, e := range entries {
|
||||||
start, err := time.Parse("2006-01-02 15:04:05.000Z", e.Start)
|
start, err := time.Parse("2006-01-02 15:04:05.000Z", e.Start)
|
||||||
|
|
||||||
if err != nil {
|
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)
|
end, err := time.Parse("2006-01-02 15:04:05.000Z", e.End)
|
||||||
|
|
||||||
if err != nil {
|
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})
|
return c.JSON(http.StatusOK, map[string]any{"hours": hours, "error": nil})
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,13 @@ import DatePicker from "primevue/datepicker";
|
||||||
import Dialog from "primevue/dialog";
|
import Dialog from "primevue/dialog";
|
||||||
import { inject, ref } from "vue";
|
import { inject, ref } from "vue";
|
||||||
|
|
||||||
const modalTotalHours = ref({
|
const modalTotalHours = ref<{
|
||||||
hours: 0,
|
hours: { [project: string]: number };
|
||||||
|
total: number;
|
||||||
|
visible: boolean;
|
||||||
|
}>({
|
||||||
|
hours: {},
|
||||||
|
total: 0,
|
||||||
visible: false,
|
visible: false,
|
||||||
});
|
});
|
||||||
const selectedDate = ref(new Date());
|
const selectedDate = ref(new Date());
|
||||||
|
|
@ -30,8 +35,16 @@ async function showModalTotalHours() {
|
||||||
|
|
||||||
const response = await pb.send(`/hours/${organisation}/${year}/${monthDoubleDigit}`, {});
|
const response = await pb.send(`/hours/${organisation}/${year}/${monthDoubleDigit}`, {});
|
||||||
|
|
||||||
|
console.log(response);
|
||||||
|
|
||||||
if (response.error === null) {
|
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;
|
modalTotalHours.value.visible = true;
|
||||||
} else {
|
} else {
|
||||||
console.error(response.error);
|
console.error(response.error);
|
||||||
|
|
@ -44,7 +57,21 @@ async function showModalTotalHours() {
|
||||||
<DatePicker v-model="selectedDate" style="width: 150px; margin-left: 3px;" />
|
<DatePicker v-model="selectedDate" style="width: 150px; margin-left: 3px;" />
|
||||||
<Button label=">" @click="setDate(1)" style="margin-left: 3px;" />
|
<Button label=">" @click="setDate(1)" style="margin-left: 3px;" />
|
||||||
<Button label="total hours" @click="showModalTotalHours" style="margin-left: 20px;" />
|
<Button label="total hours" @click="showModalTotalHours" style="margin-left: 20px;" />
|
||||||
<Dialog v-model:visible="modalTotalHours.visible" modal header="Total hours" :style="{ width: '25rem' }">
|
<Dialog v-model:visible="modalTotalHours.visible" modal header="Total hours">
|
||||||
<p>Hours: {{ modalTotalHours.hours }}</p>
|
<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>
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
Loading…
Add table
Reference in a new issue