added success and error notifications

This commit is contained in:
Gregor Vostrak
2024-04-12 03:19:58 +02:00
parent 0e96ad992f
commit 64023f3930
14 changed files with 508 additions and 208 deletions

View File

@@ -0,0 +1,62 @@
<template>
<!-- Global notification live region, render this permanently at the end of the document -->
<!-- Notification panel, dynamically insert this into the live region when it needs to be displayed -->
<transition
enter-active-class="transform ease-out duration-300 transition"
enter-from-class="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2"
enter-to-class="translate-y-0 opacity-100 sm:translate-x-0"
leave-active-class="transition ease-in duration-100"
leave-from-class="opacity-100"
leave-to-class="opacity-0">
<div
v-if="show"
class="pointer-events-auto w-full max-w-sm overflow-hidden rounded-lg border border-card-border bg-card-background shadow-lg ring-1 ring-black text-white ring-opacity-5">
<div class="p-4">
<div class="flex items-start">
<div class="flex-shrink-0">
<CheckCircleIcon
v-if="type === 'success'"
class="h-6 w-6 text-green-400"
aria-hidden="true" />
<XCircleIcon
v-if="type === 'error'"
class="h-6 w-6 text-red-400"
aria-hidden="true" />
</div>
<div class="ml-3 w-0 flex-1 pt-0.5">
<p class="text-sm font-medium text-white">
{{ title }}
</p>
<p v-if="message" class="mt-1 text-sm text-muted">
{{ message }}
</p>
</div>
<div class="ml-4 flex flex-shrink-0">
<button
type="button"
@click="show = false"
class="inline-flex rounded-md bg-card-background text-muted hover:text-white focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
<span class="sr-only">Close</span>
<XMarkIcon class="h-5 w-5" aria-hidden="true" />
</button>
</div>
</div>
</div>
</div>
</transition>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { CheckCircleIcon, XCircleIcon } from '@heroicons/vue/24/outline';
import { XMarkIcon } from '@heroicons/vue/20/solid';
import type { NotificationType } from '@/utils/notification';
defineProps<{
title: string;
type: NotificationType;
message?: string;
}>();
const show = ref(true);
</script>

View File

@@ -61,7 +61,9 @@ const filteredTags = computed(() => {
async function addTagIfNoneExists() {
if (searchValue.value.length > 0 && filteredTags.value.length === 0) {
const newTag = await tagsStore.createTag(searchValue.value);
addOrRemoveTagFromSelection(newTag.id);
if (newTag) {
addOrRemoveTagFromSelection(newTag.id);
}
searchValue.value = '';
} else {
if (highlightedItemId.value) {

View File

@@ -0,0 +1,22 @@
<template>
<div
aria-live="assertive"
class="pointer-events-none fixed inset-0 flex items-end px-4 py-6 sm:items-end sm:p-6">
<div class="flex w-full flex-col items-center space-y-4 sm:items-end">
<Notification
v-for="notification in notifications"
:type="notification.type"
:key="notification.uuid"
:title="notification.title"
:message="notification.message"></Notification>
</div>
</div>
</template>
<script setup lang="ts">
import Notification from '@/Components/Common/Notification/Notification.vue';
import { storeToRefs } from 'pinia';
import { useNotificationsStore } from '@/utils/notification';
const { notifications } = storeToRefs(useNotificationsStore());
</script>

View File

@@ -23,6 +23,7 @@ import { useTasksStore } from '@/utils/useTasks';
import { useCurrentTimeEntryStore } from '@/utils/useCurrentTimeEntry';
import { useClientsStore } from '@/utils/useClients';
import { useMembersStore } from '@/utils/useMembers';
import NotificationContainer from '@/Components/NotificationContainer.vue';
defineProps({
title: String,
@@ -39,7 +40,9 @@ onMounted(async () => {
</script>
<template>
<div class="flex flex-wrap bg-default-background text-muted">
<div
v-bind="$attrs"
class="flex flex-wrap bg-default-background text-muted">
<div
class="flex-shrink-0 h-screen fixed w-[230px] 2xl:w-[270px] px-2.5 2xl:px-4 py-4 flex flex-col justify-between">
<div>
@@ -137,4 +140,5 @@ onMounted(async () => {
</div>
</div>
</div>
<NotificationContainer></NotificationContainer>
</template>

View File

@@ -47,7 +47,7 @@ const groupedTimeEntries = computed(() => {
</script>
<template>
<AppLayout title="Dashboard" data-testid="dashboard_view">
<AppLayout title="Dashboard" data-testid="time_view">
<MainContainer
class="py-8 border-b border-default-background-separator">
<TimeTracker></TimeTracker>

View File

@@ -0,0 +1,83 @@
import { defineStore } from 'pinia';
import { ref } from 'vue';
import axios from 'axios';
export type NotificationType = 'success' | 'error';
export const useNotificationsStore = defineStore('notifications', () => {
const notifications = ref<
{
title: string;
message?: string;
uuid: string;
type: NotificationType;
}[]
>([]);
function addNotification(
type: NotificationType,
title: string,
message?: string
) {
const uuid = Math.random().toString(36).substring(7);
notifications.value.push({ title, message, type, uuid });
setTimeout(() => {
removeNotification(uuid);
}, 5000);
}
function removeNotification(uuid: string) {
const index = notifications.value.findIndex(
(notification) => notification.uuid === uuid
);
if (index !== -1) {
notifications.value.splice(index, 1);
}
}
async function handleApiRequestNotifications<T>(
apiRequest: Promise<T>,
successMessage?: string,
errorMessage?: string
) {
try {
const response = await apiRequest;
if (successMessage) {
addNotification('success', successMessage);
}
return response;
} catch (error) {
if (axios.isAxiosError(error)) {
if (
error?.response?.status === 403 ||
(error?.response?.status === 400 &&
error?.response?.data?.error === true &&
error?.response?.data?.errorMessage !== undefined)
) {
addNotification(
'error',
errorMessage ?? 'Request Error',
error.response?.data?.errorMessage ??
error?.response?.data?.message ??
'An request error occurred. Please try again later.'
);
} else if (error?.response?.status === 422) {
const message = error.response.data.errors
.map((error: { message: string }) => {
return error.message;
})
.join('\n');
addNotification('error', message);
} else {
addNotification(
'error',
'The action failed. Please try again later.'
);
}
}
}
return null;
}
return { addNotification, notifications, handleApiRequestNotifications };
});

View File

@@ -7,18 +7,24 @@ import type {
Client,
} from '@/utils/api';
import { getCurrentOrganizationId } from '@/utils/useUser';
import { useNotificationsStore } from '@/utils/notification';
export const useClientsStore = defineStore('clients', () => {
const clientResponse = ref<ClientIndexResponse | null>(null);
const { handleApiRequestNotifications } = useNotificationsStore();
async function fetchClients() {
const organization = getCurrentOrganizationId();
if (organization) {
clientResponse.value = await api.getClients({
params: {
organization: organization,
},
});
clientResponse.value = await handleApiRequestNotifications(
api.getClients({
params: {
organization: organization,
},
}),
undefined,
'Failed to fetch clients'
);
}
}
@@ -27,27 +33,35 @@ export const useClientsStore = defineStore('clients', () => {
): Promise<Client | undefined> {
const organization = getCurrentOrganizationId();
if (organization) {
const response = await api.createClient(clientBody, {
params: {
organization: organization,
},
});
const response = await handleApiRequestNotifications(
api.createClient(clientBody, {
params: {
organization: organization,
},
}),
'Client created successfully',
'Failed to create client'
);
await fetchClients();
return response.data;
return response?.data;
}
}
async function deleteClient(clientId: string) {
const organization = getCurrentOrganizationId();
if (organization) {
await api.deleteClient(
{},
{
params: {
organization: organization,
client: clientId,
},
}
await handleApiRequestNotifications(
api.deleteClient(
{},
{
params: {
organization: organization,
client: clientId,
},
}
),
'Client deleted successfully',
'Failed to delete client'
);
await fetchClients();
}

View File

@@ -7,6 +7,7 @@ import utc from 'dayjs/plugin/utc';
import { getCurrentOrganizationId, getCurrentUserId } from '@/utils/useUser';
import { useLocalStorage } from '@vueuse/core';
import { useTimeEntriesStore } from '@/utils/useTimeEntries';
import { useNotificationsStore } from '@/utils/notification';
dayjs.extend(utc);
@@ -25,6 +26,7 @@ const emptyTimeEntry = {
export const useCurrentTimeEntryStore = defineStore('currentTimeEntry', () => {
const currentTimeEntry = ref<TimeEntry>(reactive(emptyTimeEntry));
const { handleApiRequestNotifications } = useNotificationsStore();
useLocalStorage('solidtime/current-time-entry', currentTimeEntry, {
deep: true,
@@ -54,19 +56,22 @@ export const useCurrentTimeEntryStore = defineStore('currentTimeEntry', () => {
async function fetchCurrentTimeEntry() {
const organizationId = getCurrentOrganizationId();
if (organizationId) {
const timeEntriesResponse = await api.getTimeEntries({
queries: {
active: 'true',
},
params: {
organization: organizationId,
},
});
if (timeEntriesResponse.data.length === 1) {
currentTimeEntry.value = timeEntriesResponse.data[0];
} else {
currentTimeEntry.value = { ...emptyTimeEntry };
const timeEntriesResponse = await handleApiRequestNotifications(
api.getTimeEntries({
queries: {
active: 'true',
},
params: {
organization: organizationId,
},
})
);
if (timeEntriesResponse?.data) {
if (timeEntriesResponse.data.length === 1) {
currentTimeEntry.value = timeEntriesResponse.data[0];
} else {
currentTimeEntry.value = { ...emptyTimeEntry };
}
}
} else {
throw new Error(
@@ -83,19 +88,24 @@ export const useCurrentTimeEntryStore = defineStore('currentTimeEntry', () => {
currentTimeEntry.value.start !== ''
? currentTimeEntry.value.start
: dayjs().utc().format();
const response = await api.createTimeEntry(
{
user_id: user,
start: startTime,
description: currentTimeEntry.value?.description,
project_id: currentTimeEntry.value?.project_id,
task_id: currentTimeEntry.value?.task_id,
billable: currentTimeEntry.value.billable,
tags: currentTimeEntry.value?.tags,
},
{ params: { organization: organization } }
const response = await handleApiRequestNotifications(
api.createTimeEntry(
{
user_id: user,
start: startTime,
description: currentTimeEntry.value?.description,
project_id: currentTimeEntry.value?.project_id,
task_id: currentTimeEntry.value?.task_id,
billable: currentTimeEntry.value.billable,
tags: currentTimeEntry.value?.tags,
},
{ params: { organization: organization } }
),
'Timer started!'
);
currentTimeEntry.value = response.data;
if (response?.data) {
currentTimeEntry.value = response.data;
}
} else {
throw new Error(
'Failed to fetch current time entry because organization ID is missing.'
@@ -108,18 +118,21 @@ export const useCurrentTimeEntryStore = defineStore('currentTimeEntry', () => {
const organization = getCurrentOrganizationId();
if (organization) {
const currentDateTime = dayjs().utc().format();
await api.updateTimeEntry(
{
user_id: user,
start: currentTimeEntry.value.start,
end: currentDateTime,
},
{
params: {
organization: organization,
timeEntry: currentTimeEntry.value.id,
await handleApiRequestNotifications(
api.updateTimeEntry(
{
user_id: user,
start: currentTimeEntry.value.start,
end: currentDateTime,
},
}
{
params: {
organization: organization,
timeEntry: currentTimeEntry.value.id,
},
}
),
'Timer stopped!'
);
$reset();
} else {
@@ -133,25 +146,30 @@ export const useCurrentTimeEntryStore = defineStore('currentTimeEntry', () => {
const user = getCurrentUserId();
const organization = getCurrentOrganizationId();
if (organization) {
await api.updateTimeEntry(
{
description: currentTimeEntry.value.description,
user_id: user,
project_id: currentTimeEntry.value.project_id,
task_id: currentTimeEntry.value.task_id,
start: currentTimeEntry.value.start,
billable: currentTimeEntry.value.billable,
end: null,
tags: currentTimeEntry.value.tags,
},
{
params: {
organization: organization,
timeEntry: currentTimeEntry.value.id,
const response = await handleApiRequestNotifications(
api.updateTimeEntry(
{
description: currentTimeEntry.value.description,
user_id: user,
project_id: currentTimeEntry.value.project_id,
task_id: currentTimeEntry.value.task_id,
start: currentTimeEntry.value.start,
billable: currentTimeEntry.value.billable,
end: null,
tags: currentTimeEntry.value.tags,
},
}
{
params: {
organization: organization,
timeEntry: currentTimeEntry.value.id,
},
}
),
'Time entry updated!'
);
// currentTimeEntry.value = response.data;
if (response?.data) {
currentTimeEntry.value = response.data;
}
} else {
throw new Error(
'Failed to fetch current time entry because organization ID is missing.'

View File

@@ -3,18 +3,24 @@ import { api } from '../../../openapi.json.client';
import { computed, ref } from 'vue';
import type { Member, MemberIndexResponse } from '@/utils/api';
import { getCurrentOrganizationId } from '@/utils/useUser';
import { useNotificationsStore } from '@/utils/notification';
export const useMembersStore = defineStore('members', () => {
const membersResponse = ref<MemberIndexResponse | null>(null);
const { handleApiRequestNotifications } = useNotificationsStore();
async function fetchMembers() {
const organization = getCurrentOrganizationId();
if (organization) {
membersResponse.value = await api.getMembers({
params: {
organization: organization,
},
});
membersResponse.value = await handleApiRequestNotifications(
api.getMembers({
params: {
organization: organization,
},
}),
undefined,
'Failed to fetch members'
);
}
}

View File

@@ -7,19 +7,25 @@ import type {
ProjectMemberResponse,
} from '@/utils/api';
import { getCurrentOrganizationId } from '@/utils/useUser';
import { useNotificationsStore } from '@/utils/notification';
export const useProjectMembersStore = defineStore('project-members', () => {
const projectMemberResponse = ref<ProjectMemberResponse | null>(null);
const { handleApiRequestNotifications } = useNotificationsStore();
async function fetchProjectMembers(projectId: string) {
const organization = getCurrentOrganizationId();
if (organization) {
projectMemberResponse.value = await api.getProjectMembers({
params: {
organization: organization,
project: projectId,
},
});
projectMemberResponse.value = await handleApiRequestNotifications(
api.getProjectMembers({
params: {
organization: organization,
project: projectId,
},
}),
undefined,
'Failed to fetch project members'
);
}
}
@@ -29,12 +35,16 @@ export const useProjectMembersStore = defineStore('project-members', () => {
) {
const organization = getCurrentOrganizationId();
if (organization) {
await api.createProjectMember(projectMemberBody, {
params: {
organization: organization,
project: projectId,
},
});
await handleApiRequestNotifications(
api.createProjectMember(projectMemberBody, {
params: {
organization: organization,
project: projectId,
},
}),
'Project member added successfully',
'Failed to add project member'
);
await fetchProjectMembers(projectId);
}
}
@@ -45,14 +55,18 @@ export const useProjectMembersStore = defineStore('project-members', () => {
) {
const organizationId = getCurrentOrganizationId();
if (organizationId) {
await api.deleteProjectMember(
{},
{
params: {
organization: organizationId,
projectMember: projectMemberId,
},
}
await handleApiRequestNotifications(
api.deleteProjectMember(
{},
{
params: {
organization: organizationId,
projectMember: projectMemberId,
},
}
),
'Project member removed successfully',
'Failed to remove project member'
);
await fetchProjectMembers(projectId);
}

View File

@@ -3,29 +3,39 @@ import { api } from '../../../openapi.json.client';
import { computed, ref } from 'vue';
import type { CreateProjectBody, Project, ProjectResponse } from '@/utils/api';
import { getCurrentOrganizationId } from '@/utils/useUser';
import { useNotificationsStore } from '@/utils/notification';
export const useProjectsStore = defineStore('projects', () => {
const projectResponse = ref<ProjectResponse | null>(null);
const { handleApiRequestNotifications } = useNotificationsStore();
async function fetchProjects() {
const organization = getCurrentOrganizationId();
if (organization) {
projectResponse.value = await api.getProjects({
params: {
organization: organization,
},
});
projectResponse.value = await handleApiRequestNotifications(
api.getProjects({
params: {
organization: organization,
},
}),
undefined,
'Failed to fetch projects'
);
}
}
async function createProject(projectBody: CreateProjectBody) {
const organization = getCurrentOrganizationId();
if (organization) {
await api.createProject(projectBody, {
params: {
organization: organization,
},
});
await handleApiRequestNotifications(
api.createProject(projectBody, {
params: {
organization: organization,
},
}),
'Project created successfully',
'Failed to create project'
);
await fetchProjects();
}
}
@@ -33,14 +43,18 @@ export const useProjectsStore = defineStore('projects', () => {
async function deleteProject(projectId: string) {
const organizationId = getCurrentOrganizationId();
if (organizationId) {
await api.deleteProject(
{},
{
params: {
organization: organizationId,
project: projectId,
},
}
await handleApiRequestNotifications(
api.deleteProject(
{},
{
params: {
organization: organizationId,
project: projectId,
},
}
),
'Project deleted successfully',
'Failed to delete project'
);
await fetchProjects();
}

View File

@@ -3,19 +3,26 @@ import { ref } from 'vue';
import type { Tag } from '@/utils/api';
import { getCurrentOrganizationId } from '@/utils/useUser';
import { api } from '../../../openapi.json.client';
import { useNotificationsStore } from '@/utils/notification';
export const useTagsStore = defineStore('tags', () => {
const tags = ref<Tag[]>([]);
const { handleApiRequestNotifications } = useNotificationsStore();
async function fetchTags() {
const organizationId = getCurrentOrganizationId();
if (organizationId) {
const response = await api.getTags({
params: {
organization: organizationId,
},
});
tags.value = response.data;
const response = await handleApiRequestNotifications(
api.getTags({
params: {
organization: organizationId,
},
}),
undefined,
'Failed to fetch tags'
);
if (response?.data) {
tags.value = response.data;
}
} else {
throw new Error(
'Failed to fetch current tags because organization ID is missing.'
@@ -26,14 +33,18 @@ export const useTagsStore = defineStore('tags', () => {
async function deleteTag(tagId: string) {
const organizationId = getCurrentOrganizationId();
if (organizationId) {
await api.deleteTag(
{},
{
params: {
organization: organizationId,
tag: tagId,
},
}
await handleApiRequestNotifications(
api.deleteTag(
{},
{
params: {
organization: organizationId,
tag: tagId,
},
}
),
'Tag deleted successfully',
'Failed to delete tag'
);
await fetchTags();
}
@@ -42,18 +53,24 @@ export const useTagsStore = defineStore('tags', () => {
async function createTag(name: string) {
const organizationId = getCurrentOrganizationId();
if (organizationId) {
const response = await api.createTag(
{
name: name,
},
{
params: {
organization: organizationId,
const response = await handleApiRequestNotifications(
api.createTag(
{
name: name,
},
}
{
params: {
organization: organizationId,
},
}
),
'Tag created successfully',
'Failed to create tag'
);
tags.value.unshift(response.data);
return response.data;
if (response?.data) {
tags.value.unshift(response.data);
return response.data;
}
} else {
throw new Error(
'Failed to create tag because organization ID is missing.'

View File

@@ -3,42 +3,56 @@ import { getCurrentOrganizationId } from '@/utils/useUser';
import { api } from '../../../openapi.json.client';
import { reactive, ref } from 'vue';
import type { CreateTaskBody, Task } from '@/utils/api';
import { useNotificationsStore } from '@/utils/notification';
export const useTasksStore = defineStore('tasks', () => {
const tasks = ref<Task[]>(reactive([]));
const { handleApiRequestNotifications } = useNotificationsStore();
async function fetchTasks() {
const organizationId = getCurrentOrganizationId();
if (organizationId) {
const tasksResponse = await api.getTasks({
params: {
organization: organizationId,
},
});
tasks.value = tasksResponse.data;
const tasksResponse = await handleApiRequestNotifications(
api.getTasks({
params: {
organization: organizationId,
},
})
);
if (tasksResponse?.data) {
tasks.value = tasksResponse.data;
}
}
}
async function updateTask(task: Task) {
const organizationId = getCurrentOrganizationId();
if (organizationId) {
await api.updateTask(task, {
params: {
organization: organizationId,
task: task.id,
},
});
await handleApiRequestNotifications(
api.updateTask(task, {
params: {
organization: organizationId,
task: task.id,
},
}),
'Task updated successfully',
'Failed to update task'
);
}
}
async function createTask(task: CreateTaskBody) {
const organizationId = getCurrentOrganizationId();
if (organizationId) {
await api.createTask(task, {
params: {
organization: organizationId,
},
});
await handleApiRequestNotifications(
api.createTask(task, {
params: {
organization: organizationId,
},
}),
'Task created successfully',
'Failed to create task'
);
await fetchTasks();
}
}
@@ -46,14 +60,18 @@ export const useTasksStore = defineStore('tasks', () => {
async function deleteTask(taskId: string) {
const organizationId = getCurrentOrganizationId();
if (organizationId) {
await api.deleteTask(
{},
{
params: {
organization: organizationId,
task: taskId,
},
}
await handleApiRequestNotifications(
api.deleteTask(
{},
{
params: {
organization: organizationId,
task: taskId,
},
}
),
'Task deleted successfully',
'Failed to delete task'
);
await fetchTasks();
}

View File

@@ -4,24 +4,31 @@ import { api } from '../../../openapi.json.client';
import { reactive, ref } from 'vue';
import type { TimeEntry } from '@/utils/api';
import dayjs from 'dayjs';
import { useNotificationsStore } from '@/utils/notification';
export const useTimeEntriesStore = defineStore('timeEntries', () => {
const timeEntries = ref<TimeEntry[]>(reactive([]));
const allTimeEntriesLoaded = ref(false);
const { handleApiRequestNotifications } = useNotificationsStore();
async function fetchTimeEntries() {
const organizationId = getCurrentOrganizationId();
if (organizationId) {
const timeEntriesResponse = await api.getTimeEntries({
params: {
organization: organizationId,
},
queries: {
only_full_dates: 'true',
},
});
timeEntries.value = timeEntriesResponse.data;
const timeEntriesResponse = await handleApiRequestNotifications(
api.getTimeEntries({
params: {
organization: organizationId,
},
queries: {
only_full_dates: 'true',
},
}),
undefined,
'Failed to fetch time entries'
);
if (timeEntriesResponse?.data) {
timeEntries.value = timeEntriesResponse.data;
}
}
}
@@ -32,16 +39,23 @@ export const useTimeEntriesStore = defineStore('timeEntries', () => {
timeEntries.value[timeEntries.value.length - 1];
dayjs(latestTimeEntry.start).utc().format('YYYY-MM-DD');
const timeEntriesResponse = await api.getTimeEntries({
params: {
organization: organizationId,
},
queries: {
only_full_dates: 'true',
before: dayjs(latestTimeEntry.start).utc().format(),
},
});
if (timeEntriesResponse.data.length > 0) {
const timeEntriesResponse = await handleApiRequestNotifications(
api.getTimeEntries({
params: {
organization: organizationId,
},
queries: {
only_full_dates: 'true',
before: dayjs(latestTimeEntry.start).utc().format(),
},
}),
undefined,
'Failed to fetch time entries'
);
if (
timeEntriesResponse?.data &&
timeEntriesResponse.data.length > 0
) {
timeEntries.value = timeEntries.value.concat(
timeEntriesResponse.data
);
@@ -54,23 +68,31 @@ export const useTimeEntriesStore = defineStore('timeEntries', () => {
async function updateTimeEntry(timeEntry: TimeEntry) {
const organizationId = getCurrentOrganizationId();
if (organizationId) {
await api.updateTimeEntry(timeEntry, {
params: {
organization: organizationId,
timeEntry: timeEntry.id,
},
});
await handleApiRequestNotifications(
api.updateTimeEntry(timeEntry, {
params: {
organization: organizationId,
timeEntry: timeEntry.id,
},
}),
'Time entry updated successfully',
'Failed to update time entry'
);
}
}
async function createTimeEntry(timeEntry: TimeEntry) {
const organizationId = getCurrentOrganizationId();
if (organizationId) {
await api.createTimeEntry(timeEntry, {
params: {
organization: organizationId,
},
});
await handleApiRequestNotifications(
api.createTimeEntry(timeEntry, {
params: {
organization: organizationId,
},
}),
'Time entry created successfully',
'Failed to create time entry'
);
await fetchTimeEntries();
}
}
@@ -78,14 +100,18 @@ export const useTimeEntriesStore = defineStore('timeEntries', () => {
async function deleteTimeEntry(timeEntryId: string) {
const organizationId = getCurrentOrganizationId();
if (organizationId) {
await api.deleteTimeEntry(
{},
{
params: {
organization: organizationId,
timeEntry: timeEntryId,
},
}
await handleApiRequestNotifications(
api.deleteTimeEntry(
{},
{
params: {
organization: organizationId,
timeEntry: timeEntryId,
},
}
),
'Time entry deleted successfully',
'Failed to delete time entry'
);
await fetchTimeEntries();
}