mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-17 04:32:15 +01:00
unify display values for work and break time totals
This commit is contained in:
@@ -2315,7 +2315,9 @@ test('test that break entries show a break badge and split day total on the time
|
|||||||
await page.goto(PLAYWRIGHT_BASE_URL + '/time');
|
await page.goto(PLAYWRIGHT_BASE_URL + '/time');
|
||||||
await expect(page.getByTestId('break_badge').first()).toBeVisible();
|
await expect(page.getByTestId('break_badge').first()).toBeVisible();
|
||||||
await expect(page.getByTestId('break_badge').first()).toContainText('Break');
|
await expect(page.getByTestId('break_badge').first()).toContainText('Break');
|
||||||
// Day heading shows the break portion separately from worked time
|
// Day heading shows worked time first, then the break portion
|
||||||
await expect(page.getByTestId('day_break_duration').first()).toBeVisible();
|
await expect(page.getByTestId('day_break_duration').first()).toBeVisible();
|
||||||
await expect(page.getByTestId('day_break_duration').first()).toContainText('break');
|
await expect(page.getByTestId('day_break_duration').first().locator('..')).toContainText(
|
||||||
|
'2h 00min work · 0h 30min break'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -186,19 +186,27 @@ const emit = defineEmits<{
|
|||||||
? 'text-text-primary'
|
? 'text-text-primary'
|
||||||
: 'text-text-secondary',
|
: 'text-text-secondary',
|
||||||
]">
|
]">
|
||||||
<span>{{ total > 0 ? formatDuration(total) : '-' }}</span>
|
<span
|
||||||
|
>{{ formatDuration(total)
|
||||||
|
}}<template v-if="total > 0 && (breakDayTotals[dayIndex] ?? 0) > 0">
|
||||||
|
work</template
|
||||||
|
></span
|
||||||
|
>
|
||||||
<span
|
<span
|
||||||
v-if="(breakDayTotals[dayIndex] ?? 0) > 0"
|
v-if="(breakDayTotals[dayIndex] ?? 0) > 0"
|
||||||
class="font-normal text-text-tertiary">
|
class="font-normal text-text-tertiary"
|
||||||
+{{ formatDuration(breakDayTotals[dayIndex] ?? 0) }} break
|
>{{ formatDuration(breakDayTotals[dayIndex] ?? 0) }} break</span
|
||||||
</span>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="flex flex-col items-end justify-center border-t border-default-background-separator bg-background dark:bg-secondary pl-3 pr-3 py-1 text-xs font-semibold text-text-primary leading-tight">
|
class="flex flex-col items-end justify-center border-t border-default-background-separator bg-background dark:bg-secondary pl-3 pr-3 py-1 text-xs font-semibold text-text-primary leading-tight">
|
||||||
<span>{{ weekTotalFormatted }}</span>
|
<span
|
||||||
<span v-if="breakGrandTotal > 0" class="font-normal text-text-tertiary">
|
>{{ weekTotalFormatted
|
||||||
+{{ formatDuration(breakGrandTotal) }} break
|
}}<template v-if="breakGrandTotal > 0"> work</template></span
|
||||||
</span>
|
>
|
||||||
|
<span v-if="breakGrandTotal > 0" class="font-normal text-text-tertiary"
|
||||||
|
>{{ formatDuration(breakGrandTotal) }} break</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="border-t border-default-background-separator bg-background dark:bg-secondary"></div>
|
class="border-t border-default-background-separator bg-background dark:bg-secondary"></div>
|
||||||
|
|||||||
@@ -17,6 +17,27 @@ const breakSecondsValue = computed(() => props.breakSeconds ?? 0);
|
|||||||
const organization = inject('organization') as ComputedRef<Organization | undefined> | undefined;
|
const organization = inject('organization') as ComputedRef<Organization | undefined> | undefined;
|
||||||
const intervalFormat = computed(() => organization?.value?.interval_format);
|
const intervalFormat = computed(() => organization?.value?.interval_format);
|
||||||
const numberFormat = computed(() => organization?.value?.number_format);
|
const numberFormat = computed(() => organization?.value?.number_format);
|
||||||
|
|
||||||
|
const hasBreak = computed(() => breakSecondsValue.value > 0);
|
||||||
|
|
||||||
|
// Without breaks the work time stands alone, so it needs no label. Once break
|
||||||
|
// time joins it, both halves are labelled to keep them apart.
|
||||||
|
const durationSummary = computed(() => {
|
||||||
|
const work = formatHumanReadableDuration(
|
||||||
|
totalSecondsValue.value,
|
||||||
|
intervalFormat.value,
|
||||||
|
numberFormat.value
|
||||||
|
);
|
||||||
|
if (!hasBreak.value) {
|
||||||
|
return work;
|
||||||
|
}
|
||||||
|
const breakTime = formatHumanReadableDuration(
|
||||||
|
breakSecondsValue.value,
|
||||||
|
intervalFormat.value,
|
||||||
|
numberFormat.value
|
||||||
|
);
|
||||||
|
return `${work} work · ${breakTime} break`;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -24,12 +45,10 @@ const numberFormat = computed(() => organization?.value?.number_format);
|
|||||||
<div class="text-sm text-foreground" :class="isToday ? 'font-semibold' : 'font-medium'">
|
<div class="text-sm text-foreground" :class="isToday ? 'font-semibold' : 'font-medium'">
|
||||||
{{ date.format('ddd') }} {{ date.date() }}
|
{{ date.format('ddd') }} {{ date.date() }}
|
||||||
</div>
|
</div>
|
||||||
<span class="block text-xs text-muted-foreground font-medium mt-0.5">
|
<span
|
||||||
{{ formatHumanReadableDuration(totalSecondsValue, intervalFormat, numberFormat) }}
|
class="block text-xs text-muted-foreground font-medium mt-0.5"
|
||||||
<template v-if="breakSecondsValue > 0">
|
data-testid="day_duration_summary"
|
||||||
· {{ formatHumanReadableDuration(breakSecondsValue, intervalFormat, numberFormat) }}
|
>{{ durationSummary }}</span
|
||||||
break
|
>
|
||||||
</template>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ import {
|
|||||||
formatWeekday,
|
formatWeekday,
|
||||||
} from '@/packages/ui/src/utils/time';
|
} from '@/packages/ui/src/utils/time';
|
||||||
import Checkbox from '../Input/Checkbox.vue';
|
import Checkbox from '../Input/Checkbox.vue';
|
||||||
import { inject, type ComputedRef } from 'vue';
|
import { computed, inject, type ComputedRef } from 'vue';
|
||||||
import type { Organization } from '@/packages/api/src';
|
import type { Organization } from '@/packages/api/src';
|
||||||
import { CalendarIcon } from '@heroicons/vue/20/solid';
|
import { CalendarIcon } from '@heroicons/vue/20/solid';
|
||||||
|
|
||||||
const organization = inject<ComputedRef<Organization>>('organization');
|
const organization = inject<ComputedRef<Organization>>('organization');
|
||||||
|
|
||||||
withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
date: string;
|
date: string;
|
||||||
duration: number;
|
duration: number;
|
||||||
@@ -23,6 +23,26 @@ withDefaults(
|
|||||||
breakDuration: 0,
|
breakDuration: 0,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const hasBreak = computed(() => props.breakDuration > 0);
|
||||||
|
|
||||||
|
function formatDuration(seconds: number) {
|
||||||
|
return formatHumanReadableDuration(
|
||||||
|
seconds,
|
||||||
|
organization?.value?.interval_format,
|
||||||
|
organization?.value?.number_format
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Without breaks the work time stands alone, so it needs no label. Once break
|
||||||
|
// time joins it, both halves are labelled to keep them apart. The separator and
|
||||||
|
// its spacing live inside the interpolated strings so the markup cannot collapse
|
||||||
|
// them away.
|
||||||
|
const workLabel = computed(() =>
|
||||||
|
hasBreak.value ? `${formatDuration(props.duration)} work` : formatDuration(props.duration)
|
||||||
|
);
|
||||||
|
const breakLabel = computed(() => ` · ${formatDuration(props.breakDuration)} break`);
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
selectAll: [];
|
selectAll: [];
|
||||||
unselectAll: [];
|
unselectAll: [];
|
||||||
@@ -60,29 +80,14 @@ function selectUnselectAll(value: boolean) {
|
|||||||
{{ formatDate(date, organization?.date_format) }}
|
{{ formatDate(date, organization?.date_format) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-text-primary pr-2 @lg:pr-[92px]">
|
<div class="flex items-center text-text-primary pr-2 @lg:pr-[92px]">
|
||||||
|
<span class="font-medium">{{ workLabel }}</span>
|
||||||
<span
|
<span
|
||||||
v-if="breakDuration > 0"
|
v-if="hasBreak"
|
||||||
data-testid="day_break_duration"
|
data-testid="day_break_duration"
|
||||||
class="text-text-secondary font-normal mr-2">
|
class="text-text-secondary font-normal whitespace-pre"
|
||||||
{{
|
>{{ breakLabel }}</span
|
||||||
formatHumanReadableDuration(
|
>
|
||||||
breakDuration,
|
|
||||||
organization?.interval_format,
|
|
||||||
organization?.number_format
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
break ·
|
|
||||||
</span>
|
|
||||||
<span class="font-medium">
|
|
||||||
{{
|
|
||||||
formatHumanReadableDuration(
|
|
||||||
duration,
|
|
||||||
organization?.interval_format,
|
|
||||||
organization?.number_format
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</MainContainer>
|
</MainContainer>
|
||||||
|
|||||||
Reference in New Issue
Block a user