Daily and monthly stats
This commit is contained in:
parent
9cfba93f65
commit
aa706bfcaf
5 changed files with 410 additions and 12 deletions
151
src/components/juggl/JugglDailyStatisticsList.vue
Normal file
151
src/components/juggl/JugglDailyStatisticsList.vue
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
<template>
|
||||||
|
<b-table
|
||||||
|
:items="aggregatedStatistics"
|
||||||
|
primary-key="day"
|
||||||
|
hover
|
||||||
|
:busy="isLoading"
|
||||||
|
:fields="statistic_fields"
|
||||||
|
sort-by="day"
|
||||||
|
sort-desc
|
||||||
|
>
|
||||||
|
<template #table-busy>
|
||||||
|
<div class="text-center">
|
||||||
|
<b-spinner></b-spinner>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Custom data -->
|
||||||
|
<template #cell(duration)="data">
|
||||||
|
{{ getDurationTimestamp(data.item.duration) }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cell(distribution)="data">
|
||||||
|
<JugglProjectDistribution :projects="data.item.projects" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cell(details)="data">
|
||||||
|
<b-button
|
||||||
|
size="sm"
|
||||||
|
@click="data.toggleDetails"
|
||||||
|
variant="outline-secondary"
|
||||||
|
>
|
||||||
|
<b-icon class="icon-btn" icon="bar-chart-steps" />
|
||||||
|
</b-button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #row-details="data">
|
||||||
|
<b-card>
|
||||||
|
<JugglProjectDistribution
|
||||||
|
:projects="data.item.projects"
|
||||||
|
names
|
||||||
|
class="mb-3"
|
||||||
|
/>
|
||||||
|
<JugglProjectStatisticsList :statistics="data.item.statistics" />
|
||||||
|
</b-card>
|
||||||
|
</template>
|
||||||
|
</b-table>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { helperService } from "@/services/helper.service.js";
|
||||||
|
import JugglProjectStatisticsList from "./JugglProjectStatisticsList.vue";
|
||||||
|
import JugglProjectDistribution from "./JugglProjectDistribution.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "JugglDailyStatisticsList",
|
||||||
|
components: { JugglProjectStatisticsList, JugglProjectDistribution },
|
||||||
|
props: {
|
||||||
|
statistics: {
|
||||||
|
required: true,
|
||||||
|
type: Array
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: () => {
|
||||||
|
return {
|
||||||
|
statistic_fields: [
|
||||||
|
{
|
||||||
|
key: "day",
|
||||||
|
label: "Day"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "duration",
|
||||||
|
label: "Duration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "distribution",
|
||||||
|
lavel: "Distribution"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "record_count",
|
||||||
|
label: "Records"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "details",
|
||||||
|
label: "Details"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isLoading: function() {
|
||||||
|
return this.statistics === undefined;
|
||||||
|
},
|
||||||
|
aggregatedStatistics: function() {
|
||||||
|
if (this.statistics === undefined) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
var aggregated = {};
|
||||||
|
this.statistics.forEach(stat => {
|
||||||
|
var dayName = stat.date;
|
||||||
|
|
||||||
|
if (aggregated[dayName] === undefined) {
|
||||||
|
aggregated[dayName] = {
|
||||||
|
day: dayName,
|
||||||
|
duration: 0,
|
||||||
|
record_count: 0,
|
||||||
|
statistics: [],
|
||||||
|
projects: {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aggregated[dayName].projects[stat.project_id] === undefined) {
|
||||||
|
aggregated[dayName].projects[stat.project_id] = {
|
||||||
|
project_id: stat.project_id,
|
||||||
|
duration: 0,
|
||||||
|
record_count: 0,
|
||||||
|
color: stat.color,
|
||||||
|
name: stat.name,
|
||||||
|
visible: stat.visible,
|
||||||
|
statistics: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
aggregated[dayName].duration += Number(stat.duration);
|
||||||
|
aggregated[dayName].record_count += Number(stat.record_count);
|
||||||
|
aggregated[dayName].statistics.push(stat);
|
||||||
|
|
||||||
|
aggregated[dayName].projects[stat.project_id].duration += Number(
|
||||||
|
stat.duration
|
||||||
|
);
|
||||||
|
aggregated[dayName].projects[stat.project_id].record_count += Number(
|
||||||
|
stat.record_count
|
||||||
|
);
|
||||||
|
aggregated[dayName].projects[stat.project_id].statistics.push(stat);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Simplyfying object to lists
|
||||||
|
aggregated = Object.values(aggregated);
|
||||||
|
aggregated.forEach(stat => {
|
||||||
|
stat.projects = Object.values(stat.projects);
|
||||||
|
});
|
||||||
|
return aggregated;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getDurationTimestamp: helperService.getDurationTimestamp
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="sass"></style>
|
151
src/components/juggl/JugglMonthlyStatisticsList.vue
Normal file
151
src/components/juggl/JugglMonthlyStatisticsList.vue
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
<template>
|
||||||
|
<b-table
|
||||||
|
:items="aggregatedStatistics"
|
||||||
|
primary-key="month"
|
||||||
|
hover
|
||||||
|
:busy="isLoading"
|
||||||
|
:fields="statistic_fields"
|
||||||
|
sort-by="month"
|
||||||
|
sort-desc
|
||||||
|
>
|
||||||
|
<template #table-busy>
|
||||||
|
<div class="text-center">
|
||||||
|
<b-spinner></b-spinner>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Custom data -->
|
||||||
|
<template #cell(duration)="data">
|
||||||
|
{{ getDurationTimestamp(data.item.duration) }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cell(distribution)="data">
|
||||||
|
<JugglProjectDistribution :projects="data.item.projects" class="mb-3" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cell(details)="data">
|
||||||
|
<b-button
|
||||||
|
size="sm"
|
||||||
|
@click="data.toggleDetails"
|
||||||
|
variant="outline-secondary"
|
||||||
|
>
|
||||||
|
<b-icon class="icon-btn" icon="bar-chart-steps" />
|
||||||
|
</b-button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #row-details="data">
|
||||||
|
<b-card>
|
||||||
|
<JugglProjectDistribution
|
||||||
|
:projects="data.item.projects"
|
||||||
|
names
|
||||||
|
class="mb-3"
|
||||||
|
/>
|
||||||
|
<JugglProjectStatisticsList :statistics="data.item.statistics" />
|
||||||
|
</b-card>
|
||||||
|
</template>
|
||||||
|
</b-table>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { helperService } from "@/services/helper.service.js";
|
||||||
|
import JugglProjectStatisticsList from "./JugglProjectStatisticsList.vue";
|
||||||
|
import JugglProjectDistribution from "./JugglProjectDistribution.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "JugglMonthlyStatisticsList",
|
||||||
|
components: { JugglProjectStatisticsList, JugglProjectDistribution },
|
||||||
|
props: {
|
||||||
|
statistics: {
|
||||||
|
required: true,
|
||||||
|
type: Array
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: () => {
|
||||||
|
return {
|
||||||
|
statistic_fields: [
|
||||||
|
{
|
||||||
|
key: "month",
|
||||||
|
label: "Month"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "duration",
|
||||||
|
label: "Duration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "distribution",
|
||||||
|
lavel: "Distribution"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "record_count",
|
||||||
|
label: "Records"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "details",
|
||||||
|
label: "Details"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isLoading: function() {
|
||||||
|
return this.statistics === undefined;
|
||||||
|
},
|
||||||
|
aggregatedStatistics: function() {
|
||||||
|
if (this.statistics === undefined) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
var aggregated = {};
|
||||||
|
this.statistics.forEach(stat => {
|
||||||
|
var monthName = stat.date.substring(0, 7); // date is in the format "YYYY-MM-DD", so simply cutting of the day
|
||||||
|
|
||||||
|
if (aggregated[monthName] === undefined) {
|
||||||
|
aggregated[monthName] = {
|
||||||
|
month: monthName,
|
||||||
|
duration: 0,
|
||||||
|
record_count: 0,
|
||||||
|
statistics: [],
|
||||||
|
projects: {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aggregated[monthName].projects[stat.project_id] === undefined) {
|
||||||
|
aggregated[monthName].projects[stat.project_id] = {
|
||||||
|
project_id: stat.project_id,
|
||||||
|
duration: 0,
|
||||||
|
record_count: 0,
|
||||||
|
color: stat.color,
|
||||||
|
name: stat.name,
|
||||||
|
visible: stat.visible,
|
||||||
|
statistics: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
aggregated[monthName].duration += Number(stat.duration);
|
||||||
|
aggregated[monthName].record_count += Number(stat.record_count);
|
||||||
|
aggregated[monthName].statistics.push(stat);
|
||||||
|
|
||||||
|
aggregated[monthName].projects[stat.project_id].duration += Number(
|
||||||
|
stat.duration
|
||||||
|
);
|
||||||
|
aggregated[monthName].projects[stat.project_id].record_count += Number(
|
||||||
|
stat.record_count
|
||||||
|
);
|
||||||
|
aggregated[monthName].projects[stat.project_id].statistics.push(stat);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Simplyfying object to lists
|
||||||
|
aggregated = Object.values(aggregated);
|
||||||
|
aggregated.forEach(stat => {
|
||||||
|
stat.projects = Object.values(stat.projects);
|
||||||
|
});
|
||||||
|
return aggregated;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getDurationTimestamp: helperService.getDurationTimestamp
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="sass"></style>
|
45
src/components/juggl/JugglProjectDistribution.vue
Normal file
45
src/components/juggl/JugglProjectDistribution.vue
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
<template>
|
||||||
|
<b-progress :max="totalDuration">
|
||||||
|
<b-progress-bar
|
||||||
|
v-for="(project, index) in projects"
|
||||||
|
v-bind:key="project.project_id"
|
||||||
|
:value="project.duration"
|
||||||
|
:style="'background-color: ' + project.color + ' !important'"
|
||||||
|
:variant="index % 2 == 0 ? 'primary' : 'dark'"
|
||||||
|
v-b-tooltip.hover
|
||||||
|
:title="project.name + ' · ' + getDurationTimestamp(project.duration)"
|
||||||
|
><span v-if="names">{{ project.name }}</span></b-progress-bar
|
||||||
|
>
|
||||||
|
</b-progress>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { helperService } from "@/services/helper.service.js";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "JugglProjectDistribution",
|
||||||
|
props: {
|
||||||
|
projects: {
|
||||||
|
required: true,
|
||||||
|
type: Array
|
||||||
|
},
|
||||||
|
names: {
|
||||||
|
required: false,
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
totalDuration: function() {
|
||||||
|
var duration = 0;
|
||||||
|
this.projects.forEach(stat => (duration += Number(stat.duration)));
|
||||||
|
return duration;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getDurationTimestamp: helperService.getDurationTimestamp
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style></style>
|
|
@ -1,11 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<b-table
|
<b-table
|
||||||
:items="statistics"
|
:items="projectStatistics"
|
||||||
primary-key="project_id"
|
primary-key="project_id"
|
||||||
hover
|
hover
|
||||||
:busy="isLoading"
|
:busy="isLoading"
|
||||||
:fields="statistic_fields"
|
:fields="statistic_fields"
|
||||||
sort-by="name"
|
sort-by="duration"
|
||||||
|
sort-desc
|
||||||
>
|
>
|
||||||
<template #table-busy>
|
<template #table-busy>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
|
@ -21,6 +22,10 @@
|
||||||
<template #cell(duration)="data">
|
<template #cell(duration)="data">
|
||||||
{{ getDurationTimestamp(data.item.duration) }}
|
{{ getDurationTimestamp(data.item.duration) }}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template #cell(distribution)="data">
|
||||||
|
{{ ((data.item.duration / totalDuration) * 100).toFixed(0) }}%
|
||||||
|
</template>
|
||||||
</b-table>
|
</b-table>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -53,6 +58,10 @@ export default {
|
||||||
{
|
{
|
||||||
key: "record_count",
|
key: "record_count",
|
||||||
label: "Records"
|
label: "Records"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "distribution",
|
||||||
|
label: "Distribution"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
@ -60,6 +69,38 @@ export default {
|
||||||
computed: {
|
computed: {
|
||||||
isLoading: function() {
|
isLoading: function() {
|
||||||
return this.statistics === undefined;
|
return this.statistics === undefined;
|
||||||
|
},
|
||||||
|
totalDuration: function() {
|
||||||
|
var duration = 0;
|
||||||
|
this.statistics.forEach(stat => (duration += Number(stat.duration)));
|
||||||
|
return duration;
|
||||||
|
},
|
||||||
|
projectStatistics: function() {
|
||||||
|
if (this.statistics === undefined) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
var projects = {};
|
||||||
|
this.statistics.forEach(stat => {
|
||||||
|
if (projects[stat.project_id] === undefined) {
|
||||||
|
projects[stat.project_id] = {
|
||||||
|
project_id: stat.project_id,
|
||||||
|
duration: 0,
|
||||||
|
record_count: 0,
|
||||||
|
color: stat.color,
|
||||||
|
name: stat.name,
|
||||||
|
visible: stat.visible,
|
||||||
|
statistics: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
projects[stat.project_id].duration += Number(stat.duration);
|
||||||
|
projects[stat.project_id].record_count += Number(stat.record_count);
|
||||||
|
projects[stat.project_id].statistics.push(stat);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Simplyfying object to lists
|
||||||
|
return Object.values(projects);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
|
@ -88,15 +88,23 @@
|
||||||
</b-form>
|
</b-form>
|
||||||
</BaseContainer>
|
</BaseContainer>
|
||||||
|
|
||||||
<BaseSection id="projects" title="Total">
|
<BaseSection id="projects" :title="modeTitle">
|
||||||
<JugglProjectStatisticsList :statistics="visibleStatistics" />
|
<JugglDailyStatisticsList
|
||||||
|
:statistics="visibleStatistics"
|
||||||
|
v-if="mode == 'daily'"
|
||||||
|
/>
|
||||||
|
<JugglMonthlyStatisticsList
|
||||||
|
:statistics="visibleStatistics"
|
||||||
|
v-if="mode == 'monthly'"
|
||||||
|
/>
|
||||||
</BaseSection>
|
</BaseSection>
|
||||||
</LayoutNavbarPrivate>
|
</LayoutNavbarPrivate>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import LayoutNavbarPrivate from "@/components/layout/LayoutNavbarPrivate";
|
import LayoutNavbarPrivate from "@/components/layout/LayoutNavbarPrivate";
|
||||||
import JugglProjectStatisticsList from "@/components/juggl/JugglProjectStatisticsList";
|
import JugglDailyStatisticsList from "@/components/juggl/JugglDailyStatisticsList";
|
||||||
|
import JugglMonthlyStatisticsList from "@/components/juggl/JugglMonthlyStatisticsList";
|
||||||
import { helperService } from "@/services/helper.service.js";
|
import { helperService } from "@/services/helper.service.js";
|
||||||
import BaseSection from "@/components/base/BaseSection";
|
import BaseSection from "@/components/base/BaseSection";
|
||||||
import BaseContainer from "@/components/base/BaseContainer";
|
import BaseContainer from "@/components/base/BaseContainer";
|
||||||
|
@ -106,7 +114,8 @@ export default {
|
||||||
name: "Statistics",
|
name: "Statistics",
|
||||||
components: {
|
components: {
|
||||||
LayoutNavbarPrivate,
|
LayoutNavbarPrivate,
|
||||||
JugglProjectStatisticsList,
|
JugglMonthlyStatisticsList,
|
||||||
|
JugglDailyStatisticsList,
|
||||||
BaseSection,
|
BaseSection,
|
||||||
BaseContainer
|
BaseContainer
|
||||||
},
|
},
|
||||||
|
@ -138,18 +147,19 @@ export default {
|
||||||
return store.getters.getFilteredStatistics({
|
return store.getters.getFilteredStatistics({
|
||||||
projectVisible: true
|
projectVisible: true
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
modeTitle: function() {
|
||||||
|
return this.mode[0].toUpperCase() + this.mode.substring(1);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getDurationTimestamp: helperService.getDurationTimestamp,
|
getDurationTimestamp: helperService.getDurationTimestamp,
|
||||||
updateStatistics: function() {
|
updateStatistics: function() {
|
||||||
if (this.mode == "daily") {
|
if (this.mode == "daily") {
|
||||||
store.dispatch("loadStatistics", [
|
store.dispatch("loadStatistics", {
|
||||||
{
|
from: new Date(this.daily.startDate),
|
||||||
from: new Date(this.daily.startDate),
|
until: new Date(this.daily.endDate)
|
||||||
until: new Date(this.daily.endDate)
|
});
|
||||||
}
|
|
||||||
]);
|
|
||||||
} else if (this.mode == "monthly") {
|
} else if (this.mode == "monthly") {
|
||||||
store.dispatch("loadMonthlyStatistics", this.monthly);
|
store.dispatch("loadMonthlyStatistics", this.monthly);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue