add time tracker with stats

This commit is contained in:
richardtekula
2025-11-24 09:10:04 +01:00
parent 540c1719d3
commit dfcf8056f3
2 changed files with 86 additions and 24 deletions

View File

@@ -3,11 +3,29 @@ import { timeEntries, projects, todos, companies, users } from '../db/schema.js'
import { eq, and, gte, lte, desc, sql } from 'drizzle-orm';
import { NotFoundError, BadRequestError } from '../utils/errors.js';
// Helpers to normalize optional payload fields
const normalizeOptionalId = (value) => {
if (value === undefined) return undefined;
if (value === null || value === '') return null;
return value;
};
const normalizeOptionalText = (value) => {
if (value === undefined) return undefined;
if (value === null) return null;
if (typeof value !== 'string') return value;
const trimmed = value.trim();
return trimmed.length ? trimmed : null;
};
/**
* Start a new time entry
*/
export const startTimeEntry = async (userId, data) => {
const { projectId, todoId, companyId, description } = data;
const projectId = normalizeOptionalId(data.projectId);
const todoId = normalizeOptionalId(data.todoId);
const companyId = normalizeOptionalId(data.companyId);
const description = normalizeOptionalText(data.description);
// Check if user already has a running time entry
const [existingRunning] = await db
@@ -16,8 +34,20 @@ export const startTimeEntry = async (userId, data) => {
.where(and(eq(timeEntries.userId, userId), eq(timeEntries.isRunning, true)))
.limit(1);
// Automatically stop existing running timer
if (existingRunning) {
throw new BadRequestError('Máte už spustený časovač. Prosím zastavte ho pred spustením nového.');
const endTime = new Date();
const durationInMinutes = Math.round((endTime - new Date(existingRunning.startTime)) / 60000);
await db
.update(timeEntries)
.set({
endTime,
duration: durationInMinutes,
isRunning: false,
updatedAt: new Date(),
})
.where(eq(timeEntries.id, existingRunning.id));
}
// Verify project exists if provided
@@ -63,10 +93,10 @@ export const startTimeEntry = async (userId, data) => {
.insert(timeEntries)
.values({
userId,
projectId: projectId || null,
todoId: todoId || null,
companyId: companyId || null,
description: description || null,
projectId: projectId ?? null,
todoId: todoId ?? null,
companyId: companyId ?? null,
description: description ?? null,
startTime: new Date(),
endTime: null,
duration: null,
@@ -82,7 +112,10 @@ export const startTimeEntry = async (userId, data) => {
* Stop a running time entry
*/
export const stopTimeEntry = async (entryId, userId, data = {}) => {
const { projectId, todoId, companyId, description } = data;
const projectId = normalizeOptionalId(data.projectId);
const todoId = normalizeOptionalId(data.todoId);
const companyId = normalizeOptionalId(data.companyId);
const description = normalizeOptionalText(data.description);
const entry = await getTimeEntryById(entryId);
@@ -98,7 +131,7 @@ export const stopTimeEntry = async (entryId, userId, data = {}) => {
const endTime = new Date();
const durationInMinutes = Math.round((endTime - new Date(entry.startTime)) / 60000);
// Verify related entities if provided
// Verify related entities if provided (skip validation for null/undefined)
if (projectId) {
const [project] = await db
.select()
@@ -256,10 +289,14 @@ export const updateTimeEntry = async (entryId, userId, data) => {
throw new BadRequestError('Nemôžete upraviť bežiaci časovač. Najprv ho zastavte.');
}
const { startTime, endTime, projectId, todoId, companyId, description } = data;
const { startTime, endTime } = data;
const projectId = normalizeOptionalId(data.projectId);
const todoId = normalizeOptionalId(data.todoId);
const companyId = normalizeOptionalId(data.companyId);
const description = normalizeOptionalText(data.description);
// Verify related entities if being changed
if (projectId !== undefined && projectId !== null) {
if (projectId) {
const [project] = await db
.select()
.from(projects)
@@ -271,7 +308,7 @@ export const updateTimeEntry = async (entryId, userId, data) => {
}
}
if (todoId !== undefined && todoId !== null) {
if (todoId) {
const [todo] = await db
.select()
.from(todos)
@@ -283,7 +320,7 @@ export const updateTimeEntry = async (entryId, userId, data) => {
}
}
if (companyId !== undefined && companyId !== null) {
if (companyId) {
const [company] = await db
.select()
.from(companies)