mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-16 20:22:15 +01:00
add manual time entry feature
This commit is contained in:
@@ -461,3 +461,5 @@ test('test that load more works when the end of page is reached', async ({
|
|||||||
// TODO: Test for resume button click works with project / task
|
// TODO: Test for resume button click works with project / task
|
||||||
|
|
||||||
// TODO: Test that time entries are loaded at the end of the page
|
// TODO: Test that time entries are loaded at the end of the page
|
||||||
|
|
||||||
|
// TODO: Test manual time entries
|
||||||
|
|||||||
@@ -4,20 +4,21 @@
|
|||||||
|
|
||||||
|
|
||||||
:root{
|
:root{
|
||||||
--theme-color-default-background: #040618;
|
--theme-color-default-background: #0b0d1c;
|
||||||
--theme-color-icon-default: #42466C;
|
--theme-color-icon-default: #42466C;
|
||||||
--theme-color-card-background: #13152B;
|
--theme-color-card-background: #13152B;
|
||||||
--theme-color-card-background-active: #1C1E34;
|
--theme-color-card-background-active: #1C1E34;
|
||||||
--theme-color-card-border: #242940;
|
--theme-color-card-background-separator: #1c2033;
|
||||||
|
--theme-color-card-border: #1c2033;
|
||||||
--theme-color-card-border-active: #2A3461;
|
--theme-color-card-border-active: #2A3461;
|
||||||
|
--theme-color-default-background-separator: #141a2f;
|
||||||
--theme-color-tab-background: var(--theme-color-card-background);
|
--theme-color-tab-background: var(--theme-color-card-background);
|
||||||
--theme-color-tab-background-active: var(--theme-color-card-background-active);
|
--theme-color-tab-background-active: var(--theme-color-card-background-active);
|
||||||
--theme-color-tab-border: var(--theme-color-card-border);
|
--theme-color-tab-border: var(--theme-color-card-border);
|
||||||
--theme-color-row-separator-background: var(--theme-color-card-border);
|
--theme-color-row-separator-background: var(--theme-color-default-background-separator);
|
||||||
--theme-color-row-heading-background: var(--theme-color-card-background);
|
--theme-color-row-heading-background: var(--theme-color-card-background);
|
||||||
--theme-color-row-border: var(--theme-color-card-border);
|
--theme-color-row-border: var(--theme-color-card-border);
|
||||||
--theme-color-row-heading-border: var(--theme-color-card-border);
|
--theme-color-row-heading-border: var(--theme-color-card-border);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
*{
|
*{
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { computed } from 'vue';
|
|||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
size: 'base' | 'large';
|
size: 'base' | 'large' | 'xlarge';
|
||||||
tag: string;
|
tag: string;
|
||||||
class?: string;
|
class?: string;
|
||||||
color: string;
|
color: string;
|
||||||
@@ -21,6 +21,7 @@ const props = withDefaults(
|
|||||||
const badgeClasses = {
|
const badgeClasses = {
|
||||||
base: 'py-1 px-2 space-x-1.5 text-xs',
|
base: 'py-1 px-2 space-x-1.5 text-xs',
|
||||||
large: 'py-1 sm:py-1.5 px-2 sm:px-3 space-x-1.5 sm:space-x-2 text-xs sm:text-sm text-muted',
|
large: 'py-1 sm:py-1.5 px-2 sm:px-3 space-x-1.5 sm:space-x-2 text-xs sm:text-sm text-muted',
|
||||||
|
xlarge: 'py-2 sm:py-2.5 px-3 sm:px-3.5 space-x-2 sm:space-x-3 text-sm sm:text-sm text-muted',
|
||||||
};
|
};
|
||||||
|
|
||||||
const borderClasses = computed(() => {
|
const borderClasses = computed(() => {
|
||||||
|
|||||||
52
resources/js/Components/Common/DatePicker.vue
Normal file
52
resources/js/Components/Common/DatePicker.vue
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
import { getLocalizedDayJs } from '@/utils/time';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
const model = defineModel<string | null>({
|
||||||
|
default: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
function updateDate(event: Event) {
|
||||||
|
const target = event.target as HTMLInputElement;
|
||||||
|
const newValue = target.value;
|
||||||
|
const newDate = dayjs(newValue);
|
||||||
|
if (newDate) {
|
||||||
|
console.log('old', model.value);
|
||||||
|
model.value = getLocalizedDayJs(model.value)
|
||||||
|
.set('year', newDate.year())
|
||||||
|
.set('day', newDate.day())
|
||||||
|
.set('month', newDate.month())
|
||||||
|
.utc()
|
||||||
|
.format();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const date = computed(() => {
|
||||||
|
return model.value
|
||||||
|
? getLocalizedDayJs(model.value).format('YYYY-MM-DD')
|
||||||
|
: null;
|
||||||
|
});
|
||||||
|
|
||||||
|
const datePicker = ref<HTMLInputElement | null>(null);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex items-center justify-center text-muted">
|
||||||
|
<input
|
||||||
|
ref="datePicker"
|
||||||
|
@change="updateDate"
|
||||||
|
class="bg-input-background border text-white border-input-border rounded-md"
|
||||||
|
type="date"
|
||||||
|
id="start"
|
||||||
|
name="trip-start"
|
||||||
|
:value="date" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
input::-webkit-calendar-picker-indicator {
|
||||||
|
filter: invert(1);
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -5,7 +5,7 @@ import Badge from '@/Components/Common/Badge.vue';
|
|||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
name: string;
|
name: string;
|
||||||
size: 'base' | 'large';
|
size: 'base' | 'large' | 'xlarge';
|
||||||
tag: string;
|
tag: string;
|
||||||
class?: string;
|
class?: string;
|
||||||
color: string;
|
color: string;
|
||||||
@@ -23,6 +23,7 @@ const props = withDefaults(
|
|||||||
const indicatorClasses = {
|
const indicatorClasses = {
|
||||||
base: 'w-2.5 h-2.5',
|
base: 'w-2.5 h-2.5',
|
||||||
large: 'w-2 sm:w-3 h-2 sm:h-3',
|
large: 'w-2 sm:w-3 h-2 sm:h-3',
|
||||||
|
xlarge: 'w-3 sm:w-4 h-3 sm:h-4',
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,118 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import TextInput from '@/Components/TextInput.vue';
|
||||||
|
import SecondaryButton from '@/Components/SecondaryButton.vue';
|
||||||
|
import DialogModal from '@/Components/DialogModal.vue';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import PrimaryButton from '@/Components/PrimaryButton.vue';
|
||||||
|
import { useFocus } from '@vueuse/core';
|
||||||
|
import TimeTrackerTagDropdown from '@/Components/Common/TimeTracker/TimeTrackerTagDropdown.vue';
|
||||||
|
import TimeTrackerProjectTaskDropdown from '@/Components/Common/TimeTracker/TimeTrackerProjectTaskDropdown.vue';
|
||||||
|
import BillableToggleButton from '@/Components/Common/BillableToggleButton.vue';
|
||||||
|
import { getCurrentUserId } from '@/utils/useUser';
|
||||||
|
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';
|
||||||
|
|
||||||
|
const { createTimeEntry } = useTimeEntriesStore();
|
||||||
|
const show = defineModel('show', { default: false });
|
||||||
|
const saving = ref(false);
|
||||||
|
|
||||||
|
const timeEntryDefaultValues = {
|
||||||
|
description: '',
|
||||||
|
project_id: null,
|
||||||
|
task_id: null,
|
||||||
|
tags: [],
|
||||||
|
billable: false,
|
||||||
|
start: getDayJsInstance().utc().format(),
|
||||||
|
end: getDayJsInstance().utc().format(),
|
||||||
|
user_id: getCurrentUserId(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const timeEntry = ref(timeEntryDefaultValues);
|
||||||
|
|
||||||
|
async function submit() {
|
||||||
|
await createTimeEntry(timeEntry.value);
|
||||||
|
show.value = false;
|
||||||
|
timeEntry.value = timeEntryDefaultValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
const projectNameInput = ref<HTMLInputElement | null>(null);
|
||||||
|
|
||||||
|
useFocus(projectNameInput, { initialValue: true });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DialogModal closeable :show="show" @close="show = false">
|
||||||
|
<template #title>
|
||||||
|
<div class="flex space-x-2">
|
||||||
|
<span> Create manual time entry </span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #content>
|
||||||
|
<div class="sm:flex items-end space-y-2 sm:space-y-0 sm:space-x-4">
|
||||||
|
<div class="flex-1">
|
||||||
|
<InputLabel for="description" value="Description" />
|
||||||
|
<TextInput
|
||||||
|
id="description"
|
||||||
|
v-model="timeEntry.description"
|
||||||
|
@keydown.enter="submit"
|
||||||
|
type="text"
|
||||||
|
class="mt-1 block w-full"
|
||||||
|
autofocus />
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<TimeTrackerProjectTaskDropdown
|
||||||
|
class="mt-1"
|
||||||
|
size="xlarge"
|
||||||
|
v-model:project="timeEntry.project_id"
|
||||||
|
v-model:task="
|
||||||
|
timeEntry.task_id
|
||||||
|
"></TimeTrackerProjectTaskDropdown>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center space-x-2 px-4">
|
||||||
|
<TimeTrackerTagDropdown
|
||||||
|
v-model="timeEntry.tags"></TimeTrackerTagDropdown>
|
||||||
|
<BillableToggleButton
|
||||||
|
v-model="timeEntry.billable"></BillableToggleButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex pt-4">
|
||||||
|
<div class="flex-1">
|
||||||
|
<InputLabel>Start</InputLabel>
|
||||||
|
<div class="flex items-center space-x-4 mt-1">
|
||||||
|
<DatePicker v-model="timeEntry.start"></DatePicker>
|
||||||
|
<TimePicker
|
||||||
|
size="large"
|
||||||
|
v-model="timeEntry.start"></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>
|
||||||
|
<TimePicker
|
||||||
|
size="large"
|
||||||
|
v-model="timeEntry.end"></TimePicker>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<SecondaryButton @click="show = false"> Cancel</SecondaryButton>
|
||||||
|
<PrimaryButton
|
||||||
|
class="ms-3"
|
||||||
|
:class="{ 'opacity-25': saving }"
|
||||||
|
:disabled="saving"
|
||||||
|
@click="submit">
|
||||||
|
Create Time Entry
|
||||||
|
</PrimaryButton>
|
||||||
|
</template>
|
||||||
|
</DialogModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
@@ -80,7 +80,7 @@ function updateProjectAndTask(projectId: string, taskId: string) {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="border-b border-card-border transition"
|
class="border-b border-default-background-separator transition"
|
||||||
data-testid="time_entry_row">
|
data-testid="time_entry_row">
|
||||||
<MainContainer>
|
<MainContainer>
|
||||||
<div class="sm:flex py-1.5 items-center justify-between group">
|
<div class="sm:flex py-1.5 items-center justify-between group">
|
||||||
|
|||||||
@@ -1,11 +1,21 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { getLocalizedDayJs } from '@/utils/time';
|
import { getLocalizedDayJs } from '@/utils/time';
|
||||||
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
const model = defineModel<string | null>({
|
const model = defineModel<string | null>({
|
||||||
default: null,
|
default: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
size: 'base' | 'large';
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
size: 'base',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const hours = computed(() => {
|
const hours = computed(() => {
|
||||||
return model.value ? getLocalizedDayJs(model.value).hour() : null;
|
return model.value ? getLocalizedDayJs(model.value).hour() : null;
|
||||||
});
|
});
|
||||||
@@ -38,20 +48,38 @@ function updateHours(event: Event) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex items-center justify-center text-muted">
|
<div class="flex items-center justify-center text-white">
|
||||||
<input
|
<div
|
||||||
:value="hours"
|
:class="
|
||||||
@input="updateHours"
|
twMerge(
|
||||||
data-testid="time_picker_hour"
|
'border bg-input-background rounded-md border-input-border overflow-hidden',
|
||||||
type="text"
|
props.size === 'large' ? 'py-1.5 px-2' : ''
|
||||||
class="bg-card-background border-none text-sm px-1 py-0.5 w-[30px] text-center focus:ring-0 focus:bg-card-background-active" />
|
)
|
||||||
<span>:</span>
|
">
|
||||||
<input
|
<input
|
||||||
:value="minutes"
|
:value="hours"
|
||||||
@input="updateMinutes"
|
@input="updateHours"
|
||||||
data-testid="time_picker_minute"
|
data-testid="time_picker_hour"
|
||||||
type="text"
|
type="text"
|
||||||
class="bg-card-background border-none text-sm px-1 py-0.5 w-[30px] text-center focus:ring-0 focus:bg-card-background-active" />
|
:class="
|
||||||
|
twMerge(
|
||||||
|
'border-none bg-transparent px-1 py-0.5 w-[30px] text-center focus:ring-0 focus:bg-card-background-active',
|
||||||
|
props.size === 'large' ? 'text-base' : 'text-sm'
|
||||||
|
)
|
||||||
|
" />
|
||||||
|
<span>:</span>
|
||||||
|
<input
|
||||||
|
:value="minutes"
|
||||||
|
@input="updateMinutes"
|
||||||
|
data-testid="time_picker_minute"
|
||||||
|
type="text"
|
||||||
|
:class="
|
||||||
|
twMerge(
|
||||||
|
'border-none bg-transparent px-1 py-0.5 w-[30px] text-center focus:ring-0 focus:bg-card-background-active',
|
||||||
|
props.size === 'large' ? 'text-base' : 'text-sm'
|
||||||
|
)
|
||||||
|
" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -45,9 +45,11 @@ type ProjectWithTasks = {
|
|||||||
withDefaults(
|
withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
showBadgeBorder: boolean;
|
showBadgeBorder: boolean;
|
||||||
|
size: 'base' | 'large' | 'xlarge';
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
showBadgeBorder: true,
|
showBadgeBorder: true,
|
||||||
|
size: 'large',
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -298,7 +300,7 @@ function selectProject(projectId: string) {
|
|||||||
<ProjectBadge
|
<ProjectBadge
|
||||||
ref="projectDropdownTrigger"
|
ref="projectDropdownTrigger"
|
||||||
:color="selectedProjectColor"
|
:color="selectedProjectColor"
|
||||||
size="large"
|
:size="size"
|
||||||
:border="showBadgeBorder"
|
:border="showBadgeBorder"
|
||||||
tag="button"
|
tag="button"
|
||||||
:name="selectedProjectName"
|
:name="selectedProjectName"
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ const props = defineProps<{
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="props.latestTasks.length === 1"
|
v-if="props.latestTasks.length === 1"
|
||||||
class="text-center flex flex-1 justify-center items-center">
|
class="text-center flex flex-1 justify-center items-center text-sm">
|
||||||
<div>
|
<div>
|
||||||
<PlusCircleIcon
|
<PlusCircleIcon
|
||||||
class="w-8 text-icon-default inline pb-2"></PlusCircleIcon>
|
class="w-8 text-icon-default inline pb-2"></PlusCircleIcon>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
aria-live="assertive"
|
aria-live="assertive"
|
||||||
class="pointer-events-none fixed inset-0 flex items-end px-4 py-6 sm:items-end sm:p-6">
|
class="pointer-events-none fixed inset-0 flex items-end px-4 py-6 sm:items-end sm:p-6 sm:pb-24 z-[70]">
|
||||||
<div class="flex w-full flex-col items-center space-y-4 sm:items-end">
|
<div class="flex w-full flex-col items-center space-y-4 sm:items-end">
|
||||||
<Notification
|
<Notification
|
||||||
v-for="notification in notifications"
|
v-for="notification in notifications"
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ import TimeEntryRow from '@/Components/Common/TimeEntry/TimeEntryRow.vue';
|
|||||||
import { useElementVisibility } from '@vueuse/core';
|
import { useElementVisibility } from '@vueuse/core';
|
||||||
import { ClockIcon } from '@heroicons/vue/20/solid';
|
import { ClockIcon } from '@heroicons/vue/20/solid';
|
||||||
import { getLocalizedDateFromTimestamp } from '@/utils/time';
|
import { getLocalizedDateFromTimestamp } from '@/utils/time';
|
||||||
|
import SecondaryButton from '@/Components/SecondaryButton.vue';
|
||||||
|
import { PlusIcon } from '@heroicons/vue/16/solid';
|
||||||
|
import TimeEntryCreateModal from '@/Components/Common/TimeEntry/TimeEntryCreateModal.vue';
|
||||||
|
|
||||||
const timeEntriesStore = useTimeEntriesStore();
|
const timeEntriesStore = useTimeEntriesStore();
|
||||||
const { timeEntries, allTimeEntriesLoaded } = storeToRefs(timeEntriesStore);
|
const { timeEntries, allTimeEntriesLoaded } = storeToRefs(timeEntriesStore);
|
||||||
@@ -44,13 +47,33 @@ const groupedTimeEntries = computed(() => {
|
|||||||
}
|
}
|
||||||
return groupedEntries;
|
return groupedEntries;
|
||||||
});
|
});
|
||||||
|
const showManualTimeEntryModal = ref(false);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<TimeEntryCreateModal
|
||||||
|
v-model:show="showManualTimeEntryModal"></TimeEntryCreateModal>
|
||||||
<AppLayout title="Dashboard" data-testid="time_view">
|
<AppLayout title="Dashboard" data-testid="time_view">
|
||||||
<MainContainer
|
<MainContainer
|
||||||
class="pt-5 sm:pt-8 pb-4 sm:pb-6 border-b border-default-background-separator">
|
class="pt-5 sm:pt-8 pb-4 sm:pb-6 border-b border-default-background-separator">
|
||||||
<TimeTracker></TimeTracker>
|
<div
|
||||||
|
class="flex items-end divide-x divide-default-background-separator space-x-2">
|
||||||
|
<div class="flex-1">
|
||||||
|
<TimeTracker></TimeTracker>
|
||||||
|
</div>
|
||||||
|
<div class="pb-2 pl-4 flex justify-center">
|
||||||
|
<SecondaryButton
|
||||||
|
@click="showManualTimeEntryModal = true"
|
||||||
|
:icon="PlusIcon"
|
||||||
|
>Manual time entry</SecondaryButton
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</MainContainer>
|
||||||
|
<MainContainer>
|
||||||
|
<div class="flex justify-between py-2 items-center">
|
||||||
|
<div class="text-sm">0 selected</div>
|
||||||
|
</div>
|
||||||
</MainContainer>
|
</MainContainer>
|
||||||
<div v-for="(value, key) in groupedTimeEntries" :key="key">
|
<div v-for="(value, key) in groupedTimeEntries" :key="key">
|
||||||
<TimeEntryRowHeading :date="key"></TimeEntryRowHeading>
|
<TimeEntryRowHeading :date="key"></TimeEntryRowHeading>
|
||||||
|
|||||||
@@ -22,6 +22,11 @@ export type TimeEntryResponse = ZodiosResponseByAlias<
|
|||||||
>;
|
>;
|
||||||
export type TimeEntry = TimeEntryResponse['data'][0];
|
export type TimeEntry = TimeEntryResponse['data'][0];
|
||||||
|
|
||||||
|
export type CreateTimeEntryBody = ZodiosBodyByAlias<
|
||||||
|
SolidTimeApi,
|
||||||
|
'createTimeEntry'
|
||||||
|
>;
|
||||||
|
|
||||||
export type ProjectResponse = ZodiosResponseByAlias<
|
export type ProjectResponse = ZodiosResponseByAlias<
|
||||||
SolidTimeApi,
|
SolidTimeApi,
|
||||||
'getProjects'
|
'getProjects'
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export const useNotificationsStore = defineStore('notifications', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
throw new Error('Failed to handle API request');
|
||||||
}
|
}
|
||||||
|
|
||||||
return { addNotification, notifications, handleApiRequestNotifications };
|
return { addNotification, notifications, handleApiRequestNotifications };
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { defineStore } from 'pinia';
|
|||||||
import { getCurrentOrganizationId, getCurrentUserId } from '@/utils/useUser';
|
import { getCurrentOrganizationId, getCurrentUserId } from '@/utils/useUser';
|
||||||
import { api } from '../../../openapi.json.client';
|
import { api } from '../../../openapi.json.client';
|
||||||
import { reactive, ref } from 'vue';
|
import { reactive, ref } from 'vue';
|
||||||
import type { TimeEntry } from '@/utils/api';
|
import type { CreateTimeEntryBody, TimeEntry } from '@/utils/api';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { useNotificationsStore } from '@/utils/notification';
|
import { useNotificationsStore } from '@/utils/notification';
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ export const useTimeEntriesStore = defineStore('timeEntries', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createTimeEntry(timeEntry: TimeEntry) {
|
async function createTimeEntry(timeEntry: CreateTimeEntryBody) {
|
||||||
const organizationId = getCurrentOrganizationId();
|
const organizationId = getCurrentOrganizationId();
|
||||||
if (organizationId) {
|
if (organizationId) {
|
||||||
await handleApiRequestNotifications(
|
await handleApiRequestNotifications(
|
||||||
|
|||||||
Reference in New Issue
Block a user