mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-12 02:02:15 +01:00
added success and error notifications
This commit is contained in:
62
resources/js/Components/Common/Notification/Notification.vue
Normal file
62
resources/js/Components/Common/Notification/Notification.vue
Normal 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>
|
||||
@@ -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) {
|
||||
|
||||
22
resources/js/Components/NotificationContainer.vue
Normal file
22
resources/js/Components/NotificationContainer.vue
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
83
resources/js/utils/notification.ts
Normal file
83
resources/js/utils/notification.ts
Normal 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 };
|
||||
});
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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.'
|
||||
|
||||
@@ -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'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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.'
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user