Compare commits
1 Commits
main
...
UpdateExce
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0fd21399e1 |
2
.env
Normal file
2
.env
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
OPENAI_API_KEY=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
|
||||||
|
PORT=5000
|
||||||
@ -1,7 +1,7 @@
|
|||||||
# .env
|
# .env
|
||||||
|
|
||||||
# Cole o token de acesso aqui
|
# Cole o token de acesso aqui
|
||||||
WHATSAPP_TOKEN=EAAVZA9C5Lx9IBPjITD8IZCZCeGRBIACX9PInHcNHxuhmp5vK7t40Yn0kc9ZC4YeKx1ZC69tnc1MtcQFWCptQimDvQIIvugiw7BNdi0ak1COfBmIZAMAkzskVkk5qhG9WnMsVmZBEoy9AXcbI53vbqSQooZCCN7LkOhbigZCaZC3VqfLnrmIzKZBC0QhzdSzTpvfQYHocDAzCS8ejf2o6WVSXYlqJEOuLzFEkvtGR6eLvNQi6QZDZD
|
WHATSAPP_TOKEN=EAAVZA9C5Lx9IBP0kF76Yy5GJquZCOkQZCtnsLDYJZCLRfZA7BrOsZBPBk7BODsDuU1r5qYNu5vsRFlI1tNZBlnQpWXsZCZBrkqTygGphqQLZCvikGDyZBEFEyknkWM9oadz1xVtAA65JKXFbGFIJWhmFMOgauWXZC072CSkApe5UZCVGZCZAqc5we1TqCcFBvLqWnUexosBRIEb8kSThWlEDheHNoP7MrjwNcYaNBczmFmhq9aPqKm6jCgjwqjZBI0jVLjdooKkZCanaz9ZA3ZBIfNbyq8FOYUI
|
||||||
|
|
||||||
# Cole o ID do número de telefone aqui
|
# Cole o ID do número de telefone aqui
|
||||||
WHATSAPP_PHONE_NUMBER_ID=806117442588831
|
WHATSAPP_PHONE_NUMBER_ID=806117442588831
|
||||||
|
|||||||
@ -1,56 +0,0 @@
|
|||||||
[33m3993097[m[33m ([m[1;36mHEAD[m[33m -> [m[1;32mmain[m[33m)[m Merge branch 'main' of https://git.popcode.com.br/RiseUP/riseup-squad23
|
|
||||||
[33m63659b6[m Verificação do cpf e colocar o erro 404
|
|
||||||
[33mecae83c[m[33m ([m[1;31mriseup/main[m[33m, [m[1;31mriseup/HEAD[m[33m, [m[1;31morigin/main[m[33m, [m[1;31morigin/HEAD[m[33m)[m Merge pull request 'Conectando-o-resto-das-API' (#2) from Conectando-o-resto-das-API into main
|
|
||||||
[33m908d545[m[33m ([m[1;31mriseup/Conectando-o-resto-das-API[m[33m, [m[1;31morigin/Conectando-o-resto-das-API[m[33m)[m feat: adicionar upload e delete de anexos do paciente
|
|
||||||
[33m4b404c0[m Merge branch 'main' of https://git.popcode.com.br/RiseUP/riseup-squad23
|
|
||||||
[33mbd20c2d[m Merge remote-tracking branch 'origin/main'
|
|
||||||
[33m8aeabd1[m[33m ([m[1;31mriseup/Fix-dos-erros-do-projeto[m[33m, [m[1;31morigin/Fix-dos-erros-do-projeto[m[33m)[m FIx: todos os erros que aparecia no console foram resolvidos
|
|
||||||
[33m0e29e7d[m melhorias na organização de pastas
|
|
||||||
[33m98f076a[m Mergin com TableMelhorias
|
|
||||||
[33m589d590[m Mergin com novas alterações de laudo
|
|
||||||
[33m7b28e2a[m Details melhorias
|
|
||||||
[33m9480edc[m[33m ([m[1;31mriseup/PaginaDetalhes[m[33m, [m[1;31morigin/PaginaDetalhes[m[33m)[m Pàgina detalhes
|
|
||||||
[33me4515cf[m Adição das cores nos cards de consulta
|
|
||||||
[33md3dd2fd[m[33m ([m[1;31mriseup/TableMelhorias[m[33m, [m[1;31morigin/TableMelhorias[m[33m)[m Detalhe nas tabelas
|
|
||||||
[33ma54b119[m Delete Anexos apos pacientes forem excluidos
|
|
||||||
[33m6e93cb5[m atualizar paciente
|
|
||||||
[33mb9a35be[m começo do concerto do editar
|
|
||||||
[33m82469bc[m Details funcional
|
|
||||||
[33mcdfe4ea[m Validação de CPF
|
|
||||||
[33m57c8f67[m[33m ([m[1;31mriseup/DetalhesMedico[m[33m, [m[1;31morigin/DetalhesMedico[m[33m)[m Detalhes do medico
|
|
||||||
[33mb021444[m Mudanças formularios e detalhes
|
|
||||||
[33md5d03b0[m[33m ([m[1;31mriseup/mudanças-de-laudo[m[33m, [m[1;31morigin/mudanças-de-laudo[m[33m)[m atualização do laudo
|
|
||||||
[33ma502bbd[m agendamentos no incio
|
|
||||||
[33m8e1fcd9[m Merge branch 'feature/novo-cadastro-paciente'
|
|
||||||
[33mbea9076[m Merge remote-tracking branch 'origin/PaginaDetalhes'
|
|
||||||
[33me35f217[m mergin branch inicio com main
|
|
||||||
[33m1af8268[m Atualizacão do laudo
|
|
||||||
[33m725d60d[m feat: ajeitei o nome
|
|
||||||
[33mbab85ff[m[33m ([m[1;31mriseup/AgendamentoSidebar[m[33m, [m[1;31morigin/AgendamentoSidebar[m[33m)[m Concertar Agendamento
|
|
||||||
[33mb2707e3[m Refatora o estilo do formulário do paciente para uma aparência de cartão com tipografia maior
|
|
||||||
[33m37e8959[m Refatora o estilo do formulário do paciente para uma aparência de cartão com tipografia maior
|
|
||||||
[33m0930385[m feat: uma piquena mudança
|
|
||||||
[33mf6a19c4[m feat: Adiciona formulário de cadastro de paciente
|
|
||||||
[33md91b5cf[m form de agendar consulta melhorado
|
|
||||||
[33m0a60dd7[m Tabela semana e mes
|
|
||||||
[33m7f07950[m[33m ([m[1;31mriseup/feature-Melhoria-no-Dashboard[m[33m, [m[1;31morigin/feature-Melhoria-no-Dashboard[m[33m)[m feat: Criação da página início e melhoria na navegação
|
|
||||||
[33m39e25ad[m Pagina de detalhes atualizada
|
|
||||||
[33m4f84791[m pequenas mudanaças na tabela de semana e mes
|
|
||||||
[33m6737955[m form para nova consulta e tabelas de horario
|
|
||||||
[33m26ded17[m Nova pagina de detalhes
|
|
||||||
[33m874de84[m Inicio do agendamento
|
|
||||||
[33mf3e7470[m[33m ([m[1;31mriseup/gerenciamento-de-laudo[m[33m, [m[1;31morigin/gerenciamento-de-laudo[m[33m)[m Laudo do Paciente
|
|
||||||
[33m709cd4e[m Merge finalizado
|
|
||||||
[33md6b3e86[m Merge detalhes-do-pacientes para main
|
|
||||||
[33m08ffa55[m Merge remote-tracking branch 'origin/CrudMedico'
|
|
||||||
[33m70c4d5f[m Termino da organização
|
|
||||||
[33medd567d[m Inicio da organização
|
|
||||||
[33m9c09113[m Mudanças pos feedback de davi
|
|
||||||
[33maa3a5fa[m Criação da página dos detalhes dos pacientes
|
|
||||||
[33m5534568[m Inicio de detalhes e atualização do paciente
|
|
||||||
[33m06ff7d5[m Funcionalidade de delete e botão de opções
|
|
||||||
[33m5b63fa2[m Mascara telefones
|
|
||||||
[33mfb9d783[m adição da mascara do CPF
|
|
||||||
[33ma489d84[m metodo GET e POST
|
|
||||||
[33m4eaabbd[m first commit
|
|
||||||
[33ma244691[m Initial commit
|
|
||||||
5592
package-lock.json
generated
5592
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@ -5,6 +5,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ckeditor/ckeditor5-build-classic": "^41.4.2",
|
"@ckeditor/ckeditor5-build-classic": "^41.4.2",
|
||||||
"@ckeditor/ckeditor5-react": "^11.0.0",
|
"@ckeditor/ckeditor5-react": "^11.0.0",
|
||||||
|
"@jitsi/react-sdk": "^1.4.0",
|
||||||
"@sweetalert2/theme-dark": "^5.0.27",
|
"@sweetalert2/theme-dark": "^5.0.27",
|
||||||
"@testing-library/dom": "^10.4.1",
|
"@testing-library/dom": "^10.4.1",
|
||||||
"@testing-library/jest-dom": "^6.8.0",
|
"@testing-library/jest-dom": "^6.8.0",
|
||||||
@ -18,10 +19,15 @@
|
|||||||
"apexcharts": "^5.3.4",
|
"apexcharts": "^5.3.4",
|
||||||
"bootstrap": "^5.3.8",
|
"bootstrap": "^5.3.8",
|
||||||
"bootstrap-icons": "^1.13.1",
|
"bootstrap-icons": "^1.13.1",
|
||||||
|
"cors": "^2.8.5",
|
||||||
"dayjs": "^1.11.18",
|
"dayjs": "^1.11.18",
|
||||||
|
"dotenv": "^17.2.3",
|
||||||
|
"express": "^5.1.0",
|
||||||
"flatpickr": "^4.6.13",
|
"flatpickr": "^4.6.13",
|
||||||
"html2pdf.js": "^0.12.1",
|
"html2pdf.js": "^0.12.1",
|
||||||
"lucide-react": "^0.543.0",
|
"lucide-react": "^0.543.0",
|
||||||
|
"node-fetch": "^3.3.2",
|
||||||
|
"openai": "^6.7.0",
|
||||||
"perfect-scrollbar": "^1.5.6",
|
"perfect-scrollbar": "^1.5.6",
|
||||||
"powershell": "^2.3.3",
|
"powershell": "^2.3.3",
|
||||||
"quill": "^2.0.3",
|
"quill": "^2.0.3",
|
||||||
@ -33,6 +39,7 @@
|
|||||||
"react-flatpickr": "^4.0.11",
|
"react-flatpickr": "^4.0.11",
|
||||||
"react-icons": "^5.5.0",
|
"react-icons": "^5.5.0",
|
||||||
"react-input-mask": "^2.0.4",
|
"react-input-mask": "^2.0.4",
|
||||||
|
"react-is": "^19.2.0",
|
||||||
"react-quill": "^2.0.0",
|
"react-quill": "^2.0.0",
|
||||||
"react-router-dom": "^7.9.2",
|
"react-router-dom": "^7.9.2",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
@ -72,5 +79,8 @@
|
|||||||
"sass": "^1.91.0",
|
"sass": "^1.91.0",
|
||||||
"sass-loader": "^16.0.5",
|
"sass-loader": "^16.0.5",
|
||||||
"tailwindcss": "^4.1.13"
|
"tailwindcss": "^4.1.13"
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
"react": "$react"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
38
server.js
Normal file
38
server.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import express from "express";
|
||||||
|
import cors from "cors";
|
||||||
|
import dotenv from "dotenv";
|
||||||
|
import OpenAI from "openai";
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
const app = express();
|
||||||
|
app.use(cors());
|
||||||
|
app.use(express.json());
|
||||||
|
|
||||||
|
const client = new OpenAI({
|
||||||
|
apiKey: process.env.OPENAI_API_KEY, // Coloque sua chave no .env
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/api/chat", async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { message } = req.body;
|
||||||
|
|
||||||
|
const completion = await client.chat.completions.create({
|
||||||
|
model: "gpt-4o-mini", // modelo rápido e leve
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: "system",
|
||||||
|
content: "Você é a assistente virtual do site Mediconnect, chamada Ágatha. Responda de forma amigável e informativa, explicando sobre o funcionamento do site, cadastro, agendamento, e suporte técnico.",
|
||||||
|
},
|
||||||
|
{ role: "user", content: message },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const resposta = completion.choices[0].message.content;
|
||||||
|
res.json({ resposta });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erro no servidor:", error);
|
||||||
|
res.status(500).json({ erro: "Erro ao conectar com a IA" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(5000, () => console.log("Servidor rodando na porta 5000"));
|
||||||
@ -1,4 +1,3 @@
|
|||||||
// src/PagesMedico/DoctorRelatorioManager.jsx
|
|
||||||
import API_KEY from '../components/utils/apiKeys';
|
import API_KEY from '../components/utils/apiKeys';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
@ -8,56 +7,96 @@ import { GetDoctorByID } from '../components/utils/Functions-Endpoints/Doctor';
|
|||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import html2pdf from 'html2pdf.js';
|
import html2pdf from 'html2pdf.js';
|
||||||
import TiptapViewer from './TiptapViewer';
|
import TiptapViewer from './TiptapViewer';
|
||||||
|
import './styleMedico/DoctorRelatorioManager.css';
|
||||||
|
|
||||||
const DoctorRelatorioManager = () => {
|
const DoctorRelatorioManager = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { getAuthorizationHeader } = useAuth();
|
const { getAuthorizationHeader } = useAuth();
|
||||||
const authHeader = getAuthorizationHeader();
|
let authHeader = getAuthorizationHeader();
|
||||||
const [RelatoriosFiltrados, setRelatorios] = useState([]);
|
|
||||||
const [PacientesComRelatorios, setPacientesComRelatorios] = useState([]);
|
const [relatoriosOriginais, setRelatoriosOriginais] = useState([]);
|
||||||
const [MedicosComRelatorios, setMedicosComRelatorios] = useState([]);
|
const [relatoriosFiltrados, setRelatoriosFiltrados] = useState([]);
|
||||||
const [showModal, setShowModal] = useState(false);
|
const [relatoriosFinais, setRelatoriosFinais] = useState([]);
|
||||||
const [index, setIndex] = useState();
|
const [pacientesData, setPacientesData] = useState({});
|
||||||
|
const [pacientesComRelatorios, setPacientesComRelatorios] = useState([]);
|
||||||
|
const [medicosComRelatorios, setMedicosComRelatorios] = useState([]);
|
||||||
|
const [showModal, setShowModal] = useState(false);
|
||||||
|
const [relatorioModal, setRelatorioModal] = useState(null);
|
||||||
|
const [termoPesquisa, setTermoPesquisa] = useState('');
|
||||||
|
const [filtroExame, setFiltroExame] = useState('');
|
||||||
|
const [examesDisponiveis, setExamesDisponiveis] = useState([]);
|
||||||
|
const [modalIndex, setModalIndex] = useState(0);
|
||||||
|
|
||||||
|
const [paginaAtual, setPaginaAtual] = useState(1);
|
||||||
|
const [itensPorPagina, setItensPorPagina] = useState(10);
|
||||||
|
|
||||||
|
|
||||||
|
const totalPaginas = Math.ceil(relatoriosFinais.length / itensPorPagina);
|
||||||
|
const indiceInicial = (paginaAtual - 1) * itensPorPagina;
|
||||||
|
const indiceFinal = indiceInicial + itensPorPagina;
|
||||||
|
const relatoriosPaginados = relatoriosFinais.slice(indiceInicial, indiceFinal);
|
||||||
|
|
||||||
// busca lista de relatórios
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
let mounted = true;
|
||||||
|
|
||||||
const fetchReports = async () => {
|
const fetchReports = async () => {
|
||||||
try {
|
try {
|
||||||
var myHeaders = new Headers();
|
var myHeaders = new Headers();
|
||||||
myHeaders.append('apikey', API_KEY);
|
myHeaders.append('apikey', API_KEY);
|
||||||
myHeaders.append('Authorization', authHeader);
|
if (authHeader) myHeaders.append('Authorization', authHeader);
|
||||||
var requestOptions = { method: 'GET', headers: myHeaders, redirect: 'follow' };
|
var requestOptions = { method: 'GET', headers: myHeaders, redirect: 'follow' };
|
||||||
|
|
||||||
const res = await fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?select=*", requestOptions);
|
const res = await fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?select=*", requestOptions);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
setRelatorios(data || []);
|
|
||||||
|
const uniqueMap = new Map();
|
||||||
|
(Array.isArray(data) ? data : []).forEach(r => {
|
||||||
|
if (r && r.id) uniqueMap.set(r.id, r);
|
||||||
|
});
|
||||||
|
const unique = Array.from(uniqueMap.values())
|
||||||
|
.sort((a, b) => new Date(b.created_at || 0) - new Date(a.created_at || 0));
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
setRelatoriosOriginais(unique);
|
||||||
|
setRelatoriosFiltrados(unique);
|
||||||
|
setRelatoriosFinais(unique);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Erro listar relatórios', err);
|
console.error('Erro listar relatórios', err);
|
||||||
setRelatorios([]);
|
if (mounted) {
|
||||||
|
setRelatoriosOriginais([]);
|
||||||
|
setRelatoriosFiltrados([]);
|
||||||
|
setRelatoriosFinais([]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchReports();
|
fetchReports();
|
||||||
|
|
||||||
|
const refreshHandler = () => fetchReports();
|
||||||
|
window.addEventListener('reports:refresh', refreshHandler);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
mounted = false;
|
||||||
|
window.removeEventListener('reports:refresh', refreshHandler);
|
||||||
|
};
|
||||||
}, [authHeader]);
|
}, [authHeader]);
|
||||||
|
|
||||||
// depois que RelatoriosFiltrados mudar, busca pacientes e médicos correspondentes
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchRelData = async () => {
|
const fetchRelData = async () => {
|
||||||
const pacientes = [];
|
const pacientes = [];
|
||||||
const medicos = [];
|
const medicos = [];
|
||||||
for (let i = 0; i < RelatoriosFiltrados.length; i++) {
|
for (let i = 0; i < relatoriosFiltrados.length; i++) {
|
||||||
const rel = RelatoriosFiltrados[i];
|
const rel = relatoriosFiltrados[i];
|
||||||
// paciente
|
|
||||||
try {
|
try {
|
||||||
const pacienteRes = await GetPatientByID(rel.patient_id, authHeader);
|
const pacienteRes = await GetPatientByID(rel.patient_id, authHeader);
|
||||||
pacientes.push(Array.isArray(pacienteRes) ? pacienteRes[0] : pacienteRes);
|
pacientes.push(Array.isArray(pacienteRes) ? pacienteRes[0] : pacienteRes);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
pacientes.push(null);
|
pacientes.push(null);
|
||||||
}
|
}
|
||||||
// médico: tenta created_by ou requested_by id se existir
|
|
||||||
try {
|
try {
|
||||||
const doctorId = rel.created_by || rel.requested_by || null;
|
const doctorId = rel.created_by || rel.requested_by || null;
|
||||||
if (doctorId) {
|
if (doctorId) {
|
||||||
// se created_by é id (uuid) usamos GetDoctorByID, senão se requested_by for nome, guardamos nome
|
|
||||||
const docRes = await GetDoctorByID(doctorId, authHeader);
|
const docRes = await GetDoctorByID(doctorId, authHeader);
|
||||||
medicos.push(Array.isArray(docRes) ? docRes[0] : docRes);
|
medicos.push(Array.isArray(docRes) ? docRes[0] : docRes);
|
||||||
} else {
|
} else {
|
||||||
@ -70,55 +109,117 @@ const DoctorRelatorioManager = () => {
|
|||||||
setPacientesComRelatorios(pacientes);
|
setPacientesComRelatorios(pacientes);
|
||||||
setMedicosComRelatorios(medicos);
|
setMedicosComRelatorios(medicos);
|
||||||
};
|
};
|
||||||
if (RelatoriosFiltrados.length > 0) fetchRelData();
|
if (relatoriosFiltrados.length > 0) fetchRelData();
|
||||||
else {
|
else {
|
||||||
setPacientesComRelatorios([]);
|
setPacientesComRelatorios([]);
|
||||||
setMedicosComRelatorios([]);
|
setMedicosComRelatorios([]);
|
||||||
}
|
}
|
||||||
}, [RelatoriosFiltrados, authHeader]);
|
}, [relatoriosFiltrados, authHeader]);
|
||||||
|
|
||||||
const BaixarPDFdoRelatorio = (nome_paciente) => {
|
const abrirModal = (relatorio, index) => {
|
||||||
const elemento = document.getElementById("folhaA4");
|
setRelatorioModal(relatorio);
|
||||||
const opt = { margin: 0, filename: `relatorio_${nome_paciente || "paciente"}.pdf`, html2canvas: { scale: 2 }, jsPDF: { unit: "mm", format: "a4", orientation: "portrait" } };
|
setModalIndex(index);
|
||||||
|
setShowModal(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Função para limpar filtros
|
||||||
|
const limparFiltros = () => {
|
||||||
|
setTermoPesquisa('');
|
||||||
|
setFiltroExame('');
|
||||||
|
setRelatoriosFinais(relatoriosOriginais);
|
||||||
|
};
|
||||||
|
|
||||||
|
const BaixarPDFdoRelatorio = (nome_paciente, idx) => {
|
||||||
|
const elemento = document.getElementById(`folhaA4-${idx}`);
|
||||||
|
if (!elemento) {
|
||||||
|
console.error('Elemento para gerar PDF não encontrado:', `folhaA4-${idx}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const opt = {
|
||||||
|
margin: 0,
|
||||||
|
filename: `relatorio_${nome_paciente || "paciente"}.pdf`,
|
||||||
|
html2canvas: { scale: 2 },
|
||||||
|
jsPDF: { unit: "mm", format: "a4", orientation: "portrait" }
|
||||||
|
};
|
||||||
html2pdf().set(opt).from(elemento).save();
|
html2pdf().set(opt).from(elemento).save();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const irParaPagina = (pagina) => {
|
||||||
|
setPaginaAtual(pagina);
|
||||||
|
};
|
||||||
|
|
||||||
|
const avancarPagina = () => {
|
||||||
|
if (paginaAtual < totalPaginas) {
|
||||||
|
setPaginaAtual(paginaAtual + 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const voltarPagina = () => {
|
||||||
|
if (paginaAtual > 1) {
|
||||||
|
setPaginaAtual(paginaAtual - 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const gerarNumerosPaginas = () => {
|
||||||
|
const paginas = [];
|
||||||
|
const paginasParaMostrar = 5;
|
||||||
|
|
||||||
|
let inicio = Math.max(1, paginaAtual - Math.floor(paginasParaMostrar / 2));
|
||||||
|
let fim = Math.min(totalPaginas, inicio + paginasParaMostrar - 1);
|
||||||
|
|
||||||
|
inicio = Math.max(1, fim - paginasParaMostrar + 1);
|
||||||
|
|
||||||
|
for (let i = inicio; i <= fim; i++) {
|
||||||
|
paginas.push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return paginas;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{showModal && (
|
{showModal && (
|
||||||
<div className="modal">
|
<div className="modal modal-centered" role="dialog" aria-modal="true" onClick={() => setShowModal(false)}>
|
||||||
<div className="modal-dialog modal-tabela-relatorio">
|
<div className="modal-dialog modal-dialog-square" role="document" onClick={(e) => e.stopPropagation()}>
|
||||||
<div className="modal-content">
|
<div className="modal-content">
|
||||||
<div className="modal-header text-white">
|
<div className="modal-header custom-modal-header">
|
||||||
<h5 className="modal-title ">Relatório de {PacientesComRelatorios[index]?.full_name}</h5>
|
<h5 className="modal-title">Relatório de {pacientesComRelatorios[modalIndex]?.full_name}</h5>
|
||||||
<button type="button" className="btn-close" onClick={() => setShowModal(false)}></button>
|
<button type="button" className="btn-close modal-close-btn" aria-label="Close" onClick={() => setShowModal(false)}></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="modal-body">
|
<div className="modal-body">
|
||||||
<div id="folhaA4">
|
<div id={`folhaA4-${modalIndex}`} className="folhaA4">
|
||||||
<div id='header-relatorio'>
|
<div id='header-relatorio' style={{ textAlign: 'center', marginBottom: 24 }}>
|
||||||
<p>Clinica Rise up</p>
|
<p style={{ margin: 0 }}>Clinica Rise up</p>
|
||||||
<p>Dr - CRM/SP 123456</p>
|
<p style={{ margin: 0 }}>Dr - CRM/SP 123456</p>
|
||||||
<p>Avenida - (79) 9 4444-4444</p>
|
<p style={{ margin: 0 }}>Avenida - (79) 9 4444-4444</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id='infoPaciente'>
|
<div id='infoPaciente' style={{ padding: '0 6px' }}>
|
||||||
<p>Paciente: {PacientesComRelatorios[index]?.full_name}</p>
|
<p><strong>Paciente:</strong> {pacientesComRelatorios[modalIndex]?.full_name}</p>
|
||||||
<p>Data de nascimento: {PacientesComRelatorios[index]?.birth_date}</p>
|
<p><strong>Data de nascimento:</strong> {pacientesComRelatorios[modalIndex]?.birth_date || '—'}</p>
|
||||||
<p>Data do exame: {RelatoriosFiltrados[index]?.due_at || ''}</p>
|
<p><strong>Data do exame:</strong> {relatoriosFiltrados[modalIndex]?.due_at || '—'}</p>
|
||||||
{/* Exibe conteúdo salvo (content_html) */}
|
|
||||||
<p style={{ marginTop: '15px', fontWeight: 'bold' }}>Conteúdo do Relatório:</p>
|
<p style={{ marginTop: 12, fontWeight: '700' }}>Conteúdo do Relatório:</p>
|
||||||
<TiptapViewer htmlContent={RelatoriosFiltrados[index]?.content_html || RelatoriosFiltrados[index]?.content || 'Relatório não preenchido.'} />
|
<div className="tiptap-viewer-wrapper">
|
||||||
|
<TiptapViewer htmlContent={relatoriosFiltrados[modalIndex]?.content_html || relatoriosFiltrados[modalIndex]?.content || 'Relatório não preenchido.'} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div style={{ marginTop: 20, padding: '0 6px' }}>
|
||||||
<p>Dr {MedicosComRelatorios[index]?.full_name || RelatoriosFiltrados[index]?.requested_by}</p>
|
<p>Dr {medicosComRelatorios[modalIndex]?.full_name || relatoriosFiltrados[modalIndex]?.requested_by}</p>
|
||||||
<p>Emitido em: {RelatoriosFiltrados[index]?.created_at || '—'}</p>
|
<p style={{ color: '#6c757d', fontSize: '0.95rem' }}>Emitido em: {relatoriosFiltrados[modalIndex]?.created_at || '—'}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="modal-footer">
|
|
||||||
<button className="btn btn-primary" onClick={() => BaixarPDFdoRelatorio(PacientesComRelatorios[index]?.full_name)}><i className='bi bi-file-pdf-fill'></i> baixar em pdf</button>
|
<div className="modal-footer custom-modal-footer">
|
||||||
<button type="button" className="btn btn-primary" onClick={() => { setShowModal(false) }}>Fechar</button>
|
<button className="btn btn-primary" onClick={() => BaixarPDFdoRelatorio(pacientesComRelatorios[modalIndex]?.full_name, modalIndex)}>
|
||||||
|
<i className='bi bi-file-pdf-fill'></i> baixar em pdf
|
||||||
|
</button>
|
||||||
|
<button type="button" className="btn btn-outline-secondary" onClick={() => { setShowModal(false) }}>
|
||||||
|
Fechar
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -133,14 +234,51 @@ const DoctorRelatorioManager = () => {
|
|||||||
<div className="card-header d-flex justify-content-between align-items-center">
|
<div className="card-header d-flex justify-content-between align-items-center">
|
||||||
<h4 className="card-title mb-0">Relatórios Cadastrados</h4>
|
<h4 className="card-title mb-0">Relatórios Cadastrados</h4>
|
||||||
<Link to={'criar'}>
|
<Link to={'criar'}>
|
||||||
<button className="btn btn-primary"><i className="bi bi-plus-circle"></i> Adicionar Relatório</button>
|
<button className="btn btn-primary">
|
||||||
|
<i className="bi bi-plus-circle"></i> Adicionar Relatório
|
||||||
|
</button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<div className="card p-3 mb-3">
|
<div className="card p-3 mb-3">
|
||||||
<h5 className="mb-3"><i className="bi bi-funnel-fill me-2 text-primary"></i> Filtros</h5>
|
<h5 className="mb-3">
|
||||||
<div className="d-flex flex-nowrap align-items-center gap-2" style={{ overflowX: "auto", paddingBottom: "6px" }}>
|
<i className="bi bi-funnel-fill me-2 text-primary"></i> Filtros
|
||||||
<input type="text" className="form-control" placeholder="Buscar por nome..." style={{ minWidth: 250, maxWidth: 300, width: 260, flex: "0 0 auto" }} />
|
</h5>
|
||||||
|
<div className="row">
|
||||||
|
<div className="col-md-5">
|
||||||
|
<div className="mb-3">
|
||||||
|
<label className="form-label">Buscar por nome ou CPF do paciente</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-control"
|
||||||
|
placeholder="Digite nome ou CPF do paciente..."
|
||||||
|
value={termoPesquisa}
|
||||||
|
onChange={(e) => setTermoPesquisa(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="col-md-5">
|
||||||
|
<div className="mb-3">
|
||||||
|
<label className="form-label">Filtrar por tipo de exame</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-control"
|
||||||
|
placeholder="Digite o tipo de exame..."
|
||||||
|
value={filtroExame}
|
||||||
|
onChange={(e) => setFiltroExame(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="col-md-2 d-flex align-items-end">
|
||||||
|
<button className="btn btn-outline-secondary w-100" onClick={limparFiltros}>
|
||||||
|
<i className="bi bi-arrow-clockwise"></i> Limpar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-2">
|
||||||
|
<div className="contador-relatorios">
|
||||||
|
{relatoriosFinais.length} DE {relatoriosOriginais.length} RELATÓRIOS ENCONTRADOS
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -149,36 +287,91 @@ const DoctorRelatorioManager = () => {
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Paciente</th>
|
<th>Paciente</th>
|
||||||
<th>Doutor</th>
|
<th>CPF</th>
|
||||||
|
<th>Exame</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{RelatoriosFiltrados.length > 0 ? (
|
{relatoriosPaginados.length > 0 ? (
|
||||||
RelatoriosFiltrados.map((relatorio, idx) => (
|
relatoriosPaginados.map((relatorio, index) => {
|
||||||
|
const paciente = pacientesData[relatorio.patient_id];
|
||||||
|
return (
|
||||||
<tr key={relatorio.id}>
|
<tr key={relatorio.id}>
|
||||||
<td className='infos-paciente'>{PacientesComRelatorios[idx]?.full_name}</td>
|
<td>{paciente?.full_name || 'Carregando...'}</td>
|
||||||
<td className='infos-paciente'>{MedicosComRelatorios[idx]?.full_name || relatorio.requested_by || '-'}</td>
|
<td>{paciente?.cpf || 'Carregando...'}</td>
|
||||||
|
<td>{relatorio.exam}</td>
|
||||||
<td>
|
<td>
|
||||||
<div className="d-flex gap-2">
|
<div className="d-flex gap-2">
|
||||||
<button className="btn btn-sm" style={{ backgroundColor: "#E6F2FF", color: "#004085" }} onClick={() => { setShowModal(true); setIndex(idx); }}>
|
<button className="btn btn-sm btn-ver-detalhes" onClick={() => abrirModal(relatorio, index)}>
|
||||||
<i className="bi bi-eye me-1"></i> Ver Detalhes
|
<i className="bi bi-eye me-1"></i> Ver Detalhes
|
||||||
</button>
|
</button>
|
||||||
|
<button className="btn btn-sm btn-editar" onClick={() => navigate(`/medico/relatorios/${relatorio.id}/edit`)}>
|
||||||
<button className="btn btn-sm" style={{ backgroundColor: "#FFF3CD", color: "#856404" }} onClick={() => navigate(`/medico/relatorios/${relatorio.id}/edit`)}>
|
|
||||||
<i className="bi bi-pencil me-1"></i> Editar
|
<i className="bi bi-pencil me-1"></i> Editar
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))
|
);
|
||||||
|
})
|
||||||
) : (
|
) : (
|
||||||
<tr><td colSpan="8" className="text-center">Nenhum paciente encontrado.</td></tr>
|
<tr><td colSpan="4" className="text-center">Nenhum relatório encontrado.</td></tr>
|
||||||
)}
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
{relatoriosFinais.length > 0 && (
|
||||||
|
<div className="d-flex justify-content-between align-items-center mt-3">
|
||||||
|
<div className="d-flex align-items-center">
|
||||||
|
<span className="me-2 text-muted">Itens por página:</span>
|
||||||
|
<select
|
||||||
|
className="form-select form-select-sm w-auto"
|
||||||
|
value={itensPorPagina}
|
||||||
|
onChange={(e) => {
|
||||||
|
setItensPorPagina(Number(e.target.value));
|
||||||
|
setPaginaAtual(1);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value={5}>5</option>
|
||||||
|
<option value={10}>10</option>
|
||||||
|
<option value={25}>25</option>
|
||||||
|
<option value={50}>50</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="d-flex align-items-center">
|
||||||
|
<span className="me-3 text-muted">
|
||||||
|
Página {paginaAtual} de {totalPaginas} •
|
||||||
|
Mostrando {indiceInicial + 1}-{Math.min(indiceFinal, relatoriosFinais.length)} de {relatoriosFinais.length} itens
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<nav>
|
||||||
|
<ul className="pagination pagination-sm mb-0">
|
||||||
|
<li className={`page-item ${paginaAtual === 1 ? 'disabled' : ''}`}>
|
||||||
|
<button className="page-link" onClick={voltarPagina}>
|
||||||
|
<i className="bi bi-chevron-left"></i>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
{gerarNumerosPaginas().map(pagina => (
|
||||||
|
<li key={pagina} className={`page-item ${pagina === paginaAtual ? 'active' : ''}`}>
|
||||||
|
<button className="page-link" onClick={() => irParaPagina(pagina)}>
|
||||||
|
{pagina}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<li className={`page-item ${paginaAtual === totalPaginas ? 'disabled' : ''}`}>
|
||||||
|
<button className="page-link" onClick={avancarPagina}>
|
||||||
|
<i className="bi bi-chevron-right"></i>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// EditPageRelatorio.jsx
|
// src/PagesMedico/EditPageRelatorio.jsx
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useParams, useNavigate } from 'react-router-dom';
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
import API_KEY from '../components/utils/apiKeys';
|
import API_KEY from '../components/utils/apiKeys';
|
||||||
@ -52,7 +52,7 @@ const EditPageRelatorio = () => {
|
|||||||
try {
|
try {
|
||||||
const myHeaders = new Headers();
|
const myHeaders = new Headers();
|
||||||
myHeaders.append("apikey", API_KEY);
|
myHeaders.append("apikey", API_KEY);
|
||||||
myHeaders.append("Authorization", authHeader);
|
if (authHeader) myHeaders.append("Authorization", authHeader);
|
||||||
const requestOptions = { method: 'GET', headers: myHeaders, redirect: 'follow' };
|
const requestOptions = { method: 'GET', headers: myHeaders, redirect: 'follow' };
|
||||||
|
|
||||||
// Pega relatório por id (supabase geralmente retorna array para ?id=eq.X)
|
// Pega relatório por id (supabase geralmente retorna array para ?id=eq.X)
|
||||||
@ -101,12 +101,14 @@ const EditPageRelatorio = () => {
|
|||||||
try {
|
try {
|
||||||
const myHeaders = new Headers();
|
const myHeaders = new Headers();
|
||||||
myHeaders.append('apikey', API_KEY);
|
myHeaders.append('apikey', API_KEY);
|
||||||
myHeaders.append('Authorization', authHeader);
|
if (authHeader) myHeaders.append('Authorization', authHeader);
|
||||||
myHeaders.append('Content-Type', 'application/json');
|
myHeaders.append('Content-Type', 'application/json');
|
||||||
|
myHeaders.append('Accept', 'application/json');
|
||||||
|
// pedir que o Supabase retorne a representação do registro atualizado (opcional)
|
||||||
|
myHeaders.append('Prefer', 'return=representation');
|
||||||
|
|
||||||
const body = JSON.stringify({ content_html: html });
|
const body = JSON.stringify({ content_html: html });
|
||||||
|
|
||||||
// supabase: PATCH via query id=eq.<id>
|
|
||||||
const res = await fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?id=eq.${params.id}`, {
|
const res = await fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?id=eq.${params.id}`, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
headers: myHeaders,
|
headers: myHeaders,
|
||||||
@ -114,13 +116,29 @@ const EditPageRelatorio = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const txt = await res.text();
|
let txt;
|
||||||
|
try { txt = await res.text(); } catch (e) { txt = 'erro lendo resposta'; }
|
||||||
console.error('Erro PATCH', res.status, txt);
|
console.error('Erro PATCH', res.status, txt);
|
||||||
throw new Error('Erro na API');
|
throw new Error('Erro na API');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Recebe o dado atualizado e atualiza o estado do componente
|
||||||
|
let updatedData;
|
||||||
|
try {
|
||||||
|
updatedData = await res.json();
|
||||||
|
} catch (e) {
|
||||||
|
updatedData = null;
|
||||||
|
}
|
||||||
|
const updatedReport = Array.isArray(updatedData) ? updatedData[0] : updatedData;
|
||||||
|
|
||||||
|
if (updatedReport) {
|
||||||
|
setReport(updatedReport);
|
||||||
|
setHtml(updatedReport.content_html || '');
|
||||||
|
}
|
||||||
|
|
||||||
alert('Relatório atualizado com sucesso!');
|
alert('Relatório atualizado com sucesso!');
|
||||||
navigate('/medico/relatorios');
|
navigate('/medico/relatorios');
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
alert('Erro ao salvar. Veja console.');
|
alert('Erro ao salvar. Veja console.');
|
||||||
@ -150,4 +168,3 @@ const EditPageRelatorio = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default EditPageRelatorio;
|
export default EditPageRelatorio;
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
// src/PagesMedico/FormNovoRelatorio.jsx
|
||||||
import React, { useEffect, useState, useRef } from 'react';
|
import React, { useEffect, useState, useRef } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import API_KEY from '../components/utils/apiKeys';
|
import API_KEY from '../components/utils/apiKeys';
|
||||||
@ -39,7 +40,7 @@ const FormNovoRelatorio = () => {
|
|||||||
const doctorRef = useRef();
|
const doctorRef = useRef();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// carregar pacientes
|
// carregar pacientes e médicos
|
||||||
let mounted = true;
|
let mounted = true;
|
||||||
const loadPatients = async () => {
|
const loadPatients = async () => {
|
||||||
setLoadingPatients(true);
|
setLoadingPatients(true);
|
||||||
@ -109,7 +110,7 @@ const FormNovoRelatorio = () => {
|
|||||||
patient_id: patient.id,
|
patient_id: patient.id,
|
||||||
patient_name: patient.full_name || '',
|
patient_name: patient.full_name || '',
|
||||||
patient_birth: patient.birth_date || '',
|
patient_birth: patient.birth_date || '',
|
||||||
contentHtml: generateTemplate(patient.full_name || '', patient.birth_date || '', form.doctor_name)
|
contentHtml: generateTemplate(patient.full_name || '', patient.birth_date || '', prev.doctor_name)
|
||||||
}));
|
}));
|
||||||
setPatientQuery('');
|
setPatientQuery('');
|
||||||
setShowPatientDropdown(false);
|
setShowPatientDropdown(false);
|
||||||
@ -120,7 +121,7 @@ const FormNovoRelatorio = () => {
|
|||||||
...prev,
|
...prev,
|
||||||
doctor_id: doctor.id,
|
doctor_id: doctor.id,
|
||||||
doctor_name: doctor.full_name || '',
|
doctor_name: doctor.full_name || '',
|
||||||
contentHtml: generateTemplate(form.patient_name, form.patient_birth, doctor.full_name || '')
|
contentHtml: generateTemplate(prev.patient_name, prev.patient_birth, doctor.full_name || '')
|
||||||
}));
|
}));
|
||||||
setDoctorQuery('');
|
setDoctorQuery('');
|
||||||
setShowDoctorDropdown(false);
|
setShowDoctorDropdown(false);
|
||||||
@ -137,7 +138,7 @@ const FormNovoRelatorio = () => {
|
|||||||
|
|
||||||
const handleEditorChange = (html) => setForm(prev => ({ ...prev, contentHtml: html }));
|
const handleEditorChange = (html) => setForm(prev => ({ ...prev, contentHtml: html }));
|
||||||
|
|
||||||
// salvar novo relatório
|
// salvar novo relatório (agora com Prefer: return=representation e dispatch para refresh)
|
||||||
const handleSubmit = async (e) => {
|
const handleSubmit = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!form.patient_id) return alert('Selecione o paciente (clicando no item) antes de salvar.');
|
if (!form.patient_id) return alert('Selecione o paciente (clicando no item) antes de salvar.');
|
||||||
@ -146,35 +147,51 @@ const FormNovoRelatorio = () => {
|
|||||||
try {
|
try {
|
||||||
const myHeaders = new Headers();
|
const myHeaders = new Headers();
|
||||||
myHeaders.append('apikey', API_KEY);
|
myHeaders.append('apikey', API_KEY);
|
||||||
myHeaders.append('Authorization', authHeader);
|
if (authHeader) myHeaders.append('Authorization', authHeader);
|
||||||
myHeaders.append('Content-Type', 'application/json');
|
myHeaders.append('Content-Type', 'application/json');
|
||||||
|
myHeaders.append('Accept', 'application/json');
|
||||||
|
// pedir que o Supabase retorne a representação do registro criado
|
||||||
|
myHeaders.append('Prefer', 'return=representation');
|
||||||
|
|
||||||
const body = JSON.stringify({
|
// monta o payload apenas com campos válidos
|
||||||
|
const payload = {
|
||||||
patient_id: form.patient_id,
|
patient_id: form.patient_id,
|
||||||
content: form.contentHtml,
|
content: form.contentHtml,
|
||||||
content_html: form.contentHtml,
|
content_html: form.contentHtml,
|
||||||
requested_by: form.doctor_name || '',
|
requested_by: form.doctor_name || ''
|
||||||
created_by: form.doctor_id || null,
|
};
|
||||||
status: 'draft'
|
// só inclui created_by se tiver um id válido
|
||||||
});
|
if (form.doctor_id) payload.created_by = form.doctor_id;
|
||||||
|
payload.status = 'draft';
|
||||||
|
|
||||||
const res = await fetch('https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports', {
|
const res = await fetch('https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: myHeaders,
|
headers: myHeaders,
|
||||||
body,
|
body: JSON.stringify(payload),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const txt = await res.text();
|
// tenta ler JSON, se não for JSON lê o texto
|
||||||
console.error('Erro POST criar relatório:', res.status, txt);
|
let txt;
|
||||||
// mostra mensagem mais útil
|
try {
|
||||||
return alert(`Erro ao criar relatório (ver console). Status ${res.status}`);
|
txt = await res.json();
|
||||||
|
} catch (err) {
|
||||||
|
txt = await res.text();
|
||||||
}
|
}
|
||||||
|
console.error('Erro POST criar relatório:', res.status, txt);
|
||||||
|
return alert(`Erro ao criar relatório (ver console). Status ${res.status}\nMensagem: ${JSON.stringify(txt)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const created = await res.json();
|
||||||
|
console.log('Relatório criado:', created);
|
||||||
|
|
||||||
|
// dispara refresh global para a lista (DoctorRelatorioManager está escutando)
|
||||||
|
window.dispatchEvent(new Event('reports:refresh'));
|
||||||
|
|
||||||
alert('Relatório criado com sucesso!');
|
alert('Relatório criado com sucesso!');
|
||||||
navigate('/medico/relatorios');
|
navigate('/medico/relatorios');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Erro salvar relatório:', err);
|
console.error('Erro salvar relatório (catch):', err);
|
||||||
alert('Erro ao salvar relatório. Veja console.');
|
alert('Erro ao salvar relatório. Veja console.');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -185,7 +202,7 @@ const FormNovoRelatorio = () => {
|
|||||||
|
|
||||||
<form onSubmit={handleSubmit} className="card p-4 mb-4">
|
<form onSubmit={handleSubmit} className="card p-4 mb-4">
|
||||||
<div className="row g-3 align-items-end">
|
<div className="row g-3 align-items-end">
|
||||||
<div className="col-md-6" ref={patientRef}>
|
<div className="col-md-6" ref={patientRef} style={{ position: 'relative' }}>
|
||||||
<label className="form-label">Buscar paciente (digite para filtrar)</label>
|
<label className="form-label">Buscar paciente (digite para filtrar)</label>
|
||||||
<input
|
<input
|
||||||
className="form-control"
|
className="form-control"
|
||||||
@ -195,7 +212,7 @@ const FormNovoRelatorio = () => {
|
|||||||
onFocus={() => setShowPatientDropdown(true)}
|
onFocus={() => setShowPatientDropdown(true)}
|
||||||
/>
|
/>
|
||||||
{showPatientDropdown && patientQuery && (
|
{showPatientDropdown && patientQuery && (
|
||||||
<ul className="list-group position-absolute" style={{ zIndex: 50, maxHeight: 220, overflowY: 'auto', width: '45%' }}>
|
<ul className="list-group position-absolute" style={{ zIndex: 50, maxHeight: 220, overflowY: 'auto', width: '100%' }}>
|
||||||
{filteredPatients.length > 0 ? filteredPatients.map(p => (
|
{filteredPatients.length > 0 ? filteredPatients.map(p => (
|
||||||
<li key={p.id} className="list-group-item list-group-item-action" onClick={() => choosePatient(p)}>
|
<li key={p.id} className="list-group-item list-group-item-action" onClick={() => choosePatient(p)}>
|
||||||
{p.full_name} {p.cpf ? `- ${p.cpf}` : ''}
|
{p.full_name} {p.cpf ? `- ${p.cpf}` : ''}
|
||||||
@ -206,7 +223,7 @@ const FormNovoRelatorio = () => {
|
|||||||
<div className="form-text">Clique no paciente desejado para selecioná-lo e preencher o template.</div>
|
<div className="form-text">Clique no paciente desejado para selecioná-lo e preencher o template.</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-md-6" ref={doctorRef}>
|
<div className="col-md-6" ref={doctorRef} style={{ position: 'relative' }}>
|
||||||
<label className="form-label">Buscar médico (digite para filtrar)</label>
|
<label className="form-label">Buscar médico (digite para filtrar)</label>
|
||||||
<input
|
<input
|
||||||
className="form-control"
|
className="form-control"
|
||||||
@ -216,7 +233,7 @@ const FormNovoRelatorio = () => {
|
|||||||
onFocus={() => setShowDoctorDropdown(true)}
|
onFocus={() => setShowDoctorDropdown(true)}
|
||||||
/>
|
/>
|
||||||
{showDoctorDropdown && doctorQuery && (
|
{showDoctorDropdown && doctorQuery && (
|
||||||
<ul className="list-group position-absolute" style={{ zIndex: 50, maxHeight: 220, overflowY: 'auto', width: '45%', right: 0 }}>
|
<ul className="list-group position-absolute" style={{ zIndex: 50, maxHeight: 220, overflowY: 'auto', width: '100%' }}>
|
||||||
{filteredDoctors.length > 0 ? filteredDoctors.map(d => (
|
{filteredDoctors.length > 0 ? filteredDoctors.map(d => (
|
||||||
<li key={d.id} className="list-group-item list-group-item-action" onClick={() => chooseDoctor(d)}>
|
<li key={d.id} className="list-group-item list-group-item-action" onClick={() => chooseDoctor(d)}>
|
||||||
{d.full_name} {d.crm ? `- CRM ${d.crm}` : ''}
|
{d.full_name} {d.crm ? `- CRM ${d.crm}` : ''}
|
||||||
|
|||||||
31
src/PagesMedico/styleMedico/DoctorRelatorioManager.css
Normal file
31
src/PagesMedico/styleMedico/DoctorRelatorioManager.css
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
.contador-relatorios {
|
||||||
|
background-color: #1e3a8a;
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ver-detalhes {
|
||||||
|
background-color: #E6F2FF;
|
||||||
|
color: #004085;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ver-detalhes:hover {
|
||||||
|
background-color: #cce5ff;
|
||||||
|
color: #004085;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-editar {
|
||||||
|
background-color: #FFF3CD;
|
||||||
|
color: #856404;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-editar:hover {
|
||||||
|
background-color: #ffeaa7;
|
||||||
|
color: #856404;
|
||||||
|
}
|
||||||
@ -1,55 +1,152 @@
|
|||||||
#folhaA4 {
|
/* src/PagesMedico/styleMedico/FormNovoRelatorio.css */
|
||||||
width: 210mm;
|
|
||||||
min-height: 207mm;
|
|
||||||
padding: 20mm;
|
|
||||||
margin: 10mm auto;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
background: white;
|
|
||||||
|
|
||||||
}
|
/* --- Modal centralizada e quadrada (ajustada para ser mais larga e sem quadrado branco no botão fechar) --- */
|
||||||
|
|
||||||
#primeiraLinha{
|
/* backdrop + center */
|
||||||
|
.modal.modal-centered {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
align-items: center;
|
||||||
gap: 20px;
|
justify-content: center;
|
||||||
margin-bottom: 20px;
|
background: rgba(10, 20, 30, 0.45);
|
||||||
|
z-index: 2000;
|
||||||
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
input,textarea,label{
|
/* dialog box — maior horizontalmente e altura automática para scroll interno */
|
||||||
font-size: 1.1rem;
|
.modal-dialog.modal-dialog-square {
|
||||||
|
width: 880px; /* largura aumentada */
|
||||||
|
max-width: 96vw;
|
||||||
|
height: auto; /* deixa altura automática (melhor para conteúdo longo) */
|
||||||
|
max-height: 92vh;
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea{
|
/* caixa branca que contém o conteúdo - ocupa 100% da dialog */
|
||||||
|
.modal-dialog.modal-dialog-square .modal-content {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100px;
|
height: auto;
|
||||||
|
border-radius: 12px;
|
||||||
}
|
box-shadow: 0 12px 30px rgba(11,22,35,0.18);
|
||||||
|
overflow: hidden;
|
||||||
.submitButton{
|
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-left: auto;
|
flex-direction: column;
|
||||||
height:50% ;
|
background: #fff;
|
||||||
padding: 8px 20px;
|
|
||||||
|
|
||||||
font-size: medium;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.bi-download{
|
/* header */
|
||||||
font-size: 1.2rem;
|
.custom-modal-header {
|
||||||
margin-right: 5px;
|
position: relative;
|
||||||
font-weight: bold;
|
background: linear-gradient(90deg, #203B75 0%, #274A8A 100%);
|
||||||
|
color: #fff;
|
||||||
|
padding: 14px 18px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#infoPaciente{
|
.custom-modal-header .modal-title {
|
||||||
margin-top: 50px;
|
margin: 0;
|
||||||
margin-bottom: 40px;
|
font-size: 1.05rem;
|
||||||
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
#header-relatorio{
|
/* botão fechar no header — sem quadrado branco por trás */
|
||||||
|
.modal-close-btn {
|
||||||
|
background: transparent !important;
|
||||||
|
border: none;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: none;
|
||||||
|
outline: none;
|
||||||
|
position: relative;
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-close-btn::after {
|
||||||
|
content: '✕';
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* body - faz scroll interno se for longo */
|
||||||
|
.modal-body {
|
||||||
|
padding: 18px;
|
||||||
|
overflow: auto;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* footer */
|
||||||
|
.custom-modal-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 14px 18px;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* folhaA4 dentro da modal — adapta para caber */
|
||||||
|
.folhaA4 {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: transparent;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* melhor espaçamento e leitura do conteúdo */
|
||||||
|
#header-relatorio p {
|
||||||
|
color: #374151;
|
||||||
|
margin: 6px 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-paciente{
|
#infoPaciente p {
|
||||||
font-weight: bold;
|
margin: 10px 0;
|
||||||
|
color: #3d4650;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* tornar o viewer responsivo */
|
||||||
|
.tiptap-viewer-wrapper {
|
||||||
|
border: 1px dashed #e7e7e7;
|
||||||
|
padding: 12px;
|
||||||
|
margin-top: 10px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* barra de scroll customizada (opcional) */
|
||||||
|
.modal-body::-webkit-scrollbar {
|
||||||
|
width: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(100,100,100,0.18);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body::-webkit-scrollbar-track {
|
||||||
|
background: rgba(0,0,0,0.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* responsividade para telas pequenas: mantém centralizado, ajusta proporção */
|
||||||
|
@media (max-width: 680px) {
|
||||||
|
.modal-dialog.modal-dialog-square {
|
||||||
|
width: 92vw;
|
||||||
|
height: 86vh;
|
||||||
|
}
|
||||||
|
.modal-close-btn { width: 36px; height: 36px; }
|
||||||
|
.custom-modal-footer { padding: 10px; }
|
||||||
|
.modal-body { padding: 12px; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,109 +4,121 @@ import { useState, useEffect } from 'react'
|
|||||||
import API_KEY from '../components/utils/apiKeys'
|
import API_KEY from '../components/utils/apiKeys'
|
||||||
import { UserInfos } from '../components/utils/Functions-Endpoints/General'
|
import { UserInfos } from '../components/utils/Functions-Endpoints/General'
|
||||||
import FormConsultaPaciente from './FormConsultaPaciente'
|
import FormConsultaPaciente from './FormConsultaPaciente'
|
||||||
|
|
||||||
import { GetDoctorByID } from '../components/utils/Functions-Endpoints/Doctor'
|
import { GetDoctorByID } from '../components/utils/Functions-Endpoints/Doctor'
|
||||||
import { GetPatientByID } from '../components/utils/Functions-Endpoints/Patient'
|
import { GetPatientByID } from '../components/utils/Functions-Endpoints/Patient'
|
||||||
const ConsultaEditPage = ({dadosConsulta}) => {
|
// 1. Importe o useNavigate
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
|
||||||
console.log(dadosConsulta, "editar")
|
const ConsultaEditPage = ({ dadosConsulta }) => {
|
||||||
|
// 2. Crie a instância do navigate
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const {getAuthorizationHeader} = useAuth()
|
const { getAuthorizationHeader } = useAuth()
|
||||||
|
const authHeader = getAuthorizationHeader();
|
||||||
|
|
||||||
const [idUsuario, setIDusuario] = useState("6e7f8829-0574-42df-9290-8dbb70f75ada")
|
const [idUsuario, setIDusuario] = useState(null);
|
||||||
|
const [DictInfo, setDict] = useState({});
|
||||||
const [DictInfo, setDict] = useState({})
|
const [Medico, setMedico] = useState(null);
|
||||||
|
const [Paciente, setPaciente] = useState(null);
|
||||||
const [Medico, setMedico] = useState({})
|
|
||||||
|
|
||||||
const [Paciente, setPaciente] = useState([])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setDict({...dadosConsulta})
|
setDict({ ...dadosConsulta });
|
||||||
|
|
||||||
const fetchMedicoePaciente = async () => {
|
|
||||||
console.log(dadosConsulta.doctor_id)
|
|
||||||
|
|
||||||
let Medico = await GetDoctorByID(dadosConsulta.doctor_id,authHeader )
|
|
||||||
|
|
||||||
let Paciente = await GetPatientByID(dadosConsulta.patient_id,authHeader )
|
|
||||||
|
|
||||||
console.log(Paciente, 'Paciente')
|
|
||||||
|
|
||||||
|
|
||||||
setMedico(Medico[0])
|
|
||||||
setPaciente(Paciente[0])
|
|
||||||
|
|
||||||
|
|
||||||
|
const fetchInitialData = async () => {
|
||||||
|
if (dadosConsulta.doctor_id) {
|
||||||
|
const medicoData = await GetDoctorByID(dadosConsulta.doctor_id, authHeader);
|
||||||
|
setMedico(medicoData[0]);
|
||||||
}
|
}
|
||||||
const ColherInfoUsuario =async () => {
|
if (dadosConsulta.patient_id) {
|
||||||
const result = await UserInfos(authHeader)
|
const pacienteData = await GetPatientByID(dadosConsulta.patient_id, authHeader);
|
||||||
|
setPaciente(pacienteData[0]);
|
||||||
setIDusuario(result?.profile?.id)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
ColherInfoUsuario()
|
};
|
||||||
fetchMedicoePaciente()
|
|
||||||
|
|
||||||
|
const fetchUserInfo = async () => {
|
||||||
|
const result = await UserInfos(authHeader);
|
||||||
|
setIDusuario(result?.profile?.id);
|
||||||
|
};
|
||||||
|
|
||||||
}, [])
|
fetchUserInfo();
|
||||||
|
fetchInitialData();
|
||||||
|
|
||||||
|
}, [dadosConsulta, authHeader]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setDict({...DictInfo, medico_nome:Medico?.full_name, dataAtendimento:dadosConsulta.scheduled_at?.split("T")[0]})
|
if (Medico) {
|
||||||
}, [Medico])
|
setDict(prevDict => ({
|
||||||
|
...prevDict,
|
||||||
|
medico_nome: Medico?.full_name,
|
||||||
|
dataAtendimento: dadosConsulta.scheduled_at?.split("T")[0]
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}, [Medico, dadosConsulta.scheduled_at]);
|
||||||
|
|
||||||
|
const handleSave = async (DictParaPatch) => {
|
||||||
|
try {
|
||||||
let authHeader = getAuthorizationHeader()
|
const myHeaders = new Headers();
|
||||||
|
|
||||||
const handleSave = (DictParaPatch) => {
|
|
||||||
var myHeaders = new Headers();
|
|
||||||
myHeaders.append("Content-Type", "application/json");
|
myHeaders.append("Content-Type", "application/json");
|
||||||
myHeaders.append('apikey', API_KEY)
|
myHeaders.append('apikey', API_KEY);
|
||||||
myHeaders.append("authorization", authHeader)
|
myHeaders.append("authorization", authHeader);
|
||||||
|
myHeaders.append('Prefer', 'return=representation');
|
||||||
console.log(DictParaPatch)
|
|
||||||
|
|
||||||
var raw = JSON.stringify({"patient_id": DictParaPatch.patient_id,
|
|
||||||
"doctor_id": DictParaPatch.doctor_id,
|
|
||||||
|
|
||||||
"duration_minutes": 30,
|
|
||||||
|
|
||||||
"chief_complaint": "Dor de cabeça há 3 ",
|
|
||||||
|
|
||||||
"created_by": idUsuario,
|
|
||||||
|
|
||||||
"scheduled_at": `${DictParaPatch.dataAtendimento}T${DictParaPatch.horarioInicio}:00.000Z`,
|
|
||||||
|
|
||||||
"appointment_type": DictParaPatch.tipo_consulta,
|
|
||||||
|
|
||||||
"patient_notes": "Prefiro horário pela manhã",
|
|
||||||
"insurance_provider": DictParaPatch.convenio,
|
|
||||||
"status": DictParaPatch.status,
|
|
||||||
"created_by": idUsuario
|
|
||||||
|
|
||||||
|
|
||||||
|
const raw = JSON.stringify({
|
||||||
|
patient_id: DictParaPatch.patient_id,
|
||||||
|
doctor_id: DictParaPatch.doctor_id,
|
||||||
|
duration_minutes: 30,
|
||||||
|
chief_complaint: "Dor de cabeça há 3 ",
|
||||||
|
created_by: idUsuario,
|
||||||
|
scheduled_at: `${DictParaPatch.dataAtendimento}T${DictParaPatch.horarioInicio}:00.000Z`,
|
||||||
|
appointment_type: DictParaPatch.tipo_consulta,
|
||||||
|
patient_notes: "Prefiro horário pela manhã",
|
||||||
|
insurance_provider: DictParaPatch.convenio,
|
||||||
|
status: DictParaPatch.status,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const requestOptions = {
|
||||||
var requestOptions = {
|
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
headers: myHeaders,
|
headers: myHeaders,
|
||||||
body: raw,
|
body: raw,
|
||||||
redirect: 'follow'
|
redirect: 'follow'
|
||||||
};
|
};
|
||||||
|
|
||||||
fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?id=eq.${DictInfo.id}`, requestOptions)
|
const response = await fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?id=eq.${DictInfo.id}`, requestOptions);
|
||||||
.then(response => response.text())
|
|
||||||
.then(result => console.log(result))
|
if (!response.ok) {
|
||||||
.catch(error => console.log('error', error));
|
const text = await response.text();
|
||||||
|
console.error('Erro no PATCH:', response.status, text);
|
||||||
|
throw new Error('Erro na API');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updatedData = await response.json();
|
||||||
|
if (updatedData && updatedData.length > 0) {
|
||||||
|
setDict(updatedData[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Consulta atualizada com sucesso!', updatedData);
|
||||||
|
alert('Consulta atualizada com sucesso!');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erro ao salvar consulta:', error);
|
||||||
|
alert('Erro ao salvar consulta. Veja o console.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleCancel = () => {
|
||||||
|
navigate(-1);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<FormConsultaPaciente agendamento={DictInfo} setAgendamento={setDict} onSave={handleSave}/>
|
{}
|
||||||
|
<FormConsultaPaciente
|
||||||
|
agendamento={DictInfo}
|
||||||
|
setAgendamento={setDict}
|
||||||
|
onSave={handleSave}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ConsultaEditPage
|
export default ConsultaEditPage;
|
||||||
|
|||||||
@ -6,9 +6,8 @@ import { useEffect, useState } from 'react'
|
|||||||
import API_KEY from '../components/utils/apiKeys'
|
import API_KEY from '../components/utils/apiKeys'
|
||||||
import { useAuth } from '../components/utils/AuthProvider'
|
import { useAuth } from '../components/utils/AuthProvider'
|
||||||
|
|
||||||
const ConsultasPaciente = ({setConsulta}) => {
|
const ConsultasPaciente = ({ setConsulta }) => {
|
||||||
const {getAuthorizationHeader} = useAuth()
|
const { getAuthorizationHeader } = useAuth()
|
||||||
|
|
||||||
|
|
||||||
const [showDeleteModal, setShowDeleteModal] = useState(false)
|
const [showDeleteModal, setShowDeleteModal] = useState(false)
|
||||||
const [selectedID, setSelectedId] = useState("")
|
const [selectedID, setSelectedId] = useState("")
|
||||||
@ -16,74 +15,77 @@ const ConsultasPaciente = ({setConsulta}) => {
|
|||||||
|
|
||||||
const [consultas, setConsultas] = useState([])
|
const [consultas, setConsultas] = useState([])
|
||||||
|
|
||||||
const FiltrarAgendamentos = (agendamentos, id) => {
|
const FiltrarAgendamentos = (agendamentos, id) => {
|
||||||
// Verifica se a lista de agendamentos é válida antes de tentar filtrar
|
|
||||||
if (!agendamentos || !Array.isArray(agendamentos)) {
|
if (!agendamentos || !Array.isArray(agendamentos)) {
|
||||||
console.error("A lista de agendamentos é inválida.");
|
console.error("A lista de agendamentos é inválida.");
|
||||||
setConsultas([]); // Garante que setConsultas receba uma lista vazia
|
setConsultas([]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Filtragem
|
|
||||||
// O método .filter() cria uma nova lista contendo apenas os itens que retornarem 'true'
|
|
||||||
const consultasFiltradas = agendamentos.filter(agendamento => {
|
const consultasFiltradas = agendamentos.filter(agendamento => {
|
||||||
// A condição: o patient_id do agendamento deve ser estritamente igual ao id fornecido
|
|
||||||
// Usamos toString() para garantir a comparação, pois um pode ser number e o outro string
|
|
||||||
return agendamento.patient_id && agendamento.patient_id.toString() === id.toString();
|
return agendamento.patient_id && agendamento.patient_id.toString() === id.toString();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 2. Adicionar a lista no setConsultas
|
|
||||||
console.log(consultasFiltradas)
|
console.log(consultasFiltradas)
|
||||||
setConsultas(consultasFiltradas);
|
setConsultas(consultasFiltradas);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exemplo de como você chamaria (assumindo que DadosAgendamento é sua lista original):
|
|
||||||
// FiltrarAgendamentos(DadosAgendamento, Paciente.id);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
var myHeaders = new Headers();
|
const fetchConsultas = async () => {
|
||||||
|
try {
|
||||||
|
const myHeaders = new Headers();
|
||||||
myHeaders.append("Authorization", authHeader);
|
myHeaders.append("Authorization", authHeader);
|
||||||
myHeaders.append("apikey", API_KEY)
|
myHeaders.append("apikey", API_KEY)
|
||||||
|
|
||||||
var requestOptions = {
|
const requestOptions = {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: myHeaders,
|
headers: myHeaders,
|
||||||
redirect: 'follow'
|
redirect: 'follow'
|
||||||
};
|
};
|
||||||
|
|
||||||
fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?select&doctor_id&patient_id&status&scheduled_at&order&limit&offset", requestOptions)
|
const response = await fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?select=*", requestOptions);
|
||||||
.then(response => response.json())
|
const result = await response.json();
|
||||||
.then(result => {FiltrarAgendamentos(result, "6e7f8829-0574-42df-9290-8dbb70f75ada" )})
|
FiltrarAgendamentos(result, "6e7f8829-0574-42df-9290-8dbb70f75ada");
|
||||||
.catch(error => console.log('error', error));
|
} catch (error) {
|
||||||
|
console.log('error', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
}, [])
|
fetchConsultas();
|
||||||
|
}, [authHeader]);
|
||||||
|
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
const deleteConsulta = async (ID) => {
|
||||||
const deleteConsulta= (ID) => {
|
try {
|
||||||
var myHeaders = new Headers();
|
const myHeaders = new Headers();
|
||||||
myHeaders.append("Content-Type", "application/json");
|
myHeaders.append("Content-Type", "application/json");
|
||||||
myHeaders.append('apikey', API_KEY)
|
myHeaders.append('apikey', API_KEY);
|
||||||
myHeaders.append("authorization", authHeader)
|
myHeaders.append("authorization", authHeader);
|
||||||
|
|
||||||
|
const raw = JSON.stringify({ "status": "cancelled" });
|
||||||
|
|
||||||
var raw = JSON.stringify({ "status":"cancelled"
|
const requestOptions = {
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
var requestOptions = {
|
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
headers: myHeaders,
|
headers: myHeaders,
|
||||||
body: raw,
|
body: raw,
|
||||||
redirect: 'follow'
|
redirect: 'follow'
|
||||||
};
|
};
|
||||||
|
|
||||||
fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?id=eq.${selectedID}`, requestOptions)
|
const response = await fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?id=eq.${ID}`, requestOptions);
|
||||||
.then(response => {if(response.status !== 200)(console.log(response))})
|
|
||||||
.then(result => console.log(result))
|
|
||||||
.catch(error => console.log('error', error));
|
|
||||||
|
|
||||||
console.log("deletar", ID)
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text();
|
||||||
|
throw new Error(`Falha ao cancelar consulta: ${response.status} - ${errorText}`);
|
||||||
|
}
|
||||||
|
setConsultas(prevConsultas => prevConsultas.filter(consulta => consulta.id !== ID));
|
||||||
|
|
||||||
|
console.log("Consulta cancelada com sucesso!");
|
||||||
|
alert("Consulta cancelada com sucesso!");
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erro ao cancelar a consulta:', error);
|
||||||
|
alert('Erro ao cancelar a consulta. Veja o console.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -91,22 +93,19 @@ const FiltrarAgendamentos = (agendamentos, id) => {
|
|||||||
<h1> Gerencie suas consultas</h1>
|
<h1> Gerencie suas consultas</h1>
|
||||||
|
|
||||||
<div className='form-container'>
|
<div className='form-container'>
|
||||||
|
<button className="btn btn-primary" onClick={() => { navigate("criar") }}>
|
||||||
<button className="btn btn-primary" onClick={() => {navigate("criar")}}>
|
|
||||||
<i className="bi bi-plus-circle"></i> Adicionar Consulta
|
<i className="bi bi-plus-circle"></i> Adicionar Consulta
|
||||||
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<h2>Seus proximos atendimentos</h2>
|
<h2>Seus próximos atendimentos</h2>
|
||||||
|
|
||||||
{consultas.map((consulta) => (
|
{consultas.map((consulta) => (
|
||||||
<CardConsultaPaciente consulta={consulta} setConsulta={setConsulta} setShowDeleteModal={setShowDeleteModal} setSelectedId={ setSelectedId}/>
|
<CardConsultaPaciente key={consulta.id} consulta={consulta} setConsulta={setConsulta} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} />
|
||||||
|
|
||||||
))}
|
))}
|
||||||
{showDeleteModal &&
|
|
||||||
|
{showDeleteModal &&
|
||||||
<div className="modal-dialog modal-dialog-centered">
|
<div className="modal-dialog modal-dialog-centered">
|
||||||
<div className="modal-content">
|
<div className="modal-content">
|
||||||
|
|
||||||
<div className="modal-header bg-danger bg-opacity-25">
|
<div className="modal-header bg-danger bg-opacity-25">
|
||||||
<h5 className="modal-title text-danger">
|
<h5 className="modal-title text-danger">
|
||||||
Confirmação de Exclusão
|
Confirmação de Exclusão
|
||||||
@ -117,15 +116,12 @@ const FiltrarAgendamentos = (agendamentos, id) => {
|
|||||||
onClick={() => setShowDeleteModal(false)}
|
onClick={() => setShowDeleteModal(false)}
|
||||||
></button>
|
></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="modal-body">
|
<div className="modal-body">
|
||||||
<p className="mb-0 fs-5">
|
<p className="mb-0 fs-5">
|
||||||
Tem certeza que deseja excluir este agendamento?
|
Tem certeza que deseja excluir este agendamento?
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="modal-footer">
|
<div className="modal-footer">
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-primary"
|
className="btn btn-primary"
|
||||||
@ -133,23 +129,19 @@ const FiltrarAgendamentos = (agendamentos, id) => {
|
|||||||
>
|
>
|
||||||
Cancelar
|
Cancelar
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-danger"
|
className="btn btn-danger"
|
||||||
onClick={() => {deleteConsulta(selectedID);setShowDeleteModal(false)}}
|
onClick={() => { deleteConsulta(selectedID); setShowDeleteModal(false) }}
|
||||||
|
|
||||||
>
|
>
|
||||||
<i className="bi bi-trash me-1"></i> Excluir
|
<i className="bi bi-trash me-1"></i> Excluir
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>}
|
</div>}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ConsultasPaciente
|
export default ConsultasPaciente;
|
||||||
@ -4,14 +4,20 @@ import { useAuth } from '../utils/AuthProvider';
|
|||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import "./style/card-consulta.css"
|
import "./style/card-consulta.css"
|
||||||
const CardConsulta = ( {DadosConsulta, TabelaAgendamento, setShowDeleteModal, setDictInfo, setSelectedId} ) => {
|
const CardConsulta = ( {DadosConsulta, TabelaAgendamento, setShowDeleteModal, setDictInfo, setSelectedId, setShowConfirmModal, corModal, selectedID, coresConsultas, setListaConsultaID, listaConsultasID} ) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const {getAuthorizationHeader} = useAuth()
|
const {getAuthorizationHeader} = useAuth()
|
||||||
const authHeader = getAuthorizationHeader()
|
const authHeader = getAuthorizationHeader()
|
||||||
const [Paciente, setPaciente] = useState()
|
const [Paciente, setPaciente] = useState()
|
||||||
const [Medico, setMedico] = useState()
|
const [Medico, setMedico] = useState()
|
||||||
|
|
||||||
|
const [decidirBotton, setDecidirBotton] = useState("")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const ids = useMemo(() => {
|
const ids = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
doctor_id: DadosConsulta?.doctor_id,
|
doctor_id: DadosConsulta?.doctor_id,
|
||||||
@ -45,23 +51,35 @@ const CardConsulta = ( {DadosConsulta, TabelaAgendamento, setShowDeleteModal, se
|
|||||||
|
|
||||||
let nameArrayMedico = Medico?.full_name.split(' ')
|
let nameArrayMedico = Medico?.full_name.split(' ')
|
||||||
|
|
||||||
console.log(DadosConsulta.status)
|
|
||||||
|
let indice_cor = listaConsultasID.indexOf(DadosConsulta.id)
|
||||||
|
|
||||||
|
// console.log(coresConsultas)
|
||||||
|
//console.log(indice_cor,"indice no cores")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`container-cardconsulta container-cardconsulta-${TabelaAgendamento}`}>
|
<div className={`container-cardconsulta container-cardconsulta-${TabelaAgendamento} ` }>
|
||||||
|
|
||||||
|
|
||||||
{DadosConsulta.id?
|
{DadosConsulta.id?
|
||||||
|
|
||||||
<div className='cardconsulta' id={`status-card-consulta-${DadosConsulta.status}`}>
|
<div className={`cardconsulta`} id={indice_cor !== -1 ? `status-card-consulta-${coresConsultas[indice_cor]}` : `status-card-consulta-${DadosConsulta.status}`}>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<section className='cardconsulta-infosecundaria'>
|
<section className='cardconsulta-infosecundaria'>
|
||||||
<p>{DadosConsulta.horario} {nameArrayMedico && nameArrayMedico.length > 0 ? nameArrayMedico[0] : ''} {nameArrayMedico && nameArrayMedico.length > 1 ? ` ${nameArrayMedico[1]}` : ''} </p>
|
<p>Medico:{DadosConsulta.horario} {nameArrayMedico && nameArrayMedico.length > 0 ? nameArrayMedico[0] : ''} {nameArrayMedico && nameArrayMedico.length > 1 ? ` ${nameArrayMedico[1]}` : ''} </p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className='cardconsulta-infoprimaria'>
|
<section className='cardconsulta-infoprimaria'>
|
||||||
|
|
||||||
<p>{nameArrayPaciente && nameArrayPaciente.length > 0 ? nameArrayPaciente[0] : ''} {nameArrayPaciente && nameArrayPaciente.length > 1 ? ` ${nameArrayPaciente[1]}` : ''}- {}</p>
|
<p>Paciente: {nameArrayPaciente && nameArrayPaciente.length > 0 ? nameArrayPaciente[0] : ''} {nameArrayPaciente && nameArrayPaciente.length > 1 ? ` ${nameArrayPaciente[1]}` : ''}- {}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</p>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -75,16 +93,35 @@ const CardConsulta = ( {DadosConsulta, TabelaAgendamento, setShowDeleteModal, se
|
|||||||
>
|
>
|
||||||
<i className="bi bi-pencil me-1"></i>
|
<i className="bi bi-pencil me-1"></i>
|
||||||
</button>
|
</button>
|
||||||
|
{DadosConsulta.status === "cancelled" ?
|
||||||
|
<button
|
||||||
|
className="btn btn-sm btn-confirm-style"
|
||||||
|
onClick={() => {
|
||||||
|
console.log(DadosConsulta.id)
|
||||||
|
setShowConfirmModal(true)
|
||||||
|
setSelectedId(DadosConsulta.id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<i class="bi bi-check-lg"></i>
|
||||||
|
</button>
|
||||||
|
:
|
||||||
<button
|
<button
|
||||||
className="btn btn-sm btn-delete-custom-style "
|
className="btn btn-sm btn-delete-custom-style "
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
console.log(DadosConsulta.id)
|
console.log(DadosConsulta.id)
|
||||||
setSelectedId(DadosConsulta.id);
|
setSelectedId(DadosConsulta.id);
|
||||||
|
|
||||||
|
|
||||||
setShowDeleteModal(true);
|
setShowDeleteModal(true);
|
||||||
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<i className="bi bi-trash me-1"></i>
|
<i className="bi bi-trash me-1"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -94,6 +131,8 @@ const CardConsulta = ( {DadosConsulta, TabelaAgendamento, setShowDeleteModal, se
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,36 +1,25 @@
|
|||||||
import InputMask from "react-input-mask";
|
import InputMask from "react-input-mask";
|
||||||
import "./style/formagendamentos.css";
|
import "./style/formagendamentos.css";
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect, useCallback } from "react";
|
||||||
import { GetPatientByCPF } from "../utils/Functions-Endpoints/Patient";
|
import { GetPatientByCPF } from "../utils/Functions-Endpoints/Patient";
|
||||||
import { GetDoctorByName, GetAllDoctors } from "../utils/Functions-Endpoints/Doctor";
|
import { GetAllDoctors } from "../utils/Functions-Endpoints/Doctor";
|
||||||
import { useAuth } from "../utils/AuthProvider";
|
import { useAuth } from "../utils/AuthProvider";
|
||||||
import API_KEY from "../utils/apiKeys";
|
import API_KEY from "../utils/apiKeys";
|
||||||
|
|
||||||
const FormNovaConsulta = ({ onCancel, onSave, setAgendamento, agendamento }) => {
|
const FormNovaConsulta = ({ onCancel, onSave, setAgendamento, agendamento }) => {
|
||||||
const {getAuthorizationHeader} = useAuth()
|
const { getAuthorizationHeader } = useAuth();
|
||||||
|
|
||||||
console.log(agendamento, 'aqui2')
|
|
||||||
|
|
||||||
const [sessoes,setSessoes] = useState(1)
|
|
||||||
const [tempoBaseConsulta, setTempoBaseConsulta] = useState(30); // NOVO: Tempo base da consulta em minutos
|
|
||||||
|
|
||||||
const [selectedFile, setSelectedFile] = useState(null);
|
|
||||||
const [anexos, setAnexos] = useState([]);
|
|
||||||
const [loadingAnexos, setLoadingAnexos] = useState(false);
|
|
||||||
const [acessibilidade, setAcessibilidade] = useState({cadeirante:false,idoso:false,gravida:false,bebe:false, autista:false })
|
|
||||||
|
|
||||||
|
|
||||||
const [todosProfissionais, setTodosProfissionais] = useState([])
|
|
||||||
const [profissionaisFiltrados, setProfissionaisFiltrados] = useState([]);
|
|
||||||
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
|
||||||
|
|
||||||
|
|
||||||
|
const [sessoes, setSessoes] = useState(1);
|
||||||
|
const [tempoBaseConsulta] = useState(30);
|
||||||
|
const [showSuccessModal, setShowSuccessModal] = useState(false);
|
||||||
|
const [todosProfissionais, setTodosProfissionais] = useState([]);
|
||||||
|
const [profissionaisFiltrados, setProfissionaisFiltrados] = useState([]);
|
||||||
|
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
||||||
const [horarioInicio, setHorarioInicio] = useState('');
|
const [horarioInicio, setHorarioInicio] = useState('');
|
||||||
const [horarioTermino, setHorarioTermino] = useState('');
|
const [horarioTermino, setHorarioTermino] = useState('');
|
||||||
|
const [horariosDisponiveis, sethorariosDisponiveis] = useState([]);
|
||||||
|
|
||||||
const [horariosDisponiveis, sethorariosDisponiveis] = useState([])
|
const authHeader = getAuthorizationHeader();
|
||||||
|
|
||||||
let authHeader = getAuthorizationHeader()
|
|
||||||
|
|
||||||
const FormatCPF = (valor) => {
|
const FormatCPF = (valor) => {
|
||||||
const digits = String(valor).replace(/\D/g, '').slice(0, 11);
|
const digits = String(valor).replace(/\D/g, '').slice(0, 11);
|
||||||
@ -38,137 +27,116 @@ const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
|||||||
.replace(/(\d{3})(\d)/, '$1.$2')
|
.replace(/(\d{3})(\d)/, '$1.$2')
|
||||||
.replace(/(\d{3})(\d)/, '$1.$2')
|
.replace(/(\d{3})(\d)/, '$1.$2')
|
||||||
.replace(/(\d{3})(\d{1,2})$/, '$1-$2');
|
.replace(/(\d{3})(\d{1,2})$/, '$1-$2');
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const handleChange = (e) => {
|
const handleChange = (e) => {
|
||||||
const {value, name} = e.target;
|
const { value, name } = e.target;
|
||||||
console.log(value, name, agendamento)
|
|
||||||
|
|
||||||
if(name === 'email'){
|
if (name === 'email') {
|
||||||
setAgendamento({...agendamento, contato:{
|
setAgendamento(prev => ({
|
||||||
...agendamento.contato,
|
|
||||||
email:value
|
|
||||||
}})}
|
|
||||||
else if(name === 'status'){
|
|
||||||
if(agendamento.status==='requested'){
|
|
||||||
setAgendamento((prev) => ({
|
|
||||||
...prev,
|
...prev,
|
||||||
status:'confirmed',
|
contato: { ...prev.contato, email: value }
|
||||||
}));
|
}));
|
||||||
}else if(agendamento.status === 'confirmed'){
|
} else if (name === 'status') {
|
||||||
console.log(value)
|
setAgendamento(prev => ({
|
||||||
setAgendamento((prev) => ({
|
|
||||||
...prev,
|
...prev,
|
||||||
status:'requested',
|
status: prev.status === 'requested' ? 'confirmed' : 'requested'
|
||||||
}));
|
}));
|
||||||
}}
|
} else if (name === 'paciente_cpf') {
|
||||||
|
const cpfFormatted = FormatCPF(value);
|
||||||
else if(name === 'paciente_cpf'){
|
|
||||||
|
|
||||||
let cpfFormatted = FormatCPF(value)
|
|
||||||
const fetchPatient = async () => {
|
const fetchPatient = async () => {
|
||||||
let patientData = await GetPatientByCPF(cpfFormatted, authHeader);
|
const patientData = await GetPatientByCPF(cpfFormatted, authHeader);
|
||||||
if (patientData) {
|
if (patientData) {
|
||||||
setAgendamento((prev) => ({
|
setAgendamento(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
paciente_nome: patientData.full_name,
|
paciente_nome: patientData.full_name,
|
||||||
patient_id: patientData.id
|
patient_id: patientData.id
|
||||||
}));
|
}));
|
||||||
}}
|
|
||||||
setAgendamento(prev => ({ ...prev, cpf: cpfFormatted }))
|
|
||||||
fetchPatient()
|
|
||||||
}else if(name==='convenio'){
|
|
||||||
setAgendamento({...agendamento,insurance_provider:value})
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
setAgendamento({...agendamento,[name]:value})
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
setAgendamento(prev => ({ ...prev, paciente_cpf: cpfFormatted }));
|
||||||
|
fetchPatient();
|
||||||
|
} else if (name === 'convenio') {
|
||||||
|
setAgendamento(prev => ({ ...prev, insurance_provider: value }));
|
||||||
|
} else {
|
||||||
|
setAgendamento(prev => ({ ...prev, [name]: value }));
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ChamarMedicos = useCallback(async () => {
|
||||||
|
const Medicos = await GetAllDoctors(authHeader);
|
||||||
|
setTodosProfissionais(Medicos);
|
||||||
|
}, [authHeader]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const ChamarMedicos = async () => {
|
|
||||||
const Medicos = await GetAllDoctors(authHeader)
|
|
||||||
setTodosProfissionais(Medicos)
|
|
||||||
}
|
|
||||||
ChamarMedicos();
|
ChamarMedicos();
|
||||||
|
}, [ChamarMedicos]);
|
||||||
|
|
||||||
var myHeaders = new Headers();
|
useEffect(() => {
|
||||||
|
if (!agendamento.dataAtendimento || !agendamento.doctor_id) return;
|
||||||
|
|
||||||
|
const myHeaders = new Headers();
|
||||||
myHeaders.append("Content-Type", "application/json");
|
myHeaders.append("Content-Type", "application/json");
|
||||||
myHeaders.append("apikey", API_KEY)
|
myHeaders.append("apikey", API_KEY);
|
||||||
myHeaders.append("Authorization", `Bearer ${authHeader.split(' ')[1]}`);
|
myHeaders.append("Authorization", `Bearer ${authHeader.split(' ')[1]}`);
|
||||||
|
|
||||||
var raw = JSON.stringify({
|
const raw = JSON.stringify({
|
||||||
"doctor_id": agendamento.doctor_id,
|
doctor_id: agendamento.doctor_id,
|
||||||
"start_date": agendamento.dataAtendimento,
|
start_date: agendamento.dataAtendimento,
|
||||||
"end_date": `${agendamento.dataAtendimento}T23:59:59.999Z`,
|
end_date: `${agendamento.dataAtendimento}T23:59:59.999Z`,
|
||||||
|
});
|
||||||
|
|
||||||
});
|
const requestOptions = {
|
||||||
|
|
||||||
var requestOptions = {
|
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: myHeaders,
|
headers: myHeaders,
|
||||||
body: raw,
|
body: raw,
|
||||||
redirect: 'follow'
|
};
|
||||||
};
|
|
||||||
|
|
||||||
fetch("https://yuanqfswhberkoevtmfr.supabase.co/functions/v1/get-available-slots", requestOptions)
|
fetch("https://yuanqfswhberkoevtmfr.supabase.co/functions/v1/get-available-slots", requestOptions)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(result => {console.log(result); sethorariosDisponiveis(result)})
|
.then(result => sethorariosDisponiveis(result))
|
||||||
.catch(error => console.log('error', error));
|
.catch(error => console.log('error', error));
|
||||||
|
}, [agendamento.dataAtendimento, agendamento.doctor_id, authHeader]);
|
||||||
|
|
||||||
}, [agendamento.dataAtendimento, agendamento.doctor_id])
|
const handleSearchProfissional = (e) => {
|
||||||
|
|
||||||
|
|
||||||
// FUNÇÃO DE BUSCA E FILTRAGEM
|
|
||||||
const handleSearchProfissional = (e) => {
|
|
||||||
const term = e.target.value;
|
const term = e.target.value;
|
||||||
handleChange(e);
|
handleChange(e);
|
||||||
// 2. Lógica de filtragem:
|
|
||||||
if (term.trim() === '') {
|
if (term.trim() === '') {
|
||||||
setProfissionaisFiltrados([]);
|
setProfissionaisFiltrados([]);
|
||||||
setIsDropdownOpen(false);
|
setIsDropdownOpen(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Adapte o nome da propriedade (ex: 'nome', 'full_name')
|
|
||||||
const filtered = todosProfissionais.filter(p =>
|
const filtered = todosProfissionais.filter(p =>
|
||||||
p.full_name.toLowerCase().includes(term.toLowerCase())
|
p.full_name.toLowerCase().includes(term.toLowerCase())
|
||||||
);
|
);
|
||||||
|
|
||||||
setProfissionaisFiltrados(filtered);
|
setProfissionaisFiltrados(filtered);
|
||||||
setIsDropdownOpen(filtered.length > 0); // Abre se houver resultados
|
setIsDropdownOpen(filtered.length > 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSelectProfissional = (profissional) => {
|
||||||
// FUNÇÃO PARA SELECIONAR UM ITEM DO DROPDOWN
|
|
||||||
const handleSelectProfissional = async (profissional) => {
|
|
||||||
setAgendamento(prev => ({
|
setAgendamento(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
doctor_id: profissional.id,
|
doctor_id: profissional.id,
|
||||||
nome_medico: profissional.full_name
|
nome_medico: profissional.full_name
|
||||||
}));
|
}));
|
||||||
// 2. Fecha o dropdown
|
|
||||||
setProfissionaisFiltrados([]);
|
setProfissionaisFiltrados([]);
|
||||||
setIsDropdownOpen(false);
|
setIsDropdownOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const formatarHora = (datetimeString) => {
|
||||||
const formatarHora = (datetimeString) => {
|
return datetimeString?.substring(11, 16) || '';
|
||||||
return datetimeString.substring(11, 16);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const opcoesDeHorario = horariosDisponiveis?.slots?.map(item => ({
|
const opcoesDeHorario = horariosDisponiveis?.slots?.map(item => ({
|
||||||
|
|
||||||
value: formatarHora(item.datetime),
|
value: formatarHora(item.datetime),
|
||||||
label: formatarHora(item.datetime),
|
label: formatarHora(item.datetime),
|
||||||
disabled: !item.available
|
disabled: !item.available
|
||||||
}));
|
})) || [];
|
||||||
|
|
||||||
const calcularHorarioTermino = (inicio, sessoes, tempoBase) => {
|
const calcularHorarioTermino = useCallback((inicio, sessoes, tempoBase) => {
|
||||||
if (!inicio || inicio.length !== 5 || !inicio.includes(':')) return '';
|
if (!inicio || inicio.length !== 5 || !inicio.includes(':')) return '';
|
||||||
|
|
||||||
const [horas, minutos] = inicio.split(':').map(Number);
|
const [horas, minutos] = inicio.split(':').map(Number);
|
||||||
@ -180,40 +148,54 @@ const calcularHorarioTermino = (inicio, sessoes, tempoBase) => {
|
|||||||
const minutoTermino = minutosTermino % 60;
|
const minutoTermino = minutosTermino % 60;
|
||||||
|
|
||||||
const formatar = (num) => String(num).padStart(2, '0');
|
const formatar = (num) => String(num).padStart(2, '0');
|
||||||
|
|
||||||
return `${formatar(horaTermino)}:${formatar(minutoTermino)}`;
|
return `${formatar(horaTermino)}:${formatar(minutoTermino)}`;
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Recalcula o horário de término sempre que houver alteração nas variáveis dependentes
|
|
||||||
const novoTermino = calcularHorarioTermino(horarioInicio, sessoes, tempoBaseConsulta);
|
const novoTermino = calcularHorarioTermino(horarioInicio, sessoes, tempoBaseConsulta);
|
||||||
setHorarioTermino(novoTermino);
|
setHorarioTermino(novoTermino);
|
||||||
|
|
||||||
setAgendamento(prev => ({
|
setAgendamento(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
horarioTermino: novoTermino
|
horarioTermino: novoTermino
|
||||||
}));
|
}));
|
||||||
}, [horarioInicio, sessoes, tempoBaseConsulta, setAgendamento]);
|
}, [horarioInicio, sessoes, tempoBaseConsulta, setAgendamento, calcularHorarioTermino]);
|
||||||
|
|
||||||
const handleSubmit = (e) => {
|
const handleSubmit = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
alert("Agendamento salvo!");
|
setShowSuccessModal(true);
|
||||||
onSave({...agendamento, horarioInicio:horarioInicio})
|
};
|
||||||
|
|
||||||
|
const handleCloseModal = () => {
|
||||||
|
setShowSuccessModal(false);
|
||||||
|
onSave({ ...agendamento, horarioInicio: horarioInicio });
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="form-container">
|
<div className="form-container">
|
||||||
|
{showSuccessModal && (
|
||||||
|
<div className="modal-overlay">
|
||||||
|
<div className="modal-content">
|
||||||
|
<div className="modal-header">
|
||||||
|
<h5 className="modal-title">Sucesso</h5>
|
||||||
|
<button onClick={handleCloseModal} className="modal-close-btn">×</button>
|
||||||
|
</div>
|
||||||
|
<div className="modal-body">
|
||||||
|
<p className="modal-message">Agendamento salvo com sucesso!</p>
|
||||||
|
</div>
|
||||||
|
<div className="modal-footer">
|
||||||
|
<button onClick={handleCloseModal} className="modal-confirm-btn">Fechar</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<form className="form-agendamento" onSubmit={handleSubmit}>
|
<form className="form-agendamento" onSubmit={handleSubmit}>
|
||||||
<h2 className="section-title">Informações do paciente</h2>
|
<h2 className="section-title">Informações do paciente</h2>
|
||||||
|
|
||||||
<div className="campos-informacoes-paciente" id="informacoes-paciente-linha-um">
|
<div className="campos-informacoes-paciente" id="informacoes-paciente-linha-um">
|
||||||
|
|
||||||
<div className="campo-de-input">
|
<div className="campo-de-input">
|
||||||
<label>CPF do paciente</label>
|
<label>CPF do paciente</label>
|
||||||
<input type="text" name="paciente_cpf" placeholder="000.000.000-00" onChange={handleChange} value={agendamento.paciente_cpf}/>
|
<input type="text" name="paciente_cpf" placeholder="000.000.000-00" onChange={handleChange} value={agendamento.paciente_cpf}/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="campo-de-input">
|
<div className="campo-de-input">
|
||||||
@ -221,44 +203,40 @@ const handleSubmit = (e) => {
|
|||||||
<input type="text" name="paciente_nome" value={agendamento.paciente_nome} placeholder="Insira o nome do paciente" required onChange={handleChange} />
|
<input type="text" name="paciente_nome" value={agendamento.paciente_nome} placeholder="Insira o nome do paciente" required onChange={handleChange} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="campos-informacoes-paciente" id="informacoes-paciente-linha-tres">
|
|
||||||
|
|
||||||
<div >
|
<div className="campos-informacoes-paciente" id="informacoes-paciente-linha-tres">
|
||||||
|
<div>
|
||||||
<label>Convênio</label>
|
<label>Convênio</label>
|
||||||
<select name="convenio" onChange={handleChange}>
|
<select name="convenio" onChange={handleChange} value={agendamento.insurance_provider}>
|
||||||
<option value="publico">Público</option>
|
<option value="publico">Público</option>
|
||||||
<option value="unimed">Unimed</option>
|
<option value="unimed">Unimed</option>
|
||||||
<option value="bradesco_saude">Bradesco Saúde</option>
|
<option value="bradesco_saude">Bradesco Saúde</option>
|
||||||
<option value="hapvida">Hapvida</option>
|
<option value="hapvida">Hapvida</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 className="section-title">Informações do atendimento</h2>
|
<h2 className="section-title">Informações do atendimento</h2>
|
||||||
|
|
||||||
|
|
||||||
<div className="campo-informacoes-atendimento">
|
<div className="campo-informacoes-atendimento">
|
||||||
|
<div className="campo-de-input-container">
|
||||||
<div className="campo-de-input-container"> {/* NOVO CONTAINER PAI */}
|
|
||||||
<div className="campo-de-input">
|
<div className="campo-de-input">
|
||||||
<label>Nome do profissional *</label>
|
<label>Nome do profissional *</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="nome_medico" // Use o nome correto da propriedade no estado `agendamento`
|
name="nome_medico"
|
||||||
onChange={handleSearchProfissional}
|
onChange={handleSearchProfissional}
|
||||||
value={agendamento?.nome_medico}
|
value={agendamento?.nome_medico || ''}
|
||||||
autoComplete="off" // Ajuda a evitar o autocomplete nativo do navegador
|
autoComplete="off"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* DROPDOWN - RENDERIZAÇÃO CONDICIONAL */}
|
|
||||||
{isDropdownOpen && profissionaisFiltrados.length > 0 && (
|
{isDropdownOpen && profissionaisFiltrados.length > 0 && (
|
||||||
<div className='dropdown-profissionais'>
|
<div className='dropdown-profissionais'>
|
||||||
{profissionaisFiltrados.map((profissional) => (
|
{profissionaisFiltrados.map((profissional) => (
|
||||||
<div
|
<div
|
||||||
key={profissional.id} // Use o ID do profissional
|
key={profissional.id}
|
||||||
className='dropdown-item'
|
className='dropdown-item'
|
||||||
onClick={() => handleSelectProfissional(profissional)}
|
onClick={() => handleSelectProfissional(profissional)}
|
||||||
>
|
>
|
||||||
@ -267,31 +245,26 @@ const handleSubmit = (e) => {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="tipo_atendimento">
|
<div className="tipo_atendimento">
|
||||||
<label>Tipo de atendimento *</label>
|
<label>Tipo de atendimento *</label>
|
||||||
<select onChange={handleChange} name="tipo_atendimento" >
|
<select onChange={handleChange} name="tipo_atendimento" value={agendamento.tipo_atendimento}>
|
||||||
<option value="presencial" selected>Presencial</option>
|
<option value="presencial">Presencial</option>
|
||||||
<option value="teleconsulta">Teleconsulta</option>
|
<option value="teleconsulta">Teleconsulta</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section id="informacoes-atendimento-segunda-linha">
|
<section id="informacoes-atendimento-segunda-linha">
|
||||||
<section id="informacoes-atendimento-segunda-linha-esquerda">
|
<section id="informacoes-atendimento-segunda-linha-esquerda">
|
||||||
|
|
||||||
<div className="campo-informacoes-atendimento">
|
<div className="campo-informacoes-atendimento">
|
||||||
|
|
||||||
|
|
||||||
<div className="campo-de-input">
|
<div className="campo-de-input">
|
||||||
<label>Data *</label>
|
<label>Data *</label>
|
||||||
<input type="date" name="dataAtendimento" onChange={handleChange} required />
|
<input type="date" name="dataAtendimento" onChange={handleChange} value={agendamento.dataAtendimento} required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div className="linha">
|
<div className="linha">
|
||||||
{/* Dropdown de Início (Não modificado) */}
|
|
||||||
<div className="campo-de-input">
|
<div className="campo-de-input">
|
||||||
<label htmlFor="inicio">Início *</label>
|
<label htmlFor="inicio">Início *</label>
|
||||||
<select
|
<select
|
||||||
@ -302,7 +275,7 @@ const handleSubmit = (e) => {
|
|||||||
onChange={(e) => setHorarioInicio(e.target.value)}
|
onChange={(e) => setHorarioInicio(e.target.value)}
|
||||||
>
|
>
|
||||||
<option value="" disabled>Selecione a hora de início</option>
|
<option value="" disabled>Selecione a hora de início</option>
|
||||||
{opcoesDeHorario?.map((opcao, index) => (
|
{opcoesDeHorario.map((opcao, index) => (
|
||||||
<option
|
<option
|
||||||
key={index}
|
key={index}
|
||||||
value={opcao.value}
|
value={opcao.value}
|
||||||
@ -315,30 +288,29 @@ const handleSubmit = (e) => {
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* SELETOR DE SESSÕES MODIFICADO */}
|
|
||||||
{/* Removemos o 'label' para evitar o desalinhamento e colocamos o texto acima */}
|
|
||||||
<div className='seletor-wrapper'>
|
<div className='seletor-wrapper'>
|
||||||
<label>Número de Sessões *</label> {/* Novo label para o seletor */}
|
<label>Número de Sessões *</label>
|
||||||
<div className='sessao-contador'>
|
<div className='sessao-contador'>
|
||||||
<button
|
<button
|
||||||
type="button" /* Adicionado para evitar submissão de formulário */
|
type="button"
|
||||||
onClick={() => {if(sessoes === 0)return; else(setSessoes(sessoes - 1))}}
|
onClick={() => setSessoes(prev => Math.max(0, prev - 1))}
|
||||||
disabled={sessoes === 0} /* Desabilita o botão no limite */
|
disabled={sessoes === 0}
|
||||||
>
|
>
|
||||||
<i className="bi bi-chevron-compact-left"></i>
|
<i className="bi bi-chevron-compact-left"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<p className='sessao-valor'>{sessoes}</p> {/* Adicionada classe para estilização */}
|
<p className='sessao-valor'>{sessoes}</p>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button" /* Adicionado para evitar submissão de formulário */
|
type="button"
|
||||||
onClick={() => {if(sessoes === 3 )return; else(setSessoes(sessoes + 1))}}
|
onClick={() => setSessoes(prev => Math.min(3, prev + 1))}
|
||||||
disabled={sessoes === 3} /* Desabilita o botão no limite */
|
disabled={sessoes === 3}
|
||||||
>
|
>
|
||||||
<i className="bi bi-chevron-compact-right"></i>
|
<i className="bi bi-chevron-compact-right"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="campo-de-input">
|
<div className="campo-de-input">
|
||||||
<label htmlFor="termino">Término *</label>
|
<label htmlFor="termino">Término *</label>
|
||||||
<input
|
<input
|
||||||
@ -349,37 +321,37 @@ const handleSubmit = (e) => {
|
|||||||
readOnly
|
readOnly
|
||||||
className="horario-termino-readonly"
|
className="horario-termino-readonly"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="informacoes-atendimento-segunda-linha-direita">
|
<section className="informacoes-atendimento-segunda-linha-direita">
|
||||||
|
|
||||||
|
|
||||||
<div className="campo-de-input">
|
<div className="campo-de-input">
|
||||||
<label>Observações</label>
|
<label>Observações</label>
|
||||||
<textarea name="observacoes" rows="4" cols="1"></textarea>
|
<textarea name="observacoes" rows="4" cols="1" onChange={handleChange} value={agendamento.observacoes || ''}></textarea>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<div className="campo-de-input-check">
|
||||||
|
<input
|
||||||
|
className="form-check-input form-custom-check"
|
||||||
|
type="checkbox"
|
||||||
|
name="status"
|
||||||
|
onChange={handleChange}
|
||||||
|
checked={agendamento.status === 'requested'}
|
||||||
|
/>
|
||||||
|
<label className="form-check-label checkbox-label" htmlFor="status">
|
||||||
|
Adicionar a fila de espera
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="form-actions">
|
<div className="form-actions">
|
||||||
<button type="submit" className="btn-primary">Salvar agendamento</button>
|
<button type="submit" className="btn-primary">Salvar agendamento</button>
|
||||||
<button type="button" className="btn-cancel" onClick={onCancel}>Cancelar</button>
|
<button type="button" className="btn-cancel" onClick={onCancel}>Cancelar</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div className="campo-de-input-check">
|
|
||||||
<input className="form-check-input form-custom-check" type="checkbox" name="status" onChange={handleChange} />
|
|
||||||
<label className="form-check-label checkbox-label" htmlFor="status">
|
|
||||||
Adicionar a fila de espera
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import CardConsulta from './CardConsulta';
|
import CardConsulta from './CardConsulta';
|
||||||
import "./style/styleTabelas/tabeladia.css";
|
import "./style/styleTabelas/tabeladia.css";
|
||||||
|
|
||||||
const TabelaAgendamentoDia = ({ handleClickAgendamento, agendamentos, setShowDeleteModal, setDictInfo, setSelectedId }) => {
|
const TabelaAgendamentoDia = ({ handleClickAgendamento, agendamentos, setShowDeleteModal, setDictInfo,selectedID, setSelectedId, setShowConfirmModal, coresConsultas, setListaConsultaID, listaConsultasID }) => {
|
||||||
const [indiceAcesso, setIndiceAcesso] = useState(0)
|
const [indiceAcesso, setIndiceAcesso] = useState(0)
|
||||||
const [Dia, setDia] = useState()
|
const [Dia, setDia] = useState()
|
||||||
const agendamentosDoDia = agendamentos?.semana1?.segunda || [];
|
const agendamentosDoDia = agendamentos?.semana1?.segunda || [];
|
||||||
@ -10,7 +10,6 @@ const TabelaAgendamentoDia = ({ handleClickAgendamento, agendamentos, setShowDel
|
|||||||
|
|
||||||
let ListaDiasComAgendamentos = Object.keys(agendamentos)
|
let ListaDiasComAgendamentos = Object.keys(agendamentos)
|
||||||
|
|
||||||
console.log(agendamentos)
|
|
||||||
|
|
||||||
|
|
||||||
//console.log(Dia, "hshdhshhsdhs")
|
//console.log(Dia, "hshdhshhsdhs")
|
||||||
@ -19,6 +18,28 @@ const TabelaAgendamentoDia = ({ handleClickAgendamento, agendamentos, setShowDel
|
|||||||
setDia(ListaDiasComAgendamentos[indiceAcesso])
|
setDia(ListaDiasComAgendamentos[indiceAcesso])
|
||||||
}, [indiceAcesso])
|
}, [indiceAcesso])
|
||||||
|
|
||||||
|
const formatarDataComDia = (dataISO) => {
|
||||||
|
if (!dataISO) return '';
|
||||||
|
|
||||||
|
const data = new Date(dataISO); // converte para objeto Date
|
||||||
|
|
||||||
|
// nomes dos dias da semana
|
||||||
|
const dias = [
|
||||||
|
'Segunda-feira',
|
||||||
|
'Terça-feira',
|
||||||
|
'Quarta-feira',
|
||||||
|
'Quinta-feira',
|
||||||
|
'Sexta-feira',
|
||||||
|
'Sábado'
|
||||||
|
];
|
||||||
|
const diaSemana = dias[data.getDay()]; // 0 = Domingo, 1 = Segunda, etc.
|
||||||
|
|
||||||
|
const dia = dataISO.split('-')[2];
|
||||||
|
const mes = dataISO.split('-')[1];
|
||||||
|
const ano = dataISO.split('-')[0];
|
||||||
|
|
||||||
|
return `${diaSemana}, ${dia}/${mes}/${ano}`;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -27,7 +48,7 @@ const TabelaAgendamentoDia = ({ handleClickAgendamento, agendamentos, setShowDel
|
|||||||
<button onClick={() => {if(indiceAcesso === 0)return; else(setIndiceAcesso(indiceAcesso - 1))}}> <i className="bi bi-chevron-compact-left"></i></button>
|
<button onClick={() => {if(indiceAcesso === 0)return; else(setIndiceAcesso(indiceAcesso - 1))}}> <i className="bi bi-chevron-compact-left"></i></button>
|
||||||
|
|
||||||
|
|
||||||
<p>{Dia ? `${Dia?.split('-')[2]}/${Dia?.split('-')[1]}/${Dia?.split('-')[0]}`: ''}</p>
|
<p>{Dia ? formatarDataComDia(Dia) : ''}</p>
|
||||||
|
|
||||||
<button onClick={() => {if(ListaDiasComAgendamentos.length - 1 === indiceAcesso)return; else(setIndiceAcesso(indiceAcesso + 1))}}> <i className="bi bi-chevron-compact-right"></i></button>
|
<button onClick={() => {if(ListaDiasComAgendamentos.length - 1 === indiceAcesso)return; else(setIndiceAcesso(indiceAcesso + 1))}}> <i className="bi bi-chevron-compact-right"></i></button>
|
||||||
</div>
|
</div>
|
||||||
@ -51,7 +72,7 @@ const TabelaAgendamentoDia = ({ handleClickAgendamento, agendamentos, setShowDel
|
|||||||
<td className='coluna-horario'><p className='horario-texto'>{`${horario[0]}:${horario[1]}`}</p></td>
|
<td className='coluna-horario'><p className='horario-texto'>{`${horario[0]}:${horario[1]}`}</p></td>
|
||||||
<td className='mostrar-horario'>
|
<td className='mostrar-horario'>
|
||||||
<div onClick={() => handleClickAgendamento(agendamento)}>
|
<div onClick={() => handleClickAgendamento(agendamento)}>
|
||||||
<CardConsulta DadosConsulta={agendamento} TabelaAgendamento={'dia'} setShowDeleteModal={setShowDeleteModal} setDictInfo={setDictInfo} setSelectedId={setSelectedId}/>
|
<CardConsulta DadosConsulta={agendamento} TabelaAgendamento={'dia'} setShowDeleteModal={setShowDeleteModal} setDictInfo={setDictInfo} setSelectedId={setSelectedId} selectedID={selectedID} setShowConfirmModal={setShowConfirmModal} coresConsultas={coresConsultas} setListaConsultaID={setListaConsultaID} listaConsultasID={listaConsultasID}/>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import "./style/styleTabelas/tabelames.css";
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
const TabelaAgendamentoMes = ({ ListarDiasdoMes, agendamentos, setShowDeleteModal, setSelectedId ,setDictInfo }) => {
|
const TabelaAgendamentoMes = ({ ListarDiasdoMes, agendamentos, setShowDeleteModal, setSelectedId ,setDictInfo, setShowConfirmModal, coresConsultas ,setListaConsultaID, listaConsultasID }) => {
|
||||||
|
|
||||||
const dataHoje = dayjs();
|
const dataHoje = dayjs();
|
||||||
const AnoAtual = dataHoje.year();
|
const AnoAtual = dataHoje.year();
|
||||||
@ -56,19 +56,19 @@ const TabelaAgendamentoMes = ({ ListarDiasdoMes, agendamentos, setShowDeleteModa
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (diaSemana) {
|
switch (diaSemana) {
|
||||||
case 'Monday':
|
case 'segunda-feira':
|
||||||
semanas[semanaKey].segunda.push(...agendamentos[DiaComAtendimento])
|
semanas[semanaKey].segunda.push(...agendamentos[DiaComAtendimento])
|
||||||
break
|
break
|
||||||
case 'Tuesday':
|
case 'terça-feira':
|
||||||
semanas[semanaKey].terça.push(...agendamentos[DiaComAtendimento])
|
semanas[semanaKey].terça.push(...agendamentos[DiaComAtendimento])
|
||||||
break
|
break
|
||||||
case 'Wednesday':
|
case 'quarta-feira':
|
||||||
semanas[semanaKey].quarta.push(...agendamentos[DiaComAtendimento])
|
semanas[semanaKey].quarta.push(...agendamentos[DiaComAtendimento])
|
||||||
break
|
break
|
||||||
case 'Thursday':
|
case 'quinta-feira':
|
||||||
semanas[semanaKey].quinta.push(...agendamentos[DiaComAtendimento])
|
semanas[semanaKey].quinta.push(...agendamentos[DiaComAtendimento])
|
||||||
break
|
break
|
||||||
case 'Friday':
|
case 'sexta-feira':
|
||||||
semanas[semanaKey].sexta.push(...agendamentos[DiaComAtendimento])
|
semanas[semanaKey].sexta.push(...agendamentos[DiaComAtendimento])
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
@ -202,9 +202,9 @@ const TabelaAgendamentoMes = ({ ListarDiasdoMes, agendamentos, setShowDeleteModa
|
|||||||
{
|
{
|
||||||
semana && typeof semana === "object" && Object.keys(semana).map((dia) => (
|
semana && typeof semana === "object" && Object.keys(semana).map((dia) => (
|
||||||
<td key={dia} >
|
<td key={dia} >
|
||||||
<CardConsulta DadosConsulta={((semana[dia]|| [])[0]) || {status:'vazio'}} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo}/>
|
<CardConsulta TabelaAgendamento={'mes'} DadosConsulta={((semana[dia]|| [])[0]) || {status:'vazio'}} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo} setShowConfirmModal={setShowConfirmModal} coresConsultas={coresConsultas} setListaConsultaID={setListaConsultaID} listaConsultasID={listaConsultasID}/>
|
||||||
<CardConsulta DadosConsulta={((semana[dia]|| [])[1]) || {status:'vazio'}} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo}/>
|
<CardConsulta TabelaAgendamento={'mes'} DadosConsulta={((semana[dia]|| [])[1]) || {status:'vazio'}} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo} setShowConfirmModal={setShowConfirmModal} coresConsultas={coresConsultas} setListaConsultaID={setListaConsultaID} listaConsultasID={listaConsultasID}/>
|
||||||
<CardConsulta DadosConsulta={((semana[dia]|| [])[2]) || {status:'vazio'}} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo}/>
|
<CardConsulta TabelaAgendamento={'mes'} DadosConsulta={((semana[dia]|| [])[2]) || {status:'vazio'}} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo} setShowConfirmModal={setShowConfirmModal} coresConsultas={coresConsultas} setListaConsultaID={setListaConsultaID} listaConsultasID={listaConsultasID}/>
|
||||||
{semana[dia].length > 3 ? (
|
{semana[dia].length > 3 ? (
|
||||||
<div>
|
<div>
|
||||||
<p>{` +${semana[dia].length - 2}`}</p>
|
<p>{` +${semana[dia].length - 2}`}</p>
|
||||||
|
|||||||
@ -6,18 +6,20 @@ import { useEffect, useState, useMemo } from 'react';
|
|||||||
import weekOfYear from 'dayjs/plugin/weekOfYear'
|
import weekOfYear from 'dayjs/plugin/weekOfYear'
|
||||||
dayjs.extend(weekOfYear)
|
dayjs.extend(weekOfYear)
|
||||||
|
|
||||||
const TabelaAgendamentoSemana = ({ agendamentos, ListarDiasdoMes, setShowDeleteModal ,setSelectedId ,setDictInfo}) => {
|
const TabelaAgendamentoSemana = ({ agendamentos, ListarDiasdoMes, setShowDeleteModal ,setSelectedId ,setDictInfo, setShowConfirmModal, coresConsultas ,setListaConsultaID, listaConsultasID}) => {
|
||||||
|
|
||||||
// Armazena o objeto COMPLETO das semanas organizadas
|
// Armazena o objeto COMPLETO das semanas organizadas
|
||||||
const [semanasOrganizadas, setSemanasOrganizadas] = useState({});
|
const [semanasOrganizadas, setSemanasOrganizadas] = useState({});
|
||||||
// Controla qual semana está sendo exibida (o índice da chave no objeto)
|
// Controla qual semana está sendo exibida (o índice da chave no objeto)
|
||||||
const [Indice, setIndice] = useState(0);
|
const [Indice, setIndice] = useState(0);
|
||||||
|
|
||||||
|
console.log(agendamentos, "agendamentos diarios")
|
||||||
|
|
||||||
const dataHoje = dayjs();
|
const dataHoje = dayjs();
|
||||||
const AnoAtual = dataHoje.year();
|
const AnoAtual = dataHoje.year();
|
||||||
const mes = dataHoje.month() + 1;
|
const mes = dataHoje.month() + 1;
|
||||||
|
|
||||||
let DiasdoMes = ListarDiasdoMes(AnoAtual, mes)
|
|
||||||
|
|
||||||
// Array de chaves (ex: ['semana40', 'semana41', ...])
|
// Array de chaves (ex: ['semana40', 'semana41', ...])
|
||||||
const chavesDasSemanas = Object.keys(semanasOrganizadas);
|
const chavesDasSemanas = Object.keys(semanasOrganizadas);
|
||||||
@ -46,30 +48,34 @@ const TabelaAgendamentoSemana = ({ agendamentos, ListarDiasdoMes, setShowDeleteM
|
|||||||
segunda: [], terça: [], quarta: [], quinta: [], sexta: []
|
segunda: [], terça: [], quarta: [], quinta: [], sexta: []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
console.log(diaSemana)
|
||||||
|
|
||||||
switch (diaSemana) {
|
switch (diaSemana) {
|
||||||
case 'Monday':
|
|
||||||
|
|
||||||
|
case 'segunda-feira':
|
||||||
|
console.log("segunda")
|
||||||
semanas[semanaKey].segunda.push(...agendamentos[DiaComAtendimento])
|
semanas[semanaKey].segunda.push(...agendamentos[DiaComAtendimento])
|
||||||
break
|
break
|
||||||
case 'Tuesday':
|
case 'terça-feira':
|
||||||
semanas[semanaKey].terça.push(...agendamentos[DiaComAtendimento])
|
semanas[semanaKey].terça.push(...agendamentos[DiaComAtendimento])
|
||||||
break
|
break
|
||||||
case 'Wednesday':
|
case 'quarta-feira':
|
||||||
semanas[semanaKey].quarta.push(...agendamentos[DiaComAtendimento])
|
semanas[semanaKey].quarta.push(...agendamentos[DiaComAtendimento])
|
||||||
break
|
break
|
||||||
case 'Thursday':
|
case 'quinta-feira':
|
||||||
semanas[semanaKey].quinta.push(...agendamentos[DiaComAtendimento])
|
semanas[semanaKey].quinta.push(...agendamentos[DiaComAtendimento])
|
||||||
break
|
break
|
||||||
case 'Friday':
|
case 'sexta-feira':
|
||||||
semanas[semanaKey].sexta.push(...agendamentos[DiaComAtendimento])
|
semanas[semanaKey].sexta.push(...agendamentos[DiaComAtendimento])
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
console.log(semanas, "agendamentos semanais")
|
||||||
return semanas
|
return semanas
|
||||||
}, [agendamentos, AnoAtual]) // Adicionei AnoAtual como dependência por segurança
|
}, [agendamentos, AnoAtual])
|
||||||
|
|
||||||
// --- EFEITO PARA POPULAR O ESTADO ---
|
// --- EFEITO PARA POPULAR O ESTADO ---
|
||||||
|
|
||||||
@ -159,49 +165,50 @@ const TabelaAgendamentoSemana = ({ agendamentos, ListarDiasdoMes, setShowDeleteM
|
|||||||
<tbody>
|
<tbody>
|
||||||
{indicesDeLinha.map((indiceLinha) => {
|
{indicesDeLinha.map((indiceLinha) => {
|
||||||
|
|
||||||
let schedulet_at = semanaParaRenderizar.segunda[indiceLinha].scheduled_at.split("T")
|
//let schedulet_at = semanaParaRenderizar.segunda[indiceLinha].scheduled_at.split("T")
|
||||||
|
|
||||||
let horario = schedulet_at[1].split(":")
|
// let horario = schedulet_at[1].split(":")
|
||||||
|
|
||||||
console.log(horario)
|
|
||||||
|
console.log(semanaParaRenderizar, "aqui")
|
||||||
|
|
||||||
return(
|
return(
|
||||||
<tr key={indiceLinha}>
|
<tr key={indiceLinha}>
|
||||||
{/* Célula para Horário (Pode ser ajustado para mostrar o horário real) */}
|
|
||||||
<td>
|
<td>
|
||||||
|
|
||||||
<p className='horario-texto'> {`${horario[0]}:${horario[1]}`} </p>
|
{/* <p className='horario-texto'> {`${horario[0]}:${horario[1]}`} </p>*/}
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
{/* Mapeamento de COLUNAS (dias) */}
|
{/* Mapeamento de COLUNAS (dias) */}
|
||||||
<td>
|
<td>
|
||||||
{semanaParaRenderizar.segunda[indiceLinha]
|
{semanaParaRenderizar?.segunda[indiceLinha]
|
||||||
? <CardConsulta DadosConsulta={semanaParaRenderizar.segunda[indiceLinha]} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo} />
|
? <CardConsulta TabelaAgendamento={'semana'} DadosConsulta={semanaParaRenderizar?.segunda[indiceLinha]} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo} setShowConfirmModal={setShowConfirmModal} coresConsultas={coresConsultas} setListaConsultaID={setListaConsultaID} listaConsultasID={listaConsultasID}/>
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{semanaParaRenderizar.terça[indiceLinha]
|
{semanaParaRenderizar.terça[indiceLinha]
|
||||||
? <CardConsulta DadosConsulta={semanaParaRenderizar.terça[indiceLinha]} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo}/>
|
? <CardConsulta TabelaAgendamento={'semana'} DadosConsulta={semanaParaRenderizar.terça[indiceLinha]} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo} setShowConfirmModal={setShowConfirmModal} coresConsultas={coresConsultas} setListaConsultaID={setListaConsultaID} listaConsultasID={listaConsultasID}/>
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{semanaParaRenderizar.quarta[indiceLinha]
|
{semanaParaRenderizar.quarta[indiceLinha]
|
||||||
? <CardConsulta DadosConsulta={semanaParaRenderizar.quarta[indiceLinha]} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo}/>
|
? <CardConsulta TabelaAgendamento={'semana'} DadosConsulta={semanaParaRenderizar.quarta[indiceLinha]} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo} setShowConfirmModal={setShowConfirmModal} coresConsultas={coresConsultas} setListaConsultaID={setListaConsultaID} listaConsultasID={listaConsultasID}/>
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{semanaParaRenderizar.quinta[indiceLinha]
|
{semanaParaRenderizar.quinta[indiceLinha]
|
||||||
? <CardConsulta DadosConsulta={semanaParaRenderizar.quinta[indiceLinha]} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo} />
|
? <CardConsulta TabelaAgendamento={'semana'} DadosConsulta={semanaParaRenderizar.quinta[indiceLinha]} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo} setShowConfirmModal={setShowConfirmModal} coresConsultas={coresConsultas} setListaConsultaID={setListaConsultaID} listaConsultasID={listaConsultasID}/>
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{semanaParaRenderizar.sexta[indiceLinha]
|
{semanaParaRenderizar.sexta[indiceLinha]
|
||||||
? <CardConsulta DadosConsulta={semanaParaRenderizar.sexta[indiceLinha]} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo} />
|
? <CardConsulta TabelaAgendamento={'semana'} DadosConsulta={semanaParaRenderizar.sexta[indiceLinha]} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo} setShowConfirmModal={setShowConfirmModal} coresConsultas={coresConsultas} setListaConsultaID={setListaConsultaID} listaConsultasID={listaConsultasID} />
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@ -50,7 +50,7 @@
|
|||||||
/* 6. Estilo de hover para o botão de exclusão */
|
/* 6. Estilo de hover para o botão de exclusão */
|
||||||
.btn-delete-custom-style:hover {
|
.btn-delete-custom-style:hover {
|
||||||
background-color: #c82333; /* Um vermelho um pouco mais escuro para o hover */
|
background-color: #c82333; /* Um vermelho um pouco mais escuro para o hover */
|
||||||
filter: brightness(90%); /* Alternativa: escurecer um pouco mais */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 7. Estilos para os ícones dentro dos botões (já está no JSX com fs-4) */
|
/* 7. Estilos para os ícones dentro dos botões (já está no JSX com fs-4) */
|
||||||
@ -59,3 +59,14 @@
|
|||||||
/* Exemplo: se precisar de um ajuste fino além do fs-4 */
|
/* Exemplo: se precisar de um ajuste fino além do fs-4 */
|
||||||
/* font-size: 1.5rem; */
|
/* font-size: 1.5rem; */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-confirm-style{
|
||||||
|
background-color: #5ce687;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-verde{
|
||||||
|
background-color: #b7ffbd;
|
||||||
|
border: #91d392;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@ -43,8 +43,6 @@ svg{
|
|||||||
font-family: 'Material Symbols Outlined';
|
font-family: 'Material Symbols Outlined';
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
color:black
|
color:black
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-container {
|
.form-container {
|
||||||
@ -152,7 +150,6 @@ svg{
|
|||||||
background: #e5e7eb;
|
background: #e5e7eb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.cardconsulta-infosecundaria{
|
.cardconsulta-infosecundaria{
|
||||||
font-size: small;
|
font-size: small;
|
||||||
}
|
}
|
||||||
@ -166,10 +163,8 @@ svg{
|
|||||||
.campo-de-input{
|
.campo-de-input{
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#informacoes-atendimento-segunda-linha{
|
#informacoes-atendimento-segunda-linha{
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -185,13 +180,13 @@ textarea{
|
|||||||
.campos-informacoes-paciente,
|
.campos-informacoes-paciente,
|
||||||
.campo-informacoes-atendimento {
|
.campo-informacoes-atendimento {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 16px; /* espaço entre campos */
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.campo-de-input {
|
.campo-de-input {
|
||||||
flex: 1; /* todos os filhos ocupam mesmo espaço */
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column; /* mantém label em cima do input */
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
#informacoes-atendimento-segunda-linha-esquerda select[name="unidade"]{
|
#informacoes-atendimento-segunda-linha-esquerda select[name="unidade"]{
|
||||||
@ -213,7 +208,7 @@ select[name=solicitante]{
|
|||||||
.form-container {
|
.form-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: none;
|
max-width: none;
|
||||||
margin: 0; /* >>> sem espaço para encostar no topo <<< */
|
margin: 0;
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
@ -306,29 +301,24 @@ html[data-bs-theme="dark"] svg {
|
|||||||
color: #e0e0e0 !important;
|
color: #e0e0e0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* CONTAINER PAI - ESSENCIAL PARA POSICIONAMENTO */
|
|
||||||
.campo-de-input-container {
|
.campo-de-input-container {
|
||||||
position: relative; /* Define o contexto para o dropdown */
|
position: relative;
|
||||||
/* ... outros estilos de layout (display, margin, etc.) ... */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ESTILO DA LISTA DROPDOWN */
|
|
||||||
.dropdown-profissionais {
|
|
||||||
position: absolute; /* Flutua em relação ao pai (.campo-de-input-container) */
|
|
||||||
top: 100%; /* Começa logo abaixo do input */
|
|
||||||
left: 0;
|
|
||||||
width: 100%; /* Ocupa toda a largura do container pai */
|
|
||||||
|
|
||||||
/* Estilos visuais */
|
.dropdown-profissionais {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||||
z-index: 100; /* Alto z-index para garantir que fique acima de outros elementos */
|
z-index: 100;
|
||||||
max-height: 200px;
|
max-height: 200px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ESTILO DE CADA ITEM DO DROPDOWN */
|
|
||||||
.dropdown-item {
|
.dropdown-item {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -340,135 +330,190 @@ html[data-bs-theme="dark"] svg {
|
|||||||
|
|
||||||
.tipo_atendimento{
|
.tipo_atendimento{
|
||||||
margin-left: 3rem;
|
margin-left: 3rem;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* 1. Estilização Básica e Tamanho (Estado Padrão - Antes de Clicar) */
|
|
||||||
.checkbox-customs {
|
.checkbox-customs {
|
||||||
/* Remove a aparência padrão do navegador/Bootstrap */
|
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
-moz-appearance: none;
|
-moz-appearance: none;
|
||||||
appearance: none;
|
appearance: none;
|
||||||
|
width: 1.2rem;
|
||||||
/* Define o tamanho desejado */
|
|
||||||
width: 1.2rem; /* Ajuste conforme o seu gosto (ex: 1.2rem = 19.2px) */
|
|
||||||
height: 1.2rem;
|
height: 1.2rem;
|
||||||
|
background-color: #fff;
|
||||||
/* Define o visual "branco com borda preta" */
|
border: 1px solid #000;
|
||||||
background-color: #fff; /* Fundo branco */
|
border-radius: 0.25rem;
|
||||||
border: 1px solid #000; /* Borda preta de 1px */
|
|
||||||
border-radius: 0.25rem; /* Borda levemente arredondada (opcional, imita Bootstrap) */
|
|
||||||
|
|
||||||
/* Centraliza o 'check' (quando aparecer) */
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
cursor: pointer; /* Indica que é clicável */
|
cursor: pointer;
|
||||||
|
transition: all 0.5s ease;
|
||||||
/* Adiciona a transição suave */
|
|
||||||
transition: all 0.5s ease; /* Transição em 0.5 segundos para todas as propriedades */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 2. Estilização no Estado Clicado (:checked) */
|
|
||||||
.checkbox-customs:checked {
|
.checkbox-customs:checked {
|
||||||
/* Quando clicado, mantém o fundo branco (se quiser mudar, altere aqui) */
|
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
|
|
||||||
/* Se você quiser que a borda mude de cor ao clicar, altere aqui. */
|
|
||||||
/* border-color: #007bff; */ /* Exemplo: borda azul */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 3. Ocultar o 'Check' Padrão e Criar um Check Customizado */
|
|
||||||
/* O Bootstrap/Navegador insere um ícone de 'check'. Vamos controlá-lo com background-image. */
|
|
||||||
.checkbox-customs:checked {
|
|
||||||
/* Este código do Bootstrap usa um SVG para o ícone de 'check' */
|
|
||||||
/* Aqui, estamos forçando o ícone de 'check' a ser preto para combinar com a borda preta. */
|
|
||||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e");
|
|
||||||
|
|
||||||
/* Garante que o ícone fique centralizado e preencha o espaço */
|
.checkbox-customs:checked {
|
||||||
|
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e");
|
||||||
background-size: 100% 100%;
|
background-size: 100% 100%;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
}
|
}
|
||||||
/* Container dos três elementos na linha */
|
|
||||||
|
|
||||||
.linha {
|
.linha {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-end; /* Garante que os campos de input e o seletor fiquem alinhados pela base */
|
align-items: flex-end;
|
||||||
gap: 20px; /* Espaçamento entre os campos */
|
gap: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------- */
|
|
||||||
/* ESTILIZAÇÃO DO SELETOR DE SESSÕES */
|
|
||||||
/* ------------------------------------------- */
|
|
||||||
|
|
||||||
.seletor-wrapper {
|
.seletor-wrapper {
|
||||||
/* Garante que o label e o contador fiquem alinhados verticalmente com os selects */
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sessao-contador {
|
.sessao-contador {
|
||||||
/* Estilo de "campo de input" */
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
background-color: #e9ecef;
|
||||||
/* Cores e Bordas */
|
border: 1px solid #ced4da;
|
||||||
background-color: #e9ecef; /* Cor cinza claro dos inputs do Bootstrap */
|
border-radius: 0.25rem;
|
||||||
border: 1px solid #ced4da; /* Borda sutil */
|
height: 40px;
|
||||||
border-radius: 0.25rem; /* Bordas arredondadas (Padrão Bootstrap) */
|
width: 100px;
|
||||||
|
padding: 0 5px;
|
||||||
/* Garante a mesma altura dos selects */
|
|
||||||
height: 40px; /* Ajuste este valor para corresponder à altura exata do seu select */
|
|
||||||
width: 100px; /* Largura ajustável */
|
|
||||||
padding: 0 5px; /* Padding interno */
|
|
||||||
|
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sessao-valor {
|
.sessao-valor {
|
||||||
/* Estilo do número de sessões */
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0 5px;
|
padding: 0 5px;
|
||||||
font-size: 1.1rem; /* Um pouco maior que o texto dos selects */
|
font-size: 1.1rem;
|
||||||
color: #007bff; /* Cor azul destacada (como na sua imagem) */
|
color: #007bff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sessao-contador button {
|
.sessao-contador button {
|
||||||
/* Estilo dos botões de chevron */
|
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 0 2px;
|
padding: 0 2px;
|
||||||
color: #495057; /* Cor do ícone */
|
color: #495057;
|
||||||
font-size: 1.5rem; /* Aumenta o tamanho dos ícones do chevron */
|
font-size: 1.5rem;
|
||||||
line-height: 1; /* Alinha o ícone verticalmente */
|
line-height: 1;
|
||||||
transition: color 0.2s;
|
transition: color 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sessao-contador button:hover:not(:disabled) {
|
.sessao-contador button:hover:not(:disabled) {
|
||||||
color: #007bff; /* Cor azul ao passar o mouse */
|
color: #007bff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sessao-contador button:disabled {
|
.sessao-contador button:disabled {
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
color: #adb5bd; /* Cor mais clara quando desabilitado */
|
color: #adb5bd;
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------------------------------- */
|
|
||||||
/* GARANTINDO COERÊNCIA NOS SELECTS */
|
|
||||||
/* ------------------------------------------- */
|
|
||||||
|
|
||||||
.campo-de-input select {
|
|
||||||
/* Se seus selects estiverem com estilos diferentes, este bloco garante que eles se pareçam */
|
|
||||||
/* com o seletor de sessões (se já usarem classes do Bootstrap, podem não precisar disso) */
|
|
||||||
background-color: #e9ecef; /* Fundo cinza claro */
|
|
||||||
border: 1px solid #ced4da; /* Borda sutil */
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
height: 40px; /* Garante a mesma altura do sessao-contador */
|
|
||||||
/* Adicione mais estilos do seu input/select se necessário (ex: font-size, padding) */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
background: white;
|
||||||
|
padding: 2rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
|
||||||
|
max-width: 400px;
|
||||||
|
width: 90%;
|
||||||
|
text-align: center;
|
||||||
|
animation: modalAppear 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes modalAppear {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.9) translateY(-20px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1) translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header h3 {
|
||||||
|
margin: 0 0 1rem 0;
|
||||||
|
color: #28a745;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body p {
|
||||||
|
margin: 0;
|
||||||
|
color: #333;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer .btn-primary {
|
||||||
|
background: #28a745;
|
||||||
|
padding: 10px 24px;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer .btn-primary:hover {
|
||||||
|
background: #218838;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
html[data-bs-theme="dark"] .modal-content {
|
||||||
|
background: #232323 !important;
|
||||||
|
color: #e0e0e0 !important;
|
||||||
|
border: 1px solid #404053 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-bs-theme="dark"] .modal-header h3 {
|
||||||
|
color: #4ade80 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-bs-theme="dark"] .modal-body p {
|
||||||
|
color: #e0e0e0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-bs-theme="dark"] .modal-footer .btn-primary {
|
||||||
|
background: #16a34a !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-bs-theme="dark"] .modal-footer .btn-primary:hover {
|
||||||
|
background: #15803d !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.horario-termino-readonly {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
color: #6c757d;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-bs-theme="dark"] .horario-termino-readonly {
|
||||||
|
background-color: #2d3748 !important;
|
||||||
|
color: #a0aec0 !important;
|
||||||
|
}
|
||||||
@ -6,6 +6,8 @@
|
|||||||
overflow: hidden; /* mantém o arredondado */
|
overflow: hidden; /* mantém o arredondado */
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
border: 4px solid #4a90e2; /* borda azul, altere para a cor desejada */
|
border: 4px solid #4a90e2; /* borda azul, altere para a cor desejada */
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
/* 1. Estilização do TD (Container) */
|
/* 1. Estilização do TD (Container) */
|
||||||
.coluna-horario {
|
.coluna-horario {
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
overflow: hidden; /* mantém o arredondado */
|
overflow: hidden; /* mantém o arredondado */
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
border: 4px solid #4a90e2; /* borda azul, altere para a cor desejada */
|
border: 4px solid #4a90e2; /* borda azul, altere para a cor desejada */
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Células da tabela */
|
/* Células da tabela */
|
||||||
@ -67,10 +68,10 @@
|
|||||||
.tabelasemanal tr:hover {
|
.tabelasemanal tr:hover {
|
||||||
background-color: #f1f1f1 !important;
|
background-color: #f1f1f1 !important;
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
tr{
|
tr{
|
||||||
width: 1000px;
|
width: 1000px;
|
||||||
}
|
}*/
|
||||||
|
|
||||||
html[data-bs-theme="dark"] .tabelasemanal {
|
html[data-bs-theme="dark"] .tabelasemanal {
|
||||||
border: 4px solid #333;
|
border: 4px solid #333;
|
||||||
@ -111,3 +112,5 @@ html[data-bs-theme="dark"] .tabelasemanal .cardconsulta {
|
|||||||
box-shadow: 0 1px 3px rgba(0,0,0,0.3);
|
box-shadow: 0 1px 3px rgba(0,0,0,0.3);
|
||||||
border-left: 5px solid #333;
|
border-left: 5px solid #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
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,6 +20,11 @@
|
|||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phone-icon-container:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.phone-icon {
|
.phone-icon {
|
||||||
@ -33,75 +38,173 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.profile-picture-container {
|
.profile-picture-container {
|
||||||
width: 40px;
|
width: 45px;
|
||||||
height: 40px;
|
height: 45px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: 2px solid #ccc;
|
border: 2px solid #007bff;
|
||||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
|
box-shadow: 0 2px 8px rgba(0, 123, 255, 0.3);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-picture-container:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 123, 255, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-photo {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-placeholder {
|
.profile-placeholder {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: #A9A9A9;
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-placeholder::after {
|
.placeholder-icon {
|
||||||
content: '';
|
font-size: 20px;
|
||||||
position: absolute;
|
color: white;
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
width: 60%;
|
|
||||||
height: 60%;
|
|
||||||
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="%23FFFFFF" d="M224 256c70.7 0 128-57.3 128-128S294.7 0 224 0 96 57.3 96 128s57.3 128 128 128zm-45.7 48C79.8 304 0 383.8 0 482.3c0 16.7 13.5 30.2 30.2 30.2h387.6c16.7 0 30.2-13.5 30.2-30.2 0-98.5-79.8-178.3-178.3-178.3h-45.7z"/></svg>');
|
|
||||||
background-size: contain;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-dropdown {
|
.profile-dropdown {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50px;
|
top: 60px;
|
||||||
right: 0;
|
right: 0;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
border: 1px solid #ddd;
|
border: 1px solid #e0e0e0;
|
||||||
border-radius: 5px;
|
border-radius: 12px;
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
min-width: 150px;
|
min-width: 180px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
animation: dropdownFadeIn 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes dropdownFadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-button {
|
.dropdown-button {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 10px 15px;
|
padding: 12px 16px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #333;
|
color: #333;
|
||||||
transition: background-color 0.2s;
|
transition: background-color 0.2s;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-button:hover {
|
.dropdown-button:hover {
|
||||||
background-color: #f0f0f0;
|
background-color: #f8f9fa;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logout-button {
|
.logout-button {
|
||||||
color: #cc0000;
|
color: #dc3545;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logout-button:hover {
|
.logout-button:hover {
|
||||||
background-color: #ffe0e0;
|
background-color: #ffe0e0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Modal de Logout */
|
||||||
|
.logout-modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-modal-content {
|
||||||
|
background-color: white;
|
||||||
|
padding: 2rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
|
||||||
|
max-width: 400px;
|
||||||
|
width: 90%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-modal-content h3 {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: #333;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-modal-content p {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-modal-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-cancel-button {
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: transparent;
|
||||||
|
color: #333;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-cancel-button:hover {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
border-color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-confirm-button {
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: #dc3545;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-confirm-button:hover {
|
||||||
|
background-color: #c82333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Suporte Card */
|
||||||
.suporte-card-overlay {
|
.suporte-card-overlay {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
@ -187,6 +290,7 @@
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Chat Online */
|
||||||
.chat-overlay {
|
.chat-overlay {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
@ -246,6 +350,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
transition: background-color 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fechar-chat:hover {
|
.fechar-chat:hover {
|
||||||
@ -260,6 +365,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
|
background-color: #fafafa;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mensagem {
|
.mensagem {
|
||||||
@ -267,33 +373,53 @@
|
|||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
animation: messageSlideIn 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes messageSlideIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mensagem.usuario {
|
.mensagem.usuario {
|
||||||
align-self: flex-end;
|
align-self: flex-end;
|
||||||
background-color: #e3f2fd;
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
border-bottom-right-radius: 4px;
|
border-bottom-right-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mensagem.suporte {
|
.mensagem.suporte {
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
background-color: #f5f5f5;
|
background-color: white;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
border-bottom-left-radius: 4px;
|
border-bottom-left-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mensagem-texto {
|
.mensagem-texto {
|
||||||
margin-bottom: 0.25rem;
|
margin-bottom: 0.25rem;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mensagem-hora {
|
.mensagem-hora {
|
||||||
font-size: 0.7rem;
|
font-size: 0.7rem;
|
||||||
color: #666;
|
opacity: 0.8;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mensagem.usuario .mensagem-hora {
|
||||||
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
.mensagem.suporte .mensagem-hora {
|
.mensagem.suporte .mensagem-hora {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-input {
|
.chat-input {
|
||||||
@ -313,93 +439,52 @@
|
|||||||
outline: none;
|
outline: none;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
|
transition: border-color 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-campo:focus {
|
.chat-campo:focus {
|
||||||
border-color: #1e3a8a;
|
border-color: #007bff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-enviar {
|
.chat-enviar {
|
||||||
background-color: #1e3a8a;
|
background-color: #007bff;
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 0.75rem 1rem;
|
padding: 0.75rem 1.5rem;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
|
transition: background-color 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-enviar:hover {
|
.chat-enviar:hover {
|
||||||
background-color: #1e40af;
|
background-color: #0056b3;
|
||||||
}
|
|
||||||
/* Modal de Logout */
|
|
||||||
.logout-modal-overlay {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background-color: rgba(0, 0, 0, 0.5);
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
z-index: 9999;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.logout-modal-content {
|
/* Responsividade */
|
||||||
background-color: white;
|
@media (max-width: 768px) {
|
||||||
padding: 2rem;
|
.header-container {
|
||||||
border-radius: 12px;
|
padding: 10px 15px;
|
||||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
|
}
|
||||||
max-width: 400px;
|
|
||||||
width: 90%;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logout-modal-content h3 {
|
.right-corner-elements {
|
||||||
margin-bottom: 1rem;
|
gap: 15px;
|
||||||
color: #333;
|
}
|
||||||
font-size: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logout-modal-content p {
|
.profile-picture-container {
|
||||||
margin-bottom: 2rem;
|
width: 40px;
|
||||||
color: #666;
|
height: 40px;
|
||||||
line-height: 1.4;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.logout-modal-buttons {
|
.suporte-card-container,
|
||||||
display: flex;
|
.chat-container {
|
||||||
gap: 1rem;
|
margin-right: 10px;
|
||||||
justify-content: center;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logout-cancel-button {
|
.suporte-card,
|
||||||
padding: 0.75rem 1.5rem;
|
.chat-online {
|
||||||
border: 1px solid #ccc;
|
width: calc(100vw - 20px);
|
||||||
border-radius: 8px;
|
max-width: none;
|
||||||
background-color: transparent;
|
}
|
||||||
color: #333;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
transition: background-color 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logout-cancel-button:hover {
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logout-confirm-button {
|
|
||||||
padding: 0.75rem 1.5rem;
|
|
||||||
border: none;
|
|
||||||
border-radius: 8px;
|
|
||||||
background-color: #dc3545;
|
|
||||||
color: white;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
transition: background-color 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logout-confirm-button:hover {
|
|
||||||
background-color: #c82333;
|
|
||||||
}
|
}
|
||||||
@ -9,10 +9,29 @@ const Header = () => {
|
|||||||
const [mensagem, setMensagem] = useState('');
|
const [mensagem, setMensagem] = useState('');
|
||||||
const [mensagens, setMensagens] = useState([]);
|
const [mensagens, setMensagens] = useState([]);
|
||||||
const [showLogoutModal, setShowLogoutModal] = useState(false);
|
const [showLogoutModal, setShowLogoutModal] = useState(false);
|
||||||
|
const [avatarUrl, setAvatarUrl] = useState(null);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const chatInputRef = useRef(null);
|
const chatInputRef = useRef(null);
|
||||||
const mensagensContainerRef = useRef(null);
|
const mensagensContainerRef = useRef(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadAvatar = () => {
|
||||||
|
const localAvatar = localStorage.getItem('user_avatar');
|
||||||
|
if (localAvatar) {
|
||||||
|
setAvatarUrl(localAvatar);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadAvatar();
|
||||||
|
|
||||||
|
const handleStorageChange = () => {
|
||||||
|
loadAvatar();
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('storage', handleStorageChange);
|
||||||
|
return () => window.removeEventListener('storage', handleStorageChange);
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isChatOpen && chatInputRef.current) {
|
if (isChatOpen && chatInputRef.current) {
|
||||||
chatInputRef.current.focus();
|
chatInputRef.current.focus();
|
||||||
@ -25,12 +44,16 @@ const Header = () => {
|
|||||||
}
|
}
|
||||||
}, [mensagens]);
|
}, [mensagens]);
|
||||||
|
|
||||||
// Funções de Logout (do seu código)
|
// --- Logout ---
|
||||||
const handleLogoutClick = () => {
|
const handleLogoutClick = () => {
|
||||||
setShowLogoutModal(true);
|
setShowLogoutModal(true);
|
||||||
setIsDropdownOpen(false);
|
setIsDropdownOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleLogoutCancel = () => {
|
||||||
|
setShowLogoutModal(false);
|
||||||
|
};
|
||||||
|
|
||||||
const handleLogoutConfirm = async () => {
|
const handleLogoutConfirm = async () => {
|
||||||
try {
|
try {
|
||||||
const token =
|
const token =
|
||||||
@ -77,7 +100,7 @@ const Header = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const clearAuthData = () => {
|
const clearAuthData = () => {
|
||||||
["token","authToken","userToken","access_token","user","auth","userData"].forEach(key => {
|
["token", "authToken", "userToken", "access_token", "user", "auth", "userData"].forEach(key => {
|
||||||
localStorage.removeItem(key);
|
localStorage.removeItem(key);
|
||||||
sessionStorage.removeItem(key);
|
sessionStorage.removeItem(key);
|
||||||
});
|
});
|
||||||
@ -91,8 +114,6 @@ const Header = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLogoutCancel = () => setShowLogoutModal(false);
|
|
||||||
|
|
||||||
const handleProfileClick = () => {
|
const handleProfileClick = () => {
|
||||||
setIsDropdownOpen(!isDropdownOpen);
|
setIsDropdownOpen(!isDropdownOpen);
|
||||||
if (isSuporteCardOpen) setIsSuporteCardOpen(false);
|
if (isSuporteCardOpen) setIsSuporteCardOpen(false);
|
||||||
@ -120,7 +141,7 @@ const Header = () => {
|
|||||||
setMensagens([
|
setMensagens([
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
texto: 'Olá! Bem-vindo ao suporte Mediconnect. Como podemos ajudar você hoje?',
|
texto: 'Olá! Me chamo Ágatha e sou sua assistente virtual. 👋 Bem-vindo ao suporte Mediconnect. Como posso te ajudar hoje?',
|
||||||
remetente: 'suporte',
|
remetente: 'suporte',
|
||||||
hora: new Date().toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' })
|
hora: new Date().toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' })
|
||||||
}
|
}
|
||||||
@ -132,10 +153,11 @@ const Header = () => {
|
|||||||
setMensagem('');
|
setMensagem('');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEnviarMensagem = (e) => {
|
const handleEnviarMensagem = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (mensagem.trim() === '') return;
|
if (mensagem.trim() === '') return;
|
||||||
|
|
||||||
|
// Mensagem do usuário
|
||||||
const novaMensagemUsuario = {
|
const novaMensagemUsuario = {
|
||||||
id: Date.now(),
|
id: Date.now(),
|
||||||
texto: mensagem,
|
texto: mensagem,
|
||||||
@ -146,30 +168,34 @@ const Header = () => {
|
|||||||
setMensagens(prev => [...prev, novaMensagemUsuario]);
|
setMensagens(prev => [...prev, novaMensagemUsuario]);
|
||||||
setMensagem('');
|
setMensagem('');
|
||||||
|
|
||||||
setTimeout(() => {
|
try {
|
||||||
if (chatInputRef.current) {
|
const response = await fetch("http://localhost:5000/api/chat", {
|
||||||
chatInputRef.current.focus();
|
method: "POST",
|
||||||
}
|
headers: { "Content-Type": "application/json" },
|
||||||
}, 0);
|
body: JSON.stringify({ message: mensagem }),
|
||||||
|
});
|
||||||
|
|
||||||
setTimeout(() => {
|
const data = await response.json();
|
||||||
const respostas = [
|
|
||||||
'Entendi sua dúvida. Vou verificar isso para você.',
|
|
||||||
'Obrigado pela informação. Estou analisando seu caso.',
|
|
||||||
'Pode me dar mais detalhes sobre o problema?',
|
|
||||||
'Já encaminhei sua solicitação para nossa equipe técnica.',
|
|
||||||
'Vou ajudar você a resolver isso!'
|
|
||||||
];
|
|
||||||
|
|
||||||
|
// Resposta da IA
|
||||||
const respostaSuporte = {
|
const respostaSuporte = {
|
||||||
id: Date.now() + 1,
|
id: Date.now() + 1,
|
||||||
texto: respostas[Math.floor(Math.random() * respostas.length)],
|
texto: data.resposta || data.reply || "Desculpe, não consegui processar sua pergunta no momento 😅",
|
||||||
remetente: 'suporte',
|
remetente: 'suporte',
|
||||||
hora: new Date().toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' })
|
hora: new Date().toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' })
|
||||||
};
|
};
|
||||||
|
|
||||||
setMensagens(prev => [...prev, respostaSuporte]);
|
setMensagens(prev => [...prev, respostaSuporte]);
|
||||||
}, 1000);
|
} catch (error) {
|
||||||
|
console.error("Erro ao conectar com o servidor:", error);
|
||||||
|
const erroMsg = {
|
||||||
|
id: Date.now() + 1,
|
||||||
|
texto: "Ops! Ocorreu um erro ao tentar falar com o suporte.",
|
||||||
|
remetente: 'suporte',
|
||||||
|
hora: new Date().toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' })
|
||||||
|
};
|
||||||
|
setMensagens(prev => [...prev, erroMsg]);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const SuporteCard = () => (
|
const SuporteCard = () => (
|
||||||
@ -245,8 +271,12 @@ const Header = () => {
|
|||||||
|
|
||||||
{isDropdownOpen && (
|
{isDropdownOpen && (
|
||||||
<div className="profile-dropdown">
|
<div className="profile-dropdown">
|
||||||
<button type="button" onClick={handleViewProfile} className="dropdown-button">Ver Perfil</button>
|
<button type="button" onClick={handleViewProfile} className="dropdown-button">
|
||||||
<button type="button" onClick={handleLogoutClick} className="dropdown-button logout-button">Sair (Logout)</button>
|
Ver Perfil
|
||||||
|
</button>
|
||||||
|
<button type="button" onClick={handleLogoutClick} className="dropdown-button logout-button">
|
||||||
|
Sair (Logout)
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -260,16 +260,6 @@ function Sidebar({ menuItems }) {
|
|||||||
})}
|
})}
|
||||||
|
|
||||||
{/* Logout */}
|
{/* Logout */}
|
||||||
<li className="sidebar-item">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="sidebar-link btn"
|
|
||||||
onClick={handleLogoutClick}
|
|
||||||
>
|
|
||||||
<i className="bi bi-box-arrow-right"></i>
|
|
||||||
<span>Sair (Logout)</span>
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<TrocardePerfis />
|
<TrocardePerfis />
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
75
src/components/TrocardePerfis.css
Normal file
75
src/components/TrocardePerfis.css
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
|
||||||
|
.container-perfis-toggle {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
max-width: 300px;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-button {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 15px 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-button:hover {
|
||||||
|
background-color: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.acesso-text {
|
||||||
|
font-size: 1.1em;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.perfil-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 10px 20px 20px 20px;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.perfil-item {
|
||||||
|
padding: 10px 15px;
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: background-color 0.2s ease, transform 0.1s ease;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.perfil-item:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.perfil-item:focus {
|
||||||
|
box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.perfil-item:active {
|
||||||
|
background-color: #004085;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-profiles {
|
||||||
|
padding: 10px;
|
||||||
|
text-align: center;
|
||||||
|
color: #888;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
@ -1,31 +1,58 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { useNavigate, useLocation } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { UserInfos } from "./utils/Functions-Endpoints/General";
|
import { UserInfos } from "./utils/Functions-Endpoints/General";
|
||||||
import { useAuth } from "./utils/AuthProvider";
|
import { useAuth } from "./utils/AuthProvider";
|
||||||
import "../pages/style/TrocardePerfis.css";
|
import "./TrocardePerfis.css";
|
||||||
|
|
||||||
|
const ToggleIcon = ({ isOpen }) => (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
style={{ transition: 'transform 0.3s', transform: isOpen ? 'rotate(180deg)' : 'rotate(0deg)' }}
|
||||||
|
>
|
||||||
|
<polyline points="6 9 12 15 18 9"></polyline>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
const TrocardePerfis = () => {
|
const TrocardePerfis = () => {
|
||||||
const location = useLocation();
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { getAuthorizationHeader } = useAuth();
|
const { getAuthorizationHeader } = useAuth();
|
||||||
|
|
||||||
const [selectedProfile, setSelectedProfile] = useState("");
|
|
||||||
const [showProfiles, setShowProfiles] = useState([]);
|
const [showProfiles, setShowProfiles] = useState([]);
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
const authHeader = getAuthorizationHeader();
|
const authHeader = getAuthorizationHeader();
|
||||||
setSelectedProfile(location.pathname || "");
|
try {
|
||||||
const userInfo = await UserInfos(authHeader);
|
const userInfo = await UserInfos(authHeader);
|
||||||
setShowProfiles(userInfo?.roles || []);
|
setShowProfiles(userInfo?.roles || []);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erro ao buscar informações do usuário:", error);
|
||||||
|
setShowProfiles([]);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
fetchData();
|
fetchData();
|
||||||
}, [location.pathname, getAuthorizationHeader]);
|
}, [getAuthorizationHeader]);
|
||||||
|
|
||||||
const handleSelectChange = (e) => {
|
const handleProfileClick = (route) => {
|
||||||
const route = e.target.value;
|
if (route) {
|
||||||
setSelectedProfile(route);
|
navigate(route);
|
||||||
if (route) navigate(route);
|
setIsOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleToggle = () => {
|
||||||
|
setIsOpen(prev => !prev);
|
||||||
};
|
};
|
||||||
|
|
||||||
const options = [
|
const options = [
|
||||||
@ -40,20 +67,47 @@ const TrocardePerfis = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container-perfis">
|
<div className="container-perfis-toggle">
|
||||||
<p className="acesso-text">Acesso aos módulos:</p>
|
|
||||||
<select
|
<div
|
||||||
className="perfil-select"
|
className="toggle-button"
|
||||||
value={selectedProfile}
|
onClick={handleToggle}
|
||||||
onChange={handleSelectChange}
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
handleToggle();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="acesso-text">Acesso aos módulos</span>
|
||||||
|
<ToggleIcon isOpen={isOpen} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isOpen && (
|
||||||
|
<div className="perfil-list">
|
||||||
|
{options.length > 0 ? (
|
||||||
|
options.map((opt) => (
|
||||||
|
<div
|
||||||
|
key={opt.key}
|
||||||
|
className="perfil-item"
|
||||||
|
onClick={() => handleProfileClick(opt.route)}
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
handleProfileClick(opt.route);
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<option value="">Selecionar perfil</option>
|
|
||||||
{options.map((opt) => (
|
|
||||||
<option key={opt.key} value={opt.route}>
|
|
||||||
{opt.label}
|
{opt.label}
|
||||||
</option>
|
</div>
|
||||||
))}
|
))
|
||||||
</select>
|
) : (
|
||||||
|
<p className="no-profiles">Nenhum perfil disponível.</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -126,12 +126,14 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAvailabilityUpdate = useCallback(
|
const handleAvailabilityUpdate = useCallback((newAvailability) => {
|
||||||
(newAvailability) => {
|
setFormData((prev) => {
|
||||||
setFormData((prev) => ({ ...prev, availability: newAvailability }));
|
if (JSON.stringify(prev.availability) !== JSON.stringify(newAvailability)) {
|
||||||
},
|
return { ...prev, availability: newAvailability };
|
||||||
[setFormData]
|
}
|
||||||
);
|
return prev;
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleCepBlur = async () => {
|
const handleCepBlur = async () => {
|
||||||
const cep = formData.cep?.replace(/\D/g, "");
|
const cep = formData.cep?.replace(/\D/g, "");
|
||||||
@ -229,25 +231,6 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePatchAvailability = async (id, updatedAvailability) => {
|
|
||||||
try {
|
|
||||||
const response = await fetch(`${ENDPOINT_AVAILABILITY}?id=${id}`, {
|
|
||||||
method: "PATCH",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(updatedAvailability),
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
console.log("Disponibilidade atualizada:", data);
|
|
||||||
alert("Disponibilidade atualizada com sucesso!");
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Erro ao atualizar disponibilidade:", error);
|
|
||||||
alert("Erro ao atualizar disponibilidade.");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
const missingFields = [];
|
const missingFields = [];
|
||||||
if (!formData.full_name) missingFields.push("full_name");
|
if (!formData.full_name) missingFields.push("full_name");
|
||||||
@ -290,20 +273,12 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
|||||||
await onSave({ ...formData });
|
await onSave({ ...formData });
|
||||||
|
|
||||||
if (formData.availability && formData.availability.length > 0) {
|
if (formData.availability && formData.availability.length > 0) {
|
||||||
if (formData.availabilityId) {
|
|
||||||
await handlePatchAvailability(
|
|
||||||
formData.availabilityId,
|
|
||||||
formData.availability
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
await handleCreateAvailability(formData.availability);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
alert("Médico salvo e disponibilidade enviada ao mock com sucesso!");
|
alert("Médico salvo com sucesso!");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Erro ao salvar médico ou disponibilidade:", error);
|
console.error("Erro ao salvar médico:", error);
|
||||||
alert("Erro ao salvar médico ou disponibilidade.");
|
alert("Erro ao salvar médico.");
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -734,7 +709,7 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* BOTÕES DE AÇÃO */}
|
{/* BOTÕES DE AÇÃO */}
|
||||||
<div className="actions-container">
|
<div className="btns-container">
|
||||||
<button
|
<button
|
||||||
className="btn btn-success btn-submit"
|
className="btn btn-success btn-submit"
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect, useCallback } from "react";
|
import React, { useState, useEffect, useCallback, useRef } from "react";
|
||||||
import { Clock } from "lucide-react";
|
import { Clock } from "lucide-react";
|
||||||
|
|
||||||
const initialBlockTemplate = {
|
const initialBlockTemplate = {
|
||||||
@ -21,21 +21,19 @@ const emptyAvailabilityTemplate = [
|
|||||||
const HorariosDisponibilidade = ({
|
const HorariosDisponibilidade = ({
|
||||||
initialAvailability = emptyAvailabilityTemplate,
|
initialAvailability = emptyAvailabilityTemplate,
|
||||||
onUpdate,
|
onUpdate,
|
||||||
|
onCancel,
|
||||||
}) => {
|
}) => {
|
||||||
const [availability, setAvailability] = useState(initialAvailability);
|
const [availability, setAvailability] = useState(initialAvailability);
|
||||||
|
const isFirstRun = useRef(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (initialAvailability !== emptyAvailabilityTemplate) {
|
if (initialAvailability && initialAvailability.length > 0) {
|
||||||
setAvailability(initialAvailability);
|
setAvailability(initialAvailability);
|
||||||
|
} else {
|
||||||
|
setAvailability(emptyAvailabilityTemplate);
|
||||||
}
|
}
|
||||||
}, [initialAvailability]);
|
}, [initialAvailability]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (onUpdate) {
|
|
||||||
onUpdate(availability);
|
|
||||||
}
|
|
||||||
}, [availability, onUpdate]);
|
|
||||||
|
|
||||||
const handleDayCheck = useCallback((dayIndex, currentIsChecked) => {
|
const handleDayCheck = useCallback((dayIndex, currentIsChecked) => {
|
||||||
const isChecked = !currentIsChecked;
|
const isChecked = !currentIsChecked;
|
||||||
|
|
||||||
@ -110,6 +108,10 @@ const HorariosDisponibilidade = ({
|
|||||||
);
|
);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleSave = useCallback(() => {
|
||||||
|
if (onUpdate) onUpdate(availability);
|
||||||
|
}, [availability, onUpdate]);
|
||||||
|
|
||||||
const renderTimeBlock = (dayIndex, bloco) => (
|
const renderTimeBlock = (dayIndex, bloco) => (
|
||||||
<div
|
<div
|
||||||
key={bloco.id}
|
key={bloco.id}
|
||||||
@ -165,7 +167,7 @@ const HorariosDisponibilidade = ({
|
|||||||
width: "100%",
|
width: "100%",
|
||||||
boxSizing: "border-box",
|
boxSizing: "border-box",
|
||||||
outline: "none",
|
outline: "none",
|
||||||
fontSize: "13px"
|
fontSize: "13px",
|
||||||
}}
|
}}
|
||||||
step="300"
|
step="300"
|
||||||
/>
|
/>
|
||||||
@ -186,7 +188,12 @@ const HorariosDisponibilidade = ({
|
|||||||
<div style={{ display: "flex", alignItems: "center", gap: "4px" }}>
|
<div style={{ display: "flex", alignItems: "center", gap: "4px" }}>
|
||||||
<label
|
<label
|
||||||
htmlFor={`termino-${dayIndex}-${bloco.id}`}
|
htmlFor={`termino-${dayIndex}-${bloco.id}`}
|
||||||
style={{ fontWeight: 500, color: "#4b5563", width: "56px", fontSize: "13px" }}
|
style={{
|
||||||
|
fontWeight: 500,
|
||||||
|
color: "#4b5563",
|
||||||
|
width: "56px",
|
||||||
|
fontSize: "13px",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Término:
|
Término:
|
||||||
</label>
|
</label>
|
||||||
@ -258,8 +265,7 @@ const HorariosDisponibilidade = ({
|
|||||||
marginLeft: window.innerWidth < 640 ? "0" : "16px",
|
marginLeft: window.innerWidth < 640 ? "0" : "16px",
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
}}
|
}}
|
||||||
>
|
></span>
|
||||||
</span>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -632,7 +632,7 @@ function PatientForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* BOTÕES DE AÇÃO */}
|
{/* BOTÕES DE AÇÃO */}
|
||||||
<div className="actions-container">
|
<div className="btns-container">
|
||||||
<button className="btn btn-success btn-submit" onClick={handleSubmit} disabled={isLoading}>
|
<button className="btn btn-success btn-submit" onClick={handleSubmit} disabled={isLoading}>
|
||||||
{isLoading ? 'Salvando...' : 'Salvar Paciente'}
|
{isLoading ? 'Salvando...' : 'Salvar Paciente'}
|
||||||
</button>
|
</button>
|
||||||
@ -642,6 +642,7 @@ function PatientForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
|||||||
</button>
|
</button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,48 +1,56 @@
|
|||||||
import API_KEY from '../apiKeys';
|
import API_KEY from '../apiKeys';
|
||||||
|
|
||||||
const GetDoctorByID = async (ID, authHeader) => {
|
const GetDoctorByID = async (ID, authHeader) => {
|
||||||
var myHeaders = new Headers();
|
const myHeaders = new Headers();
|
||||||
myHeaders.append('apikey', API_KEY);
|
myHeaders.append('apikey', API_KEY);
|
||||||
if (authHeader) myHeaders.append('Authorization', authHeader);
|
if (authHeader) myHeaders.append('Authorization', authHeader);
|
||||||
|
|
||||||
const requestOptions = { method: 'GET', redirect: 'follow', headers: myHeaders };
|
const requestOptions = {
|
||||||
const res = await fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors?id=eq.${ID}`, requestOptions);
|
method: 'GET',
|
||||||
|
redirect: 'follow',
|
||||||
|
headers: myHeaders,
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await fetch(
|
||||||
|
`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors?id=eq.${ID}`,
|
||||||
|
requestOptions
|
||||||
|
);
|
||||||
const DictMedico = await res.json();
|
const DictMedico = await res.json();
|
||||||
return DictMedico;
|
return DictMedico;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const GetAllDoctors = async (authHeader) => {
|
const GetAllDoctors = async (authHeader) => {
|
||||||
var myHeaders = new Headers();
|
const myHeaders = new Headers();
|
||||||
myHeaders.append("apikey", API_KEY);
|
myHeaders.append('apikey', API_KEY);
|
||||||
myHeaders.append("Authorization", authHeader);
|
if (authHeader) myHeaders.append('Authorization', authHeader);
|
||||||
|
|
||||||
var requestOptions = {
|
const requestOptions = {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: myHeaders,
|
headers: myHeaders,
|
||||||
redirect: 'follow'
|
redirect: 'follow',
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors", requestOptions)
|
|
||||||
const DictMedicos = await result.json()
|
|
||||||
return DictMedicos
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const result = await fetch(
|
||||||
|
'https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors?select=id,full_name,crm&limit=500',
|
||||||
|
requestOptions
|
||||||
|
);
|
||||||
|
const DictMedicos = await result.json();
|
||||||
|
return DictMedicos;
|
||||||
|
};
|
||||||
|
|
||||||
const GetDoctorByName = async (nome, authHeader) => {
|
const GetDoctorByName = async (nome, authHeader) => {
|
||||||
const Medicos = await GetAllDoctors(authHeader)
|
const Medicos = await GetAllDoctors(authHeader);
|
||||||
|
|
||||||
for (let i = 0; i < Medicos.length; i++) {
|
for (let i = 0; i < Medicos.length; i++) {
|
||||||
|
|
||||||
if (Medicos[i].full_name === nome) {
|
if (Medicos[i].full_name === nome) {
|
||||||
console.log('Medico encontrado:', Medicos[i]);
|
console.log('Médico encontrado:', Medicos[i]);
|
||||||
return Medicos[i];
|
return Medicos[i];
|
||||||
}
|
}
|
||||||
else{console.log("nada encontrado")}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('Nenhum médico encontrado com o nome:', nome);
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
}
|
export { GetDoctorByID, GetDoctorByName, GetAllDoctors };
|
||||||
|
|
||||||
export {GetDoctorByID, GetDoctorByName, GetAllDoctors}
|
|
||||||
|
|||||||
17
src/components/utils/fetchErros/CabecalhoError.jsx
Normal file
17
src/components/utils/fetchErros/CabecalhoError.jsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import './style.css';
|
||||||
|
|
||||||
|
const CabecalhoError = ({ showCabecalho, message, errorCode }) => {
|
||||||
|
if (!showCabecalho) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="cabecalho-error-wrapper">
|
||||||
|
<div className="cabecalho-error">
|
||||||
|
|
||||||
|
<p className='cabecalho-error-text'>{message}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CabecalhoError;
|
||||||
15
src/components/utils/fetchErros/ManagerFunction.js
Normal file
15
src/components/utils/fetchErros/ManagerFunction.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
function manager (setShowModal, RefreshingToken, setErrorInfo,errorData) {
|
||||||
|
|
||||||
|
console.log((errorData, "MANAGER"))
|
||||||
|
|
||||||
|
|
||||||
|
if(errorData.httpStatus === 401){
|
||||||
|
RefreshingToken()
|
||||||
|
}else{
|
||||||
|
setErrorInfo(errorData)
|
||||||
|
setShowModal("modal");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default manager
|
||||||
@ -16,7 +16,10 @@ if( ErrorData.httpStatus === 401){
|
|||||||
console.log('uaua')
|
console.log('uaua')
|
||||||
}else if(ErrorData.httpStatus === 404){
|
}else if(ErrorData.httpStatus === 404){
|
||||||
setModalMensagem("Erro interno do sistema")
|
setModalMensagem("Erro interno do sistema")
|
||||||
}else{setModalMensagem(ErrorData.mensagem)}
|
}else if(ErrorData.httpStatus === undefined){
|
||||||
|
setModalMensagem("Erro operacional no sistema")
|
||||||
|
}
|
||||||
|
else{setModalMensagem(ErrorData.mensagem)}
|
||||||
|
|
||||||
}, [ErrorData])
|
}, [ErrorData])
|
||||||
|
|
||||||
@ -24,7 +27,7 @@ return(
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|
||||||
{showModal ?
|
{showModal === "modal"?
|
||||||
|
|
||||||
<div className="modal-overlay">
|
<div className="modal-overlay">
|
||||||
|
|
||||||
|
|||||||
26
src/components/utils/fetchErros/style.css
Normal file
26
src/components/utils/fetchErros/style.css
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
.cabecalho-error {
|
||||||
|
background-color: #f3616d; /* vermelho forte */
|
||||||
|
color: white;
|
||||||
|
display: flex;
|
||||||
|
align-items: center; /* centraliza verticalmente */
|
||||||
|
justify-content: center; /* centraliza horizontalmente */
|
||||||
|
padding: 12px 20px;
|
||||||
|
margin: 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 16px;
|
||||||
|
width: calc(100% - 20px); /* ocupa quase toda a largura da div pai */
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cabecalho-error-text {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 30px; /* espaço para "Erro 404" */
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cabecalho-error-code {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
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);
|
||||||
13
src/openaiService.js
Normal file
13
src/openaiService.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// src/services/openaiService.js
|
||||||
|
export async function perguntarOpenAI(mensagem) {
|
||||||
|
const resposta = await fetch("http://localhost:5000/api/chat", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ message: mensagem }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await resposta.json();
|
||||||
|
return data.reply;
|
||||||
|
}
|
||||||
@ -21,12 +21,15 @@ import { Search } from 'lucide-react';
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const Agendamento = ({setDictInfo}) => {
|
const Agendamento = ({setDictInfo}) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const [listaTodosAgendamentos, setListaTodosAgendamentos] = useState([])
|
||||||
|
|
||||||
const [selectedID, setSelectedId] = useState('0')
|
const [selectedID, setSelectedId] = useState('0')
|
||||||
const [filaEsperaData, setfilaEsperaData] = useState([])
|
const [filaEsperaData, setFilaEsperaData] = useState([])
|
||||||
const [FiladeEspera, setFiladeEspera] = useState(false);
|
const [FiladeEspera, setFiladeEspera] = useState(false);
|
||||||
const [tabela, setTabela] = useState('diario');
|
const [tabela, setTabela] = useState('diario');
|
||||||
const [PageNovaConsulta, setPageConsulta] = useState(false);
|
const [PageNovaConsulta, setPageConsulta] = useState(false);
|
||||||
@ -42,39 +45,59 @@ const Agendamento = ({setDictInfo}) => {
|
|||||||
const [FiltredTodosMedicos, setFiltredTodosMedicos] = useState([])
|
const [FiltredTodosMedicos, setFiltredTodosMedicos] = useState([])
|
||||||
const [searchTermDoctor, setSearchTermDoctor] = useState('');
|
const [searchTermDoctor, setSearchTermDoctor] = useState('');
|
||||||
|
|
||||||
|
const [MedicoFiltrado, setMedicoFiltrado] = useState({id:"vazio"})
|
||||||
|
|
||||||
|
|
||||||
|
const [cacheAgendamentos, setCacheAgendamentos] = useState([])
|
||||||
|
|
||||||
|
const [showConfirmModal, setShowConfirmModal] = useState(false)
|
||||||
|
|
||||||
|
const [motivoCancelamento, setMotivoCancelamento] = useState("")
|
||||||
|
|
||||||
|
const [corModal, setCorModal] = useState("")
|
||||||
|
|
||||||
|
const [listaConsultasID, setListaConsultaID] = useState([])
|
||||||
|
const [coresConsultas,setCoresConsultas] = useState([])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let authHeader = getAuthorizationHeader()
|
let authHeader = getAuthorizationHeader()
|
||||||
|
|
||||||
|
const cacheMedicos = {};
|
||||||
|
const cachePacientes = {};
|
||||||
|
|
||||||
const FiltrarAgendamentos = async (listaTodosAgendamentos) => {
|
|
||||||
const ConfigurarFiladeEspera = async (patient_id, doctor_id, agendamento) => {
|
|
||||||
|
|
||||||
let medico = await GetDoctorByID(doctor_id, authHeader);
|
|
||||||
let paciente = await GetPatientByID(patient_id, authHeader);
|
|
||||||
|
|
||||||
console.log(medico)
|
useMemo(() => {
|
||||||
|
if (!listaTodosAgendamentos.length) return { agendamentosOrganizados: {}, filaEsperaData: [] };
|
||||||
|
console.log("recarregando")
|
||||||
|
const DictAgendamentosOrganizados = {};
|
||||||
|
const ListaFilaDeEspera = [];
|
||||||
|
|
||||||
let dicionario = {
|
const fetchDados = async () => {
|
||||||
agendamento: agendamento,
|
for (const agendamento of listaTodosAgendamentos) {
|
||||||
|
if (agendamento.status === "requested") {
|
||||||
|
// Cache de médico e paciente
|
||||||
|
if (!cacheMedicos[agendamento.doctor_id]) {
|
||||||
|
cacheMedicos[agendamento.doctor_id] = await GetDoctorByID(agendamento.doctor_id, authHeader);
|
||||||
|
}
|
||||||
|
if (!cachePacientes[agendamento.patient_id]) {
|
||||||
|
cachePacientes[agendamento.patient_id] = await GetPatientByID(agendamento.patient_id, authHeader);
|
||||||
|
}
|
||||||
|
|
||||||
|
const medico = cacheMedicos[agendamento.doctor_id];
|
||||||
|
const paciente = cachePacientes[agendamento.patient_id];
|
||||||
|
|
||||||
|
ListaFilaDeEspera.push({
|
||||||
|
agendamento,
|
||||||
Infos: {
|
Infos: {
|
||||||
nome_medico: medico[0]?.full_name,
|
nome_medico: medico[0]?.full_name,
|
||||||
doctor_id: medico[0]?.id,
|
doctor_id: medico[0]?.id,
|
||||||
patient_id: paciente[0].id,
|
patient_id: paciente[0]?.id,
|
||||||
paciente_nome: paciente[0].full_name,
|
paciente_nome: paciente[0]?.full_name,
|
||||||
paciente_cpf: paciente[0].cpf
|
paciente_cpf: paciente[0]?.cpf,
|
||||||
}
|
},
|
||||||
};
|
});
|
||||||
return dicionario;
|
|
||||||
};
|
|
||||||
|
|
||||||
let DictAgendamentosOrganizados = {};
|
|
||||||
let ListaFilaDeEspera = [];
|
|
||||||
|
|
||||||
|
|
||||||
for (const agendamento of listaTodosAgendamentos) {
|
|
||||||
if (agendamento.status === 'requested') {
|
|
||||||
let v = await ConfigurarFiladeEspera(agendamento.patient_id, agendamento.doctor_id, agendamento);
|
|
||||||
ListaFilaDeEspera.push(v);
|
|
||||||
} else {
|
} else {
|
||||||
const DiaAgendamento = agendamento.scheduled_at.split("T")[0];
|
const DiaAgendamento = agendamento.scheduled_at.split("T")[0];
|
||||||
|
|
||||||
@ -86,28 +109,28 @@ const Agendamento = ({setDictInfo}) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ordenar por data
|
||||||
for (const DiaAgendamento in DictAgendamentosOrganizados) {
|
for (const DiaAgendamento in DictAgendamentosOrganizados) {
|
||||||
DictAgendamentosOrganizados[DiaAgendamento].sort((a, b) => {
|
DictAgendamentosOrganizados[DiaAgendamento].sort((a, b) => a.scheduled_at.localeCompare(b.scheduled_at));
|
||||||
if (a.scheduled_at < b.scheduled_at) return -1;
|
|
||||||
if (a.scheduled_at > b.scheduled_at) return 1;
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const chavesOrdenadas = Object.keys(DictAgendamentosOrganizados).sort();
|
||||||
|
|
||||||
const chavesOrdenadas = Object.keys(DictAgendamentosOrganizados).sort((a, b) => {
|
const DictAgendamentosFinal = {};
|
||||||
if (a < b) return -1;
|
|
||||||
if (a > b) return 1;
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
let DictAgendamentosFinal = {};
|
|
||||||
for (const data of chavesOrdenadas) {
|
for (const data of chavesOrdenadas) {
|
||||||
DictAgendamentosFinal[data] = DictAgendamentosOrganizados[data];
|
DictAgendamentosFinal[data] = DictAgendamentosOrganizados[data];
|
||||||
}
|
}
|
||||||
|
|
||||||
setAgendamentosOrganizados(DictAgendamentosFinal);
|
setAgendamentosOrganizados(DictAgendamentosFinal);
|
||||||
setfilaEsperaData(ListaFilaDeEspera);
|
setFilaEsperaData(ListaFilaDeEspera);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fetchDados();
|
||||||
|
|
||||||
|
return { agendamentosOrganizados: DictAgendamentosOrganizados, filaEsperaData: ListaFilaDeEspera };
|
||||||
|
}, [listaTodosAgendamentos]); // 👉 só recalcula quando a lista muda
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
var myHeaders = new Headers();
|
var myHeaders = new Headers();
|
||||||
myHeaders.append("Authorization", authHeader);
|
myHeaders.append("Authorization", authHeader);
|
||||||
@ -121,7 +144,7 @@ const Agendamento = ({setDictInfo}) => {
|
|||||||
|
|
||||||
fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?select&doctor_id&patient_id&status&scheduled_at&order&limit&offset", requestOptions)
|
fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?select&doctor_id&patient_id&status&scheduled_at&order&limit&offset", requestOptions)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(result => {FiltrarAgendamentos(result);console.log(result)})
|
.then(result => {setListaTodosAgendamentos(result);console.log(result)})
|
||||||
.catch(error => console.log('error', error));
|
.catch(error => console.log('error', error));
|
||||||
|
|
||||||
const PegarTodosOsMedicos = async () => {
|
const PegarTodosOsMedicos = async () => {
|
||||||
@ -135,24 +158,7 @@ const Agendamento = ({setDictInfo}) => {
|
|||||||
PegarTodosOsMedicos()
|
PegarTodosOsMedicos()
|
||||||
|
|
||||||
}, [])
|
}, [])
|
||||||
useEffect(() => {
|
|
||||||
console.log("mudou FiltredTodosMedicos:", FiltredTodosMedicos);
|
|
||||||
if (FiltredTodosMedicos.length === 1) {
|
|
||||||
const unicoMedico = FiltredTodosMedicos[0];
|
|
||||||
console.log(unicoMedico)
|
|
||||||
const idMedicoFiltrado = unicoMedico.idMedico;
|
|
||||||
console.log(`Médico único encontrado: ${unicoMedico.nomeMedico}. ID: ${idMedicoFiltrado}`);
|
|
||||||
|
|
||||||
const agendamentosDoMedico = filtrarAgendamentosPorMedico(
|
|
||||||
DictAgendamentosOrganizados,
|
|
||||||
idMedicoFiltrado
|
|
||||||
);
|
|
||||||
console.log(`Total de agendamentos filtrados para este médico: ${agendamentosDoMedico.length}`);
|
|
||||||
console.log("Lista completa de Agendamentos do Médico:", agendamentosDoMedico);
|
|
||||||
FiltrarAgendamentos(agendamentosDoMedico)
|
|
||||||
|
|
||||||
}
|
|
||||||
}, [FiltredTodosMedicos]);
|
|
||||||
|
|
||||||
const deleteConsulta = (selectedPatientId) => {
|
const deleteConsulta = (selectedPatientId) => {
|
||||||
var myHeaders = new Headers();
|
var myHeaders = new Headers();
|
||||||
@ -161,7 +167,32 @@ const deleteConsulta = (selectedPatientId) => {
|
|||||||
myHeaders.append("authorization", authHeader)
|
myHeaders.append("authorization", authHeader)
|
||||||
|
|
||||||
|
|
||||||
var raw = JSON.stringify({ "status":"cancelled"
|
var raw = JSON.stringify({ "status":"cancelled",
|
||||||
|
"cancellation_reason": motivoCancelamento
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
var requestOptions = {
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: myHeaders,
|
||||||
|
body: raw,
|
||||||
|
redirect: 'follow'
|
||||||
|
};
|
||||||
|
|
||||||
|
fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?id=eq.${selectedPatientId}`, requestOptions)
|
||||||
|
.then(response => {if(response.status !== 200)(console.log(response))})
|
||||||
|
.then(result => console.log(result))
|
||||||
|
.catch(error => console.log('error', error));
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmConsulta = (selectedPatientId) => {
|
||||||
|
var myHeaders = new Headers();
|
||||||
|
myHeaders.append("Content-Type", "application/json");
|
||||||
|
myHeaders.append('apikey', API_KEY)
|
||||||
|
myHeaders.append("authorization", authHeader)
|
||||||
|
|
||||||
|
|
||||||
|
var raw = JSON.stringify({ "status":"confirmed"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -179,17 +210,6 @@ const deleteConsulta = (selectedPatientId) => {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const filtrarAgendamentosPorMedico = (dictAgendamentos, idMedicoFiltrado) => {
|
|
||||||
|
|
||||||
const todasAsListasDeAgendamentos = Object.values(dictAgendamentos);
|
|
||||||
const todosOsAgendamentos = todasAsListasDeAgendamentos.flat();
|
|
||||||
|
|
||||||
const agendamentosFiltrados = todosOsAgendamentos.filter(agendamento =>
|
|
||||||
agendamento.doctor_id === idMedicoFiltrado
|
|
||||||
);
|
|
||||||
|
|
||||||
return agendamentosFiltrados;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const filteredAgendamentos = useMemo(() => {
|
const filteredAgendamentos = useMemo(() => {
|
||||||
@ -238,12 +258,60 @@ const filtrarAgendamentosPorMedico = (dictAgendamentos, idMedicoFiltrado) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log("mudou FiltredTodosMedicos:", FiltredTodosMedicos);
|
||||||
|
if (MedicoFiltrado.id != "vazio" ) {
|
||||||
|
const unicoMedico = MedicoFiltrado;
|
||||||
|
console.log(unicoMedico)
|
||||||
|
const idMedicoFiltrado = unicoMedico.idMedico;
|
||||||
|
console.log(`Médico único encontrado: ${unicoMedico.nomeMedico}. ID: ${idMedicoFiltrado}`);
|
||||||
|
|
||||||
|
const agendamentosDoMedico = filtrarAgendamentosPorMedico(
|
||||||
|
DictAgendamentosOrganizados,
|
||||||
|
idMedicoFiltrado
|
||||||
|
);
|
||||||
|
console.log(`Total de agendamentos filtrados para este médico: ${agendamentosDoMedico.length}`);
|
||||||
|
console.log("Lista completa de Agendamentos do Médico:", agendamentosDoMedico);
|
||||||
|
|
||||||
|
//FiltrarAgendamentos(agendamentosDoMedico)
|
||||||
|
|
||||||
|
setListaTodosAgendamentos(agendamentosDoMedico)
|
||||||
|
|
||||||
|
}
|
||||||
|
}, [FiltredTodosMedicos, MedicoFiltrado]);
|
||||||
|
|
||||||
|
const filtrarAgendamentosPorMedico = (dictAgendamentos, idMedicoFiltrado) => {
|
||||||
|
setCacheAgendamentos(DictAgendamentosOrganizados);
|
||||||
|
|
||||||
|
const todasAsListasDeAgendamentos = Object.values(dictAgendamentos);
|
||||||
|
const todosOsAgendamentos = todasAsListasDeAgendamentos.flat();
|
||||||
|
|
||||||
|
const agendamentosFiltrados = todosOsAgendamentos.filter(agendamento =>
|
||||||
|
agendamento.doctor_id === idMedicoFiltrado
|
||||||
|
);
|
||||||
|
|
||||||
|
return agendamentosFiltrados;
|
||||||
|
};
|
||||||
|
|
||||||
const handleSearchMedicos = (term) => {
|
const handleSearchMedicos = (term) => {
|
||||||
setSearchTermDoctor(term);
|
setSearchTermDoctor(term);
|
||||||
if (term.trim() === '') {
|
if (term.trim() === '') {
|
||||||
|
if(MedicoFiltrado.id !== "vazio"){
|
||||||
|
console.log("Medico escolhido, mas vai ser apagado")
|
||||||
|
console.log(cacheAgendamentos, "cache ")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
setFiltredTodosMedicos([]);
|
setFiltredTodosMedicos([]);
|
||||||
|
setMedicoFiltrado({id:"vazio"})
|
||||||
|
|
||||||
|
//2 FiltrarAgendamentos()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (FiltredTodosMedicos.length === 1){
|
||||||
|
setMedicoFiltrado({...FiltredTodosMedicos[0]})
|
||||||
|
}
|
||||||
|
|
||||||
const filtered = ListaDeMedicos.filter(medico =>
|
const filtered = ListaDeMedicos.filter(medico =>
|
||||||
medico.nomeMedico.toLowerCase().includes(term.toLowerCase())
|
medico.nomeMedico.toLowerCase().includes(term.toLowerCase())
|
||||||
@ -252,10 +320,9 @@ const handleSearchMedicos = (term) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const handleClickCancel = () => setPageConsulta(false)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
<div className='spinner'></div>
|
||||||
<h1>Agendar nova consulta</h1>
|
<h1>Agendar nova consulta</h1>
|
||||||
|
|
||||||
|
|
||||||
@ -306,6 +373,9 @@ const handleSearchMedicos = (term) => {
|
|||||||
className='dropdown-item'
|
className='dropdown-item'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSearchTermDoctor(medico.nomeMedico);
|
setSearchTermDoctor(medico.nomeMedico);
|
||||||
|
setFiltredTodosMedicos([]);
|
||||||
|
setMedicoFiltrado(medico)
|
||||||
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<p>{medico.nomeMedico} </p>
|
<p>{medico.nomeMedico} </p>
|
||||||
@ -336,6 +406,7 @@ const handleSearchMedicos = (term) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Fila de espera
|
Fila de espera
|
||||||
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -365,9 +436,9 @@ const handleSearchMedicos = (term) => {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Componentes de Tabela - Adicionado props de delete da main */}
|
{/* Componentes de Tabela - Adicionado props de delete da main */}
|
||||||
{tabela === "diario" && <TabelaAgendamentoDia handleClickAgendamento={handleClickAgendamento} agendamentos={DictAgendamentosOrganizados} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo} />}
|
{tabela === "diario" && <TabelaAgendamentoDia handleClickAgendamento={handleClickAgendamento} agendamentos={DictAgendamentosOrganizados} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} selectedID={selectedID} setDictInfo={setDictInfo} setShowConfirmModal={setShowConfirmModal} coresConsultas={coresConsultas} setListaConsultaID={setListaConsultaID} listaConsultasID={listaConsultasID} />}
|
||||||
{tabela === 'semanal' && <TabelaAgendamentoSemana agendamentos={DictAgendamentosOrganizados} ListarDiasdoMes={ListarDiasdoMes} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo}/>}
|
{tabela === 'semanal' && <TabelaAgendamentoSemana agendamentos={DictAgendamentosOrganizados} ListarDiasdoMes={ListarDiasdoMes} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} selectedID={selectedID} setDictInfo={setDictInfo} setShowConfirmModal={setShowConfirmModal} coresConsultas={coresConsultas} setListaConsultaID={setListaConsultaID} listaConsultasID={listaConsultasID}/>}
|
||||||
{tabela === 'mensal' && <TabelaAgendamentoMes ListarDiasdoMes={ListarDiasdoMes} aplicarCores={true} agendamentos={DictAgendamentosOrganizados} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo} />}
|
{tabela === 'mensal' && <TabelaAgendamentoMes ListarDiasdoMes={ListarDiasdoMes} aplicarCores={true} agendamentos={DictAgendamentosOrganizados} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} selectedID={selectedID} setDictInfo={setDictInfo} setShowConfirmModal={setShowConfirmModal} coresConsultas={coresConsultas} setListaConsultaID={setListaConsultaID} listaConsultasID={listaConsultasID}/>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@ -455,7 +526,7 @@ const handleSearchMedicos = (term) => {
|
|||||||
|
|
||||||
<div className="modal-header bg-danger bg-opacity-25">
|
<div className="modal-header bg-danger bg-opacity-25">
|
||||||
<h5 className="modal-title text-danger">
|
<h5 className="modal-title text-danger">
|
||||||
Confirmação de Exclusão
|
Confirmação de Cancelamento
|
||||||
</h5>
|
</h5>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -466,8 +537,12 @@ const handleSearchMedicos = (term) => {
|
|||||||
|
|
||||||
<div className="modal-body">
|
<div className="modal-body">
|
||||||
<p className="mb-0 fs-5">
|
<p className="mb-0 fs-5">
|
||||||
Tem certeza que deseja excluir este agendamento?
|
Qual o motivo do cancelamento?
|
||||||
</p>
|
</p>
|
||||||
|
<div className='campo-de-input'>
|
||||||
|
|
||||||
|
<textarea className='input-modal' value={motivoCancelamento} onChange={(e) => setMotivoCancelamento(e.target.value)} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="modal-footer">
|
<div className="modal-footer">
|
||||||
@ -475,7 +550,9 @@ const handleSearchMedicos = (term) => {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-primary"
|
className="btn btn-primary"
|
||||||
onClick={() => setShowDeleteModal(false)}
|
onClick={() => {setShowDeleteModal(false);
|
||||||
|
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Cancelar
|
Cancelar
|
||||||
</button>
|
</button>
|
||||||
@ -484,7 +561,22 @@ const handleSearchMedicos = (term) => {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-danger"
|
className="btn btn-danger"
|
||||||
onClick={() => {deleteConsulta(selectedID);setShowDeleteModal(false)}}
|
onClick={() => {deleteConsulta(selectedID);
|
||||||
|
setShowDeleteModal(false)
|
||||||
|
let lista_cores = coresConsultas
|
||||||
|
|
||||||
|
let lista = listaConsultasID
|
||||||
|
|
||||||
|
lista.push(selectedID)
|
||||||
|
lista_cores.push("cancelled")
|
||||||
|
|
||||||
|
setCoresConsultas(lista_cores)
|
||||||
|
|
||||||
|
setListaConsultaID(lista)
|
||||||
|
|
||||||
|
console.log("lista", lista)
|
||||||
|
|
||||||
|
}}
|
||||||
|
|
||||||
>
|
>
|
||||||
<i className="bi bi-trash me-1"></i> Excluir
|
<i className="bi bi-trash me-1"></i> Excluir
|
||||||
@ -495,6 +587,79 @@ const handleSearchMedicos = (term) => {
|
|||||||
</div>)}
|
</div>)}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{showConfirmModal &&(
|
||||||
|
<div
|
||||||
|
className="modal fade show"
|
||||||
|
style={{
|
||||||
|
display: "block",
|
||||||
|
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
||||||
|
}}
|
||||||
|
tabIndex="-1"
|
||||||
|
onClick={(e) =>
|
||||||
|
e.target.classList.contains("modal") && setShowDeleteModal(false)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="modal-dialog modal-dialog-centered">
|
||||||
|
<div className="modal-content">
|
||||||
|
|
||||||
|
<div className="modal-header bg-success">
|
||||||
|
<h5 className="modal-title">
|
||||||
|
Confirmação de edição
|
||||||
|
</h5>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="modal-body">
|
||||||
|
<p className="mb-0 fs-5">
|
||||||
|
Tem certeza que deseja retirar o cancelamento ?
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="modal-footer">
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-primary"
|
||||||
|
onClick={() => {setShowConfirmModal(false); setSelectedId("")}}
|
||||||
|
>
|
||||||
|
Cancelar
|
||||||
|
</button>
|
||||||
|
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-success"
|
||||||
|
onClick={() => {confirmConsulta(selectedID);setShowConfirmModal(false)
|
||||||
|
let lista_cores = coresConsultas
|
||||||
|
|
||||||
|
let lista = listaConsultasID
|
||||||
|
|
||||||
|
lista.push(selectedID)
|
||||||
|
lista_cores.push("confirmed")
|
||||||
|
|
||||||
|
setCoresConsultas(lista_cores)
|
||||||
|
|
||||||
|
setListaConsultaID(lista)
|
||||||
|
|
||||||
|
|
||||||
|
}}
|
||||||
|
|
||||||
|
>
|
||||||
|
<i className="bi bi-trash me-1"></i> Confirmar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,6 @@ const AgendamentoEditPage = ({setDictInfo, DictInfo}) => {
|
|||||||
//let DataAtual = dayjs()
|
//let DataAtual = dayjs()
|
||||||
const {getAuthorizationHeader} = useAuth()
|
const {getAuthorizationHeader} = useAuth()
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
const [PatientToPatch, setPatientToPatch] = useState({})
|
|
||||||
|
|
||||||
let id = params.id
|
let id = params.id
|
||||||
|
|
||||||
@ -22,8 +21,7 @@ const AgendamentoEditPage = ({setDictInfo, DictInfo}) => {
|
|||||||
//console.log(DictInfo, 'aqui')
|
//console.log(DictInfo, 'aqui')
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setDictInfo({...DictInfo?.Infos,...DictInfo?.agendamento})
|
setDictInfo({...DictInfo, dataAtendimento:DictInfo.scheduled_at.split("T")[0]})
|
||||||
|
|
||||||
|
|
||||||
const ColherInfoUsuario =async () => {
|
const ColherInfoUsuario =async () => {
|
||||||
const result = await UserInfos(authHeader)
|
const result = await UserInfos(authHeader)
|
||||||
|
|||||||
@ -1,75 +1,68 @@
|
|||||||
import React, { useState, useEffect, useCallback } from "react";
|
import React, { useState, useEffect, useCallback } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import HorariosDisponibilidade from "../components/doctors/HorariosDisponibilidade";
|
||||||
|
const ENDPOINT =
|
||||||
const ENDPOINT_LISTAR = "https://mock.apidog.com/m1/1053378-0-default/rest/v1/doctor_availability";
|
"https://mock.apidog.com/m1/1053378-0-default/rest/v1/doctor_availability";
|
||||||
|
|
||||||
|
|
||||||
const MEDICOS_MOCKADOS = [
|
const MEDICOS_MOCKADOS = [
|
||||||
{ id: 53, nome: " João Silva" },
|
{ id: 53, nome: "João Silva" },
|
||||||
{ id: 19, nome: " Ana Costa" },
|
{ id: 19, nome: "Ana Costa" },
|
||||||
{ id: 11, nome: " Pedro Santos" },
|
{ id: 11, nome: "Pedro Santos" },
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
const diasDaSemana = ["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb"];
|
const diasDaSemana = ["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb"];
|
||||||
|
|
||||||
|
|
||||||
const formatarDataHora = (isoString) => {
|
const formatarDataHora = (isoString) => {
|
||||||
if (!isoString) return "N/A";
|
if (!isoString) return "N/A";
|
||||||
try {
|
try {
|
||||||
const data = new Date(isoString);
|
const data = new Date(isoString);
|
||||||
|
// Usa o toLocaleTimeString para extrair hora e minuto
|
||||||
return data.toLocaleTimeString("pt-BR", { hour: '2-digit', minute: '2-digit', timeZone: 'UTC' });
|
return data.toLocaleTimeString("pt-BR", {
|
||||||
} catch (error) {
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
timeZone: "UTC",
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
return "Data Inválida";
|
return "Data Inválida";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const DisponibilidadesDoctorPage = () => {
|
const DisponibilidadesDoctorPage = () => {
|
||||||
const [disponibilidades, setDisponibilidades] = useState([]);
|
const [disponibilidades, setDisponibilidades] = useState([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [filtroMedicoNome, setFiltroMedicoNome] = useState("");
|
const [filtroMedicoNome, setFiltroMedicoNome] = useState("");
|
||||||
|
const [gerenciarModo, setGerenciarModo] = useState(false);
|
||||||
const [medicoEncontradoId, setMedicoEncontradoId] = useState(null);
|
const [editando, setEditando] = useState(null); // ID da disponibilidade sendo editada
|
||||||
|
|
||||||
|
|
||||||
const encontrarMedicoIdPorNome = (nome) => {
|
const encontrarMedicoIdPorNome = (nome) => {
|
||||||
if (!nome) return null;
|
if (!nome) return null;
|
||||||
const termoBusca = nome.toLowerCase();
|
const termo = nome.toLowerCase();
|
||||||
|
const medico = MEDICOS_MOCKADOS.find((m) =>
|
||||||
|
m.nome.toLowerCase().includes(termo)
|
||||||
const medico = MEDICOS_MOCKADOS.find(m =>
|
|
||||||
m.nome.toLowerCase().includes(termoBusca)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return medico ? medico.id : null;
|
return medico ? medico.id : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchDisponibilidades = useCallback(async (nome) => {
|
const fetchDisponibilidades = useCallback(async (nome) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setDisponibilidades([]);
|
|
||||||
setMedicoEncontradoId(null);
|
|
||||||
|
|
||||||
const doctorId = encontrarMedicoIdPorNome(nome);
|
const doctorId = encontrarMedicoIdPorNome(nome);
|
||||||
|
|
||||||
if (!doctorId) {
|
if (!doctorId) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = `${ENDPOINT_LISTAR}?select=*&doctor_id=eq.${doctorId}`;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url);
|
const res = await fetch(`${ENDPOINT}?doctor_id=eq.${doctorId}`);
|
||||||
const result = await response.json();
|
const data = await res.json();
|
||||||
|
|
||||||
let dados = Array.isArray(result) ? result : [];
|
setDisponibilidades(
|
||||||
|
Array.isArray(data)
|
||||||
setDisponibilidades(dados);
|
? data
|
||||||
setMedicoEncontradoId(doctorId);
|
: data && Array.isArray(data.items)
|
||||||
} catch (error) {
|
? data.items
|
||||||
|
: []
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Erro ao buscar disponibilidades:", e);
|
||||||
setDisponibilidades([]);
|
setDisponibilidades([]);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@ -77,6 +70,13 @@ const DisponibilidadesDoctorPage = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!gerenciarModo && editando) {
|
||||||
|
setEditando(null);
|
||||||
|
}
|
||||||
|
}, [gerenciarModo]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (editando) return;
|
||||||
if (filtroMedicoNome) {
|
if (filtroMedicoNome) {
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
fetchDisponibilidades(filtroMedicoNome);
|
fetchDisponibilidades(filtroMedicoNome);
|
||||||
@ -85,93 +85,220 @@ const DisponibilidadesDoctorPage = () => {
|
|||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
} else {
|
} else {
|
||||||
setDisponibilidades([]);
|
setDisponibilidades([]);
|
||||||
setMedicoEncontradoId(null);
|
|
||||||
}
|
}
|
||||||
}, [filtroMedicoNome, fetchDisponibilidades]);
|
}, [filtroMedicoNome, fetchDisponibilidades, editando]);
|
||||||
|
|
||||||
const rotaGerenciar = medicoEncontradoId
|
const atualizarDisponibilidade = async (id, novoIntervalo) => {
|
||||||
? `../medicos/${medicoEncontradoId}/edit`
|
try {
|
||||||
: `../medicos/novo/edit`;
|
const res = await fetch(`${ENDPOINT}?id=eq.${id}`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ slot_minutes: novoIntervalo }),
|
||||||
|
});
|
||||||
|
if (res.ok) {
|
||||||
|
alert("Disponibilidade atualizada com sucesso!");
|
||||||
|
setEditando(null);
|
||||||
|
fetchDisponibilidades(filtroMedicoNome);
|
||||||
|
} else {
|
||||||
|
alert("Erro ao atualizar disponibilidade");
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
alert("Falha ao conectar com o servidor");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const deletarDisponibilidade = async (id) => {
|
||||||
|
if (!window.confirm("Deseja realmente excluir esta disponibilidade?"))
|
||||||
|
return;
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${ENDPOINT}?id=eq.${id}`, { method: "DELETE" });
|
||||||
|
if (res.ok) {
|
||||||
|
alert("Disponibilidade excluída!");
|
||||||
|
setDisponibilidades((prev) => prev.filter((d) => d.id !== id));
|
||||||
|
} else {
|
||||||
|
alert("Erro ao excluir disponibilidade");
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
alert("Erro ao conectar com o servidor");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const disponibilidadeParaEdicao = editando
|
||||||
|
? disponibilidades.find((d) => d.id === editando)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const initialAvailabilityParaEdicao = diasDaSemana.map((dia, weekdayIndex) => {
|
||||||
|
const blocosDoDia = disponibilidades
|
||||||
|
.filter(d => d.weekday === weekdayIndex)
|
||||||
|
.map(d => ({
|
||||||
|
id: d.id,
|
||||||
|
inicio: d.start_time
|
||||||
|
? new Date(d.start_time).toISOString().substring(11, 16)
|
||||||
|
: "07:00",
|
||||||
|
termino: d.end_time
|
||||||
|
? new Date(d.end_time).toISOString().substring(11, 16)
|
||||||
|
: "17:00",
|
||||||
|
isNew: false,
|
||||||
|
slot_minutes: d.slot_minutes,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return {
|
||||||
|
dia,
|
||||||
|
isChecked: blocosDoDia.length > 0,
|
||||||
|
blocos: blocosDoDia,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleUpdateHorarios = (horariosAtualizados) => {
|
||||||
|
console.log("Horários editados:", horariosAtualizados);
|
||||||
|
|
||||||
|
setEditando(null);
|
||||||
|
fetchDisponibilidades(filtroMedicoNome);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="main-content">
|
<div id="main-content">
|
||||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
{/* Cabeçalho */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<h1 style={{ fontSize: "1.5rem", fontWeight: "bold", color: "#333" }}>
|
<h1 style={{ fontSize: "1.5rem", fontWeight: "bold", color: "#333" }}>
|
||||||
Disponibilidades por Médico
|
Disponibilidades por Médico
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<Link
|
{/* Botão Voltar/Gerenciar */}
|
||||||
to={rotaGerenciar}
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
if (editando) {
|
||||||
|
setEditando(null); // Se está editando, volta para a tabela
|
||||||
|
} else {
|
||||||
|
setGerenciarModo((m) => !m); // Senão, alterna o modo de gerenciamento
|
||||||
|
}
|
||||||
|
}}
|
||||||
className="btn-primary"
|
className="btn-primary"
|
||||||
style={{
|
style={{
|
||||||
padding: "10px 20px",
|
padding: "10px 20px",
|
||||||
fontSize: "14px",
|
fontSize: "14px",
|
||||||
whiteSpace: "nowrap",
|
whiteSpace: "nowrap",
|
||||||
textDecoration: "none",
|
|
||||||
display: "inline-block",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
+ Gerenciar Disponibilidades
|
{editando
|
||||||
</Link>
|
? "← Voltar para Tabela"
|
||||||
|
: gerenciarModo
|
||||||
|
? "← Voltar"
|
||||||
|
: "+ Gerenciar Disponibilidades"}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Campo de busca - ESCONDIDO NO MODO DE EDIÇÃO */}
|
||||||
|
{!editando && (
|
||||||
<div className="atendimento-eprocura">
|
<div className="atendimento-eprocura">
|
||||||
<div className="busca-atendimento">
|
<div className="busca-atendimento">
|
||||||
<div style={{ marginRight: '10px' }}>
|
|
||||||
<i className="fa-solid fa-user-doctor"></i>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Filtrar por Nome do Médico..."
|
placeholder="Filtrar por Nome do Médico..."
|
||||||
value={filtroMedicoNome}
|
value={filtroMedicoNome}
|
||||||
onChange={(e) => setFiltroMedicoNome(e.target.value)}
|
onChange={(e) => setFiltroMedicoNome(e.target.value)}
|
||||||
style={{ border: "1px solid #ccc", borderRadius: "4px", padding: "5px" }}
|
style={{
|
||||||
|
border: "1px solid #ccc",
|
||||||
|
borderRadius: "4px",
|
||||||
|
padding: "5px",
|
||||||
|
marginTop: "10px",
|
||||||
|
marginBottom: "10px",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<section className="calendario-ou-filaespera">
|
<section className="calendario-ou-filaespera">
|
||||||
<div className="fila-container">
|
<div className="fila-container">
|
||||||
<h2 className="fila-titulo">
|
<h2 className="fila-titulo">
|
||||||
Disponibilidades Encontradas ({disponibilidades.length})
|
{editando
|
||||||
|
? "Editar Disponibilidade"
|
||||||
|
: gerenciarModo
|
||||||
|
? "Gerenciar Disponibilidades"
|
||||||
|
: "Disponibilidades Encontradas"}{" "}
|
||||||
|
({disponibilidades.length})
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<p className="text-center py-10">Carregando disponibilidades...</p>
|
<p>Carregando...</p>
|
||||||
) : (filtroMedicoNome && disponibilidades.length === 0) ? (
|
) : disponibilidades.length === 0 ? (
|
||||||
<p className="text-center py-10">
|
<p>Nenhuma disponibilidade encontrada.</p>
|
||||||
Nenhuma disponibilidade encontrada para o nome buscado.
|
) : editando ? (
|
||||||
</p>
|
<>
|
||||||
|
<HorariosDisponibilidade
|
||||||
|
initialAvailability={initialAvailabilityParaEdicao}
|
||||||
|
onUpdate={handleUpdateHorarios}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={() =>
|
||||||
|
handleUpdateHorarios(initialAvailabilityParaEdicao)
|
||||||
|
}
|
||||||
|
style={{
|
||||||
|
marginTop: "20px",
|
||||||
|
padding: "10px 20px",
|
||||||
|
fontSize: "16px",
|
||||||
|
fontWeight: "bold",
|
||||||
|
borderRadius: "8px",
|
||||||
|
backgroundColor: "#3b82f6",
|
||||||
|
color: "white",
|
||||||
|
border: "none",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Salvar Alterações
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<table className="fila-tabela" style={{ width: "100%", borderCollapse: "collapse" }}>
|
<table style={{ width: "100%", borderCollapse: "collapse" }}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
{[ "Dia da Semana", "Início", "Término", "Intervalo", "Tipo Consulta"].map(
|
<th>Dia da Semana</th>
|
||||||
(header) => (
|
<th>Início</th>
|
||||||
<th
|
<th>Término</th>
|
||||||
key={header}
|
<th>Intervalo</th>
|
||||||
style={{ padding: "10px", borderBottom: "2px solid #ddd", textAlign: "left" }}
|
<th>Tipo Consulta</th>
|
||||||
>
|
{gerenciarModo && <th>Ações</th>}
|
||||||
{header}
|
|
||||||
</th>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{disponibilidades.map((disp, index) => (
|
{disponibilidades.map((disp) => (
|
||||||
<tr key={disp.id || index} style={{ borderBottom: "1px solid #eee" }}>
|
<tr key={disp.id}>
|
||||||
<td style={{ padding: "10px", fontSize: "0.9em" }}>
|
<td>{diasDaSemana[disp.weekday]}</td>
|
||||||
{diasDaSemana[disp.weekday] || disp.weekday}
|
<td>{formatarDataHora(disp.start_time)}</td>
|
||||||
|
<td>{formatarDataHora(disp.end_time)}</td>
|
||||||
|
<td>{disp.slot_minutes}</td>
|
||||||
|
<td>{disp.appointment_type}</td>
|
||||||
|
{gerenciarModo && (
|
||||||
|
<td>
|
||||||
|
<button
|
||||||
|
onClick={() => setEditando(disp.id)}
|
||||||
|
style={{
|
||||||
|
backgroundColor: "#10b981",
|
||||||
|
color: "white",
|
||||||
|
borderRadius: "6px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Editar
|
||||||
|
</button>{" "}
|
||||||
|
<button
|
||||||
|
onClick={() => deletarDisponibilidade(disp.id)}
|
||||||
|
style={{
|
||||||
|
backgroundColor: "#c72f2f",
|
||||||
|
color: "white",
|
||||||
|
borderRadius: "6px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Excluir
|
||||||
|
</button>
|
||||||
</td>
|
</td>
|
||||||
<td style={{ padding: "10px", fontSize: "0.9em" }}>
|
)}
|
||||||
{formatarDataHora(disp.start_time)}
|
|
||||||
</td>
|
|
||||||
<td style={{ padding: "10px", fontSize: "0.9em" }}>
|
|
||||||
{formatarDataHora(disp.end_time)}
|
|
||||||
</td>
|
|
||||||
<td style={{ padding: "10px", fontSize: "0.9em" }}>{disp.slot_minutes}</td>
|
|
||||||
<td style={{ padding: "10px", fontSize: "0.9em" }}>{disp.appointment_type}</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -180,7 +307,6 @@ const DisponibilidadesDoctorPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,6 @@ function TableDoctor() {
|
|||||||
const [filtroEspecialidade, setFiltroEspecialidade] = useState("Todos");
|
const [filtroEspecialidade, setFiltroEspecialidade] = useState("Todos");
|
||||||
const [filtroAniversariante, setFiltroAniversariante] = useState(false);
|
const [filtroAniversariante, setFiltroAniversariante] = useState(false);
|
||||||
|
|
||||||
|
|
||||||
const [showFiltrosAvancados, setShowFiltrosAvancados] = useState(false);
|
const [showFiltrosAvancados, setShowFiltrosAvancados] = useState(false);
|
||||||
const [filtroCidade, setFiltroCidade] = useState("");
|
const [filtroCidade, setFiltroCidade] = useState("");
|
||||||
const [filtroEstado, setFiltroEstado] = useState("");
|
const [filtroEstado, setFiltroEstado] = useState("");
|
||||||
@ -22,6 +21,9 @@ function TableDoctor() {
|
|||||||
const [dataFinal, setDataFinal] = useState("");
|
const [dataFinal, setDataFinal] = useState("");
|
||||||
|
|
||||||
|
|
||||||
|
const [paginaAtual, setPaginaAtual] = useState(1);
|
||||||
|
const [itensPorPagina, setItensPorPagina] = useState(10);
|
||||||
|
|
||||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||||
const [selectedDoctorId, setSelectedDoctorId] = useState(null);
|
const [selectedDoctorId, setSelectedDoctorId] = useState(null);
|
||||||
|
|
||||||
@ -36,9 +38,9 @@ function TableDoctor() {
|
|||||||
setIdadeMaxima("");
|
setIdadeMaxima("");
|
||||||
setDataInicial("");
|
setDataInicial("");
|
||||||
setDataFinal("");
|
setDataFinal("");
|
||||||
|
setPaginaAtual(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const deleteDoctor = async (id) => {
|
const deleteDoctor = async (id) => {
|
||||||
const authHeader = getAuthorizationHeader()
|
const authHeader = getAuthorizationHeader()
|
||||||
console.log(id, 'teu id')
|
console.log(id, 'teu id')
|
||||||
@ -63,7 +65,6 @@ function TableDoctor() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const ehAniversariante = (dataNascimento) => {
|
const ehAniversariante = (dataNascimento) => {
|
||||||
if (!dataNascimento) return false;
|
if (!dataNascimento) return false;
|
||||||
const hoje = new Date();
|
const hoje = new Date();
|
||||||
@ -75,7 +76,6 @@ function TableDoctor() {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const calcularIdade = (dataNascimento) => {
|
const calcularIdade = (dataNascimento) => {
|
||||||
if (!dataNascimento) return 0;
|
if (!dataNascimento) return 0;
|
||||||
const hoje = new Date();
|
const hoje = new Date();
|
||||||
@ -104,18 +104,16 @@ function TableDoctor() {
|
|||||||
|
|
||||||
fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors", requestOptions)
|
fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors", requestOptions)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(result => {setMedicos(result); console.log(result)})
|
.then(result => setMedicos(result))
|
||||||
.catch(error => console.log('error', error));
|
.catch(error => console.log('error', error));
|
||||||
}, [isAuthenticated, getAuthorizationHeader]);
|
}, [isAuthenticated, getAuthorizationHeader]);
|
||||||
|
|
||||||
|
|
||||||
const medicosFiltrados = Array.isArray(medicos) ? medicos.filter((medico) => {
|
const medicosFiltrados = Array.isArray(medicos) ? medicos.filter((medico) => {
|
||||||
const buscaNome = medico.full_name?.toLowerCase().includes(search.toLowerCase());
|
const buscaNome = medico.full_name?.toLowerCase().includes(search.toLowerCase());
|
||||||
const buscaCPF = medico.cpf?.toLowerCase().includes(search.toLowerCase());
|
const buscaCPF = medico.cpf?.toLowerCase().includes(search.toLowerCase());
|
||||||
const buscaEmail = medico.email?.toLowerCase().includes(search.toLowerCase());
|
const buscaEmail = medico.email?.toLowerCase().includes(search.toLowerCase());
|
||||||
const passaBusca = search === "" || buscaNome || buscaCPF || buscaEmail;
|
const passaBusca = search === "" || buscaNome || buscaCPF || buscaEmail;
|
||||||
|
|
||||||
|
|
||||||
const passaEspecialidade = filtroEspecialidade === "Todos" || medico.specialty === filtroEspecialidade;
|
const passaEspecialidade = filtroEspecialidade === "Todos" || medico.specialty === filtroEspecialidade;
|
||||||
|
|
||||||
const passaAniversario = filtroAniversariante
|
const passaAniversario = filtroAniversariante
|
||||||
@ -132,23 +130,62 @@ function TableDoctor() {
|
|||||||
const passaIdadeMinima = idadeMinima ? idade >= parseInt(idadeMinima) : true;
|
const passaIdadeMinima = idadeMinima ? idade >= parseInt(idadeMinima) : true;
|
||||||
const passaIdadeMaxima = idadeMaxima ? idade <= parseInt(idadeMaxima) : true;
|
const passaIdadeMaxima = idadeMaxima ? idade <= parseInt(idadeMaxima) : true;
|
||||||
|
|
||||||
|
|
||||||
const passaDataInicial = dataInicial ?
|
const passaDataInicial = dataInicial ?
|
||||||
medico.created_at && new Date(medico.created_at) >= new Date(dataInicial) : true;
|
medico.created_at && new Date(medico.created_at) >= new Date(dataInicial) : true;
|
||||||
|
|
||||||
const passaDataFinal = dataFinal ?
|
const passaDataFinal = dataFinal ?
|
||||||
medico.created_at && new Date(medico.created_at) <= new Date(dataFinal) : true;
|
medico.created_at && new Date(medico.created_at) <= new Date(dataFinal) : true;
|
||||||
|
|
||||||
|
|
||||||
const resultado = passaBusca && passaEspecialidade && passaAniversario &&
|
const resultado = passaBusca && passaEspecialidade && passaAniversario &&
|
||||||
passaCidade && passaEstado && passaIdadeMinima && passaIdadeMaxima &&
|
passaCidade && passaEstado && passaIdadeMinima && passaIdadeMaxima &&
|
||||||
passaDataInicial && passaDataFinal;
|
passaDataInicial && passaDataFinal;
|
||||||
|
|
||||||
return resultado;
|
return resultado;
|
||||||
}) : [];
|
}) : [];
|
||||||
|
|
||||||
|
|
||||||
|
const totalPaginas = Math.ceil(medicosFiltrados.length / itensPorPagina);
|
||||||
|
const indiceInicial = (paginaAtual - 1) * itensPorPagina;
|
||||||
|
const indiceFinal = indiceInicial + itensPorPagina;
|
||||||
|
const medicosPaginados = medicosFiltrados.slice(indiceInicial, indiceFinal);
|
||||||
|
|
||||||
|
|
||||||
|
const irParaPagina = (pagina) => {
|
||||||
|
setPaginaAtual(pagina);
|
||||||
|
};
|
||||||
|
|
||||||
|
const avancarPagina = () => {
|
||||||
|
if (paginaAtual < totalPaginas) {
|
||||||
|
setPaginaAtual(paginaAtual + 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const voltarPagina = () => {
|
||||||
|
if (paginaAtual > 1) {
|
||||||
|
setPaginaAtual(paginaAtual - 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const gerarNumerosPaginas = () => {
|
||||||
|
const paginas = [];
|
||||||
|
const paginasParaMostrar = 5;
|
||||||
|
|
||||||
|
let inicio = Math.max(1, paginaAtual - Math.floor(paginasParaMostrar / 2));
|
||||||
|
let fim = Math.min(totalPaginas, inicio + paginasParaMostrar - 1);
|
||||||
|
|
||||||
|
inicio = Math.max(1, fim - paginasParaMostrar + 1);
|
||||||
|
|
||||||
|
for (let i = inicio; i <= fim; i++) {
|
||||||
|
paginas.push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return paginas;
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log(` Médicos totais: ${medicos.length}, Filtrados: ${medicosFiltrados.length}`);
|
setPaginaAtual(1);
|
||||||
}, [medicos, medicosFiltrados, search]);
|
}, [search, filtroEspecialidade, filtroAniversariante, filtroCidade, filtroEstado, idadeMinima, idadeMaxima, dataInicial, dataFinal]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -169,7 +206,6 @@ function TableDoctor() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
|
|
||||||
<div className="card p-3 mb-3 table-doctor-filters">
|
<div className="card p-3 mb-3 table-doctor-filters">
|
||||||
<h5 className="mb-3">
|
<h5 className="mb-3">
|
||||||
<i className="bi bi-funnel-fill me-2 text-primary"></i>{" "}
|
<i className="bi bi-funnel-fill me-2 text-primary"></i>{" "}
|
||||||
@ -180,16 +216,15 @@ function TableDoctor() {
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className="form-control"
|
className="form-control"
|
||||||
placeholder="Buscar por nome ou CPF..."
|
placeholder="Buscar por nome, CPF ou email..."
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<small className="text-muted">
|
<small className="text-muted">
|
||||||
Digite o nome completo ou número do CPF
|
Digite o nome completo, CPF ou email
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div className="filtros-basicos">
|
<div className="filtros-basicos">
|
||||||
<select
|
<select
|
||||||
className="form-select filter-especialidade"
|
className="form-select filter-especialidade"
|
||||||
@ -213,7 +248,6 @@ function TableDoctor() {
|
|||||||
</select>
|
</select>
|
||||||
|
|
||||||
<div className="filter-buttons-container">
|
<div className="filter-buttons-container">
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className={`btn filter-btn ${filtroAniversariante
|
className={`btn filter-btn ${filtroAniversariante
|
||||||
? "btn-primary"
|
? "btn-primary"
|
||||||
@ -243,13 +277,11 @@ function TableDoctor() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{showFiltrosAvancados && (
|
{showFiltrosAvancados && (
|
||||||
<div className="mt-3 p-3 border rounded advanced-filters">
|
<div className="mt-3 p-3 border rounded advanced-filters">
|
||||||
<h6 className="mb-3">Filtros Avançados</h6>
|
<h6 className="mb-3">Filtros Avançados</h6>
|
||||||
|
|
||||||
<div className="row g-3">
|
<div className="row g-3">
|
||||||
|
|
||||||
<div className="col-md-6">
|
<div className="col-md-6">
|
||||||
<label className="form-label fw-bold">Cidade</label>
|
<label className="form-label fw-bold">Cidade</label>
|
||||||
<input
|
<input
|
||||||
@ -296,7 +328,6 @@ function TableDoctor() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Data de Cadastro */}
|
|
||||||
<div className="col-md-6">
|
<div className="col-md-6">
|
||||||
<label className="form-label fw-bold">Data inicial</label>
|
<label className="form-label fw-bold">Data inicial</label>
|
||||||
<input
|
<input
|
||||||
@ -318,17 +349,21 @@ function TableDoctor() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<div className="mt-3">
|
||||||
|
<div className="contador-medicos">
|
||||||
|
{medicosFiltrados.length} DE {medicos.length} MÉDICOS ENCONTRADOS
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{(search || filtroEspecialidade !== "Todos" || filtroAniversariante ||
|
||||||
{(search || filtroEspecialidade !== "Todos" || filtroAniversariante || // filtroVIP removido
|
|
||||||
filtroCidade || filtroEstado || idadeMinima || idadeMaxima || dataInicial || dataFinal) && (
|
filtroCidade || filtroEstado || idadeMinima || idadeMaxima || dataInicial || dataFinal) && (
|
||||||
<div className="alert alert-info mb-3 filters-active">
|
<div className="alert alert-info mb-3 filters-active">
|
||||||
<strong>Filtros ativos:</strong>
|
<strong>Filtros ativos:</strong>
|
||||||
<div className="mt-1">
|
<div className="mt-1">
|
||||||
{search && <span className="badge bg-primary me-2">Busca: "{search}"</span>}
|
{search && <span className="badge bg-primary me-2">Busca: "{search}"</span>}
|
||||||
{filtroEspecialidade !== "Todos" && <span className="badge bg-primary me-2">Especialidade: {filtroEspecialidade}</span>}
|
{filtroEspecialidade !== "Todos" && <span className="badge bg-primary me-2">Especialidade: {filtroEspecialidade}</span>}
|
||||||
|
|
||||||
{filtroAniversariante && <span className="badge bg-primary me-2">Aniversariantes</span>}
|
{filtroAniversariante && <span className="badge bg-primary me-2">Aniversariantes</span>}
|
||||||
{filtroCidade && <span className="badge bg-primary me-2">Cidade: {filtroCidade}</span>}
|
{filtroCidade && <span className="badge bg-primary me-2">Cidade: {filtroCidade}</span>}
|
||||||
{filtroEstado && <span className="badge bg-primary me-2">Estado: {filtroEstado}</span>}
|
{filtroEstado && <span className="badge bg-primary me-2">Estado: {filtroEstado}</span>}
|
||||||
@ -340,14 +375,6 @@ function TableDoctor() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
<div className="mb-3">
|
|
||||||
<span className="badge results-badge">
|
|
||||||
{medicosFiltrados.length} de {medicos.length} médicos encontrados
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div className="table-responsive">
|
<div className="table-responsive">
|
||||||
<table className="table table-striped table-hover table-doctor-table">
|
<table className="table table-striped table-hover table-doctor-table">
|
||||||
<thead>
|
<thead>
|
||||||
@ -360,8 +387,8 @@ function TableDoctor() {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{medicosFiltrados.length > 0 ? (
|
{medicosPaginados.length > 0 ? (
|
||||||
medicosFiltrados.map((medico) => (
|
medicosPaginados.map((medico) => (
|
||||||
<tr key={medico.id}>
|
<tr key={medico.id}>
|
||||||
<td>
|
<td>
|
||||||
<div className="d-flex align-items-center">
|
<div className="d-flex align-items-center">
|
||||||
@ -371,7 +398,6 @@ function TableDoctor() {
|
|||||||
<i className="bi bi-gift"></i>
|
<i className="bi bi-gift"></i>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>{medico.cpf}</td>
|
<td>{medico.cpf}</td>
|
||||||
@ -410,13 +436,75 @@ function TableDoctor() {
|
|||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan="5" className="empty-state">
|
<td colSpan="5" className="text-center py-4">
|
||||||
Nenhum médico encontrado.
|
<div className="text-muted">
|
||||||
|
<i className="bi bi-search display-4"></i>
|
||||||
|
<p className="mt-2">Nenhum médico encontrado com os filtros aplicados.</p>
|
||||||
|
{(search || filtroEspecialidade !== "Todos" || filtroAniversariante ||
|
||||||
|
filtroCidade || filtroEstado || idadeMinima || idadeMaxima || dataInicial || dataFinal) && (
|
||||||
|
<button className="btn btn-outline-primary btn-sm mt-2" onClick={limparFiltros}>
|
||||||
|
Limpar filtros
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
{/* Paginação */}
|
||||||
|
{medicosFiltrados.length > 0 && (
|
||||||
|
<div className="d-flex justify-content-between align-items-center mt-3">
|
||||||
|
<div className="d-flex align-items-center">
|
||||||
|
<span className="me-2 text-muted">Itens por página:</span>
|
||||||
|
<select
|
||||||
|
className="form-select form-select-sm w-auto"
|
||||||
|
value={itensPorPagina}
|
||||||
|
onChange={(e) => {
|
||||||
|
setItensPorPagina(Number(e.target.value));
|
||||||
|
setPaginaAtual(1);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value={5}>5</option>
|
||||||
|
<option value={10}>10</option>
|
||||||
|
<option value={25}>25</option>
|
||||||
|
<option value={50}>50</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="d-flex align-items-center">
|
||||||
|
<span className="me-3 text-muted">
|
||||||
|
Página {paginaAtual} de {totalPaginas} •
|
||||||
|
Mostrando {indiceInicial + 1}-{Math.min(indiceFinal, medicosFiltrados.length)} de {medicosFiltrados.length} médicos
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<nav>
|
||||||
|
<ul className="pagination pagination-sm mb-0">
|
||||||
|
<li className={`page-item ${paginaAtual === 1 ? 'disabled' : ''}`}>
|
||||||
|
<button className="page-link" onClick={voltarPagina}>
|
||||||
|
<i className="bi bi-chevron-left"></i>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
{gerarNumerosPaginas().map(pagina => (
|
||||||
|
<li key={pagina} className={`page-item ${pagina === paginaAtual ? 'active' : ''}`}>
|
||||||
|
<button className="page-link" onClick={() => irParaPagina(pagina)}>
|
||||||
|
{pagina}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<li className={`page-item ${paginaAtual === totalPaginas ? 'disabled' : ''}`}>
|
||||||
|
<button className="page-link" onClick={avancarPagina}>
|
||||||
|
<i className="bi bi-chevron-right"></i>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
import React, { useState, useEffect, use } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
import { useAuth } from "../components/utils/AuthProvider";
|
import { useAuth } from "../components/utils/AuthProvider";
|
||||||
import API_KEY from "../components/utils/apiKeys";
|
import API_KEY from "../components/utils/apiKeys";
|
||||||
import { UserInfos } from "../components/utils/Functions-Endpoints/General";
|
import { UserInfos } from "../components/utils/Functions-Endpoints/General";
|
||||||
|
import CabecalhoError from "../components/utils/fetchErros/CabecalhoError";
|
||||||
|
|
||||||
function Login({ onEnterSystem }) {
|
function Login({ onEnterSystem }) {
|
||||||
const { setAuthTokens } = useAuth();
|
const { setAuthTokens } = useAuth();
|
||||||
|
const [showCabecalho, setShowCabecalho ] = useState(false)
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [form, setForm] = useState({
|
const [form, setForm] = useState({
|
||||||
username: "",
|
username: "",
|
||||||
@ -126,6 +128,9 @@ function Login({ onEnterSystem }) {
|
|||||||
} else if (UserData?.roles?.includes("financeiro")) {
|
} else if (UserData?.roles?.includes("financeiro")) {
|
||||||
navigate(`/financeiro/`);
|
navigate(`/financeiro/`);
|
||||||
}
|
}
|
||||||
|
}else{
|
||||||
|
console.log("ERROROROROROOR")
|
||||||
|
setShowCabecalho(true)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setAlert("Preencha todos os campos!");
|
setAlert("Preencha todos os campos!");
|
||||||
@ -148,11 +153,7 @@ function Login({ onEnterSystem }) {
|
|||||||
<p className="auth-subtitle mb-5">
|
<p className="auth-subtitle mb-5">
|
||||||
Entre com os dados que você inseriu durante o registro.
|
Entre com os dados que você inseriu durante o registro.
|
||||||
</p>
|
</p>
|
||||||
{alert && (
|
<CabecalhoError showCabecalho={showCabecalho} message={"E-mail ou senha incorretos."}/>
|
||||||
<div className="alert alert-info" role="alert">
|
|
||||||
{alert}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<form onSubmit={handleLogin}>
|
<form onSubmit={handleLogin}>
|
||||||
<div className="form-group position-relative has-icon-left mb-4">
|
<div className="form-group position-relative has-icon-left mb-4">
|
||||||
<input
|
<input
|
||||||
|
|||||||
@ -1,38 +1,186 @@
|
|||||||
// src/pages/ProfilePage.jsx
|
import React, { useState, useEffect, useCallback } from "react";
|
||||||
import React, { useState } from "react";
|
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
import "./style/ProfilePage.css";
|
import "./style/ProfilePage.css";
|
||||||
|
|
||||||
const simulatedUserData = {
|
|
||||||
email: "admin@squad23.com",
|
const MOCK_API_BASE_URL = "https://mock.apidog.com/m1/1053378-0-default";
|
||||||
role: "Administrador",
|
|
||||||
|
|
||||||
|
const getLocalAvatar = () => localStorage.getItem('user_avatar');
|
||||||
|
const setLocalAvatar = (avatarData) => localStorage.setItem('user_avatar', avatarData);
|
||||||
|
const clearLocalAvatar = () => localStorage.removeItem('user_avatar');
|
||||||
|
|
||||||
|
const ROLES = {
|
||||||
|
ADMIN: "Administrador",
|
||||||
|
SECRETARY: "Secretária",
|
||||||
|
DOCTOR: "Médico",
|
||||||
|
FINANCIAL: "Financeiro"
|
||||||
};
|
};
|
||||||
|
|
||||||
const ProfilePage = () => {
|
const ProfilePage = () => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const getRoleFromPath = () => {
|
const getRoleFromPath = useCallback(() => {
|
||||||
const path = location.pathname;
|
const path = location.pathname;
|
||||||
if (path.includes("/admin")) return "Administrador";
|
if (path.includes("/admin")) return ROLES.ADMIN;
|
||||||
if (path.includes("/secretaria")) return "Secretária";
|
if (path.includes("/secretaria")) return ROLES.SECRETARY;
|
||||||
if (path.includes("/medico")) return "Médico";
|
if (path.includes("/medico")) return ROLES.DOCTOR;
|
||||||
if (path.includes("/financeiro")) return "Financeiro";
|
if (path.includes("/financeiro")) return ROLES.FINANCIAL;
|
||||||
return "Usuário Padrão";
|
return "Usuário";
|
||||||
};
|
}, [location.pathname]);
|
||||||
|
|
||||||
const userRole = simulatedUserData.role || getRoleFromPath();
|
const userRole = getRoleFromPath();
|
||||||
const userEmail = simulatedUserData.email || "email.nao.encontrado@example.com";
|
|
||||||
|
|
||||||
const [userName, setUserName] = useState("Admin Padrão");
|
const [userName, setUserName] = useState("Admin Padrão");
|
||||||
|
const [userEmail, setUserEmail] = useState("admin@squad23.com");
|
||||||
|
const [avatarUrl, setAvatarUrl] = useState(null);
|
||||||
const [isEditingName, setIsEditingName] = useState(false);
|
const [isEditingName, setIsEditingName] = useState(false);
|
||||||
|
const [isUploading, setIsUploading] = useState(false);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
const handleNameKeyDown = (e) => {
|
|
||||||
if (e.key === "Enter") setIsEditingName(false);
|
useEffect(() => {
|
||||||
|
const handleEscKey = (event) => {
|
||||||
|
if (event.keyCode === 27) handleClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('keydown', handleEscKey);
|
||||||
|
return () => document.removeEventListener('keydown', handleEscKey);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadProfileData = () => {
|
||||||
|
|
||||||
|
const localAvatar = getLocalAvatar();
|
||||||
|
if (localAvatar) {
|
||||||
|
setAvatarUrl(localAvatar);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadProfileData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleNameSave = () => {
|
||||||
|
if (userName.trim() === "") {
|
||||||
|
setError("Nome não pode estar vazio");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsEditingName(false);
|
||||||
|
setError(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNameKeyDown = (event) => {
|
||||||
|
if (event.key === "Enter") handleNameSave();
|
||||||
|
if (event.key === "Escape") {
|
||||||
|
setUserName("Admin Padrão");
|
||||||
|
setIsEditingName(false);
|
||||||
|
setError(null);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClose = () => navigate(-1);
|
const handleClose = () => navigate(-1);
|
||||||
|
|
||||||
|
|
||||||
|
const handleAvatarUpload = async (event) => {
|
||||||
|
const file = event.target.files[0];
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
|
||||||
|
const MAX_FILE_SIZE = 5 * 1024 * 1024;
|
||||||
|
const ACCEPTED_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
|
||||||
|
|
||||||
|
if (file.size > MAX_FILE_SIZE) {
|
||||||
|
setError("Arquivo muito grande. Máximo 5MB.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ACCEPTED_TYPES.includes(file.type)) {
|
||||||
|
setError("Tipo de arquivo não suportado. Use JPEG, PNG, GIF ou WebP.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsUploading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await uploadAvatarToMockAPI(file);
|
||||||
|
|
||||||
|
const newAvatarUrl = result.url || result.avatarUrl;
|
||||||
|
if (newAvatarUrl) {
|
||||||
|
setAvatarUrl(newAvatarUrl);
|
||||||
|
setLocalAvatar(newAvatarUrl);
|
||||||
|
console.log('Avatar enviado para API com sucesso');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (apiError) {
|
||||||
|
|
||||||
|
console.log('API não disponível, salvando localmente...');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (e) => {
|
||||||
|
const imageDataUrl = e.target.result;
|
||||||
|
setLocalAvatar(imageDataUrl);
|
||||||
|
setAvatarUrl(imageDataUrl);
|
||||||
|
console.log('Avatar salvo localmente');
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
console.error('Erro no processamento:', error);
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (e) => {
|
||||||
|
const imageDataUrl = e.target.result;
|
||||||
|
setLocalAvatar(imageDataUrl);
|
||||||
|
setAvatarUrl(imageDataUrl);
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
} finally {
|
||||||
|
setIsUploading(false);
|
||||||
|
event.target.value = '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const uploadAvatarToMockAPI = async (file) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("avatar", file);
|
||||||
|
|
||||||
|
const response = await fetch(`${MOCK_API_BASE_URL}/storage/v1/object/avatars/[path]`, {
|
||||||
|
method: "POST",
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const clearAvatar = () => {
|
||||||
|
|
||||||
|
fetch(`${MOCK_API_BASE_URL}/storage/v1/object/avatars/[path]`, {
|
||||||
|
method: "DELETE"
|
||||||
|
}).catch(() => {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
clearLocalAvatar();
|
||||||
|
setAvatarUrl(null);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="profile-overlay" role="dialog" aria-modal="true">
|
<div className="profile-overlay" role="dialog" aria-modal="true">
|
||||||
<div className="profile-modal">
|
<div className="profile-modal">
|
||||||
@ -47,54 +195,108 @@ const ProfilePage = () => {
|
|||||||
<div className="profile-content">
|
<div className="profile-content">
|
||||||
<div className="profile-left">
|
<div className="profile-left">
|
||||||
<div className="avatar-wrapper">
|
<div className="avatar-wrapper">
|
||||||
<div className="avatar-square" />
|
<div className="avatar-square">
|
||||||
<button
|
{avatarUrl ? (
|
||||||
className="avatar-edit-btn"
|
<img
|
||||||
title="Editar foto"
|
src={avatarUrl}
|
||||||
aria-label="Editar foto"
|
alt="Avatar do usuário"
|
||||||
type="button"
|
className="avatar-img"
|
||||||
|
onError={() => {
|
||||||
|
setAvatarUrl(null);
|
||||||
|
clearLocalAvatar();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="avatar-placeholder">
|
||||||
|
{userName.split(' ').map(n => n[0]).join('').toUpperCase()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label
|
||||||
|
className={`avatar-edit-btn ${isUploading ? 'uploading' : ''}`}
|
||||||
|
title="Alterar foto de perfil"
|
||||||
>
|
>
|
||||||
✏️
|
{isUploading ? 'Enviando...' : 'Alterar Foto'}
|
||||||
</button>
|
<input
|
||||||
|
type="file"
|
||||||
|
accept="image/*"
|
||||||
|
onChange={handleAvatarUpload}
|
||||||
|
disabled={isUploading}
|
||||||
|
style={{ display: "none" }}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
{isUploading && (
|
||||||
|
<p className="upload-status">
|
||||||
|
Processando imagem...
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="profile-right">
|
<div className="profile-right">
|
||||||
<div className="profile-name-row">
|
<div className="profile-name-row">
|
||||||
{isEditingName ? (
|
{isEditingName ? (
|
||||||
|
<div className="name-edit-wrapper">
|
||||||
<input
|
<input
|
||||||
className="profile-name-input"
|
className="profile-name-input"
|
||||||
value={userName}
|
value={userName}
|
||||||
onChange={(e) => setUserName(e.target.value)}
|
onChange={(e) => setUserName(e.target.value)}
|
||||||
onBlur={() => setIsEditingName(false)}
|
onBlur={handleNameSave}
|
||||||
onKeyDown={handleNameKeyDown}
|
onKeyDown={handleNameKeyDown}
|
||||||
autoFocus
|
autoFocus
|
||||||
|
maxLength={50}
|
||||||
/>
|
/>
|
||||||
|
<div className="name-edit-hint">
|
||||||
|
Pressione Enter para salvar, ESC para cancelar
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<h2 className="profile-username">{userName}</h2>
|
<h2 className="profile-username">
|
||||||
|
{userName}
|
||||||
|
</h2>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className="profile-edit-inline"
|
className="profile-edit-inline"
|
||||||
onClick={() => setIsEditingName(!isEditingName)}
|
onClick={() => setIsEditingName(!isEditingName)}
|
||||||
aria-label="Editar nome"
|
|
||||||
type="button"
|
type="button"
|
||||||
|
aria-label={isEditingName ? 'Cancelar edição' : 'Editar nome'}
|
||||||
>
|
>
|
||||||
✏️
|
{isEditingName ? 'Cancelar' : 'Editar'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<div className="error-message">
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="profile-info">
|
||||||
<p className="profile-email">
|
<p className="profile-email">
|
||||||
Email: <strong>{userEmail}</strong>
|
<span>Email:</span>
|
||||||
|
<strong>{userEmail}</strong>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p className="profile-role">
|
<p className="profile-role">
|
||||||
Cargo: <strong>{userRole}</strong>
|
<span>Cargo:</span>
|
||||||
|
<strong>{userRole}</strong>
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="profile-actions-row">
|
<div className="profile-actions">
|
||||||
<button className="btn btn-close" onClick={handleClose}>
|
{avatarUrl && (
|
||||||
Fechar
|
<button onClick={clearAvatar} className="btn btn-clear">
|
||||||
|
Remover Avatar
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
className="btn btn-close"
|
||||||
|
onClick={handleClose}
|
||||||
|
>
|
||||||
|
Fechar Perfil
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -4,9 +4,11 @@ import API_KEY from "../components/utils/apiKeys";
|
|||||||
import { useAuth } from "../components/utils/AuthProvider";
|
import { useAuth } from "../components/utils/AuthProvider";
|
||||||
import "./style/TablePaciente.css";
|
import "./style/TablePaciente.css";
|
||||||
import ModalErro from "../components/utils/fetchErros/ModalErro";
|
import ModalErro from "../components/utils/fetchErros/ModalErro";
|
||||||
|
import manager from "../components/utils/fetchErros/ManagerFunction";
|
||||||
|
|
||||||
function TablePaciente({ setCurrentPage, setPatientID }) {
|
function TablePaciente({ setCurrentPage, setPatientID }) {
|
||||||
|
|
||||||
const { getAuthorizationHeader, isAuthenticated, RefreshingToken } = useAuth();
|
const { getAuthorizationHeader, isAuthenticated } = useAuth();
|
||||||
|
|
||||||
const [pacientes, setPacientes] = useState([]);
|
const [pacientes, setPacientes] = useState([]);
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
@ -21,12 +23,16 @@ function TablePaciente({ setCurrentPage, setPatientID }) {
|
|||||||
const [dataInicial, setDataInicial] = useState("");
|
const [dataInicial, setDataInicial] = useState("");
|
||||||
const [dataFinal, setDataFinal] = useState("");
|
const [dataFinal, setDataFinal] = useState("");
|
||||||
|
|
||||||
|
|
||||||
|
const [paginaAtual, setPaginaAtual] = useState(1);
|
||||||
|
const [itensPorPagina, setItensPorPagina] = useState(10);
|
||||||
|
|
||||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||||
const [selectedPatientId, setSelectedPatientId] = useState(null);
|
const [selectedPatientId, setSelectedPatientId] = useState(null);
|
||||||
|
|
||||||
const [showModalError, setShowModalError] = useState(false);
|
const [showModalError, setShowModalError] = useState("");
|
||||||
|
|
||||||
const [ ErrorInfo, setErrorInfo] = useState({})
|
const [ErrorInfo, setErrorInfo] = useState({})
|
||||||
|
|
||||||
const GetAnexos = async (id) => {
|
const GetAnexos = async (id) => {
|
||||||
var myHeaders = new Headers();
|
var myHeaders = new Headers();
|
||||||
@ -103,7 +109,15 @@ function TablePaciente({ setCurrentPage, setPatientID }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Função para refresh token (adicionada)
|
||||||
|
const RefreshingToken = () => {
|
||||||
|
console.log("Refreshing token...");
|
||||||
|
// Aqui você pode adicionar a lógica de refresh do token se necessário
|
||||||
|
// Por enquanto é apenas um placeholder para evitar o erro
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
||||||
const authHeader = getAuthorizationHeader()
|
const authHeader = getAuthorizationHeader()
|
||||||
|
|
||||||
console.log(authHeader, 'aqui autorização')
|
console.log(authHeader, 'aqui autorização')
|
||||||
@ -120,7 +134,7 @@ function TablePaciente({ setCurrentPage, setPatientID }) {
|
|||||||
fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/patients", requestOptions)
|
fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/patients", requestOptions)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
|
|
||||||
// 1. VERIFICAÇÃO DO STATUS HTTP (Se não for 2xx)
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
||||||
return response.json().then(errorData => {
|
return response.json().then(errorData => {
|
||||||
@ -136,27 +150,23 @@ function TablePaciente({ setCurrentPage, setPatientID }) {
|
|||||||
console.error("ERRO DETALHADO:", errorObject);
|
console.error("ERRO DETALHADO:", errorObject);
|
||||||
throw errorObject;
|
throw errorObject;
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Se a resposta for OK (2xx), processamos o JSON normalmente
|
|
||||||
return response.json();
|
return response.json();
|
||||||
})
|
})
|
||||||
.then(result => {
|
.then(result => {
|
||||||
// 4. Bloco de SUCESSO
|
|
||||||
setPacientes(result);
|
setPacientes(result);
|
||||||
console.log("Sucesso:", result);
|
console.log("Sucesso:", result);
|
||||||
|
|
||||||
// IMPORTANTE: Se o modal estava aberto, feche-o no sucesso
|
|
||||||
setShowModalError(false);
|
setShowModalError(false);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
// 5. Bloco de ERRO (Captura erros de rede ou o erro lançado pelo 'throw')
|
console.error(error, "deu erro")
|
||||||
//console.error('Falha na requisição:', error.message);
|
manager(setShowModalError, RefreshingToken, setErrorInfo, error)
|
||||||
if(error.httpStatus === 401){
|
|
||||||
RefreshingToken()
|
|
||||||
}
|
|
||||||
setErrorInfo(error)
|
|
||||||
setShowModalError(true);
|
|
||||||
});
|
});
|
||||||
}, [isAuthenticated, getAuthorizationHeader]);
|
}, [isAuthenticated, getAuthorizationHeader]);
|
||||||
|
|
||||||
@ -195,6 +205,7 @@ function TablePaciente({ setCurrentPage, setPatientID }) {
|
|||||||
setIdadeMaxima("");
|
setIdadeMaxima("");
|
||||||
setDataInicial("");
|
setDataInicial("");
|
||||||
setDataFinal("");
|
setDataFinal("");
|
||||||
|
setPaginaAtual(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
const pacientesFiltrados = Array.isArray(pacientes) ? pacientes.filter((paciente) => {
|
const pacientesFiltrados = Array.isArray(pacientes) ? pacientes.filter((paciente) => {
|
||||||
@ -238,13 +249,53 @@ function TablePaciente({ setCurrentPage, setPatientID }) {
|
|||||||
return resultado;
|
return resultado;
|
||||||
}) : [];
|
}) : [];
|
||||||
|
|
||||||
|
|
||||||
|
const totalPaginas = Math.ceil(pacientesFiltrados.length / itensPorPagina);
|
||||||
|
const indiceInicial = (paginaAtual - 1) * itensPorPagina;
|
||||||
|
const indiceFinal = indiceInicial + itensPorPagina;
|
||||||
|
const pacientesPaginados = pacientesFiltrados.slice(indiceInicial, indiceFinal);
|
||||||
|
|
||||||
|
|
||||||
|
const irParaPagina = (pagina) => {
|
||||||
|
setPaginaAtual(pagina);
|
||||||
|
};
|
||||||
|
|
||||||
|
const avancarPagina = () => {
|
||||||
|
if (paginaAtual < totalPaginas) {
|
||||||
|
setPaginaAtual(paginaAtual + 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const voltarPagina = () => {
|
||||||
|
if (paginaAtual > 1) {
|
||||||
|
setPaginaAtual(paginaAtual - 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const gerarNumerosPaginas = () => {
|
||||||
|
const paginas = [];
|
||||||
|
const paginasParaMostrar = 5;
|
||||||
|
|
||||||
|
let inicio = Math.max(1, paginaAtual - Math.floor(paginasParaMostrar / 2));
|
||||||
|
let fim = Math.min(totalPaginas, inicio + paginasParaMostrar - 1);
|
||||||
|
|
||||||
|
inicio = Math.max(1, fim - paginasParaMostrar + 1);
|
||||||
|
|
||||||
|
for (let i = inicio; i <= fim; i++) {
|
||||||
|
paginas.push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return paginas;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log(` Pacientes totais: ${pacientes?.length}, Filtrados: ${pacientesFiltrados?.length}`);
|
setPaginaAtual(1);
|
||||||
}, [pacientes, pacientesFiltrados, search]);
|
}, [search, filtroConvenio, filtroVIP, filtroAniversariante, filtroCidade, filtroEstado, idadeMinima, idadeMaxima, dataInicial, dataFinal]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ModalErro showModal={showModalError} setShowModal={setShowModalError} ErrorData={ErrorInfo}/>
|
|
||||||
<div className="page-heading">
|
<div className="page-heading">
|
||||||
<h3>Lista de Pacientes</h3>
|
<h3>Lista de Pacientes</h3>
|
||||||
</div>
|
</div>
|
||||||
@ -296,6 +347,7 @@ function TablePaciente({ setCurrentPage, setPatientID }) {
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
className={`btn btn-sm ${filtroVIP ? "btn-primary" : "btn-outline-primary"}`}
|
className={`btn btn-sm ${filtroVIP ? "btn-primary" : "btn-outline-primary"}`}
|
||||||
|
onClick={() => setFiltroVIP(!filtroVIP)}
|
||||||
style={{ padding: "0.25rem 0.5rem" }}
|
style={{ padding: "0.25rem 0.5rem" }}
|
||||||
>
|
>
|
||||||
<i className="bi bi-award me-1"></i> VIP
|
<i className="bi bi-award me-1"></i> VIP
|
||||||
@ -400,6 +452,12 @@ function TablePaciente({ setCurrentPage, setPatientID }) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<div className="mt-3">
|
||||||
|
<div className="contador-pacientes">
|
||||||
|
{pacientesFiltrados.length} DE {pacientes.length} PACIENTES ENCONTRADOS
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{(search || filtroConvenio !== "Todos" || filtroVIP || filtroAniversariante ||
|
{(search || filtroConvenio !== "Todos" || filtroVIP || filtroAniversariante ||
|
||||||
@ -421,12 +479,6 @@ function TablePaciente({ setCurrentPage, setPatientID }) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="mb-3">
|
|
||||||
<span className="badge results-badge">
|
|
||||||
{pacientesFiltrados?.length} de {pacientes?.length} pacientes encontrados
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="table-responsive">
|
<div className="table-responsive">
|
||||||
<table className="table table-striped table-hover table-paciente-table">
|
<table className="table table-striped table-hover table-paciente-table">
|
||||||
<thead>
|
<thead>
|
||||||
@ -439,8 +491,8 @@ function TablePaciente({ setCurrentPage, setPatientID }) {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{pacientesFiltrados.length > 0 ? (
|
{pacientesPaginados.length > 0 ? (
|
||||||
pacientesFiltrados.map((paciente) => (
|
pacientesPaginados.map((paciente) => (
|
||||||
<tr key={paciente.id}>
|
<tr key={paciente.id}>
|
||||||
<td>
|
<td>
|
||||||
<div className="d-flex align-items-center patient-name-container">
|
<div className="d-flex align-items-center patient-name-container">
|
||||||
@ -495,13 +547,75 @@ function TablePaciente({ setCurrentPage, setPatientID }) {
|
|||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan="5" className="empty-state">
|
<td colSpan="5" className="text-center py-4">
|
||||||
Nenhum paciente encontrado.
|
<div className="text-muted">
|
||||||
|
<i className="bi bi-search display-4"></i>
|
||||||
|
<p className="mt-2">Nenhum paciente encontrado com os filtros aplicados.</p>
|
||||||
|
{(search || filtroConvenio !== "Todos" || filtroVIP || filtroAniversariante ||
|
||||||
|
filtroCidade || filtroEstado || idadeMinima || idadeMaxima || dataInicial || dataFinal) && (
|
||||||
|
<button className="btn btn-outline-primary btn-sm mt-2" onClick={limparFiltros}>
|
||||||
|
Limpar filtros
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
{/* Paginação */}
|
||||||
|
{pacientesFiltrados.length > 0 && (
|
||||||
|
<div className="d-flex justify-content-between align-items-center mt-3">
|
||||||
|
<div className="d-flex align-items-center">
|
||||||
|
<span className="me-2 text-muted">Itens por página:</span>
|
||||||
|
<select
|
||||||
|
className="form-select form-select-sm w-auto"
|
||||||
|
value={itensPorPagina}
|
||||||
|
onChange={(e) => {
|
||||||
|
setItensPorPagina(Number(e.target.value));
|
||||||
|
setPaginaAtual(1);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value={5}>5</option>
|
||||||
|
<option value={10}>10</option>
|
||||||
|
<option value={25}>25</option>
|
||||||
|
<option value={50}>50</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="d-flex align-items-center">
|
||||||
|
<span className="me-3 text-muted">
|
||||||
|
Página {paginaAtual} de {totalPaginas} •
|
||||||
|
Mostrando {indiceInicial + 1}-{Math.min(indiceFinal, pacientesFiltrados.length)} de {pacientesFiltrados.length} pacientes
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<nav>
|
||||||
|
<ul className="pagination pagination-sm mb-0">
|
||||||
|
<li className={`page-item ${paginaAtual === 1 ? 'disabled' : ''}`}>
|
||||||
|
<button className="page-link" onClick={voltarPagina}>
|
||||||
|
<i className="bi bi-chevron-left"></i>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
{gerarNumerosPaginas().map(pagina => (
|
||||||
|
<li key={pagina} className={`page-item ${pagina === paginaAtual ? 'active' : ''}`}>
|
||||||
|
<button className="page-link" onClick={() => irParaPagina(pagina)}>
|
||||||
|
{pagina}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<li className={`page-item ${paginaAtual === totalPaginas ? 'disabled' : ''}`}>
|
||||||
|
<button className="page-link" onClick={avancarPagina}>
|
||||||
|
<i className="bi bi-chevron-right"></i>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -427,3 +427,9 @@ html[data-bs-theme="dark"] {
|
|||||||
background-color: #f0f0f0; /* Cor ao passar o mouse */
|
background-color: #f0f0f0; /* Cor ao passar o mouse */
|
||||||
color: #007bff;
|
color: #007bff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.input-modal{
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@ -41,7 +41,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.modal-header-success {
|
.modal-header-success {
|
||||||
background-color: #28a745 !important;
|
background-color: #1e3a8a !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-header-error {
|
.modal-header-error {
|
||||||
@ -104,12 +104,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.modal-button-success {
|
.modal-button-success {
|
||||||
background-color: #28a745;
|
background-color: #1e3a8a;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-button-success:hover {
|
.modal-button-success:hover {
|
||||||
background-color: #218838;
|
background-color: #1e3a8a;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-button-error {
|
.modal-button-error {
|
||||||
@ -186,10 +186,8 @@
|
|||||||
outline: 2px solid #0056b3;
|
outline: 2px solid #0056b3;
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Garantir que as cores dos cabeçalhos sejam aplicadas */
|
|
||||||
.modal-overlay .modal-container .modal-header.modal-header-success {
|
.modal-overlay .modal-container .modal-header.modal-header-success {
|
||||||
background-color: #28a745 !important;
|
background-color: #1e3a8a !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-overlay .modal-container .modal-header.modal-header-error {
|
.modal-overlay .modal-container .modal-header.modal-header-error {
|
||||||
@ -258,7 +256,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.modal-header-success {
|
.modal-header-success {
|
||||||
background-color: #006400 !important;
|
background-color: #1e3a8a !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-header-error {
|
.modal-header-error {
|
||||||
|
|||||||
@ -91,7 +91,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.modal-header.success {
|
.modal-header.success {
|
||||||
background-color: #28a745 !important;
|
background-color: #1e3a8a !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-header.error {
|
.modal-header.error {
|
||||||
@ -168,11 +168,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.modal-confirm-button.success {
|
.modal-confirm-button.success {
|
||||||
background-color: #28a745 !important;
|
background-color: #1e3a8a !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-confirm-button.success:hover {
|
.modal-confirm-button.success:hover {
|
||||||
background-color: #218838 !important;
|
background-color: #1e3a8a !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-confirm-button.error {
|
.modal-confirm-button.error {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
/* src/pages/ProfilePage.css */
|
/* src/pages/ProfilePage.css */
|
||||||
|
|
||||||
/* Overlay que cobre toda a tela */
|
/* Overlay */
|
||||||
.profile-overlay {
|
.profile-overlay {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
@ -8,171 +8,318 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
z-index: 20000; /* acima de header, vlibras, botões de acessibilidade */
|
z-index: 20000;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Card central (estilo modal amplo parecido com a 4ª foto) */
|
/* Modal */
|
||||||
.profile-modal {
|
.profile-modal {
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
border-radius: 10px;
|
border-radius: 12px;
|
||||||
padding: 18px;
|
padding: 20px;
|
||||||
width: min(1100px, 96%);
|
width: min(600px, 96%);
|
||||||
max-width: 1100px;
|
max-width: 600px;
|
||||||
box-shadow: 0 18px 60px rgba(0, 0, 0, 0.5);
|
box-shadow: 0 18px 60px rgba(0, 0, 0, 0.5);
|
||||||
position: relative;
|
position: relative;
|
||||||
box-sizing: border-box;
|
|
||||||
overflow: visible;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Botão fechar (X) no canto do card */
|
/* Botão fechar */
|
||||||
.profile-close {
|
.profile-close {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 14px;
|
top: 15px;
|
||||||
right: 14px;
|
right: 15px;
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
font-size: 26px;
|
font-size: 24px;
|
||||||
color: #666;
|
color: #666;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
line-height: 1;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Conteúdo dividido em 2 colunas: esquerda avatar / direita infos */
|
.profile-close:hover {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Layout */
|
||||||
.profile-content {
|
.profile-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 28px;
|
gap: 30px;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
padding: 22px 18px;
|
padding: 20px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Coluna esquerda - avatar */
|
/* Avatar */
|
||||||
.profile-left {
|
.profile-left {
|
||||||
width: 220px;
|
width: 160px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Avatar quadrado com sombra (estilo da foto 4) */
|
|
||||||
.avatar-wrapper {
|
.avatar-wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 180px;
|
width: 140px;
|
||||||
height: 180px;
|
height: 140px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar-square {
|
.avatar-square {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background-color: #d0d0d0;
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'><path fill='%23FFFFFF' d='M224 256c70.7 0 128-57.3 128-128S294.7 0 224 0 96 57.3 96 128s57.3 128 128 128zm-45.7 48C79.8 304 0 383.8 0 482.3c0 16.7 13.5 30.2 30.2 30.2h387.6c16.7 0 30.2-13.5 30.2-30.2 0-98.5-79.8-178.3-178.3-178.3h-45.7z'/></svg>");
|
overflow: hidden;
|
||||||
background-position: center;
|
display: flex;
|
||||||
background-repeat: no-repeat;
|
align-items: center;
|
||||||
background-size: 55%;
|
justify-content: center;
|
||||||
box-shadow: 0 8px 24px rgba(0,0,0,0.25);
|
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-placeholder {
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Botão editar sobre o avatar — círculo pequeno */
|
|
||||||
.avatar-edit-btn {
|
.avatar-edit-btn {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: -8px;
|
right: -8px;
|
||||||
bottom: -8px;
|
bottom: -8px;
|
||||||
transform: translate(0, 0);
|
|
||||||
border: none;
|
border: none;
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
padding: 8px 9px;
|
padding: 8px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
box-shadow: 0 6px 14px rgba(0,0,0,0.18);
|
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 0.95rem;
|
font-size: 0.9rem;
|
||||||
line-height: 1;
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Coluna direita - informações */
|
.avatar-edit-btn:hover {
|
||||||
|
background: #f0f0f0;
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-edit-btn.uploading {
|
||||||
|
background: #ffd700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-status {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -25px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Informações */
|
||||||
.profile-right {
|
.profile-right {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 280px;
|
min-width: 250px;
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Nome e botão de editar inline */
|
|
||||||
.profile-name-row {
|
.profile-name-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-username {
|
.profile-username {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 1.9rem;
|
font-size: 1.8rem;
|
||||||
color: #222;
|
color: #222;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-edit-inline {
|
.profile-edit-inline {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 1.05rem;
|
font-size: 1rem;
|
||||||
color: #444;
|
color: #444;
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-edit-inline:hover {
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Edição de nome */
|
||||||
|
.name-edit-wrapper {
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* input de edição do nome */
|
|
||||||
.profile-name-input {
|
.profile-name-input {
|
||||||
font-size: 1.6rem;
|
font-size: 1.6rem;
|
||||||
padding: 6px 8px;
|
padding: 5px 8px;
|
||||||
border: 1px solid #e0e0e0;
|
border: 2px solid #007bff;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
|
width: 100%;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-name-input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name-edit-hint {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: #666;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Informações do perfil */
|
||||||
|
.profile-info {
|
||||||
|
margin: 20px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* email/role */
|
|
||||||
.profile-email,
|
.profile-email,
|
||||||
.profile-role {
|
.profile-role {
|
||||||
margin: 6px 0;
|
margin: 8px 0;
|
||||||
color: #555;
|
color: #555;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
|
||||||
|
|
||||||
.profile-role {
|
|
||||||
margin-top: 14px;
|
|
||||||
padding-top: 12px;
|
|
||||||
border-top: 1px solid #f1f1f1;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ações (apenas fechar aqui) */
|
|
||||||
.profile-actions-row {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
gap: 8px;
|
||||||
margin-top: 18px;
|
}
|
||||||
|
|
||||||
|
.profile-email span,
|
||||||
|
.profile-role span {
|
||||||
|
color: #777;
|
||||||
|
min-width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mensagem de erro */
|
||||||
|
.error-message {
|
||||||
|
background-color: #fee;
|
||||||
|
color: #c33;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid #fcc;
|
||||||
|
margin: 15px 0;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ações */
|
||||||
|
.profile-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* botões */
|
|
||||||
.btn {
|
.btn {
|
||||||
padding: 8px 14px;
|
padding: 10px 20px;
|
||||||
border-radius: 8px;
|
border-radius: 6px;
|
||||||
border: 1px solid transparent;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 0.95rem;
|
font-size: 0.9rem;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-close {
|
.btn-close {
|
||||||
background: #f0f0f0;
|
background: #f0f0f0;
|
||||||
color: #222;
|
color: #222;
|
||||||
border: 1px solid #e6e6e6;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* responsividade */
|
.btn-close:hover {
|
||||||
@media (max-width: 880px) {
|
background: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-clear {
|
||||||
|
background: #dc3545;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-clear:hover {
|
||||||
|
background: #c82333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsividade */
|
||||||
|
@media (max-width: 680px) {
|
||||||
.profile-content {
|
.profile-content {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 14px;
|
gap: 20px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-left {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-wrapper {
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-email,
|
||||||
|
.profile-role {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-actions {
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
.profile-left { width: 100%; }
|
}
|
||||||
.avatar-wrapper { width: 140px; height: 140px; }
|
|
||||||
.profile-right { width: 100%; text-align: center; }
|
@media (max-width: 480px) {
|
||||||
|
.profile-modal {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-content {
|
||||||
|
padding: 10px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-username {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-wrapper {
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
}
|
||||||
|
}.avatar-edit-btn {
|
||||||
|
background: #007bff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-top: 10px;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-edit-btn:hover {
|
||||||
|
background: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-edit-btn.uploading {
|
||||||
|
background: #6c757d;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-edit-inline {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 13px;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-edit-inline:hover {
|
||||||
|
background: #e9ecef;
|
||||||
}
|
}
|
||||||
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
.table-doctor-container {
|
.table-doctor-container {
|
||||||
line-height: 2.5;
|
line-height: 2.5;
|
||||||
}
|
}
|
||||||
@ -49,7 +48,7 @@
|
|||||||
background-color: rgba(0, 0, 0, 0.025);
|
background-color: rgba(0, 0, 0, 0.025);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Badges */
|
|
||||||
.specialty-badge {
|
.specialty-badge {
|
||||||
background-color: #1e3a8a !important;
|
background-color: #1e3a8a !important;
|
||||||
color: white !important;
|
color: white !important;
|
||||||
@ -58,8 +57,6 @@
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.results-badge {
|
.results-badge {
|
||||||
background-color: #1e3a8a;
|
background-color: #1e3a8a;
|
||||||
color: white;
|
color: white;
|
||||||
@ -75,7 +72,6 @@
|
|||||||
font-size: 0.75em;
|
font-size: 0.75em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.btn-view {
|
.btn-view {
|
||||||
background-color: #E6F2FF !important;
|
background-color: #E6F2FF !important;
|
||||||
color: #004085 !important;
|
color: #004085 !important;
|
||||||
@ -115,7 +111,6 @@
|
|||||||
border-color: #ED969E;
|
border-color: #ED969E;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.advanced-filters {
|
.advanced-filters {
|
||||||
border: 1px solid #dee2e6;
|
border: 1px solid #dee2e6;
|
||||||
border-radius: 0.375rem;
|
border-radius: 0.375rem;
|
||||||
@ -132,7 +127,6 @@
|
|||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.delete-modal .modal-header {
|
.delete-modal .modal-header {
|
||||||
background-color: rgba(220, 53, 69, 0.1);
|
background-color: rgba(220, 53, 69, 0.1);
|
||||||
border-bottom: 1px solid rgba(220, 53, 69, 0.2);
|
border-bottom: 1px solid rgba(220, 53, 69, 0.2);
|
||||||
@ -143,7 +137,6 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.filter-especialidade {
|
.filter-especialidade {
|
||||||
min-width: 180px !important;
|
min-width: 180px !important;
|
||||||
max-width: 200px;
|
max-width: 200px;
|
||||||
@ -160,7 +153,6 @@
|
|||||||
padding: 0.375rem 0.75rem;
|
padding: 0.375rem 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.filtros-basicos {
|
.filtros-basicos {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@ -168,7 +160,6 @@
|
|||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.table-doctor-table {
|
.table-doctor-table {
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
@ -207,7 +198,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.empty-state {
|
.empty-state {
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@ -224,7 +214,6 @@
|
|||||||
padding: 0.4em 0.65em;
|
padding: 0.4em 0.65em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.table-doctor-table tbody tr {
|
.table-doctor-table tbody tr {
|
||||||
transition: background-color 0.15s ease-in-out;
|
transition: background-color 0.15s ease-in-out;
|
||||||
}
|
}
|
||||||
@ -234,3 +223,115 @@
|
|||||||
.btn-delete {
|
.btn-delete {
|
||||||
transition: all 0.15s ease-in-out;
|
transition: all 0.15s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.contador-medicos {
|
||||||
|
background-color: #1e3a8a;
|
||||||
|
color: white;
|
||||||
|
padding: 0.5em 0.75em;
|
||||||
|
font-size: 0.875em;
|
||||||
|
font-weight: 500;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
text-align: center;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.pagination {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-link {
|
||||||
|
color: #495057;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
padding: 0.375rem 0.75rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-link:hover {
|
||||||
|
color: #1e3a8a;
|
||||||
|
background-color: #e9ecef;
|
||||||
|
border-color: #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-item.active .page-link {
|
||||||
|
background-color: #1e3a8a;
|
||||||
|
border-color: #1e3a8a;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-item.disabled .page-link {
|
||||||
|
color: #6c757d;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-color: #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.d-flex.justify-content-between.align-items-center {
|
||||||
|
border-top: 1px solid #dee2e6;
|
||||||
|
padding-top: 1rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.text-center.py-4 .text-muted {
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-center.py-4 .bi-search {
|
||||||
|
font-size: 3rem;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-center.py-4 p {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-center.py-4 td {
|
||||||
|
border-bottom: none;
|
||||||
|
padding: 2rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.d-flex.justify-content-between.align-items-center {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
align-items: stretch !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.d-flex.justify-content-between.align-items-center > div {
|
||||||
|
justify-content: center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.me-3.text-muted {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contador-medicos {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
padding: 0.4em 0.6em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-select.form-select-sm.w-auto {
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.filters-active .badge {
|
||||||
|
font-size: 0.75em;
|
||||||
|
padding: 0.4em 0.65em;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
.table-paciente-container {
|
.table-paciente-container {
|
||||||
line-height: 2.5;
|
line-height: 2.5;
|
||||||
}
|
}
|
||||||
@ -49,7 +48,6 @@
|
|||||||
background-color: rgba(0, 0, 0, 0.025);
|
background-color: rgba(0, 0, 0, 0.025);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.insurance-badge {
|
.insurance-badge {
|
||||||
background-color: #6c757d !important;
|
background-color: #6c757d !important;
|
||||||
color: white !important;
|
color: white !important;
|
||||||
@ -81,7 +79,6 @@
|
|||||||
font-size: 0.75em;
|
font-size: 0.75em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.btn-view {
|
.btn-view {
|
||||||
background-color: #E6F2FF !important;
|
background-color: #E6F2FF !important;
|
||||||
color: #004085 !important;
|
color: #004085 !important;
|
||||||
@ -121,7 +118,6 @@
|
|||||||
border-color: #ED969E;
|
border-color: #ED969E;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.advanced-filters {
|
.advanced-filters {
|
||||||
border: 1px solid #dee2e6;
|
border: 1px solid #dee2e6;
|
||||||
border-radius: 0.375rem;
|
border-radius: 0.375rem;
|
||||||
@ -148,7 +144,6 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.empty-state {
|
.empty-state {
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@ -165,7 +160,6 @@
|
|||||||
padding: 0.4em 0.65em;
|
padding: 0.4em 0.65em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.table-paciente-table tbody tr {
|
.table-paciente-table tbody tr {
|
||||||
transition: background-color 0.15s ease-in-out;
|
transition: background-color 0.15s ease-in-out;
|
||||||
}
|
}
|
||||||
@ -176,7 +170,6 @@
|
|||||||
transition: all 0.15s ease-in-out;
|
transition: all 0.15s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.table-paciente-table {
|
.table-paciente-table {
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
@ -213,6 +206,7 @@
|
|||||||
margin-left: 0 !important;
|
margin-left: 0 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.compact-select {
|
.compact-select {
|
||||||
font-size: 1.0rem;
|
font-size: 1.0rem;
|
||||||
padding: 0.45rem 0.5rem;
|
padding: 0.45rem 0.5rem;
|
||||||
@ -227,8 +221,120 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.table-paciente-filters .d-flex {
|
.table-paciente-filters .d-flex {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===== ESTILOS PARA PAGINAÇÃO ===== */
|
||||||
|
|
||||||
|
.contador-pacientes {
|
||||||
|
background-color: #1e3a8a;
|
||||||
|
color: white;
|
||||||
|
padding: 0.5em 0.75em;
|
||||||
|
font-size: 0.875em;
|
||||||
|
font-weight: 500;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
text-align: center;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Estilos para a paginação */
|
||||||
|
.pagination {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-link {
|
||||||
|
color: #495057;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
padding: 0.375rem 0.75rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-link:hover {
|
||||||
|
color: #1e3a8a;
|
||||||
|
background-color: #e9ecef;
|
||||||
|
border-color: #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-item.active .page-link {
|
||||||
|
background-color: #1e3a8a;
|
||||||
|
border-color: #1e3a8a;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-item.disabled .page-link {
|
||||||
|
color: #6c757d;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-color: #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ajustes para a seção de paginação */
|
||||||
|
.d-flex.justify-content-between.align-items-center {
|
||||||
|
border-top: 1px solid #dee2e6;
|
||||||
|
padding-top: 1rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Estilos para empty state */
|
||||||
|
.text-center.py-4 .text-muted {
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-center.py-4 .bi-search {
|
||||||
|
font-size: 3rem;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-center.py-4 p {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-center.py-4 td {
|
||||||
|
border-bottom: none;
|
||||||
|
padding: 2rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsividade para paginação */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.d-flex.justify-content-between.align-items-center {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
align-items: stretch !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.d-flex.justify-content-between.align-items-center > div {
|
||||||
|
justify-content: center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.me-3.text-muted {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contador-pacientes {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
padding: 0.4em 0.6em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ajuste para o select de itens por página */
|
||||||
|
.form-select.form-select-sm.w-auto {
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Melhorar a aparência dos badges de filtros ativos */
|
||||||
|
.filters-active .badge {
|
||||||
|
font-size: 0.75em;
|
||||||
|
padding: 0.4em 0.65em;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
@ -9,7 +9,7 @@ import Chat from "../../PagesMedico/Chat";
|
|||||||
import DoctorItems from "../../data/sidebar-items-medico.json";
|
import DoctorItems from "../../data/sidebar-items-medico.json";
|
||||||
import FormNovoRelatorio from "../../PagesMedico/FormNovoRelatorio";
|
import FormNovoRelatorio from "../../PagesMedico/FormNovoRelatorio";
|
||||||
import EditPageRelatorio from "../../PagesMedico/EditPageRelatorio";
|
import EditPageRelatorio from "../../PagesMedico/EditPageRelatorio";
|
||||||
// ...existing code...
|
import BotaoVideoChamada from '../../components/BotaoVideoChamada';
|
||||||
|
|
||||||
function PerfilMedico() {
|
function PerfilMedico() {
|
||||||
return (
|
return (
|
||||||
@ -27,6 +27,10 @@ function PerfilMedico() {
|
|||||||
<Route path="/chat" element={<Chat />} />
|
<Route path="/chat" element={<Chat />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* ADICIONADO AQUI */}
|
||||||
|
<BotaoVideoChamada />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|||||||
@ -6,6 +6,10 @@ import LaudoManager from "../../pages/LaudoManager";
|
|||||||
import ConsultaCadastroManager from "../../PagesPaciente/ConsultaCadastroManager";
|
import ConsultaCadastroManager from "../../PagesPaciente/ConsultaCadastroManager";
|
||||||
import ConsultasPaciente from "../../PagesPaciente/ConsultasPaciente";
|
import ConsultasPaciente from "../../PagesPaciente/ConsultasPaciente";
|
||||||
import ConsultaEditPage from "../../PagesPaciente/ConsultaEditPage";
|
import ConsultaEditPage from "../../PagesPaciente/ConsultaEditPage";
|
||||||
|
|
||||||
|
// 1. IMPORTAÇÃO ADICIONADA
|
||||||
|
import BotaoVideoPaciente from "../../components/BotaoVideoPaciente";
|
||||||
|
|
||||||
function PerfilPaciente({ onLogout }) {
|
function PerfilPaciente({ onLogout }) {
|
||||||
|
|
||||||
const [dadosConsulta, setConsulta] = useState({})
|
const [dadosConsulta, setConsulta] = useState({})
|
||||||
@ -26,7 +30,12 @@ const [dadosConsulta, setConsulta] = useState({})
|
|||||||
<Route path="*" element={<h2>Página não encontrada</h2>} />
|
<Route path="*" element={<h2>Página não encontrada</h2>} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 2. COMPONENTE ADICIONADO AQUI */}
|
||||||
|
<BotaoVideoPaciente />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user