Relatorio em audio
This commit is contained in:
parent
4d2bca5641
commit
bc7a2ed7d8
@ -1,2 +1,2 @@
|
||||
export * from "./core/error.js";
|
||||
export * from "openai/core/error.js";
|
||||
//# sourceMappingURL=error.d.ts.map
|
||||
@ -46,7 +46,7 @@ type OverloadedParameters<T> = T extends ({
|
||||
* [1]: https://www.typescriptlang.org/tsconfig/#typeAcquisition
|
||||
*/
|
||||
/** @ts-ignore For users with \@types/node */
|
||||
type UndiciTypesRequestInit = NotAny<import('../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../../../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../../../../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../../../../../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../../../../../../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../../../../../../../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../../../../../../../../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../../../../../../../../../node_modules/undici-types/index.d.ts').RequestInit>;
|
||||
type UndiciTypesRequestInit = NotAny<import('../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../../../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../../../../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../../../../../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('undici-types').RequestInit> | NotAny<import('../../../../../../../../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../../../../../../../../node_modules/undici-types/index.d.ts').RequestInit> | NotAny<import('../../../../../../../../../../node_modules/undici-types/index.d.ts').RequestInit>;
|
||||
/** @ts-ignore For users with undici */
|
||||
type UndiciRequestInit = NotAny<import('../node_modules/undici/index.d.ts').RequestInit> | NotAny<import('../../node_modules/undici/index.d.ts').RequestInit> | NotAny<import('../../../node_modules/undici/index.d.ts').RequestInit> | NotAny<import('../../../../node_modules/undici/index.d.ts').RequestInit> | NotAny<import('../../../../../node_modules/undici/index.d.ts').RequestInit> | NotAny<import('../../../../../../node_modules/undici/index.d.ts').RequestInit> | NotAny<import('../../../../../../../node_modules/undici/index.d.ts').RequestInit> | NotAny<import('../../../../../../../../node_modules/undici/index.d.ts').RequestInit> | NotAny<import('../../../../../../../../../node_modules/undici/index.d.ts').RequestInit> | NotAny<import('../../../../../../../../../../node_modules/undici/index.d.ts').RequestInit>;
|
||||
/** @ts-ignore For users with \@types/bun */
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
export * from "./core/pagination.js";
|
||||
export * from "openai/core/pagination.js";
|
||||
//# sourceMappingURL=pagination.d.ts.map
|
||||
@ -1,2 +1,2 @@
|
||||
export * from "./completions/index.js";
|
||||
export * from "openai/resources/chat/completions/index.js";
|
||||
//# sourceMappingURL=completions.d.ts.map
|
||||
@ -1,2 +1,2 @@
|
||||
export * from "./core/uploads.js";
|
||||
export * from "openai/core/uploads.js";
|
||||
//# sourceMappingURL=uploads.d.ts.map
|
||||
@ -3,7 +3,7 @@ const multer = require('multer');
|
||||
const cors = require('cors');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const OpenAI = require('openai');
|
||||
const OpenAI = require('openai/index.js');
|
||||
require('dotenv').config();
|
||||
|
||||
const app = express();
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useNavigate, useLocation } from 'react-router-dom'; // <--- 1. ADICIONADO useLocation
|
||||
import html2pdf from 'html2pdf.js';
|
||||
import './styleMedico/NovoRelatorioAudio.css';
|
||||
|
||||
const NovoRelatorioAudio = () => {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation(); // <--- 2. Inicializa o hook para ler os dados enviados
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
@ -13,9 +14,33 @@ const NovoRelatorioAudio = () => {
|
||||
exam: '',
|
||||
diagnostico: '',
|
||||
conclusao: '',
|
||||
medico_nome: 'Dr.______________________'
|
||||
medico_nome: 'Dr._______________________________' // Ajuste conforme necessário
|
||||
});
|
||||
|
||||
// --- 3. NOVO BLOCO: Receber dados da Videochamada ---
|
||||
useEffect(() => {
|
||||
// Verifica se a navegação trouxe dados no "state"
|
||||
if (location.state && location.state.aiResult) {
|
||||
console.log("Dados recebidos da Videochamada:", location.state);
|
||||
|
||||
const { aiResult, paciente } = location.state;
|
||||
|
||||
// Atualiza o formulário com o que a IA gerou
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
// Se tiver paciente vindo da chamada, usa ele. Se não, mantém vazio.
|
||||
paciente_nome: paciente?.name || prev.paciente_nome,
|
||||
|
||||
// Dados da IA
|
||||
exam: aiResult.exam || 'Consulta Telemedicina',
|
||||
diagnostico: aiResult.diagnostico || '',
|
||||
conclusao: aiResult.conclusao || ''
|
||||
}));
|
||||
}
|
||||
}, [location]); // Executa assim que a página carrega
|
||||
|
||||
// --- FIM DO BLOCO NOVO ---
|
||||
|
||||
const handleAudioUpload = async (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
@ -83,8 +108,8 @@ const NovoRelatorioAudio = () => {
|
||||
<>
|
||||
<label htmlFor="audioFile" style={{cursor:'pointer', width:'100%', display:'block'}}>
|
||||
<i className="bi bi-mic fs-2 text-info"></i>
|
||||
<div style={{color:'#fff', fontWeight:'bold', marginTop:'10px'}}>Gravar / Enviar Áudio</div>
|
||||
<div className="small text-muted mt-1">Clique para selecionar</div>
|
||||
<div style={{color:'#fff', fontWeight:'bold', marginTop:'10px'}}>Upload Manual</div>
|
||||
<div className="small text-muted mt-1">Se preferir, envie um arquivo .wav</div>
|
||||
</label>
|
||||
<input
|
||||
id="audioFile"
|
||||
@ -191,7 +216,7 @@ const NovoRelatorioAudio = () => {
|
||||
|
||||
<p style={{fontWeight:'bold', marginBottom:'5px'}}>Análise:</p>
|
||||
<p style={{marginBottom:'25px', textAlign:'justify', whiteSpace:'pre-wrap'}}>
|
||||
{formData.diagnostico || <span style={{color:'#ccc'}}>O diagnóstico aparecerá aqui após o processamento do áudio...</span>}
|
||||
{formData.diagnostico || <span style={{color:'#ccc'}}>O diagnóstico aparecerá aqui...</span>}
|
||||
</p>
|
||||
|
||||
<p style={{fontWeight:'bold', marginBottom:'5px'}}>Conclusão:</p>
|
||||
|
||||
@ -1,19 +1,10 @@
|
||||
@keyframes slide-up {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
/* ... Mantenha o keyframe slide-up e container ... */
|
||||
|
||||
.video-chat-container {
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
/* --- O BOTÃO FLUTUANTE (COM CORREÇÃO) --- */
|
||||
/* O BOTÃO FLUTUANTE */
|
||||
.video-chat-button {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
@ -28,7 +19,7 @@
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center; /* <-- Correção do alinhamento */
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
@ -38,13 +29,16 @@
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
/* --- A JANELA DE CHAT --- */
|
||||
/* --- A JANELA DE CHAT (AGORA MAIOR) --- */
|
||||
.video-chat-window {
|
||||
position: fixed;
|
||||
bottom: 90px;
|
||||
right: 95px;
|
||||
width: 500px;
|
||||
height: 380px;
|
||||
|
||||
/* ALTERAÇÃO DE TAMANHO AQUI: */
|
||||
width: 600px; /* Antes era menor, aumentei para ficar melhor o vídeo */
|
||||
height: 500px; /* Aumentei a altura também */
|
||||
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
@ -52,16 +46,15 @@
|
||||
z-index: 10000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden; /* Importante para o border-radius */
|
||||
|
||||
/* Animação de "surgir" */
|
||||
overflow: hidden;
|
||||
animation: slide-up 0.3s ease-out;
|
||||
|
||||
/* Animação "premium" para tela cheia */
|
||||
transition: all 0.4s ease-in-out;
|
||||
}
|
||||
|
||||
/* --- MODO TELA CHEIA (SIMULADO) --- */
|
||||
/* ... Mantenha o resto do CSS igual (header, body, lists, pseudo-fullscreen)... */
|
||||
|
||||
/* REPETINDO O RESTANTE PARA GARANTIR QUE FUNCIONE: */
|
||||
|
||||
.video-chat-window.pseudo-fullscreen {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
@ -73,10 +66,9 @@
|
||||
}
|
||||
|
||||
.video-chat-window.pseudo-fullscreen .video-chat-header {
|
||||
display: none;
|
||||
display: flex; /* Mantém o header visível para controle */
|
||||
}
|
||||
|
||||
/* --- HEADER DA JANELA --- */
|
||||
.video-chat-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@ -84,159 +76,37 @@
|
||||
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;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.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;
|
||||
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; }
|
||||
|
||||
/* --- 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;
|
||||
flex-grow: 1; overflow-y: hidden; display: flex; flex-direction: column;
|
||||
padding: 0; 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-list-container { width: 100%; height: 100%; display: flex; flex-direction: column; }
|
||||
.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;
|
||||
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; }
|
||||
|
||||
/* --- 2. TELA DE CHAMADA --- */
|
||||
.call-screen {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #2c2c2c;
|
||||
color: white;
|
||||
.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-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);
|
||||
}
|
||||
.call-btn:hover { background-color: #218838; }
|
||||
|
||||
.call-screen { width: 100%; height: 100%; display: flex; flex-direction: column; background-color: #2c2c2c; color: white; }
|
||||
@ -1,6 +1,6 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import './BotaoVideoChamada.css';
|
||||
import { FaVideo, FaExpand, FaCompress, FaPhoneSlash } from 'react-icons/fa';
|
||||
import { FaVideo, FaExpand, FaCompress, FaMicrophone, FaStop, FaExclamationTriangle } from 'react-icons/fa';
|
||||
import { JitsiMeeting } from '@jitsi/react-sdk';
|
||||
import { db } from '../firebaseConfig';
|
||||
import { ref, set, remove } from "firebase/database";
|
||||
@ -12,7 +12,7 @@ const mockPacientes = [
|
||||
|
||||
// DADOS DO MÉDICO
|
||||
const MEU_ID_MEDICO = 'medico-99';
|
||||
const MEU_NOME_MEDICO = 'Dr. Rafael';
|
||||
const MEU_NOME_MEDICO = 'Dr. ';
|
||||
|
||||
const BotaoVideoChamada = () => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
@ -21,6 +21,11 @@ const BotaoVideoChamada = () => {
|
||||
const [callingPatient, setCallingPatient] = useState(null);
|
||||
const [roomName, setRoomName] = useState('');
|
||||
|
||||
// Estados de Gravação
|
||||
const [isRecording, setIsRecording] = useState(false);
|
||||
const mediaRecorderRef = useRef(null);
|
||||
const audioChunksRef = useRef([]);
|
||||
|
||||
// UseEffect da tecla "Esc"
|
||||
useEffect(() => {
|
||||
const handleEscKey = (event) => {
|
||||
@ -32,9 +37,77 @@ const BotaoVideoChamada = () => {
|
||||
return () => document.removeEventListener('keydown', handleEscKey);
|
||||
}, [isFullScreen]);
|
||||
|
||||
// --- LÓGICA DE GRAVAÇÃO COM DIAGNÓSTICO DETALHADO ---
|
||||
const handleToggleRecording = async () => {
|
||||
if (!isRecording) {
|
||||
// --- TENTAR INICIAR GRAVAÇÃO ---
|
||||
try {
|
||||
console.log("1. Verificando suporte do navegador...");
|
||||
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
||||
throw new Error("Navegador não suportado ou conexão insegura (sem HTTPS).");
|
||||
}
|
||||
|
||||
console.log("2. Solicitando acesso ao microfone...");
|
||||
// Solicita o áudio. Isso deve disparar o popup do navegador.
|
||||
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
||||
|
||||
console.log("3. Acesso concedido! Iniciando gravador...");
|
||||
const mediaRecorder = new MediaRecorder(stream);
|
||||
mediaRecorderRef.current = mediaRecorder;
|
||||
audioChunksRef.current = [];
|
||||
|
||||
mediaRecorder.ondataavailable = (event) => {
|
||||
if (event.data.size > 0) audioChunksRef.current.push(event.data);
|
||||
};
|
||||
|
||||
mediaRecorder.onstop = () => {
|
||||
// Cria o arquivo final
|
||||
const audioBlob = new Blob(audioChunksRef.current, { type: 'audio/wav' });
|
||||
const audioUrl = URL.createObjectURL(audioBlob);
|
||||
|
||||
// Download automático
|
||||
const link = document.createElement('a');
|
||||
link.href = audioUrl;
|
||||
link.download = `Consulta_${callingPatient?.name || 'Audio'}_${Date.now()}.wav`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
};
|
||||
|
||||
mediaRecorder.start();
|
||||
setIsRecording(true);
|
||||
|
||||
} catch (err) {
|
||||
console.error("ERRO GRAVE NA GRAVAÇÃO:", err);
|
||||
|
||||
let msgErro = "Erro desconhecido ao acessar microfone.";
|
||||
|
||||
// IDENTIFICAÇÃO DO ERRO PARA O USUÁRIO
|
||||
if (err.name === 'NotAllowedError' || err.name === 'PermissionDeniedError') {
|
||||
msgErro = "BLOQUEIO DE SISTEMA: O Windows/Mac ou o Navegador bloqueou o acesso. Verifique: Configurações > Privacidade > Microfone.";
|
||||
} else if (err.name === 'NotFoundError') {
|
||||
msgErro = "MICROFONE NÃO ENCONTRADO: Nenhum dispositivo de entrada detectado.";
|
||||
} else if (err.name === 'NotReadableError' || err.name === 'TrackStartError') {
|
||||
msgErro = "DISPOSITIVO OCUPADO: O microfone já está sendo usado por outro programa (Zoom, Meet, ou a própria chamada).";
|
||||
} else if (err.message.includes("insegura")) {
|
||||
msgErro = "ERRO DE SEGURANÇA: O navegador bloqueia microfone se não for 'localhost' ou 'https'.";
|
||||
}
|
||||
|
||||
alert(`⚠️ ${msgErro}\n\nDetalhe técnico: ${err.name}`);
|
||||
}
|
||||
} else {
|
||||
// --- PARAR GRAVAÇÃO ---
|
||||
if (mediaRecorderRef.current) {
|
||||
mediaRecorderRef.current.stop();
|
||||
// Importante: Libera o hardware do microfone
|
||||
mediaRecorderRef.current.stream.getTracks().forEach(track => track.stop());
|
||||
}
|
||||
setIsRecording(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 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}`);
|
||||
|
||||
@ -57,10 +130,15 @@ const BotaoVideoChamada = () => {
|
||||
const callRef = ref(db, `calls/paciente-${callingPatient.id}`);
|
||||
remove(callRef);
|
||||
}
|
||||
|
||||
// Se estiver gravando, para automaticamente
|
||||
if (isRecording) {
|
||||
handleToggleRecording();
|
||||
}
|
||||
|
||||
setCallActive(false);
|
||||
setCallingPatient(null);
|
||||
setRoomName('');
|
||||
console.log("Chamada encerrada.");
|
||||
};
|
||||
|
||||
// Função para fechar a janela
|
||||
@ -72,7 +150,6 @@ const BotaoVideoChamada = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Função de Tela Cheia
|
||||
const handleFullScreen = () => {
|
||||
setIsFullScreen(!isFullScreen);
|
||||
};
|
||||
@ -83,12 +160,35 @@ const BotaoVideoChamada = () => {
|
||||
<div className={`video-chat-window ${isFullScreen ? 'pseudo-fullscreen' : ''}`}>
|
||||
|
||||
<div className="video-chat-header">
|
||||
<h3>{callActive ? `Em chamada com...` : 'Iniciar Chamada'}</h3>
|
||||
<h3>{callActive ? `Em chamada: ${callingPatient?.name}` : 'Central de Videochamada'}</h3>
|
||||
|
||||
{/* ================================== */}
|
||||
{/* BOTÕES DE VOLTA - CORREÇÃO AQUI */}
|
||||
{/* ================================== */}
|
||||
<div className="video-chat-controls">
|
||||
|
||||
{/* BOTÃO DE GRAVAR SEMPRE VISÍVEL */}
|
||||
<button
|
||||
onClick={handleToggleRecording}
|
||||
className="control-btn"
|
||||
title={isRecording ? "Parar Gravação" : "Gravar Áudio"}
|
||||
style={{
|
||||
color: isRecording ? '#dc3545' : '#555',
|
||||
fontWeight: isRecording ? 'bold' : 'normal',
|
||||
marginRight: '15px',
|
||||
border: isRecording ? '1px solid #dc3545' : '1px solid #ccc',
|
||||
backgroundColor: isRecording ? '#ffe6e6' : 'white',
|
||||
borderRadius: '20px',
|
||||
padding: '5px 15px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '5px',
|
||||
transition: 'all 0.3s'
|
||||
}}
|
||||
>
|
||||
{isRecording ? <FaStop size={12} /> : <FaMicrophone size={12} />}
|
||||
<span style={{fontSize: 12}}>
|
||||
{isRecording ? 'GRAVANDO' : 'GRAVAR'}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button onClick={handleFullScreen} className="control-btn fullscreen-btn">
|
||||
{isFullScreen ? <FaCompress size={14} /> : <FaExpand size={14} />}
|
||||
</button>
|
||||
@ -131,7 +231,7 @@ const BotaoVideoChamada = () => {
|
||||
) : (
|
||||
// TELA DE LISTA DE PACIENTES
|
||||
<div className="patient-list-container">
|
||||
<p>Selecione um paciente para iniciar a chamada:</p>
|
||||
<p>Selecione um paciente:</p>
|
||||
<ul className="patient-list">
|
||||
{mockPacientes.map((paciente) => (
|
||||
<li key={paciente.id} className="patient-item">
|
||||
@ -142,6 +242,18 @@ const BotaoVideoChamada = () => {
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div style={{
|
||||
padding:'15px',
|
||||
margin:'10px',
|
||||
backgroundColor:'#f8f9fa',
|
||||
borderRadius:'5px',
|
||||
fontSize:'12px',
|
||||
color:'#666',
|
||||
textAlign:'center'
|
||||
}}>
|
||||
<FaExclamationTriangle style={{marginRight:5, color:'#ffc107'}}/>
|
||||
<strong>Dica:</strong> Se a gravação falhar, clique em "Gravar" antes de entrar na chamada.
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -1,307 +1,105 @@
|
||||
/* 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 */
|
||||
/* --- 1. Container e Fonte --- */
|
||||
.paciente-video-container {
|
||||
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 {
|
||||
/* --- 2. Animações --- */
|
||||
@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 */
|
||||
/* --- 3. O Botão Flutuante (Aparece sempre) --- */
|
||||
.paciente-video-button {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 95px;
|
||||
right: 95px; /* Ajuste aqui se quiser mais para a direita ou esquerda */
|
||||
z-index: 9999;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
background-color: #007bff;
|
||||
background-color: #007bff; /* Azul padrão */
|
||||
color: white;
|
||||
border: none;
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
||||
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);
|
||||
background-color: #0056b3;
|
||||
}
|
||||
/* Aplica a animação "pulsar" */
|
||||
|
||||
/* Estado "Chamando" (Pulsando) */
|
||||
.paciente-video-button.ringing {
|
||||
background-color: #28a745; /* Verde quando toca */
|
||||
animation: ringing 1.5s infinite;
|
||||
}
|
||||
|
||||
/* Janela de Vídeo */
|
||||
/* --- 4. A Janela do Chat (Quando aberta) --- */
|
||||
.paciente-video-window {
|
||||
position: fixed;
|
||||
bottom: 90px;
|
||||
bottom: 90px; /* Fica logo acima do botão */
|
||||
right: 95px;
|
||||
width: 500px;
|
||||
height: 380px;
|
||||
width: 350px; /* Largura padrão confortável */
|
||||
height: 500px;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 -5px 20px rgba(0, 0, 0, 0.15);
|
||||
z-index: 10000;
|
||||
z-index: 10000; /* Fica acima do botão */
|
||||
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) */
|
||||
|
||||
/* Header da Janela (Barra cinza no topo) */
|
||||
.paciente-video-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 15px;
|
||||
background-color: #333;
|
||||
color: white;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.paciente-video-header h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.paciente-video-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* Corpo da Janela (Onde o vídeo aparece) */
|
||||
.paciente-video-body {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #f7f9fc;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* --- 5. Modo Tela Cheia --- */
|
||||
.paciente-video-window.pseudo-fullscreen {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
@ -311,157 +109,76 @@
|
||||
border: none;
|
||||
z-index: 99999;
|
||||
}
|
||||
|
||||
.paciente-video-window.pseudo-fullscreen .paciente-video-header {
|
||||
display: none;
|
||||
display: none; /* Esconde o header na tela cheia */
|
||||
}
|
||||
|
||||
/* Header da Janela */
|
||||
.paciente-video-header {
|
||||
|
||||
/* Tela de Chamada Recebida */
|
||||
.incoming-call-screen {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 10px 15px;
|
||||
background-color: #f7f7f7;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
flex-shrink: 0;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
.paciente-video-header h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
|
||||
.incoming-call-screen h3 {
|
||||
margin: 10px 0 20px 0;
|
||||
color: #333;
|
||||
}
|
||||
.paciente-video-controls {
|
||||
|
||||
.incoming-call-actions {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* Botões de Ação (Atender/Recusar) */
|
||||
.accept-btn, .decline-btn {
|
||||
border: none;
|
||||
border-radius: 30px;
|
||||
padding: 10px 20px;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
/* Corpo da Janela */
|
||||
.paciente-video-body {
|
||||
flex-grow: 1;
|
||||
overflow-y: hidden;
|
||||
.accept-btn { background-color: #28a745; }
|
||||
.decline-btn { background-color: #dc3545; }
|
||||
.accept-btn:hover, .decline-btn:hover { transform: scale(1.05); }
|
||||
|
||||
/* Tela de Espera */
|
||||
.patient-idle-screen {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
transition: padding 0.4s ease-in-out;
|
||||
}
|
||||
.paciente-video-window.pseudo-fullscreen .paciente-video-body {
|
||||
padding: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #777;
|
||||
font-style: italic;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* --- ESTILOS DOS 3 ESTADOS --- */
|
||||
|
||||
/* 1. Tela de Chamada Ativa (Jitsi) */
|
||||
/* Tela de Vídeo Ativo */
|
||||
.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;
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
/* 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) */
|
||||
/* Botões de Controle (Fechar/Expandir) */
|
||||
.control-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
color: #888;
|
||||
padding: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.control-btn:hover {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
.close-btn {
|
||||
font-size: 24px;
|
||||
line-height: 1;
|
||||
}
|
||||
.fullscreen-btn {
|
||||
font-size: 14px;
|
||||
}
|
||||
.control-btn:hover { opacity: 1; }
|
||||
Loading…
x
Reference in New Issue
Block a user