fix perm and camera

This commit is contained in:
Louis Mazin 2026-09-19 16:11:22 +02:00
parent ebf0d5b6ec
commit bb0f242dd5

View File

@ -150,25 +150,54 @@ const TimeClockPage = () => {
} }
}; };
const stopScanner = () => { const scannerSessionRef = useRef(0);
const controls = qrControlsRef.current;
qrControlsRef.current = null;
const stopScanner = () => {
// Invalide immédiatement toute session de scan en cours
scannerSessionRef.current += 1;
// Arrêter les contrôles ZXing
if (qrControlsRef.current) {
try { try {
controls?.stop(); qrControlsRef.current.stop();
} catch (stopError) { } catch (error) {
console.warn('Arrêt du lecteur QR déjà effectué.', stopError); console.warn('Impossible d’arrêter le scanner QR.', error);
} }
if (videoRef.current) { qrControlsRef.current = null;
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; videoRef.current.srcObject = null;
} }
}; };
const handleQrResult = async (code) => { const closeScanner = () => {
setScanConflict(null);
stopScanner(); 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 { try {
const response = await timeApi.scanTimeEntry(code); const response = await timeApi.scanTimeEntry(code);
@ -176,7 +205,11 @@ const TimeClockPage = () => {
await loadTimeEntries(); await loadTimeEntries();
} catch (scanError) { } catch (scanError) {
if (scanError.code === 409 && scanError.conflict) { if (scanError.code === 409 && scanError.conflict) {
setScanConflict({ code, type: scanError.conflict, message: scanError.message }); setScanConflict({
code,
type: scanError.conflict,
message: scanError.message,
});
} else { } else {
setError(scanError.message); setError(scanError.message);
} }
@ -185,12 +218,15 @@ const TimeClockPage = () => {
const resolveScanConflict = async (resolution) => { const resolveScanConflict = async (resolution) => {
if (!scanConflict) return; if (!scanConflict) return;
setError(''); setError('');
try { try {
const response = await timeApi.scanTimeEntry(scanConflict.code, { const response = await timeApi.scanTimeEntry(scanConflict.code, {
resolution, resolution,
time: conflictTime, time: conflictTime,
}); });
setScanMessage(response.message); setScanMessage(response.message);
setScanConflict(null); setScanConflict(null);
await loadTimeEntries(); await loadTimeEntries();
@ -199,26 +235,37 @@ const TimeClockPage = () => {
} }
}; };
useEffect(() => () => stopScanner(), []); // Nettoyage lors du démontage
useEffect(() => {
return () => {
stopScanner();
};
}, []);
// Gestion du scanner QR
useEffect(() => { useEffect(() => {
if (!isScannerDialogOpen) { if (!isScannerDialogOpen) {
return undefined; return;
} }
const sessionId = scannerSessionRef.current;
let cancelled = false; let cancelled = false;
let reader = null; let reader = null;
const startReader = async () => { const startReader = async () => {
// Attendre que Radix ait réellement monté le DialogContent // Attendre que DialogContent soit réellement monté
await new Promise((resolve) => { await new Promise((resolve) => {
requestAnimationFrame(() => { requestAnimationFrame(() => {
requestAnimationFrame(resolve); requestAnimationFrame(resolve);
}); });
}); });
if (cancelled || !videoRef.current) { // Le dialog a pu être fermé pendant l’attente
console.warn('Vidéo du scanner non disponible.'); if (
cancelled ||
scannerSessionRef.current !== sessionId ||
!videoRef.current
) {
return; return;
} }
@ -234,33 +281,51 @@ const TimeClockPage = () => {
}, },
videoRef.current, videoRef.current,
(result) => { (result) => {
if (!result || cancelled) { if (
!result ||
cancelled ||
scannerSessionRef.current !== sessionId
) {
return; return;
} }
handleQrResult(result.getText()); handleQrResult(result.getText(), sessionId);
setIsScannerDialogOpen(false); }
},
); );
if (cancelled) { // La fermeture peut avoir eu lieu pendant l’initialisation
if (
cancelled ||
scannerSessionRef.current !== sessionId
) {
try {
controls.stop(); controls.stop();
} catch (error) {
console.warn(
'Impossible d’arrêter le scanner après annulation.',
error
);
}
return; return;
} }
qrControlsRef.current = controls; qrControlsRef.current = controls;
} catch (scannerError) { } catch (scannerError) {
if (cancelled) { if (
cancelled ||
scannerSessionRef.current !== sessionId
) {
return; return;
} }
console.error('Erreur scanner QR:', scannerError); console.error('Erreur scanner QR:', scannerError);
setError( 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,26 +334,51 @@ const TimeClockPage = () => {
return () => { return () => {
cancelled = true; cancelled = true;
// Invalide cette session
if (scannerSessionRef.current === sessionId) {
scannerSessionRef.current += 1;
}
try { try {
reader?.reset(); reader?.reset();
} catch (resetError) { } catch (error) {
console.warn( console.warn(
'Réinitialisation du lecteur QR ignorée.', 'Réinitialisation du lecteur QR ignorée.',
resetError, error
); );
} }
const controls = qrControlsRef.current; // Arrêter les contrôles ZXing
qrControlsRef.current = null; if (qrControlsRef.current) {
try { try {
controls?.stop(); qrControlsRef.current.stop();
} catch (stopError) { } catch (error) {
console.warn( console.warn(
'Arrêt du lecteur QR ignoré.', 'Impossible d’arrêter les contrôles QR.',
stopError, error
); );
} }
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]); }, [isScannerDialogOpen]);
@ -303,7 +393,7 @@ const TimeClockPage = () => {
window.location.hostname !== '192.168.1.19' window.location.hostname !== '192.168.1.19'
) { ) {
setError( 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; return;
} }
@ -313,6 +403,9 @@ const TimeClockPage = () => {
return; return;
} }
// Nouvelle session de scanner
scannerSessionRef.current += 1;
setIsScannerDialogOpen(true); setIsScannerDialogOpen(true);
}; };
@ -377,10 +470,10 @@ const TimeClockPage = () => {
<Dialog <Dialog
open={isScannerDialogOpen} open={isScannerDialogOpen}
onOpenChange={(open) => { onOpenChange={(open) => {
setIsScannerDialogOpen(open); if (open) {
setIsScannerDialogOpen(true);
if (!open) { } else {
stopScanner(); closeScanner();
} }
}} }}
> >
@ -412,7 +505,7 @@ const TimeClockPage = () => {
/> />
<Button <Button
onClick={() => setIsScannerDialogOpen(false)} onClick={closeScanner}
variant="secondary" variant="secondary"
className="mt-2" className="mt-2"
> >