Ultimas correções

This commit is contained in:
pedrofedericoo 2025-11-27 12:05:41 -03:00
parent 176489f9fd
commit d67f4d6db4
5 changed files with 942 additions and 557 deletions

View File

@ -1,14 +1,21 @@
import React, { useState, useMemo, useEffect, useCallback } from 'react'; import React, { useState, useMemo, useEffect, useCallback } from "react";
import { useNavigate } from 'react-router-dom'; import { useNavigate } from "react-router-dom";
import API_KEY from '../components/utils/apiKeys.js'; import API_KEY from "../components/utils/apiKeys.js";
import AgendamentoCadastroManager from '../pages/AgendamentoCadastroManager.jsx'; import AgendamentoCadastroManager from "../pages/AgendamentoCadastroManager.jsx";
import { GetAllDoctors } from '../components/utils/Functions-Endpoints/Doctor.js'; import { GetAllDoctors } from "../components/utils/Functions-Endpoints/Doctor.js";
import { useAuth } from '../components/utils/AuthProvider.js'; import { useAuth } from "../components/utils/AuthProvider.js";
import dayjs from 'dayjs'; import dayjs from "dayjs";
import 'dayjs/locale/pt-br'; import "dayjs/locale/pt-br";
import isBetween from 'dayjs/plugin/isBetween'; import isBetween from "dayjs/plugin/isBetween";
import localeData from 'dayjs/plugin/localeData'; import localeData from "dayjs/plugin/localeData";
import { Search, ChevronLeft, ChevronRight, Edit, Trash2, CheckCircle } from 'lucide-react'; import {
Search,
ChevronLeft,
ChevronRight,
Edit,
Trash2,
CheckCircle,
} from "lucide-react";
import "../pages/style/Agendamento.css"; import "../pages/style/Agendamento.css";
import "../pages/style/FilaEspera.css"; import "../pages/style/FilaEspera.css";
import Spinner from "../components/Spinner.jsx"; import Spinner from "../components/Spinner.jsx";
@ -22,7 +29,6 @@ const Agendamento = () => {
const { getAuthorizationHeader, user } = useAuth(); const { getAuthorizationHeader, user } = useAuth();
const authHeader = getAuthorizationHeader(); const authHeader = getAuthorizationHeader();
const ID_MEDICO_ESPECIFICO = "078d2a67-b4c1-43c8-ae32-c1e75bb5b3df"; const ID_MEDICO_ESPECIFICO = "078d2a67-b4c1-43c8-ae32-c1e75bb5b3df";
const [listaTodosAgendamentos, setListaTodosAgendamentos] = useState([]); const [listaTodosAgendamentos, setListaTodosAgendamentos] = useState([]);
@ -55,15 +61,17 @@ const Agendamento = () => {
year: currentDate.year(), year: currentDate.year(),
}); });
const fetchAppointments = useCallback(async () => { const fetchAppointments = useCallback(async () => {
if (!authHeader) return; if (!authHeader) return;
setShowSpinner(true); setShowSpinner(true);
const myHeaders = new Headers(); const myHeaders = new Headers();
myHeaders.append("Authorization", authHeader); myHeaders.append("Authorization", authHeader);
myHeaders.append("apikey", API_KEY); myHeaders.append("apikey", API_KEY);
const requestOptions = { method: 'GET', headers: myHeaders, redirect: 'follow' }; const requestOptions = {
method: "GET",
headers: myHeaders,
redirect: "follow",
};
const apiUrl = `https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?doctor_id=eq.${ID_MEDICO_ESPECIFICO}&select=*`; const apiUrl = `https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?doctor_id=eq.${ID_MEDICO_ESPECIFICO}&select=*`;
@ -155,10 +163,12 @@ const Agendamento = () => {
if (authHeader) { if (authHeader) {
fetchAppointments(); fetchAppointments();
if (user?.role !== 'doctor') { if (user?.role !== "doctor") {
GetAllDoctors(authHeader).then(docs => { GetAllDoctors(authHeader).then((docs) => {
if (docs) { if (docs) {
setListaDeMedicos(docs.map(d => ({ nomeMedico: d.full_name, idMedico: d.id }))); setListaDeMedicos(
docs.map((d) => ({ nomeMedico: d.full_name, idMedico: d.id }))
);
} }
}); });
} }
@ -167,7 +177,6 @@ const Agendamento = () => {
useEffect(() => { useEffect(() => {
const processData = async () => { const processData = async () => {
if (!listaTodosAgendamentos.length) { if (!listaTodosAgendamentos.length) {
setAgendamentosOrganizados({}); setAgendamentosOrganizados({});
setFilaEsperaData([]); setFilaEsperaData([]);
@ -176,7 +185,6 @@ const Agendamento = () => {
setShowSpinner(true); setShowSpinner(true);
const appointmentsToShow = listaTodosAgendamentos; const appointmentsToShow = listaTodosAgendamentos;
const patientIdsToFetch = new Set(); const patientIdsToFetch = new Set();
@ -194,11 +202,14 @@ const Agendamento = () => {
const fetchPromises = []; const fetchPromises = [];
if (patientIdsToFetch.size > 0) { if (patientIdsToFetch.size > 0) {
const query = `id=in.(${Array.from(patientIdsToFetch).join(',')})`; const query = `id=in.(${Array.from(patientIdsToFetch).join(",")})`;
fetchPromises.push( fetchPromises.push(
fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/patients?${query}&select=*`, { fetch(
headers: { apikey: API_KEY, Authorization: authHeader } `https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/patients?${query}&select=*`,
}).then(res => res.json()) {
headers: { apikey: API_KEY, Authorization: authHeader },
}
).then((res) => res.json())
); );
} else { } else {
fetchPromises.push(Promise.resolve(null)); fetchPromises.push(Promise.resolve(null));
@ -274,8 +285,6 @@ const Agendamento = () => {
processData(); processData();
}, [listaTodosAgendamentos, authHeader]); }, [listaTodosAgendamentos, authHeader]);
const handleEditConsulta = (agendamento) => { const handleEditConsulta = (agendamento) => {
setAgendamentoParaEdicao(agendamento); setAgendamentoParaEdicao(agendamento);
setPageConsulta(true); setPageConsulta(true);
@ -306,22 +315,48 @@ const Agendamento = () => {
}; };
const dateGrid = useMemo(() => generateDateGrid(), [currentDate]); const dateGrid = useMemo(() => generateDateGrid(), [currentDate]);
const weekDays = ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb']; const weekDays = ["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb"];
const handleDateClick = (day) => setSelectedDay(day); const handleDateClick = (day) => setSelectedDay(day);
const DeleteModal = () => ( const DeleteModal = () => (
<div className="modal fade show delete-modal" style={{ display: "block", backgroundColor: "rgba(0,0,0,0.5)" }} tabIndex="-1"> <div
className="modal fade show delete-modal"
style={{ display: "block", backgroundColor: "rgba(0,0,0,0.5)" }}
tabIndex="-1"
>
<div className="modal-dialog modal-dialog-centered"> <div className="modal-dialog modal-dialog-centered">
<div className="modal-content"> <div className="modal-content">
<div className="modal-header" style={{ backgroundColor: '#dc3545', color: 'white' }}> <div
className="modal-header"
style={{ backgroundColor: "#dc3545", color: "white" }}
>
<h5 className="modal-title">Confirmação de Cancelamento</h5> <h5 className="modal-title">Confirmação de Cancelamento</h5>
</div> </div>
<div className="modal-body"> <div className="modal-body">
<p>Qual o motivo do cancelamento?</p> <p>Qual o motivo do cancelamento?</p>
<textarea className="form-control" rows="3" value={motivoCancelamento} onChange={(e) => setMotivoCancelamento(e.target.value)} placeholder="Ex: Motivo pessoal, reagendamento, etc."></textarea> <textarea
className="form-control"
rows="3"
value={motivoCancelamento}
onChange={(e) => setMotivoCancelamento(e.target.value)}
placeholder="Ex: Motivo pessoal, reagendamento, etc."
></textarea>
</div> </div>
<div className="modal-footer"> <div className="modal-footer">
<button type="button" className="btn btn-primary" onClick={() => { setShowDeleteModal(false); setMotivoCancelamento(""); }}>Cancelar</button> <button
<button type="button" className="btn btn-danger" onClick={() => deleteConsulta(selectedID)}> type="button"
className="btn btn-primary"
onClick={() => {
setShowDeleteModal(false);
setMotivoCancelamento("");
}}
>
Cancelar
</button>
<button
type="button"
className="btn btn-danger"
onClick={() => deleteConsulta(selectedID)}
>
Confirmar Cancelamento Confirmar Cancelamento
</button> </button>
</div> </div>
@ -412,22 +447,9 @@ const DeleteModal = () => (
return ( return (
<div> <div>
<h1>Agendar nova consulta</h1> <h1>Agendar nova consulta</h1>
<div className="btns-gerenciamento-e-consulta" style={{ display: 'flex', gap: '10px', marginBottom: '20px' }}>
<button
className="btn btn-primary"
onClick={() => {
setAgendamentoParaEdicao(null);
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>
{!PageNovaConsulta ? ( {!PageNovaConsulta ? (
<div className='atendimento-eprocura'> <div className="atendimento-eprocura">
{user?.role !== 'doctor' && ( {user?.role !== "doctor" && (
<div className="card p-3 mb-3 table-paciente-filters"> <div className="card p-3 mb-3 table-paciente-filters">
<h5 className="mb-3"> <h5 className="mb-3">
<i className="bi bi-funnel-fill me-2 text-primary"></i> <i className="bi bi-funnel-fill me-2 text-primary"></i>
@ -441,10 +463,19 @@ const DeleteModal = () => (
value={searchTermDoctor} value={searchTermDoctor}
onChange={(e) => handleSearchMedicos(e.target.value)} onChange={(e) => handleSearchMedicos(e.target.value)}
/> />
<small className="text-muted">Buscar médico para filtrar consultas</small> <small className="text-muted">
Buscar médico para filtrar consultas
</small>
{searchTermDoctor && FiltredTodosMedicos.length > 0 && ( {searchTermDoctor && FiltredTodosMedicos.length > 0 && (
<div className="list-group position-absolute w-100" style={{ zIndex: 1000, maxHeight: '200px', overflowY: 'auto' }}> <div
className="list-group position-absolute w-100"
style={{
zIndex: 1000,
maxHeight: "200px",
overflowY: "auto",
}}
>
{FiltredTodosMedicos.map((medico) => ( {FiltredTodosMedicos.map((medico) => (
<button <button
key={medico.idMedico} key={medico.idMedico}
@ -471,10 +502,10 @@ const DeleteModal = () => (
<button <button
type="button" type="button"
className="btn-close btn-close-white ms-2" className="btn-close btn-close-white ms-2"
style={{ fontSize: '0.6rem' }} style={{ fontSize: "0.6rem" }}
onClick={() => { onClick={() => {
setMedicoFiltrado({ id: "vazio" }); setMedicoFiltrado({ id: "vazio" });
setSearchTermDoctor(''); setSearchTermDoctor("");
}} }}
></button> ></button>
</span> </span>
@ -482,29 +513,114 @@ const DeleteModal = () => (
)} )}
</div> </div>
)} )}
<div className='container-btns-agenda-fila_esepera'> <div className="container-btns-agenda-fila_esepera">
<button className={`btn-agenda ${!FiladeEspera ? "opc-agenda-ativo" : ""}`} onClick={() => { setFiladeEspera(false); }}>Agenda</button> <div className="tabs-agenda-fila">
<button className={`btn-fila-espera ${FiladeEspera ? "opc-filaespera-ativo" : ""}`} onClick={() => { setFiladeEspera(true); }}>Fila de espera</button> <button
className={`btn-agenda ${
!FiladeEspera ? "opc-agenda-ativo" : ""
}`}
onClick={() => {
setFiladeEspera(false);
}}
>
Agenda
</button>
<button
className={`btn-fila-espera ${
FiladeEspera ? "opc-filaespera-ativo" : ""
}`}
onClick={() => {
setFiladeEspera(true);
}}
>
Fila de espera
</button>
</div> </div>
<section className='calendario-ou-filaespera'> <div
className="btns-gerenciamento-e-consulta"
style={{ display: "flex", gap: "10px", marginBottom: "20px" }}
>
<button
className="btn btn-primary"
onClick={() => {
setAgendamentoParaEdicao(null);
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>
</div>
<section className="calendario-ou-filaespera">
{!FiladeEspera ? ( {!FiladeEspera ? (
<div className="calendar-wrapper"> <div className="calendar-wrapper">
<div className="calendar-info-panel"> <div className="calendar-info-panel">
<div className="info-date-display"><span>{selectedDay.format('MMM')}</span><strong>{selectedDay.format('DD')}</strong></div> <div className="info-date-display">
<div className="info-details"><h3>{selectedDay.format('dddd')}</h3><p>{selectedDay.format('D [de] MMMM [de] YYYY')}</p></div> <span>{selectedDay.format("MMM")}</span>
<strong>{selectedDay.format("DD")}</strong>
</div>
<div className="info-details">
<h3>{selectedDay.format("dddd")}</h3>
<p>{selectedDay.format("D [de] MMMM [de] YYYY")}</p>
</div>
<div className="appointments-list"> <div className="appointments-list">
<h4>Consultas para {selectedDay.format('DD/MM')}</h4> <h4>Consultas para {selectedDay.format("DD/MM")}</h4>
{showSpinner ? <Spinner /> : (DictAgendamentosOrganizados[selectedDay.format('YYYY-MM-DD')]?.filter(app => MedicoFiltrado.id === "vazio" || app.doctor_id === MedicoFiltrado.id).length > 0) ? ( {showSpinner ? (
DictAgendamentosOrganizados[selectedDay.format('YYYY-MM-DD')] <Spinner />
.filter(app => MedicoFiltrado.id === "vazio" || app.doctor_id === MedicoFiltrado.id) ) : DictAgendamentosOrganizados[
.map(app => ( selectedDay.format("YYYY-MM-DD")
<div key={app.id} className="appointment-item" data-status={app.status}> ]?.filter(
<div className="item-time">{dayjs(app.scheduled_at).format('HH:mm')}</div> (app) =>
<div className="item-details"><span>{app.paciente_nome}</span><small>Dr(a). {app.medico_nome}</small></div> MedicoFiltrado.id === "vazio" ||
app.doctor_id === MedicoFiltrado.id
).length > 0 ? (
DictAgendamentosOrganizados[
selectedDay.format("YYYY-MM-DD")
]
.filter(
(app) =>
MedicoFiltrado.id === "vazio" ||
app.doctor_id === MedicoFiltrado.id
)
.map((app) => (
<div
key={app.id}
className="appointment-item"
data-status={app.status}
>
<div className="item-time">
{dayjs(app.scheduled_at).format("HH:mm")}
</div>
<div className="item-details">
<span>{app.paciente_nome}</span>
<small>Dr(a). {app.medico_nome}</small>
</div>
<div className="appointment-actions"> <div className="appointment-actions">
{app.status === 'cancelled' ? ( {app.status === "cancelled" ? (
<button className="btn-action btn-edit" onClick={() => { setSelectedId(app.id); confirmConsulta(app.id); }}> <button
<CheckCircle size={16} title="Reverter Cancelamento" /> className="btn-action btn-edit"
onClick={() => {
setSelectedId(app.id);
confirmConsulta(app.id);
}}
>
<CheckCircle
size={16}
title="Reverter Cancelamento"
/>
</button> </button>
) : ( ) : (
<button <button
@ -515,72 +631,156 @@ const DeleteModal = () => (
<Edit size={16} /> <Edit size={16} />
</button> </button>
)} )}
{app.status !== 'cancelled' && ( {app.status !== "cancelled" && (
<button className="btn-action btn-delete" onClick={() => { setSelectedId(app.id); setShowDeleteModal(true); }} title="Cancelar Agendamento"> <button
className="btn-action btn-delete"
onClick={() => {
setSelectedId(app.id);
setShowDeleteModal(true);
}}
title="Cancelar Agendamento"
>
<Trash2 size={16} /> <Trash2 size={16} />
</button> </button>
)} )}
</div> </div>
</div> </div>
)) ))
) : (<div className="no-appointments-info"><p>Nenhuma consulta agendada.</p></div>)} ) : (
<div className="no-appointments-info">
<p>Nenhuma consulta agendada.</p>
</div>
)}
</div> </div>
</div> </div>
<div className="calendar-main"> <div className="calendar-main">
<div className="calendar-legend"> <div className="calendar-legend">
<div className="legend-item" data-status="completed">Realizado</div> <div className="legend-item" data-status="completed">
<div className="legend-item" data-status="confirmed">Confirmado</div> Realizado
<div className="legend-item" data-status="agendado">Agendado</div> </div>
<div className="legend-item" data-status="cancelled">Cancelado</div> <div className="legend-item" data-status="confirmed">
Confirmado
</div>
<div className="legend-item" data-status="agendado">
Agendado
</div>
<div className="legend-item" data-status="cancelled">
Cancelado
</div>
</div> </div>
<div className="calendar-controls"> <div className="calendar-controls">
<div className="date-indicator"> <div className="date-indicator">
<h2>{currentDate.format('MMMM [de] YYYY')}</h2> <h2>{currentDate.format("MMMM [de] YYYY")}</h2>
<div className="quick-jump-controls" style={{ display: 'flex', gap: '5px', marginTop: '10px' }}> <div
className="quick-jump-controls"
style={{
display: "flex",
gap: "5px",
marginTop: "10px",
}}
>
<select <select
value={quickJump.month} value={quickJump.month}
onChange={(e) => handleQuickJumpChange('month', e.target.value)} onChange={(e) =>
handleQuickJumpChange("month", e.target.value)
}
className="form-select form-select-sm w-auto" className="form-select form-select-sm w-auto"
> >
{dayjs.months().map((month, index) => ( {dayjs.months().map((month, index) => (
<option key={index} value={index}>{month.charAt(0).toUpperCase() + month.slice(1)}</option> <option key={index} value={index}>
{month.charAt(0).toUpperCase() + month.slice(1)}
</option>
))} ))}
</select> </select>
<select <select
value={quickJump.year} value={quickJump.year}
onChange={(e) => handleQuickJumpChange('year', e.target.value)} onChange={(e) =>
handleQuickJumpChange("year", e.target.value)
}
className="form-select form-select-sm w-auto" className="form-select form-select-sm w-auto"
> >
{Array.from({ length: 11 }, (_, i) => dayjs().year() - 5 + i).map(year => ( {Array.from(
<option key={year} value={year}>{year}</option> { length: 11 },
(_, i) => dayjs().year() - 5 + i
).map((year) => (
<option key={year} value={year}>
{year}
</option>
))} ))}
</select> </select>
<button <button
className="btn btn-sm btn-outline-primary" className="btn btn-sm btn-outline-primary"
onClick={applyQuickJump} onClick={applyQuickJump}
disabled={quickJump.month === currentDate.month() && quickJump.year === currentDate.year()} disabled={
quickJump.month === currentDate.month() &&
quickJump.year === currentDate.year()
}
> >
Ir Ir
</button> </button>
</div> </div>
</div> </div>
<div className="nav-buttons"> <div className="nav-buttons">
<button onClick={() => { setCurrentDate(currentDate.subtract(1, 'month')); setSelectedDay(currentDate.subtract(1, 'month')); }}><ChevronLeft size={20} /></button> <button
<button onClick={() => { setCurrentDate(dayjs()); setSelectedDay(dayjs()); }}>Hoje</button> onClick={() => {
<button onClick={() => { setCurrentDate(currentDate.add(1, 'month')); setSelectedDay(currentDate.add(1, 'month')); }}><ChevronRight size={20} /></button> setCurrentDate(currentDate.subtract(1, "month"));
setSelectedDay(currentDate.subtract(1, "month"));
}}
>
<ChevronLeft size={20} />
</button>
<button
onClick={() => {
setCurrentDate(dayjs());
setSelectedDay(dayjs());
}}
>
Hoje
</button>
<button
onClick={() => {
setCurrentDate(currentDate.add(1, "month"));
setSelectedDay(currentDate.add(1, "month"));
}}
>
<ChevronRight size={20} />
</button>
</div> </div>
</div> </div>
<div className="calendar-grid"> <div className="calendar-grid">
{weekDays.map(day => <div key={day} className="day-header">{day}</div>)} {weekDays.map((day) => (
<div key={day} className="day-header">
{day}
</div>
))}
{dateGrid.map((day, index) => { {dateGrid.map((day, index) => {
const dayString = day.format('YYYY-MM-DD'); const dayString = day.format("YYYY-MM-DD");
const appointmentsOnDay = DictAgendamentosOrganizados[dayString] || []; const appointmentsOnDay =
const filteredAppointments = appointmentsOnDay.filter(app => MedicoFiltrado.id === "vazio" || app.doctor_id === MedicoFiltrado.id); DictAgendamentosOrganizados[dayString] || [];
const cellClasses = `day-cell ${day.isSame(currentDate, 'month') ? 'current-month' : 'other-month'} ${day.isSame(dayjs(), 'day') ? 'today' : ''} ${day.isSame(selectedDay, 'day') ? 'selected' : ''}`; const filteredAppointments = appointmentsOnDay.filter(
(app) =>
MedicoFiltrado.id === "vazio" ||
app.doctor_id === MedicoFiltrado.id
);
const cellClasses = `day-cell ${
day.isSame(currentDate, "month")
? "current-month"
: "other-month"
} ${day.isSame(dayjs(), "day") ? "today" : ""} ${
day.isSame(selectedDay, "day") ? "selected" : ""
}`;
return ( return (
<div key={index} className={cellClasses} onClick={() => handleDateClick(day)}> <div
<span>{day.format('D')}</span> key={index}
{filteredAppointments.length > 0 && <div className="appointments-indicator">{filteredAppointments.length}</div>} className={cellClasses}
onClick={() => handleDateClick(day)}
>
<span>{day.format("D")}</span>
{filteredAppointments.length > 0 && (
<div className="appointments-indicator">
{filteredAppointments.length}
</div>
)}
</div> </div>
); );
})} })}
@ -592,37 +792,78 @@ const DeleteModal = () => (
<section className="row"> <section className="row">
<div className="col-12"> <div className="col-12">
<div className="card table-paciente-card"> <div className="card table-paciente-card">
<div className="card-header"><h4 className="card-title mb-0">Fila de Espera</h4></div> <div className="card-header">
<h4 className="card-title mb-0">Fila de Espera</h4>
</div>
<div className="card-body"> <div className="card-body">
<div className="card p-3 mb-3 table-paciente-filters"> <div className="card p-3 mb-3 table-paciente-filters">
<h5 className="mb-3"><i className="bi bi-funnel-fill me-2 text-primary"></i> Filtros</h5> <h5 className="mb-3">
<div className="mb-3"><input type="text" className="form-control" placeholder="Buscar por paciente, CPF ou médico..." value={waitlistSearch} onChange={(e) => setWaitlistSearch(e.target.value)} /><small className="text-muted">Digite o nome do paciente, CPF ou nome do médico</small></div> <i className="bi bi-funnel-fill me-2 text-primary"></i>{" "}
Filtros
</h5>
<div className="mb-3">
<input
type="text"
className="form-control"
placeholder="Buscar por paciente, CPF ou médico..."
value={waitlistSearch}
onChange={(e) =>
setWaitlistSearch(e.target.value)
}
/>
<small className="text-muted">
Digite o nome do paciente, CPF ou nome do médico
</small>
</div>
<div className="d-flex flex-wrap align-items-center gap-2 mb-3"> <div className="d-flex flex-wrap align-items-center gap-2 mb-3">
<div className="d-flex align-items-center gap-2"> <div className="d-flex align-items-center gap-2">
<span className="me-2 text-muted small">Ordenar por:</span> <span className="me-2 text-muted small">
Ordenar por:
</span>
<select <select
className="form-select compact-select sort-select w-auto" className="form-select compact-select sort-select w-auto"
value={waitSortKey ? `${waitSortKey}-${waitSortDir}` : ''} value={
waitSortKey
? `${waitSortKey}-${waitSortDir}`
: ""
}
onChange={(e) => { onChange={(e) => {
const v = e.target.value; const v = e.target.value;
if (!v) { setWaitSortKey(null); setWaitSortDir('asc'); return; } if (!v) {
const [k, d] = v.split('-'); setWaitSortKey(null);
setWaitSortDir("asc");
return;
}
const [k, d] = v.split("-");
setWaitSortKey(k); setWaitSortKey(k);
setWaitSortDir(d); setWaitSortDir(d);
}} }}
> >
<option value="">Sem ordenação</option> <option value="">Sem ordenação</option>
<option value="paciente-asc">Paciente (A-Z)</option> <option value="paciente-asc">
<option value="paciente-desc">Paciente (Z-A)</option> Paciente (A-Z)
</option>
<option value="paciente-desc">
Paciente (Z-A)
</option>
<option value="medico-asc">Médico (A-Z)</option> <option value="medico-asc">Médico (A-Z)</option>
<option value="medico-desc">Médico (Z-A)</option> <option value="medico-desc">
<option value="data-asc">Data (mais antiga)</option> Médico (Z-A)
<option value="data-desc">Data (mais recente)</option> </option>
<option value="data-asc">
Data (mais antiga)
</option>
<option value="data-desc">
Data (mais recente)
</option>
</select> </select>
</div> </div>
</div> </div>
<div className="mt-3"> <div className="mt-3">
<div className="contador-pacientes">{filaEsperaFiltrada.length} DE {filaEsperaData.length} SOLICITAÇÕES ENCONTRADAS</div> <div className="contador-pacientes">
{filaEsperaFiltrada.length} DE{" "}
{filaEsperaData.length} SOLICITAÇÕES ENCONTRADAS
</div>
</div> </div>
</div> </div>
<div className="table-responsive"> <div className="table-responsive">
@ -643,10 +884,21 @@ const DeleteModal = () => (
<td>{item?.Infos?.paciente_nome}</td> <td>{item?.Infos?.paciente_nome}</td>
<td>{item?.Infos?.paciente_cpf}</td> <td>{item?.Infos?.paciente_cpf}</td>
<td>{item?.Infos?.medico_nome}</td> <td>{item?.Infos?.medico_nome}</td>
<td>{dayjs(item.agendamento.scheduled_at).format('DD/MM/YYYY')}</td>
<td> <td>
<button className="btn btn-sm btn-delete" onClick={() => { setSelectedId(item.agendamento.id); setShowDeleteModal(true); }}> {dayjs(
<i className="bi bi-trash me-1"></i> Excluir item.agendamento.scheduled_at
).format("DD/MM/YYYY")}
</td>
<td>
<button
className="btn btn-sm btn-delete"
onClick={() => {
setSelectedId(item.agendamento.id);
setShowDeleteModal(true);
}}
>
<i className="bi bi-trash me-1"></i>{" "}
Excluir
</button> </button>
</td> </td>
</tr> </tr>
@ -655,7 +907,16 @@ const DeleteModal = () => (
<tr> <tr>
<td colSpan="5" className="text-center py-4"> <td colSpan="5" className="text-center py-4">
<div className="text-muted"> <div className="text-muted">
{showSpinner ? <Spinner /> : (<><i className="bi bi-inbox display-4"></i><p className="mt-2">Nenhuma solicitação encontrada.</p></>)} {showSpinner ? (
<Spinner />
) : (
<>
<i className="bi bi-inbox display-4"></i>
<p className="mt-2">
Nenhuma solicitação encontrada.
</p>
</>
)}
</div> </div>
</td> </td>
</tr> </tr>
@ -665,11 +926,16 @@ const DeleteModal = () => (
{filaEsperaFiltrada.length > 0 && ( {filaEsperaFiltrada.length > 0 && (
<div className="d-flex justify-content-between align-items-center mt-3"> <div className="d-flex justify-content-between align-items-center mt-3">
<div className="d-flex align-items-center"> <div className="d-flex align-items-center">
<span className="me-2 text-muted">Itens por página:</span> <span className="me-2 text-muted">
Itens por página:
</span>
<select <select
className="form-select form-select-sm w-auto" className="form-select form-select-sm w-auto"
value={waitPerPage} value={waitPerPage}
onChange={(e) => { setWaitPerPage(Number(e.target.value)); setWaitPage(1); }} onChange={(e) => {
setWaitPerPage(Number(e.target.value));
setWaitPage(1);
}}
> >
<option value={5}>5</option> <option value={5}>5</option>
<option value={10}>10</option> <option value={10}>10</option>
@ -678,21 +944,61 @@ const DeleteModal = () => (
</select> </select>
</div> </div>
<div className="d-flex align-items-center"> <div className="d-flex align-items-center">
<span className="me-3 text-muted">Página {waitPage} de {waitTotalPages} Mostrando {waitIndiceInicial + 1}-{Math.min(waitIndiceFinal, filaEsperaFiltrada.length)} de {filaEsperaFiltrada.length}</span> <span className="me-3 text-muted">
Página {waitPage} de {waitTotalPages}
Mostrando {waitIndiceInicial + 1}-
{Math.min(
waitIndiceFinal,
filaEsperaFiltrada.length
)}{" "}
de {filaEsperaFiltrada.length}
</span>
<nav> <nav>
<ul className="pagination pagination-sm mb-0"> <ul className="pagination pagination-sm mb-0">
<li className={`page-item ${waitPage === 1 ? 'disabled' : ''}`}> <li
<button className="page-link" onClick={() => setWaitPage(p => Math.max(1, p - 1))}> className={`page-item ${
waitPage === 1 ? "disabled" : ""
}`}
>
<button
className="page-link"
onClick={() =>
setWaitPage((p) => Math.max(1, p - 1))
}
>
<i className="bi bi-chevron-left"></i> <i className="bi bi-chevron-left"></i>
</button> </button>
</li> </li>
{gerarNumerosWaitPages().map(pagina => ( {gerarNumerosWaitPages().map((pagina) => (
<li key={pagina} className={`page-item ${pagina === waitPage ? 'active' : ''}`}> <li
<button className="page-link" onClick={() => setWaitPage(pagina)}>{pagina}</button> key={pagina}
className={`page-item ${
pagina === waitPage ? "active" : ""
}`}
>
<button
className="page-link"
onClick={() => setWaitPage(pagina)}
>
{pagina}
</button>
</li> </li>
))} ))}
<li className={`page-item ${waitPage === waitTotalPages ? 'disabled' : ''}`}> <li
<button className="page-link" onClick={() => setWaitPage(p => Math.min(waitTotalPages, p + 1))}> className={`page-item ${
waitPage === waitTotalPages
? "disabled"
: ""
}`}
>
<button
className="page-link"
onClick={() =>
setWaitPage((p) =>
Math.min(waitTotalPages, p + 1)
)
}
>
<i className="bi bi-chevron-right"></i> <i className="bi bi-chevron-right"></i>
</button> </button>
</li> </li>
@ -723,6 +1029,6 @@ const DeleteModal = () => (
{showDeleteModal && <DeleteModal />} {showDeleteModal && <DeleteModal />}
</div> </div>
); );
} };
export default Agendamento; export default Agendamento;

View File

@ -140,6 +140,7 @@
border-bottom: 3px solid transparent; border-bottom: 3px solid transparent;
padding: 8px; padding: 8px;
border-radius: 10px 10px 0px 0px; border-radius: 10px 10px 0px 0px;
color: #fff;
font-weight: bold; font-weight: bold;
cursor: pointer; cursor: pointer;
} }

View File

@ -1,5 +1,5 @@
import React, { useState, useEffect, useMemo, useCallback } from "react"; import React, { useState, useEffect, useMemo, useCallback } from "react";
import './style/FinanceiroDashboard.css'; import "./style/FinanceiroDashboard.css";
const CONVENIOS_LIST = [ const CONVENIOS_LIST = [
"Particular", "Particular",
@ -8,7 +8,7 @@ const CONVENIOS_LIST = [
"SulAmérica", "SulAmérica",
"Unimed", "Unimed",
"Cassio", "Cassio",
"Outro" "Outro",
]; ];
function CurrencyInput({ value, onChange, label, id }) { function CurrencyInput({ value, onChange, label, id }) {
@ -17,22 +17,25 @@ function CurrencyInput({ value, onChange, label, id }) {
let stringValue = String(numericValue); let stringValue = String(numericValue);
while (stringValue.length < 3) { while (stringValue.length < 3) {
stringValue = '0' + stringValue; stringValue = "0" + stringValue;
} }
const integerPart = stringValue.slice(0, -2); const integerPart = stringValue.slice(0, -2);
const decimalPart = stringValue.slice(-2); const decimalPart = stringValue.slice(-2);
const formattedInteger = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, '.'); const formattedInteger = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, ".");
return `R$ ${formattedInteger},${decimalPart}`; return `R$ ${formattedInteger},${decimalPart}`;
}, [value]); }, [value]);
const handleKeyDown = useCallback((e) => { const handleKeyDown = useCallback(
(e) => {
const key = e.key; const key = e.key;
if (['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab'].includes(key)) { if (
if (key === 'Backspace' || key === 'Delete') { ["Backspace", "Delete", "ArrowLeft", "ArrowRight", "Tab"].includes(key)
) {
if (key === "Backspace" || key === "Delete") {
e.preventDefault(); e.preventDefault();
const numericValue = value || 0; const numericValue = value || 0;
let newValueString = String(numericValue); let newValueString = String(numericValue);
@ -63,7 +66,9 @@ function CurrencyInput({ value, onChange, label, id }) {
const newNumericValue = parseInt(newValueString); const newNumericValue = parseInt(newValueString);
onChange(newNumericValue); onChange(newNumericValue);
}, [value, onChange]); },
[value, onChange]
);
return ( return (
<div className="form-group"> <div className="form-group">
@ -91,7 +96,7 @@ function mockFetchPagamentos() {
data_vencimento: "2025-09-30", data_vencimento: "2025-09-30",
status: "pendente", status: "pendente",
desconto: 0, desconto: 0,
observacoes: "Pagamento parcelado em 2x" observacoes: "Pagamento parcelado em 2x",
}, },
{ {
id: "PAY-002", id: "PAY-002",
@ -101,7 +106,7 @@ function mockFetchPagamentos() {
data_vencimento: "2025-09-15", data_vencimento: "2025-09-15",
status: "pago", status: "pago",
desconto: 1000, desconto: 1000,
observacoes: "" observacoes: "",
}, },
{ {
id: "PAY-003", id: "PAY-003",
@ -111,7 +116,7 @@ function mockFetchPagamentos() {
data_vencimento: "2025-09-20", data_vencimento: "2025-09-20",
status: "vencido", status: "vencido",
desconto: 0, desconto: 0,
observacoes: "Não respondeu ao contato" observacoes: "Não respondeu ao contato",
}, },
{ {
id: "PAY-004", id: "PAY-004",
@ -121,8 +126,8 @@ function mockFetchPagamentos() {
data_vencimento: "2025-09-29", data_vencimento: "2025-09-29",
status: "pago", status: "pago",
desconto: 500, desconto: 500,
observacoes: "Desconto por pagamento adiantado" observacoes: "Desconto por pagamento adiantado",
} },
]; ];
} }
@ -132,7 +137,11 @@ export default function FinanceiroDashboard() {
const [query, setQuery] = useState(""); const [query, setQuery] = useState("");
const [filtroStatus, setFiltroStatus] = useState("Todos"); const [filtroStatus, setFiltroStatus] = useState("Todos");
const [novoPagamento, setNovoPagamento] = useState(false); const [novoPagamento, setNovoPagamento] = useState(false);
const [summary, setSummary] = useState({ totalRecebido: 0, totalAReceber: 0, totalDescontos: 0 }); const [summary, setSummary] = useState({
totalRecebido: 0,
totalAReceber: 0,
totalDescontos: 0,
});
useEffect(() => { useEffect(() => {
const data = mockFetchPagamentos(); const data = mockFetchPagamentos();
@ -141,7 +150,13 @@ export default function FinanceiroDashboard() {
function formatCurrency(centavos) { function formatCurrency(centavos) {
const valorEmReais = centavos / 100; const valorEmReais = centavos / 100;
return "R$ " + valorEmReais.toFixed(2).replace(".", ",").replace(/\B(?=(\d{3})+(?!\d))/g, '.'); return (
"R$ " +
valorEmReais
.toFixed(2)
.replace(".", ",")
.replace(/\B(?=(\d{3})+(?!\d))/g, ".")
);
} }
function getValorLiquido(valor, desconto) { function getValorLiquido(valor, desconto) {
@ -149,10 +164,11 @@ export default function FinanceiroDashboard() {
} }
const filteredPagamentos = useMemo(() => { const filteredPagamentos = useMemo(() => {
return pagamentos.filter(p => { return pagamentos.filter((p) => {
const q = query.toLowerCase(); const q = query.toLowerCase();
const statusOk = filtroStatus === "Todos" || p.status === filtroStatus; const statusOk = filtroStatus === "Todos" || p.status === filtroStatus;
const buscaOk = p.paciente.nome.toLowerCase().includes(q) || const buscaOk =
p.paciente.nome.toLowerCase().includes(q) ||
p.id.toLowerCase().includes(q); p.id.toLowerCase().includes(q);
return statusOk && buscaOk; return statusOk && buscaOk;
}); });
@ -163,9 +179,9 @@ export default function FinanceiroDashboard() {
let aReceber = 0; let aReceber = 0;
let descontos = 0; let descontos = 0;
filteredPagamentos.forEach(p => { filteredPagamentos.forEach((p) => {
const valorLiquido = getValorLiquido(p.valor, p.desconto); const valorLiquido = getValorLiquido(p.valor, p.desconto);
if (p.status === 'pago') { if (p.status === "pago") {
recebido += valorLiquido; recebido += valorLiquido;
descontos += p.desconto; descontos += p.desconto;
} else { } else {
@ -176,29 +192,37 @@ export default function FinanceiroDashboard() {
setSummary({ setSummary({
totalRecebido: recebido, totalRecebido: recebido,
totalAReceber: aReceber, totalAReceber: aReceber,
totalDescontos: descontos totalDescontos: descontos,
}); });
}, [filteredPagamentos]); }, [filteredPagamentos]);
function handleDelete(id) { function handleDelete(id) {
if (window.confirm("Tem certeza que deseja excluir este pagamento?")) { if (window.confirm("Tem certeza que deseja excluir este pagamento?")) {
setPagamentos(prev => prev.filter(p => p.id !== id)); setPagamentos((prev) => prev.filter((p) => p.id !== id));
setModalPagamento(null); setModalPagamento(null);
} }
} }
function handleSave(pagamento) { function handleSave(pagamento) {
if (!pagamento.paciente.nome || !pagamento.valor || !pagamento.data_vencimento || !pagamento.paciente.convenio) { if (
!pagamento.paciente.nome ||
!pagamento.valor ||
!pagamento.data_vencimento ||
!pagamento.paciente.convenio
) {
alert("Preencha Paciente, Convênio, Valor e Data de Vencimento."); alert("Preencha Paciente, Convênio, Valor e Data de Vencimento.");
return; return;
} }
if (novoPagamento) { if (novoPagamento) {
const newId = "PAY-" + (pagamentos.length + 1).toString().padStart(3, "0"); const newId =
"PAY-" + (pagamentos.length + 1).toString().padStart(3, "0");
pagamento.id = newId; pagamento.id = newId;
setPagamentos(prev => [...prev, pagamento]); setPagamentos((prev) => [...prev, pagamento]);
} else { } else {
setPagamentos(prev => prev.map(p => p.id === pagamento.id ? pagamento : p)); setPagamentos((prev) =>
prev.map((p) => (p.id === pagamento.id ? pagamento : p))
);
} }
setModalPagamento(null); setModalPagamento(null);
setNovoPagamento(false); setNovoPagamento(false);
@ -234,10 +258,14 @@ export default function FinanceiroDashboard() {
className="input-field" className="input-field"
placeholder="Buscar paciente" placeholder="Buscar paciente"
value={query} value={query}
onChange={e => setQuery(e.target.value)} onChange={(e) => setQuery(e.target.value)}
style={{ flexGrow: 1 }} style={{ flexGrow: 1 }}
/> />
<select className="select-field" value={filtroStatus} onChange={e => setFiltroStatus(e.target.value)}> <select
className="select-field"
value={filtroStatus}
onChange={(e) => setFiltroStatus(e.target.value)}
>
<option value="Todos">Todos</option> <option value="Todos">Todos</option>
<option value="pago">Pago</option> <option value="pago">Pago</option>
<option value="pendente">Pendente</option> <option value="pendente">Pendente</option>
@ -250,10 +278,10 @@ export default function FinanceiroDashboard() {
paciente: { nome: "", convenio: CONVENIOS_LIST[0] }, paciente: { nome: "", convenio: CONVENIOS_LIST[0] },
valor: 0, valor: 0,
forma_pagamento: "Dinheiro", forma_pagamento: "Dinheiro",
data_vencimento: new Date().toISOString().split('T')[0], data_vencimento: new Date().toISOString().split("T")[0],
status: "pendente", status: "pendente",
desconto: 0, desconto: 0,
observacoes:"" observacoes: "",
}); });
setNovoPagamento(true); setNovoPagamento(true);
}} }}
@ -281,21 +309,30 @@ export default function FinanceiroDashboard() {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{filteredPagamentos.map(p => ( {filteredPagamentos.map((p) => (
<tr key={p.id}> <tr key={p.id}>
<td style={{ fontWeight: 600 }}>{p.paciente.nome}</td> <td style={{ fontWeight: 600 }}>{p.paciente.nome}</td>
<td>{p.paciente.convenio}</td> <td>{p.paciente.convenio}</td>
<td>{formatCurrency(p.valor)}</td> <td>{formatCurrency(p.valor)}</td>
<td>{formatCurrency(p.desconto)}</td> <td>{formatCurrency(p.desconto)}</td>
<td style={{ fontWeight: 600 }}>{formatCurrency(getValorLiquido(p.valor, p.desconto))}</td> <td style={{ fontWeight: 600 }}>
{formatCurrency(getValorLiquido(p.valor, p.desconto))}
</td>
<td>{p.forma_pagamento}</td> <td>{p.forma_pagamento}</td>
<td>{p.data_vencimento.split('-').reverse().join('/')}</td> <td>{p.data_vencimento.split("-").reverse().join("/")}</td>
<td><span className={`badge ${p.status}`}>{p.status.toUpperCase()}</span></td> <td>
<span className={`badge ${p.status}`}>
{p.status.toUpperCase()}
</span>
</td>
<td> <td>
<div className="action-group"> <div className="action-group">
<button <button
className="btn-view" className="btn-view"
onClick={() => { setModalPagamento({...p}); setNovoPagamento(false); }} onClick={() => {
setModalPagamento({ ...p });
setNovoPagamento(false);
}}
> >
<i className="bi bi-eye me-1"></i> Ver / Editar <i className="bi bi-eye me-1"></i> Ver / Editar
</button> </button>
@ -316,11 +353,20 @@ export default function FinanceiroDashboard() {
</div> </div>
{modalPagamento && ( {modalPagamento && (
<div className="modal" onClick={(e) => e.target.classList.contains('modal') && closeModal()}> <div
className="modal"
onClick={(e) => e.target.classList.contains("modal") && closeModal()}
>
<div className="modal-card"> <div className="modal-card">
<div className="modal-header"> <div className="modal-header">
<h2>{novoPagamento ? "Adicionar Pagamento" : `Editar Pagamento - ${modalPagamento.paciente.nome}`}</h2> <h2>
<button className="close-btn" onClick={closeModal}>×</button> {novoPagamento
? "Adicionar Pagamento"
: `Editar Pagamento - ${modalPagamento.paciente.nome}`}
</h2>
<button className="close-btn" onClick={closeModal}>
×
</button>
</div> </div>
<div className="modal-body"> <div className="modal-body">
@ -330,7 +376,15 @@ export default function FinanceiroDashboard() {
id="paciente_nome" id="paciente_nome"
className="input-field" className="input-field"
value={modalPagamento.paciente.nome} value={modalPagamento.paciente.nome}
onChange={e => setModalPagamento({...modalPagamento, paciente:{...modalPagamento.paciente, nome:e.target.value}})} onChange={(e) =>
setModalPagamento({
...modalPagamento,
paciente: {
...modalPagamento.paciente,
nome: e.target.value,
},
})
}
/> />
</div> </div>
@ -340,11 +394,21 @@ export default function FinanceiroDashboard() {
id="convenio" id="convenio"
className="select-field" className="select-field"
value={modalPagamento.paciente.convenio} value={modalPagamento.paciente.convenio}
onChange={e => setModalPagamento({...modalPagamento, paciente:{...modalPagamento.paciente, convenio:e.target.value}})} onChange={(e) =>
setModalPagamento({
...modalPagamento,
paciente: {
...modalPagamento.paciente,
convenio: e.target.value,
},
})
}
> >
<option value="">Selecione</option> <option value="">Selecione</option>
{CONVENIOS_LIST.map(conv => ( {CONVENIOS_LIST.map((conv) => (
<option key={conv} value={conv}>{conv}</option> <option key={conv} value={conv}>
{conv}
</option>
))} ))}
</select> </select>
</div> </div>
@ -352,14 +416,18 @@ export default function FinanceiroDashboard() {
id="valor" id="valor"
label="Valor da consulta (R$)" label="Valor da consulta (R$)"
value={modalPagamento.valor} value={modalPagamento.valor}
onChange={newValue => setModalPagamento({...modalPagamento, valor: newValue})} onChange={(newValue) =>
setModalPagamento({ ...modalPagamento, valor: newValue })
}
/> />
<CurrencyInput <CurrencyInput
id="desconto" id="desconto"
label="Desconto aplicado (R$)" label="Desconto aplicado (R$)"
value={modalPagamento.desconto} value={modalPagamento.desconto}
onChange={newValue => setModalPagamento({...modalPagamento, desconto: newValue})} onChange={(newValue) =>
setModalPagamento({ ...modalPagamento, desconto: newValue })
}
/> />
<div className="form-group"> <div className="form-group">
@ -368,7 +436,12 @@ export default function FinanceiroDashboard() {
id="forma" id="forma"
className="select-field" className="select-field"
value={modalPagamento.forma_pagamento} value={modalPagamento.forma_pagamento}
onChange={e => setModalPagamento({...modalPagamento, forma_pagamento:e.target.value})} onChange={(e) =>
setModalPagamento({
...modalPagamento,
forma_pagamento: e.target.value,
})
}
> >
<option>Dinheiro</option> <option>Dinheiro</option>
<option>Cartão</option> <option>Cartão</option>
@ -384,7 +457,12 @@ export default function FinanceiroDashboard() {
className="input-field" className="input-field"
type="date" type="date"
value={modalPagamento.data_vencimento} value={modalPagamento.data_vencimento}
onChange={e => setModalPagamento({...modalPagamento, data_vencimento:e.target.value})} onChange={(e) =>
setModalPagamento({
...modalPagamento,
data_vencimento: e.target.value,
})
}
/> />
</div> </div>
@ -394,7 +472,12 @@ export default function FinanceiroDashboard() {
id="status" id="status"
className="select-field" className="select-field"
value={modalPagamento.status} value={modalPagamento.status}
onChange={e => setModalPagamento({...modalPagamento, status:e.target.value})} onChange={(e) =>
setModalPagamento({
...modalPagamento,
status: e.target.value,
})
}
> >
<option value="pago">Pago</option> <option value="pago">Pago</option>
<option value="pendente">Pendente</option> <option value="pendente">Pendente</option>
@ -409,24 +492,28 @@ export default function FinanceiroDashboard() {
className="input-field" className="input-field"
rows={3} rows={3}
value={modalPagamento.observacoes} value={modalPagamento.observacoes}
onChange={e => setModalPagamento({...modalPagamento, observacoes:e.target.value})} onChange={(e) =>
setModalPagamento({
...modalPagamento,
observacoes: e.target.value,
})
}
/> />
</div> </div>
</div>
<div className="modal-footer"> <div className="modal-footer">
<button className="btn-view" onClick={() => handleSave(modalPagamento)}> <button
className="btn-view"
onClick={() => handleSave(modalPagamento)}
>
<i className="bi bi-check-circle me-1"></i> Salvar <i className="bi bi-check-circle me-1"></i> Salvar
</button> </button>
<button <button className="btn-delete" onClick={closeModal}>
className="btn-delete"
onClick={closeModal}
>
<i className="bi bi-x-circle me-1"></i> Cancelar <i className="bi bi-x-circle me-1"></i> Cancelar
</button> </button>
</div> </div>
</div> </div>
</div> </div>
</div>
)} )}
</div> </div>
); );

View File

@ -186,9 +186,9 @@ html[data-bs-theme="dark"] .btn-delete {
/* Badges de status */ /* Badges de status */
.badge { .badge {
display: inline-block; display: inline-block;
padding: 4px 10px; padding: 8px 18px !important;
border-radius: 9999px; border-radius: 9999px;
font-size: 12px; font-size: 14px !important;
font-weight: 600; font-weight: 600;
text-transform: uppercase; text-transform: uppercase;
} }
@ -228,7 +228,6 @@ html[data-bs-theme="dark"] .btn-delete {
width: 100%; width: 100%;
max-width: 550px; max-width: 550px;
max-height: 85vh; max-height: 85vh;
overflow-y: auto;
box-sizing: border-box; box-sizing: border-box;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1); box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
} }
@ -243,7 +242,7 @@ html[data-bs-theme="dark"] .btn-delete {
.modal-header h2 { .modal-header h2 {
font-size: 20px; font-size: 20px;
font-weight: 700; font-weight: 700;
color: #1f2937; color: #fff;
margin: 0; margin: 0;
} }

View File

@ -25,7 +25,7 @@
/* Estatísticas - Paciente */ /* Estatísticas - Paciente */
.stats-paciente-grid { .stats-paciente-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 1.5rem; gap: 1.5rem;
margin-bottom: 2.5rem; margin-bottom: 2.5rem;
} }
@ -81,10 +81,10 @@
} }
/* Cores dos ícones - Paciente */ /* Cores dos ícones - Paciente */
.stat-paciente-icon-wrapper.blue { background-color: #5d5dff; } .stat-paciente-icon-wrapper.blue { background-color: #051AFF; }
.stat-paciente-icon-wrapper.green { background-color: #30d158; } .stat-paciente-icon-wrapper.green { background-color: #5F5DF2; }
.stat-paciente-icon-wrapper.purple { background-color: #a272ff; } .stat-paciente-icon-wrapper.purple { background-color: #a272ff; }
.stat-paciente-icon-wrapper.orange { background-color: #f1952e; } .stat-paciente-icon-wrapper.orange { background-color: #0065FF; }
/* Ações Rápidas - Paciente */ /* Ações Rápidas - Paciente */
.quick-actions-paciente h2 { .quick-actions-paciente h2 {
@ -432,14 +432,6 @@ html[data-bs-theme="dark"] .view-all-paciente-button:hover {
padding: 1rem; padding: 1rem;
} }
.stats-paciente-grid {
grid-template-columns: 1fr;
}
.actions-paciente-grid {
grid-template-columns: 1fr;
}
.consulta-paciente-info { .consulta-paciente-info {
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;