This commit is contained in:
Louis Mazin 2026-09-18 03:50:25 +02:00
parent 18bdc192f0
commit 29d1facc83

View File

@ -14,11 +14,8 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from
const TimeClockPage = () => { const TimeClockPage = () => {
const videoRef = useRef(null); const videoRef = useRef(null);
const streamRef = useRef(null);
const scanFrameRef = useRef(null);
const qrControlsRef = useRef(null); const qrControlsRef = useRef(null);
const { isAdmin } = useAuth(); const { isAdmin } = useAuth();
const [isScanning, setIsScanning] = useState(false);
const [scanMessage, setScanMessage] = useState(''); const [scanMessage, setScanMessage] = useState('');
const [scanConflict, setScanConflict] = useState(null); const [scanConflict, setScanConflict] = useState(null);
const [conflictTime, setConflictTime] = useState(new Date().toTimeString().slice(0, 5)); const [conflictTime, setConflictTime] = useState(new Date().toTimeString().slice(0, 5));
@ -163,16 +160,10 @@ const TimeClockPage = () => {
console.warn('Arrêt du lecteur QR déjà effectué.', stopError); console.warn('Arrêt du lecteur QR déjà effectué.', stopError);
} }
if (scanFrameRef.current) {
cancelAnimationFrame(scanFrameRef.current);
scanFrameRef.current = null;
}
streamRef.current?.getTracks().forEach((track) => track.stop());
streamRef.current = null;
if (videoRef.current) { if (videoRef.current) {
videoRef.current.pause();
videoRef.current.srcObject = null; videoRef.current.srcObject = null;
} }
setIsScanning(false);
}; };
const handleQrResult = async (code) => { const handleQrResult = async (code) => {
@ -211,14 +202,28 @@ const TimeClockPage = () => {
useEffect(() => () => stopScanner(), []); useEffect(() => () => stopScanner(), []);
useEffect(() => { useEffect(() => {
if (!isScannerDialogOpen || !isScanning || !videoRef.current) { if (!isScannerDialogOpen) {
return undefined; return undefined;
} }
let cancelled = false; let cancelled = false;
const reader = new BrowserQRCodeReader(); let reader = null;
const startReader = async () => { const startReader = async () => {
// Attendre que Radix ait réellement monté le DialogContent
await new Promise((resolve) => {
requestAnimationFrame(() => {
requestAnimationFrame(resolve);
});
});
if (cancelled || !videoRef.current) {
console.warn('Vidéo du scanner non disponible.');
return;
}
reader = new BrowserQRCodeReader();
try { try {
const controls = await reader.decodeFromConstraints( const controls = await reader.decodeFromConstraints(
{ {
@ -229,32 +234,32 @@ const TimeClockPage = () => {
}, },
videoRef.current, videoRef.current,
(result) => { (result) => {
if (result && !cancelled) { if (!result || cancelled) {
handleQrResult(result.getText()); return;
} }
handleQrResult(result.getText());
}, },
); );
if (cancelled) { if (cancelled) {
try { controls.stop();
controls.stop(); return;
} catch (stopError) {
console.warn(
'Arrêt du lecteur QR déjà effectué.',
stopError,
);
}
} else {
qrControlsRef.current = controls;
} }
qrControlsRef.current = controls;
} catch (scannerError) { } catch (scannerError) {
if (!cancelled) { if (cancelled) {
setError( return;
'Impossible d’accéder à la caméra. Vérifiez les permissions et utilisez HTTPS sur le réseau.',
);
console.error(scannerError);
stopScanner();
} }
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.',
);
stopScanner();
} }
}; };
@ -264,15 +269,27 @@ const TimeClockPage = () => {
cancelled = true; cancelled = true;
try { try {
reader.reset(); reader?.reset();
} catch (resetError) { } catch (resetError) {
console.warn( console.warn(
'Réinitialisation du lecteur QR ignorée.', 'Réinitialisation du lecteur QR ignorée.',
resetError, resetError,
); );
} }
const controls = qrControlsRef.current;
qrControlsRef.current = null;
try {
controls?.stop();
} catch (stopError) {
console.warn(
'Arrêt du lecteur QR ignoré.',
stopError,
);
}
}; };
}, [isScannerDialogOpen, isScanning]); }, [isScannerDialogOpen]);
const startScanner = () => { const startScanner = () => {
setError(''); setError('');
@ -295,7 +312,7 @@ const TimeClockPage = () => {
return; return;
} }
setIsScanning(true); setIsScannerDialogOpen(true);
}; };
const formatTime = (timeString) => { const formatTime = (timeString) => {
@ -366,35 +383,41 @@ const TimeClockPage = () => {
} }
}} }}
> >
<DialogTrigger asChild> <Button
<Button onClick={startScanner}
onClick={startScanner} variant="default"
variant="default" className="mb-4"
className="mb-4" >
> {isAdmin ? 'Pointer avec un QR code' : 'Pointer'}
{isAdmin ? 'Pointer avec un QR code' : 'Pointer'} </Button>
</Button>
</DialogTrigger> <DialogContent className="max-w-2xl">
<DialogContent className="max-w-2xl max-h-[90vh] overflow-auto">
<DialogHeader> <DialogHeader>
<DialogTitle>Aperçu de la caméra</DialogTitle> <DialogTitle>Aperçu de la caméra</DialogTitle>
</DialogHeader> </DialogHeader>
{isScanning ? (
<div className="scanner-panel"> <div className="scanner-panel">
<video ref={videoRef} className="scanner-video w-full h-auto" playsInline muted /> <video
<div className="scanner-frame" aria-hidden="true" /> ref={videoRef}
<Button onClick={stopScanner} variant="secondary" className="mt-2"> className="scanner-video h-auto w-full"
Annuler playsInline
</Button> muted
</div> autoPlay
) : ( />
<div className="text-center py-8">
<p>La caméra est prête à être utilisée</p> <div
<Button onClick={startScanner} variant="default" className="mt-4"> className="scanner-frame"
Démarrer le scanner aria-hidden="true"
</Button> />
</div>
)} <Button
onClick={() => setIsScannerDialogOpen(false)}
variant="secondary"
className="mt-2"
>
Annuler
</Button>
</div>
</DialogContent> </DialogContent>
</Dialog> </Dialog>