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 controls = qrControlsRef.current;
qrControlsRef.current = null;
const scannerSessionRef = useRef(0);
const stopScanner = () => {
// Invalide immédiatement toute session de scan en cours
scannerSessionRef.current += 1;
// Arrêter les contrôles ZXing
if (qrControlsRef.current) {
try {
controls?.stop();
} catch (stopError) {
console.warn('Arrêt du lecteur QR déjà effectué.', stopError);
qrControlsRef.current.stop();
} catch (error) {
console.warn('Impossible d’arrêter le scanner QR.', error);
}
if (videoRef.current) {
videoRef.current.pause();
qrControlsRef.current = null;
}
// 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) {
// 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,26 +334,51 @@ 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 {
controls?.stop();
} catch (stopError) {
qrControlsRef.current.stop();
} catch (error) {
console.warn(
'Arrêt du lecteur QR ignoré.',
stopError,
'Impossible d’arrêter les contrôles QR.',
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]);
@ -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 = () => {
<Dialog
open={isScannerDialogOpen}
onOpenChange={(open) => {
setIsScannerDialogOpen(open);
if (!open) {
stopScanner();
if (open) {
setIsScannerDialogOpen(true);
} else {
closeScanner();
}
}}
>
@ -412,7 +505,7 @@ const TimeClockPage = () => {
/>
<Button
onClick={() => setIsScannerDialogOpen(false)}
onClick={closeScanner}
variant="secondary"
className="mt-2"
>