Zeiterfassung + Visual-Polish (Keycap-Buttons, Space Grotesk)
ci / quality (push) Has been cancelled
ci / build (push) Has been cancelled

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>
This commit is contained in:
dejanandClaude Opus 4.7 committed 2026-06-02 17:27:40 +02:00
1 parent 4f33509a0f
commit 95beb99cd4
23 files changed
+3442 -49

No files matched your search

+11 -8
View File
@@ -9,20 +9,23 @@ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
size?: Size;
}
// Buttons im ToolCTRL-Website-Stil (Keycap-Optik). Die eigentliche Optik kommt
// aus den .btn-keycap-* Klassen in globals.css (Gradient-Fläche, Tastenkante,
// Druckeffekt). Die Tailwind-Größenklassen liefern nur die Maße.
const base =
'inline-flex items-center justify-center gap-2 font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed';
'inline-flex items-center justify-center gap-2 font-medium disabled:opacity-50 disabled:cursor-not-allowed';
const variantStyles: Record<Variant, string> = {
primary: 'bg-[var(--color-brand)] text-[var(--color-brand-fg)] hover:bg-black/85',
secondary: 'border border-[var(--color-ink)] text-[var(--color-ink)] hover:bg-black/5',
ghost: 'text-[var(--color-ink)] hover:bg-black/5',
destructive: 'bg-[var(--color-ampel-red-fg)] text-white hover:bg-red-800',
primary: 'btn-keycap btn-keycap-solid',
secondary: 'btn-keycap btn-keycap-ghost',
ghost: 'rounded-md text-[var(--color-ink)] hover:bg-black/5 transition-colors',
destructive: 'btn-keycap btn-keycap-destructive',
};
const sizeStyles: Record<Size, string> = {
sm: 'h-7 px-3 text-xs rounded-[var(--radius-md)]',
md: 'h-9 px-4 text-sm rounded-[var(--radius-md)]',
lg: 'h-11 px-6 text-base rounded-[var(--radius-md)]',
sm: 'h-7 px-3 text-xs',
md: 'h-9 px-4 text-sm',
lg: 'h-11 px-6 text-base',
};
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
+2 -1
View File
@@ -2,10 +2,11 @@ import { clsx } from 'clsx';
import type { HTMLAttributes } from 'react';
function Root({ className, ...rest }: HTMLAttributes<HTMLDivElement>) {
// Scharfe Kanten, Haarlinien-Rahmen ohne Schatten — ToolCTRL-Website-Stil.
return (
<div
className={clsx(
'bg-[var(--color-surface)] border border-[var(--color-border)] rounded-[var(--radius-lg)] shadow-[var(--shadow-sm)]',
'bg-[var(--color-surface)] border border-[var(--color-border)]',
className,
)}
{...rest}