riseup-squad23/src/pages/DisponibilidadesDoctorPage.jsx

442 lines
16 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useEffect, useMemo } from "react";
import HorariosDisponibilidade from "../components/doctors/HorariosDisponibilidade";
import { useAuth } from "../components/utils/AuthProvider";
import API_KEY from "../components/utils/apiKeys";
import "./style/DisponibilidadesDoctorPage.css";
const ENDPOINT = "https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctor_availability";
const DOCTORS_ENDPOINT = "https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors";
const diasDaSemana = [
"Domingo",
"Segunda",
"Terça",
"Quarta",
"Quinta",
"Sexta",
"Sábado"
];
const weekdayNumToStr = {
0: "sunday",
1: "monday",
2: "tuesday",
3: "wednesday",
4: "thursday",
5: "friday",
6: "saturday",
};
const weekdayStrToNum = Object.fromEntries(
Object.entries(weekdayNumToStr).map(([num, str]) => [str, Number(num)])
);
const DisponibilidadesDoctorPage = () => {
const { getAuthorizationHeader } = useAuth();
const [disponibilidades, setDisponibilidades] = useState([]);
const [doctors, setDoctors] = useState([]);
const [searchTerm, setSearchTerm] = useState("");
const [editando, setEditando] = useState(null);
const [expandedDoctors, setExpandedDoctors] = useState({});
const [showSuggestions, setShowSuggestions] = useState(false);
const [availabilityEdit, setAvailabilityEdit] = useState([]);
const getHeaders = () => {
const myHeaders = new Headers();
const authHeader = getAuthorizationHeader();
if (authHeader) myHeaders.append("Authorization", authHeader);
myHeaders.append("Content-Type", "application/json");
if (API_KEY) myHeaders.append("apikey", API_KEY);
myHeaders.append("Prefer", "return=representation");
return myHeaders;
};
useEffect(() => {
const fetchDoctors = async () => {
try {
const requestOptions = {
method: "GET",
headers: getHeaders(),
};
const response = await fetch(DOCTORS_ENDPOINT, requestOptions);
const result = await response.json();
setDoctors(Array.isArray(result) ? result : []);
} catch (error) {
setDoctors([]);
}
};
fetchDoctors();
}, [getAuthorizationHeader]);
useEffect(() => {
const fetchDisponibilidades = async () => {
try {
const res = await fetch(ENDPOINT, { method: "GET", headers: getHeaders() });
if (res.ok) {
const data = await res.json();
setDisponibilidades(Array.isArray(data) ? data : []);
}
} catch (error) {
setDisponibilidades([]);
}
};
fetchDisponibilidades();
}, [getAuthorizationHeader]);
const toggleExpandDoctor = (doctorId) => {
setExpandedDoctors((prev) => ({ ...prev, [doctorId]: !prev[doctorId] }));
};
const salvarTodasDisponibilidades = async (doctorId, horariosAtualizados) => {
try {
const headers = getHeaders();
const promises = [];
const currentIds = new Set();
for (const dia of horariosAtualizados) {
if (dia.isChecked && dia.blocos.length > 0) {
for (const bloco of dia.blocos) {
const inicio = bloco.inicio.includes(":") ? bloco.inicio : bloco.inicio + ":00";
const termino = bloco.termino.includes(":") ? bloco.termino : bloco.termino + ":00";
const payload = {
doctor_id: doctorId,
weekday: weekdayNumToStr[dia.weekday],
start_time: inicio,
end_time: termino,
slot_minutes: bloco.slot_minutes || 30,
appointment_type: bloco.appointment_type || "presencial",
active: true,
};
if (bloco.id && !bloco.isNew) {
currentIds.add(bloco.id);
promises.push(
fetch(`${ENDPOINT}?id=eq.${bloco.id}`, {
method: "PATCH",
headers,
body: JSON.stringify(payload),
}).then(() => ({ type: 'PATCH', id: bloco.id }))
);
} else {
promises.push(
fetch(ENDPOINT, {
method: "POST",
headers,
body: JSON.stringify(payload),
})
.then(res => res.json())
.then(data => {
const createdItem = Array.isArray(data) ? data[0] : data;
return { type: 'POST', id: createdItem?.id };
})
);
}
}
}
}
const results = await Promise.all(promises);
results.forEach(res => {
if (res.type === 'POST' && res.id) currentIds.add(res.id);
});
const existingRes = await fetch(`${ENDPOINT}?doctor_id=eq.${String(doctorId)}`, {
method: "GET", headers
});
if (existingRes.ok) {
const existingData = await existingRes.json();
const deletePromises = existingData
.filter(dbItem => !currentIds.has(dbItem.id))
.map(dbItem =>
fetch(`${ENDPOINT}?id=eq.${dbItem.id}`, { method: "DELETE", headers })
);
await Promise.all(deletePromises);
}
setEditando(null);
setAvailabilityEdit([]);
const res = await fetch(ENDPOINT, { method: "GET", headers: getHeaders() });
if (res.ok) {
const data = await res.json();
setDisponibilidades(Array.isArray(data) ? data : []);
}
} catch (error) {
console.error(error);
}
};
const deletarDisponibilidade = async (id) => {
if (!window.confirm("Deseja realmente excluir esta disponibilidade?")) return;
try {
const res = await fetch(`${ENDPOINT}?id=eq.${id}`, { method: "DELETE", headers: getHeaders() });
if (res.ok) setDisponibilidades((prev) => prev.filter((d) => d.id !== id));
} catch (error) {}
};
const disponibilidadesAgrupadas = useMemo(() => {
const agrupadas = {};
doctors.forEach((doctor) => {
agrupadas[doctor.id] = {
doctor_id: doctor.id,
doctor_name: doctor.full_name || doctor.name,
disponibilidades: [],
};
});
disponibilidades.forEach((disp) => {
if (agrupadas[disp.doctor_id]) agrupadas[disp.doctor_id].disponibilidades.push(disp);
});
Object.values(agrupadas).forEach((grupo) => {
if (grupo.disponibilidades.length === 0) {
grupo.disponibilidades.push({
id: `empty-${grupo.doctor_id}`,
doctor_id: grupo.doctor_id,
doctor_name: grupo.doctor_name,
is_empty: true,
});
}
});
let resultado = Object.values(agrupadas);
if (searchTerm) resultado = resultado.filter((grupo) => grupo.doctor_name.toLowerCase().includes(searchTerm.toLowerCase()));
return resultado;
}, [disponibilidades, doctors, searchTerm]);
const formatTime = (timeString) => {
if (!timeString) return "";
return timeString.includes(":") ? timeString.substring(0, 5) : timeString;
};
const getDiaSemana = (weekday) => {
const dias = {
0: "Domingo",
1: "Segunda",
2: "Terça",
3: "Quarta",
4: "Quinta",
5: "Sexta",
6: "Sábado",
sunday: "Domingo",
monday: "Segunda",
tuesday: "Terça",
wednesday: "Quarta",
thursday: "Quinta",
friday: "Sexta",
saturday: "Sábado",
};
const key = typeof weekday === "string" ? weekday.toLowerCase() : weekday;
return dias[key] || "Desconhecido";
};
const initialAvailabilityParaEdicao = useMemo(() => {
if (!editando) return [];
const disponibilidadesMedico = disponibilidades.filter((d) => String(d.doctor_id) === String(editando));
const blocosPorDia = {};
disponibilidadesMedico.forEach((d) => {
const num = typeof d.weekday === "string" ? weekdayStrToNum[d.weekday.toLowerCase()] : d.weekday;
if (num === undefined) return;
if (!blocosPorDia[num]) blocosPorDia[num] = [];
if (d.active !== false) {
blocosPorDia[num].push({
id: d.id,
inicio: formatTime(d.start_time) || "07:00",
termino: formatTime(d.end_time) || "17:00",
slot_minutes: d.slot_minutes || 30,
appointment_type: d.appointment_type || "presencial",
isNew: false,
});
}
});
const resultado = [1, 2, 3, 4, 5, 6, 0].map((weekday) => {
const blocosDoDia = blocosPorDia[weekday] || [];
return {
dia: diasDaSemana[weekday],
weekday: weekday,
isChecked: blocosDoDia.length > 0,
blocos:
blocosDoDia.length > 0
? blocosDoDia
: [
{
id: null,
inicio: "07:00",
termino: "17:00",
slot_minutes: 30,
appointment_type: "presencial",
isNew: true,
},
],
};
});
return resultado;
}, [disponibilidades, editando]);
const handleUpdateHorarios = (horariosAtualizados) => {
if (!editando) return;
setAvailabilityEdit(horariosAtualizados || []);
};
const filteredDoctors = useMemo(() => {
if (!searchTerm) return doctors;
return doctors.filter((doc) => (doc.full_name || doc.name).toLowerCase().includes(searchTerm.toLowerCase()));
}, [doctors, searchTerm]);
const handleCancelarEdicao = () => {
setEditando(null);
setAvailabilityEdit([]);
};
const handleDoctorSelect = (doctor) => {
setSearchTerm(doctor.full_name || doctor.name);
setShowSuggestions(false);
};
const handleClearSearch = () => {
setSearchTerm("");
setShowSuggestions(false);
};
const getStatusBadgeClass = (disp) => {
if (disp.is_empty) return "status-badge status-not-configured";
if (disp.active === false) return "status-badge status-inactive";
return "status-badge status-active";
};
const getStatusText = (disp) => {
if (disp.is_empty) return "Não configurado";
if (disp.active === false) return "Inativa";
return "Ativa";
};
return (
<div className="disponibilidades-container">
<h1 className="disponibilidades-title">Disponibilidades dos Médicos</h1>
<div className="search-container">
<div className="search-input-container">
<input
type="text"
placeholder="Buscar médico por nome..."
value={searchTerm}
onChange={(e) => {
setSearchTerm(e.target.value);
setShowSuggestions(true);
}}
onFocus={() => setShowSuggestions(true)}
className="search-input"
/>
{searchTerm && (
<button onClick={handleClearSearch} className="clear-search-btn">
×
</button>
)}
</div>
{showSuggestions && searchTerm && filteredDoctors.length > 0 && (
<div className="suggestions-dropdown">
{filteredDoctors.map((doc) => (
<div key={doc.id} onClick={() => handleDoctorSelect(doc)} className="suggestion-item">
{doc.full_name || doc.name}
</div>
))}
</div>
)}
</div>
<section className="calendario-ou-filaespera">
<div className="fila-container">
<h2 className="section-title">{editando ? `Editar Horários` : "Lista de Disponibilidades"}</h2>
{doctors.length === 0 ? (
<p className="loading-text">Carregando médicos...</p>
) : editando ? (
<>
<div className="edit-container">
{initialAvailabilityParaEdicao.length > 0 ? (
<HorariosDisponibilidade initialAvailability={initialAvailabilityParaEdicao} onUpdate={handleUpdateHorarios} onCancel={handleCancelarEdicao} />
) : (
<p className="loading-text">Carregando horários para edição...</p>
)}
</div>
<div className="disp-buttons-container">
<button
onClick={() =>
salvarTodasDisponibilidades(editando, availabilityEdit.length > 0 ? availabilityEdit : initialAvailabilityParaEdicao)
}
className="disp-btn-primary"
>
Salvar Alterações
</button>
<button onClick={handleCancelarEdicao} className="disp-btn-danger">
Cancelar
</button>
</div>
</>
) : (
<div className="doctor-group-container">
{disponibilidadesAgrupadas.length === 0 ? (
<p className="no-results">Nenhum médico encontrado</p>
) : (
disponibilidadesAgrupadas.map((grupo) => (
<div key={grupo.doctor_id} className={`doctor-group ${expandedDoctors[grupo.doctor_id] ? "expanded" : ""}`}>
<div className="doctor-header" onClick={() => toggleExpandDoctor(grupo.doctor_id)}>
<h3 className="doctor-name">
{grupo.doctor_name}
<span className="doctor-hours">({grupo.disponibilidades.filter((d) => !d.is_empty).length} horários)</span>
</h3>
<span className={`expand-icon ${expandedDoctors[grupo.doctor_id] ? "expanded" : ""}`}></span>
</div>
{expandedDoctors[grupo.doctor_id] && (
<div className="doctor-content">
<div className="edit-btn-container">
<button onClick={() => setEditando(grupo.doctor_id)} className="disp-btn-edit">
{grupo.disponibilidades.some((d) => !d.is_empty) ? "Editar" : "Cadastrar Horários"}
</button>
</div>
<div className="table-container">
<table className="disponibilidades-table">
<thead>
<tr>
<th>Dia da Semana</th>
<th>Início</th>
<th>Término</th>
<th>Intervalo (min)</th>
<th>Tipo</th>
<th>Status</th>
<th>Ações</th>
</tr>
</thead>
<tbody>
{grupo.disponibilidades.map((disp) => (
<tr key={disp.id}>
<td>{disp.is_empty ? "Nenhum horário cadastrado" : getDiaSemana(disp.weekday)}</td>
<td>{disp.is_empty ? "-" : formatTime(disp.start_time)}</td>
<td>{disp.is_empty ? "-" : formatTime(disp.end_time)}</td>
<td>{disp.is_empty ? "-" : disp.slot_minutes || 30}</td>
<td>{disp.is_empty ? "-" : disp.appointment_type || "presencial"}</td>
<td>
<span className={getStatusBadgeClass(disp)}>{getStatusText(disp)}</span>
</td>
<td>{!disp.is_empty && <button onClick={() => deletarDisponibilidade(disp.id)} className="disp-btn-delete">Excluir</button>}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
</div>
))
)}
</div>
)}
</div>
</section>
</div>
);
};
export default DisponibilidadesDoctorPage;