fix timezone issues related to time entries spanning over a day

This commit is contained in:
Gregor Vostrak
2024-06-12 17:06:10 +02:00
parent 91cb6ab087
commit 92dde6a701
5 changed files with 55 additions and 23 deletions

View File

@@ -1,11 +1,13 @@
<script setup lang="ts">
import { ref } from 'vue';
import { getLocalizedDayJs } from '@/utils/time';
import { getDayJsInstance, getLocalizedDayJs } from '@/utils/time';
import { twMerge } from 'tailwind-merge';
const props = defineProps<{
class?: string;
}>();
// This has to be a localized timestamp, not UTC
const model = defineModel<string | null>({
default: null,
});
@@ -15,13 +17,12 @@ const tempDate = ref(getLocalizedDayJs(model.value).format('YYYY-MM-DD'));
function updateDate(event: Event) {
const target = event.target as HTMLInputElement;
const newValue = target.value;
const newDate = getLocalizedDayJs(newValue);
const newDate = getDayJsInstance()(newValue);
if (newDate) {
model.value = getLocalizedDayJs(model.value)
.set('year', newDate.year())
.set('month', newDate.month())
.set('date', newDate.date())
.utc()
.format();
emit('changed', model.value);
}

View File

@@ -2,7 +2,7 @@
import TextInput from '@/Components/TextInput.vue';
import SecondaryButton from '@/Components/SecondaryButton.vue';
import DialogModal from '@/Components/DialogModal.vue';
import { ref } from 'vue';
import { ref, watch } from 'vue';
import PrimaryButton from '@/Components/PrimaryButton.vue';
import { useFocus } from '@vueuse/core';
import TimeTrackerTagDropdown from '@/Components/Common/TimeTracker/TimeTrackerTagDropdown.vue';
@@ -13,7 +13,7 @@ import { useTimeEntriesStore } from '@/utils/useTimeEntries';
import InputLabel from '@/Components/InputLabel.vue';
import TimePicker from '@/Components/Common/TimePicker.vue';
import DatePicker from '@/Components/Common/DatePicker.vue';
import { getDayJsInstance } from '@/utils/time';
import { getDayJsInstance, getLocalizedDayJs } from '@/utils/time';
const { createTimeEntry } = useTimeEntriesStore();
const show = defineModel('show', { default: false });
@@ -32,9 +32,25 @@ const timeEntryDefaultValues = {
const timeEntry = ref({ ...timeEntryDefaultValues });
const localStart = ref(
getLocalizedDayJs(timeEntryDefaultValues.start).format()
);
watch(localStart, (value) => {
timeEntry.value.start = getLocalizedDayJs(value).utc().format();
});
const localEnd = ref(getLocalizedDayJs(timeEntryDefaultValues.end).format());
watch(localEnd, (value) => {
timeEntry.value.end = getLocalizedDayJs(value).utc().format();
});
async function submit() {
await createTimeEntry(timeEntry.value);
timeEntry.value = { ...timeEntryDefaultValues };
localStart.value = getLocalizedDayJs(timeEntryDefaultValues.start).format();
localEnd.value = getLocalizedDayJs(timeEntryDefaultValues.end).format();
show.value = false;
}
@@ -85,19 +101,19 @@ useFocus(projectNameInput, { initialValue: true });
<div class="flex-1">
<InputLabel>Start</InputLabel>
<div class="flex items-center space-x-4 mt-1">
<DatePicker v-model="timeEntry.start"></DatePicker>
<DatePicker v-model="localStart"></DatePicker>
<TimePicker
size="large"
v-model="timeEntry.start"></TimePicker>
v-model="localStart"></TimePicker>
</div>
</div>
<div class="flex-1">
<InputLabel>End</InputLabel>
<div class="flex items-center space-x-4 mt-1">
<DatePicker v-model="timeEntry.end"></DatePicker>
<DatePicker v-model="localEnd"></DatePicker>
<TimePicker
size="large"
v-model="timeEntry.end"></TimePicker>
v-model="localEnd"></TimePicker>
</div>
</div>
</div>

View File

@@ -1,8 +1,9 @@
<script setup lang="ts">
import { computed } from 'vue';
import { getLocalizedDayJs } from '@/utils/time';
import { getDayJsInstance, getLocalizedDayJs } from '@/utils/time';
import { twMerge } from 'tailwind-merge';
// This has to be a localized timestamp, not UTC
const model = defineModel<string | null>({
default: null,
});
@@ -28,9 +29,8 @@ function updateMinutes(event: Event) {
const target = event.target as HTMLInputElement;
const newValue = target.value;
if (!isNaN(parseInt(newValue))) {
model.value = getLocalizedDayJs(model.value)
model.value = getDayJsInstance()(model.value)
.set('minutes', Math.min(parseInt(newValue), 59))
.utc()
.format();
}
}
@@ -41,7 +41,6 @@ function updateHours(event: Event) {
if (!isNaN(parseInt(newValue))) {
model.value = getLocalizedDayJs(model.value)
.set('hours', Math.min(parseInt(newValue), 23))
.utc()
.format();
}
}

View File

@@ -3,23 +3,33 @@ import { defineProps, ref, watch } from 'vue';
import TimePicker from '@/Components/Common/TimePicker.vue';
import { useFocusWithin } from '@vueuse/core';
import DatePicker from '@/Components/Common/DatePicker.vue';
import { getDayJsInstance, getLocalizedDayJs } from '@/utils/time';
import dayjs from 'dayjs';
const props = defineProps<{
start: string;
end: string | null;
}>();
// The timestamps for the changed event are UTC
const emit = defineEmits(['changed']);
const tempStart = ref(props.start);
const tempEnd = ref(props.end || null);
const tempStart = ref(
props.start ? getLocalizedDayJs(props.start).format() : dayjs().format()
);
const tempEnd = ref(props.end ? getLocalizedDayJs(props.end).format() : null);
watch(props, () => {
tempStart.value = props.start;
tempEnd.value = props.end;
tempStart.value = getLocalizedDayJs(props.start).format();
tempEnd.value = getLocalizedDayJs(props.end).format();
});
function updateTimeEntry() {
if (tempStart.value !== props.start || tempEnd.value !== props.end) {
emit('changed', tempStart.value, tempEnd.value);
emit(
'changed',
getDayJsInstance()(tempStart.value).utc().format(),
getDayJsInstance()(tempEnd.value).utc().format()
);
}
}

View File

@@ -13,7 +13,11 @@ import DateRangePicker from '@/Components/Common/DateRangePicker.vue';
import ReportingChart from '@/Components/Common/Reporting/ReportingChart.vue';
import BillableIcon from '@/Components/Common/Icons/BillableIcon.vue';
import { onMounted, ref } from 'vue';
import { formatHumanReadableDuration, getDayJsInstance } from '@/utils/time';
import {
formatHumanReadableDuration,
getDayJsInstance,
getLocalizedDayJs,
} from '@/utils/time';
import { type GroupingOption, useReportingStore } from '@/utils/useReporting';
import { storeToRefs } from 'pinia';
import TagDropdown from '@/Components/Common/Tag/TagDropdown.vue';
@@ -31,9 +35,11 @@ import { getCurrentMembershipId, getCurrentRole } from '@/utils/useUser';
import ClientMultiselectDropdown from '@/Components/Common/Client/ClientMultiselectDropdown.vue';
const startDate = ref<string | null>(
getDayJsInstance()().subtract(14, 'd').format('YYYY-MM-DD')
getLocalizedDayJs(getDayJsInstance()().format()).subtract(2, 'd').format()
);
const endDate = ref<string | null>(
getLocalizedDayJs(getDayJsInstance()().format()).format()
);
const endDate = ref<string | null>(getDayJsInstance()().format('YYYY-MM-DD'));
const selectedTags = ref<string[]>([]);
const selectedProjects = ref<string[]>([]);
const selectedMembers = ref<string[]>([]);
@@ -54,8 +60,8 @@ const { groupByOptions } = reportingStore;
function getFilterAttributes() {
let params: AggregatedTimeEntriesQueryParams = {
start: getDayJsInstance()(startDate.value).utc().format(),
end: getDayJsInstance()(endDate.value).endOf('day').utc().format(),
start: getLocalizedDayJs(startDate.value).startOf('day').utc().format(),
end: getLocalizedDayJs(endDate.value).endOf('day').utc().format(),
};
params = {
...params,