codigo Ed

This commit is contained in:
Rafael_Monteiro Alves 2025-11-24 19:25:12 -03:00
parent 9c9f551ca6
commit 4787a0cd66
7 changed files with 6581 additions and 38 deletions

6302
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,6 @@ import API_KEY from "./utils/apiKeys";
import "./AgendarConsulta/style/formagendamentos.css";
import { GetAllDoctors } from './utils/Functions-Endpoints/Doctor';
const ENDPOINT_CRIAR_EXCECAO = "https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctor_exceptions";
const FormCriarExcecao = ({ onCancel, doctorID }) => {

View File

@ -1,7 +1,5 @@
import API_KEY from "../apiKeys";
const GetPatientByID = async (ID,authHeader) => {
var myHeaders = new Headers();

View File

@ -4,6 +4,11 @@
"icon": "calendar",
"url": "/medico/agendamento"
},
{
"name": "Exceções de Disponibilidade",
"icon": "calendar-x-fill",
"url": "/medico/excecoes-disponibilidade"
},
{
"name": "Relatórios",
@ -17,5 +22,4 @@
"url": "/medico/chat"
}
]

View File

@ -8,11 +8,14 @@ import "./style/Agendamento.css";
import './style/FilaEspera.css';
import { useAuth } from '../components/utils/AuthProvider';
import API_KEY from '../components/utils/apiKeys';
import { GetAllDoctors } from '../components/utils/Functions-Endpoints/Doctor';
import { UserInfos } from '../components/utils/Functions-Endpoints/General';
dayjs.extend(weekday);
dayjs.locale('pt-br');
const ENDPOINT_BASE = "https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctor_exceptions";
const API_ROOT = "https://yuanqfswhberkoevtmfr.supabase.co/rest/v1";
const getDateRange = (date, view) => {
const startDayjs = dayjs(date);
@ -99,7 +102,52 @@ const ExcecoesDisponibilidade = () => {
const result = await response.json();
if (response.ok && Array.isArray(result)) {
setExcecoes(result);
// usar função existente para obter lista de médicos
const doctorIds = Array.from(new Set(result.map(r => r.doctor_id).filter(Boolean)));
let doctors = [];
try {
// GetAllDoctors já retorna id e full_name (ver Doctor.js)
doctors = await GetAllDoctors(authHeader);
} catch (err) {
console.warn('Falha ao obter lista de médicos via GetAllDoctors:', err);
doctors = [];
}
const doctorMap = {};
if (Array.isArray(doctors)) {
doctors.forEach(d => {
if (d && d.id != null) doctorMap[d.id] = d.full_name || d.name || d.display_name || d.id;
});
}
// buscar nomes de quem criou (profiles) usa REST direto (não há função pronta)
const createdByIds = Array.from(new Set(result.map(r => r.created_by).filter(Boolean)));
const creatorMap = {};
if (createdByIds.length > 0) {
const inList = createdByIds.map(id => (isNaN(Number(id)) ? `"${String(id).replace(/"/g,'\\"')}"` : id)).join(',');
const profilesUrl = `${API_ROOT}/profiles?select=*&id=in.(${inList})`;
try {
const resProfiles = await fetch(profilesUrl, { method: 'GET', headers: myHeaders, redirect: 'follow' });
if (resProfiles.ok) {
const profiles = await resProfiles.json();
profiles.forEach(p => {
creatorMap[p.id] = p.full_name || p.name || p.username || p.email || p.id;
});
} else {
console.warn('Não foi possível buscar profiles:', resProfiles.status);
}
} catch (err) {
console.warn('Erro ao buscar profiles:', err);
}
}
// anexa nomes às exceções (fallback para ids caso não encontre nome)
const enriched = result.map(r => ({
...r,
doctor_name: doctorMap[r.doctor_id] || r.doctor_name || r.doctor_id,
created_by_name: creatorMap[r.created_by] || r.created_by_name || r.created_by
}));
setExcecoes(enriched);
} else {
setExcecoes([]);
console.error("Erro ao listar exceções (Status:", response.status, "):", result);
@ -232,7 +280,7 @@ const ExcecoesDisponibilidade = () => {
<table className="fila-tabela">
<thead>
<tr>
<th>Médico (ID)</th>
<th>Médico (Nome)</th>
<th>Data</th>
<th>Início</th>
<th>Término</th>
@ -244,12 +292,12 @@ const ExcecoesDisponibilidade = () => {
<tbody>
{excecoes.map((exc) => (
<tr key={exc.id}>
<td><p>{exc.doctor_id}</p></td>
<td><p>{exc.doctor_name || exc.doctor_id}</p></td>
<td>{dayjs(exc.date).format('DD/MM/YYYY')}</td>
<td>{exc.start_time ? dayjs(exc.start_time, 'HH:mm:ss').format('HH:mm') : '—'}</td>
<td>{exc.end_time ? dayjs(exc.end_time, 'HH:mm:ss').format('HH:mm') : '—'}</td>
<td><p>{exc.reason}</p></td>
<td>{exc.created_by || '—'}</td>
<td>{exc.created_by_name || exc.created_by || '—'}</td>
<td>
<div className="d-flex gap-2">
<button

View File

@ -0,0 +1,249 @@
import React, { useState, useEffect, useMemo, useCallback } from 'react';
import dayjs from 'dayjs';
import weekday from 'dayjs/plugin/weekday';
import 'dayjs/locale/pt-br';
import { useAuth } from '../components/utils/AuthProvider'
import FormCriarExcecao from '../components/FormCriarExcecao';
dayjs.extend(weekday);
dayjs.locale('pt-br');
const ENDPOINT_BASE = 'https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctor_exceptions';
const getDateRange = (date, view) => {
const base = dayjs(date);
let fromDate, toDate, titleRange;
if (view === 'diario') {
fromDate = base.startOf('day').format('YYYY-MM-DD');
toDate = base.endOf('day').format('YYYY-MM-DD');
titleRange = base.format('DD/MM/YYYY');
} else if (view === 'semanal') {
fromDate = base.startOf('week').format('YYYY-MM-DD');
toDate = base.endOf('week').format('YYYY-MM-DD');
titleRange = `${base.startOf('week').format('DD/MM')} - ${base.endOf('week').format('DD/MM')}`;
} else { // mensal
fromDate = base.startOf('month').format('YYYY-MM-DD');
toDate = base.endOf('month').format('YYYY-MM-DD');
titleRange = base.format('MMMM YYYY');
}
return { fromDate, toDate, titleRange };
};
const ExcecoesDisponibilidadeDoctor = () => {
const { getAuthorizationHeader, user } = useAuth();
const [excecoes, setExcecoes] = useState([]);
const [loading, setLoading] = useState(false);
const [visualizacao, setVisualizacao] = useState('diario');
const [dataFiltro, setDataFiltro] = useState(dayjs().format('YYYY-MM-DD'));
const [mostrarForm, setMostrarForm] = useState(false);
const [erro, setErro] = useState('');
const [buscaTexto, setBuscaTexto] = useState('');
const doctorID = user?.doctor_id || user?.id; // ajuste conforme estrutura real
const { fromDate, toDate, titleRange } = useMemo(
() => getDateRange(dataFiltro, visualizacao),
[dataFiltro, visualizacao]
);
const fetchExcecoes = useCallback(async () => {
if (!doctorID) return;
setLoading(true);
setErro('');
try {
// trata getAuthorizationHeader() que pode retornar objeto ou string
const maybeAuth = getAuthorizationHeader();
const headers = {};
if (typeof maybeAuth === 'string') {
headers['Authorization'] = maybeAuth;
} else if (maybeAuth && typeof maybeAuth === 'object') {
Object.assign(headers, maybeAuth);
}
headers['Content-Type'] = 'application/json';
headers['Prefer'] = 'count=exact';
const params = new URLSearchParams();
params.append('doctor_id', `eq.${doctorID}`);
params.append('date', `gte.${fromDate}`);
params.append('date', `lte.${toDate}`);
params.append('order', 'date.asc');
const res = await fetch(`${ENDPOINT_BASE}?${params.toString()}`, {
headers
});
if (!res.ok) {
throw new Error('Falha ao carregar exceções');
}
const data = await res.json();
setExcecoes(Array.isArray(data) ? data : []);
} catch (e) {
setErro(e.message);
} finally {
setLoading(false);
}
}, [doctorID, fromDate, toDate, getAuthorizationHeader]);
useEffect(() => {
fetchExcecoes();
}, [fetchExcecoes]);
const handleDelete = async (id) => {
if (!window.confirm('Remover esta exceção?')) return;
try {
const maybeAuth = getAuthorizationHeader();
const headers = {};
if (typeof maybeAuth === 'string') {
headers['Authorization'] = maybeAuth;
} else if (maybeAuth && typeof maybeAuth === 'object') {
Object.assign(headers, maybeAuth);
}
headers['Content-Type'] = 'application/json';
const res = await fetch(`${ENDPOINT_BASE}?id=eq.${id}`, {
method: 'DELETE',
headers
});
if (!res.ok) throw new Error('Erro ao deletar');
setExcecoes(prev => prev.filter(e => e.id !== id));
} catch (e) {
alert(e.message);
}
};
const excecoesFiltradas = useMemo(() => {
if (!buscaTexto.trim()) return excecoes;
const txt = buscaTexto.toLowerCase();
return excecoes.filter(e =>
(e.reason || '').toLowerCase().includes(txt) ||
(e.kind || '').toLowerCase().includes(txt)
);
}, [buscaTexto, excecoes]);
const mudarData = (delta) => {
const base = dayjs(dataFiltro);
let nova;
if (visualizacao === 'diario') nova = base.add(delta, 'day');
else if (visualizacao === 'semanal') nova = base.add(delta, 'week');
else nova = base.add(delta, 'month');
setDataFiltro(nova.format('YYYY-MM-DD'));
};
if (mostrarForm) {
return (
<div className="container mt-3">
<h4>Nova Exceção</h4>
<FormCriarExcecao
doctorID={doctorID}
onCancel={(reload) => {
setMostrarForm(false);
if (reload) fetchExcecoes();
}}
/>
</div>
);
}
return (
<div className="container mt-3">
<div className="d-flex justify-content-between align-items-center mb-3">
<h4>Exceções de Disponibilidade</h4>
<button className="btn btn-primary" onClick={() => setMostrarForm(true)}>
Criar Exceção
</button>
</div>
<div className="d-flex gap-2 flex-wrap mb-3">
<div className="btn-group">
<button
className={`btn btn-sm ${visualizacao === 'diario' ? 'btn-secondary' : 'btn-outline-secondary'}`}
onClick={() => setVisualizacao('diario')}
>
Diário
</button>
<button
className={`btn btn-sm ${visualizacao === 'semanal' ? 'btn-secondary' : 'btn-outline-secondary'}`}
onClick={() => setVisualizacao('semanal')}
>
Semanal
</button>
<button
className={`btn btn-sm ${visualizacao === 'mensal' ? 'btn-secondary' : 'btn-outline-secondary'}`}
onClick={() => setVisualizacao('mensal')}
>
Mensal
</button>
</div>
<div className="d-flex align-items-center gap-2">
<button className="btn btn-outline-dark btn-sm" onClick={() => mudarData(-1)}>&lt;</button>
<strong>{titleRange}</strong>
<button className="btn btn-outline-dark btn-sm" onClick={() => mudarData(1)}>&gt;</button>
</div>
<input
type="date"
className="form-control form-control-sm"
value={dataFiltro}
onChange={e => setDataFiltro(e.target.value)}
style={{ maxWidth: 160 }}
/>
<input
type="text"
placeholder="Buscar (motivo / tipo)"
className="form-control form-control-sm"
value={buscaTexto}
onChange={e => setBuscaTexto(e.target.value)}
style={{ flex: 1, minWidth: 180 }}
/>
</div>
{erro && <div className="alert alert-danger py-1">{erro}</div>}
{loading && <div>Carregando...</div>}
{!loading && excecoesFiltradas.length === 0 && (
<div className="alert alert-info py-1">Nenhuma exceção neste intervalo.</div>
)}
{!loading && excecoesFiltradas.length > 0 && (
<div className="table-responsive">
<table className="table table-sm table-striped">
<thead>
<tr>
<th>Data</th>
<th>Início</th>
<th>Término</th>
<th>Tipo</th>
<th>Motivo</th>
<th></th>
</tr>
</thead>
<tbody>
{excecoesFiltradas.map(ex => (
<tr key={ex.id}>
<td>{ex.date ? dayjs(ex.date).format('DD/MM/YYYY') : '-'}</td>
<td>{ex.start_time || ex.inicio || '-'}</td>
<td>{ex.end_time || ex.termino || '-'}</td>
<td>{ex.kind || ex.tipoAtendimento || '-'}</td>
<td>{ex.reason || ex.motivo || '-'}</td>
<td>
<button
className="btn btn-outline-danger btn-sm"
onClick={() => handleDelete(ex.id)}
>
Remover
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
);
};
export default ExcecoesDisponibilidadeDoctor;

View File

@ -11,6 +11,8 @@ import FormNovoRelatorio from "../../PagesMedico/FormNovoRelatorio";
import EditPageRelatorio from "../../PagesMedico/EditPageRelatorio";
import BotaoVideoChamada from '../../components/BotaoVideoChamada';
import ExcecoesDisponibilidadeDoctor from "../../pages/ExcecoesDisponibilidadeDoctor.jsx";
import DoctorAgendamentoEditPage from "../../PagesMedico/DoctorAgendamentoEditPage";
function PerfilMedico() {
@ -31,6 +33,7 @@ function PerfilMedico() {
<Route path="/relatorios" element={<DoctorRelatorioManager />} />
<Route path="/agendamento" element={<DoctorAgendamentoManager setDictInfo={setDictInfo}/>} />
<Route path="/agendamento/edit" element={<DoctorAgendamentoEditPage DictInfo={dictInfo} setDictInfo={setDictInfo}/>} />
<Route path="/excecoes-disponibilidade" element={<ExcecoesDisponibilidadeDoctor />} />
<Route path="/chat" element={<Chat />} />
</Routes>
</div>