Client - Stats refactoring + display fix when week starts on Monday #95

This commit is contained in:
Sam 2021-10-04 13:53:48 +02:00
parent f328e632ac
commit b86d2be4d6
6 changed files with 758 additions and 235 deletions

View File

@ -0,0 +1,97 @@
<template>
<div class="sports-menu">
<label
v-for="sport in translatedSports"
type="checkbox"
:key="sport.id"
:style="{ color: sportColors[sport.label] }"
>
<input
type="checkbox"
:id="sport.id"
:name="sport.label"
:checked="selectedSportIds.includes(sport.id)"
@input="updateSelectedSportIds(sport.id)"
/>
<SportImage :sport-label="sport.label" />
{{ sport.translatedLabel }}
</label>
</div>
</template>
<script lang="ts">
import { ComputedRef, PropType, computed, defineComponent } from 'vue'
import { useI18n } from 'vue-i18n'
import SportImage from '@/components/Common/SportImage/index.vue'
import { ISport, ITranslatedSport } from '@/types/sports'
import { translateSports, sportColors } from '@/utils/sports'
export default defineComponent({
name: 'SportsMenu',
components: {
SportImage,
},
props: {
selectedSportIds: {
type: Array as PropType<number[]>,
default: () => [],
},
userSports: {
type: Object as PropType<ISport[]>,
required: true,
},
},
emits: ['selectedSportIdsUpdate'],
setup(props, { emit }) {
const { t } = useI18n()
const translatedSports: ComputedRef<ITranslatedSport[]> = computed(() =>
translateSports(props.userSports, t)
)
function updateSelectedSportIds(sportId: number) {
emit('selectedSportIdsUpdate', sportId)
}
return {
sportColors,
translatedSports,
updateSelectedSportIds,
}
},
})
</script>
<style lang="scss">
@import '~@/scss/base.scss';
.sports-menu {
display: flex;
justify-content: space-between;
padding: $default-padding;
@media screen and (max-width: $medium-limit) {
justify-content: normal;
flex-wrap: wrap;
}
label {
display: flex;
align-items: center;
font-size: 0.9em;
font-weight: normal;
min-width: 120px;
padding: $default-padding;
@media screen and (max-width: $medium-limit) {
min-width: 100px;
}
@media screen and (max-width: $x-small-limit) {
width: 100%;
}
}
.sport-img {
padding: 3px;
width: 20px;
height: 20px;
}
}
</style>

View File

@ -0,0 +1,116 @@
<template>
<div class="chart-menu">
<div class="chart-arrow">
<i
class="fa fa-chevron-left"
aria-hidden="true"
@click="emit('arrowClick', true)"
/>
</div>
<div class="time-frames">
<div class="time-frames-checkboxes">
<div v-for="frame in timeFrames" class="time-frame" :key="frame">
<label>
<input
type="radio"
:id="frame"
:name="frame"
:checked="selectedTimeFrame === frame"
@input="onUpdateTimeFrame(frame)"
/>
<span>{{ t(`statistics.TIME_FRAMES.${frame}`) }}</span>
</label>
</div>
</div>
</div>
<div class="chart-arrow">
<i
class="fa fa-chevron-right"
aria-hidden="true"
@click="emit('arrowClick', false)"
/>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
import { useI18n } from 'vue-i18n'
export default defineComponent({
name: 'StatsMenu',
emits: ['arrowClick', 'timeFrameUpdate'],
setup(props, { emit }) {
const { t } = useI18n()
let selectedTimeFrame = ref('month')
const timeFrames = ['week', 'month', 'year']
function onUpdateTimeFrame(timeFrame: string) {
selectedTimeFrame.value = timeFrame
emit('timeFrameUpdate', timeFrame)
}
return {
selectedTimeFrame,
t,
timeFrames,
onUpdateTimeFrame,
emit,
}
},
})
</script>
<style lang="scss" scoped>
@import '~@/scss/base';
.chart-menu {
display: flex;
.chart-arrow,
.time-frames {
flex-grow: 1;
text-align: center;
}
.chart-arrow {
cursor: pointer;
}
.time-frames {
display: flex;
justify-content: space-around;
.time-frames-checkboxes {
display: inline-flex;
.time-frame {
label {
font-weight: normal;
float: left;
padding: 0 5px;
cursor: pointer;
}
label input {
display: none;
}
label span {
border: solid 1px var(--time-frame-border-color);
border-radius: 9%;
display: block;
font-size: 0.9em;
padding: 2px 6px;
text-align: center;
}
input:checked + span {
background-color: var(--time-frame-checked-bg-color);
color: var(--time-frame-checked-color);
}
}
}
}
}
</style>

View File

@ -1,40 +1,9 @@
<template>
<div id="user-statistics">
<Card v-if="translatedSports">
<template #title>{{ $t('statistics.STATISTICS') }}</template>
<template #content>
<div class="chart-filters">
<div class="chart-arrow">
<i
class="fa fa-chevron-left"
aria-hidden="true"
@click="handleOnClickArrows(true)"
<div id="user-statistics" v-if="translatedSports">
<StatsMenu
@timeFrameUpdate="updateTimeFrame"
@arrowClick="handleOnClickArrows"
/>
</div>
<div class="time-frames">
<div class="time-frames-checkboxes">
<div v-for="frame in timeFrames" class="time-frame" :key="frame">
<label>
<input
type="radio"
:id="frame"
:name="frame"
:checked="selectedTimeFrame === frame"
@input="updateTimeFrame(frame)"
/>
<span>{{ t(`statistics.TIME_FRAMES.${frame}`) }}</span>
</label>
</div>
</div>
</div>
<div class="chart-arrow">
<i
class="fa fa-chevron-right"
aria-hidden="true"
@click="handleOnClickArrows(false)"
/>
</div>
</div>
<StatChart
:sports="sports"
:user="user"
@ -42,44 +11,15 @@
:displayed-sport-ids="selectedSportIds"
:fullStats="true"
/>
<div class="sports">
<label
v-for="sport in translatedSports"
type="checkbox"
:key="sport.id"
:style="{ color: sportColors[sport.label] }"
>
<input
type="checkbox"
:id="sport.id"
:name="sport.label"
:checked="selectedSportIds.includes(sport.id)"
@input="updateSelectedSportIds(sport.id)"
<SportsMenu
:selected-sport-ids="selectedSportIds"
:user-sports="sports"
@selectedSportIdsUpdate="updateSelectedSportIds"
/>
<SportImage :sport-label="sport.label" />
{{ sport.translatedLabel }}
</label>
</div>
</template>
</Card>
</div>
</template>
<script lang="ts">
import {
addMonths,
addWeeks,
addYears,
endOfMonth,
endOfWeek,
endOfYear,
startOfMonth,
startOfWeek,
startOfYear,
subMonths,
subWeeks,
subYears,
} from 'date-fns'
import {
ComputedRef,
PropType,
@ -91,20 +31,21 @@
} from 'vue'
import { useI18n } from 'vue-i18n'
import Card from '@/components/Common/Card.vue'
import SportImage from '@/components/Common/SportImage/index.vue'
import StatChart from '@/components/Common/StatsChart/index.vue'
import SportsMenu from '@/components/Statistics/SportsMenu.vue'
import StatsMenu from '@/components/Statistics/StatsMenu.vue'
import { ISport, ITranslatedSport } from '@/types/sports'
import { IStatisticsDateParams } from '@/types/statistics'
import { IAuthUserProfile } from '@/types/user'
import { translateSports, sportColors } from '@/utils/sports'
import { getStatsDateParams, updateChartParams } from '@/utils/statistics'
export default defineComponent({
name: 'UserMonthStats',
name: 'Statistics',
components: {
Card,
SportImage,
SportsMenu,
StatChart,
StatsMenu,
},
props: {
sports: {
@ -133,65 +74,14 @@
chartParams.value = getChartParams(selectedTimeFrame.value)
}
function getChartParams(timeFrame: string): IStatisticsDateParams {
const date = new Date()
const start =
timeFrame === 'year'
? startOfYear(subYears(date, 9))
: selectedTimeFrame.value === 'week'
? startOfMonth(subMonths(date, 2))
: startOfMonth(subMonths(date, 11))
const end =
timeFrame === 'year'
? endOfYear(date)
: timeFrame === 'week'
? endOfWeek(date)
: endOfMonth(date)
return {
duration: timeFrame,
end,
start,
}
return getStatsDateParams(new Date(), timeFrame, props.user.weekm)
}
function handleOnClickArrows(backward: boolean) {
chartParams.value = {
duration: selectedTimeFrame.value,
end:
selectedTimeFrame.value === 'year'
? startOfYear(
backward
? endOfYear(subYears(chartParams.value.end, 1))
: endOfYear(addYears(chartParams.value.end, 1))
chartParams.value = updateChartParams(
chartParams.value,
backward,
props.user.weekm
)
: selectedTimeFrame.value === 'week'
? startOfMonth(
backward
? endOfWeek(subWeeks(chartParams.value.end, 1))
: endOfWeek(addWeeks(chartParams.value.end, 1))
)
: startOfMonth(
backward
? endOfMonth(subMonths(chartParams.value.end, 1))
: endOfMonth(addMonths(chartParams.value.end, 1))
),
start:
selectedTimeFrame.value === 'year'
? startOfYear(
backward
? startOfYear(subYears(chartParams.value.start, 1))
: startOfYear(addYears(chartParams.value.start, 1))
)
: selectedTimeFrame.value === 'week'
? startOfMonth(
backward
? startOfWeek(subWeeks(chartParams.value.start, 1))
: startOfWeek(addWeeks(chartParams.value.start, 1))
)
: startOfMonth(
backward
? startOfMonth(subMonths(chartParams.value.start, 1))
: startOfMonth(addMonths(chartParams.value.start, 1))
),
}
}
function getSports(sports: ISport[]) {
return sports.map((sport) => sport.id)
@ -233,98 +123,9 @@
@import '~@/scss/base';
#user-statistics {
display: flex;
width: 100%;
margin-bottom: 30px;
::v-deep(.card) {
width: 100%;
.card-content {
.chart-filters {
display: flex;
.chart-arrow,
.time-frames {
flex-grow: 1;
text-align: center;
}
.chart-arrow {
cursor: pointer;
}
.time-frames {
display: flex;
justify-content: space-around;
.time-frames-checkboxes {
display: inline-flex;
.time-frame {
label {
font-weight: normal;
float: left;
padding: 0 5px;
cursor: pointer;
}
label input {
display: none;
}
label span {
border: solid 1px var(--time-frame-border-color);
border-radius: 9%;
display: block;
font-size: 0.9em;
padding: 2px 6px;
text-align: center;
}
input:checked + span {
background-color: var(--time-frame-checked-bg-color);
color: var(--time-frame-checked-color);
}
}
}
}
}
.chart-radio {
justify-content: space-around;
padding: $default-padding * 2 $default-padding;
}
.sports {
display: flex;
justify-content: space-between;
padding: $default-padding * 2 $default-padding;
@media screen and (max-width: $medium-limit) {
justify-content: normal;
flex-wrap: wrap;
}
label {
display: flex;
align-items: center;
font-size: 0.9em;
font-weight: normal;
min-width: 120px;
padding: $default-padding;
@media screen and (max-width: $medium-limit) {
min-width: 100px;
}
@media screen and (max-width: $x-small-limit) {
width: 100%;
}
}
.sport-img {
padding: 3px;
width: 20px;
height: 20px;
}
}
}
}
}
</style>

View File

@ -1,4 +1,18 @@
import { format } from 'date-fns'
import {
addMonths,
addWeeks,
addYears,
endOfMonth,
endOfWeek,
endOfYear,
format,
startOfMonth,
startOfWeek,
startOfYear,
subMonths,
subWeeks,
subYears,
} from 'date-fns'
import { IChartDataset } from '@/types/chart'
import { ISport } from '@/types/sports'
@ -105,3 +119,56 @@ export const formatStats = (
datasets,
}
}
export const getStatsDateParams = (
date: Date,
timeFrame: string,
weekStartingMonday: boolean
): IStatisticsDateParams => {
const weekStartsOn = weekStartingMonday ? 1 : 0
const start =
timeFrame === 'year'
? startOfYear(subYears(date, 9))
: timeFrame === 'week'
? startOfWeek(subMonths(date, 2), { weekStartsOn })
: startOfMonth(subMonths(date, 11)) // month
const end =
timeFrame === 'year'
? endOfYear(date)
: timeFrame === 'week'
? endOfWeek(date, { weekStartsOn })
: endOfMonth(date) // month
return {
duration: timeFrame,
end,
start,
}
}
export const updateChartParams = (
chartParams: IStatisticsDateParams,
backward: boolean,
weekStartingMonday: boolean
): IStatisticsDateParams => {
const { duration, start, end } = chartParams
const weekStartsOn = weekStartingMonday ? 1 : 0
return {
duration,
end:
duration === 'year'
? endOfYear(backward ? subYears(end, 1) : addYears(end, 1))
: duration === 'week'
? endOfWeek(backward ? subWeeks(end, 1) : addWeeks(end, 1), {
weekStartsOn,
})
: endOfMonth(backward ? subMonths(end, 1) : addMonths(end, 1)),
start:
duration === 'year'
? startOfYear(backward ? subYears(start, 1) : addYears(start, 1))
: duration === 'week'
? startOfWeek(backward ? subWeeks(start, 1) : addWeeks(start, 1), {
weekStartsOn,
})
: startOfMonth(backward ? subMonths(start, 1) : addMonths(start, 1)),
}
}

View File

@ -1,7 +1,12 @@
<template>
<div id="statistics">
<div class="container" v-if="authUser.username">
<Card>
<template #title>{{ $t('statistics.STATISTICS') }}</template>
<template #content>
<Statistics :user="authUser" :sports="sports" />
</template>
</Card>
</div>
</div>
</template>
@ -9,6 +14,7 @@
<script lang="ts">
import { ComputedRef, computed, defineComponent } from 'vue'
import Card from '@/components/Common/Card.vue'
import Statistics from '@/components/Statistics/index.vue'
import { USER_STORE, SPORTS_STORE } from '@/store/constants'
import { ISport } from '@/types/sports'
@ -18,6 +24,7 @@
export default defineComponent({
name: 'StatisticsView',
components: {
Card,
Statistics,
},
setup() {
@ -38,6 +45,14 @@
<style lang="scss" scoped>
@import '~@/scss/base';
#statistics {
height: 100%;
display: flex;
width: 100%;
margin-bottom: 30px;
.container {
width: 100%;
::v-deep(.card) {
width: 100%;
}
}
}
</style>

View File

@ -7,7 +7,13 @@ import {
TStatisticsDatasets,
TStatisticsFromApi,
} from '@/types/statistics'
import { formatStats, getDateKeys, getDatasets } from '@/utils/statistics'
import {
formatStats,
getDateKeys,
getDatasets,
getStatsDateParams,
updateChartParams,
} from '@/utils/statistics'
describe('getDateKeys (week starting Sunday)', () => {
const testsParams = [
@ -418,3 +424,424 @@ describe('formatStats', () => {
)
})
})
describe("getStatsDateParams when time frame is 'month')", () => {
const weekStartingMonday = [false, true]
weekStartingMonday.map((weekStartingMonday) => {
const testsParams = [
{
description: 'it returns date params when input date is 04/10/2021',
input: {
date: new Date('October 04, 2021 11:00:00'),
},
expected: {
duration: 'month',
start: new Date('November 01, 2020 00:00:00'),
end: new Date('October 31, 2021 23:59:59.999'),
},
},
{
description: 'it returns date params when input date is 03/02/2020',
input: {
date: new Date('February 03, 2020 23:30:00'),
},
expected: {
duration: 'month',
start: new Date('March 01, 2019 00:00:00'),
end: new Date('February 29, 2020 23:59:59.999'),
},
},
]
testsParams.map((testParams) => {
it(testParams.description, () => {
assert.deepEqual(
getStatsDateParams(
testParams.input.date,
'month',
weekStartingMonday
),
testParams.expected
)
})
})
})
})
describe("getStatsDateParams when time frame is 'year')", () => {
const weekStartingMonday = [false, true]
weekStartingMonday.map((weekStartingMonday) => {
const testsParams = [
{
description: 'it returns date params when input date is 04/10/2021',
input: {
date: new Date('October 04, 2021 11:00:00'),
},
expected: {
duration: 'year',
start: new Date('January 01, 2012 00:00:00'),
end: new Date('December 31, 2021 23:59:59.999'),
},
},
{
description: 'it returns date params when input date is 03/02/2020',
input: {
date: new Date('February 03, 2020 23:30:00'),
},
expected: {
duration: 'year',
start: new Date('January 01, 2011 00:00:00'),
end: new Date('December 31, 2020 23:59:59.999'),
},
},
]
testsParams.map((testParams) => {
it(testParams.description, () => {
assert.deepEqual(
getStatsDateParams(testParams.input.date, 'year', weekStartingMonday),
testParams.expected
)
})
})
})
})
describe("getStatsDateParams when time frame is 'week')", () => {
const testsParams = [
{
description:
'it returns date params when input date is 04/10/2021, when week start on Sunday',
input: {
date: new Date('October 04, 2021 11:00:00'),
weekStartingMonday: false,
},
expected: {
duration: 'week',
start: new Date('August 01, 2021 00:00:00'),
end: new Date('October 09, 2021 23:59:59.999'),
},
},
{
description:
'it returns date params when input date is 03/02/2020, when week start on Sunday',
input: {
date: new Date('February 03, 2020 23:30:00'),
weekStartingMonday: false,
},
expected: {
duration: 'week',
start: new Date('December 01, 2019 00:00:00'),
end: new Date('February 08, 2020 23:59:59.999'),
},
},
{
description:
'it returns date params when input date is 04/10/2021, when week start on Monday',
input: {
date: new Date('October 04, 2021 11:00:00'),
weekStartingMonday: true,
},
expected: {
duration: 'week',
start: new Date('August 02, 2021 00:00:00'),
end: new Date('October 10, 2021 23:59:59.999'),
},
},
{
description:
'it returns date params when input date is 03/02/2020, when week start on Monday',
input: {
date: new Date('February 03, 2020 23:30:00'),
weekStartingMonday: true,
},
expected: {
duration: 'week',
start: new Date('December 02, 2019 00:00:00'),
end: new Date('February 09, 2020 23:59:59.999'),
},
},
]
testsParams.map((testParams) => {
it(testParams.description, () => {
assert.deepEqual(
getStatsDateParams(
testParams.input.date,
'week',
testParams.input.weekStartingMonday
),
testParams.expected
)
})
})
})
describe("updateChartParams when time frame is 'month')", () => {
const weekStartingMonday = [false, true]
weekStartingMonday.map((weekStartingMonday) => {
const testsParams = [
{
description:
'it return forward date params when start date is 01/11/2020',
input: {
chartParams: {
duration: 'month',
start: new Date('November 01, 2020 00:00:00'),
end: new Date('October 31, 2021 23:59:59.999'),
},
backward: false,
},
expected: {
duration: 'month',
start: new Date('December 01, 2020 00:00:00'),
end: new Date('November 30, 2021 23:59:59.999'),
},
},
{
description:
'it return forward date params when start date is 01/02/2019',
input: {
chartParams: {
duration: 'month',
start: new Date('February 01, 2019 00:00:00'),
end: new Date('January 31, 2020 23:59:59.999'),
},
backward: false,
},
expected: {
duration: 'month',
start: new Date('March 01, 2019 00:00:00'),
end: new Date('February 29, 2020 23:59:59.999'),
},
},
{
description:
'it return backward date params when input date is 01/12/2020',
input: {
chartParams: {
duration: 'month',
start: new Date('December 01, 2020 00:00:00'),
end: new Date('November 30, 2021 23:59:59.999'),
},
backward: true,
},
expected: {
duration: 'month',
start: new Date('November 01, 2020 00:00:00'),
end: new Date('October 31, 2021 23:59:59.999'),
},
},
{
description:
'it return backward date params when input date is 01/03/2019',
input: {
chartParams: {
duration: 'month',
start: new Date('March 01, 2019 00:00:00'),
end: new Date('February 29, 2020 23:59:59.999'),
},
backward: true,
},
expected: {
duration: 'month',
start: new Date('February 01, 2019 00:00:00'),
end: new Date('January 31, 2020 23:59:59.999'),
},
},
]
testsParams.map((testParams) => {
it(testParams.description, () => {
assert.deepEqual(
updateChartParams(
testParams.input.chartParams,
testParams.input.backward,
weekStartingMonday
),
testParams.expected
)
})
})
})
})
describe("updateChartParams when time frame is 'year')", () => {
const weekStartingMonday = [false, true]
weekStartingMonday.map((weekStartingMonday) => {
const testsParams = [
{
description: 'it returns date params when start date is 01/10/2012',
input: {
chartParams: {
duration: 'year',
start: new Date('January 01, 2012 00:00:00'),
end: new Date('December 31, 2021 23:59:59.999'),
},
backward: false,
},
expected: {
duration: 'year',
start: new Date('January 01, 2013 00:00:00'),
end: new Date('December 31, 2022 23:59:59.999'),
},
},
{
description: 'it returns date params when input date is 01/01/2011',
input: {
chartParams: {
duration: 'year',
start: new Date('January 01, 2011 00:00:00'),
end: new Date('December 31, 2020 23:59:59.999'),
},
backward: false,
},
expected: {
duration: 'year',
start: new Date('January 01, 2012 00:00:00'),
end: new Date('December 31, 2021 23:59:59.999'),
},
},
{
description: 'it returns date params when start date is 01/10/2013',
input: {
chartParams: {
duration: 'year',
start: new Date('January 01, 2013 00:00:00'),
end: new Date('December 31, 2022 23:59:59.999'),
},
backward: true,
},
expected: {
duration: 'year',
start: new Date('January 01, 2012 00:00:00'),
end: new Date('December 31, 2021 23:59:59.999'),
},
},
{
description: 'it returns date params when input date is 01/01/2012',
input: {
chartParams: {
duration: 'year',
start: new Date('January 01, 2012 00:00:00'),
end: new Date('December 31, 2021 23:59:59.999'),
},
backward: true,
},
expected: {
duration: 'year',
start: new Date('January 01, 2011 00:00:00'),
end: new Date('December 31, 2020 23:59:59.999'),
},
},
]
testsParams.map((testParams) => {
it(testParams.description, () => {
assert.deepEqual(
updateChartParams(
testParams.input.chartParams,
testParams.input.backward,
weekStartingMonday
),
testParams.expected
)
})
})
})
})
describe("updateChartParams when time frame is 'week')", () => {
const testsParams = [
{
description:
'it returns forward date params when start date is 01/09/2021 and week starts on Sunday',
input: {
chartParams: {
duration: 'week',
start: new Date('August 01, 2021 00:00:00'),
end: new Date('October 09, 2021 23:59:59.999'),
},
backward: false,
weekStartingMonday: false,
},
expected: {
duration: 'week',
start: new Date('August 08, 2021 00:00:00'),
end: new Date('October 16, 2021 23:59:59.999'),
},
},
{
description:
'it returns backward date params when start date is 01/09/2021 and week starts on Sunday',
input: {
chartParams: {
duration: 'week',
start: new Date('August 01, 2021 00:00:00'),
end: new Date('October 09, 2021 23:59:59.999'),
},
backward: true,
weekStartingMonday: false,
},
expected: {
duration: 'week',
start: new Date('July 25, 2021 00:00:00'),
end: new Date('October 02, 2021 23:59:59.999'),
},
},
{
description:
'it returns forward date params when start date is 01/09/2021 and week starts on Monday',
input: {
chartParams: {
duration: 'week',
start: new Date('August 02, 2021 00:00:00'),
end: new Date('October 10, 2021 23:59:59.999'),
},
backward: false,
weekStartingMonday: true,
},
expected: {
duration: 'week',
start: new Date('August 09, 2021 00:00:00'),
end: new Date('October 17, 2021 23:59:59.999'),
},
},
{
description:
'it returns backward date params when start date is 01/09/2021 and week starts on Monday',
input: {
chartParams: {
duration: 'week',
start: new Date('August 02, 2021 00:00:00'),
end: new Date('October 10, 2021 23:59:59.999'),
},
backward: true,
weekStartingMonday: true,
},
expected: {
duration: 'week',
start: new Date('July 26, 2021 00:00:00'),
end: new Date('October 03, 2021 23:59:59.999'),
},
},
]
testsParams.map((testParams) => {
it(testParams.description, () => {
assert.deepEqual(
updateChartParams(
testParams.input.chartParams,
testParams.input.backward,
testParams.input.weekStartingMonday
),
testParams.expected
)
})
})
})