mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-18 05:02:14 +01:00
fix timezone issues related to time entries spanning over a day
This commit is contained in:
@@ -1,11 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { getLocalizedDayJs } from '@/utils/time';
|
import { getDayJsInstance, getLocalizedDayJs } from '@/utils/time';
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
class?: string;
|
class?: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
// This has to be a localized timestamp, not UTC
|
||||||
const model = defineModel<string | null>({
|
const model = defineModel<string | null>({
|
||||||
default: null,
|
default: null,
|
||||||
});
|
});
|
||||||
@@ -15,13 +17,12 @@ const tempDate = ref(getLocalizedDayJs(model.value).format('YYYY-MM-DD'));
|
|||||||
function updateDate(event: Event) {
|
function updateDate(event: Event) {
|
||||||
const target = event.target as HTMLInputElement;
|
const target = event.target as HTMLInputElement;
|
||||||
const newValue = target.value;
|
const newValue = target.value;
|
||||||
const newDate = getLocalizedDayJs(newValue);
|
const newDate = getDayJsInstance()(newValue);
|
||||||
if (newDate) {
|
if (newDate) {
|
||||||
model.value = getLocalizedDayJs(model.value)
|
model.value = getLocalizedDayJs(model.value)
|
||||||
.set('year', newDate.year())
|
.set('year', newDate.year())
|
||||||
.set('month', newDate.month())
|
.set('month', newDate.month())
|
||||||
.set('date', newDate.date())
|
.set('date', newDate.date())
|
||||||
.utc()
|
|
||||||
.format();
|
.format();
|
||||||
emit('changed', model.value);
|
emit('changed', model.value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import TextInput from '@/Components/TextInput.vue';
|
import TextInput from '@/Components/TextInput.vue';
|
||||||
import SecondaryButton from '@/Components/SecondaryButton.vue';
|
import SecondaryButton from '@/Components/SecondaryButton.vue';
|
||||||
import DialogModal from '@/Components/DialogModal.vue';
|
import DialogModal from '@/Components/DialogModal.vue';
|
||||||
import { ref } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
import PrimaryButton from '@/Components/PrimaryButton.vue';
|
import PrimaryButton from '@/Components/PrimaryButton.vue';
|
||||||
import { useFocus } from '@vueuse/core';
|
import { useFocus } from '@vueuse/core';
|
||||||
import TimeTrackerTagDropdown from '@/Components/Common/TimeTracker/TimeTrackerTagDropdown.vue';
|
import TimeTrackerTagDropdown from '@/Components/Common/TimeTracker/TimeTrackerTagDropdown.vue';
|
||||||
@@ -13,7 +13,7 @@ import { useTimeEntriesStore } from '@/utils/useTimeEntries';
|
|||||||
import InputLabel from '@/Components/InputLabel.vue';
|
import InputLabel from '@/Components/InputLabel.vue';
|
||||||
import TimePicker from '@/Components/Common/TimePicker.vue';
|
import TimePicker from '@/Components/Common/TimePicker.vue';
|
||||||
import DatePicker from '@/Components/Common/DatePicker.vue';
|
import DatePicker from '@/Components/Common/DatePicker.vue';
|
||||||
import { getDayJsInstance } from '@/utils/time';
|
import { getDayJsInstance, getLocalizedDayJs } from '@/utils/time';
|
||||||
|
|
||||||
const { createTimeEntry } = useTimeEntriesStore();
|
const { createTimeEntry } = useTimeEntriesStore();
|
||||||
const show = defineModel('show', { default: false });
|
const show = defineModel('show', { default: false });
|
||||||
@@ -32,9 +32,25 @@ const timeEntryDefaultValues = {
|
|||||||
|
|
||||||
const timeEntry = ref({ ...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() {
|
async function submit() {
|
||||||
await createTimeEntry(timeEntry.value);
|
await createTimeEntry(timeEntry.value);
|
||||||
timeEntry.value = { ...timeEntryDefaultValues };
|
timeEntry.value = { ...timeEntryDefaultValues };
|
||||||
|
localStart.value = getLocalizedDayJs(timeEntryDefaultValues.start).format();
|
||||||
|
localEnd.value = getLocalizedDayJs(timeEntryDefaultValues.end).format();
|
||||||
show.value = false;
|
show.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,19 +101,19 @@ useFocus(projectNameInput, { initialValue: true });
|
|||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<InputLabel>Start</InputLabel>
|
<InputLabel>Start</InputLabel>
|
||||||
<div class="flex items-center space-x-4 mt-1">
|
<div class="flex items-center space-x-4 mt-1">
|
||||||
<DatePicker v-model="timeEntry.start"></DatePicker>
|
<DatePicker v-model="localStart"></DatePicker>
|
||||||
<TimePicker
|
<TimePicker
|
||||||
size="large"
|
size="large"
|
||||||
v-model="timeEntry.start"></TimePicker>
|
v-model="localStart"></TimePicker>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<InputLabel>End</InputLabel>
|
<InputLabel>End</InputLabel>
|
||||||
<div class="flex items-center space-x-4 mt-1">
|
<div class="flex items-center space-x-4 mt-1">
|
||||||
<DatePicker v-model="timeEntry.end"></DatePicker>
|
<DatePicker v-model="localEnd"></DatePicker>
|
||||||
<TimePicker
|
<TimePicker
|
||||||
size="large"
|
size="large"
|
||||||
v-model="timeEntry.end"></TimePicker>
|
v-model="localEnd"></TimePicker>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { getLocalizedDayJs } from '@/utils/time';
|
import { getDayJsInstance, getLocalizedDayJs } from '@/utils/time';
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
|
// This has to be a localized timestamp, not UTC
|
||||||
const model = defineModel<string | null>({
|
const model = defineModel<string | null>({
|
||||||
default: null,
|
default: null,
|
||||||
});
|
});
|
||||||
@@ -28,9 +29,8 @@ function updateMinutes(event: Event) {
|
|||||||
const target = event.target as HTMLInputElement;
|
const target = event.target as HTMLInputElement;
|
||||||
const newValue = target.value;
|
const newValue = target.value;
|
||||||
if (!isNaN(parseInt(newValue))) {
|
if (!isNaN(parseInt(newValue))) {
|
||||||
model.value = getLocalizedDayJs(model.value)
|
model.value = getDayJsInstance()(model.value)
|
||||||
.set('minutes', Math.min(parseInt(newValue), 59))
|
.set('minutes', Math.min(parseInt(newValue), 59))
|
||||||
.utc()
|
|
||||||
.format();
|
.format();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,7 +41,6 @@ function updateHours(event: Event) {
|
|||||||
if (!isNaN(parseInt(newValue))) {
|
if (!isNaN(parseInt(newValue))) {
|
||||||
model.value = getLocalizedDayJs(model.value)
|
model.value = getLocalizedDayJs(model.value)
|
||||||
.set('hours', Math.min(parseInt(newValue), 23))
|
.set('hours', Math.min(parseInt(newValue), 23))
|
||||||
.utc()
|
|
||||||
.format();
|
.format();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,23 +3,33 @@ import { defineProps, ref, watch } from 'vue';
|
|||||||
import TimePicker from '@/Components/Common/TimePicker.vue';
|
import TimePicker from '@/Components/Common/TimePicker.vue';
|
||||||
import { useFocusWithin } from '@vueuse/core';
|
import { useFocusWithin } from '@vueuse/core';
|
||||||
import DatePicker from '@/Components/Common/DatePicker.vue';
|
import DatePicker from '@/Components/Common/DatePicker.vue';
|
||||||
|
import { getDayJsInstance, getLocalizedDayJs } from '@/utils/time';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
start: string;
|
start: string;
|
||||||
end: string | null;
|
end: string | null;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
// The timestamps for the changed event are UTC
|
||||||
const emit = defineEmits(['changed']);
|
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, () => {
|
watch(props, () => {
|
||||||
tempStart.value = props.start;
|
tempStart.value = getLocalizedDayJs(props.start).format();
|
||||||
tempEnd.value = props.end;
|
tempEnd.value = getLocalizedDayJs(props.end).format();
|
||||||
});
|
});
|
||||||
function updateTimeEntry() {
|
function updateTimeEntry() {
|
||||||
if (tempStart.value !== props.start || tempEnd.value !== props.end) {
|
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()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,11 @@ import DateRangePicker from '@/Components/Common/DateRangePicker.vue';
|
|||||||
import ReportingChart from '@/Components/Common/Reporting/ReportingChart.vue';
|
import ReportingChart from '@/Components/Common/Reporting/ReportingChart.vue';
|
||||||
import BillableIcon from '@/Components/Common/Icons/BillableIcon.vue';
|
import BillableIcon from '@/Components/Common/Icons/BillableIcon.vue';
|
||||||
import { onMounted, ref } from '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 { type GroupingOption, useReportingStore } from '@/utils/useReporting';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import TagDropdown from '@/Components/Common/Tag/TagDropdown.vue';
|
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';
|
import ClientMultiselectDropdown from '@/Components/Common/Client/ClientMultiselectDropdown.vue';
|
||||||
|
|
||||||
const startDate = ref<string | null>(
|
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 selectedTags = ref<string[]>([]);
|
||||||
const selectedProjects = ref<string[]>([]);
|
const selectedProjects = ref<string[]>([]);
|
||||||
const selectedMembers = ref<string[]>([]);
|
const selectedMembers = ref<string[]>([]);
|
||||||
@@ -54,8 +60,8 @@ const { groupByOptions } = reportingStore;
|
|||||||
|
|
||||||
function getFilterAttributes() {
|
function getFilterAttributes() {
|
||||||
let params: AggregatedTimeEntriesQueryParams = {
|
let params: AggregatedTimeEntriesQueryParams = {
|
||||||
start: getDayJsInstance()(startDate.value).utc().format(),
|
start: getLocalizedDayJs(startDate.value).startOf('day').utc().format(),
|
||||||
end: getDayJsInstance()(endDate.value).endOf('day').utc().format(),
|
end: getLocalizedDayJs(endDate.value).endOf('day').utc().format(),
|
||||||
};
|
};
|
||||||
params = {
|
params = {
|
||||||
...params,
|
...params,
|
||||||
|
|||||||
Reference in New Issue
Block a user