Anwesenheit: Fallback-Anker erster Zeiteintrag bei Auto-Tagen
ci / quality (push) Failing after 38s
ci / build (push) Has been skipped
deploy / deploy (push) Successful in 1m1s

Auto-Tage vom 6-Uhr-Schnitt haben keinen echten Lock-in — statt die
Anwesenheit ganz auszublenden, ankert sie jetzt am ersten Zeiteintrag
des Tages. teamToday/listToday liefern presenceStartedAt/presenceEndedAt
(echter Check-in hat Vorrang); Team-Karte, Heute-Balken und "Zeit heute"
nutzen die neuen Felder.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
dejanandClaude Fable 5 committed 2026-06-12 12:20:18 +02:00
1 parent e049e0a88c
commit 94faf5e41f
4 files changed
+56 -17

No files matched your search

@@ -30,12 +30,12 @@ function initials(name: string) {
/** Anwesenheit (Lock-in → Feierabend bzw. jetzt) — reine Anzeige, kein Tracking. */
function presenceSecondsOf(it: {
checkedInAt: string | null;
checkedOutAt: string | null;
presenceStartedAt: string | null;
presenceEndedAt: 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));
if (!it.presenceStartedAt) return null;
const endMs = it.presenceEndedAt ? new Date(it.presenceEndedAt).getTime() : Date.now();
return Math.max(0, Math.round((endMs - new Date(it.presenceStartedAt).getTime()) / 1000));
}
export function TeamTimeSection() {
@@ -101,7 +101,7 @@ export function TeamTimeSection() {
</span>
)}
{presenceSecondsOf(it) !== null && (
<span title="Anwesenheit: Lock-in bis Feierabend (bzw. jetzt) — unabhängig vom Tracking.">
<span title="Anwesenheit: ab Lock-in (bzw. erstem Zeiteintrag des Tages) bis Feierabend bzw. jetzt — unabhängig vom Tracking.">
{' · Anwesend: '}
<span className="font-mono">
{formatDurationCompact(presenceSecondsOf(it)!)}
@@ -31,11 +31,16 @@ export function TimelineSection() {
const todayQuery = trpc.dayEntry.today.useQuery();
const [manualOpen, setManualOpen] = useState(false);
const [editing, setEditing] = useState<string | null>(null);
// Auto-Check-in vom 6-Uhr-Schnitt ist kein echter Lock-in → keine Anwesenheit.
// Anwesenheits-Anker: echter Lock-in; bei Auto-Tagen (6-Uhr-Schnitt) liefert
// listToday den ersten Zeiteintrag des Tages als Fallback.
const todayEntry = todayQuery.data?.entry;
const presenceFrom =
todayEntry && !todayEntry.autoCheckedIn && todayEntry.checkedInAt
? todayEntry.checkedInAt
: (listQuery.data?.presenceStartedAt ?? null);
const presenceSeconds = usePresenceSeconds(
todayEntry?.autoCheckedIn ? null : todayEntry?.checkedInAt,
todayEntry?.checkedOutAt,
presenceFrom,
todayEntry?.checkedOutAt ?? listQuery.data?.presenceEndedAt ?? null,
);
if (listQuery.isPending) return null;
+6 -2
View File
@@ -357,8 +357,12 @@ function CheckInOutBar({
}).format(new Date(d))
: '—';
// Anwesenheit (Lock-in → Feierabend bzw. jetzt) — reine Anzeige, kein Tracking.
// Auto-Check-in vom 6-Uhr-Schnitt ist kein echter Lock-in → keine Anwesenheit.
const presenceSeconds = usePresenceSeconds(autoCheckedIn ? null : checkedInAt, checkedOutAt);
// Auto-Check-in vom 6-Uhr-Schnitt ist kein echter Lock-in → Fallback-Anker
// (erster Zeiteintrag des Tages) aus listToday.
const listToday = trpc.time.listToday.useQuery(undefined, { refetchInterval: 30_000 });
const presenceFrom =
!autoCheckedIn && checkedInAt ? checkedInAt : (listToday.data?.presenceStartedAt ?? null);
const presenceSeconds = usePresenceSeconds(presenceFrom, checkedOutAt);
return (
<>