Zeiterfassung - Neue Tabelle time_entry mit started_at/ended_at/duration_seconds, optional todo_id/bezug, category-Enum (focus/meeting/break/admin/learning/other), description, day_entry_id-Verknüpfung. Partial-Unique-Index erzwingt max. einen aktiven Timer pro User. - tRPC time-Router: now, start (stoppt aktiven automatisch), stop, listToday (Liste + Tagessumme), update, upsertManual (retroaktiv), delete, teamToday (personenübergreifend mit byCategory + aktivem Timer pro User). - TopNav: persistente TimerPill links neben Streak. Quick-Picker mit heutigen ToDos, Kategorie-Auswahl und Freitext-Start. Live-Sekundenzähler. - /heute: TimelineSection mit Edit/Delete pro Eintrag, ToDo-Bindung nachträglich änderbar, Block-nachtragen-Dialog. Pro ToDo-Zeile ▶-Start- und 🕐-Nachtrag-Button. - /puls: TeamTimeSection mit aktiver Timer-Anzeige + Tagessumme + Kategorie-Breakdown pro User (refresh alle 30s, Live-Tick jede Sekunde). Visual - Space Grotesk als Hausschrift (selbst-gehostet, latin + latin-ext) - Keycap-Button-Stil (Gradient + Druckeffekt) für primary/secondary/destructive - Neue Helper-Module: format-duration, time-categories Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
47 lines
1.5 KiB
TypeScript
47 lines
1.5 KiB
TypeScript
import { z } from 'zod';
|
|
|
|
export const timeCategorySchema = z.enum([
|
|
'focus',
|
|
'meeting',
|
|
'break',
|
|
'admin',
|
|
'learning',
|
|
'other',
|
|
]);
|
|
export type TimeCategory = z.infer<typeof timeCategorySchema>;
|
|
|
|
export const TIME_CATEGORIES: { value: TimeCategory; label: string }[] = [
|
|
{ value: 'focus', label: 'Fokus' },
|
|
{ value: 'meeting', label: 'Meeting' },
|
|
{ value: 'admin', label: 'Admin' },
|
|
{ value: 'learning', label: 'Lernen' },
|
|
{ value: 'break', label: 'Pause' },
|
|
{ value: 'other', label: 'Sonstiges' },
|
|
];
|
|
|
|
export const startTimeEntryInput = z.object({
|
|
todoId: z.string().uuid().optional(),
|
|
category: timeCategorySchema.default('focus'),
|
|
description: z.string().max(200).optional(),
|
|
});
|
|
export type StartTimeEntryInput = z.infer<typeof startTimeEntryInput>;
|
|
|
|
export const updateTimeEntryInput = z.object({
|
|
id: z.string().uuid(),
|
|
startedAt: z.string().datetime().optional(),
|
|
endedAt: z.string().datetime().nullable().optional(),
|
|
todoId: z.string().uuid().nullable().optional(),
|
|
category: timeCategorySchema.optional(),
|
|
description: z.string().max(200).nullable().optional(),
|
|
});
|
|
export type UpdateTimeEntryInput = z.infer<typeof updateTimeEntryInput>;
|
|
|
|
export const manualTimeEntryInput = z.object({
|
|
startedAt: z.string().datetime(),
|
|
endedAt: z.string().datetime(),
|
|
todoId: z.string().uuid().optional(),
|
|
category: timeCategorySchema.default('focus'),
|
|
description: z.string().max(200).optional(),
|
|
});
|
|
export type ManualTimeEntryInput = z.infer<typeof manualTimeEntryInput>;
|