codigo0/src/components/tools/RCPTimer.tsx

199 lines
6.6 KiB
TypeScript
Raw Normal View History

import { useState, useEffect, useRef } from 'react';
import { Play, Pause, RotateCcw, AlertTriangle, Clock, Info } from 'lucide-react';
import { Button } from '@/components/ui/button';
import Badge from '@/components/shared/Badge';
const RCPTimer = () => {
const [isRunning, setIsRunning] = useState(false);
const [elapsedTime, setElapsedTime] = useState(0); // en segundos
const [cycles, setCycles] = useState(0);
const [lastCycleTime, setLastCycleTime] = useState(0);
const intervalRef = useRef<NodeJS.Timeout | null>(null);
const audioRef = useRef<HTMLAudioElement | null>(null);
// Ciclo de RCP: 2 minutos = 120 segundos
const CYCLE_DURATION = 120;
useEffect(() => {
if (isRunning) {
intervalRef.current = setInterval(() => {
setElapsedTime((prev) => {
const newTime = prev + 1;
const cycleTime = newTime - lastCycleTime;
// Alerta cada 2 minutos (cambio de reanimador)
if (cycleTime >= CYCLE_DURATION) {
setCycles((prev) => prev + 1);
setLastCycleTime(newTime);
playAlert();
}
return newTime;
});
}, 1000);
} else {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
}
return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
};
}, [isRunning, lastCycleTime]);
const playAlert = () => {
// Crear audio para alerta (usando Web Audio API)
if (typeof Audio !== 'undefined') {
const audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
oscillator.frequency.value = 800;
oscillator.type = 'sine';
gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.5);
oscillator.start(audioContext.currentTime);
oscillator.stop(audioContext.currentTime + 0.5);
}
};
const handleStart = () => {
setIsRunning(true);
if (elapsedTime === 0) {
setLastCycleTime(0);
}
};
const handlePause = () => {
setIsRunning(false);
};
const handleReset = () => {
setIsRunning(false);
setElapsedTime(0);
setCycles(0);
setLastCycleTime(0);
};
const formatTime = (seconds: number): string => {
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
};
const cycleTime = elapsedTime - lastCycleTime;
const timeUntilNextCycle = CYCLE_DURATION - cycleTime;
const progress = (cycleTime / CYCLE_DURATION) * 100;
return (
<div className="card-procedure">
<h3 className="font-bold text-foreground text-lg mb-4">
Temporizador de RCP
</h3>
<div className="space-y-4">
{/* Información */}
<div className="p-3 bg-muted/50 rounded-lg border border-border">
<div className="flex items-start gap-2">
<Info className="w-5 h-5 text-info mt-0.5 flex-shrink-0" />
<div className="text-sm text-muted-foreground">
<p className="font-semibold text-foreground mb-1">Ciclos de RCP:</p>
<p>Cada 2 minutos (120 segundos) se debe cambiar de reanimador para mantener calidad de compresiones.</p>
</div>
</div>
</div>
{/* Tiempo principal */}
<div className="p-6 bg-card border-2 border-primary rounded-xl text-center">
<div className="flex items-center justify-center gap-2 mb-2">
<Clock className="w-6 h-6 text-primary" />
<p className="text-muted-foreground text-sm">Tiempo Total</p>
</div>
<p className="text-5xl font-bold text-foreground mb-2">
{formatTime(elapsedTime)}
</p>
<Badge variant="info" className="text-sm px-3 py-1">
Ciclos completados: {cycles}
</Badge>
</div>
{/* Progreso del ciclo actual */}
{isRunning && (
<div className="space-y-2">
<div className="flex items-center justify-between text-sm">
<span className="text-muted-foreground">Tiempo hasta cambio de reanimador</span>
<span className="font-bold text-foreground">
{formatTime(timeUntilNextCycle)}
</span>
</div>
<div className="w-full bg-muted rounded-full h-3 overflow-hidden">
<div
className="h-full bg-primary transition-all duration-1000"
style={{ width: `${progress}%` }}
/>
</div>
{timeUntilNextCycle <= 10 && timeUntilNextCycle > 0 && (
<div className="p-3 bg-[hsl(var(--emergency-high))]/10 border border-[hsl(var(--emergency-high))]/30 rounded-lg">
<div className="flex items-center gap-2">
<AlertTriangle className="w-5 h5 text-[hsl(var(--emergency-high))]" />
<p className="text-sm text-[hsl(var(--emergency-high))] font-semibold">
¡Cambio de reanimador en {timeUntilNextCycle} segundos!
</p>
</div>
</div>
)}
</div>
)}
{/* Controles */}
<div className="flex gap-2">
{!isRunning ? (
<Button
onClick={handleStart}
className="flex-1 bg-primary text-primary-foreground"
>
<Play className="w-4 h-4 mr-2" />
Iniciar
</Button>
) : (
<Button
onClick={handlePause}
variant="outline"
className="flex-1"
>
<Pause className="w-4 h-4 mr-2" />
Pausar
</Button>
)}
<Button
onClick={handleReset}
variant="outline"
className="flex-1"
>
<RotateCcw className="w-4 h-4 mr-2" />
Reiniciar
</Button>
</div>
{/* Instrucciones */}
<div className="p-3 bg-muted/50 rounded-lg border border-border">
<p className="text-xs text-muted-foreground">
<strong>Uso:</strong> Iniciar cuando comience RCP. El temporizador alertará cada 2 minutos para cambio de reanimador.
Pausar durante desfibrilación si es necesario.
</p>
</div>
</div>
</div>
);
};
export default RCPTimer;