From e0fbc386fa0960c80171a9226918ba067f52e11e Mon Sep 17 00:00:00 2001 From: DeeCee03 Date: Thu, 11 Jun 2026 19:28:29 +0200 Subject: [PATCH] Anwesenheit auch in "Zeit im Team heute" anzeigen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit teamToday liefert pro Person checkedInAt/checkedOutAt aus dem heutigen day_entry mit; die Team-Karte zeigt daneben "Anwesend: …" (live, bzw. eingefroren nach Feierabend). Reine Anzeige, kein Eingriff ins Tracking. Co-Authored-By: Claude Fable 5 --- apps/api/src/routers/time.ts | 30 ++++++++++++++----- .../src/components/time/TeamTimeSection.tsx | 18 +++++++++++ 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/apps/api/src/routers/time.ts b/apps/api/src/routers/time.ts index 2818f59..62940d2 100644 --- a/apps/api/src/routers/time.ts +++ b/apps/api/src/routers/time.ts @@ -341,14 +341,30 @@ export const timeRouter = router({ perUser.set(r.userId, bucket); } + // Anwesenheit (Lock-in → Feierabend) pro Person — reine Anzeige neben dem Tracking. + const dayRows = await ctx.db + .select({ + userId: dayEntry.userId, + checkedInAt: dayEntry.checkedInAt, + checkedOutAt: dayEntry.checkedOutAt, + }) + .from(dayEntry) + .where(eq(dayEntry.date, today)); + const presenceByUser = new Map(dayRows.map((d) => [d.userId, d])); + // „Gesamt" pro Person = Wall-Clock (Vereinigung der Intervalle). - return Array.from(perUser.values()).map((b) => ({ - userId: b.userId, - totalSeconds: unionSeconds(b.intervals), - trackedSeconds: b.trackedSeconds, - byCategory: b.byCategory, - active: b.active, - })); + return Array.from(perUser.values()).map((b) => { + const p = presenceByUser.get(b.userId); + return { + userId: b.userId, + totalSeconds: unionSeconds(b.intervals), + trackedSeconds: b.trackedSeconds, + byCategory: b.byCategory, + active: b.active, + checkedInAt: p?.checkedInAt ? p.checkedInAt.toISOString() : null, + checkedOutAt: p?.checkedOutAt ? p.checkedOutAt.toISOString() : null, + }; + }); }), /** diff --git a/apps/web/src/components/time/TeamTimeSection.tsx b/apps/web/src/components/time/TeamTimeSection.tsx index 330a7f6..15717de 100644 --- a/apps/web/src/components/time/TeamTimeSection.tsx +++ b/apps/web/src/components/time/TeamTimeSection.tsx @@ -28,6 +28,16 @@ function initials(name: string) { .join(''); } +/** Anwesenheit (Lock-in → Feierabend bzw. jetzt) — reine Anzeige, kein Tracking. */ +function presenceSecondsOf(it: { + checkedInAt: string | null; + checkedOutAt: string | null; +}): number | null { + if (!it.checkedInAt) return null; + const endMs = it.checkedOutAt ? new Date(it.checkedOutAt).getTime() : Date.now(); + return Math.max(0, Math.round((endMs - new Date(it.checkedInAt).getTime()) / 1000)); +} + export function TeamTimeSection() { const team = trpc.time.teamToday.useQuery(undefined, { refetchInterval: 30_000 }); const users = trpc.user.list.useQuery(); @@ -90,6 +100,14 @@ export function TeamTimeSection() { )} + {presenceSecondsOf(it) !== null && ( + + {' · Anwesend: '} + + {formatDurationCompact(presenceSecondsOf(it)!)} + + + )}