diff --git a/apps/admin/src/auth/AuthContext.test.tsx b/apps/admin/src/auth/AuthContext.test.tsx index e28b808..c8193da 100644 --- a/apps/admin/src/auth/AuthContext.test.tsx +++ b/apps/admin/src/auth/AuthContext.test.tsx @@ -92,4 +92,23 @@ describe('AuthContext admin gate', () => { expect(getToken()).toBeNull(); expect(screen.getByTestId('authed')).toHaveTextContent('false'); }); + + it('signOut resets the path to / so the next login lands on Live', async () => { + mockedFetchMe.mockResolvedValue({ + user: { id: 'a1', email: 'admin@solelog.local', name: 'Admin', role: 'admin' }, + }); + window.history.replaceState(null, '', '/activities'); + expect(window.location.pathname).toBe('/activities'); + + const user = userEvent.setup(); + renderHarness(); + await user.click(screen.getByRole('button', { name: 'go' })); + await waitFor(() => expect(screen.getByTestId('authed')).toHaveTextContent('true')); + + await user.click(screen.getByRole('button', { name: 'uit' })); + + await waitFor(() => expect(screen.getByTestId('authed')).toHaveTextContent('false')); + expect(window.location.pathname).toBe('/'); + expect(getToken()).toBeNull(); + }); }); diff --git a/apps/admin/src/auth/AuthContext.tsx b/apps/admin/src/auth/AuthContext.tsx index 304bf4b..14cf91a 100644 --- a/apps/admin/src/auth/AuthContext.tsx +++ b/apps/admin/src/auth/AuthContext.tsx @@ -34,6 +34,9 @@ export function AuthProvider({ children }: { children: ReactNode }) { }, []); const signOut = useCallback(() => { + // Reset the path to / so the next authed mount lands on Live, not whatever tab + // (e.g. Account/Activities) the admin happened to log out from. + window.history.replaceState(null, '', '/'); clearToken(); setIsAuthed(false); }, []); diff --git a/apps/admin/src/screens/Live.test.tsx b/apps/admin/src/screens/Live.test.tsx index 8dcdc39..f99e7eb 100644 --- a/apps/admin/src/screens/Live.test.tsx +++ b/apps/admin/src/screens/Live.test.tsx @@ -23,6 +23,8 @@ function makeSession(over: Partial): WorkSession { start_time: new Date(Date.now() - 65_000).toISOString(), end_time: null, duration_seconds: null, + paused_seconds: 0, + paused_at: null, status: 'active', source: 'app', notes: null, diff --git a/apps/admin/src/screens/Live.tsx b/apps/admin/src/screens/Live.tsx index 7ae3511..782b4a7 100644 --- a/apps/admin/src/screens/Live.tsx +++ b/apps/admin/src/screens/Live.tsx @@ -49,7 +49,13 @@ export default function Live() { } function LiveCard({ session, now }: { session: WorkSession; now: number }) { - const elapsed = formatTime((now - Date.parse(session.start_time)) / 1000); + // While paused the worked timer freezes at the pause moment; otherwise it counts to now. + // Worked = (base - start) - paused_seconds, where base is the pause moment when paused. + const base = session.paused_at ? Date.parse(session.paused_at) : now; + const worked = Math.max( + 0, + Math.floor((base - Date.parse(session.start_time)) / 1000) - session.paused_seconds + ); return (
@@ -58,7 +64,11 @@ function LiveCard({ session, now }: { session: WorkSession; now: number }) {
{session.activity_name ?? 'Onbekende handeling'}
{session.pair_count} zolen
-
{elapsed}
+
{formatTime(worked)}
+ {session.paused_at && Gepauzeerd} + {session.paused_seconds > 0 && ( + Pauze {formatTime(session.paused_seconds)} + )}
); } diff --git a/apps/admin/src/styles.css b/apps/admin/src/styles.css index f58e117..8b6e3da 100644 --- a/apps/admin/src/styles.css +++ b/apps/admin/src/styles.css @@ -404,3 +404,19 @@ body { font-variant-numeric: tabular-nums; color: var(--text); } + +.live-badge-paused { + align-self: flex-start; + font-size: 12px; + font-weight: 600; + color: var(--amber); + background: #fef3c7; + border-radius: 999px; + padding: 4px 10px; +} + +.live-paused-total { + font-size: 13px; + color: var(--text-muted); + font-variant-numeric: tabular-nums; +}