Feature: Zeit-pro-Projekt-Auswertung im Projekt-Detail

time_entry erfasst bezugKind='project'+bezugId, aber niemand sah je, wie viel
Zeit ein Projekt frisst. Neue Query time.byProject (Gesamtsumme + pro Person);
neue "Getrackte Zeit"-Sektion im Projekt-Detail (blendet sich aus, wenn nichts
getrackt wurde).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Raffael SheikhandClaude Opus 4.8 committed 2026-06-07 12:15:05 +02:00
1 parent 2eef94afce
commit 7dd1f2fb20
2 files changed
+68

No files matched your search

+34
View File
@@ -12,6 +12,7 @@ import { Modal } from '../components/ui/Modal';
import { useConfirm } from '../components/ui/ConfirmProvider';
import { useCurrentUser } from '../hooks/useCurrentUser';
import { voice } from '../lib/voice';
import { formatDurationCompact } from '../lib/format-duration';
import { toast } from 'sonner';
import { clsx } from 'clsx';
import { LEAD_STATUS_META, type LeadStatus } from '../lib/lead-meta';
@@ -191,6 +192,8 @@ export function ProjektRoute() {
<MilestonesSection projectId={p.id} milestones={milestones} deadline={p.deadline} />
<ZeitSection projectId={p.id} />
{p.dodText && (
<Card.Root>
<Card.Header>
@@ -584,6 +587,37 @@ function Zieldreieck({ flex }: { flex: ZieldreieckFlex | null }) {
);
}
// --- Getrackte Zeit ---------------------------------------------------------
function ZeitSection({ projectId }: { projectId: string }) {
const q = trpc.time.byProject.useQuery({ projectId });
if (!q.data || q.data.totalSeconds === 0) return null;
return (
<Card.Root>
<Card.Header>
<h2 className="text-lg font-semibold">Getrackte Zeit</h2>
</Card.Header>
<Card.Body className="space-y-2">
<p className="text-sm">
Gesamt:{' '}
<span className="font-mono font-semibold">
{formatDurationCompact(q.data.totalSeconds)}
</span>
</p>
<ul className="space-y-1 text-sm">
{q.data.perUser.map((u) => (
<li key={u.userId} className="flex justify-between gap-2">
<span className="truncate">{u.name}</span>
<span className="shrink-0 font-mono text-[var(--color-ink-muted)]">
{formatDurationCompact(u.seconds)}
</span>
</li>
))}
</ul>
</Card.Body>
</Card.Root>
);
}
// --- Phasen-Stepper + Gate-Review -------------------------------------------
function PhaseSection({ projectId, phase }: { projectId: string; phase: ProjectPhase }) {
const utils = trpc.useUtils();