add dashboard frontend

This commit is contained in:
Gregor Vostrak
2024-03-11 18:02:54 +01:00
parent e8912650c0
commit 20fc123c36
86 changed files with 5124 additions and 849 deletions

View File

@@ -0,0 +1,4 @@
import type { ApiOf } from '@zodios/core';
import { api } from '../../../openapi.json.client';
export type SolidTimeApi = ApiOf<typeof api>;

View File

@@ -0,0 +1,25 @@
const colors = [
'#ef5350',
'#ec407a',
'#ab47bc',
'#7e57c2',
'#5c6bc0',
'#42a5f5',
'#29b6f6',
'#26c6da',
'#26a69a',
'#66bb6a',
'#9ccc65',
'#d4e157',
'#ffee58',
'#ffca28',
'#ffa726',
'#ff7043',
'#8d6e63',
'#bdbdbd',
'#78909c',
];
export function getRandomColor() {
return colors[Math.floor(Math.random() * colors.length)];
}

View File

@@ -0,0 +1,6 @@
export function formatMoney(amount: number, currency: string) {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: currency,
}).format(amount);
}

View File

@@ -0,0 +1,8 @@
import dayjs from 'dayjs';
import duration from 'dayjs/plugin/duration';
dayjs.extend(duration);
export function formatHumanReadableDuration(duration: number): string {
return dayjs.duration(duration, 's').format('HH[h] mm[min]');
}

View File

@@ -0,0 +1,151 @@
import { defineStore } from 'pinia';
import { computed, reactive, ref } from 'vue';
import { api } from '../../../openapi.json.client';
import type { ZodiosResponseByAlias } from '@zodios/core';
import type { SolidTimeApi } from '@/utils/api';
import dayjs from 'dayjs';
import { getCurrentOrganizationId, getCurrentUserId } from '@/utils/useUser';
type TimeEntryResponse = ZodiosResponseByAlias<SolidTimeApi, 'getTimeEntries'>;
export type TimeEntry = TimeEntryResponse['data'][0];
const emptyTimeEntry = {
id: '',
description: null,
user_id: '',
start: '',
end: null,
duration: null,
task_id: null,
project_id: null,
tags: [],
} as TimeEntry;
export const useCurrentTimeEntryStore = defineStore('currentTimeEntry', () => {
const currentTimeEntry = ref<TimeEntry>(reactive(emptyTimeEntry));
function $reset() {
currentTimeEntry.value = { ...emptyTimeEntry };
}
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 };
}
} else {
throw new Error(
'Failed to fetch current time entry because organization ID is missing.'
);
}
}
async function startTimer() {
const user = getCurrentUserId();
const organization = getCurrentOrganizationId();
if (organization) {
const startTime =
currentTimeEntry.value.start !== ''
? currentTimeEntry.value.start
: dayjs().utc().format();
const response = await api.createTimeEntry(
{
user_id: user,
start: startTime,
description: currentTimeEntry.value?.description,
},
{ params: { organization: organization } }
);
currentTimeEntry.value = response.data;
} else {
throw new Error(
'Failed to fetch current time entry because organization ID is missing.'
);
}
}
async function stopTimer() {
const user = getCurrentUserId();
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,
},
}
);
$reset();
} else {
throw new Error(
'Failed to stop current timer because organization ID is missing.'
);
}
}
async function updateTimer() {
const user = getCurrentUserId();
const organization = getCurrentOrganizationId();
if (organization) {
await api.updateTimeEntry(
{
description: currentTimeEntry.value.description,
user_id: user,
project_id: currentTimeEntry.value.project_id,
start: currentTimeEntry.value.start,
end: null,
tags: currentTimeEntry.value.tags,
},
{
params: {
organization: organization,
timeEntry: currentTimeEntry.value.id,
},
}
);
// currentTimeEntry.value = response.data;
} else {
throw new Error(
'Failed to fetch current time entry because organization ID is missing.'
);
}
}
const isActive = computed(() => {
if (currentTimeEntry.value) {
return (
currentTimeEntry.value.start !== '' &&
currentTimeEntry.value.start !== null &&
currentTimeEntry.value.end === null
);
}
return false;
});
return {
currentTimeEntry,
fetchCurrentTimeEntry,
startTimer,
stopTimer,
updateTimer,
isActive,
};
});

View File

@@ -0,0 +1,24 @@
import { defineStore } from 'pinia';
import { api } from '../../../openapi.json.client';
import { computed, ref } from 'vue';
import type { ZodiosResponseByAlias } from '@zodios/core';
import type { SolidTimeApi } from '@/utils/api';
type ProjectResponse = ZodiosResponseByAlias<SolidTimeApi, 'getProjects'>;
export type Project = ProjectResponse['data'][0];
export const useProjectsStore = defineStore('projects', () => {
const projectResponse = ref<ProjectResponse | null>(null);
async function fetchProjects(organizationId: string) {
projectResponse.value = await api.getProjects({
params: {
organization: organizationId,
},
});
}
const projects = computed(() => projectResponse.value?.data || []);
return { projects, fetchProjects };
});

View File

@@ -0,0 +1,53 @@
import { defineStore } from 'pinia';
import { ref } from 'vue';
import type { ZodiosResponseByAlias } from '@zodios/core';
import type { SolidTimeApi } from '@/utils/api';
import { getCurrentOrganizationId } from '@/utils/useUser';
import { api } from '../../../openapi.json.client';
type TagIndexResponse = ZodiosResponseByAlias<SolidTimeApi, 'getTags'>;
export type Tag = TagIndexResponse['data'][0];
export const useTagsStore = defineStore('tags', () => {
const tags = ref<Tag[]>([]);
async function fetchTags() {
const organizationId = getCurrentOrganizationId();
if (organizationId) {
const response = await api.getTags({
params: {
organization: organizationId,
},
});
tags.value = response.data;
} else {
throw new Error(
'Failed to fetch current tags because organization ID is missing.'
);
}
}
async function createTag(name: string) {
const organizationId = getCurrentOrganizationId();
if (organizationId) {
const response = await api.createTag(
{
name: name,
},
{
params: {
organization: organizationId,
},
}
);
tags.value.unshift(response.data);
return response.data;
} else {
throw new Error(
'Failed to create tag because organization ID is missing.'
);
}
}
return { tags, fetchTags, createTag };
});

View File

@@ -0,0 +1,17 @@
import { usePage } from '@inertiajs/vue3';
import type { User } from '@/types/models';
const page = usePage<{
auth: {
user: User;
};
}>();
function getCurrentUserId() {
return page.props.auth.user.id;
}
function getCurrentOrganizationId() {
return page.props.auth.user.current_team_id;
}
export { getCurrentOrganizationId, getCurrentUserId };