Compare commits

...

2 Commits

7 changed files with 198 additions and 99 deletions

View File

@ -1,8 +1,11 @@
import React, { useState, useRef } from "react"; import React, { useState, useRef, useCallback } from "react";
import { Link, useNavigate, useLocation } from "react-router-dom"; import { Link, useNavigate, useLocation } from "react-router-dom";
import "./DoctorForm.css"; import "./DoctorForm.css";
import HorariosDisponibilidade from "../doctors/HorariosDisponibilidade"; import HorariosDisponibilidade from "../doctors/HorariosDisponibilidade";
const ENDPOINT_AVAILABILITY =
"https://mock.apidog.com/m1/1053378-0-default/rest/v1/doctor_availability";
function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) { function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation(); const location = useLocation();
@ -123,9 +126,12 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
} }
}; };
const handleAvailabilityUpdate = (newAvailability) => { const handleAvailabilityUpdate = useCallback(
setFormData((prev) => ({ ...prev, availability: newAvailability })); (newAvailability) => {
}; setFormData((prev) => ({ ...prev, availability: newAvailability }));
},
[setFormData]
);
const handleCepBlur = async () => { const handleCepBlur = async () => {
const cep = formData.cep?.replace(/\D/g, ""); const cep = formData.cep?.replace(/\D/g, "");
@ -205,6 +211,42 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
} }
}, 300); }, 300);
}; };
const handleCreateAvailability = async (newAvailability) => {
try {
const response = await fetch(ENDPOINT_AVAILABILITY, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(newAvailability),
});
const data = await response.json();
console.log("Disponibilidade criada :", data);
alert("Disponibilidade criada com sucesso!");
} catch (error) {
console.error("Erro ao criar disponibilidade:", error);
alert("Erro ao criar disponibilidade.");
}
};
const handlePatchAvailability = async (id, updatedAvailability) => {
try {
const response = await fetch(`${ENDPOINT_AVAILABILITY}?id=${id}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(updatedAvailability),
});
const data = await response.json();
console.log("Disponibilidade atualizada:", data);
alert("Disponibilidade atualizada com sucesso!");
} catch (error) {
console.error("Erro ao atualizar disponibilidade:", error);
alert("Erro ao atualizar disponibilidade.");
}
};
const handleSubmit = async () => { const handleSubmit = async () => {
const missingFields = []; const missingFields = [];
@ -246,9 +288,23 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
try { try {
await onSave({ ...formData }); await onSave({ ...formData });
if (formData.availability && formData.availability.length > 0) {
if (formData.availabilityId) {
await handlePatchAvailability(
formData.availabilityId,
formData.availability
);
} else {
await handleCreateAvailability(formData.availability);
}
}
alert("Médico salvo e disponibilidade enviada ao mock com sucesso!");
} catch (error) { } catch (error) {
throw error; console.error("Erro ao salvar médico ou disponibilidade:", error);
} alert("Erro ao salvar médico ou disponibilidade.");
};
}; };
const handleModalClose = () => { const handleModalClose = () => {
@ -677,7 +733,7 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
</div> </div>
</div> </div>
{/* BOTÕES DE AÇÃO */} {/* BOTÕES DE AÇÃO */}
<div className="actions-container"> <div className="actions-container">
<button <button
className="btn btn-success btn-submit" className="btn btn-success btn-submit"

View File

@ -3,8 +3,8 @@ import { Clock } from "lucide-react";
const initialBlockTemplate = { const initialBlockTemplate = {
id: null, id: null,
inicio: "09:00", inicio: "07:00",
termino: "17:00", termino: "18:00",
isNew: true, isNew: true,
}; };
@ -118,9 +118,9 @@ const HorariosDisponibilidade = ({
flexDirection: window.innerWidth < 640 ? "column" : "row", flexDirection: window.innerWidth < 640 ? "column" : "row",
alignItems: window.innerWidth < 640 ? "flex-start" : "center", alignItems: window.innerWidth < 640 ? "flex-start" : "center",
justifyContent: "space-between", justifyContent: "space-between",
padding: "16px", padding: "8px",
marginBottom: "16px", marginBottom: "8px",
borderRadius: "12px", borderRadius: "8px",
boxShadow: boxShadow:
"0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)", "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)",
transition: "all 0.3s", transition: "all 0.3s",
@ -132,7 +132,7 @@ const HorariosDisponibilidade = ({
style={{ style={{
display: "flex", display: "flex",
flexDirection: window.innerWidth < 640 ? "column" : "row", flexDirection: window.innerWidth < 640 ? "column" : "row",
gap: window.innerWidth < 640 ? "0" : "32px", gap: window.innerWidth < 640 ? "0" : "12px",
width: window.innerWidth < 640 ? "100%" : "auto", width: window.innerWidth < 640 ? "100%" : "auto",
}} }}
> >
@ -140,7 +140,7 @@ const HorariosDisponibilidade = ({
style={{ style={{
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
gap: "8px", gap: "4px",
marginBottom: window.innerWidth < 640 ? "8px" : "0", marginBottom: window.innerWidth < 640 ? "8px" : "0",
}} }}
> >
@ -159,20 +159,21 @@ const HorariosDisponibilidade = ({
handleTimeChange(dayIndex, bloco.id, "inicio", e.target.value) handleTimeChange(dayIndex, bloco.id, "inicio", e.target.value)
} }
style={{ style={{
padding: "8px", padding: "4px 6px",
border: "1px solid #d1d5db", border: "1px solid #d1d5db",
borderRadius: "8px", borderRadius: "6px",
width: "100%", width: "100%",
boxSizing: "border-box", boxSizing: "border-box",
outline: "none", outline: "none",
fontSize: "13px"
}} }}
step="300" step="300"
/> />
<Clock <Clock
size={16} size={12}
style={{ style={{
position: "absolute", position: "absolute",
right: "12px", right: "8px",
top: "50%", top: "50%",
transform: "translateY(-50%)", transform: "translateY(-50%)",
color: "#9ca3af", color: "#9ca3af",
@ -182,10 +183,10 @@ const HorariosDisponibilidade = ({
</div> </div>
</div> </div>
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}> <div style={{ display: "flex", alignItems: "center", gap: "4px" }}>
<label <label
htmlFor={`termino-${dayIndex}-${bloco.id}`} htmlFor={`termino-${dayIndex}-${bloco.id}`}
style={{ fontWeight: 500, color: "#4b5563", width: "64px" }} style={{ fontWeight: 500, color: "#4b5563", width: "56px", fontSize: "13px" }}
> >
Término: Término:
</label> </label>
@ -198,20 +199,21 @@ const HorariosDisponibilidade = ({
handleTimeChange(dayIndex, bloco.id, "termino", e.target.value) handleTimeChange(dayIndex, bloco.id, "termino", e.target.value)
} }
style={{ style={{
padding: "8px", padding: "4px 6px",
border: "1px solid #d1d5db", border: "1px solid #d1d5db",
borderRadius: "8px", borderRadius: "6px",
width: "100%", width: "100%",
boxSizing: "border-box", boxSizing: "border-box",
outline: "none", outline: "none",
fontSize: "13px",
}} }}
step="300" step="300"
/> />
<Clock <Clock
size={16} size={12}
style={{ style={{
position: "absolute", position: "absolute",
right: "12px", right: "8px",
top: "50%", top: "50%",
transform: "translateY(-50%)", transform: "translateY(-50%)",
color: "#9ca3af", color: "#9ca3af",
@ -225,12 +227,12 @@ const HorariosDisponibilidade = ({
<button <button
onClick={() => handleRemoveBlock(dayIndex, bloco.id)} onClick={() => handleRemoveBlock(dayIndex, bloco.id)}
style={{ style={{
marginTop: window.innerWidth < 640 ? "16px" : "0", marginTop: window.innerWidth < 640 ? "8px" : "0",
padding: "8px 24px", padding: "4px 10px",
backgroundColor: "#ef4444", backgroundColor: "#ef4444",
color: "white", color: "white",
fontWeight: 600, fontWeight: 600,
borderRadius: "12px", borderRadius: "13px",
boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1)", boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1)",
transition: "all 0.2s", transition: "all 0.2s",
width: window.innerWidth < 640 ? "100%" : "auto", width: window.innerWidth < 640 ? "100%" : "auto",
@ -257,7 +259,6 @@ const HorariosDisponibilidade = ({
fontWeight: 500, fontWeight: 500,
}} }}
> >
(Novo)
</span> </span>
)} )}
</div> </div>
@ -295,8 +296,8 @@ const HorariosDisponibilidade = ({
key={day.dia} key={day.dia}
style={{ style={{
backgroundColor: "#f9fafb", backgroundColor: "#f9fafb",
padding: "20px", padding: "8px",
borderRadius: "12px", borderRadius: "10px",
border: "1px solid #e5e7eb", border: "1px solid #e5e7eb",
}} }}
> >
@ -365,11 +366,11 @@ const HorariosDisponibilidade = ({
<button <button
onClick={() => handleAddBlock(dayIndex)} onClick={() => handleAddBlock(dayIndex)}
style={{ style={{
marginTop: "24px", marginTop: "15px",
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
padding: "12px 24px", padding: "10px 22px",
backgroundColor: "#10b981", backgroundColor: "#10b981",
color: "white", color: "white",
fontWeight: "bold", fontWeight: "bold",

View File

@ -21,7 +21,7 @@
}, },
{ {
"name": "Relaototios", "name": "Relatórios",
"icon": "table", "icon": "table",
"url": "/admin/laudo" "url": "/admin/laudo"
}, },

View File

@ -21,22 +21,22 @@ import { Search } from 'lucide-react';
const Agendamento = ({setDictInfo}) => { // Mantido setDictInfo (versão main) const Agendamento = ({setDictInfo}) => {
const navigate = useNavigate(); const navigate = useNavigate();
// Estados mesclados
const [selectedID, setSelectedId] = useState('0') // (main) const [selectedID, setSelectedId] = useState('0')
const [filaEsperaData, setfilaEsperaData] = useState([]) // (main) const [filaEsperaData, setfilaEsperaData] = useState([])
const [FiladeEspera, setFiladeEspera] = useState(false); const [FiladeEspera, setFiladeEspera] = useState(false);
const [tabela, setTabela] = useState('diario'); const [tabela, setTabela] = useState('diario');
const [PageNovaConsulta, setPageConsulta] = useState(false); const [PageNovaConsulta, setPageConsulta] = useState(false);
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState('');
const [agendamentos, setAgendamentos] = useState() // (main) const [agendamentos, setAgendamentos] = useState()
const {getAuthorizationHeader} = useAuth() const {getAuthorizationHeader} = useAuth()
const [DictAgendamentosOrganizados, setAgendamentosOrganizados ] = useState({}) const [DictAgendamentosOrganizados, setAgendamentosOrganizados ] = useState({})
const [showDeleteModal, setShowDeleteModal] = useState(false) const [showDeleteModal, setShowDeleteModal] = useState(false)
const [AgendamentoFiltrado, setAgendamentoFiltrado] = useState() // (main) const [AgendamentoFiltrado, setAgendamentoFiltrado] = useState()
const [ListaDeMedicos, setListaDeMedicos] = useState([]) const [ListaDeMedicos, setListaDeMedicos] = useState([])
const [FiltredTodosMedicos, setFiltredTodosMedicos] = useState([]) const [FiltredTodosMedicos, setFiltredTodosMedicos] = useState([])
@ -45,10 +45,10 @@ const Agendamento = ({setDictInfo}) => { // Mantido setDictInfo (versão main)
let authHeader = getAuthorizationHeader() let authHeader = getAuthorizationHeader()
// Função FiltrarAgendamentos (Mesclado: Mantido o da MAIN, mais completo e com ordenação/fila de espera real)
const FiltrarAgendamentos = async (listaTodosAgendamentos) => { const FiltrarAgendamentos = async (listaTodosAgendamentos) => {
const ConfigurarFiladeEspera = async (patient_id, doctor_id, agendamento) => { const ConfigurarFiladeEspera = async (patient_id, doctor_id, agendamento) => {
// Assumindo que GetDoctorByID e GetPatientByID estão definidos no seu escopo
let medico = await GetDoctorByID(doctor_id, authHeader); let medico = await GetDoctorByID(doctor_id, authHeader);
let paciente = await GetPatientByID(patient_id, authHeader); let paciente = await GetPatientByID(patient_id, authHeader);
@ -431,7 +431,7 @@ const handleSearchMedicos = (term) => {
<AgendamentoCadastroManager setPageConsulta={setPageConsulta} /> <AgendamentoCadastroManager setPageConsulta={setPageConsulta} />
)} )}
{/* Modal de Confirmação de Exclusão (Da MAIN) */} {/* Modal de Confirmação de Exclusão */}
{showDeleteModal && ( {showDeleteModal && (
<div <div
className="modal fade show" className="modal fade show"

View File

@ -3,40 +3,96 @@ import { Link } from "react-router-dom";
const ENDPOINT_LISTAR = "https://mock.apidog.com/m1/1053378-0-default/rest/v1/doctor_availability"; const ENDPOINT_LISTAR = "https://mock.apidog.com/m1/1053378-0-default/rest/v1/doctor_availability";
const MEDICOS_MOCKADOS = [
{ id: 53, nome: " João Silva" },
{ id: 19, nome: " Ana Costa" },
{ id: 11, nome: " Pedro Santos" },
];
const diasDaSemana = ["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb"];
const formatarDataHora = (isoString) => {
if (!isoString) return "N/A";
try {
const data = new Date(isoString);
return data.toLocaleTimeString("pt-BR", { hour: '2-digit', minute: '2-digit', timeZone: 'UTC' });
} catch (error) {
return "Data Inválida";
}
};
const DisponibilidadesDoctorPage = () => { const DisponibilidadesDoctorPage = () => {
const [disponibilidades, setDisponibilidades] = useState([]); const [disponibilidades, setDisponibilidades] = useState([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [filtroMedicoId, setFiltroMedicoId] = useState(""); const [filtroMedicoNome, setFiltroMedicoNome] = useState("");
const [filtroActive, setFiltroActive] = useState("true");
const [medicoValido, setMedicoValido] = useState(false);
const fetchDisponibilidades = useCallback(async (doctorId, activeStatus) => { const [medicoEncontradoId, setMedicoEncontradoId] = useState(null);
const encontrarMedicoIdPorNome = (nome) => {
if (!nome) return null;
const termoBusca = nome.toLowerCase();
const medico = MEDICOS_MOCKADOS.find(m =>
m.nome.toLowerCase().includes(termoBusca)
);
return medico ? medico.id : null;
};
const fetchDisponibilidades = useCallback(async (nome) => {
setLoading(true); setLoading(true);
let url = `${ENDPOINT_LISTAR}?select=*`; setDisponibilidades([]);
if (doctorId) url += `&doctor_id=eq.${doctorId}`; setMedicoEncontradoId(null);
if (activeStatus === "true" || activeStatus === "false") url += `&active=eq.${activeStatus}`;
const doctorId = encontrarMedicoIdPorNome(nome);
if (!doctorId) {
setLoading(false);
return;
}
const url = `${ENDPOINT_LISTAR}?select=*&doctor_id=eq.${doctorId}`;
try { try {
const response = await fetch(url); const response = await fetch(url);
const result = await response.json(); const result = await response.json();
setDisponibilidades(Array.isArray(result) ? result : []);
setMedicoValido(Array.isArray(result) && result.length > 0); let dados = Array.isArray(result) ? result : [];
setDisponibilidades(dados);
setMedicoEncontradoId(doctorId);
} catch (error) { } catch (error) {
setDisponibilidades([]); setDisponibilidades([]);
setMedicoValido(false);
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, []); }, []);
useEffect(() => { useEffect(() => {
if (filtroMedicoId) { if (filtroMedicoNome) {
fetchDisponibilidades(filtroMedicoId, filtroActive); const timer = setTimeout(() => {
fetchDisponibilidades(filtroMedicoNome);
}, 300);
return () => clearTimeout(timer);
} else { } else {
setDisponibilidades([]); setDisponibilidades([]);
setMedicoValido(false); setMedicoEncontradoId(null);
} }
}, [filtroMedicoId, filtroActive, fetchDisponibilidades]); }, [filtroMedicoNome, fetchDisponibilidades]);
const rotaGerenciar = medicoEncontradoId
? `../medicos/${medicoEncontradoId}/edit`
: `../medicos/novo/edit`;
return ( return (
<div id="main-content"> <div id="main-content">
@ -44,8 +100,9 @@ const DisponibilidadesDoctorPage = () => {
<h1 style={{ fontSize: "1.5rem", fontWeight: "bold", color: "#333" }}> <h1 style={{ fontSize: "1.5rem", fontWeight: "bold", color: "#333" }}>
Disponibilidades por Médico Disponibilidades por Médico
</h1> </h1>
<Link <Link
to={medicoValido ? `../medicos/${filtroMedicoId}/edit` : "#"} to={rotaGerenciar}
className="btn-primary" className="btn-primary"
style={{ style={{
padding: "10px 20px", padding: "10px 20px",
@ -53,8 +110,6 @@ const DisponibilidadesDoctorPage = () => {
whiteSpace: "nowrap", whiteSpace: "nowrap",
textDecoration: "none", textDecoration: "none",
display: "inline-block", display: "inline-block",
opacity: medicoValido ? 1 : 0.6,
pointerEvents: medicoValido ? "auto" : "none",
}} }}
> >
+ Gerenciar Disponibilidades + Gerenciar Disponibilidades
@ -63,28 +118,16 @@ const DisponibilidadesDoctorPage = () => {
<div className="atendimento-eprocura"> <div className="atendimento-eprocura">
<div className="busca-atendimento"> <div className="busca-atendimento">
<div> <div style={{ marginRight: '10px' }}>
<i className="fa-solid fa-user-doctor"></i> <i className="fa-solid fa-user-doctor"></i>
<input <input
type="text" type="text"
placeholder="Filtrar por ID do Médico..." placeholder="Filtrar por Nome do Médico..."
value={filtroMedicoId} value={filtroMedicoNome}
onChange={(e) => setFiltroMedicoId(e.target.value)} onChange={(e) => setFiltroMedicoNome(e.target.value)}
style={{ border: "1px solid #ccc", borderRadius: "4px", padding: "5px" }} style={{ border: "1px solid #ccc", borderRadius: "4px", padding: "5px" }}
/> />
</div> </div>
<div>
<i className="fa-solid fa-check-circle"></i>
<select
value={filtroActive}
onChange={(e) => setFiltroActive(e.target.value)}
style={{ padding: "8px", border: "1px solid #ccc", borderRadius: "4px" }}
>
<option value="true">Ativas</option>
<option value="false">Inativas</option>
<option value="">Todas</option>
</select>
</div>
</div> </div>
<section className="calendario-ou-filaespera"> <section className="calendario-ou-filaespera">
@ -95,15 +138,15 @@ const DisponibilidadesDoctorPage = () => {
{loading ? ( {loading ? (
<p className="text-center py-10">Carregando disponibilidades...</p> <p className="text-center py-10">Carregando disponibilidades...</p>
) : disponibilidades.length === 0 ? ( ) : (filtroMedicoNome && disponibilidades.length === 0) ? (
<p className="text-center py-10"> <p className="text-center py-10">
Nenhuma disponibilidade encontrada para os filtros aplicados. Nenhuma disponibilidade encontrada para o nome buscado.
</p> </p>
) : ( ) : (
<table className="fila-tabela" style={{ width: "100%", borderCollapse: "collapse" }}> <table className="fila-tabela" style={{ width: "100%", borderCollapse: "collapse" }}>
<thead> <thead>
<tr> <tr>
{["ID", "ID Médico", "Dia da Semana", "Início", "Término", "Intervalo (min)", "Tipo Consulta", "Status"].map( {[ "Dia da Semana", "Início", "Término", "Intervalo", "Tipo Consulta"].map(
(header) => ( (header) => (
<th <th
key={header} key={header}
@ -118,18 +161,17 @@ const DisponibilidadesDoctorPage = () => {
<tbody> <tbody>
{disponibilidades.map((disp, index) => ( {disponibilidades.map((disp, index) => (
<tr key={disp.id || index} style={{ borderBottom: "1px solid #eee" }}> <tr key={disp.id || index} style={{ borderBottom: "1px solid #eee" }}>
<td style={{ padding: "10px", fontSize: "0.9em" }}>{disp.id || "N/A"}</td> <td style={{ padding: "10px", fontSize: "0.9em" }}>
<td style={{ padding: "10px", fontSize: "0.9em" }}>{disp.doctor_id}</td> {diasDaSemana[disp.weekday] || disp.weekday}
<td style={{ padding: "10px", fontSize: "0.9em" }}>{disp.weekday}</td> </td>
<td style={{ padding: "10px", fontSize: "0.9em" }}>{disp.start_time || "N/A"}</td> <td style={{ padding: "10px", fontSize: "0.9em" }}>
<td style={{ padding: "10px", fontSize: "0.9em" }}>{disp.end_time || "N/A"}</td> {formatarDataHora(disp.start_time)}
</td>
<td style={{ padding: "10px", fontSize: "0.9em" }}>
{formatarDataHora(disp.end_time)}
</td>
<td style={{ padding: "10px", fontSize: "0.9em" }}>{disp.slot_minutes}</td> <td style={{ padding: "10px", fontSize: "0.9em" }}>{disp.slot_minutes}</td>
<td style={{ padding: "10px", fontSize: "0.9em" }}>{disp.appointment_type}</td> <td style={{ padding: "10px", fontSize: "0.9em" }}>{disp.appointment_type}</td>
<td style={{ padding: "10px", fontSize: "0.9em" }}>
<span className={`status-tag ${disp.active ? "legenda-item-realizado" : "legenda-item-cancelado"}`}>
{disp.active ? "Ativa" : "Inativa"}
</span>
</td>
</tr> </tr>
))} ))}
</tbody> </tbody>

View File

@ -135,9 +135,9 @@ function Login({ onEnterSystem }) {
return ( return (
<> <>
<div className="mt-3 card-position"> <div className="mt-3 card-position">
<div className="col-lg-5 col-md-7 col-sm-9 col-12 mx-auto"> <div className="col-lg-5 col-md-7 col-sm-9 col-5 mx-auto">
<div className="card shadow-sm d-flex justify-content-between align-items-center"> <div className="card w-10 shadow-sm d-flex justify-content-between align-items-center">
<div id="auth-left" className="w-100"> <div id="auth-left" className="w-10">
<div className="auth-logo"> <div className="auth-logo">
<br /> <br />
<Link to="/"> <Link to="/">

View File

@ -123,8 +123,8 @@ function Register() {
return ( return (
<> <>
<div className="mt-3 card-position"> <div className="mt-3 card-position">
<div className="col-lg-5 col-12"> <div className="col-lg-5 col-6">
<div className="card shadow-sm d-flex justify-content-between align-items-center"> <div className="card w-10 shadow-sm d-flex justify-content-between align-items-center">
<div id="auth-left"> <div id="auth-left">
<div className="auth-logo"> <div className="auth-logo">
<br /> <br />