Add break time feature to time entries

- Add breakMinutes field to TimeEntry model and database migration
- Users can now add break duration (minutes) to time entries
- Break time is subtracted from total tracked duration
- Validation ensures break time cannot exceed total entry duration
- Statistics and client target balance calculations account for breaks
- Frontend UI includes break time input in TimeEntryFormModal
- Duration displays show break time deduction (e.g., '7h (−1h break)')
- Both project/client statistics and weekly balance calculations updated
This commit is contained in:
simon.franken
2026-02-23 14:39:30 +01:00
parent d09247d2a5
commit 685a311001
11 changed files with 69 additions and 16 deletions

View File

@@ -20,6 +20,7 @@ export function TimeEntryFormModal({ entry, onClose, createTimeEntry, updateTime
return {
startTime: getLocalISOString(new Date(entry.startTime)),
endTime: getLocalISOString(new Date(entry.endTime)),
breakMinutes: entry.breakMinutes,
description: entry.description || '',
projectId: entry.projectId,
};
@@ -29,6 +30,7 @@ export function TimeEntryFormModal({ entry, onClose, createTimeEntry, updateTime
return {
startTime: getLocalISOString(oneHourAgo),
endTime: getLocalISOString(now),
breakMinutes: 0,
description: '',
projectId: projects?.[0]?.id || '',
};
@@ -97,6 +99,16 @@ export function TimeEntryFormModal({ entry, onClose, createTimeEntry, updateTime
/>
</div>
</div>
<div>
<label className="label">Break (minutes)</label>
<input
type="number"
min="0"
value={formData.breakMinutes ?? 0}
onChange={(e) => setFormData({ ...formData, breakMinutes: parseInt(e.target.value) || 0 })}
className="input"
/>
</div>
<div>
<label className="label">Description</label>
<textarea

View File

@@ -56,7 +56,7 @@ export function DashboardPage() {
const totalTodaySeconds =
todayEntries?.entries.reduce((total, entry) => {
return total + calculateDuration(entry.startTime, entry.endTime);
return total + calculateDuration(entry.startTime, entry.endTime, entry.breakMinutes);
}, 0) || 0;
const targetsWithData = targets?.filter(t => t.weeks.length > 0) ?? [];
@@ -216,7 +216,10 @@ export function DashboardPage() {
<div className="text-xs text-gray-400">{formatTime(entry.startTime)} {formatTime(entry.endTime)}</div>
</td>
<td className="px-4 py-3 whitespace-nowrap text-sm text-gray-900 font-mono">
{formatDurationFromDatesHoursMinutes(entry.startTime, entry.endTime)}
{formatDurationFromDatesHoursMinutes(entry.startTime, entry.endTime, entry.breakMinutes)}
{entry.breakMinutes > 0 && (
<span className="text-xs text-gray-400 ml-1">({entry.breakMinutes}m break)</span>
)}
</td>
<td className="px-4 py-3 whitespace-nowrap text-right">
<button onClick={() => handleOpenModal(entry)} className="p-1.5 text-gray-400 hover:text-gray-600 mr-1"><Edit2 className="h-4 w-4" /></button>

View File

@@ -78,7 +78,10 @@ export function TimeEntriesPage() {
</div>
</td>
<td className="px-4 py-3 whitespace-nowrap text-sm font-mono text-gray-900">
{formatDurationFromDatesHoursMinutes(entry.startTime, entry.endTime)}
{formatDurationFromDatesHoursMinutes(entry.startTime, entry.endTime, entry.breakMinutes)}
{entry.breakMinutes > 0 && (
<span className="text-xs text-gray-400 ml-1">({entry.breakMinutes}m)</span>
)}
</td>
<td className="px-4 py-3 whitespace-nowrap text-right">
<button onClick={() => handleOpenModal(entry)} className="p-1.5 text-gray-400 hover:text-gray-600 mr-1"><Edit2 className="h-4 w-4" /></button>

View File

@@ -28,6 +28,7 @@ export interface TimeEntry {
id: string;
startTime: string;
endTime: string;
breakMinutes: number;
description: string | null;
projectId: string;
project: Pick<Project, 'id' | 'name' | 'color'> & {
@@ -129,6 +130,7 @@ export interface UpdateProjectInput {
export interface CreateTimeEntryInput {
startTime: string;
endTime: string;
breakMinutes?: number;
description?: string;
projectId: string;
}
@@ -136,6 +138,7 @@ export interface CreateTimeEntryInput {
export interface UpdateTimeEntryInput {
startTime?: string;
endTime?: string;
breakMinutes?: number;
description?: string;
projectId?: string;
}

View File

@@ -43,7 +43,14 @@ export function formatDurationHoursMinutes(totalSeconds: number): string {
return `${hours}h ${minutes}m`;
}
export function calculateDuration(startTime: string, endTime: string): number {
export function calculateDuration(startTime: string, endTime: string, breakMinutes: number = 0): number {
const start = parseISO(startTime);
const end = parseISO(endTime);
const totalSeconds = differenceInSeconds(end, start);
return totalSeconds - (breakMinutes * 60);
}
export function calculateGrossDuration(startTime: string, endTime: string): number {
const start = parseISO(startTime);
const end = parseISO(endTime);
return differenceInSeconds(end, start);
@@ -52,16 +59,18 @@ export function calculateDuration(startTime: string, endTime: string): number {
export function formatDurationFromDates(
startTime: string,
endTime: string,
breakMinutes: number = 0,
): string {
const seconds = calculateDuration(startTime, endTime);
const seconds = calculateDuration(startTime, endTime, breakMinutes);
return formatDuration(seconds);
}
export function formatDurationFromDatesHoursMinutes(
startTime: string,
endTime: string,
breakMinutes: number = 0,
): string {
const seconds = calculateDuration(startTime, endTime);
const seconds = calculateDuration(startTime, endTime, breakMinutes);
return formatDurationHoursMinutes(seconds);
}