refactor(zeit): Pausen nicht mehr buchen — Lücken zwischen Tasks = Pause
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:
1 parent
5bbb4abffa
commit
7f9a4506c5
4 files changed
+5
-69
No files matched your search
@@ -143,31 +143,6 @@ export const timeRouter = router({
|
|||||||
return { stopped };
|
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.
|
* Alle Time-Entries des heutigen Tages plus aktiver.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useRef, useState } from 'react';
|
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 { clsx } from 'clsx';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { trpc } from '../../lib/trpc';
|
import { trpc } from '../../lib/trpc';
|
||||||
@@ -85,15 +85,6 @@ export function TimerPill() {
|
|||||||
toast('Timer läuft.');
|
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 count = active.length;
|
||||||
const longest = count > 0 ? Math.max(...active.map(elapsedOf)) : 0;
|
const longest = count > 0 ? Math.max(...active.map(elapsedOf)) : 0;
|
||||||
|
|
||||||
@@ -130,17 +121,6 @@ export function TimerPill() {
|
|||||||
</button>
|
</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 && (
|
{pickerOpen && (
|
||||||
<QuickPicker
|
<QuickPicker
|
||||||
todos={(todayQuery.data?.todos ?? []).filter((t) => t.status === 'open')}
|
todos={(todayQuery.data?.todos ?? []).filter((t) => t.status === 'open')}
|
||||||
@@ -174,7 +154,6 @@ function TimersPanel({
|
|||||||
onChanged: () => Promise<void>;
|
onChanged: () => Promise<void>;
|
||||||
}) {
|
}) {
|
||||||
const stopMutation = trpc.time.stop.useMutation({ onSuccess: onChanged });
|
const stopMutation = trpc.time.stop.useMutation({ onSuccess: onChanged });
|
||||||
const updateMutation = trpc.time.update.useMutation({ onSuccess: onChanged });
|
|
||||||
return (
|
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)]">
|
<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)]">
|
<p className="px-1 text-xs font-medium text-[var(--color-ink-muted)]">
|
||||||
@@ -199,21 +178,6 @@ function TimersPanel({
|
|||||||
<Square size={12} fill="currentColor" />
|
<Square size={12} fill="currentColor" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -238,7 +238,8 @@ function TodoRow({
|
|||||||
? stopMutation.mutate({ id: activeHere.id })
|
? stopMutation.mutate({ id: activeHere.id })
|
||||||
: startMutation.mutate({ todoId: todo.id, category: 'focus' })
|
: 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={
|
className={
|
||||||
isRunningHere
|
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)]'
|
? '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)]'
|
||||||
|
|||||||
@@ -15,9 +15,10 @@ export const TIME_CATEGORIES: { value: TimeCategory; label: string }[] = [
|
|||||||
{ value: 'meeting', label: 'Meeting' },
|
{ value: 'meeting', label: 'Meeting' },
|
||||||
{ value: 'admin', label: 'Admin' },
|
{ value: 'admin', label: 'Admin' },
|
||||||
{ value: 'learning', label: 'Lernen' },
|
{ value: 'learning', label: 'Lernen' },
|
||||||
{ value: 'break', label: 'Pause' },
|
|
||||||
{ value: 'other', label: 'Sonstiges' },
|
{ 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({
|
export const startTimeEntryInput = z.object({
|
||||||
todoId: z.string().uuid().optional(),
|
todoId: z.string().uuid().optional(),
|
||||||
@@ -36,11 +37,6 @@ export const updateTimeEntryInput = z.object({
|
|||||||
});
|
});
|
||||||
export type UpdateTimeEntryInput = z.infer<typeof updateTimeEntryInput>;
|
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({
|
export const teamTimelineInput = z.object({
|
||||||
date: z
|
date: z
|
||||||
.string()
|
.string()
|
||||||
|
|||||||
Reference in new issue
Block a user