Merged in feature/vbv-148-human-readable-duration (pull request #8)
Implement human readable durations * Implement duration formatting for humans * Add UTs * Rework rounding and formatting * Rename function
This commit is contained in:
parent
da13345511
commit
722b9f7937
|
|
@ -4,6 +4,7 @@ import type { LearningContent, LearningSequence } from '@/types'
|
||||||
import { useCircleStore } from '@/stores/circle'
|
import { useCircleStore } from '@/stores/circle'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
import { humanizeDuration } from '@/utils/humanizeDuration'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
learningSequence: LearningSequence
|
learningSequence: LearningSequence
|
||||||
|
|
@ -73,14 +74,14 @@ const learningSequenceBorderClass = computed(() => {
|
||||||
<h3 class="text-xl font-semibold">
|
<h3 class="text-xl font-semibold">
|
||||||
{{ learningSequence.title }}
|
{{ learningSequence.title }}
|
||||||
</h3>
|
</h3>
|
||||||
<div>{{ learningSequence.minutes }} Minuten</div>
|
<div>{{ humanizeDuration(learningSequence.minutes) }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-white px-4 lg:px-6 border border-gray-500" :class="learningSequenceBorderClass">
|
<div class="bg-white px-4 lg:px-6 border border-gray-500" :class="learningSequenceBorderClass">
|
||||||
<div v-for="learningUnit in learningSequence.learningUnits" :key="learningUnit.id" class="pt-3 lg:pt-6">
|
<div v-for="learningUnit in learningSequence.learningUnits" :key="learningUnit.id" class="pt-3 lg:pt-6">
|
||||||
<div class="pb-3 lg:pg-6 flex gap-4 text-blue-900" v-if="learningUnit.title">
|
<div class="pb-3 lg:pg-6 flex gap-4 text-blue-900" v-if="learningUnit.title">
|
||||||
<div class="font-semibold">{{ learningUnit.title }}</div>
|
<div class="font-semibold">{{ learningUnit.title }}</div>
|
||||||
<div>{{ learningUnit.minutes }} Minuten</div>
|
<div>{{ humanizeDuration(learningUnit.minutes) }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { expect, test } from 'vitest'
|
||||||
|
import { humanizeDuration } from '../humanizeDuration'
|
||||||
|
|
||||||
|
test('format duration for humans', () => {
|
||||||
|
expect(humanizeDuration(1)).toBe('1 Minute')
|
||||||
|
expect(humanizeDuration(15)).toBe('15 Minuten')
|
||||||
|
expect(humanizeDuration(42)).toBe('45 Minuten')
|
||||||
|
expect(humanizeDuration(60)).toBe('1 Stunde')
|
||||||
|
expect(humanizeDuration(122)).toBe('2 Stunden')
|
||||||
|
expect(humanizeDuration(120)).toBe('2 Stunden')
|
||||||
|
expect(humanizeDuration(132)).toBe('2 Stunden 15 Minuten')
|
||||||
|
expect(humanizeDuration(632)).toBe('10 Stunden')
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
function pluralize(text: string, count: number) {
|
||||||
|
if (count === 1) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
return text + 'n';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function humanizeDuration(minutes: number) {
|
||||||
|
const hours = Math.floor(minutes / 60)
|
||||||
|
const remainingMinutes = minutes % 60
|
||||||
|
|
||||||
|
if (hours === 0 && minutes < 16) {
|
||||||
|
return pluralize(`${remainingMinutes} Minute`, remainingMinutes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remaining minutes are rounded to 15 mins
|
||||||
|
const roundToMinutes = 15
|
||||||
|
const roundedMinutes = Math.round((minutes % 60) / roundToMinutes) * roundToMinutes
|
||||||
|
|
||||||
|
const hoursString = hours > 0 ? pluralize(`${hours} Stunde`, hours) : ''
|
||||||
|
|
||||||
|
const showMinutesUpToHours = 10
|
||||||
|
const minutesString = roundedMinutes > 0 && hours < showMinutesUpToHours
|
||||||
|
? pluralize(`${roundedMinutes} Minute`, roundedMinutes) : ''
|
||||||
|
|
||||||
|
const delimiter = hoursString && minutesString ? ' ' : ''
|
||||||
|
return `${hoursString}${delimiter}${minutesString}`
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,7 @@ import { useCircleStore } from '@/stores/circle'
|
||||||
import { useAppStore } from '@/stores/app'
|
import { useAppStore } from '@/stores/app'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
import { humanizeDuration } from '@/utils/humanizeDuration'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
|
|
@ -28,7 +29,7 @@ const circleStore = useCircleStore()
|
||||||
const duration = computed(() => {
|
const duration = computed(() => {
|
||||||
if (circleStore.circle) {
|
if (circleStore.circle) {
|
||||||
const minutes = _.sumBy(circleStore.circle.learningSequences, 'minutes')
|
const minutes = _.sumBy(circleStore.circle.learningSequences, 'minutes')
|
||||||
return `${minutes} Minuten`
|
return humanizeDuration(minutes)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ''
|
return ''
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue