diff --git a/apps/admin/src/screens/Live.test.tsx b/apps/admin/src/screens/Live.test.tsx index 63a9428..bf1fefe 100644 --- a/apps/admin/src/screens/Live.test.tsx +++ b/apps/admin/src/screens/Live.test.tsx @@ -1,4 +1,5 @@ -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import type { WorkSession } from '@solelog/shared'; @@ -120,6 +121,58 @@ describe('Live', () => { expect(await screen.findByText('Pauze 00:02:05')).toBeInTheDocument(); }); + it('shows Stop and Annuleer buttons on an active card', async () => { + mockApiFetch.mockResolvedValue([makeSession({ id: 7, user_name: 'Jan' })]); + + renderLive(); + + expect(await screen.findByText('Jan')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Stop' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Annuleer' })).toBeInTheDocument(); + }); + + it('clicking Stop calls the stop endpoint for that session', async () => { + const user = userEvent.setup(); + mockApiFetch.mockImplementation((path: string) => { + if (path === '/api/admin/sessions/active') { + return Promise.resolve([makeSession({ id: 7, user_name: 'Jan' })]); + } + return Promise.resolve({}); + }); + + renderLive(); + + await user.click(await screen.findByRole('button', { name: 'Stop' })); + + await waitFor(() => { + expect(mockApiFetch).toHaveBeenCalledWith( + '/api/admin/sessions/7/stop', + expect.objectContaining({ method: 'POST' }), + ); + }); + }); + + it('clicking Annuleer calls the discard endpoint for that session', async () => { + const user = userEvent.setup(); + mockApiFetch.mockImplementation((path: string) => { + if (path === '/api/admin/sessions/active') { + return Promise.resolve([makeSession({ id: 7, user_name: 'Jan' })]); + } + return Promise.resolve({}); + }); + + renderLive(); + + await user.click(await screen.findByRole('button', { name: 'Annuleer' })); + + await waitFor(() => { + expect(mockApiFetch).toHaveBeenCalledWith( + '/api/admin/sessions/7/discard', + expect.objectContaining({ method: 'POST' }), + ); + }); + }); + it('keeps the timer counting (no Gepauzeerd badge) when not paused', async () => { vi.useFakeTimers(); mockApiFetch.mockResolvedValue([ diff --git a/apps/admin/src/screens/Live.tsx b/apps/admin/src/screens/Live.tsx index 1792492..711ca69 100644 --- a/apps/admin/src/screens/Live.tsx +++ b/apps/admin/src/screens/Live.tsx @@ -1,6 +1,10 @@ import { useEffect, useState } from 'react'; import type { WorkSession } from '@solelog/shared'; -import { useActiveSessions } from '../api/admin-sessions'; +import { + useActiveSessions, + useAdminDiscardSession, + useAdminStopSession, +} from '../api/admin-sessions'; import { formatTime } from '../lib/elapsed'; export default function Live() { @@ -56,6 +60,14 @@ function LiveCard({ session, now }: { session: WorkSession; now: number }) { 0, Math.floor((base - Date.parse(session.start_time)) / 1000) - session.paused_seconds, ); + + // Stop closes the session (server computes the duration); Annuleer discards it. + // Both hooks invalidate ['admin','sessions'], so the active query refetches and the + // card drops off the Live grid once the session is no longer active. + const stopSession = useAdminStopSession(); + const discardSession = useAdminDiscardSession(); + const busy = stopSession.isPending || discardSession.isPending; + return (
@@ -69,6 +81,24 @@ function LiveCard({ session, now }: { session: WorkSession; now: number }) { {session.paused_seconds > 0 && ( Pauze {formatTime(session.paused_seconds)} )} +
+ + +
); } diff --git a/apps/admin/src/styles.css b/apps/admin/src/styles.css index c0c4ccc..228eadf 100644 --- a/apps/admin/src/styles.css +++ b/apps/admin/src/styles.css @@ -421,6 +421,12 @@ body { font-variant-numeric: tabular-nums; } +.live-card-actions { + display: flex; + gap: 8px; + margin-top: 8px; +} + /* ---- Sessions management (Sessies) ---- */ .sessions-head { display: flex;