mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-13 02:32:15 +01:00
add break time entries and simplified time tracker ui
This commit is contained in:
@@ -26,9 +26,33 @@ const emptyTimeEntry = {
|
||||
project_id: null,
|
||||
tags: [],
|
||||
billable: false,
|
||||
type: 'work',
|
||||
organization_id: '',
|
||||
} as TimeEntry;
|
||||
|
||||
export type ResumeTimeEntryContext = {
|
||||
description: string | null;
|
||||
project_id: string | null;
|
||||
task_id: string | null;
|
||||
tags: string[];
|
||||
billable: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Time entries are loaded newest-first. Ignore scheduled entries so resuming after a break
|
||||
* always uses the latest work entry that has actually started.
|
||||
*/
|
||||
export function getLastWorkTimeEntry(
|
||||
timeEntries: TimeEntry[],
|
||||
currentTime: Dayjs = dayjs().utc()
|
||||
): TimeEntry | null {
|
||||
return (
|
||||
timeEntries.find(
|
||||
(entry) => entry.type === 'work' && !dayjs(entry.start).utc().isAfter(currentTime)
|
||||
) ?? null
|
||||
);
|
||||
}
|
||||
|
||||
export const useCurrentTimeEntryStore = defineStore('currentTimeEntry', () => {
|
||||
const currentTimeEntry = ref<TimeEntry>({ ...emptyTimeEntry });
|
||||
const { handleApiRequestNotifications } = useNotificationsStore();
|
||||
@@ -117,6 +141,7 @@ export const useCurrentTimeEntryStore = defineStore('currentTimeEntry', () => {
|
||||
project_id: currentTimeEntry.value?.project_id,
|
||||
task_id: currentTimeEntry.value?.task_id,
|
||||
billable: currentTimeEntry.value.billable,
|
||||
type: currentTimeEntry.value?.type ?? 'work',
|
||||
tags: currentTimeEntry.value?.tags,
|
||||
},
|
||||
{ params: { organization: organization } }
|
||||
@@ -133,11 +158,11 @@ export const useCurrentTimeEntryStore = defineStore('currentTimeEntry', () => {
|
||||
}
|
||||
}
|
||||
|
||||
async function stopTimer() {
|
||||
async function stopTimer(endTime?: string) {
|
||||
const user = getCurrentUserId();
|
||||
const organization = getCurrentOrganizationId();
|
||||
if (organization) {
|
||||
const currentDateTime = dayjs().utc().format();
|
||||
const currentDateTime = endTime ?? dayjs().utc().format();
|
||||
await handleApiRequestNotifications(
|
||||
() =>
|
||||
api.updateTimeEntry(
|
||||
@@ -161,6 +186,58 @@ export const useCurrentTimeEntryStore = defineStore('currentTimeEntry', () => {
|
||||
}
|
||||
}
|
||||
|
||||
async function startBreak() {
|
||||
const organization = getCurrentOrganizationId();
|
||||
const membership = getCurrentMembershipId();
|
||||
if (!organization || !membership) {
|
||||
throw new Error('Failed to start break because organization ID is missing.');
|
||||
}
|
||||
// One timestamp for both the work end and the break start, so the entries touch exactly
|
||||
const switchTime = dayjs().utc().format();
|
||||
if (isActive.value && currentTimeEntry.value.type !== 'break') {
|
||||
await stopTimer(switchTime);
|
||||
}
|
||||
startLiveTimer();
|
||||
const response = await handleApiRequestNotifications(
|
||||
() =>
|
||||
api.createTimeEntry(
|
||||
{
|
||||
member_id: membership,
|
||||
start: switchTime,
|
||||
billable: false,
|
||||
type: 'break',
|
||||
},
|
||||
{ params: { organization: organization } }
|
||||
),
|
||||
'Break started!',
|
||||
'Your timer was stopped, but the break could not be started.'
|
||||
);
|
||||
if (response?.data) {
|
||||
currentTimeEntry.value = response.data;
|
||||
} else {
|
||||
stopLiveTimer();
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: ['timeEntries'] });
|
||||
}
|
||||
|
||||
async function resumeWorkAfterBreak(context: ResumeTimeEntryContext) {
|
||||
if (isActive.value && currentTimeEntry.value.type === 'break') {
|
||||
stopLiveTimer();
|
||||
await stopTimer();
|
||||
}
|
||||
currentTimeEntry.value = {
|
||||
...emptyTimeEntry,
|
||||
description: context.description ?? '',
|
||||
project_id: context.project_id,
|
||||
task_id: context.task_id,
|
||||
tags: context.tags ?? [],
|
||||
billable: context.billable,
|
||||
};
|
||||
startLiveTimer();
|
||||
await startTimer();
|
||||
queryClient.invalidateQueries({ queryKey: ['timeEntries'] });
|
||||
}
|
||||
|
||||
async function updateTimer() {
|
||||
const user = getCurrentUserId();
|
||||
const organization = getCurrentOrganizationId();
|
||||
@@ -213,6 +290,10 @@ export const useCurrentTimeEntryStore = defineStore('currentTimeEntry', () => {
|
||||
return false;
|
||||
});
|
||||
|
||||
const isOnBreak = computed(() => {
|
||||
return isActive.value && currentTimeEntry.value.type === 'break';
|
||||
});
|
||||
|
||||
async function setActiveState(newState: boolean) {
|
||||
if (newState) {
|
||||
startLiveTimer();
|
||||
@@ -229,6 +310,9 @@ export const useCurrentTimeEntryStore = defineStore('currentTimeEntry', () => {
|
||||
fetchCurrentTimeEntry,
|
||||
updateTimer,
|
||||
isActive,
|
||||
isOnBreak,
|
||||
startBreak,
|
||||
resumeWorkAfterBreak,
|
||||
startLiveTimer,
|
||||
stopLiveTimer,
|
||||
now,
|
||||
|
||||
Reference in New Issue
Block a user