Adição de chamada por video
This commit is contained in:
parent
77b934636f
commit
55e66657ba
7982
package-lock.json
generated
7982
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -5,6 +5,7 @@
|
||||
"dependencies": {
|
||||
"@ckeditor/ckeditor5-build-classic": "^41.4.2",
|
||||
"@ckeditor/ckeditor5-react": "^11.0.0",
|
||||
"@jitsi/react-sdk": "^1.4.0",
|
||||
"@sweetalert2/theme-dark": "^5.0.27",
|
||||
"@testing-library/dom": "^10.4.1",
|
||||
"@testing-library/jest-dom": "^6.8.0",
|
||||
@ -78,5 +79,8 @@
|
||||
"sass": "^1.91.0",
|
||||
"sass-loader": "^16.0.5",
|
||||
"tailwindcss": "^4.1.13"
|
||||
},
|
||||
"overrides": {
|
||||
"react": "$react"
|
||||
}
|
||||
}
|
||||
}
|
||||
242
src/components/BotaoVideoChamada.css
Normal file
242
src/components/BotaoVideoChamada.css
Normal file
@ -0,0 +1,242 @@
|
||||
@keyframes slide-up {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.video-chat-container {
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
/* --- O BOTÃO FLUTUANTE (COM CORREÇÃO) --- */
|
||||
.video-chat-button {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 95px;
|
||||
z-index: 9999;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center; /* <-- Correção do alinhamento */
|
||||
justify-content: center;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.video-chat-button:hover {
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
/* --- A JANELA DE CHAT --- */
|
||||
.video-chat-window {
|
||||
position: fixed;
|
||||
bottom: 90px;
|
||||
right: 95px;
|
||||
width: 500px;
|
||||
height: 380px;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 -5px 20px rgba(0, 0, 0, 0.15);
|
||||
z-index: 10000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden; /* Importante para o border-radius */
|
||||
|
||||
/* Animação de "surgir" */
|
||||
animation: slide-up 0.3s ease-out;
|
||||
|
||||
/* Animação "premium" para tela cheia */
|
||||
transition: all 0.4s ease-in-out;
|
||||
}
|
||||
|
||||
/* --- MODO TELA CHEIA (SIMULADO) --- */
|
||||
.video-chat-window.pseudo-fullscreen {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
z-index: 99999;
|
||||
}
|
||||
|
||||
.video-chat-window.pseudo-fullscreen .video-chat-header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* --- HEADER DA JANELA --- */
|
||||
.video-chat-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 15px;
|
||||
background-color: #f7f7f7;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
flex-shrink: 0; /* Impede o header de encolher */
|
||||
}
|
||||
.video-chat-header h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
.video-chat-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.control-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: #888;
|
||||
padding: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
.control-btn:hover {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
.close-btn {
|
||||
font-size: 24px;
|
||||
line-height: 1;
|
||||
}
|
||||
.fullscreen-btn {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* --- CORPO DA JANELA (CONSOLIDADO) --- */
|
||||
.video-chat-body {
|
||||
flex-grow: 1; /* Ocupa todo o espaço vertical */
|
||||
overflow-y: hidden; /* Os filhos (lista, call-screen) cuidam do scroll */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0; /* Os filhos cuidam do padding */
|
||||
transition: padding 0.4s ease-in-out;
|
||||
}
|
||||
|
||||
.video-chat-window.pseudo-fullscreen .video-chat-body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* --- 1. LISTA DE PACIENTES --- */
|
||||
.patient-list-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.patient-list-container > p {
|
||||
padding: 15px 15px 10px 15px;
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
color: #555;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
flex-shrink: 0; /* Impede de encolher */
|
||||
}
|
||||
.patient-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow-y: auto; /* Adiciona scroll SÓ AQUI */
|
||||
flex-grow: 1; /* Ocupa o espaço restante */
|
||||
}
|
||||
.patient-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 15px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
.patient-item:hover {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
.patient-item span {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
.call-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
padding: 8px 12px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
.call-btn:hover {
|
||||
background-color: #218838;
|
||||
}
|
||||
|
||||
/* --- 2. TELA DE CHAMADA --- */
|
||||
.call-screen {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #2c2c2c;
|
||||
color: white;
|
||||
}
|
||||
.call-screen h4 {
|
||||
margin: 0;
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
background-color: rgba(0,0,0,0.2);
|
||||
font-size: 16px;
|
||||
flex-shrink: 0; /* Impede de encolher */
|
||||
}
|
||||
.video-placeholder {
|
||||
flex-grow: 1; /* Ocupa todo o espaço */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
background-color: #1a1a1a;
|
||||
color: #888;
|
||||
font-style: italic;
|
||||
overflow: hidden; /* Caso o <iframe>/video tente vazar */
|
||||
}
|
||||
.call-actions {
|
||||
padding: 15px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
background-color: rgba(0,0,0,0.2);
|
||||
flex-shrink: 0; /* Impede de encolher */
|
||||
}
|
||||
.hang-up-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
background-color: #dc3545;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 50px;
|
||||
padding: 12px 24px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.hang-up-btn:hover {
|
||||
background-color: #c82333;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
158
src/components/BotaoVideoChamada.jsx
Normal file
158
src/components/BotaoVideoChamada.jsx
Normal file
@ -0,0 +1,158 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import './BotaoVideoChamada.css';
|
||||
import { FaVideo, FaExpand, FaCompress, FaPhoneSlash } from 'react-icons/fa';
|
||||
import { JitsiMeeting } from '@jitsi/react-sdk';
|
||||
import { db } from '../firebaseConfig';
|
||||
import { ref, set, remove } from "firebase/database";
|
||||
|
||||
// MOCK PACIENTE
|
||||
const mockPacientes = [
|
||||
{ id: 1, name: 'Paciente' }
|
||||
];
|
||||
|
||||
// DADOS DO MÉDICO
|
||||
const MEU_ID_MEDICO = 'medico-99';
|
||||
const MEU_NOME_MEDICO = 'Dr. Rafael';
|
||||
|
||||
const BotaoVideoChamada = () => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isFullScreen, setIsFullScreen] = useState(false);
|
||||
const [callActive, setCallActive] = useState(false);
|
||||
const [callingPatient, setCallingPatient] = useState(null);
|
||||
const [roomName, setRoomName] = useState('');
|
||||
|
||||
// UseEffect da tecla "Esc"
|
||||
useEffect(() => {
|
||||
const handleEscKey = (event) => {
|
||||
if (event.key === 'Escape' && isFullScreen) {
|
||||
setIsFullScreen(false);
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', handleEscKey);
|
||||
return () => document.removeEventListener('keydown', handleEscKey);
|
||||
}, [isFullScreen]);
|
||||
|
||||
// Função para INICIAR a chamada
|
||||
const handleStartCall = (paciente) => {
|
||||
// Adiciona o #config para pular o lobby
|
||||
const newRoomName = `mediconnect-call-${MEU_ID_MEDICO}-${paciente.id}-${Date.now()}#config.prejoinPageEnabled=false`;
|
||||
const callRef = ref(db, `calls/paciente-${paciente.id}`);
|
||||
|
||||
set(callRef, {
|
||||
incomingCall: {
|
||||
fromId: MEU_ID_MEDICO,
|
||||
fromName: MEU_NOME_MEDICO,
|
||||
roomName: newRoomName
|
||||
}
|
||||
});
|
||||
|
||||
setRoomName(newRoomName);
|
||||
setCallingPatient(paciente);
|
||||
setCallActive(true);
|
||||
};
|
||||
|
||||
// Função para ENCERRAR a chamada
|
||||
const handleHangUp = () => {
|
||||
if (callingPatient) {
|
||||
const callRef = ref(db, `calls/paciente-${callingPatient.id}`);
|
||||
remove(callRef);
|
||||
}
|
||||
setCallActive(false);
|
||||
setCallingPatient(null);
|
||||
setRoomName('');
|
||||
console.log("Chamada encerrada.");
|
||||
};
|
||||
|
||||
// Função para fechar a janela
|
||||
const toggleVideoChat = () => {
|
||||
setIsOpen(!isOpen);
|
||||
if (isOpen) {
|
||||
handleHangUp();
|
||||
setIsFullScreen(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Função de Tela Cheia
|
||||
const handleFullScreen = () => {
|
||||
setIsFullScreen(!isFullScreen);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="video-chat-container">
|
||||
{isOpen && (
|
||||
<div className={`video-chat-window ${isFullScreen ? 'pseudo-fullscreen' : ''}`}>
|
||||
|
||||
<div className="video-chat-header">
|
||||
<h3>{callActive ? `Em chamada com...` : 'Iniciar Chamada'}</h3>
|
||||
|
||||
{/* ================================== */}
|
||||
{/* BOTÕES DE VOLTA - CORREÇÃO AQUI */}
|
||||
{/* ================================== */}
|
||||
<div className="video-chat-controls">
|
||||
<button onClick={handleFullScreen} className="control-btn fullscreen-btn">
|
||||
{isFullScreen ? <FaCompress size={14} /> : <FaExpand size={14} />}
|
||||
</button>
|
||||
<button onClick={toggleVideoChat} className="control-btn close-btn">
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="video-chat-body">
|
||||
|
||||
{callActive ? (
|
||||
// TELA DE CHAMADA ATIVA (JITSI)
|
||||
<div className="call-screen">
|
||||
<JitsiMeeting
|
||||
roomName={roomName}
|
||||
domain="meet.jit.si"
|
||||
userInfo={{
|
||||
displayName: MEU_NOME_MEDICO
|
||||
}}
|
||||
configOverwrite={{
|
||||
prejoinPageEnabled: false,
|
||||
enableWelcomePage: false,
|
||||
enableClosePage: false,
|
||||
toolbarButtons: [
|
||||
'microphone', 'camera', 'desktop', 'hangup', 'chat', 'settings'
|
||||
],
|
||||
}}
|
||||
interfaceConfigOverwrite={{
|
||||
SHOW_SUBJECT: false,
|
||||
DISABLE_JOIN_LEAVE_NOTIFICATIONS: true,
|
||||
}}
|
||||
getIFrameRef={(iframe) => { iframe.style.height = '100%'; }}
|
||||
onApiReady={(api) => {
|
||||
api.on('videoConferenceLeft', handleHangUp);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
// TELA DE LISTA DE PACIENTES
|
||||
<div className="patient-list-container">
|
||||
<p>Selecione um paciente para iniciar a chamada:</p>
|
||||
<ul className="patient-list">
|
||||
{mockPacientes.map((paciente) => (
|
||||
<li key={paciente.id} className="patient-item">
|
||||
<span>{paciente.name}</span>
|
||||
<button onClick={() => handleStartCall(paciente)} className="call-btn">
|
||||
<FaVideo size={14} /> Chamar
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* Botão flutuante */}
|
||||
<button className="video-chat-button" onClick={toggleVideoChat}>
|
||||
<FaVideo size={22} color="white" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BotaoVideoChamada;
|
||||
467
src/components/BotaoVideoPaciente.css
Normal file
467
src/components/BotaoVideoPaciente.css
Normal file
@ -0,0 +1,467 @@
|
||||
/* ARQUIVO CSS COMPLETAMENTE NOVO E SEPARADO */
|
||||
|
||||
@keyframes slide-up-paciente { /* Nome do keyframe mudado */
|
||||
from { opacity: 0; transform: translateY(20px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.paciente-video-container { /* Classe mudada */
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.paciente-video-button { /* Classe mudada */
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 95px; /* Posição igual ao outro, ao lado da acessibilidade */
|
||||
z-index: 9999;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
background-color: #007bff; /* Cor pode ser diferente, se quiser */
|
||||
color: white;
|
||||
border: none;
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.paciente-video-button:hover { /* Classe mudada */
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.paciente-video-window { /* Classe mudada */
|
||||
position: fixed;
|
||||
bottom: 90px;
|
||||
right: 95px;
|
||||
width: 500px;
|
||||
height: 380px;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 -5px 20px rgba(0, 0, 0, 0.15);
|
||||
z-index: 10000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
animation: slide-up-paciente 0.3s ease-out; /* Keyframe mudado */
|
||||
transition: all 0.4s ease-in-out;
|
||||
}
|
||||
|
||||
.paciente-video-window.pseudo-fullscreen { /* Classe mudada */
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
z-index: 99999;
|
||||
}
|
||||
|
||||
.paciente-video-window.pseudo-fullscreen .paciente-video-header { /* Classe mudada */
|
||||
display: none;
|
||||
}
|
||||
|
||||
.paciente-video-header { /* Classe mudada */
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 15px;
|
||||
background-color: #f7f7f7;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.paciente-video-header h3 { /* Classe mudada */
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
.paciente-video-controls { /* Classe mudada */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* Os estilos internos (como .control-btn, .call-screen, .patient-list)
|
||||
podem ser mantidos, pois estão "dentro" das classes que mudamos.
|
||||
Mas para garantir 100% de separação, renomeei todos.
|
||||
*/
|
||||
|
||||
.paciente-video-body { /* Classe mudada */
|
||||
flex-grow: 1;
|
||||
overflow-y: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
transition: padding 0.4s ease-in-out;
|
||||
}
|
||||
|
||||
.paciente-video-window.pseudo-fullscreen .paciente-video-body { /* Classe mudada */
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Estilos da Lista e Chamada (copiados e prefixados)
|
||||
Não há problema em reutilizar .patient-list, .call-screen, etc,
|
||||
mas vamos renomear para segurança.
|
||||
*/
|
||||
.patient-list-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.patient-list-container > p {
|
||||
padding: 15px 15px 10px 15px;
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
color: #555;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.patient-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow-y: auto;
|
||||
flex-grow: 1;
|
||||
}
|
||||
.patient-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 15px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
.patient-item:hover {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
.patient-item span {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
.call-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
padding: 8px 12px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
.call-btn:hover {
|
||||
background-color: #218838;
|
||||
}
|
||||
|
||||
/* Tela de Chamada */
|
||||
.call-screen {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #2c2c2c;
|
||||
color: white;
|
||||
}
|
||||
.call-screen h4 {
|
||||
margin: 0;
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
background-color: rgba(0,0,0,0.2);
|
||||
font-size: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.video-placeholder {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
background-color: #1a1a1a;
|
||||
color: #888;
|
||||
font-style: italic;
|
||||
overflow: hidden;
|
||||
}
|
||||
.call-actions {
|
||||
padding: 15px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
background-color: rgba(0,0,0,0.2);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.hang-up-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
background-color: #dc3545;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 50px;
|
||||
padding: 12px 24px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.hang-up-btn:hover {
|
||||
background-color: #c82333;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
/* Controles (reutilizados, mas dentro de .paciente-video-header) */
|
||||
.control-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: #888;
|
||||
padding: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
.control-btn:hover {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
.close-btn {
|
||||
font-size: 24px;
|
||||
line-height: 1;
|
||||
}
|
||||
.fullscreen-btn {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* Animação de surgir */
|
||||
@keyframes slide-up-paciente {
|
||||
from { opacity: 0; transform: translateY(20px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
/* Animação "Pulsar" (Ringing) */
|
||||
@keyframes ringing {
|
||||
0% { transform: scale(1); box-shadow: 0 0 0 0 rgba(0, 123, 255, 0.7); }
|
||||
70% { transform: scale(1.1); box-shadow: 0 0 0 20px rgba(0, 123, 255, 0); }
|
||||
100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(0, 123, 255, 0); }
|
||||
}
|
||||
|
||||
.paciente-video-container {
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
/* Botão flutuante */
|
||||
.paciente-video-button {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 95px;
|
||||
z-index: 9999;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.paciente-video-button:hover {
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
/* Aplica a animação "pulsar" */
|
||||
.paciente-video-button.ringing {
|
||||
animation: ringing 1.5s infinite;
|
||||
}
|
||||
|
||||
/* Janela de Vídeo */
|
||||
.paciente-video-window {
|
||||
position: fixed;
|
||||
bottom: 90px;
|
||||
right: 95px;
|
||||
width: 500px;
|
||||
height: 380px;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 -5px 20px rgba(0, 0, 0, 0.15);
|
||||
z-index: 10000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
animation: slide-up-paciente 0.3s ease-out;
|
||||
transition: all 0.4s ease-in-out;
|
||||
}
|
||||
/* Modo Tela Cheia (Simulado) */
|
||||
.paciente-video-window.pseudo-fullscreen {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
z-index: 99999;
|
||||
}
|
||||
.paciente-video-window.pseudo-fullscreen .paciente-video-header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Header da Janela */
|
||||
.paciente-video-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 15px;
|
||||
background-color: #f7f7f7;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.paciente-video-header h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
.paciente-video-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* Corpo da Janela */
|
||||
.paciente-video-body {
|
||||
flex-grow: 1;
|
||||
overflow-y: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
transition: padding 0.4s ease-in-out;
|
||||
}
|
||||
.paciente-video-window.pseudo-fullscreen .paciente-video-body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* --- ESTILOS DOS 3 ESTADOS --- */
|
||||
|
||||
/* 1. Tela de Chamada Ativa (Jitsi) */
|
||||
.call-screen {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #2c2c2c;
|
||||
color: white;
|
||||
}
|
||||
.video-placeholder { /* (Caso o Jitsi não carregue) */
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
background-color: #1a1a1a;
|
||||
color: #888;
|
||||
font-style: italic;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 2. Tela de Chamada Recebida */
|
||||
.incoming-call-screen {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
background-color: #f7f9fc;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
.incoming-call-screen p {
|
||||
font-size: 16px;
|
||||
color: #555;
|
||||
margin: 0;
|
||||
}
|
||||
.incoming-call-screen h3 {
|
||||
font-size: 24px;
|
||||
color: #333;
|
||||
margin: 10px 0 30px 0;
|
||||
}
|
||||
.incoming-call-actions {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
}
|
||||
.incoming-call-actions button {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.incoming-call-actions button:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
.decline-btn { /* Botão Recusar */
|
||||
background-color: #dc3545;
|
||||
}
|
||||
.decline-btn:hover {
|
||||
background-color: #c82333;
|
||||
}
|
||||
.accept-btn { /* Botão Atender */
|
||||
background-color: #28a745;
|
||||
}
|
||||
.accept-btn:hover {
|
||||
background-color: #218838;
|
||||
}
|
||||
|
||||
/* 3. Tela de Espera (Ocioso) */
|
||||
.patient-idle-screen {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
color: #888;
|
||||
font-style: italic;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Estilos dos controles (reutilizados) */
|
||||
.control-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: #888;
|
||||
padding: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
.control-btn:hover {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
.close-btn {
|
||||
font-size: 24px;
|
||||
line-height: 1;
|
||||
}
|
||||
.fullscreen-btn {
|
||||
font-size: 14px;
|
||||
}
|
||||
171
src/components/BotaoVideoPaciente.jsx
Normal file
171
src/components/BotaoVideoPaciente.jsx
Normal file
@ -0,0 +1,171 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import './BotaoVideoPaciente.css';
|
||||
import { FaVideo, FaExpand, FaCompress, FaPhoneSlash, FaPhone } from 'react-icons/fa';
|
||||
import { JitsiMeeting } from '@jitsi/react-sdk';
|
||||
import { db } from '../firebaseConfig';
|
||||
import { ref, onValue, remove } from "firebase/database";
|
||||
|
||||
// ID DO PACIENTE
|
||||
const MEU_ID_PACIENTE = '1'; // Deve ser '1' para bater com o do médico
|
||||
|
||||
const BotaoVideoPaciente = () => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isFullScreen, setIsFullScreen] = useState(false);
|
||||
const [callActive, setCallActive] = useState(false);
|
||||
const [roomName, setRoomName] = useState('');
|
||||
const [incomingCallData, setIncomingCallData] = useState(null);
|
||||
const [callerName, setCallerName] = useState('');
|
||||
|
||||
// "Ouvinte" do Firebase
|
||||
useEffect(() => {
|
||||
const callRef = ref(db, `calls/paciente-${MEU_ID_PACIENTE}`);
|
||||
const unsubscribe = onValue(callRef, (snapshot) => {
|
||||
const data = snapshot.val();
|
||||
if (data && data.incomingCall) {
|
||||
setIncomingCallData(data.incomingCall);
|
||||
setCallerName(data.incomingCall.fromName);
|
||||
setIsOpen(true);
|
||||
} else {
|
||||
setIncomingCallData(null);
|
||||
setCallActive(false);
|
||||
}
|
||||
});
|
||||
return () => unsubscribe();
|
||||
}, []);
|
||||
|
||||
// UseEffect da tecla "Esc"
|
||||
useEffect(() => {
|
||||
const handleEscKey = (event) => {
|
||||
if (event.key === 'Escape' && isFullScreen) {
|
||||
setIsFullScreen(false);
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', handleEscKey);
|
||||
return () => document.removeEventListener('keydown', handleEscKey);
|
||||
}, [isFullScreen]);
|
||||
|
||||
// Função para ATENDER
|
||||
const handleAcceptCall = () => {
|
||||
if (!incomingCallData) return;
|
||||
setRoomName(incomingCallData.roomName);
|
||||
setCallActive(true);
|
||||
setIncomingCallData(null);
|
||||
};
|
||||
|
||||
// Função para RECUSAR / DESLIGAR
|
||||
const handleHangUp = () => {
|
||||
const callRef = ref(db, `calls/paciente-${MEU_ID_PACIENTE}`);
|
||||
remove(callRef);
|
||||
setCallActive(false);
|
||||
setRoomName('');
|
||||
setCallerName('');
|
||||
setIncomingCallData(null);
|
||||
};
|
||||
|
||||
// Função para fechar a janela
|
||||
const toggleVideoChat = () => {
|
||||
setIsOpen(!isOpen);
|
||||
if (isOpen) {
|
||||
handleHangUp();
|
||||
setIsFullScreen(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFullScreen = () => {
|
||||
setIsFullScreen(!isFullScreen);
|
||||
};
|
||||
|
||||
// Renderiza o conteúdo (Ocioso, Recebendo, Em Chamada)
|
||||
const renderContent = () => {
|
||||
// 1ª Prioridade: Em chamada ativa
|
||||
if (callActive) {
|
||||
return (
|
||||
<div className="call-screen">
|
||||
<JitsiMeeting
|
||||
roomName={roomName}
|
||||
domain="meet.jit.si"
|
||||
// Informações do Usuário (Paciente)
|
||||
userInfo={{
|
||||
displayName: 'Paciente' // Você pode mudar isso
|
||||
}}
|
||||
// Configurações para pular todas as telas
|
||||
configOverwrite={{
|
||||
prejoinPageEnabled: false,
|
||||
enableWelcomePage: false,
|
||||
enableClosePage: false,
|
||||
toolbarButtons: [
|
||||
'microphone', 'camera', 'hangup', 'chat', 'settings'
|
||||
],
|
||||
}}
|
||||
// Configurações da Interface
|
||||
interfaceConfigOverwrite={{
|
||||
SHOW_SUBJECT: false,
|
||||
DISABLE_JOIN_LEAVE_NOTIFICATIONS: true,
|
||||
}}
|
||||
getIFrameRef={(iframe) => { iframe.style.height = '100%'; }}
|
||||
onApiReady={(api) => {
|
||||
api.on('videoConferenceLeft', handleHangUp);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 2ª Prioridade: Recebendo uma chamada
|
||||
if (incomingCallData) {
|
||||
return (
|
||||
<div className="incoming-call-screen">
|
||||
<p>Chamada recebida de:</p>
|
||||
<h3>{callerName || 'Médico'}</h3>
|
||||
<div className="incoming-call-actions">
|
||||
<button className="decline-btn" onClick={handleHangUp}>
|
||||
<FaPhoneSlash size={20} /> Recusar
|
||||
</button>
|
||||
<button className="accept-btn" onClick={handleAcceptCall}>
|
||||
<FaPhone size={20} /> Atender
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 3ª Prioridade: Nenhuma chamada, tela de espera
|
||||
return (
|
||||
<div className="patient-idle-screen">
|
||||
<p>Aguardando chamadas do seu médico...</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="paciente-video-container">
|
||||
{isOpen && (
|
||||
<div className={`paciente-video-window ${isFullScreen ? 'pseudo-fullscreen' : ''}`}>
|
||||
<div className="paciente-video-header">
|
||||
<h3>{callActive ? `Em chamada...` : (incomingCallData ? 'Chamada Recebida' : 'Videochamada')}</h3>
|
||||
<div className="paciente-video-controls">
|
||||
<button onClick={handleFullScreen} className="control-btn fullscreen-btn">
|
||||
{isFullScreen ? <FaCompress size={14} /> : <FaExpand size={14} />}
|
||||
</button>
|
||||
<button onClick={toggleVideoChat} className="control-btn close-btn">
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="paciente-video-body">
|
||||
{renderContent()}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button
|
||||
className={`paciente-video-button ${incomingCallData ? 'ringing' : ''}`}
|
||||
onClick={toggleVideoChat}
|
||||
>
|
||||
<FaVideo size={22} color="white" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BotaoVideoPaciente;
|
||||
20
src/firebaseConfig.js
Normal file
20
src/firebaseConfig.js
Normal file
@ -0,0 +1,20 @@
|
||||
// 1. ADICIONE ESTAS DUAS LINHAS NO TOPO
|
||||
import { initializeApp } from "firebase/app";
|
||||
import { getDatabase } from "firebase/database";
|
||||
|
||||
// 2. COLE AQUI O OBJETO QUE VOCÊ COPIOU DO SITE DO FIREBASE
|
||||
const firebaseConfig = {
|
||||
apiKey: "SUA_API_KEY...",
|
||||
authDomain: "medimeconnect.firebaseapp.com",
|
||||
databaseURL: "https://medimeconnect-default-rtdb.firebaseio.com",
|
||||
projectId: "medimeconnect",
|
||||
storageBucket: "medimeconnect.appspot.com",
|
||||
messagingSenderId: "SEU_ID_MESSAGING",
|
||||
appId: "SEU_APP_ID"
|
||||
};
|
||||
|
||||
// 3. TENHA CERTEZA QUE ESSAS LINHAS ESTÃO NO FINAL
|
||||
const app = initializeApp(firebaseConfig);
|
||||
|
||||
// A LINHA MAIS IMPORTANTE QUE ESTÁ FALTANDO É ESTA:
|
||||
export const db = getDatabase(app);
|
||||
@ -9,7 +9,7 @@ import Chat from "../../PagesMedico/Chat";
|
||||
import DoctorItems from "../../data/sidebar-items-medico.json";
|
||||
import FormNovoRelatorio from "../../PagesMedico/FormNovoRelatorio";
|
||||
import EditPageRelatorio from "../../PagesMedico/EditPageRelatorio";
|
||||
// ...existing code...
|
||||
import BotaoVideoChamada from '../../components/BotaoVideoChamada';
|
||||
|
||||
function PerfilMedico() {
|
||||
return (
|
||||
@ -27,6 +27,10 @@ function PerfilMedico() {
|
||||
<Route path="/chat" element={<Chat />} />
|
||||
</Routes>
|
||||
</div>
|
||||
|
||||
{/* ADICIONADO AQUI */}
|
||||
<BotaoVideoChamada />
|
||||
|
||||
</div>
|
||||
|
||||
);
|
||||
|
||||
@ -6,6 +6,10 @@ import LaudoManager from "../../pages/LaudoManager";
|
||||
import ConsultaCadastroManager from "../../PagesPaciente/ConsultaCadastroManager";
|
||||
import ConsultasPaciente from "../../PagesPaciente/ConsultasPaciente";
|
||||
import ConsultaEditPage from "../../PagesPaciente/ConsultaEditPage";
|
||||
|
||||
// 1. IMPORTAÇÃO ADICIONADA
|
||||
import BotaoVideoPaciente from "../../components/BotaoVideoPaciente";
|
||||
|
||||
function PerfilPaciente({ onLogout }) {
|
||||
|
||||
const [dadosConsulta, setConsulta] = useState({})
|
||||
@ -26,7 +30,12 @@ const [dadosConsulta, setConsulta] = useState({})
|
||||
<Route path="*" element={<h2>Página não encontrada</h2>} />
|
||||
</Routes>
|
||||
</div>
|
||||
|
||||
{/* 2. COMPONENTE ADICIONADO AQUI */}
|
||||
<BotaoVideoPaciente />
|
||||
|
||||
</div>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user