mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-06-15 05:22:44 +01:00
Compare commits
1 Commits
8a1a187f7f
...
feature/ad
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb4d3ec061 |
@@ -10,7 +10,8 @@ defineProps<{
|
||||
<div class="px-4 py-2 2xl:py-3 border-b border-b-background-separator">
|
||||
<div class="col-span-2">
|
||||
<div class="flex justify-between">
|
||||
<p class="font-semibold text-sm text-text-primary">
|
||||
<p
|
||||
class="font-semibold text-sm min-w-0 overflow-ellipsis overflow-hidden flex-1 text-text-primary">
|
||||
{{ name }}
|
||||
</p>
|
||||
<div v-if="working" class="flex space-x-1.5 items-center justify-end">
|
||||
|
||||
@@ -117,6 +117,12 @@ async function createTimeEntry(timeEntry: Omit<CreateTimeEntryBody, 'member_id'>
|
||||
showManualTimeEntryModal.value = false;
|
||||
}
|
||||
|
||||
async function createTimeEntryFromCurrentEntry() {
|
||||
const { start, end, description, project_id, task_id, billable, tags } = currentTimeEntry.value;
|
||||
await createTimeEntry({ start, end, description, project_id, task_id, billable, tags });
|
||||
currentTimeEntryStore.$reset();
|
||||
}
|
||||
|
||||
const { handleApiRequestNotifications } = useNotificationsStore();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
@@ -195,7 +201,8 @@ const { tags } = storeToRefs(useTagsStore());
|
||||
@stop-live-timer="stopLiveTimer"
|
||||
@start-timer="setActiveState(true)"
|
||||
@stop-timer="setActiveState(false)"
|
||||
@update-time-entry="updateTimeEntry"></TimeTrackerControls>
|
||||
@update-time-entry="updateTimeEntry"
|
||||
@create-time-entry="createTimeEntryFromCurrentEntry"></TimeTrackerControls>
|
||||
</div>
|
||||
<TimeTrackerMoreOptionsDropdown
|
||||
:has-active-timer="isActive"
|
||||
|
||||
@@ -93,6 +93,7 @@ const inputValue = ref(model.value ? getLocalizedDayJs(model.value).format('HH:m
|
||||
data-testid="time_picker_input"
|
||||
type="text"
|
||||
@blur="updateTime"
|
||||
@keydown.enter.prevent="updateTime"
|
||||
@focus="($event.target as HTMLInputElement).select()"
|
||||
@mouseup="($event.target as HTMLInputElement).select()"
|
||||
@click="($event.target as HTMLInputElement).select()"
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { defineProps, nextTick, ref, watch } from 'vue';
|
||||
import { useFocusWithin } from '@vueuse/core';
|
||||
import DatePicker from '@/packages/ui/src/Input/DatePicker.vue';
|
||||
import { getDayJsInstance, getLocalizedDayJs } from '@/packages/ui/src/utils/time';
|
||||
import dayjs from 'dayjs';
|
||||
import TimePickerSimple from '@/packages/ui/src/Input/TimePickerSimple.vue';
|
||||
import { Button } from '@/Components/ui/button';
|
||||
|
||||
const props = defineProps<{
|
||||
start: string;
|
||||
@@ -17,31 +17,42 @@ const emit = defineEmits(['changed', 'close']);
|
||||
|
||||
const tempStart = ref(props.start ? getLocalizedDayJs(props.start).format() : dayjs().format());
|
||||
const tempEnd = ref(props.end ? getLocalizedDayJs(props.end).format() : null);
|
||||
const showEndTimePicker = ref(false);
|
||||
|
||||
watch(props, () => {
|
||||
tempStart.value = getLocalizedDayJs(props.start).format();
|
||||
tempEnd.value = props.end ? getLocalizedDayJs(props.end).format() : null;
|
||||
showEndTimePicker.value = false;
|
||||
});
|
||||
|
||||
function updateTimeEntry() {
|
||||
const tempStartUtc = getDayJsInstance()(tempStart.value).utc().format();
|
||||
const tempEndUtc = tempEnd.value ? getDayJsInstance()(tempEnd.value).utc().format() : null;
|
||||
|
||||
if (tempStartUtc !== props.start || tempEndUtc !== props.end) {
|
||||
emit(
|
||||
'changed',
|
||||
getDayJsInstance()(tempStart.value).utc().format(),
|
||||
getDayJsInstance()(tempEnd.value).utc().format()
|
||||
tempEnd.value ? getDayJsInstance()(tempEnd.value).utc().format() : null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const dropdownContent = ref();
|
||||
const { focused } = useFocusWithin(dropdownContent);
|
||||
function setEndTime() {
|
||||
showEndTimePicker.value = true;
|
||||
tempEnd.value = getDayJsInstance()().format();
|
||||
}
|
||||
|
||||
watch(focused, (newValue, oldValue) => {
|
||||
if (oldValue === true && newValue === false) {
|
||||
function confirmEndTime() {
|
||||
// wait for the v-model for the end time to update
|
||||
nextTick(() => {
|
||||
updateTimeEntry();
|
||||
}
|
||||
});
|
||||
showEndTimePicker.value = false;
|
||||
emit('close');
|
||||
});
|
||||
}
|
||||
|
||||
const dropdownContent = ref();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -67,7 +78,7 @@ watch(focused, (newValue, oldValue) => {
|
||||
</div>
|
||||
<div class="px-2">
|
||||
<div class="font-semibold text-text-primary text-sm pb-2">End</div>
|
||||
<div v-if="tempEnd !== null" class="space-y-2">
|
||||
<div v-if="end !== null && tempEnd !== null" class="space-y-2">
|
||||
<TimePickerSimple
|
||||
v-model="tempEnd"
|
||||
data-testid="time_entry_range_end"
|
||||
@@ -77,6 +88,22 @@ watch(focused, (newValue, oldValue) => {
|
||||
class="text-xs text-text-tertiary max-w-24 px-1.5 py-1.5"
|
||||
@changed="updateTimeEntry"></DatePicker>
|
||||
</div>
|
||||
<div v-else-if="end === null && !showEndTimePicker">
|
||||
<Button variant="outline" size="sm" @click="setEndTime"> Set End Time </Button>
|
||||
</div>
|
||||
<div v-else-if="showEndTimePicker && tempEnd !== null" class="space-y-2">
|
||||
<TimePickerSimple
|
||||
v-model="tempEnd"
|
||||
data-testid="time_entry_range_end"
|
||||
@keydown.enter.prevent.stop="confirmEndTime"></TimePickerSimple>
|
||||
<DatePicker
|
||||
v-model="tempEnd"
|
||||
class="text-xs text-text-tertiary max-w-24 px-1.5 py-1.5"
|
||||
@keydown.enter.prevent="confirmEndTime"></DatePicker>
|
||||
<Button variant="outline" size="sm" class="w-full" @click="confirmEndTime">
|
||||
Confirm
|
||||
</Button>
|
||||
</div>
|
||||
<div v-else class="text-text-secondary">-- : --</div>
|
||||
<div tabindex="0" @focusin="emit('close')"></div>
|
||||
</div>
|
||||
|
||||
@@ -49,6 +49,7 @@ const emit = defineEmits<{
|
||||
updateTimeEntry: [];
|
||||
startLiveTimer: [];
|
||||
stopLiveTimer: [];
|
||||
createTimeEntry: [];
|
||||
}>();
|
||||
|
||||
function updateProject() {
|
||||
@@ -280,6 +281,7 @@ useSelectEvents(
|
||||
@stop-live-timer="emit('stopLiveTimer')"
|
||||
@update-timer="emit('updateTimeEntry')"
|
||||
@start-timer="emit('startTimer')"
|
||||
@create-time-entry="emit('createTimeEntry')"
|
||||
@keydown.enter="startTimerIfNotActive"></TimeTrackerRangeSelector>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -16,6 +16,7 @@ const emit = defineEmits<{
|
||||
stopLiveTimer: [];
|
||||
updateTimer: [];
|
||||
startTimer: [];
|
||||
createTimeEntry: [];
|
||||
}>();
|
||||
|
||||
const open = ref(false);
|
||||
@@ -73,12 +74,16 @@ function updateTimerAndStartLiveTimerUpdate() {
|
||||
|
||||
const temporaryCustomTimerEntry = ref<string>('');
|
||||
|
||||
async function updateTimeRange(newStart: string) {
|
||||
async function updateTimeRange(newStart: string, newEnd: string | null) {
|
||||
// prohibit updates in the future
|
||||
if (getDayJsInstance()(newStart).isBefore(getDayJsInstance()())) {
|
||||
currentTimeEntry.value.start = newStart;
|
||||
currentTimeEntry.value.end = newEnd;
|
||||
if (currentTimeEntry.value.id) {
|
||||
emit('updateTimer');
|
||||
} else if (newEnd !== null) {
|
||||
// If there's no ID but we have both start and end, create a new time entry
|
||||
emit('createTimeEntry');
|
||||
} else {
|
||||
emit('startTimer');
|
||||
}
|
||||
@@ -91,6 +96,14 @@ const startTime = computed(() => {
|
||||
}
|
||||
return dayjs().utc().format();
|
||||
});
|
||||
|
||||
const endTime = computed(() => {
|
||||
if (currentTimeEntry.value.end && currentTimeEntry.value.end !== '') {
|
||||
return currentTimeEntry.value.end;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
const inputField = ref<HTMLInputElement | null>(null);
|
||||
|
||||
const timeRangeSelector = ref<HTMLElement | null>(null);
|
||||
@@ -154,7 +167,7 @@ function closeAndFocusInput() {
|
||||
<div ref="timeRangeSelector">
|
||||
<TimeRangeSelector
|
||||
:start="startTime"
|
||||
:end="null"
|
||||
:end="endTime"
|
||||
@changed="updateTimeRange"
|
||||
@close="closeAndFocusInput">
|
||||
</TimeRangeSelector>
|
||||
|
||||
@@ -162,7 +162,7 @@ export const useCurrentTimeEntryStore = defineStore('currentTimeEntry', () => {
|
||||
task_id: currentTimeEntry.value.task_id,
|
||||
start: currentTimeEntry.value.start,
|
||||
billable: currentTimeEntry.value.billable,
|
||||
end: null,
|
||||
end: currentTimeEntry.value.end,
|
||||
tags: currentTimeEntry.value.tags,
|
||||
},
|
||||
{
|
||||
@@ -175,7 +175,12 @@ export const useCurrentTimeEntryStore = defineStore('currentTimeEntry', () => {
|
||||
'Time entry updated!'
|
||||
);
|
||||
if (response?.data) {
|
||||
currentTimeEntry.value = response.data;
|
||||
if (response.data.end === null) {
|
||||
currentTimeEntry.value = response.data;
|
||||
} else {
|
||||
$reset();
|
||||
stopLiveTimer();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
@@ -215,5 +220,6 @@ export const useCurrentTimeEntryStore = defineStore('currentTimeEntry', () => {
|
||||
stopLiveTimer,
|
||||
now,
|
||||
setActiveState,
|
||||
$reset,
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user