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>
|
||||
<b-table
|
||||
:items="statistics"
|
||||
:items="projectStatistics"
|
||||
primary-key="project_id"
|
||||
hover
|
||||
:busy="isLoading"
|
||||
:fields="statistic_fields"
|
||||
sort-by="name"
|
||||
sort-by="duration"
|
||||
sort-desc
|
||||
>
|
||||
<template #table-busy>
|
||||
<div class="text-center">
|
||||
|
@ -21,6 +22,10 @@
|
|||
<template #cell(duration)="data">
|
||||
{{ getDurationTimestamp(data.item.duration) }}
|
||||
</template>
|
||||
|
||||
<template #cell(distribution)="data">
|
||||
{{ ((data.item.duration / totalDuration) * 100).toFixed(0) }}%
|
||||
</template>
|
||||
</b-table>
|
||||
</template>
|
||||
|
||||
|
@ -53,6 +58,10 @@ export default {
|
|||
{
|
||||
key: "record_count",
|
||||
label: "Records"
|
||||
},
|
||||
{
|
||||
key: "distribution",
|
||||
label: "Distribution"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
@ -60,6 +69,38 @@ export default {
|
|||
computed: {
|
||||
isLoading: function() {
|
||||
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: {
|
||||
|
|
|
@ -88,15 +88,23 @@
|
|||
</b-form>
|
||||
</BaseContainer>
|
||||
|
||||
<BaseSection id="projects" title="Total">
|
||||
<JugglProjectStatisticsList :statistics="visibleStatistics" />
|
||||
<BaseSection id="projects" :title="modeTitle">
|
||||
<JugglDailyStatisticsList
|
||||
:statistics="visibleStatistics"
|
||||
v-if="mode == 'daily'"
|
||||
/>
|
||||
<JugglMonthlyStatisticsList
|
||||
:statistics="visibleStatistics"
|
||||
v-if="mode == 'monthly'"
|
||||
/>
|
||||
</BaseSection>
|
||||
</LayoutNavbarPrivate>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
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 BaseSection from "@/components/base/BaseSection";
|
||||
import BaseContainer from "@/components/base/BaseContainer";
|
||||
|
@ -106,7 +114,8 @@ export default {
|
|||
name: "Statistics",
|
||||
components: {
|
||||
LayoutNavbarPrivate,
|
||||
JugglProjectStatisticsList,
|
||||
JugglMonthlyStatisticsList,
|
||||
JugglDailyStatisticsList,
|
||||
BaseSection,
|
||||
BaseContainer
|
||||
},
|
||||
|
@ -138,18 +147,19 @@ export default {
|
|||
return store.getters.getFilteredStatistics({
|
||||
projectVisible: true
|
||||
});
|
||||
},
|
||||
modeTitle: function() {
|
||||
return this.mode[0].toUpperCase() + this.mode.substring(1);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getDurationTimestamp: helperService.getDurationTimestamp,
|
||||
updateStatistics: function() {
|
||||
if (this.mode == "daily") {
|
||||
store.dispatch("loadStatistics", [
|
||||
{
|
||||
from: new Date(this.daily.startDate),
|
||||
until: new Date(this.daily.endDate)
|
||||
}
|
||||
]);
|
||||
store.dispatch("loadStatistics", {
|
||||
from: new Date(this.daily.startDate),
|
||||
until: new Date(this.daily.endDate)
|
||||
});
|
||||
} else if (this.mode == "monthly") {
|
||||
store.dispatch("loadMonthlyStatistics", this.monthly);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue