codigo Ed
This commit is contained in:
parent
9c9f551ca6
commit
4787a0cd66
6302
package-lock.json
generated
6302
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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 }) => {
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
import API_KEY from "../apiKeys";
|
||||
|
||||
|
||||
|
||||
const GetPatientByID = async (ID,authHeader) => {
|
||||
|
||||
var myHeaders = new Headers();
|
||||
|
||||
@ -3,6 +3,11 @@
|
||||
"name": "Seus Agendamentos",
|
||||
"icon": "calendar",
|
||||
"url": "/medico/agendamento"
|
||||
},
|
||||
{
|
||||
"name": "Exceções de Disponibilidade",
|
||||
"icon": "calendar-x-fill",
|
||||
"url": "/medico/excecoes-disponibilidade"
|
||||
},
|
||||
|
||||
{
|
||||
@ -17,5 +22,4 @@
|
||||
"url": "/medico/chat"
|
||||
}
|
||||
|
||||
|
||||
]
|
||||
|
||||
@ -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
|
||||
|
||||
249
src/pages/ExcecoesDisponibilidadeDoctor.jsx
Normal file
249
src/pages/ExcecoesDisponibilidadeDoctor.jsx
Normal 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)}><</button>
|
||||
<strong>{titleRange}</strong>
|
||||
<button className="btn btn-outline-dark btn-sm" onClick={() => mudarData(1)}>></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;
|
||||
@ -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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user