mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-15 19:52:15 +01:00
add dashboard frontend
This commit is contained in:
4
resources/js/utils/api.ts
Normal file
4
resources/js/utils/api.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import type { ApiOf } from '@zodios/core';
|
||||
import { api } from '../../../openapi.json.client';
|
||||
|
||||
export type SolidTimeApi = ApiOf<typeof api>;
|
||||
25
resources/js/utils/color.ts
Normal file
25
resources/js/utils/color.ts
Normal 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)];
|
||||
}
|
||||
6
resources/js/utils/money.ts
Normal file
6
resources/js/utils/money.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export function formatMoney(amount: number, currency: string) {
|
||||
return new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: currency,
|
||||
}).format(amount);
|
||||
}
|
||||
8
resources/js/utils/time.ts
Normal file
8
resources/js/utils/time.ts
Normal 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]');
|
||||
}
|
||||
151
resources/js/utils/useCurrentTimeEntry.ts
Normal file
151
resources/js/utils/useCurrentTimeEntry.ts
Normal 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,
|
||||
};
|
||||
});
|
||||
24
resources/js/utils/useProjects.ts
Normal file
24
resources/js/utils/useProjects.ts
Normal 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 };
|
||||
});
|
||||
53
resources/js/utils/useTags.ts
Normal file
53
resources/js/utils/useTags.ts
Normal 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 };
|
||||
});
|
||||
17
resources/js/utils/useUser.ts
Normal file
17
resources/js/utils/useUser.ts
Normal 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 };
|
||||
Reference in New Issue
Block a user