refactor(zeit): Pausen nicht mehr buchen — Lücken zwischen Tasks = Pause
ci / quality (push) Failing after 45s
ci / build (push) Has been skipped
deploy / deploy (push) Successful in 53s

Pause-Feature entfernt: kein Pause-Button, kein break-Buchen mehr. Man bucht
nur Tasks; stoppt man eine Task und es läuft keine andere, ist die Lücke
automatisch eine Pause — im Kalender/Tagesansicht schlicht leer. Tasks bleiben
pausierbar (Stop = Pause, erneut Start = Fortsetzen → mehrere Segmente auf
dieselbe Task, Lücke dazwischen leer). Wall-Clock zählt Lücken ohnehin nicht.

- time.pause-Endpoint entfernt; 'break' aus der angebotenen Kategorienliste
  raus (bleibt im Enum für Altdaten).
- TimerPill: Pause-Button + Pause-Panel-Grund entfernt; Stop/Start je Task bleibt
  (= pausieren/fortsetzen). Heute-ToDo-Zeile entsprechend beschriftet.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
dejanandClaude Opus 4.8 committed 2026-06-06 18:56:54 +02:00
1 parent 5bbb4abffa
commit 7f9a4506c5
4 files changed
+5 -69

No files matched your search

-25
View File
@@ -143,31 +143,6 @@ export const timeRouter = router({
return { stopped };
}),
/**
* Pause: stoppt ALLE laufenden Tasks und startet eine Pause (break).
*/
pause: protectedProcedure
.input(TimeEntrySchemas.pauseInput)
.mutation(async ({ ctx, input }) => {
const now = new Date();
const dayEntryId = await findCurrentDayEntryId(ctx.db, ctx.user.id);
return ctx.db.transaction(async (tx) => {
await stopAllActive(tx, ctx.user.id, now);
const [brk] = await tx
.insert(timeEntry)
.values({
userId: ctx.user.id,
dayEntryId,
category: 'break',
description: input.description ?? null,
startedAt: now,
})
.returning();
if (!brk) throw new Error('Pause konnte nicht gestartet werden.');
return brk;
});
}),
/**
* Alle Time-Entries des heutigen Tages plus aktiver.
*/
+1 -37
View File
@@ -1,5 +1,5 @@
import { useEffect, useRef, useState } from 'react';
import { Play, Square, Plus, Pause } from 'lucide-react';
import { Play, Square, Plus } from 'lucide-react';
import { clsx } from 'clsx';
import { toast } from 'sonner';
import { trpc } from '../../lib/trpc';
@@ -85,15 +85,6 @@ export function TimerPill() {
toast('Timer läuft.');
},
});
const pauseMutation = trpc.time.pause.useMutation({
onSuccess: async () => {
await invalidate();
setPickerOpen(false);
setPanelOpen(false);
toast('Pause läuft.');
},
});
const count = active.length;
const longest = count > 0 ? Math.max(...active.map(elapsedOf)) : 0;
@@ -130,17 +121,6 @@ export function TimerPill() {
</button>
)}
<button
type="button"
onClick={() => pauseMutation.mutate({})}
disabled={pauseMutation.isPending}
className="inline-flex items-center gap-1 rounded-full bg-black/5 px-2.5 py-1 text-xs font-medium text-[var(--color-ink-muted)] hover:bg-black/10 hover:text-[var(--color-ink)] disabled:opacity-50"
title="Pause — stoppt alle laufenden Tasks und trägt eine Pause ein"
>
<Pause size={10} fill="currentColor" />
<span>Pause</span>
</button>
{pickerOpen && (
<QuickPicker
todos={(todayQuery.data?.todos ?? []).filter((t) => t.status === 'open')}
@@ -174,7 +154,6 @@ function TimersPanel({
onChanged: () => Promise<void>;
}) {
const stopMutation = trpc.time.stop.useMutation({ onSuccess: onChanged });
const updateMutation = trpc.time.update.useMutation({ onSuccess: onChanged });
return (
<div className="absolute right-0 top-full z-40 mt-2 w-[320px] rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-surface-elevated)] p-3 shadow-[var(--shadow-lg)]">
<p className="px-1 text-xs font-medium text-[var(--color-ink-muted)]">
@@ -199,21 +178,6 @@ function TimersPanel({
<Square size={12} fill="currentColor" />
</button>
</div>
{a.category === 'break' && (
<input
type="text"
defaultValue={a.description ?? ''}
placeholder="Grund (optional) …"
maxLength={200}
onBlur={(e) => {
const v = e.target.value.trim();
if ((a.description ?? '') !== v) {
updateMutation.mutate({ id: a.id, description: v || null });
}
}}
className="mt-1 ml-4 w-[calc(100%-1rem)] rounded-[var(--radius-md)] border border-[var(--color-border)] bg-white px-2 py-1 text-xs"
/>
)}
</div>
))}
</div>
+2 -1
View File
@@ -238,7 +238,8 @@ function TodoRow({
? stopMutation.mutate({ id: activeHere.id })
: startMutation.mutate({ todoId: todo.id, category: 'focus' })
}
aria-label={isRunningHere ? 'Timer stoppen' : 'Timer für diesen ToDo starten'}
aria-label={isRunningHere ? 'Task pausieren' : 'Task starten / fortsetzen'}
title={isRunningHere ? 'Pausieren' : 'Starten / fortsetzen'}
className={
isRunningHere
? 'inline-flex h-7 items-center gap-1 rounded-full bg-[var(--color-ampel-red-bg)] px-2 text-xs font-medium text-[var(--color-ampel-red-fg)]'
+2 -6
View File
@@ -15,9 +15,10 @@ export const TIME_CATEGORIES: { value: TimeCategory; label: string }[] = [
{ value: 'meeting', label: 'Meeting' },
{ value: 'admin', label: 'Admin' },
{ value: 'learning', label: 'Lernen' },
{ value: 'break', label: 'Pause' },
{ value: 'other', label: 'Sonstiges' },
];
// 'break' bleibt im Enum (timeCategorySchema) für Altdaten, wird aber nicht mehr
// angeboten — Pausen werden nicht gebucht (Lücken zwischen Tasks = Pause).
export const startTimeEntryInput = z.object({
todoId: z.string().uuid().optional(),
@@ -36,11 +37,6 @@ export const updateTimeEntryInput = z.object({
});
export type UpdateTimeEntryInput = z.infer<typeof updateTimeEntryInput>;
export const pauseInput = z.object({
description: z.string().max(200).optional(),
});
export type PauseInput = z.infer<typeof pauseInput>;
export const teamTimelineInput = z.object({
date: z
.string()