mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-13 18:52:14 +01:00
add support for interval / duration format in frontend views
This commit is contained in:
@@ -9,13 +9,14 @@ import type {
|
||||
Task,
|
||||
TimeEntry,
|
||||
Client,
|
||||
Organization,
|
||||
} from '@/packages/api/src';
|
||||
import TimeEntryDescriptionInput from '@/packages/ui/src/TimeEntry/TimeEntryDescriptionInput.vue';
|
||||
import TimeEntryRowTagDropdown from '@/packages/ui/src/TimeEntry/TimeEntryRowTagDropdown.vue';
|
||||
import TimeEntryMoreOptionsDropdown from '@/packages/ui/src/TimeEntry/TimeEntryMoreOptionsDropdown.vue';
|
||||
import TimeTrackerProjectTaskDropdown from '@/packages/ui/src/TimeTracker/TimeTrackerProjectTaskDropdown.vue';
|
||||
import BillableToggleButton from '@/packages/ui/src/Input/BillableToggleButton.vue';
|
||||
import { ref } from 'vue';
|
||||
import { ref, inject, type ComputedRef } from 'vue';
|
||||
import {
|
||||
formatHumanReadableDuration,
|
||||
formatStartEnd,
|
||||
@@ -48,6 +49,8 @@ const emit = defineEmits<{
|
||||
unselected: [TimeEntry[]];
|
||||
}>();
|
||||
|
||||
const organization = inject<ComputedRef<Organization>>('organization');
|
||||
|
||||
function updateTimeEntryDescription(description: string) {
|
||||
props.updateTimeEntries(
|
||||
props.timeEntry.timeEntries.map((timeEntry: TimeEntry) => timeEntry.id),
|
||||
@@ -113,10 +116,10 @@ function onSelectChange(checked: boolean) {
|
||||
</GroupedItemsCountButton>
|
||||
<TimeEntryDescriptionInput
|
||||
class="min-w-0 mr-4"
|
||||
:model-value="
|
||||
timeEntry.description
|
||||
"
|
||||
@changed="updateTimeEntryDescription"></TimeEntryDescriptionInput>
|
||||
:model-value="timeEntry.description"
|
||||
@changed="
|
||||
updateTimeEntryDescription
|
||||
"></TimeEntryDescriptionInput>
|
||||
<TimeTrackerProjectTaskDropdown
|
||||
:clients
|
||||
:create-project
|
||||
@@ -128,10 +131,10 @@ function onSelectChange(checked: boolean) {
|
||||
:project="timeEntry.project_id"
|
||||
:enable-estimated-time
|
||||
:currency="currency"
|
||||
:task="
|
||||
timeEntry.task_id
|
||||
"
|
||||
@changed="updateProjectAndTask"></TimeTrackerProjectTaskDropdown>
|
||||
:task="timeEntry.task_id"
|
||||
@changed="
|
||||
updateProjectAndTask
|
||||
"></TimeTrackerProjectTaskDropdown>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center font-medium lg:space-x-2">
|
||||
@@ -139,7 +142,9 @@ function onSelectChange(checked: boolean) {
|
||||
:create-tag
|
||||
:tags="tags"
|
||||
:model-value="timeEntry.tags"
|
||||
@changed="updateTimeEntryTags"></TimeEntryRowTagDropdown>
|
||||
@changed="
|
||||
updateTimeEntryTags
|
||||
"></TimeEntryRowTagDropdown>
|
||||
<BillableToggleButton
|
||||
:model-value="timeEntry.billable"
|
||||
class="opacity-50 focus-visible:opacity-100 group-hover:opacity-100"
|
||||
@@ -155,17 +160,23 @@ function onSelectChange(checked: boolean) {
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
class="text-text-primary min-w-[90px] px-2 py-1.5 bg-transparent text-center hover:bg-card-background rounded-lg border border-transparent hover:border-card-border text-sm font-semibold focus-visible:outline-none focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:bg-tertiary"
|
||||
class="text-text-primary min-w-[90px] px-2.5 py-1.5 bg-transparent text-right hover:bg-card-background rounded-lg border border-transparent hover:border-card-border text-sm font-semibold focus-visible:outline-none focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:bg-tertiary"
|
||||
@click="expanded = !expanded">
|
||||
{{
|
||||
formatHumanReadableDuration(timeEntry.duration ?? 0)
|
||||
formatHumanReadableDuration(
|
||||
timeEntry.duration ?? 0,
|
||||
organization?.interval_format,
|
||||
organization?.number_format
|
||||
)
|
||||
}}
|
||||
</button>
|
||||
|
||||
<TimeTrackerStartStop
|
||||
:active="!!(timeEntry.start && !timeEntry.end)"
|
||||
class="opacity-20 hidden sm:flex group-hover:opacity-100 focus-visible:opacity-100"
|
||||
@changed="onStartStopClick(timeEntry)"></TimeTrackerStartStop>
|
||||
@changed="
|
||||
onStartStopClick(timeEntry)
|
||||
"></TimeTrackerStartStop>
|
||||
<TimeEntryMoreOptionsDropdown
|
||||
@delete="
|
||||
deleteTimeEntries(timeEntry?.timeEntries ?? [])
|
||||
|
||||
@@ -2,10 +2,18 @@
|
||||
import {
|
||||
calculateDifference,
|
||||
formatHumanReadableDuration,
|
||||
parseTimeInput,
|
||||
} from '@/packages/ui/src/utils/time';
|
||||
import { computed, defineProps, ref } from 'vue';
|
||||
import parse from 'parse-duration';
|
||||
import { computed, defineProps, ref, inject, type ComputedRef } from 'vue';
|
||||
import dayjs from 'dayjs';
|
||||
import type { Organization } from '@/packages/api/src';
|
||||
|
||||
const organization = inject<ComputedRef<Organization>>('organization');
|
||||
|
||||
const organizationSettings = computed(() => ({
|
||||
intervalFormat: organization?.value?.interval_format ?? 'hours-minutes',
|
||||
numberFormat: organization?.value?.number_format ?? 'point',
|
||||
}));
|
||||
|
||||
const props = defineProps<{
|
||||
start: string;
|
||||
@@ -19,15 +27,22 @@ const temporaryCustomTimerEntry = ref<string>('');
|
||||
const open = ref(false);
|
||||
|
||||
function updateTimerAndStartLiveTimerUpdate() {
|
||||
const time = parse(temporaryCustomTimerEntry.value, 's');
|
||||
if (time && time > 0) {
|
||||
const defaultUnit =
|
||||
organizationSettings?.value?.intervalFormat === 'decimal'
|
||||
? 'hours'
|
||||
: 'minutes';
|
||||
const { seconds } = parseTimeInput(
|
||||
temporaryCustomTimerEntry.value,
|
||||
defaultUnit
|
||||
);
|
||||
if (seconds && seconds > 0) {
|
||||
let newEndDate = props.end;
|
||||
let newStartDate = props.start;
|
||||
if (props.end) {
|
||||
// only update end for time entries that are already finished
|
||||
newEndDate = dayjs(props.start).utc().add(time, 's').format();
|
||||
newEndDate = dayjs(props.start).utc().add(seconds, 's').format();
|
||||
} else {
|
||||
newStartDate = dayjs().utc().subtract(time, 's').format();
|
||||
newStartDate = dayjs().utc().subtract(seconds, 's').format();
|
||||
}
|
||||
emit('changed', newStartDate, newEndDate);
|
||||
}
|
||||
@@ -40,7 +55,9 @@ const currentTime = computed({
|
||||
return temporaryCustomTimerEntry.value;
|
||||
}
|
||||
return formatHumanReadableDuration(
|
||||
calculateDifference(props.start, props.end)
|
||||
calculateDifference(props.start, props.end),
|
||||
organizationSettings.value.intervalFormat,
|
||||
organizationSettings.value.numberFormat
|
||||
);
|
||||
},
|
||||
// setter
|
||||
@@ -63,8 +80,9 @@ function selectInput(event: Event) {
|
||||
<template>
|
||||
<input
|
||||
v-model="currentTime"
|
||||
data-testid="time_entry_duration_input"
|
||||
name="Duration"
|
||||
class="text-text-primary w-[90px] px-2 py-1.5 bg-transparent text-center hover:bg-card-background rounded-lg border border-transparent hover:border-card-border text-sm font-semibold focus-visible:bg-tertiary focus-visible:border-transparent focus-visible:ring-2 focus-visible:ring-ring"
|
||||
class="text-text-primary w-[90px] px-2.5 py-1.5 bg-transparent text-right hover:bg-card-background rounded-lg border border-transparent hover:border-card-border text-sm font-semibold focus-visible:bg-tertiary focus-visible:border-transparent focus-visible:ring-2 focus-visible:ring-ring"
|
||||
@focus="selectInput"
|
||||
@keydown.tab="open = false"
|
||||
@blur="updateTimerAndStartLiveTimerUpdate"
|
||||
|
||||
@@ -6,6 +6,11 @@ import {
|
||||
formatWeekday,
|
||||
} from '@/packages/ui/src/utils/time';
|
||||
import Checkbox from '../Input/Checkbox.vue';
|
||||
import { inject, type ComputedRef } from 'vue';
|
||||
import type { Organization } from '@/packages/api/src';
|
||||
|
||||
const organization = inject<ComputedRef<Organization>>('organization');
|
||||
|
||||
defineProps<{
|
||||
date: string;
|
||||
duration: number;
|
||||
@@ -58,7 +63,13 @@ function selectUnselectAll(value: boolean) {
|
||||
</div>
|
||||
<div class="text-text-secondary pr-[90px] lg:pr-[92px]">
|
||||
<span class="font-semibold">
|
||||
{{ formatHumanReadableDuration(duration) }}
|
||||
{{
|
||||
formatHumanReadableDuration(
|
||||
duration,
|
||||
organization?.interval_format,
|
||||
organization?.number_format
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user