Ajuste das exceções

This commit is contained in:
Eduarda-SS 2025-11-26 17:55:47 -03:00
parent f7cb04a68f
commit 461bf3b413
5 changed files with 171 additions and 101 deletions

View File

@ -3,11 +3,6 @@
"name": "Seus Agendamentos", "name": "Seus Agendamentos",
"icon": "calendar", "icon": "calendar",
"url": "/medico/agendamento" "url": "/medico/agendamento"
},
{
"name": "Exceções de Disponibilidade",
"icon": "calendar-x-fill",
"url": "/medico/excecoes-disponibilidade"
}, },
{ {

View File

@ -377,8 +377,16 @@ const DeleteModal = () => (
> >
<i className="bi bi-plus-circle"></i> Adicionar Consulta <i className="bi bi-plus-circle"></i> Adicionar Consulta
</button> </button>
<button className="manage-button btn" onClick={() => navigate("/secretaria/excecoes-disponibilidade")}><i className="bi bi-gear-fill me-1"></i> Gerenciar Exceções</button> <button
<button className='manage-button btn' onClick={() => navigate('/secretaria/disponibilidade')}><i className="bi bi-gear-fill me-1"></i> Mudar Disponibilidade</button> className="btn btn-primary"
onClick={() => navigate("/medico/excecoes-disponibilidade")}>
<i className="bi bi-gear-fill me-1"></i> Gerenciar Exceções de Disponibilidade
</button>
<button
className='btn btn-primary'
onClick={() => navigate('/secretaria/disponibilidade')}>
<i className="bi bi-gear-fill me-1"></i> Mudar Disponibilidade
</button>
</div> </div>
{!PageNovaConsulta ? ( {!PageNovaConsulta ? (
<div className='atendimento-eprocura'> <div className='atendimento-eprocura'>

View File

@ -2,6 +2,10 @@ import { useState, useEffect, useMemo, useCallback } from 'react';
import { useAuth } from '../../_assets/utils/AuthProvider' import { useAuth } from '../../_assets/utils/AuthProvider'
import FormExcecaoDisponibilidade from '../../components/medico/FormExcecaoDisponibilidade'; import FormExcecaoDisponibilidade from '../../components/medico/FormExcecaoDisponibilidade';
import '../../_assets/css/components/agendamento/FormAgendamento.css';
import '../../_assets/css/pages/agendamento/Agendamento.css';
import '../../_assets/css/pages/agendamento/FilaEspera.css';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import weekday from 'dayjs/plugin/weekday'; import weekday from 'dayjs/plugin/weekday';
import 'dayjs/locale/pt-br'; import 'dayjs/locale/pt-br';
@ -19,9 +23,12 @@ const getDateRange = (date, view) => {
toDate = base.endOf('day').format('YYYY-MM-DD'); toDate = base.endOf('day').format('YYYY-MM-DD');
titleRange = base.format('DD/MM/YYYY'); titleRange = base.format('DD/MM/YYYY');
} else if (view === 'semanal') { } else if (view === 'semanal') {
fromDate = base.startOf('week').format('YYYY-MM-DD'); let weekStart = base.startOf('week');
toDate = base.endOf('week').format('YYYY-MM-DD'); if (weekStart.day() !== 1) weekStart = base.weekday(1);
titleRange = `${base.startOf('week').format('DD/MM')} - ${base.endOf('week').format('DD/MM')}`; 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 { // mensal } else { // mensal
fromDate = base.startOf('month').format('YYYY-MM-DD'); fromDate = base.startOf('month').format('YYYY-MM-DD');
toDate = base.endOf('month').format('YYYY-MM-DD'); toDate = base.endOf('month').format('YYYY-MM-DD');
@ -40,7 +47,7 @@ const ExcecoesDisponibilidadeDoctor = () => {
const [erro, setErro] = useState(''); const [erro, setErro] = useState('');
const [buscaTexto, setBuscaTexto] = useState(''); const [buscaTexto, setBuscaTexto] = useState('');
const doctorID = user?.doctor_id || user?.id; // ajuste conforme estrutura real const doctorID = user?.doctor_id || user?.id || '';
const { fromDate, toDate, titleRange } = useMemo( const { fromDate, toDate, titleRange } = useMemo(
() => getDateRange(dataFiltro, visualizacao), () => getDateRange(dataFiltro, visualizacao),
@ -52,7 +59,6 @@ const ExcecoesDisponibilidadeDoctor = () => {
setLoading(true); setLoading(true);
setErro(''); setErro('');
try { try {
// trata getAuthorizationHeader() que pode retornar objeto ou string
const maybeAuth = getAuthorizationHeader(); const maybeAuth = getAuthorizationHeader();
const headers = {}; const headers = {};
if (typeof maybeAuth === 'string') { if (typeof maybeAuth === 'string') {
@ -130,66 +136,95 @@ const ExcecoesDisponibilidadeDoctor = () => {
setDataFiltro(nova.format('YYYY-MM-DD')); setDataFiltro(nova.format('YYYY-MM-DD'));
}; };
const handleCancelForm = (recarregar = false) => {
setMostrarForm(false);
if (recarregar) fetchExcecoes();
};
if (mostrarForm) { if (mostrarForm) {
return ( return (
<div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '15px' }}>
<h1>Gerenciar Exceções de Disponibilidade</h1>
</div>
<div className="container mt-3"> <div className="container mt-3">
<h4>Nova Exceção</h4>
<FormExcecaoDisponibilidade <FormExcecaoDisponibilidade
doctorID={doctorID} doctorID={doctorID}
onCancel={(reload) => { onCancel={(reload) => handleCancelForm(reload)}
setMostrarForm(false);
if (reload) fetchExcecoes();
}}
/> />
</div> </div>
</div>
); );
} }
return ( return (
<div className="container mt-3"> <div>
<div className="d-flex justify-content-between align-items-center mb-3"> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '15px' }}>
<h4>Exceções de Disponibilidade</h4> <h1>Gerenciar Exceções de Disponibilidade</h1>
<button className="btn btn-primary" onClick={() => setMostrarForm(true)}> <button
Criar Exceção className="btn-primary"
onClick={() => setMostrarForm(true)}
style={{ padding: '10px 20px', fontSize: '14px', whiteSpace: 'nowrap' }}
>
+ Criar Nova Exceção
</button> </button>
</div> </div>
<div className="d-flex gap-2 flex-wrap mb-3"> <div className='atendimento-eprocura'>
<div className="btn-group"> <div className='busca-atendimento'>
<div>
<i className="fa-solid fa-user-doctor"></i>
<input
type="text"
placeholder="ID do Médico"
value={doctorID}
readOnly
/>
</div>
<div>
<i className="fa-solid fa-calendar"></i>
<input
type="date"
value={dataFiltro}
onChange={(e) => setDataFiltro(e.target.value)}
/>
</div>
</div>
<div className='container-btns-agenda-fila_esepera' style={{ marginTop: 8 }}>
<button <button
className={`btn btn-sm ${visualizacao === 'diario' ? 'btn-secondary' : 'btn-outline-secondary'}`} className={`btn-agenda ${visualizacao === "diario" ? "opc-agenda-ativo" : ""}`}
onClick={() => setVisualizacao('diario')} onClick={() => setVisualizacao('diario')}
> >
Diário Dia
</button> </button>
<button <button
className={`btn btn-sm ${visualizacao === 'semanal' ? 'btn-secondary' : 'btn-outline-secondary'}`} className={`btn-fila-espera ${visualizacao === "semanal" ? "opc-filaespera-ativo" : ""}`}
onClick={() => setVisualizacao('semanal')} onClick={() => setVisualizacao('semanal')}
> >
Semanal Semana
</button> </button>
<button <button
className={`btn btn-sm ${visualizacao === 'mensal' ? 'btn-secondary' : 'btn-outline-secondary'}`} className={`btn-fila-espera ${visualizacao === "mensal" ? "opc-filaespera-ativo" : ""}`}
onClick={() => setVisualizacao('mensal')} onClick={() => setVisualizacao('mensal')}
> >
Mensal Mês
</button> </button>
</div> </div>
<div className="d-flex align-items-center gap-2"> <section className='calendario-ou-filaespera' style={{ marginTop: 12 }}>
<div className="fila-container">
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<h2 className="fila-titulo">Exceções em {titleRange} ({excecoes.length})</h2>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<button className="btn btn-outline-dark btn-sm" onClick={() => mudarData(-1)}>&lt;</button> <button className="btn btn-outline-dark btn-sm" onClick={() => mudarData(-1)}>&lt;</button>
<strong>{titleRange}</strong> <strong>{titleRange}</strong>
<button className="btn btn-outline-dark btn-sm" onClick={() => mudarData(1)}>&gt;</button> <button className="btn btn-outline-dark btn-sm" onClick={() => mudarData(1)}>&gt;</button>
</div> </div>
</div>
<input <div style={{ marginTop: 8, marginBottom: 8, display: 'flex', gap: 8, alignItems: 'center' }}>
type="date"
className="form-control form-control-sm"
value={dataFiltro}
onChange={e => setDataFiltro(e.target.value)}
style={{ maxWidth: 160 }}
/>
<input <input
type="text" type="text"
placeholder="Buscar (motivo / tipo)" placeholder="Buscar (motivo / tipo)"
@ -201,48 +236,62 @@ const ExcecoesDisponibilidadeDoctor = () => {
</div> </div>
{erro && <div className="alert alert-danger py-1">{erro}</div>} {erro && <div className="alert alert-danger py-1">{erro}</div>}
{loading && <div>Carregando...</div>} {loading ? (
<p>Carregando exceções...</p>
{!loading && excecoesFiltradas.length === 0 && ( ) : excecoesFiltradas.length === 0 ? (
<div className="alert alert-info py-1">Nenhuma exceção neste intervalo.</div> <p>Nenhuma exceção encontrada para os filtros aplicados.</p>
)} ) : (
<table className="fila-tabela">
{!loading && excecoesFiltradas.length > 0 && (
<div className="table-responsive">
<table className="table table-sm table-striped">
<thead> <thead>
<tr> <tr>
<th>Médico (Nome)</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>Tipo</th>
<th>Motivo</th> <th>Motivo</th>
<th></th> <th>Ações</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{excecoesFiltradas.map(ex => ( {excecoesFiltradas.map((ex) => (
<tr key={ex.id}> <tr key={ex.id}>
<td><p>{ex.doctor_name || ex.doctor_id || doctorID}</p></td>
<td>{ex.date ? dayjs(ex.date).format('DD/MM/YYYY') : '-'}</td> <td>{ex.date ? dayjs(ex.date).format('DD/MM/YYYY') : '-'}</td>
<td>{ex.start_time || ex.inicio || '-'}</td> <td>{ex.start_time ? (dayjs(ex.start_time, 'HH:mm:ss').isValid() ? dayjs(ex.start_time, 'HH:mm:ss').format('HH:mm') : ex.start_time) : (ex.inicio || '-')}</td>
<td>{ex.end_time || ex.termino || '-'}</td> <td>{ex.end_time ? (dayjs(ex.end_time, 'HH:mm:ss').isValid() ? dayjs(ex.end_time, 'HH:mm:ss').format('HH:mm') : ex.end_time) : (ex.termino || '-')}</td>
<td>{ex.kind || ex.tipoAtendimento || '-'}</td> <td>{ex.kind || ex.tipoAtendimento || '-'}</td>
<td>{ex.reason || ex.motivo || '-'}</td> <td><p>{ex.reason || ex.motivo || '-'}</p></td>
<td> <td>
<div className="d-flex gap-2">
<button <button
className="btn btn-outline-danger btn-sm" className="btn btn-sm btn-edit"
onClick={() => {
// ao editar, mostra o formulário. FormExcecaoDisponibilidade precisa lidar com edição via props
setMostrarForm(true);
// Form pode receber os dados via estado global/URL ou ser ajustado para receber o registro atual
} }
>
<i className="bi bi-pencil me-1"></i> Editar
</button>
<button
className="btn btn-sm btn-delete"
onClick={() => handleDelete(ex.id)} onClick={() => handleDelete(ex.id)}
> >
Remover <i className="bi bi-trash me-1"></i> Excluir
</button> </button>
</div>
</td> </td>
</tr> </tr>
))} ))}
</tbody> </tbody>
</table> </table>
</div>
)} )}
</div> </div>
</section>
</div>
</div>
); );
}; };

View File

@ -200,6 +200,16 @@ const ExcecoesDisponibilidade = () => {
} }
} }
// navegação de datas (adicionada, mesma lógica da versão do médico)
const mudarData = (delta) => {
const base = dayjs(filtroData);
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');
setFiltroData(nova.format('YYYY-MM-DD'));
}
const handleCancelForm = (recarregar = false) => { const handleCancelForm = (recarregar = false) => {
setPageNovaExcecao(false); setPageNovaExcecao(false);
if (recarregar) { if (recarregar) {
@ -273,7 +283,15 @@ const ExcecoesDisponibilidade = () => {
{/* Tabela de Exceções (Título usa o titleRange calculado) */} {/* 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">
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<h2 className="fila-titulo">Exceções em {titleRange} ({excecoes.length})</h2> <h2 className="fila-titulo">Exceções em {titleRange} ({excecoes.length})</h2>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<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>
</div>
{loading ? ( {loading ? (
<p>Carregando exceções...</p> <p>Carregando exceções...</p>
) : excecoes.length === 0 ? ( ) : excecoes.length === 0 ? (

View File

@ -408,13 +408,13 @@ const Agendamento = ({ setDictInfo }) => {
<i className="bi bi-plus-circle"></i> Adicionar Consulta <i className="bi bi-plus-circle"></i> Adicionar Consulta
</button> </button>
<button <button
className="manage-button btn" className="btn btn-primary"
onClick={() => navigate("/secretaria/excecoes-disponibilidade")} onClick={() => navigate("/secretaria/excecoes-disponibilidade")}
> >
<i className="bi bi-gear-fill me-1"></i> Gerenciar Exceções <i className="bi bi-gear-fill me-1"></i> Gerenciar Exceções
</button> </button>
<button <button
className='manage-button btn' className='btn btn-primary'
onClick={() => navigate('/secretaria/disponibilidade')} onClick={() => navigate('/secretaria/disponibilidade')}
> >
<i className="bi bi-gear-fill me-1"></i> Mudar Disponibilidade <i className="bi bi-gear-fill me-1"></i> Mudar Disponibilidade