diff --git a/frontend/src/pages/TimeClockPage.jsx b/frontend/src/pages/TimeClockPage.jsx index 07ad273..30a87aa 100644 --- a/frontend/src/pages/TimeClockPage.jsx +++ b/frontend/src/pages/TimeClockPage.jsx @@ -150,25 +150,54 @@ const TimeClockPage = () => { } }; - const stopScanner = () => { - const controls = qrControlsRef.current; - qrControlsRef.current = null; + const scannerSessionRef = useRef(0); - try { - controls?.stop(); - } catch (stopError) { - console.warn('Arrêt du lecteur QR déjà effectué.', stopError); + const stopScanner = () => { + // Invalide immédiatement toute session de scan en cours + scannerSessionRef.current += 1; + + // Arrêter les contrôles ZXing + if (qrControlsRef.current) { + try { + qrControlsRef.current.stop(); + } catch (error) { + console.warn('Impossible d’arrêter le scanner QR.', error); + } + + qrControlsRef.current = null; } - if (videoRef.current) { - videoRef.current.pause(); + // Arrêter explicitement la caméra + if (videoRef.current?.srcObject) { + const stream = videoRef.current.srcObject; + + stream.getTracks().forEach((track) => { + try { + track.stop(); + } catch (error) { + console.warn('Impossible d’arrêter la piste caméra.', error); + } + }); + videoRef.current.srcObject = null; } }; - const handleQrResult = async (code) => { - setScanConflict(null); + const closeScanner = () => { stopScanner(); + setIsScannerDialogOpen(false); + }; + + const handleQrResult = async (code, sessionId) => { + // Si cette session n’est plus active, on ignore le résultat + if (scannerSessionRef.current !== sessionId) { + return; + } + + // Fermer immédiatement la caméra + closeScanner(); + + setScanConflict(null); try { const response = await timeApi.scanTimeEntry(code); @@ -176,7 +205,11 @@ const TimeClockPage = () => { await loadTimeEntries(); } catch (scanError) { if (scanError.code === 409 && scanError.conflict) { - setScanConflict({ code, type: scanError.conflict, message: scanError.message }); + setScanConflict({ + code, + type: scanError.conflict, + message: scanError.message, + }); } else { setError(scanError.message); } @@ -185,12 +218,15 @@ const TimeClockPage = () => { const resolveScanConflict = async (resolution) => { if (!scanConflict) return; + setError(''); + try { const response = await timeApi.scanTimeEntry(scanConflict.code, { resolution, time: conflictTime, }); + setScanMessage(response.message); setScanConflict(null); await loadTimeEntries(); @@ -199,26 +235,37 @@ const TimeClockPage = () => { } }; - useEffect(() => () => stopScanner(), []); + // Nettoyage lors du démontage + useEffect(() => { + return () => { + stopScanner(); + }; + }, []); + // Gestion du scanner QR useEffect(() => { if (!isScannerDialogOpen) { - return undefined; + return; } + const sessionId = scannerSessionRef.current; let cancelled = false; let reader = null; const startReader = async () => { - // Attendre que Radix ait réellement monté le DialogContent + // Attendre que DialogContent soit réellement monté await new Promise((resolve) => { requestAnimationFrame(() => { requestAnimationFrame(resolve); }); }); - if (cancelled || !videoRef.current) { - console.warn('Vidéo du scanner non disponible.'); + // Le dialog a pu être fermé pendant l’attente + if ( + cancelled || + scannerSessionRef.current !== sessionId || + !videoRef.current + ) { return; } @@ -234,33 +281,51 @@ const TimeClockPage = () => { }, videoRef.current, (result) => { - if (!result || cancelled) { + if ( + !result || + cancelled || + scannerSessionRef.current !== sessionId + ) { return; } - handleQrResult(result.getText()); - setIsScannerDialogOpen(false); - }, + handleQrResult(result.getText(), sessionId); + } ); - if (cancelled) { - controls.stop(); + // La fermeture peut avoir eu lieu pendant l’initialisation + if ( + cancelled || + scannerSessionRef.current !== sessionId + ) { + try { + controls.stop(); + } catch (error) { + console.warn( + 'Impossible d’arrêter le scanner après annulation.', + error + ); + } + return; } qrControlsRef.current = controls; } catch (scannerError) { - if (cancelled) { + if ( + cancelled || + scannerSessionRef.current !== sessionId + ) { return; } console.error('Erreur scanner QR:', scannerError); setError( - 'Impossible d’accéder à la caméra. Vérifiez les permissions et utilisez HTTPS sur le réseau.', + 'Impossible d’accéder à la caméra. Vérifiez les permissions et utilisez HTTPS sur le réseau.' ); - stopScanner(); + closeScanner(); } }; @@ -269,25 +334,50 @@ const TimeClockPage = () => { return () => { cancelled = true; + // Invalide cette session + if (scannerSessionRef.current === sessionId) { + scannerSessionRef.current += 1; + } + try { reader?.reset(); - } catch (resetError) { + } catch (error) { console.warn( 'Réinitialisation du lecteur QR ignorée.', - resetError, + error ); } - const controls = qrControlsRef.current; - qrControlsRef.current = null; + // Arrêter les contrôles ZXing + if (qrControlsRef.current) { + try { + qrControlsRef.current.stop(); + } catch (error) { + console.warn( + 'Impossible d’arrêter les contrôles QR.', + error + ); + } - try { - controls?.stop(); - } catch (stopError) { - console.warn( - 'Arrêt du lecteur QR ignoré.', - stopError, - ); + qrControlsRef.current = null; + } + + // Arrêter explicitement toutes les pistes caméra + if (videoRef.current?.srcObject) { + const stream = videoRef.current.srcObject; + + stream.getTracks().forEach((track) => { + try { + track.stop(); + } catch (error) { + console.warn( + 'Impossible d’arrêter la piste caméra.', + error + ); + } + }); + + videoRef.current.srcObject = null; } }; }, [isScannerDialogOpen]); @@ -303,7 +393,7 @@ const TimeClockPage = () => { window.location.hostname !== '192.168.1.19' ) { setError( - 'La caméra nécessite une connexion HTTPS sur cette adresse réseau.', + 'La caméra nécessite une connexion HTTPS sur cette adresse réseau.' ); return; } @@ -313,6 +403,9 @@ const TimeClockPage = () => { return; } + // Nouvelle session de scanner + scannerSessionRef.current += 1; + setIsScannerDialogOpen(true); }; @@ -377,10 +470,10 @@ const TimeClockPage = () => { { - setIsScannerDialogOpen(open); - - if (!open) { - stopScanner(); + if (open) { + setIsScannerDialogOpen(true); + } else { + closeScanner(); } }} > @@ -412,7 +505,7 @@ const TimeClockPage = () => { />