Arummando o agendamento

This commit is contained in:
GilenoNeto901 2025-12-02 14:04:36 -03:00
parent 07ed113291
commit 6f3a49575c
3 changed files with 314 additions and 200 deletions

View File

@ -31,6 +31,7 @@ const Agendamento = () => {
const ID_MEDICO_ESPECIFICO = "078d2a67-b4c1-43c8-ae32-c1e75bb5b3df"; const ID_MEDICO_ESPECIFICO = "078d2a67-b4c1-43c8-ae32-c1e75bb5b3df";
const [listaTodosAgendamentos, setListaTodosAgendamentos] = useState([]); const [listaTodosAgendamentos, setListaTodosAgendamentos] = useState([]);
const [selectedID, setSelectedId] = useState("0"); const [selectedID, setSelectedId] = useState("0");
const [filaEsperaData, setFilaEsperaData] = useState([]); const [filaEsperaData, setFilaEsperaData] = useState([]);
@ -39,12 +40,16 @@ const Agendamento = () => {
const [DictAgendamentosOrganizados, setAgendamentosOrganizados] = useState( const [DictAgendamentosOrganizados, setAgendamentosOrganizados] = useState(
{} {}
); );
const [showDeleteModal, setShowDeleteModal] = useState(false); const [showDeleteModal, setShowDeleteModal] = useState(false);
const [ListaDeMedicos, setListaDeMedicos] = useState([]); const [ListaDeMedicos, setListaDeMedicos] = useState([]);
const [FiltredTodosMedicos, setFiltredTodosMedicos] = useState([]); const [FiltredTodosMedicos, setFiltredTodosMedicos] = useState([]);
const [searchTermDoctor, setSearchTermDoctor] = useState(""); const [searchTermDoctor, setSearchTermDoctor] = useState("");
const [MedicoFiltrado, setMedicoFiltrado] = useState({ id: "vazio" }); const [MedicoFiltrado, setMedicoFiltrado] = useState({ id: "vazio" });
const [motivoCancelamento, setMotivoCancelamento] = useState(""); const [motivoCancelamento, setMotivoCancelamento] = useState("");
const [searchTermPatient, setSearchTermPatient] = useState("");
const [patientDropdownOpen, setPatientDropdownOpen] = useState(false);
const [showSpinner, setShowSpinner] = useState(true); const [showSpinner, setShowSpinner] = useState(true);
const [waitlistSearch, setWaitlistSearch] = useState(""); const [waitlistSearch, setWaitlistSearch] = useState("");
const [waitSortKey, setWaitSortKey] = useState(null); const [waitSortKey, setWaitSortKey] = useState(null);
@ -303,6 +308,14 @@ const Agendamento = () => {
} }
}; };
const filtrarPorPaciente = (appointments) => {
if (!searchTermPatient.trim()) return appointments;
const term = searchTermPatient.toLowerCase();
return appointments.filter((app) =>
app.paciente_nome?.toLowerCase().includes(term)
);
};
const generateDateGrid = () => { const generateDateGrid = () => {
const grid = []; const grid = [];
const startOfMonth = currentDate.startOf("month"); const startOfMonth = currentDate.startOf("month");
@ -411,6 +424,13 @@ const Agendamento = () => {
() => applySortingWaitlist(filaEsperaFiltrada), () => applySortingWaitlist(filaEsperaFiltrada),
[filaEsperaFiltrada, applySortingWaitlist] [filaEsperaFiltrada, applySortingWaitlist]
); );
const listaPacientesUnicos = useMemo(() => {
const pacientes = Object.values(cachePacientes || {});
return pacientes.sort((a, b) =>
(a.full_name || "").localeCompare(b.full_name || "")
);
}, [cachePacientes]);
const waitTotalPages = const waitTotalPages =
Math.ceil(filaEsperaOrdenada.length / waitPerPage) || 1; Math.ceil(filaEsperaOrdenada.length / waitPerPage) || 1;
const waitIndiceInicial = (waitPage - 1) * waitPerPage; const waitIndiceInicial = (waitPage - 1) * waitPerPage;
@ -449,71 +469,11 @@ const Agendamento = () => {
<h1>Agendar nova consulta</h1> <h1>Agendar nova consulta</h1>
{!PageNovaConsulta ? ( {!PageNovaConsulta ? (
<div className="atendimento-eprocura"> <div className="atendimento-eprocura">
{user?.role !== "doctor" && (
<div className="card p-3 mb-3 table-paciente-filters">
<h5 className="mb-3">
<i className="bi bi-funnel-fill me-2 text-primary"></i>
Filtrar por Médico
</h5>
<div className="position-relative">
<input
type="text"
className="form-control"
placeholder="Digite o nome do médico..."
value={searchTermDoctor}
onChange={(e) => handleSearchMedicos(e.target.value)}
/>
<small className="text-muted">
Buscar médico para filtrar consultas
</small>
{searchTermDoctor && FiltredTodosMedicos.length > 0 && (
<div
className="list-group position-absolute w-100"
style={{
zIndex: 1000,
maxHeight: "200px",
overflowY: "auto",
}}
>
{FiltredTodosMedicos.map((medico) => (
<button
key={medico.idMedico}
type="button"
className="list-group-item list-group-item-action"
onClick={() => {
setSearchTermDoctor(medico.nomeMedico);
setFiltredTodosMedicos([]);
setMedicoFiltrado({ id: medico.idMedico });
}}
>
{medico.nomeMedico}
</button>
))}
</div>
)}
</div>
{MedicoFiltrado.id !== "vazio" && (
<div className="mt-3">
<span className="badge bg-primary me-2">
<i className="bi bi-person-check me-1"></i>
{searchTermDoctor}
<button
type="button"
className="btn-close btn-close-white ms-2"
style={{ fontSize: "0.6rem" }}
onClick={() => {
setMedicoFiltrado({ id: "vazio" });
setSearchTermDoctor("");
}}
></button>
</span>
</div>
)}
</div>
)}
<div className="container-btns-agenda-fila_esepera"> <div className="container-btns-agenda-fila_esepera">
<div className="tabs-agenda-fila"> <div className="tabs-agenda-fila">
<button <button
className={`btn-agenda ${ className={`btn-agenda ${
@ -538,7 +498,7 @@ const Agendamento = () => {
</div> </div>
<div <div
className="btns-gerenciamento-e-consulta" className="btns-gerenciamento-e-consulta"
style={{ display: "flex", gap: "10px", marginBottom: "20px" }} style={{ display: "flex", gap: "10px" }}
> >
<button <button
className="btn btn-primary" className="btn btn-primary"
@ -575,99 +535,191 @@ const Agendamento = () => {
<div className="info-details"> <div className="info-details">
<h3>{selectedDay.format("dddd")}</h3> <h3>{selectedDay.format("dddd")}</h3>
<p>{selectedDay.format("D [de] MMMM [de] YYYY")}</p> <p>{selectedDay.format("D [de] MMMM [de] YYYY")}</p>
<div className="calendar-legend">
<div className="legend-item" data-status="completed">Realizado</div>
<div className="legend-item" data-status="confirmed">Confirmado</div>
<div className="legend-item" data-status="agendado">Agendado</div>
<div className="legend-item" data-status="cancelled">Cancelado</div>
</div>
</div> </div>
<div className="appointments-list"> <div className="appointments-list">
<h4>Consultas para {selectedDay.format("DD/MM")}</h4> <h4>Consultas para {selectedDay.format("DD/MM")}</h4>
{showSpinner ? ( {showSpinner ? (
<Spinner /> <Spinner />
) : DictAgendamentosOrganizados[ ) : (() => {
selectedDay.format("YYYY-MM-DD") const appointmentsForDay =
]?.filter( DictAgendamentosOrganizados[selectedDay.format("YYYY-MM-DD")] || [];
(app) =>
MedicoFiltrado.id === "vazio" || // 1º filtro: por médico (se tiver)
app.doctor_id === MedicoFiltrado.id const filteredByDoctor = appointmentsForDay.filter(
).length > 0 ? ( (app) =>
DictAgendamentosOrganizados[ MedicoFiltrado.id === "vazio" ||
selectedDay.format("YYYY-MM-DD") app.doctor_id === MedicoFiltrado.id
] );
.filter(
(app) => // 2º filtro: por paciente (campo de cima)
MedicoFiltrado.id === "vazio" || const filteredByPatient = filtrarPorPaciente(filteredByDoctor);
app.doctor_id === MedicoFiltrado.id
) if (filteredByPatient.length === 0) {
.map((app) => ( return (
<div <div className="no-appointments-info">
key={app.id} <p>
className="appointment-item" {searchTermPatient
data-status={app.status} ? `Nenhuma consulta encontrada para "${searchTermPatient}".`
> : "Nenhuma consulta agendada."}
<div className="item-time"> </p>
{dayjs(app.scheduled_at).format("HH:mm")} </div>
</div> );
<div className="item-details"> }
<span>{app.paciente_nome}</span>
<small>Dr(a). {app.medico_nome}</small> return filteredByPatient.map((app) => (
</div> <div
<div className="appointment-actions"> key={app.id}
{app.status === "cancelled" ? ( className="appointment-item"
<button data-status={app.status}
className="btn-action btn-edit" >
onClick={() => { <div className="item-time">
setSelectedId(app.id); {dayjs(app.scheduled_at).format("HH:mm")}
confirmConsulta(app.id); </div>
}} <div className="item-details">
> <span>{app.paciente_nome}</span>
<CheckCircle <small>Dr(a). {app.medico_nome}</small>
size={16} </div>
title="Reverter Cancelamento" <div className="appointment-actions">
/> {app.status === "cancelled" ? (
</button> <button
) : ( className="btn-action btn-edit"
<button onClick={() => {
className="btn-action btn-edit" setSelectedId(app.id);
onClick={() => handleEditConsulta(app)} confirmConsulta(app.id);
title="Editar Agendamento" }}
> >
<Edit size={16} /> <CheckCircle
</button> size={16}
)} title="Reverter Cancelamento"
{app.status !== "cancelled" && ( />
<button </button>
className="btn-action btn-delete" ) : (
onClick={() => { <button
setSelectedId(app.id); className="btn-action btn-edit"
setShowDeleteModal(true); onClick={() => handleEditConsulta(app)}
}} title="Editar Agendamento"
title="Cancelar Agendamento" >
> <Edit size={16} />
<Trash2 size={16} /> </button>
</button> )}
)} {app.status !== "cancelled" && (
</div> <button
</div> className="btn-action btn-delete"
)) onClick={() => {
) : ( setSelectedId(app.id);
<div className="no-appointments-info"> setShowDeleteModal(true);
<p>Nenhuma consulta agendada.</p> }}
</div> title="Cancelar Agendamento"
)} >
<Trash2 size={16} />
</button>
)}
</div>
</div>
));
})()}
</div> </div>
</div> </div>
<div className="calendar-main"> <div className="calendar-main">
<div className="patient-filter-container" style={{ marginBottom: "20px" }}>
<div className="card p-3">
<div className="filter-header d-flex align-items-center gap-2 mb-2">
<i className="bi bi-funnel-fill text-primary"></i>
<h5 className="mb-0">Filtrar por Paciente</h5>
</div>
<div className="position-relative">
<Search
size={20}
className="position-absolute"
style={{ left: "10px", top: "50%", transform: "translateY(-50%)", color: "#6c757d" }}
/>
<input
type="text"
className="form-control ps-5"
placeholder="Buscar paciente para filtrar consultas"
value={searchTermPatient}
onChange={(e) => {
setSearchTermPatient(e.target.value);
setPatientDropdownOpen(true);
}}
onFocus={() => {
if (searchTermPatient.trim()) {
setPatientDropdownOpen(true);
}
}}
style={{ paddingLeft: "40px" }}
/>
{patientDropdownOpen && searchTermPatient.trim() && (
<div
className="patient-suggestions"
style={{
position: "absolute",
top: "100%",
left: 0,
right: 0,
zIndex: 10,
backgroundColor: "white",
border: "1px solid #ced4da",
borderTop: "none",
maxHeight: "250px",
overflowY: "auto",
}}
>
{listaPacientesUnicos
.filter((p) => {
const nome = (p.full_name || "").toLowerCase();
const term = searchTermPatient.toLowerCase();
return nome.startsWith(term); // só começo do nome
})
.slice(0, 20)
.map((p) => (
<button
key={p.id}
type="button"
className="w-100 text-start px-3 py-1"
style={{
border: "none",
background: "white",
fontSize: "0.9rem",
}}
onClick={() => {
setSearchTermPatient(p.full_name);
setPatientDropdownOpen(false);
}}
>
{p.full_name} - {p.cpf}
</button>
))}
</div>
)}
</div>
{searchTermPatient && (
<div className="mt-2">
<button
className="btn btn-sm btn-outline-secondary"
onClick={() => setSearchTermPatient("")}
>
<i className="bi bi-x-circle me-1"></i>
Limpar filtro
</button>
</div>
)}
</div>
</div>
<div className="calendar-legend"> <div className="calendar-legend">
<div className="legend-item" data-status="completed">
Realizado </div>
</div>
<div className="legend-item" data-status="confirmed">
Confirmado
</div>
<div className="legend-item" data-status="agendado">
Agendado
</div>
<div className="legend-item" data-status="cancelled">
Cancelado
</div>
</div>
<div className="calendar-controls"> <div className="calendar-controls">
<div className="date-indicator"> <div className="date-indicator">
<h2>{currentDate.format("MMMM [de] YYYY")}</h2> <h2>{currentDate.format("MMMM [de] YYYY")}</h2>
@ -755,13 +807,17 @@ const Agendamento = () => {
))} ))}
{dateGrid.map((day, index) => { {dateGrid.map((day, index) => {
const dayString = day.format("YYYY-MM-DD"); const dayString = day.format("YYYY-MM-DD");
const appointmentsOnDay = const appointmentsOnDay =
DictAgendamentosOrganizados[dayString] || []; DictAgendamentosOrganizados[dayString] || [];
const filteredAppointments = appointmentsOnDay.filter(
(app) => const filteredAppointments = filtrarPorPaciente(
MedicoFiltrado.id === "vazio" || appointmentsOnDay.filter(
app.doctor_id === MedicoFiltrado.id (app) =>
); MedicoFiltrado.id === "vazio" ||
app.doctor_id === MedicoFiltrado.id
)
);
const cellClasses = `day-cell ${ const cellClasses = `day-cell ${
day.isSame(currentDate, "month") day.isSame(currentDate, "month")
? "current-month" ? "current-month"

View File

@ -166,13 +166,20 @@
} }
.container-btns-agenda-fila_esepera { .container-btns-agenda-fila_esepera {
margin-top: 30px;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between;
align-items: flex-end;
gap: 20px; gap: 20px;
margin-left: 20px;
margin-top: 0;
margin-bottom: 0;
margin-left: 0;
padding: 0 20px 0;
border-bottom: 1px solid #edf1f7;
} }
.btn-fila-espera, .btn-fila-espera,
.btn-agenda { .btn-agenda {
background-color: transparent; background-color: transparent;
@ -270,3 +277,9 @@ html[data-bs-theme="dark"] .legenda-item-agendado {
border: 3px solid #4d4d2e; border: 3px solid #4d4d2e;
color: #f7f7c4; color: #f7f7c4;
} }
.calendar-legend {
margin-top: 8px;
display: flex;
gap: 8px;
justify-content: center;
}

View File

@ -24,6 +24,7 @@ dayjs.extend(isBetween);
dayjs.extend(localeData); dayjs.extend(localeData);
const Agendamento = ({ setDictInfo }) => { const Agendamento = ({ setDictInfo }) => {
const navigate = useNavigate(); const navigate = useNavigate();
const [listaTodosAgendamentos, setListaTodosAgendamentos] = useState([]); const [listaTodosAgendamentos, setListaTodosAgendamentos] = useState([]);
const [selectedID, setSelectedId] = useState('0'); const [selectedID, setSelectedId] = useState('0');
@ -37,6 +38,7 @@ const Agendamento = ({ setDictInfo }) => {
const [ListaDeMedicos, setListaDeMedicos] = useState([]); const [ListaDeMedicos, setListaDeMedicos] = useState([]);
const [FiltredTodosMedicos, setFiltredTodosMedicos] = useState([]); const [FiltredTodosMedicos, setFiltredTodosMedicos] = useState([]);
const [searchTermDoctor, setSearchTermDoctor] = useState(''); const [searchTermDoctor, setSearchTermDoctor] = useState('');
const [doctorDropdownOpen, setDoctorDropdownOpen] = useState(false);
const [MedicoFiltrado, setMedicoFiltrado] = useState({ id: "vazio" }); const [MedicoFiltrado, setMedicoFiltrado] = useState({ id: "vazio" });
const [cacheAgendamentos, setCacheAgendamentos] = useState([]); const [cacheAgendamentos, setCacheAgendamentos] = useState([]);
const [appointmentToEdit, setAppointmentToEdit] = useState(null); const [appointmentToEdit, setAppointmentToEdit] = useState(null);
@ -412,22 +414,11 @@ const Agendamento = ({ setDictInfo }) => {
<h3>{selectedDay.format('dddd')}</h3> <h3>{selectedDay.format('dddd')}</h3>
<p>{selectedDay.format('D [de] MMMM [de] YYYY')}</p> <p>{selectedDay.format('D [de] MMMM [de] YYYY')}</p>
<div <div className="calendar-legend">
className="calendar-legend compact-legend" <div className="legend-item" data-status="completed">Realizado</div>
style={{ marginTop: 6, gap: 4, fontSize: 10 }} <div className="legend-item" data-status="confirmed">Confirmado</div>
> <div className="legend-item" data-status="agendado">Agendado</div>
<div className="legend-item" data-status="completed" style={{ padding: '2px 8px' }}> <div className="legend-item" data-status="cancelled">Cancelado</div>
Realizado
</div>
<div className="legend-item" data-status="confirmed" style={{ padding: '2px 8px' }}>
Confirmado
</div>
<div className="legend-item" data-status="agendado" style={{ padding: '2px 8px' }}>
Agendado
</div>
<div className="legend-item" data-status="cancelled" style={{ padding: '2px 8px' }}>
Cancelado
</div>
</div> </div>
<h4 className="subtitle-consultas"> <h4 className="subtitle-consultas">
@ -440,12 +431,13 @@ const Agendamento = ({ setDictInfo }) => {
) : (() => { ) : (() => {
const allApps = const allApps =
DictAgendamentosOrganizados[selectedDay.format('YYYY-MM-DD')] || []; DictAgendamentosOrganizados[selectedDay.format('YYYY-MM-DD')] || [];
const filtered = allApps.filter(app => const termDoc = searchTermDoctor.toLowerCase();
!searchTermDoctor const filtered = allApps.filter(app =>
? true !termDoc
: (app.medico_nome || '').toLowerCase() ? true
.includes(searchTermDoctor.toLowerCase()) : (app.medico_nome || '').toLowerCase().startsWith(termDoc)
); );
return filtered.length > 0 ? ( return filtered.length > 0 ? (
filtered.map(app => ( filtered.map(app => (
<div key={app.id} className="appointment-item" data-status={app.status}> <div key={app.id} className="appointment-item" data-status={app.status}>
@ -495,17 +487,70 @@ const Agendamento = ({ setDictInfo }) => {
})()} })()}
</div> </div>
</div> </div>
<div className="calendar-main">
{/* SUBSTITUIR POR ESTE BLOCO 👇 */}
<div className="calendar-header-filter">
<div className="position-relative">
<input
type="text"
className="form-control"
placeholder="Filtrar atendimento do médico"
value={searchTermDoctor}
onChange={(e) => {
setSearchTermDoctor(e.target.value);
setDoctorDropdownOpen(true);
}}
onFocus={() => {
if (searchTermDoctor.trim()) setDoctorDropdownOpen(true);
}}
/>
{doctorDropdownOpen && searchTermDoctor.trim() && (
<div
className="doctor-suggestions"
style={{
position: "absolute",
top: "100%",
left: 0,
right: 0,
zIndex: 10,
backgroundColor: "white",
border: "1px solid #ced4da",
borderTop: "none",
maxHeight: "250px",
overflowY: "auto",
}}
>
{ListaDeMedicos
.filter((m) => {
const nome = (m.nomeMedico || "").toLowerCase();
const term = searchTermDoctor.toLowerCase();
return nome.startsWith(term);
})
.slice(0, 20)
.map((m) => (
<button
key={m.idMedico}
type="button"
className="w-100 text-start px-3 py-1"
style={{
border: "none",
background: "white",
fontSize: "0.9rem",
}}
onClick={() => {
setSearchTermDoctor(m.nomeMedico);
setDoctorDropdownOpen(false);
}}
>
{m.nomeMedico}
</button>
))}
</div>
)}
</div>
</div>
<div className="calendar-main">
<div className="calendar-header-filter">
<input
type="text"
className="form-control"
placeholder="Filtrar atendimento do médico"
value={searchTermDoctor}
onChange={(e) => setSearchTermDoctor(e.target.value)}
/>
</div>
<div className="calendar-controls"> <div className="calendar-controls">
<div className="date-indicator"> <div className="date-indicator">
@ -578,12 +623,12 @@ const Agendamento = ({ setDictInfo }) => {
{dateGrid.map((day, index) => { {dateGrid.map((day, index) => {
const dayKey = day.format('YYYY-MM-DD'); const dayKey = day.format('YYYY-MM-DD');
const appointmentsOnDay = DictAgendamentosOrganizados[dayKey] || []; const appointmentsOnDay = DictAgendamentosOrganizados[dayKey] || [];
const filteredAppointments = appointmentsOnDay.filter(app => const termDoc = searchTermDoctor.toLowerCase();
!searchTermDoctor const filteredAppointments = appointmentsOnDay.filter(app =>
? true !termDoc
: (app.medico_nome || '').toLowerCase() ? true
.includes(searchTermDoctor.toLowerCase()) : (app.medico_nome || '').toLowerCase().startsWith(termDoc)
); );
const cellClasses = `day-cell ${ const cellClasses = `day-cell ${
day.isSame(currentDate, 'month') ? 'current-month' : 'other-month' day.isSame(currentDate, 'month') ? 'current-month' : 'other-month'
} ${day.isSame(dayjs(), 'day') ? 'today' : ''} ${ } ${day.isSame(dayjs(), 'day') ? 'today' : ''} ${