Relatorio em audio

This commit is contained in:
RafaelMTA13 2025-12-03 15:54:01 -03:00
parent 4d2bca5641
commit bc7a2ed7d8
10 changed files with 299 additions and 575 deletions

View File

@ -1,2 +1,2 @@
export * from "./core/error.js";
export * from "openai/core/error.js";
//# sourceMappingURL=error.d.ts.map

View File

@ -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 */

View File

@ -1,2 +1,2 @@
export * from "./core/pagination.js";
export * from "openai/core/pagination.js";
//# sourceMappingURL=pagination.d.ts.map

View File

@ -1,2 +1,2 @@
export * from "./completions/index.js";
export * from "openai/resources/chat/completions/index.js";
//# sourceMappingURL=completions.d.ts.map

View File

@ -1,2 +1,2 @@
export * from "./core/uploads.js";
export * from "openai/core/uploads.js";
//# sourceMappingURL=uploads.d.ts.map

View File

@ -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();

View File

@ -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>

View File

@ -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; }

View File

@ -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>

View File

@ -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 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; }