Update de exceções para API de produção

This commit is contained in:
Eduarda-SS 2025-10-28 22:33:24 -03:00
parent d80b13e404
commit 6255ebfb9d
2 changed files with 214 additions and 89 deletions

View File

@ -1,13 +1,17 @@
// src/components/FormCriarExcecao.jsx // src/components/FormCriarExcecao.jsx
import React, { useState } from "react"; import React, { useState } from "react";
import { useAuth } from "./utils/AuthProvider"; // <-- added
import API_KEY from "./utils/apiKeys"; // <-- added
// Assumindo que você usa o mesmo estilo // Assumindo que você usa o mesmo estilo
import "./AgendarConsulta/style/formagendamentos.css"; import "./AgendarConsulta/style/formagendamentos.css";
const ENDPOINT_CRIAR_EXCECAO = "https://mock.apidog.com/m1/1053378-0-default/rest/v1/doctor_exceptions";
const ENDPOINT_CRIAR_EXCECAO = "https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctor_exceptions";
const FormCriarExcecao = ({ onCancel, doctorID }) => { const FormCriarExcecao = ({ onCancel, doctorID }) => {
const { getAuthorizationHeader, user, getUserInfo } = useAuth(); // useAuth inside component
const [dadosAtendimento, setDadosAtendimento] = useState({ const [dadosAtendimento, setDadosAtendimento] = useState({
profissional: doctorID || '', profissional: doctorID || '',
tipoAtendimento: '', tipoAtendimento: '',
@ -38,25 +42,57 @@ const FormCriarExcecao = ({ onCancel, doctorID }) => {
} }
// Adiciona ":00" se o campo de hora estiver preenchido // Adiciona ":00" se o campo de hora estiver preenchido
const startTime = inicio ? inicio + ":00" : undefined; const startTime = inicio ? inicio + ":00" : null;
const endTime = termino ? termino + ":00" : undefined; const endTime = termino ? termino + ":00" : null;
const payload = { // resolve authorization header and try to get current user id for created_by
let authHeader = "";
try {
authHeader = getAuthorizationHeader ? getAuthorizationHeader() : "";
} catch (err) {
console.warn("Não foi possível obter Authorization header via useAuth()", err);
}
// try to resolve created_by (se disponível no contexto)
let createdBy = user?.id || null;
if (!createdBy && typeof getUserInfo === "function") {
try {
const info = await getUserInfo();
createdBy = info?.id || info?.profile?.id || null;
} catch (err) {
console.warn("getUserInfo falhou:", err);
}
}
// fallback localStorage (opcional)
if (!createdBy) {
try {
const stored = localStorage.getItem("user");
if (stored) {
const parsed = JSON.parse(stored);
createdBy = parsed?.id || parsed?.user?.id || null;
}
} catch {}
}
const raw = JSON.stringify({
doctor_id: profissional, doctor_id: profissional,
date: dataAtendimento, date: dataAtendimento,
kind: tipoAtendimento,
start_time: startTime, start_time: startTime,
end_time: endTime, end_time: endTime,
kind: tipoAtendimento,
reason: motivo, reason: motivo,
}; created_by: createdBy // pode ser null se não encontrado
});
var myHeaders = new Headers(); var myHeaders = new Headers();
if (authHeader) myHeaders.append("Authorization", authHeader);
myHeaders.append("Content-Type", "application/json"); myHeaders.append("Content-Type", "application/json");
if (API_KEY) myHeaders.append("apikey", API_KEY); // <-- added
var requestOptions = { var requestOptions = {
method: 'POST', method: 'POST',
headers: myHeaders, headers: myHeaders,
body: JSON.stringify(payload), body: raw,
redirect: 'follow' redirect: 'follow'
}; };
@ -105,7 +141,7 @@ const FormCriarExcecao = ({ onCancel, doctorID }) => {
<div className="campo-de-input"> <div className="campo-de-input">
<label>Tipo de exceção *</label> <label>Tipo de exceção *</label>
<select name="tipoAtendimento" onChange={handleAtendimentoChange} value={dadosAtendimento.tipoAtendimento} required> <select name="tipoAtendimento" onChange={handleAtendimentoChange} value={dadosAtendimento.tipoAtendimento} required>
<option value="" disabled selected>Selecione o tipo de exceção</option> <option value="">Selecione o tipo de exceção</option>
<option value="liberacao" >Liberação (Criar Slot)</option> <option value="liberacao" >Liberação (Criar Slot)</option>
<option value="bloqueio" >Bloqueio (Remover Slot)</option> <option value="bloqueio" >Bloqueio (Remover Slot)</option>
</select> </select>

View File

@ -1,40 +1,112 @@
import React, { useState, useEffect, useCallback } from 'react'; import React, { useState, useEffect, useCallback, useMemo } from 'react';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
// Adicionar locale e plugin se quiser exibir o nome do mês em português
import 'dayjs/locale/pt-br';
import weekday from 'dayjs/plugin/weekday';
import FormCriarExcecao from '../components/FormCriarExcecao'; import FormCriarExcecao from '../components/FormCriarExcecao';
import "../components/AgendarConsulta/style/formagendamentos.css"; import "../components/AgendarConsulta/style/formagendamentos.css";
import "./style/Agendamento.css"; import "./style/Agendamento.css";
import './style/FilaEspera.css'; import './style/FilaEspera.css';
import { useAuth } from '../components/utils/AuthProvider';
import API_KEY from '../components/utils/apiKeys';
const ENDPOINT_LISTAR = "https://mock.apidog.com/m1/1053378-0-default/rest/v1/doctor_exceptions"; dayjs.extend(weekday);
const ENDPOINT_DELETAR = "https://mock.apidog.com/m1/1053378-0-default/rest/v1/doctor_exceptions/"; dayjs.locale('pt-br');
// --- Configurações da API Supabase ---
const ENDPOINT_BASE = "https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctor_exceptions";
// const AUTH_TOKEN = "SEU_TOKEN_DE_AUTORIZACAO_AQUI"; // removed
// ------------------------------------
// Função auxiliar para calcular o range de datas
const getDateRange = (date, view) => {
const startDayjs = dayjs(date);
let fromDate, toDate, titleRange;
if (view === 'diario') {
fromDate = startDayjs.format('YYYY-MM-DD');
toDate = startDayjs.format('YYYY-MM-DD');
titleRange = startDayjs.format('DD/MM/YYYY');
} else if (view === 'semanal') {
// Padrão Dayjs: Sunday=0, Monday=1.
// startOf('week') pode ser Domingo ou Segunda, dependendo do locale.
// Se precisar forçar a Segunda-feira:
let weekStart = startDayjs.startOf('week');
if (weekStart.day() !== 1) { // Se não for segunda-feira (1), ajusta
weekStart = startDayjs.weekday(1); // Vai para a segunda-feira desta semana
}
// Se a data de filtro for hoje (quinta), weekStart é a segunda-feira.
// O Supabase filtra por dia. O range deve incluir a semana inteira.
const weekEnd = weekStart.add(6, 'day');
fromDate = weekStart.format('YYYY-MM-DD');
toDate = weekEnd.format('YYYY-MM-DD');
titleRange = `Semana de ${weekStart.format('DD/MM')} a ${weekEnd.format('DD/MM')}`;
} else if (view === 'mensal') {
const monthStart = startDayjs.startOf('month');
const monthEnd = startDayjs.endOf('month');
fromDate = monthStart.format('YYYY-MM-DD');
toDate = monthEnd.format('YYYY-MM-DD');
titleRange = startDayjs.format('MMMM/YYYY').toUpperCase();
}
return { fromDate, toDate, titleRange };
};
const ExcecoesDisponibilidade = () => { const ExcecoesDisponibilidade = () => {
const { getAuthorizationHeader } = useAuth(); // hook must be at top level of component
const [pageNovaExcecao, setPageNovaExcecao] = useState(false); const [pageNovaExcecao, setPageNovaExcecao] = useState(false);
const [excecoes, setExcecoes] = useState([]); const [excecoes, setExcecoes] = useState([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
// Filtros
const [filtroMedicoId, setFiltroMedicoId] = useState(''); const [filtroMedicoId, setFiltroMedicoId] = useState('');
const [filtroData, setFiltroData] = useState(dayjs().format('YYYY-MM-DD')); const [filtroData, setFiltroData] = useState(dayjs().format('YYYY-MM-DD'));
// Estado para controlar a visualização (Diário, Semanal) // Estado para controlar a visualização ('diario', 'semanal', 'mensal')
const [visualizacao, setVisualizacao] = useState('diario'); const [visualizacao, setVisualizacao] = useState('diario');
// Função para buscar as exceções // helper to get Authorization header string (uses AuthProvider)
const fetchExcecoes = useCallback(async (doctorId, date) => { const resolveAuthHeader = () => {
setLoading(true); try {
let url = `${ENDPOINT_LISTAR}?select=*`; const h = getAuthorizationHeader(); // returns "Bearer <token>" or ''
return h || '';
} catch {
return '';
}
}
// Função para buscar as exceções (ACEITA RANGE)
const fetchExcecoes = useCallback(async (fromDate, toDate, doctorId) => {
setLoading(true);
let url = `${ENDPOINT_BASE}?select=*`;
// Filtro por ID do Médico
if (doctorId) { if (doctorId) {
url += `&doctor_id=eq.${doctorId}`; // Assume filtro por igualdade de ID url += `&doctor_id=eq.${doctorId}`;
}
if (date) {
url += `&date=eq.${date}`; // Assume filtro por igualdade de data
} }
// FILTRO PRINCIPAL: INTERVALO DE DATAS USANDO gte (>=) e lte (<=)
url += `&date=gte.${fromDate}&date=lte.${toDate}`;
const myHeaders = new Headers();
const authHeader = resolveAuthHeader();
if (authHeader) myHeaders.append("Authorization", authHeader);
myHeaders.append("Content-Type", "application/json");
if (API_KEY) myHeaders.append("apikey", API_KEY);
try { try {
const requestOptions = { method: 'GET', redirect: 'follow' }; const requestOptions = {
method: 'GET',
headers: myHeaders,
redirect: 'follow'
};
const response = await fetch(url, requestOptions); const response = await fetch(url, requestOptions);
const result = await response.json(); const result = await response.json();
@ -42,8 +114,8 @@ const ExcecoesDisponibilidade = () => {
setExcecoes(result); setExcecoes(result);
} else { } else {
setExcecoes([]); setExcecoes([]);
console.error("Erro ao listar exceções:", result); console.error("Erro ao listar exceções (Status:", response.status, "):", result);
alert("Erro ao carregar lista de exceções."); alert(`Erro ao carregar lista de exceções. Status: ${response.status}. Detalhes: ${result.message || JSON.stringify(result)}`);
} }
} catch (error) { } catch (error) {
console.error('Erro na requisição de listagem de exceções:', error); console.error('Erro na requisição de listagem de exceções:', error);
@ -52,65 +124,76 @@ const ExcecoesDisponibilidade = () => {
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, []); }, [getAuthorizationHeader]);
// Função para deletar uma exceção // Calcula o range de datas e o título a ser exibido
const deletarExcecao = async (id) => { const { fromDate, toDate, titleRange } = useMemo(() =>
if (!window.confirm(`Tem certeza que deseja deletar a exceção com ID: ${id}?`)) return; getDateRange(filtroData, visualizacao),
[filtroData, visualizacao]
);
// Efeito para carregar exceções quando os filtros e a visualização mudam
useEffect(() => {
fetchExcecoes(fromDate, toDate, filtroMedicoId);
}, [fetchExcecoes, filtroMedicoId, fromDate, toDate]); // Dependências atualizadas
// Deleta uma exceção pelo id e atualiza a lista localmente
const deleteExcecao = async (id) => {
if (!window.confirm("Confirma exclusão desta exceção?")) return;
const myHeaders = new Headers();
const authHeader = resolveAuthHeader();
if (authHeader) myHeaders.append("Authorization", authHeader);
if (API_KEY) myHeaders.append("apikey", API_KEY);
myHeaders.append("Content-Type", "application/json");
try { try {
const requestOptions = { method: 'DELETE', redirect: 'follow' }; const res = await fetch(`${ENDPOINT_BASE}?id=eq.${id}`, {
const response = await fetch(`${ENDPOINT_DELETAR}${id}`, requestOptions); method: 'DELETE',
headers: myHeaders,
if (response.ok || response.status === 204) { redirect: 'follow'
alert(`Exceção ${id} deletada com sucesso.`); });
fetchExcecoes(filtroMedicoId, filtroData); // Recarrega a lista if (res.ok) {
// remove locally
setExcecoes(prev => prev.filter(x => x.id !== id));
} else { } else {
const result = await response.json(); const text = await res.text();
alert(`Erro ao deletar exceção. Detalhes: ${result.message || JSON.stringify(result)}`); console.error('Erro ao deletar exceção', res.status, text);
alert(`Erro ao excluir exceção. Status: ${res.status}. ${text}`);
}
} catch (err) {
console.error('Erro na requisição de exclusão:', err);
alert('Erro ao excluir exceção.');
} }
} catch (error) {
console.error('Erro na requisição de deleção:', error);
alert('Erro de comunicação ao tentar deletar a exceção.');
} }
};
// Efeito para carregar exceções quando os filtros mudam
useEffect(() => {
fetchExcecoes(filtroMedicoId, filtroData);
}, [fetchExcecoes, filtroMedicoId, filtroData]);
// Handler de cancelamento do formulário de criação
const handleCancelForm = (recarregar = false) => { const handleCancelForm = (recarregar = false) => {
setPageNovaExcecao(false); setPageNovaExcecao(false);
if (recarregar) { if (recarregar) {
fetchExcecoes(filtroMedicoId, filtroData); // Recarrega se a criação foi bem-sucedida fetchExcecoes(fromDate, toDate, filtroMedicoId);
} }
} }
// Se o formulário de criação estiver aberto, renderiza apenas ele
if (pageNovaExcecao) { if (pageNovaExcecao) {
return <FormCriarExcecao onCancel={handleCancelForm} doctorID={filtroMedicoId} />; return <FormCriarExcecao onCancel={handleCancelForm} doctorID={filtroMedicoId} />;
} }
// Renderiza a tela de listagem (layout da agenda)
return ( return (
<div> <div>
{/* Título e Botão de Criação */}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '15px' }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '15px' }}>
<h1>Gerenciar Exceções de Disponibilidade</h1> <h1>Gerenciar Exceções de Disponibilidade</h1>
<button <button
className="btn-primary" className="btn-primary"
onClick={() => setPageNovaExcecao(true)} onClick={() => setPageNovaExcecao(true)}
// Use a classe btn-primary que deve estar funcionando
style={{ padding: '10px 20px', fontSize: '14px', whiteSpace: 'nowrap' }} style={{ padding: '10px 20px', fontSize: '14px', whiteSpace: 'nowrap' }}
> >
+ Criar Nova Exceção + Criar Nova Exceção
</button> </button>
</div> </div>
<div className='atendimento-eprocura'> <div className='atendimento-eprocura'>
{/* Filtros e Busca (Adaptados do Agendamento) */} {/* Filtros de Médico e Data */}
<div className='busca-atendimento'> <div className='busca-atendimento'>
<div> <div>
<i className="fa-solid fa-user-doctor"></i> <i className="fa-solid fa-user-doctor"></i>
@ -131,7 +214,7 @@ const ExcecoesDisponibilidade = () => {
</div> </div>
</div> </div>
{/* Botões de Visualização (Dia/Semana/Mês) - Adaptados */} {/* Botões de Visualização (Dia/Semana/Mês) */}
<div className='container-btns-agenda-fila_esepera'> <div className='container-btns-agenda-fila_esepera'>
<button <button
className={`btn-agenda ${visualizacao === "diario" ? "opc-agenda-ativo" : ""}`} className={`btn-agenda ${visualizacao === "diario" ? "opc-agenda-ativo" : ""}`}
@ -153,10 +236,10 @@ const ExcecoesDisponibilidade = () => {
</button> </button>
</div> </div>
{/* Tabela de Exceções (Simulando a Tabela de Agendamentos) */} {/* Tabela de Exceções (Título usa o titleRange calculado) */}
<section className='calendario-ou-filaespera'> <section className='calendario-ou-filaespera'>
<div className="fila-container"> <div className="fila-container">
<h2 className="fila-titulo">Exceções em {filtroData} ({excecoes.length})</h2> <h2 className="fila-titulo">Exceções em {titleRange} ({excecoes.length})</h2>
{loading ? ( {loading ? (
<p>Carregando exceções...</p> <p>Carregando exceções...</p>
) : excecoes.length === 0 ? ( ) : excecoes.length === 0 ? (
@ -165,39 +248,45 @@ const ExcecoesDisponibilidade = () => {
<table className="fila-tabela"> <table className="fila-tabela">
<thead> <thead>
<tr> <tr>
<th>ID Exceção</th> <th>Médico (ID)</th>
<th>ID Médico</th>
<th>Data</th> <th>Data</th>
<th>Início</th> <th>Início</th>
<th>Término</th> <th>Término</th>
<th>Tipo</th>
<th>Motivo</th> <th>Motivo</th>
<th>Criado por</th>
<th>Ações</th> <th>Ações</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{excecoes.map((excecao, index) => ( {excecoes.map((exc) => (
<tr key={excecao.id || index}> <tr key={exc.id}>
<td>{excecao.id || 'N/A'}</td> <td><p>{exc.doctor_id}</p></td>
<td>{excecao.doctor_id}</td> <td>{dayjs(exc.date).format('DD/MM/YYYY')}</td>
<td>{excecao.date}</td> <td>{exc.start_time ? dayjs(exc.start_time, 'HH:mm:ss').format('HH:mm') : '—'}</td>
<td>{excecao.start_time ? excecao.start_time.substring(0, 5) : 'Dia Todo'}</td> <td>{exc.end_time ? dayjs(exc.end_time, 'HH:mm:ss').format('HH:mm') : '—'}</td>
<td>{excecao.end_time ? excecao.end_time.substring(0, 5) : 'Dia Todo'}</td> <td><p>{exc.reason}</p></td>
<td>{exc.created_by || '—'}</td>
<td> <td>
<span className={`status-tag ${excecao.kind === 'bloqueio' ? 'legenda-item-cancelado' : 'legenda-item-realizado'}`}> <div className="d-flex gap-2">
{excecao.kind}
</span>
</td>
<td>{excecao.reason}</td>
<td>
{excecao.id && (
<button <button
onClick={() => deletarExcecao(excecao.id)} className="btn btn-sm btn-edit"
style={{ background: '#dc3545', color: 'white', border: 'none', padding: '5px 10px', cursor: 'pointer', borderRadius: '4px' }} onClick={() => {
// Implementar edição conforme fluxo do seu app.
// Aqui apenas abre o formulário pré-preenchendo o doctorID.
setFiltroMedicoId(exc.doctor_id || '');
setPageNovaExcecao(true);
}}
> >
Deletar <i className="bi bi-pencil me-1"></i> Editar
</button> </button>
)}
<button
className="btn btn-sm btn-delete"
onClick={() => deleteExcecao(exc.id)}
>
<i className="bi bi-trash me-1"></i> Excluir
</button>
</div>
</td> </td>
</tr> </tr>
))} ))}