From 5aaaf7d3e508116a31644b698f00e7960a600ba6 Mon Sep 17 00:00:00 2001 From: pedrofedericoo Date: Thu, 20 Nov 2025 14:58:38 -0300 Subject: [PATCH] Disponibilidades Atualizadas --- package-lock.json | 31 -- src/components/doctors/DoctorForm.css | 11 + src/components/doctors/DoctorForm.jsx | 25 +- .../doctors/HorariosDisponibilidade.css | 14 + .../doctors/HorariosDisponibilidade.jsx | 36 +- src/pages/DisponibilidadesDoctorPage.jsx | 326 ++++++--------- src/pages/DoctorEditPage.jsx | 389 ++++++++++++++---- src/pages/DoctorTable.jsx | 6 +- src/pages/style/Agendamento.css | 23 +- .../style/DisponibilidadesDoctorPage.css | 28 +- src/pages/style/FilaEspera.css | 9 + src/pages/style/TableDoctor.css | 11 + src/pages/style/TablePaciente.css | 10 + .../perfil_secretaria/PerfilSecretaria.jsx | 4 +- 14 files changed, 577 insertions(+), 346 deletions(-) diff --git a/package-lock.json b/package-lock.json index b9a1a503..bb50c8d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31871,23 +31871,6 @@ "node": ">= 0.8.0" } }, - "node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "license": "MIT", - "optional": true, - "peer": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, "node_modules/pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -33013,20 +32996,6 @@ "node": ">=14.0.0" } }, - "node_modules/react-scripts/node_modules/yaml": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", - "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - } - }, "node_modules/react-toastify": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-11.0.5.tgz", diff --git a/src/components/doctors/DoctorForm.css b/src/components/doctors/DoctorForm.css index d5f23798..81ef2e44 100644 --- a/src/components/doctors/DoctorForm.css +++ b/src/components/doctors/DoctorForm.css @@ -198,4 +198,15 @@ font-size: 1.1rem; padding: 0.6rem 1.2rem; } +} + +@media (max-width: 576px) { + .doctor-form-container { padding: 0.75rem; } + .doctor-form-title { font-size: 1.75rem; } + .form-section { padding: 0.75rem; } + .section-header { font-size: 1.25rem; } + .form-label { font-size: 1rem; } + .form-control-custom { font-size: 1rem; } + .btns-container { display: flex; flex-direction: column; gap: 8px; } + .btn-submit, .btn-cancel { width: 100%; margin-right: 0; } } \ No newline at end of file diff --git a/src/components/doctors/DoctorForm.jsx b/src/components/doctors/DoctorForm.jsx index 8f553ab1..f11e8354 100644 --- a/src/components/doctors/DoctorForm.jsx +++ b/src/components/doctors/DoctorForm.jsx @@ -135,14 +135,11 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) { } }; - const handleAvailabilityUpdate = useCallback((newAvailability) => { - setFormData((prev) => { - if (JSON.stringify(prev.availability) !== JSON.stringify(newAvailability)) { +const handleAvailabilityUpdate = useCallback((newAvailability) => { + setFormData((prev) => { return { ...prev, availability: newAvailability }; - } - return prev; }); - }, []); +}, [setFormData]); const handleCepBlur = async () => { const cep = formData.cep?.replace(/\D/g, ""); @@ -326,17 +323,9 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) { } try { - const savedDoctor = await onSave({ ...formData }); - - if (formData.availability && formData.availability.length > 0 && savedDoctor.id) { - if (formData.availabilityId) { - - await handlePatchAvailability(formData.availabilityId, formData.availability); - } else { - - await handleCreateAvailability(savedDoctor.id, formData.availability); - } - } + // Chama a função onSave (handleSave no DoctorEditPage) com o formData completo. + // A lógica de salvamento do médico e da disponibilidade é responsabilidade do componente pai. + await onSave({ ...formData }); } catch (error) { console.error("Erro ao salvar médico ou disponibilidade:", error); @@ -760,7 +749,7 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) { Defina seus horários de atendimento para cada dia da semana. Marque um dia para começar a adicionar blocos de tempo.

- diff --git a/src/components/doctors/HorariosDisponibilidade.css b/src/components/doctors/HorariosDisponibilidade.css index c726b672..e5b0921e 100644 --- a/src/components/doctors/HorariosDisponibilidade.css +++ b/src/components/doctors/HorariosDisponibilidade.css @@ -151,4 +151,18 @@ .btn-add:hover { background-color: #059669; +} + +@media (max-width: 768px) { + .horarios-container { grid-template-columns: repeat(2, 1fr); } +} + +@media (max-width: 576px) { + .horarios-container { grid-template-columns: 1fr; gap: 10px; } + .day-card { height: auto; } + .day-header label { font-size: 13px; } + .time-inputs { flex-direction: column; } + .input-wrapper input { font-size: 12px; } + .btn-add { font-size: 12px; } + .btn-remove { font-size: 12px; } } \ No newline at end of file diff --git a/src/components/doctors/HorariosDisponibilidade.jsx b/src/components/doctors/HorariosDisponibilidade.jsx index be139888..7c376f16 100644 --- a/src/components/doctors/HorariosDisponibilidade.jsx +++ b/src/components/doctors/HorariosDisponibilidade.jsx @@ -10,13 +10,13 @@ const initialBlockTemplate = { }; 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: [] }, + { dia: "Domingo", weekday: 0, isChecked: false, blocos: [] }, + { dia: "Segunda-feira", weekday: 1, isChecked: false, blocos: [] }, + { dia: "Terça-feira", weekday: 2, isChecked: false, blocos: [] }, + { dia: "Quarta-feira", weekday: 3, isChecked: false, blocos: [] }, + { dia: "Quinta-feira", weekday: 4, isChecked: false, blocos: [] }, + { dia: "Sexta-feira", weekday: 5, isChecked: false, blocos: [] }, + { dia: "Sábado", weekday: 6, isChecked: false, blocos: [] }, ]; const HorariosDisponibilidade = ({ @@ -35,10 +35,18 @@ const HorariosDisponibilidade = ({ } }, [initialAvailability]); + useEffect(() => { + if (isFirstRun.current) { + isFirstRun.current = false; + return; + } + if (onUpdate) onUpdate(availability); + }, [availability, onUpdate]); + const handleDayCheck = useCallback((dayIndex, currentIsChecked) => { const isChecked = !currentIsChecked; - setAvailability((prev) => - prev.map((day, i) => + setAvailability((prev) => { + const updated = prev.map((day, i) => i === dayIndex ? { ...day, @@ -56,8 +64,10 @@ const HorariosDisponibilidade = ({ : [], } : day - ) - ); + ); + console.log('handleDayCheck - updated availability:', updated); + return updated; + }); }, []); const handleAddBlock = useCallback((dayIndex) => { @@ -107,9 +117,7 @@ const HorariosDisponibilidade = ({ ); }, []); - const handleSave = useCallback(() => { - if (onUpdate) onUpdate(availability); - }, [availability, onUpdate]); + return (
diff --git a/src/pages/DisponibilidadesDoctorPage.jsx b/src/pages/DisponibilidadesDoctorPage.jsx index 9d70dd53..ef488d82 100644 --- a/src/pages/DisponibilidadesDoctorPage.jsx +++ b/src/pages/DisponibilidadesDoctorPage.jsx @@ -7,9 +7,29 @@ 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 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 DisponibilidadesDoctorPage = () => { const { getAuthorizationHeader } = useAuth(); const [disponibilidades, setDisponibilidades] = useState([]); const [doctors, setDoctors] = useState([]); @@ -17,6 +37,7 @@ const DisponibilidadesDoctorPage = ( ) => { const [editando, setEditando] = useState(null); const [expandedDoctors, setExpandedDoctors] = useState({}); const [showSuggestions, setShowSuggestions] = useState(false); + const [availabilityEdit, setAvailabilityEdit] = useState([]); const getHeaders = () => { const myHeaders = new Headers(); @@ -24,6 +45,7 @@ const DisponibilidadesDoctorPage = ( ) => { 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; }; @@ -31,15 +53,13 @@ const DisponibilidadesDoctorPage = ( ) => { const fetchDoctors = async () => { try { const requestOptions = { - method: 'GET', + method: "GET", headers: getHeaders(), }; - const response = await fetch(DOCTORS_ENDPOINT, requestOptions); const result = await response.json(); setDoctors(Array.isArray(result) ? result : []); } catch (error) { - console.error("Erro ao carregar médicos:", error); setDoctors([]); } }; @@ -49,16 +69,12 @@ const DisponibilidadesDoctorPage = ( ) => { useEffect(() => { const fetchDisponibilidades = async () => { try { - const res = await fetch(ENDPOINT, { - method: "GET", - headers: getHeaders() - }); + 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("Erro ao buscar disponibilidades:", error); setDisponibilidades([]); } }; @@ -66,35 +82,24 @@ const DisponibilidadesDoctorPage = ( ) => { }, [getAuthorizationHeader]); const toggleExpandDoctor = (doctorId) => { - setExpandedDoctors((prev) => ({ - ...prev, - [doctorId]: !prev[doctorId], - })); + setExpandedDoctors((prev) => ({ ...prev, [doctorId]: !prev[doctorId] })); }; const salvarTodasDisponibilidades = async (doctorId, horariosAtualizados) => { try { const headers = getHeaders(); - - // 1. Obter as disponibilidades existentes para este médico - const existingDisponibilidadesRes = await fetch(`${ENDPOINT}?doctor_id=eq.${String(doctorId)}`, { - method: "GET", - headers, - }); - const existingDisponibilidades = existingDisponibilidadesRes.ok ? await existingDisponibilidadesRes.json() : []; - - const disponibilidadesParaManter = new Set(); + const promises = []; + const currentIds = new Set(); for (const dia of horariosAtualizados) { if (dia.isChecked && dia.blocos.length > 0) { - // Processar blocos de horários 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: dia.weekday, + weekday: weekdayNumToStr[dia.weekday], start_time: inicio, end_time: termino, slot_minutes: bloco.slot_minutes || 30, @@ -103,82 +108,74 @@ const DisponibilidadesDoctorPage = ( ) => { }; if (bloco.id && !bloco.isNew) { - // Atualizar disponibilidade existente - await fetch(`${ENDPOINT}?id=eq.${bloco.id}`, { - method: "PATCH", - headers, - body: JSON.stringify(payload), - }); - disponibilidadesParaManter.add(bloco.id); + 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 { - // Criar nova disponibilidade - const postRes = await fetch(ENDPOINT, { - method: "POST", - headers, - body: JSON.stringify(payload), - }); - if (postRes.ok) { - const newDisp = await postRes.json(); - // Adicionar o ID da nova disponibilidade para evitar exclusão acidental - if (newDisp && newDisp.length > 0) { - disponibilidadesParaManter.add(newDisp[0].id); - } - } + 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 }; + }) + ); } } } } - // 2. Excluir disponibilidades antigas que não foram mantidas - const disponibilidadesParaExcluir = existingDisponibilidades.filter( - (disp) => !disponibilidadesParaManter.has(disp.id) - ); + const results = await Promise.all(promises); + + results.forEach(res => { + if (res.type === 'POST' && res.id) currentIds.add(res.id); + }); - for (const disp of disponibilidadesParaExcluir) { - await fetch(`${ENDPOINT}?id=eq.${disp.id}`, { - method: "DELETE", - headers, - }); + 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); } - alert("Horários atualizados com sucesso!"); setEditando(null); - - const res = await fetch(ENDPOINT, { - method: "GET", - headers: getHeaders() - }); + 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("Erro ao salvar disponibilidades:", error); - alert("Erro ao salvar os horários"); + 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) { - alert("Disponibilidade excluída com sucesso!"); - setDisponibilidades((prev) => prev.filter((d) => d.id !== id)); - } - } catch (error) { - console.error("Erro:", error); - alert("Erro ao conectar com o servidor"); - } + 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, @@ -186,13 +183,9 @@ const DisponibilidadesDoctorPage = ( ) => { disponibilidades: [], }; }); - disponibilidades.forEach((disp) => { - if (agrupadas[disp.doctor_id]) { - agrupadas[disp.doctor_id].disponibilidades.push(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({ @@ -203,15 +196,8 @@ const DisponibilidadesDoctorPage = ( ) => { }); } }); - let resultado = Object.values(agrupadas); - - if (searchTerm) { - resultado = resultado.filter(grupo => - grupo.doctor_name.toLowerCase().includes(searchTerm.toLowerCase()) - ); - } - + if (searchTerm) resultado = resultado.filter((grupo) => grupo.doctor_name.toLowerCase().includes(searchTerm.toLowerCase())); return resultado; }, [disponibilidades, doctors, searchTerm]); @@ -237,60 +223,66 @@ const DisponibilidadesDoctorPage = ( ) => { 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) - ); - - return [1, 2, 3, 4, 5, 6, 0].map((weekday) => { - const blocosDoDia = disponibilidadesMedico - .filter((d) => d.weekday === weekday && d.active !== false) - .map((d) => ({ + 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, - }], + 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; - salvarTodasDisponibilidades(editando, horariosAtualizados); + setAvailabilityEdit(horariosAtualizados || []); }; const filteredDoctors = useMemo(() => { if (!searchTerm) return doctors; - return doctors.filter((doc) => - (doc.full_name || doc.name).toLowerCase().includes(searchTerm.toLowerCase()) - ); + return doctors.filter((doc) => (doc.full_name || doc.name).toLowerCase().includes(searchTerm.toLowerCase())); }, [doctors, searchTerm]); const handleCancelarEdicao = () => { setEditando(null); + setAvailabilityEdit([]); }; const handleDoctorSelect = (doctor) => { @@ -317,9 +309,7 @@ const DisponibilidadesDoctorPage = ( ) => { return (
-

- Disponibilidades dos Médicos -

+

Disponibilidades dos Médicos

@@ -335,10 +325,7 @@ const DisponibilidadesDoctorPage = ( ) => { className="search-input" /> {searchTerm && ( - )} @@ -347,11 +334,7 @@ const DisponibilidadesDoctorPage = ( ) => { {showSuggestions && searchTerm && filteredDoctors.length > 0 && (
{filteredDoctors.map((doc) => ( -
handleDoctorSelect(doc)} - className="suggestion-item" - > +
handleDoctorSelect(doc)} className="suggestion-item"> {doc.full_name || doc.name}
))} @@ -361,9 +344,7 @@ const DisponibilidadesDoctorPage = ( ) => {
-

- {editando ? `Editar Horários` : "Lista de Disponibilidades"} -

+

{editando ? `Editar Horários` : "Lista de Disponibilidades"}

{doctors.length === 0 ? (

Carregando médicos...

@@ -371,13 +352,7 @@ const DisponibilidadesDoctorPage = ( ) => { <>
{initialAvailabilityParaEdicao.length > 0 ? ( - + ) : (

Carregando horários para edição...

)} @@ -385,16 +360,15 @@ const DisponibilidadesDoctorPage = ( ) => {
-
@@ -405,33 +379,20 @@ const DisponibilidadesDoctorPage = ( ) => {

Nenhum médico encontrado

) : ( disponibilidadesAgrupadas.map((grupo) => ( -
-
toggleExpandDoctor(grupo.doctor_id)} - > +
+
toggleExpandDoctor(grupo.doctor_id)}>

{grupo.doctor_name} - - ({grupo.disponibilidades.filter(d => !d.is_empty).length} horários) - + ({grupo.disponibilidades.filter((d) => !d.is_empty).length} horários)

- - ▼ - +
{expandedDoctors[grupo.doctor_id] && (
-
@@ -451,36 +412,15 @@ const DisponibilidadesDoctorPage = ( ) => { {grupo.disponibilidades.map((disp) => ( + {disp.is_empty ? "Nenhum horário cadastrado" : getDiaSemana(disp.weekday)} + {disp.is_empty ? "-" : formatTime(disp.start_time)} + {disp.is_empty ? "-" : formatTime(disp.end_time)} + {disp.is_empty ? "-" : disp.slot_minutes || 30} + {disp.is_empty ? "-" : disp.appointment_type || "presencial"} - {disp.is_empty ? "Nenhum horário cadastrado" : getDiaSemana(disp.weekday)} - - - {disp.is_empty ? "-" : formatTime(disp.start_time)} - - - {disp.is_empty ? "-" : formatTime(disp.end_time)} - - - {disp.is_empty ? "-" : disp.slot_minutes || 30} - - - {disp.is_empty ? "-" : disp.appointment_type || "presencial"} - - - - {getStatusText(disp)} - - - - {!disp.is_empty && ( - - )} + {getStatusText(disp)} + {!disp.is_empty && } ))} @@ -499,4 +439,4 @@ const DisponibilidadesDoctorPage = ( ) => { ); }; -export default DisponibilidadesDoctorPage; +export default DisponibilidadesDoctorPage; \ No newline at end of file diff --git a/src/pages/DoctorEditPage.jsx b/src/pages/DoctorEditPage.jsx index f50b934a..a4e00cfe 100644 --- a/src/pages/DoctorEditPage.jsx +++ b/src/pages/DoctorEditPage.jsx @@ -1,104 +1,333 @@ -import React, { useEffect, useState, useCallback } from "react"; -import { useParams, useSearchParams } from "react-router-dom"; -import { GetDoctorByID } from "../components/utils/Functions-Endpoints/Doctor"; +import React, { useState, useEffect, useMemo } from "react"; +import { useParams, useNavigate, useLocation } from "react-router-dom"; 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"; +const ENDPOINT = "https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors"; +const ENDPOINT_AVAILABILITY = "https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctor_availability"; -const DoctorEditPage = ({DictInfo}) => { +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 EditDoctorPage = () => { + const { id } = useParams(); + const navigate = useNavigate(); + const location = useLocation(); const { getAuthorizationHeader } = useAuth(); - const [DoctorToPUT, setDoctorPUT] = useState({}); + + const [doctor, setDoctor] = useState(null); + const [availability, setAvailability] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [isSaving, setIsSaving] = useState(false); - const Parametros = useParams(); - const [searchParams] = useSearchParams(); - - const DoctorID = "b24c88b2-1d51-4c04-8fe8-e75c3f2817d1"; - const availabilityId = searchParams.get("availabilityId"); + const effectiveId = id; - const [availabilityToPATCH, setAvailabilityToPATCH] = useState(null); - const [mode, setMode] = useState("doctor"); - console.log("teste", DictInfo) - - useEffect(() => { - setDoctorPUT(DictInfo) - }, [DictInfo]); - - const HandlePutDoctor = async () => { + const getHeaders = () => { + const myHeaders = new Headers(); const authHeader = getAuthorizationHeader(); - - var myHeaders = new Headers(); - myHeaders.append("apikey", API_KEY); - myHeaders.append("Authorization", authHeader); + if (authHeader) myHeaders.append("Authorization", authHeader); myHeaders.append("Content-Type", "application/json"); - - var raw = JSON.stringify(DoctorToPUT); - - console.log("Enviando médico para atualização (PUT):", DoctorToPUT); - - var requestOptions = { - method: "PUT", - headers: myHeaders, - body: raw, - redirect: "follow", - }; - - fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors?id=eq.${DictInfo.id}`,requestOptions) - .then(response => console.log(response)) + if (API_KEY) myHeaders.append("apikey", API_KEY); + myHeaders.append("Prefer", "return=representation"); + return myHeaders; }; - // 2. Função para Atualizar DISPONIBILIDADE (PATCH) - const HandlePatchAvailability = async (data) => { - const authHeader = getAuthorizationHeader(); - - 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", - }; - + const salvarDisponibilidades = async (doctorId, horariosAtualizados) => { try { - const response = await fetch( - `${ENDPOINT_AVAILABILITY}?id=eq.${availabilityId}`, - requestOptions + 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_AVAILABILITY}?id=eq.${bloco.id}`, { + method: "PATCH", + headers, + body: JSON.stringify(payload), + }).then((res) => { + if (!res.ok) throw new Error(`Erro no PATCH: ${res.status}`); + return { type: "PATCH", id: bloco.id }; + }) + ); + } else { + promises.push( + fetch(ENDPOINT_AVAILABILITY, { + method: "POST", + headers, + body: JSON.stringify(payload), + }) + .then((res) => res.json()) + .then((data) => { + const createdItem = Array.isArray(data) ? data[0] : data; + if (createdItem && createdItem.id) { + return { type: "POST", id: createdItem.id }; + } + return { type: "POST", id: null }; + }) + ); + } + } + } + } + + const results = await Promise.all(promises); + + results.forEach((res) => { + if (res.type === "POST" && res.id) currentIds.add(res.id); + }); + + const existingDisponibilidadesRes = await fetch( + `${ENDPOINT_AVAILABILITY}?doctor_id=eq.${String(doctorId)}`, + { method: "GET", headers } ); - console.log("Resposta PATCH Disponibilidade:", response); - alert("Disponibilidade atualizada com sucesso!"); - // Opcional: Redirecionar de volta para a lista de disponibilidades - // navigate('/disponibilidades'); + + if (existingDisponibilidadesRes.ok) { + const existingDisponibilidades = await existingDisponibilidadesRes.json(); + + const deletePromises = existingDisponibilidades + .filter((disp) => !currentIds.has(disp.id)) + .map((disp) => + fetch(`${ENDPOINT_AVAILABILITY}?id=eq.${disp.id}`, { + method: "DELETE", + headers, + }) + ); + + await Promise.all(deletePromises); + } + + const updatedResponse = await fetch( + `${ENDPOINT_AVAILABILITY}?doctor_id=eq.${doctorId}&order=weekday.asc,start_time.asc`, + { method: "GET", headers } + ); + + if (updatedResponse.ok) { + const updatedData = await updatedResponse.json(); + setAvailability(updatedData); + } } catch (error) { - console.error("Erro ao atualizar disponibilidade:", error); - alert("Erro ao atualizar disponibilidade."); throw error; } }; + const normalizeAvailabilityForForm = (availabilityData) => { + if (!Array.isArray(availabilityData)) return []; + + const disponibilidadesMedico = availabilityData.filter((d) => + String(d.doctor_id) === String(effectiveId) && d.active !== false + ); + const blocosPorDia = {}; + + disponibilidadesMedico.forEach((d) => { + const num = typeof d.weekday === "string" ? weekdayStrToNum[d.weekday.toLowerCase()] : d.weekday; + if (num === undefined || num === null) return; + if (!blocosPorDia[num]) blocosPorDia[num] = []; + blocosPorDia[num].push({ + id: d.id, + inicio: d.start_time?.substring(0, 5) || "07:00", + termino: d.end_time?.substring(0, 5) || "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; + }; + + const availabilityFormatted = useMemo(() => { + return normalizeAvailabilityForForm(availability); + }, [availability, effectiveId]); + + useEffect(() => { + const fetchDoctorData = async () => { + if (!effectiveId || effectiveId === "edit") { + alert("ID do médico não encontrado"); + navigate("/secretaria/medicos"); + return; + } + + try { + const doctorResponse = await fetch(`${ENDPOINT}?id=eq.${effectiveId}`, { + method: "GET", + headers: getHeaders(), + }); + + if (!doctorResponse.ok) { + throw new Error("Erro ao carregar dados do médico"); + } + + const doctorData = await doctorResponse.json(); + if (doctorData.length === 0) { + throw new Error("Médico não encontrado"); + } + + setDoctor(doctorData[0]); + + const availabilityResponse = await fetch( + `${ENDPOINT_AVAILABILITY}?doctor_id=eq.${effectiveId}&order=weekday.asc,start_time.asc`, + { + method: "GET", + headers: getHeaders(), + } + ); + + if (availabilityResponse.ok) { + const availabilityData = await availabilityResponse.json(); + setAvailability(availabilityData); + } else { + setAvailability([]); + } + + } catch (error) { + alert("Erro ao carregar dados do médico"); + navigate("/secretaria/medicos"); + } finally { + setIsLoading(false); + } + }; + + if (effectiveId) { + fetchDoctorData(); + } + }, [effectiveId, navigate]); + + const handleSave = async (formData) => { + const { availability: updatedAvailability, ...doctorDataToSave } = formData; + + try { + setIsSaving(true); + + const response = await fetch(`${ENDPOINT}?id=eq.${effectiveId}`, { + method: "PATCH", + headers: getHeaders(), + body: JSON.stringify(doctorDataToSave), + }); + + if (!response.ok) { + throw new Error("Erro ao salvar dados do médico"); + } + + if (updatedAvailability && updatedAvailability.length > 0) { + await salvarDisponibilidades(effectiveId, updatedAvailability); + } + + alert("Médico e horários atualizados com sucesso!"); + navigate("/secretaria/medicos"); + + } catch (error) { + alert(`Erro ao salvar dados: ${error.message}`); + } finally { + setIsSaving(false); + } + console.log('Horários a serem salvos:', updatedAvailability); + }; + + const handleCancel = () => { + navigate("/secretaria/medicos"); + }; + + if (isLoading) { + return ( +
+
+
+ Carregando... +
+
+

+ Carregando dados do médico ID: {effectiveId || "..."} +

+
+ ); + } + + if (!doctor) { + if (!isLoading) { + return ( +
+
+ Médico não encontrado +
+
+ ); + } + return null; + } + + const formData = { + ...doctor, + availability: (doctor && doctor.availability) ? doctor.availability : availabilityFormatted, + }; + return ( -
- +
+
+
+

Editar Médico

+ +
+
); }; -export default DoctorEditPage; +export default EditDoctorPage; \ No newline at end of file diff --git a/src/pages/DoctorTable.jsx b/src/pages/DoctorTable.jsx index cdcce24b..a0f36504 100644 --- a/src/pages/DoctorTable.jsx +++ b/src/pages/DoctorTable.jsx @@ -147,7 +147,7 @@ function TableDoctor({setDictInfo}) { return resultado; }) : []; - // Aplica ordenação rápida + const applySorting = (arr) => { if (!Array.isArray(arr) || !sortKey) return arr; const copy = [...arr]; @@ -437,13 +437,13 @@ function TableDoctor({setDictInfo}) { {medico.email || 'Não informado'}
- + - + diff --git a/src/pages/style/Agendamento.css b/src/pages/style/Agendamento.css index d887b3a5..1237a379 100644 --- a/src/pages/style/Agendamento.css +++ b/src/pages/style/Agendamento.css @@ -193,8 +193,8 @@ } .appointment-actions { - display: flex; - gap: 8px; + display: flex; + gap: 8px; } .btn-action { @@ -221,5 +221,22 @@ color: #fff; } .btn-action.btn-delete:hover { - background-color: #C53030; + background-color: #C53030; +} + +@media (max-width: 768px) { + .unidade-selecionarprofissional { flex-direction: column; align-items: stretch; gap: 12px; } + .calendar-wrapper { flex-direction: column; padding: 16px; } + .calendar-info-panel { flex: 0 0 auto; border-right: none; border-bottom: 1px solid #E2E8F0; padding-right: 0; padding-bottom: 16px; } + .calendar-grid { grid-template-columns: repeat(4, 1fr); } + .calendar-controls { flex-direction: column; align-items: flex-start; gap: 8px; } +} + +@media (max-width: 576px) { + .calendar-grid { grid-template-columns: repeat(2, 1fr); } + .date-indicator h2 { font-size: 1.25rem; } + .legend-item { font-size: 0.75rem; padding: 4px 8px; } + .appointment-item { flex-direction: column; align-items: stretch; gap: 8px; } + .appointment-actions { width: 100%; } + .btn-action { width: 100%; } } diff --git a/src/pages/style/DisponibilidadesDoctorPage.css b/src/pages/style/DisponibilidadesDoctorPage.css index 27d9d52f..bf216c84 100644 --- a/src/pages/style/DisponibilidadesDoctorPage.css +++ b/src/pages/style/DisponibilidadesDoctorPage.css @@ -218,6 +218,30 @@ background-color: #27ae60; } +@media (max-width: 576px) { + .disponibilidades-container { padding: 12px; } + .disponibilidades-title { font-size: 1.25rem; } + .search-container { padding: 12px; } + .doctor-header { padding: 12px; } + .doctor-name { font-size: 1rem; } + .doctor-hours { display: none; } + .expand-icon { font-size: 0.9rem; } + .table-container { padding: 0 6px 6px 6px; } + .disponibilidades-table thead th { padding: 6px 8px; } + .disponibilidades-table td { padding: 10px 8px; font-size: 0.8125rem; } + .disponibilidades-table thead th:nth-child(4), + .disponibilidades-table thead th:nth-child(5), + .disponibilidades-table thead th:nth-child(7), + .disponibilidades-table tbody td:nth-child(4), + .disponibilidades-table tbody td:nth-child(5), + .disponibilidades-table tbody td:nth-child(7) { + display: none; + } + .disp-buttons-container { flex-direction: column; gap: 10px; } + .disp-btn-primary, .disp-btn-danger { width: 100%; } + .suggestions-dropdown { width: calc(100% - 30px); left: 15px; right: 15px; } +} + .status-inactive { background-color: #e74c3c; } @@ -304,7 +328,7 @@ font-size: 0.875rem; font-weight: 600; border-radius: 6px; - background-color: #95a5a6; + background-color: #fa273c; color: white; border: none; cursor: pointer; @@ -312,7 +336,7 @@ } .disp-btn-danger:hover { - background-color: #7f8c8d; + background-color: #f41936; } /* Section Titles */ diff --git a/src/pages/style/FilaEspera.css b/src/pages/style/FilaEspera.css index eb2777d2..18c8f499 100644 --- a/src/pages/style/FilaEspera.css +++ b/src/pages/style/FilaEspera.css @@ -256,6 +256,15 @@ html, body { font-size: 1.5rem; } } +@media (max-width: 576px) { + .unidade-selecionarprofissional { flex-direction: column; gap: 10px; } + .unidade-selecionarprofissional input, + .unidade-selecionarprofissional select { width: 100%; margin-left: 0; } + .busca-fila-espera { position: static; width: 100%; margin-bottom: 8px; } + .fila-header { height: auto; flex-direction: column; gap: 8px; } + .btns-e-legenda-container { flex-direction: column; gap: 10px; } + .legenda-tabela { justify-content: center; flex-wrap: wrap; } +} .fila-header { position: relative; display: flex; diff --git a/src/pages/style/TableDoctor.css b/src/pages/style/TableDoctor.css index b5d653fe..da439c58 100644 --- a/src/pages/style/TableDoctor.css +++ b/src/pages/style/TableDoctor.css @@ -160,6 +160,17 @@ gap: 0.75rem; } +@media (max-width: 576px) { + .table-doctor-card .card-header { padding: 0.75rem 1rem; } + .table-doctor-table th, .table-doctor-table td { padding: 8px 6px; } + .table-doctor-table thead th:nth-child(2), + .table-doctor-table thead th:nth-child(4), + .table-doctor-table tbody td:nth-child(2), + .table-doctor-table tbody td:nth-child(4) { display: none; } + .filter-buttons-container { width: 100%; } + .filter-btn { width: 100%; } +} + @media (max-width: 768px) { .table-doctor-table { font-size: 0.875rem; diff --git a/src/pages/style/TablePaciente.css b/src/pages/style/TablePaciente.css index 8e9a6993..55e35571 100644 --- a/src/pages/style/TablePaciente.css +++ b/src/pages/style/TablePaciente.css @@ -337,4 +337,14 @@ font-size: 0.75em; padding: 0.4em 0.65em; margin-bottom: 0.25rem; +} + +@media (max-width: 576px) { + .table-paciente-card .card-header { padding: 0.75rem 1rem; } + .table-paciente-table th, .table-paciente-table td { padding: 8px 6px; } + .table-paciente-table thead th:nth-child(2), + .table-paciente-table thead th:nth-child(4), + .table-paciente-table tbody td:nth-child(2), + .table-paciente-table tbody td:nth-child(4) { display: none; } + .table-paciente-filters .btn-sm { width: 100%; } } \ No newline at end of file diff --git a/src/perfis/perfil_secretaria/PerfilSecretaria.jsx b/src/perfis/perfil_secretaria/PerfilSecretaria.jsx index f76b9bc8..2d024ab5 100644 --- a/src/perfis/perfil_secretaria/PerfilSecretaria.jsx +++ b/src/perfis/perfil_secretaria/PerfilSecretaria.jsx @@ -34,8 +34,8 @@ function PerfilSecretaria({ onLogout }) { } /> } /> } /> - } /> - } /> + } /> + } /> } /> } /> } />