Compare commits
No commits in common. "ba9888466733520572852796b52c4ed14f628e50" and "c87bfbb4de4dfd8ffe9889fb16f63fe05e7afa8f" have entirely different histories.
ba98884667
...
c87bfbb4de
@ -7,7 +7,6 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
|
||||
const FormatTelefones = (valor) => {
|
||||
const digits = String(valor).replace(/\D/g, "").slice(0, 11);
|
||||
return digits
|
||||
@ -29,6 +28,7 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
const cpfLimpo = cpf.replace(/\D/g, "");
|
||||
|
||||
if (cpfLimpo.length !== 11) return false;
|
||||
|
||||
if (/^(\d)\1+$/.test(cpfLimpo)) return false;
|
||||
|
||||
let soma = 0;
|
||||
@ -51,7 +51,6 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const [avatarUrl, setAvatarUrl] = useState(null);
|
||||
const [showRequiredModal, setShowRequiredModal] = useState(false);
|
||||
const [emptyFields, setEmptyFields] = useState([]);
|
||||
@ -123,10 +122,6 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
}
|
||||
};
|
||||
|
||||
const handleAvailabilityUpdate = (newAvailability) => {
|
||||
setFormData((prev) => ({ ...prev, availability: newAvailability }));
|
||||
};
|
||||
|
||||
const handleCepBlur = async () => {
|
||||
const cep = formData.cep?.replace(/\D/g, "");
|
||||
if (cep && cep.length === 8) {
|
||||
@ -255,10 +250,8 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
setShowRequiredModal(false);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Modal de Alerta */}
|
||||
{showRequiredModal && (
|
||||
<div className="modal-overlay">
|
||||
<div className="modal-content">
|
||||
@ -306,7 +299,6 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Formulário Principal */}
|
||||
<div className="card doctor-form-container shadow-sm">
|
||||
<h3 className="doctor-form-title">MediConnect</h3>
|
||||
|
||||
@ -327,7 +319,6 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
}`}
|
||||
>
|
||||
<div className="row mt-3">
|
||||
{/* Foto / Avatar */}
|
||||
<div className="col-md-6 mb-3 avatar-container">
|
||||
<div className="me-3">
|
||||
{avatarUrl ? (
|
||||
@ -363,7 +354,6 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Nome Completo */}
|
||||
<div className="col-md-6 mb-3">
|
||||
<label className="form-label">Nome: *</label>
|
||||
<input
|
||||
@ -375,7 +365,6 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
{/* Data de Nascimento */}
|
||||
<div className="col-md-6 mb-3">
|
||||
<label className="form-label">Data de nascimento:</label>
|
||||
<input
|
||||
@ -388,7 +377,6 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
max="2025-09-24"
|
||||
/>
|
||||
</div>
|
||||
{/* CPF */}
|
||||
<div className="col-md-6 mb-3">
|
||||
<label className="form-label">CPF: *</label>
|
||||
<input
|
||||
@ -411,7 +399,6 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Estado do CRM */}
|
||||
<div className="col-md-6 mb-3">
|
||||
<label className="form-label">Estado do CRM: *</label>
|
||||
<select
|
||||
@ -451,7 +438,6 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* CRM */}
|
||||
<div className="col-md-6 mb-3">
|
||||
<label className="form-label">CRM: *</label>
|
||||
<input
|
||||
@ -464,7 +450,6 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Especialização */}
|
||||
<div className="col-md-6 mb-3">
|
||||
<label className="form-label">Especialização:</label>
|
||||
<select
|
||||
@ -512,7 +497,6 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
className={`collapse${collapsedSections.contato ? " show" : ""}`}
|
||||
>
|
||||
<div className="row mt-3">
|
||||
{/* Email */}
|
||||
<div className="col-md-6 mb-3">
|
||||
<label className="form-label">Email: *</label>
|
||||
<input
|
||||
@ -524,7 +508,6 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
{/* Telefone 1 (Principal) */}
|
||||
<div className="col-md-6 mb-3">
|
||||
<label className="form-label">Telefone: *</label>
|
||||
<input
|
||||
@ -536,7 +519,6 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
{/* Telefone 2 (Opcional) */}
|
||||
<div className="col-md-6 mb-3">
|
||||
<label className="form-label">Telefone 2:</label>
|
||||
<input
|
||||
@ -566,7 +548,6 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
className={`collapse${collapsedSections.endereco ? " show" : ""}`}
|
||||
>
|
||||
<div className="row mt-3">
|
||||
{/* CEP */}
|
||||
<div className="col-md-4 mb-3">
|
||||
<label className="form-label">CEP:</label>
|
||||
<input
|
||||
@ -578,7 +559,6 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
onBlur={handleCepBlur}
|
||||
/>
|
||||
</div>
|
||||
{/* Rua */}
|
||||
<div className="col-md-8 mb-3">
|
||||
<label className="form-label">Rua:</label>
|
||||
<input
|
||||
@ -589,7 +569,6 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
{/* Bairro */}
|
||||
<div className="col-md-6 mb-3">
|
||||
<label className="form-label">Bairro:</label>
|
||||
<input
|
||||
@ -600,7 +579,6 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
{/* Cidade */}
|
||||
<div className="col-md-4 mb-3">
|
||||
<label className="form-label">Cidade:</label>
|
||||
<input
|
||||
@ -611,7 +589,6 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
{/* Estado */}
|
||||
<div className="col-md-2 mb-3">
|
||||
<label className="form-label">Estado:</label>
|
||||
<input
|
||||
@ -622,7 +599,6 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
{/* Número */}
|
||||
<div className="col-md-4 mb-3">
|
||||
<label className="form-label">Número:</label>
|
||||
<input
|
||||
@ -633,7 +609,6 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
{/* Complemento */}
|
||||
<div className="col-md-8 mb-3">
|
||||
<label className="form-label">Complemento:</label>
|
||||
<input
|
||||
@ -649,35 +624,31 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
</div>
|
||||
|
||||
{/* HORÁRIOS */}
|
||||
<div className="form-section">
|
||||
<h4
|
||||
className="section-header"
|
||||
onClick={() => handleToggleCollapse("horarios")}
|
||||
>
|
||||
Horários de Atendimento
|
||||
<span className="section-toggle">
|
||||
{collapsedSections.horarios ? "▲" : "▼"}
|
||||
</span>
|
||||
</h4>
|
||||
<div
|
||||
className={`collapse${collapsedSections.horarios ? " show" : ""}`}
|
||||
>
|
||||
<div className="row mt-3">
|
||||
<div className="col-12 mb-3">
|
||||
<p className="form-label text-muted">
|
||||
Defina seus horários de atendimento para cada dia da semana.
|
||||
Marque um dia para começar a adicionar blocos de tempo.
|
||||
</p>
|
||||
<HorariosDisponibilidade
|
||||
initialAvailability={formData.availability}
|
||||
onUpdate={handleAvailabilityUpdate}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-section">
|
||||
<h4
|
||||
className="section-header"
|
||||
onClick={() => handleToggleCollapse("horarios")}
|
||||
>
|
||||
Horários
|
||||
<span className="section-toggle">
|
||||
{collapsedSections.horarios ? "▲" : "▼"}
|
||||
</span>
|
||||
</h4>
|
||||
|
||||
{/* BOTÕES DE AÇÃO */}
|
||||
<div className={`collapse${collapsedSections.horarios ? " show" : ""}`}>
|
||||
<HorariosDisponibilidade
|
||||
onChange={(dados) => {
|
||||
console.log("Disponibilidades atualizadas:", dados);
|
||||
// Se quiser salvar no formData:
|
||||
// setFormData(prev => ({ ...prev, disponibilidades: dados }));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/* BOTÕES DE AÇÃO */}
|
||||
<div className="actions-container">
|
||||
<button
|
||||
className="btn btn-success btn-submit"
|
||||
@ -691,7 +662,6 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
|
||||
<button className="btn btn-light btn-cancel">Cancelar</button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,401 +1,106 @@
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import { Clock } from "lucide-react";
|
||||
import React, { useState } from "react";
|
||||
|
||||
const initialBlockTemplate = {
|
||||
id: null,
|
||||
inicio: "09:00",
|
||||
termino: "17:00",
|
||||
isNew: true,
|
||||
};
|
||||
|
||||
const emptyAvailabilityTemplate = [
|
||||
{ dia: "Segunda-feira", isChecked: false, blocos: [] },
|
||||
{ dia: "Terça-feira", isChecked: false, blocos: [] },
|
||||
{ dia: "Quarta-feira", isChecked: false, blocos: [] },
|
||||
{ dia: "Quinta-feira", isChecked: false, blocos: [] },
|
||||
{ dia: "Sexta-feira", isChecked: false, blocos: [] },
|
||||
{ dia: "Sábado", isChecked: false, blocos: [] },
|
||||
{ dia: "Domingo", isChecked: false, blocos: [] },
|
||||
const diasDaSemana = [
|
||||
"segunda",
|
||||
"terca",
|
||||
"quarta",
|
||||
"quinta",
|
||||
"sexta",
|
||||
"sabado",
|
||||
"domingo"
|
||||
];
|
||||
|
||||
const HorariosDisponibilidade = ({
|
||||
initialAvailability = emptyAvailabilityTemplate,
|
||||
onUpdate,
|
||||
}) => {
|
||||
const [availability, setAvailability] = useState(initialAvailability);
|
||||
|
||||
useEffect(() => {
|
||||
if (initialAvailability !== emptyAvailabilityTemplate) {
|
||||
setAvailability(initialAvailability);
|
||||
}
|
||||
}, [initialAvailability]);
|
||||
|
||||
useEffect(() => {
|
||||
if (onUpdate) {
|
||||
onUpdate(availability);
|
||||
}
|
||||
}, [availability, onUpdate]);
|
||||
|
||||
const handleDayCheck = useCallback((dayIndex, currentIsChecked) => {
|
||||
const isChecked = !currentIsChecked;
|
||||
|
||||
setAvailability((prev) =>
|
||||
prev.map((day, i) =>
|
||||
i === dayIndex
|
||||
? {
|
||||
...day,
|
||||
isChecked,
|
||||
blocos: isChecked
|
||||
? day.blocos.length === 0
|
||||
? [
|
||||
{
|
||||
...initialBlockTemplate,
|
||||
id: Date.now() + Math.random(),
|
||||
isNew: true,
|
||||
},
|
||||
]
|
||||
: day.blocos
|
||||
: [],
|
||||
}
|
||||
: day
|
||||
)
|
||||
);
|
||||
}, []);
|
||||
|
||||
const handleAddBlock = useCallback((dayIndex) => {
|
||||
const tempId = Date.now() + Math.random();
|
||||
const newBlock = { ...initialBlockTemplate, id: tempId, isNew: true };
|
||||
|
||||
setAvailability((prev) =>
|
||||
prev.map((day, i) =>
|
||||
i === dayIndex
|
||||
? {
|
||||
...day,
|
||||
blocos: [...day.blocos, newBlock],
|
||||
isChecked: true,
|
||||
}
|
||||
: day
|
||||
)
|
||||
);
|
||||
}, []);
|
||||
|
||||
const handleRemoveBlock = useCallback((dayIndex, blockId) => {
|
||||
setAvailability((prev) =>
|
||||
prev.map((day, i) => {
|
||||
if (i === dayIndex) {
|
||||
const newBlocos = day.blocos.filter((bloco) => bloco.id !== blockId);
|
||||
return {
|
||||
...day,
|
||||
blocos: newBlocos,
|
||||
isChecked: newBlocos.length > 0,
|
||||
};
|
||||
}
|
||||
return day;
|
||||
})
|
||||
);
|
||||
}, []);
|
||||
|
||||
const handleTimeChange = useCallback((dayIndex, blockId, field, value) => {
|
||||
setAvailability((prev) =>
|
||||
prev.map((day, i) =>
|
||||
i === dayIndex
|
||||
? {
|
||||
...day,
|
||||
blocos: day.blocos.map((bloco) =>
|
||||
bloco.id === blockId ? { ...bloco, [field]: value } : bloco
|
||||
),
|
||||
}
|
||||
: day
|
||||
)
|
||||
);
|
||||
}, []);
|
||||
|
||||
const renderTimeBlock = (dayIndex, bloco) => (
|
||||
<div
|
||||
key={bloco.id}
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: window.innerWidth < 640 ? "column" : "row",
|
||||
alignItems: window.innerWidth < 640 ? "flex-start" : "center",
|
||||
justifyContent: "space-between",
|
||||
padding: "16px",
|
||||
marginBottom: "16px",
|
||||
borderRadius: "12px",
|
||||
boxShadow:
|
||||
"0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)",
|
||||
transition: "all 0.3s",
|
||||
backgroundColor: bloco.isNew ? "#eef2ff" : "#ffffff",
|
||||
border: bloco.isNew ? "2px solid #6366f1" : "1px solid #e5e7eb",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: window.innerWidth < 640 ? "column" : "row",
|
||||
gap: window.innerWidth < 640 ? "0" : "32px",
|
||||
width: window.innerWidth < 640 ? "100%" : "auto",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "8px",
|
||||
marginBottom: window.innerWidth < 640 ? "8px" : "0",
|
||||
}}
|
||||
>
|
||||
<label
|
||||
htmlFor={`inicio-${dayIndex}-${bloco.id}`}
|
||||
style={{ fontWeight: 500, color: "#4b5563", width: "64px" }}
|
||||
>
|
||||
Início:
|
||||
</label>
|
||||
<div style={{ position: "relative" }}>
|
||||
<input
|
||||
id={`inicio-${dayIndex}-${bloco.id}`}
|
||||
type="time"
|
||||
value={bloco.inicio}
|
||||
onChange={(e) =>
|
||||
handleTimeChange(dayIndex, bloco.id, "inicio", e.target.value)
|
||||
}
|
||||
style={{
|
||||
padding: "8px",
|
||||
border: "1px solid #d1d5db",
|
||||
borderRadius: "8px",
|
||||
width: "100%",
|
||||
boxSizing: "border-box",
|
||||
outline: "none",
|
||||
}}
|
||||
step="300"
|
||||
/>
|
||||
<Clock
|
||||
size={16}
|
||||
style={{
|
||||
position: "absolute",
|
||||
right: "12px",
|
||||
top: "50%",
|
||||
transform: "translateY(-50%)",
|
||||
color: "#9ca3af",
|
||||
pointerEvents: "none",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
||||
<label
|
||||
htmlFor={`termino-${dayIndex}-${bloco.id}`}
|
||||
style={{ fontWeight: 500, color: "#4b5563", width: "64px" }}
|
||||
>
|
||||
Término:
|
||||
</label>
|
||||
<div style={{ position: "relative" }}>
|
||||
<input
|
||||
id={`termino-${dayIndex}-${bloco.id}`}
|
||||
type="time"
|
||||
value={bloco.termino}
|
||||
onChange={(e) =>
|
||||
handleTimeChange(dayIndex, bloco.id, "termino", e.target.value)
|
||||
}
|
||||
style={{
|
||||
padding: "8px",
|
||||
border: "1px solid #d1d5db",
|
||||
borderRadius: "8px",
|
||||
width: "100%",
|
||||
boxSizing: "border-box",
|
||||
outline: "none",
|
||||
}}
|
||||
step="300"
|
||||
/>
|
||||
<Clock
|
||||
size={16}
|
||||
style={{
|
||||
position: "absolute",
|
||||
right: "12px",
|
||||
top: "50%",
|
||||
transform: "translateY(-50%)",
|
||||
color: "#9ca3af",
|
||||
pointerEvents: "none",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => handleRemoveBlock(dayIndex, bloco.id)}
|
||||
style={{
|
||||
marginTop: window.innerWidth < 640 ? "16px" : "0",
|
||||
padding: "8px 24px",
|
||||
backgroundColor: "#ef4444",
|
||||
color: "white",
|
||||
fontWeight: 600,
|
||||
borderRadius: "12px",
|
||||
boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1)",
|
||||
transition: "all 0.2s",
|
||||
width: window.innerWidth < 640 ? "100%" : "auto",
|
||||
cursor: "pointer",
|
||||
border: "none",
|
||||
opacity: 1,
|
||||
}}
|
||||
onMouseEnter={(e) =>
|
||||
(e.currentTarget.style.backgroundColor = "#dc2626")
|
||||
}
|
||||
onMouseLeave={(e) =>
|
||||
(e.currentTarget.style.backgroundColor = "#ef4444")
|
||||
}
|
||||
>
|
||||
Remover Bloco
|
||||
</button>
|
||||
{bloco.isNew && (
|
||||
<span
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
color: "#6366f1",
|
||||
marginTop: "8px",
|
||||
marginLeft: window.innerWidth < 640 ? "0" : "16px",
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
(Novo)
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
export default function HorariosDisponibilidade({ onChange }) {
|
||||
const [disponibilidades, setDisponibilidades] = useState(
|
||||
diasDaSemana.map(dia => ({
|
||||
weekday: dia,
|
||||
slots: [{ start_time: "09:00", end_time: "17:00" }],
|
||||
ativo: false,
|
||||
}))
|
||||
);
|
||||
|
||||
function handleToggleDia(index) {
|
||||
const updated = [...disponibilidades];
|
||||
updated[index].ativo = !updated[index].ativo;
|
||||
setDisponibilidades(updated);
|
||||
onChange?.(updated);
|
||||
}
|
||||
|
||||
function handleSlotChange(indexDia, indexSlot, campo, valor) {
|
||||
const updated = [...disponibilidades];
|
||||
updated[indexDia].slots[indexSlot][campo] = valor;
|
||||
setDisponibilidades(updated);
|
||||
onChange?.(updated);
|
||||
}
|
||||
|
||||
function handleAddSlot(indexDia) {
|
||||
const updated = [...disponibilidades];
|
||||
updated[indexDia].slots.push({ start_time: "", end_time: "" });
|
||||
setDisponibilidades(updated);
|
||||
onChange?.(updated);
|
||||
}
|
||||
|
||||
function handleRemoveSlot(indexDia, indexSlot) {
|
||||
const updated = [...disponibilidades];
|
||||
updated[indexDia].slots.splice(indexSlot, 1);
|
||||
setDisponibilidades(updated);
|
||||
onChange?.(updated);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
maxWidth: "960px",
|
||||
margin: "0 auto",
|
||||
fontFamily: "Inter, sans-serif",
|
||||
}}
|
||||
>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "16px" }}>
|
||||
{availability.map((day, dayIndex) => {
|
||||
const isChecked = day.isChecked;
|
||||
<div className="space-y-4">
|
||||
{disponibilidades.map((dia, i) => (
|
||||
<div key={dia.weekday} className="border p-3 rounded-md bg-gray-50">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="font-semibold capitalize">
|
||||
{dia.weekday}
|
||||
</label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={dia.ativo}
|
||||
onChange={() => handleToggleDia(i)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
const dayHeaderStyle = {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
padding: "12px 0",
|
||||
borderBottom: "1px solid #e5e7eb",
|
||||
marginBottom: "16px",
|
||||
backgroundColor: isChecked ? "#1f2937" : "#f9fafb",
|
||||
borderRadius: "8px",
|
||||
paddingLeft: "16px",
|
||||
paddingRight: "16px",
|
||||
cursor: "pointer",
|
||||
transition: "background-color 0.2s",
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
key={day.dia}
|
||||
style={{
|
||||
backgroundColor: "#f9fafb",
|
||||
padding: "20px",
|
||||
borderRadius: "12px",
|
||||
border: "1px solid #e5e7eb",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
...dayHeaderStyle,
|
||||
backgroundColor: isChecked ? "#1f2937" : "#f9fafb",
|
||||
borderBottom: isChecked
|
||||
? "1px solid #4b5563"
|
||||
: "1px solid #e5e7eb",
|
||||
}}
|
||||
onClick={() => handleDayCheck(dayIndex, isChecked)}
|
||||
>
|
||||
<label
|
||||
style={{
|
||||
fontSize: "18px",
|
||||
fontWeight: "bold",
|
||||
color: isChecked ? "white" : "#1f2937",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "12px",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
<span>{day.dia}</span>
|
||||
{dia.ativo && (
|
||||
<div className="mt-2 space-y-2">
|
||||
{dia.slots.map((slot, j) => (
|
||||
<div key={j} className="flex gap-2 items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isChecked}
|
||||
onChange={() => {}}
|
||||
style={{
|
||||
width: "20px",
|
||||
height: "20px",
|
||||
accentColor: isChecked ? "#3b82f6" : "#9ca3af",
|
||||
marginLeft: "8px",
|
||||
}}
|
||||
type="time"
|
||||
value={slot.start_time}
|
||||
onChange={e => handleSlotChange(i, j, "start_time", e.target.value)}
|
||||
className="border rounded p-1"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{isChecked && (
|
||||
<div style={{ marginTop: "16px" }}>
|
||||
{day.blocos.length === 0 && (
|
||||
<p
|
||||
style={{
|
||||
color: "#6b7280",
|
||||
fontStyle: "italic",
|
||||
marginBottom: "16px",
|
||||
}}
|
||||
<span>até</span>
|
||||
<input
|
||||
type="time"
|
||||
value={slot.end_time}
|
||||
onChange={e => handleSlotChange(i, j, "end_time", e.target.value)}
|
||||
className="border rounded p-1"
|
||||
/>
|
||||
{dia.slots.length > 1 && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleRemoveSlot(i, j)}
|
||||
className="text-red-600 hover:underline"
|
||||
>
|
||||
Nenhum bloco de horário definido.
|
||||
</p>
|
||||
Remover
|
||||
</button>
|
||||
)}
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "16px",
|
||||
}}
|
||||
>
|
||||
{day.blocos.map((bloco) =>
|
||||
renderTimeBlock(dayIndex, bloco)
|
||||
)}
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => handleAddBlock(dayIndex)}
|
||||
style={{
|
||||
marginTop: "24px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
padding: "12px 24px",
|
||||
backgroundColor: "#10b981",
|
||||
color: "white",
|
||||
fontWeight: "bold",
|
||||
borderRadius: "12px",
|
||||
boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1)",
|
||||
transition: "all 0.3s",
|
||||
cursor: "pointer",
|
||||
border: "none",
|
||||
}}
|
||||
onMouseEnter={(e) =>
|
||||
(e.currentTarget.style.backgroundColor = "#059669")
|
||||
}
|
||||
onMouseLeave={(e) =>
|
||||
(e.currentTarget.style.backgroundColor = "#10b981")
|
||||
}
|
||||
>
|
||||
+ Adicionar novo bloco
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
))}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleAddSlot(i)}
|
||||
className="text-blue-600 hover:underline mt-2"
|
||||
>
|
||||
+ Adicionar pausa/bloco
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default HorariosDisponibilidade;
|
||||
}
|
||||
|
||||
@ -6,7 +6,6 @@ import TabelaAgendamentoDia from '../components/AgendarConsulta/TabelaAgendament
|
||||
import TabelaAgendamentoSemana from '../components/AgendarConsulta/TabelaAgendamentoSemana';
|
||||
import TabelaAgendamentoMes from '../components/AgendarConsulta/TabelaAgendamentoMes';
|
||||
import FormNovaConsulta from '../components/AgendarConsulta/FormNovaConsulta';
|
||||
// Importação de endpoints para lógica da Fila de Espera e Médicos (versão main)
|
||||
import { GetPatientByID } from '../components/utils/Functions-Endpoints/Patient.js';
|
||||
import { GetAllDoctors, GetDoctorByID } from '../components/utils/Functions-Endpoints/Doctor.js';
|
||||
|
||||
@ -14,6 +13,7 @@ import { useAuth } from '../components/utils/AuthProvider.js';
|
||||
// ✨ NOVO: Caminho de importação corrigido com base na sua estrutura de pastas
|
||||
import AgendamentosMes from '../components/AgendarConsulta/DadosConsultasMock.js';
|
||||
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
import "./style/Agendamento.css";
|
||||
import './style/FilaEspera.css';
|
||||
@ -21,22 +21,21 @@ import { Search } from 'lucide-react';
|
||||
|
||||
|
||||
|
||||
const Agendamento = ({setDictInfo}) => { // Mantido setDictInfo (versão main)
|
||||
const Agendamento = ({setDictInfo}) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
// Estados mesclados
|
||||
const [selectedID, setSelectedId] = useState('0') // (main)
|
||||
const [filaEsperaData, setfilaEsperaData] = useState([]) // (main)
|
||||
const [FiladeEspera, setFiladeEspera] = useState(false);
|
||||
const [selectedID, setSelectedId] = useState('0')
|
||||
const [filaEsperaData, setfilaEsperaData] = useState([])
|
||||
const [FiladeEspera, setFiladeEspera] = useState(false);
|
||||
const [tabela, setTabela] = useState('diario');
|
||||
const [PageNovaConsulta, setPageConsulta] = useState(false);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [agendamentos, setAgendamentos] = useState() // (main)
|
||||
const [agendamentos, setAgendamentos] = useState()
|
||||
const {getAuthorizationHeader} = useAuth()
|
||||
const [DictAgendamentosOrganizados, setAgendamentosOrganizados ] = useState({})
|
||||
const [DictAgendamentosOrganizados, setAgendamentosOrganizados ] = useState({})
|
||||
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false)
|
||||
const [AgendamentoFiltrado, setAgendamentoFiltrado] = useState() // (main)
|
||||
const [AgendamentoFiltrado, setAgendamentoFiltrado] = useState()
|
||||
|
||||
const [ListaDeMedicos, setListaDeMedicos] = useState([])
|
||||
const [FiltredTodosMedicos, setFiltredTodosMedicos] = useState([])
|
||||
@ -45,7 +44,6 @@ const Agendamento = ({setDictInfo}) => { // Mantido setDictInfo (versão main)
|
||||
|
||||
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 ConfigurarFiladeEspera = async (patient_id, doctor_id, agendamento) => {
|
||||
// Assumindo que GetDoctorByID e GetPatientByID estão definidos no seu escopo
|
||||
@ -68,9 +66,11 @@ const Agendamento = ({setDictInfo}) => { // Mantido setDictInfo (versão main)
|
||||
let DictAgendamentosOrganizados = {};
|
||||
let ListaFilaDeEspera = [];
|
||||
|
||||
|
||||
// 1. Agrupamento (igual ao seu código original)
|
||||
for (const agendamento of listaTodosAgendamentos) {
|
||||
if (agendamento.status === 'requested') {
|
||||
// Recomenda-se usar Promise.all para melhorar a performance
|
||||
// mas, para manter a estrutura, mantemos o await no loop.
|
||||
let v = await ConfigurarFiladeEspera(agendamento.patient_id, agendamento.doctor_id, agendamento);
|
||||
ListaFilaDeEspera.push(v);
|
||||
} else {
|
||||
@ -84,30 +84,42 @@ const Agendamento = ({setDictInfo}) => { // Mantido setDictInfo (versão main)
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// 2. Ordenação Interna: Ordenar os agendamentos por HORÁRIO (do menor para o maior)
|
||||
for (const DiaAgendamento in DictAgendamentosOrganizados) {
|
||||
DictAgendamentosOrganizados[DiaAgendamento].sort((a, b) => {
|
||||
// Compara as strings de data/hora (ISO 8601) diretamente,
|
||||
// que funcionam para ordenação cronológica.
|
||||
if (a.scheduled_at < b.scheduled_at) return -1;
|
||||
if (a.scheduled_at > b.scheduled_at) return 1;
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// 3. Ordenação Externa: Ordenar os DIAS (as chaves do objeto)
|
||||
// Para garantir que as chaves fiquem na sequência cronológica correta.
|
||||
|
||||
// Pega as chaves (datas)
|
||||
const chavesOrdenadas = Object.keys(DictAgendamentosOrganizados).sort((a, b) => {
|
||||
// Compara as chaves de data (strings 'YYYY-MM-DD')
|
||||
if (a < b) return -1;
|
||||
if (a > b) return 1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
// Cria um novo objeto no formato desejado, garantindo a ordem das chaves
|
||||
let DictAgendamentosFinal = {};
|
||||
for (const data of chavesOrdenadas) {
|
||||
DictAgendamentosFinal[data] = DictAgendamentosOrganizados[data];
|
||||
}
|
||||
setAgendamentosOrganizados(DictAgendamentosFinal);
|
||||
setAgendamentosOrganizados(DictAgendamentosFinal); // Use o objeto final ordenado
|
||||
setfilaEsperaData(ListaFilaDeEspera);
|
||||
};
|
||||
|
||||
// Requisição inicial para mostrar os agendamentos do banco de dados
|
||||
useEffect(() => {
|
||||
var myHeaders = new Headers();
|
||||
var myHeaders = new Headers();
|
||||
myHeaders.append("Authorization", authHeader);
|
||||
myHeaders.append("apikey", API_KEY)
|
||||
|
||||
@ -133,6 +145,7 @@ const Agendamento = ({setDictInfo}) => { // Mantido setDictInfo (versão main)
|
||||
PegarTodosOsMedicos()
|
||||
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
console.log("mudou FiltredTodosMedicos:", FiltredTodosMedicos);
|
||||
if (FiltredTodosMedicos.length === 1) {
|
||||
@ -170,10 +183,21 @@ fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?id=eq.${sel
|
||||
.catch(error => console.log('error', error));
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Filtra todos os agendamentos em um objeto aninhado (data -> [agendamentos])
|
||||
* com base no ID do médico.
|
||||
*
|
||||
* @param {Object} dictAgendamentos - O dicionário de agendamentos.
|
||||
* @param {string} idMedicoFiltrado - O ID do médico (doctor_id) para ser usado como filtro.
|
||||
* @returns {Array} Um array contendo todos os agendamentos que correspondem ao idMedicoFiltrado.
|
||||
*/
|
||||
const filtrarAgendamentosPorMedico = (dictAgendamentos, idMedicoFiltrado) => {
|
||||
|
||||
// O corpo da função deve usar esses nomes de variáveis:
|
||||
const todasAsListasDeAgendamentos = Object.values(dictAgendamentos);
|
||||
|
||||
const todosOsAgendamentos = todasAsListasDeAgendamentos.flat();
|
||||
|
||||
const agendamentosFiltrados = todosOsAgendamentos.filter(agendamento =>
|
||||
@ -185,6 +209,7 @@ const filtrarAgendamentosPorMedico = (dictAgendamentos, idMedicoFiltrado) => {
|
||||
|
||||
|
||||
|
||||
// Lógica para filtrar os dados da AGENDA (AgendamentosMes)
|
||||
const filteredAgendamentos = useMemo(() => {
|
||||
if (!searchTerm.trim()) {
|
||||
return AgendamentosMes;
|
||||
@ -224,7 +249,6 @@ const filtrarAgendamentosPorMedico = (dictAgendamentos, idMedicoFiltrado) => {
|
||||
return ListaDiasDatas
|
||||
}
|
||||
|
||||
|
||||
const handleClickAgendamento = (agendamento) => {
|
||||
if (agendamento.status !== 'vazio') return
|
||||
else setPageConsulta(true)
|
||||
@ -238,6 +262,7 @@ const handleSearchMedicos = (term) => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Lógica simples de filtragem:
|
||||
const filtered = ListaDeMedicos.filter(medico =>
|
||||
medico.nomeMedico.toLowerCase().includes(term.toLowerCase())
|
||||
);
|
||||
@ -250,64 +275,56 @@ const handleSearchMedicos = (term) => {
|
||||
return (
|
||||
<div>
|
||||
<h1>Agendar nova consulta</h1>
|
||||
|
||||
|
||||
<div className="btns-gerenciamento-e-consulta" style={{ display: 'flex', gap: '10px', marginBottom: '20px' }}>
|
||||
<button className="btn btn-primary" onClick={() => setPageConsulta(true)}>
|
||||
<i className="bi bi-plus-circle"></i> Adicionar Consulta
|
||||
</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 className='manage-button btn' onClick={() => navigate('/secretaria/disponibilidade')}>
|
||||
<i className="bi bi-gear-fill me-1"></i>
|
||||
Mudar Disponibilidade
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button className='manage-button btn' onClick={() => navigate('/secretaria/excecoes-disponibilidade')}>
|
||||
<i className="bi bi-gear-fill me-1"></i>
|
||||
Mudar Disponibilidade
|
||||
</button>
|
||||
|
||||
<button className="btn btn-primary" onClick={() => setPageConsulta(true)}>
|
||||
<i className="bi bi-plus-circle"></i> Adicionar Consulta
|
||||
</button>
|
||||
{!PageNovaConsulta ? (
|
||||
<div className='atendimento-eprocura'>
|
||||
<div className='unidade-selecionarprofissional'>
|
||||
|
||||
{/* Bloco de busca por médico */}
|
||||
<div className='busca-atendimento-container'>
|
||||
<div className='input-e-dropdown-wrapper'>
|
||||
<div className='busca-atendimento'>
|
||||
<div>
|
||||
<i className="fa-solid fa-calendar-day"></i>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Filtrar atendimento por médico..."
|
||||
value={searchTermDoctor}
|
||||
onChange={(e) => handleSearchMedicos(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='input-e-dropdown-wrapper'>
|
||||
|
||||
<div className='busca-atendimento'>
|
||||
<div>
|
||||
<i className="fa-solid fa-calendar-day"></i>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Filtrar atendimento por médico..."
|
||||
value={searchTermDoctor}
|
||||
onChange={(e) => handleSearchMedicos(e.target.value)} // Chama a nova função de filtro
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* DROPDOWN (RENDERIZAÇÃO CONDICIONAL) */}
|
||||
{searchTermDoctor && FiltredTodosMedicos.length > 0 && (
|
||||
<div className='dropdown-medicos'>
|
||||
{FiltredTodosMedicos.map((medico) => (
|
||||
<div
|
||||
key={medico.id}
|
||||
className='dropdown-item'
|
||||
onClick={() => {
|
||||
setSearchTermDoctor(medico.nomeMedico);
|
||||
}}
|
||||
>
|
||||
<p>{medico.nomeMedico} </p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* DROPDOWN (RENDERIZAÇÃO CONDICIONAL) */}
|
||||
{searchTermDoctor && FiltredTodosMedicos.length > 0 && (
|
||||
<div className='dropdown-medicos'>
|
||||
{FiltredTodosMedicos.map((medico) => (
|
||||
<div
|
||||
key={medico.id}
|
||||
className='dropdown-item'
|
||||
onClick={() => {
|
||||
// Ação ao selecionar o médico
|
||||
setSearchTermDoctor(medico.nomeMedico); // Preenche o input
|
||||
//setFiltredTodosMedicos([]); // Fecha o dropdown
|
||||
// Lógica adicional, como selecionar o ID do médico...
|
||||
}}
|
||||
>
|
||||
<p>{medico.nomeMedico} </p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@ -357,7 +374,6 @@ const handleSearchMedicos = (term) => {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Componentes de Tabela - Adicionado props de delete da main */}
|
||||
{tabela === "diario" && <TabelaAgendamentoDia handleClickAgendamento={handleClickAgendamento} agendamentos={DictAgendamentosOrganizados} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo} />}
|
||||
{tabela === 'semanal' && <TabelaAgendamentoSemana agendamentos={DictAgendamentosOrganizados} ListarDiasdoMes={ListarDiasdoMes} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo}/>}
|
||||
{tabela === 'mensal' && <TabelaAgendamentoMes ListarDiasdoMes={ListarDiasdoMes} aplicarCores={true} agendamentos={DictAgendamentosOrganizados} setShowDeleteModal={setShowDeleteModal} setSelectedId={setSelectedId} setDictInfo={setDictInfo} />}
|
||||
@ -380,10 +396,11 @@ const handleSearchMedicos = (term) => {
|
||||
<table className="fila-tabela">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nome do Paciente</th> {/* Ajustado o cabeçalho */}
|
||||
<th>CPF</th> {/* Ajustado o cabeçalho */}
|
||||
<th>Médico Solicitado</th> {/* Ajustado o cabeçalho */}
|
||||
<th>Data da Solicitação</th> {/* Ajustado o cabeçalho */}
|
||||
<th>Nome</th>
|
||||
<th>Telefone</th>
|
||||
|
||||
<th>Telefone</th>
|
||||
<th>Entrou na fila de espera</th>
|
||||
<th>Ações</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -391,15 +408,15 @@ const handleSearchMedicos = (term) => {
|
||||
{filaEsperaData.map((item, index) => (
|
||||
<tr key={index}>
|
||||
<td> <p>{item.Infos?.paciente_nome} </p> </td>
|
||||
<td><p>{item.Infos?.paciente_cpf} </p></td>
|
||||
<td><p>{item.Infos?.nome_nedico} </p></td>
|
||||
<td>{dayjs(item.agendamento.created_at).format('DD/MM/YYYY HH:mm')}</td>
|
||||
<td><p>{} </p></td>
|
||||
<td>{}</td>
|
||||
<td>{}</td>
|
||||
<td> <div className="d-flex gap-2">
|
||||
|
||||
<button className="btn btn-sm btn-edit"
|
||||
onClick={() => {
|
||||
console.log(item, 'item')
|
||||
navigate(`${2}/edit`)
|
||||
navigate(`${2}/edit`)
|
||||
setDictInfo(item)
|
||||
}}
|
||||
>
|
||||
@ -430,7 +447,6 @@ const handleSearchMedicos = (term) => {
|
||||
<AgendamentoCadastroManager setPageConsulta={setPageConsulta} />
|
||||
)}
|
||||
|
||||
{/* Modal de Confirmação de Exclusão (Da MAIN) */}
|
||||
{showDeleteModal && (
|
||||
<div
|
||||
className="modal fade show"
|
||||
|
||||
@ -1,145 +0,0 @@
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
const ENDPOINT_LISTAR = "https://mock.apidog.com/m1/1053378-0-default/rest/v1/doctor_availability";
|
||||
|
||||
const DisponibilidadesDoctorPage = () => {
|
||||
const [disponibilidades, setDisponibilidades] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [filtroMedicoId, setFiltroMedicoId] = useState("");
|
||||
const [filtroActive, setFiltroActive] = useState("true");
|
||||
const [medicoValido, setMedicoValido] = useState(false);
|
||||
|
||||
const fetchDisponibilidades = useCallback(async (doctorId, activeStatus) => {
|
||||
setLoading(true);
|
||||
let url = `${ENDPOINT_LISTAR}?select=*`;
|
||||
if (doctorId) url += `&doctor_id=eq.${doctorId}`;
|
||||
if (activeStatus === "true" || activeStatus === "false") url += `&active=eq.${activeStatus}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
const result = await response.json();
|
||||
setDisponibilidades(Array.isArray(result) ? result : []);
|
||||
setMedicoValido(Array.isArray(result) && result.length > 0);
|
||||
} catch (error) {
|
||||
setDisponibilidades([]);
|
||||
setMedicoValido(false);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (filtroMedicoId) {
|
||||
fetchDisponibilidades(filtroMedicoId, filtroActive);
|
||||
} else {
|
||||
setDisponibilidades([]);
|
||||
setMedicoValido(false);
|
||||
}
|
||||
}, [filtroMedicoId, filtroActive, fetchDisponibilidades]);
|
||||
|
||||
return (
|
||||
<div id="main-content">
|
||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||
<h1 style={{ fontSize: "1.5rem", fontWeight: "bold", color: "#333" }}>
|
||||
Disponibilidades por Médico
|
||||
</h1>
|
||||
<Link
|
||||
to={medicoValido ? `../medicos/${filtroMedicoId}/edit` : "#"}
|
||||
className="btn-primary"
|
||||
style={{
|
||||
padding: "10px 20px",
|
||||
fontSize: "14px",
|
||||
whiteSpace: "nowrap",
|
||||
textDecoration: "none",
|
||||
display: "inline-block",
|
||||
opacity: medicoValido ? 1 : 0.6,
|
||||
pointerEvents: medicoValido ? "auto" : "none",
|
||||
}}
|
||||
>
|
||||
+ Gerenciar Disponibilidades
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="atendimento-eprocura">
|
||||
<div className="busca-atendimento">
|
||||
<div>
|
||||
<i className="fa-solid fa-user-doctor"></i>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Filtrar por ID do Médico..."
|
||||
value={filtroMedicoId}
|
||||
onChange={(e) => setFiltroMedicoId(e.target.value)}
|
||||
style={{ border: "1px solid #ccc", borderRadius: "4px", padding: "5px" }}
|
||||
/>
|
||||
</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>
|
||||
|
||||
<section className="calendario-ou-filaespera">
|
||||
<div className="fila-container">
|
||||
<h2 className="fila-titulo">
|
||||
Disponibilidades Encontradas ({disponibilidades.length})
|
||||
</h2>
|
||||
|
||||
{loading ? (
|
||||
<p className="text-center py-10">Carregando disponibilidades...</p>
|
||||
) : disponibilidades.length === 0 ? (
|
||||
<p className="text-center py-10">
|
||||
Nenhuma disponibilidade encontrada para os filtros aplicados.
|
||||
</p>
|
||||
) : (
|
||||
<table className="fila-tabela" style={{ width: "100%", borderCollapse: "collapse" }}>
|
||||
<thead>
|
||||
<tr>
|
||||
{["ID", "ID Médico", "Dia da Semana", "Início", "Término", "Intervalo (min)", "Tipo Consulta", "Status"].map(
|
||||
(header) => (
|
||||
<th
|
||||
key={header}
|
||||
style={{ padding: "10px", borderBottom: "2px solid #ddd", textAlign: "left" }}
|
||||
>
|
||||
{header}
|
||||
</th>
|
||||
)
|
||||
)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{disponibilidades.map((disp, index) => (
|
||||
<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" }}>{disp.doctor_id}</td>
|
||||
<td style={{ padding: "10px", fontSize: "0.9em" }}>{disp.weekday}</td>
|
||||
<td style={{ padding: "10px", fontSize: "0.9em" }}>{disp.start_time || "N/A"}</td>
|
||||
<td style={{ padding: "10px", fontSize: "0.9em" }}>{disp.end_time || "N/A"}</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" }}>
|
||||
<span className={`status-tag ${disp.active ? "legenda-item-realizado" : "legenda-item-cancelado"}`}>
|
||||
{disp.active ? "Ativa" : "Inativa"}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DisponibilidadesDoctorPage;
|
||||
@ -1,146 +1,77 @@
|
||||
import React, { useEffect, useState, useCallback } from "react";
|
||||
import { useParams, useSearchParams } from "react-router-dom";
|
||||
import { GetDoctorByID } from "../components/utils/Functions-Endpoints/Doctor";
|
||||
import DoctorForm from "../components/doctors/DoctorForm";
|
||||
import { useAuth } from "../components/utils/AuthProvider";
|
||||
import API_KEY from "../components/utils/apiKeys";
|
||||
|
||||
const ENDPOINT_AVAILABILITY =
|
||||
"https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctor_availability";
|
||||
|
||||
import React from 'react'
|
||||
import { GetDoctorByID } from '../components/utils/Functions-Endpoints/Doctor'
|
||||
import DoctorForm from '../components/doctors/DoctorForm'
|
||||
import { useAuth } from '../components/utils/AuthProvider'
|
||||
import {useEffect, useState} from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import API_KEY from '../components/utils/apiKeys'
|
||||
const DoctorEditPage = () => {
|
||||
const { getAuthorizationHeader } = useAuth();
|
||||
const [DoctorToPUT, setDoctorPUT] = useState({});
|
||||
const {getAuthorizationHeader, isAuthenticated} = useAuth();
|
||||
const [DoctorToPUT, setDoctorPUT] = useState({})
|
||||
|
||||
const Parametros = useParams()
|
||||
|
||||
const Parametros = useParams();
|
||||
const [searchParams] = useSearchParams();
|
||||
const DoctorID = Parametros.id;
|
||||
const availabilityId = searchParams.get("availabilityId");
|
||||
const DoctorID = Parametros.id
|
||||
|
||||
const [availabilityToPATCH, setAvailabilityToPATCH] = useState(null);
|
||||
const [mode, setMode] = useState("doctor");
|
||||
useEffect(() => {
|
||||
|
||||
const authHeader = getAuthorizationHeader()
|
||||
|
||||
useEffect(() => {
|
||||
const authHeader = getAuthorizationHeader();
|
||||
|
||||
if (availabilityId) {
|
||||
setMode("availability");
|
||||
|
||||
fetch(`${ENDPOINT_AVAILABILITY}?id=eq.${availabilityId}&select=*`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
apikey: API_KEY,
|
||||
Authorization: authHeader,
|
||||
},
|
||||
GetDoctorByID(DoctorID, authHeader)
|
||||
.then((data) => {
|
||||
console.log(data, "médico vindo da API");
|
||||
setDoctorPUT(data[0])
|
||||
; // supabase retorna array
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
if (data && data.length > 0) {
|
||||
setAvailabilityToPATCH(data[0]);
|
||||
console.log("Disponibilidade vinda da API:", data[0]);
|
||||
}
|
||||
})
|
||||
.catch((err) => console.error("Erro ao buscar disponibilidade:", err));
|
||||
} else {
|
||||
setMode("doctor");
|
||||
GetDoctorByID(DoctorID, authHeader)
|
||||
.then((data) => {
|
||||
console.log(data, "médico vindo da API");
|
||||
setDoctorPUT(data[0]);
|
||||
})
|
||||
.catch((err) => console.error("Erro ao buscar paciente:", err));
|
||||
}
|
||||
}, [DoctorID, availabilityId, getAuthorizationHeader]);
|
||||
.catch((err) => console.error("Erro ao buscar paciente:", err));
|
||||
|
||||
|
||||
}, [])
|
||||
const HandlePutDoctor = async () => {
|
||||
const authHeader = getAuthorizationHeader();
|
||||
const authHeader = getAuthorizationHeader()
|
||||
|
||||
|
||||
var myHeaders = new Headers();
|
||||
myHeaders.append("apikey", API_KEY);
|
||||
myHeaders.append("Authorization", authHeader);
|
||||
myHeaders.append("Content-Type", "application/json");
|
||||
var myHeaders = new Headers();
|
||||
myHeaders.append('apikey', API_KEY)
|
||||
myHeaders.append("Authorization", authHeader);
|
||||
myHeaders.append("Content-Type", "application/json");
|
||||
|
||||
var raw = JSON.stringify(DoctorToPUT);
|
||||
var raw = JSON.stringify(DoctorToPUT);
|
||||
|
||||
console.log("Enviando médico para atualização (PUT):", DoctorToPUT);
|
||||
console.log("Enviando médico para atualização:", DoctorToPUT);
|
||||
|
||||
var requestOptions = {
|
||||
method: "PUT",
|
||||
headers: myHeaders,
|
||||
body: raw,
|
||||
redirect: "follow",
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors?id=eq.${DoctorID}`,
|
||||
requestOptions
|
||||
);
|
||||
console.log("Resposta PUT Doutor:", response);
|
||||
alert("Dados do médico atualizados com sucesso!");
|
||||
} catch (error) {
|
||||
console.error("Erro ao atualizar médico:", error);
|
||||
alert("Erro ao atualizar dados do médico.");
|
||||
throw error;
|
||||
}
|
||||
var requestOptions = {
|
||||
method: 'PUT',
|
||||
headers: myHeaders,
|
||||
body: raw,
|
||||
redirect: 'follow'
|
||||
};
|
||||
|
||||
// 2. Função para Atualizar DISPONIBILIDADE (PATCH)
|
||||
const HandlePatchAvailability = async (data) => {
|
||||
const authHeader = getAuthorizationHeader();
|
||||
try {
|
||||
const response = await fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors?id=eq.${DoctorID}`,requestOptions);
|
||||
console.log(response)
|
||||
|
||||
} catch (error) {
|
||||
console.error("Erro ao atualizar paciente:", error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
var myHeaders = new Headers();
|
||||
myHeaders.append("apikey", API_KEY);
|
||||
myHeaders.append("Authorization", authHeader);
|
||||
myHeaders.append("Content-Type", "application/json");
|
||||
}
|
||||
|
||||
var raw = JSON.stringify(data);
|
||||
|
||||
console.log("Enviando disponibilidade para atualização (PATCH):", data);
|
||||
|
||||
var requestOptions = {
|
||||
method: "PATCH",
|
||||
headers: myHeaders,
|
||||
body: raw,
|
||||
redirect: "follow",
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${ENDPOINT_AVAILABILITY}?id=eq.${availabilityId}`,
|
||||
requestOptions
|
||||
);
|
||||
console.log("Resposta PATCH Disponibilidade:", response);
|
||||
alert("Disponibilidade atualizada com sucesso!");
|
||||
// Opcional: Redirecionar de volta para a lista de disponibilidades
|
||||
// navigate('/disponibilidades');
|
||||
} catch (error) {
|
||||
console.error("Erro ao atualizar disponibilidade:", error);
|
||||
alert("Erro ao atualizar disponibilidade.");
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold mb-4">
|
||||
{mode === "availability"
|
||||
? `Editar Horário Disponível (ID: ${availabilityId.substring(0, 8)})`
|
||||
: `Editar Médico (ID: ${DoctorID})`}
|
||||
</h1>
|
||||
|
||||
<DoctorForm
|
||||
onSave={
|
||||
mode === "availability" ? HandlePatchAvailability : HandlePutDoctor
|
||||
}
|
||||
formData={mode === "availability" ? availabilityToPATCH : DoctorToPUT}
|
||||
setFormData={
|
||||
mode === "availability" ? setAvailabilityToPATCH : setDoctorPUT
|
||||
}
|
||||
isEditingAvailability={mode === "availability"}
|
||||
/>
|
||||
<DoctorForm
|
||||
onSave={HandlePutDoctor}
|
||||
|
||||
formData={DoctorToPUT}
|
||||
setFormData={setDoctorPUT}
|
||||
|
||||
/>
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default DoctorEditPage;
|
||||
export default DoctorEditPage
|
||||
@ -2,7 +2,7 @@
|
||||
import { Routes, Route } from "react-router-dom";
|
||||
import { useState } from "react";
|
||||
import Sidebar from "../../components/Sidebar";
|
||||
import HorariosDisponibilidade from "../../components/doctors/HorariosDisponibilidade";
|
||||
import FinanceiroDashboard from "../../pages/FinanceiroDashboard";
|
||||
import SecretariaItems from "../../data/sidebar-items-secretaria.json";
|
||||
import Inicio from "../../pages/Inicio";
|
||||
import TablePaciente from "../../pages/TablePaciente";
|
||||
@ -16,7 +16,6 @@ import EditPage from "../../pages/EditPage";
|
||||
import DoctorDetails from "../../pages/DoctorDetails";
|
||||
import DoctorEditPage from "../../pages/DoctorEditPage";
|
||||
import ExcecoesDisponibilidade from "../../pages/ExcecoesDisponibilidade";
|
||||
import DisponibilidadesDoctorPage from "../../pages/DisponibilidadesDoctorPage"
|
||||
import AgendamentoEditPage from "../../pages/AgendamentoEditPage";
|
||||
|
||||
function PerfilSecretaria({ onLogout }) {
|
||||
@ -38,11 +37,9 @@ function PerfilSecretaria({ onLogout }) {
|
||||
<Route path="medicos/:id/edit" element={<DoctorEditPage />} />
|
||||
<Route path="agendamento" element={<Agendamento setDictInfo={setDictInfo}/>} />
|
||||
<Route path="agendamento/:id/edit" element={<AgendamentoEditPage setDictInfo={setDictInfo} DictInfo={DictInfo}/>} />
|
||||
<Route path="laudo" element={<LaudoManager />} />
|
||||
<Route path="disponibilidade" element={<DisponibilidadesDoctorPage />} />
|
||||
<Route path="horarios" element={<HorariosDisponibilidade/>}/>
|
||||
<Route path="excecoes-disponibilidade" element={<ExcecoesDisponibilidade />} />
|
||||
<Route path="laudo" element={<LaudoManager />} />
|
||||
<Route path="*" element={<h2>Página não encontrada</h2>} />
|
||||
<Route path="excecoes-disponibilidade" element={<ExcecoesDisponibilidade />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user