Compare commits

...

2 Commits

Author SHA1 Message Date
176489f9fd Merge branch 'main' into aperfeicoamento-do-design 2025-11-27 01:17:34 -03:00
b46da18c45 Correções de design 2025-11-27 01:02:34 -03:00
5 changed files with 402 additions and 276 deletions

View File

@ -10,50 +10,50 @@ 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";
dayjs.locale("pt-br");
dayjs.locale('pt-br');
dayjs.extend(isBetween); dayjs.extend(isBetween);
dayjs.extend(localeData); dayjs.extend(localeData);
const Agendamento = () => { const Agendamento = () => {
const navigate = useNavigate(); const navigate = useNavigate();
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([]);
const [selectedID, setSelectedId] = useState('0'); const [selectedID, setSelectedId] = useState("0");
const [filaEsperaData, setFilaEsperaData] = useState([]); const [filaEsperaData, setFilaEsperaData] = useState([]);
const [FiladeEspera, setFiladeEspera] = useState(false); const [FiladeEspera, setFiladeEspera] = useState(false);
const [PageNovaConsulta, setPageConsulta] = useState(false); const [PageNovaConsulta, setPageConsulta] = useState(false);
const [DictAgendamentosOrganizados, setAgendamentosOrganizados] = useState({}); const [DictAgendamentosOrganizados, setAgendamentosOrganizados] = useState(
const [showDeleteModal, setShowDeleteModal] = useState(false); {}
const [ListaDeMedicos, setListaDeMedicos] = useState([]); );
const [FiltredTodosMedicos, setFiltredTodosMedicos] = useState([]); const [showDeleteModal, setShowDeleteModal] = useState(false);
const [searchTermDoctor, setSearchTermDoctor] = useState(''); const [ListaDeMedicos, setListaDeMedicos] = useState([]);
const [MedicoFiltrado, setMedicoFiltrado] = useState({ id: "vazio" }); const [FiltredTodosMedicos, setFiltredTodosMedicos] = useState([]);
const [motivoCancelamento, setMotivoCancelamento] = useState(""); const [searchTermDoctor, setSearchTermDoctor] = useState("");
const [showSpinner, setShowSpinner] = useState(true); const [MedicoFiltrado, setMedicoFiltrado] = useState({ id: "vazio" });
const [waitlistSearch, setWaitlistSearch] = useState(''); const [motivoCancelamento, setMotivoCancelamento] = useState("");
const [waitSortKey, setWaitSortKey] = useState(null); const [showSpinner, setShowSpinner] = useState(true);
const [waitSortDir, setWaitSortDir] = useState('asc'); const [waitlistSearch, setWaitlistSearch] = useState("");
const [waitPage, setWaitPage] = useState(1); const [waitSortKey, setWaitSortKey] = useState(null);
const [waitPerPage, setWaitPerPage] = useState(10); const [waitSortDir, setWaitSortDir] = useState("asc");
const [cacheMedicos, setCacheMedicos] = useState({}); const [waitPage, setWaitPage] = useState(1);
const [cachePacientes, setCachePacientes] = useState({}); const [waitPerPage, setWaitPerPage] = useState(10);
const [currentDate, setCurrentDate] = useState(dayjs()); const [cacheMedicos, setCacheMedicos] = useState({});
const [selectedDay, setSelectedDay] = useState(dayjs()); const [cachePacientes, setCachePacientes] = useState({});
const [agendamentoParaEdicao, setAgendamentoParaEdicao] = useState(null); const [currentDate, setCurrentDate] = useState(dayjs());
const [quickJump, setQuickJump] = useState({ const [selectedDay, setSelectedDay] = useState(dayjs());
month: currentDate.month(), const [agendamentoParaEdicao, setAgendamentoParaEdicao] = useState(null);
year: currentDate.year() const [quickJump, setQuickJump] = useState({
}); month: currentDate.month(),
year: currentDate.year(),
});
const fetchAppointments = useCallback(async () => { const fetchAppointments = useCallback(async () => {
@ -67,65 +67,89 @@ const Agendamento = () => {
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=*`;
try { try {
const res = await fetch(apiUrl, requestOptions); const res = await fetch(apiUrl, requestOptions);
const data = await res.json(); const data = await res.json();
setListaTodosAgendamentos(data || []); setListaTodosAgendamentos(data || []);
} catch (err) { } catch (err) {
console.error('Erro ao buscar agendamentos', err); console.error("Erro ao buscar agendamentos", err);
setListaTodosAgendamentos([]); setListaTodosAgendamentos([]);
} finally { } finally {
setShowSpinner(false); setShowSpinner(false);
} }
}, [authHeader, ID_MEDICO_ESPECIFICO]); }, [authHeader, ID_MEDICO_ESPECIFICO]);
const updateAppointmentStatus = useCallback(
const updateAppointmentStatus = useCallback(async (id, updates) => { async (id, updates) => {
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);
myHeaders.append("Content-Type", "application/json"); myHeaders.append("Content-Type", "application/json");
myHeaders.append("Prefer", "return=representation"); myHeaders.append("Prefer", "return=representation");
const requestOptions = { method: 'PATCH', headers: myHeaders, body: JSON.stringify(updates) }; const requestOptions = {
try { method: "PATCH",
const response = await fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?id=eq.${id}`, requestOptions); headers: myHeaders,
if (response.ok) { body: JSON.stringify(updates),
await fetchAppointments(); };
return true; try {
} else { const response = await fetch(
console.error('Erro ao atualizar agendamento:', await response.text()); `https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?id=eq.${id}`,
return false; requestOptions
} );
} catch (error) { if (response.ok) {
console.error('Erro de rede/servidor:', error); await fetchAppointments();
return false; return true;
} finally {
setShowSpinner(false);
}
}, [authHeader, fetchAppointments]);
const deleteConsulta = useCallback(async (id) => {
const success = await updateAppointmentStatus(id, { status: "cancelled", cancellation_reason: motivoCancelamento, updated_at: new Date().toISOString() });
if (success) {
setShowDeleteModal(false);
setMotivoCancelamento("");
setSelectedId('0');
} else { } else {
alert("Falha ao cancelar a consulta."); console.error(
"Erro ao atualizar agendamento:",
await response.text()
);
return false;
} }
}, [motivoCancelamento, updateAppointmentStatus]); } catch (error) {
console.error("Erro de rede/servidor:", error);
return false;
} finally {
setShowSpinner(false);
}
},
[authHeader, fetchAppointments]
);
const deleteConsulta = useCallback(
async (id) => {
const success = await updateAppointmentStatus(id, {
status: "cancelled",
cancellation_reason: motivoCancelamento,
updated_at: new Date().toISOString(),
});
if (success) {
setShowDeleteModal(false);
setMotivoCancelamento("");
setSelectedId("0");
} else {
alert("Falha ao cancelar a consulta.");
}
},
[motivoCancelamento, updateAppointmentStatus]
);
const confirmConsulta = useCallback(async (id) => { const confirmConsulta = useCallback(
const success = await updateAppointmentStatus(id, { status: "agendado", cancellation_reason: null, updated_at: new Date().toISOString() }); async (id) => {
if (success) { const success = await updateAppointmentStatus(id, {
setSelectedId('0'); status: "agendado",
} else { cancellation_reason: null,
alert("Falha ao reverter o cancelamento."); updated_at: new Date().toISOString(),
} });
}, [updateAppointmentStatus]); if (success) {
setSelectedId("0");
} else {
alert("Falha ao reverter o cancelamento.");
}
},
[updateAppointmentStatus]
);
useEffect(() => { useEffect(() => {
if(authHeader) { if(authHeader) {
@ -150,24 +174,24 @@ const Agendamento = () => {
return; return;
} }
setShowSpinner(true); setShowSpinner(true);
const appointmentsToShow = listaTodosAgendamentos; const appointmentsToShow = listaTodosAgendamentos;
const patientIdsToFetch = new Set(); const patientIdsToFetch = new Set();
const doctorIdsToFetch = new Set(); const doctorIdsToFetch = new Set();
appointmentsToShow.forEach(ag => { appointmentsToShow.forEach((ag) => {
if (ag.patient_id && !cachePacientes[ag.patient_id]) { if (ag.patient_id && !cachePacientes[ag.patient_id]) {
patientIdsToFetch.add(ag.patient_id); patientIdsToFetch.add(ag.patient_id);
} }
if (ag.doctor_id && !cacheMedicos[ag.doctor_id]) { if (ag.doctor_id && !cacheMedicos[ag.doctor_id]) {
doctorIdsToFetch.add(ag.doctor_id); doctorIdsToFetch.add(ag.doctor_id);
} }
}); });
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(',')})`;
@ -180,95 +204,106 @@ const Agendamento = () => {
fetchPromises.push(Promise.resolve(null)); fetchPromises.push(Promise.resolve(null));
} }
if (doctorIdsToFetch.size > 0) { if (doctorIdsToFetch.size > 0) {
const query = `id=in.(${Array.from(doctorIdsToFetch).join(',')})`; const query = `id=in.(${Array.from(doctorIdsToFetch).join(",")})`;
fetchPromises.push( fetchPromises.push(
fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors?${query}&select=id,full_name`, { fetch(
headers: { apikey: API_KEY, Authorization: authHeader } `https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors?${query}&select=id,full_name`,
}).then(res => res.json()) {
); headers: { apikey: API_KEY, Authorization: authHeader },
} else {
fetchPromises.push(Promise.resolve(null));
} }
).then((res) => res.json())
const [newPatients, newDoctors] = await Promise.all(fetchPromises); );
} else {
fetchPromises.push(Promise.resolve(null));
}
const updatedPatientCache = { ...cachePacientes }; const [newPatients, newDoctors] = await Promise.all(fetchPromises);
if (newPatients) newPatients.forEach(p => updatedPatientCache[p.id] = p);
const updatedDoctorCache = { ...cacheMedicos }; const updatedPatientCache = { ...cachePacientes };
if (newDoctors) newDoctors.forEach(d => updatedDoctorCache[d.id] = d); if (newPatients)
newPatients.forEach((p) => (updatedPatientCache[p.id] = p));
setCachePacientes(updatedPatientCache);
setCacheMedicos(updatedDoctorCache);
const newDict = {}; const updatedDoctorCache = { ...cacheMedicos };
const newFila = []; if (newDoctors) newDoctors.forEach((d) => (updatedDoctorCache[d.id] = d));
for (const agendamento of appointmentsToShow) { setCachePacientes(updatedPatientCache);
const medico = updatedDoctorCache[agendamento.doctor_id]; setCacheMedicos(updatedDoctorCache);
const paciente = updatedPatientCache[agendamento.patient_id];
if (!medico || !paciente) continue; const newDict = {};
const newFila = [];
const agendamentoMelhorado = { for (const agendamento of appointmentsToShow) {
...agendamento, const medico = updatedDoctorCache[agendamento.doctor_id];
medico_nome: medico.full_name || 'N/A', const paciente = updatedPatientCache[agendamento.patient_id];
paciente_nome: paciente.full_name || 'N/A',
paciente_cpf: paciente.cpf || 'N/A'
};
if (agendamento.status === "requested") { if (!medico || !paciente) continue;
newFila.push({ agendamento: agendamentoMelhorado, Infos: agendamentoMelhorado });
} else {
const DiaAgendamento = dayjs(agendamento.scheduled_at).format("YYYY-MM-DD");
if (!newDict[DiaAgendamento]) newDict[DiaAgendamento] = [];
newDict[DiaAgendamento].push(agendamentoMelhorado);
}
}
for (const key in newDict) { const agendamentoMelhorado = {
newDict[key].sort((a, b) => new Date(a.scheduled_at) - new Date(b.scheduled_at)); ...agendamento,
} medico_nome: medico.full_name || "N/A",
paciente_nome: paciente.full_name || "N/A",
setAgendamentosOrganizados(newDict); paciente_cpf: paciente.cpf || "N/A",
setFilaEsperaData(newFila);
setShowSpinner(false);
}; };
if (agendamento.status === "requested") {
newFila.push({
agendamento: agendamentoMelhorado,
Infos: agendamentoMelhorado,
});
} else {
const DiaAgendamento = dayjs(agendamento.scheduled_at).format(
"YYYY-MM-DD"
);
if (!newDict[DiaAgendamento]) newDict[DiaAgendamento] = [];
newDict[DiaAgendamento].push(agendamentoMelhorado);
}
}
for (const key in newDict) {
newDict[key].sort(
(a, b) => new Date(a.scheduled_at) - new Date(b.scheduled_at)
);
}
setAgendamentosOrganizados(newDict);
setFilaEsperaData(newFila);
setShowSpinner(false);
};
processData(); processData();
}, [listaTodosAgendamentos, authHeader]); }, [listaTodosAgendamentos, authHeader]);
const handleEditConsulta = (agendamento) => { const handleEditConsulta = (agendamento) => {
setAgendamentoParaEdicao(agendamento); setAgendamentoParaEdicao(agendamento);
setPageConsulta(true); setPageConsulta(true);
}; };
const handleSearchMedicos = (term) => { const handleSearchMedicos = (term) => {
setSearchTermDoctor(term); setSearchTermDoctor(term);
if (term.trim()) { if (term.trim()) {
const filtered = ListaDeMedicos.filter(medico => const filtered = ListaDeMedicos.filter((medico) =>
medico.nomeMedico.toLowerCase().includes(term.toLowerCase()) medico.nomeMedico.toLowerCase().includes(term.toLowerCase())
); );
setFiltredTodosMedicos(filtered); setFiltredTodosMedicos(filtered);
} else { } else {
setFiltredTodosMedicos([]); setFiltredTodosMedicos([]);
setMedicoFiltrado({ id: "vazio" }); setMedicoFiltrado({ id: "vazio" });
} }
}; };
const generateDateGrid = () => { const generateDateGrid = () => {
const grid = []; const grid = [];
const startOfMonth = currentDate.startOf('month'); const startOfMonth = currentDate.startOf("month");
let currentDay = startOfMonth.subtract(startOfMonth.day(), 'day'); let currentDay = startOfMonth.subtract(startOfMonth.day(), "day");
for (let i = 0; i < 42; i++) { for (let i = 0; i < 42; i++) {
grid.push(currentDay); grid.push(currentDay);
currentDay = currentDay.add(1, 'day'); currentDay = currentDay.add(1, "day");
} }
return grid; return grid;
}; };
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'];
@ -293,68 +328,86 @@ const DeleteModal = () => (
</div> </div>
</div> </div>
</div> </div>
); );
useEffect(() => {
setQuickJump({
month: currentDate.month(),
year: currentDate.year(),
});
}, [currentDate]);
useEffect(() => { const filaEsperaFiltrada = useMemo(() => {
setQuickJump({ if (!waitlistSearch.trim()) return filaEsperaData;
month: currentDate.month(), const term = waitlistSearch.toLowerCase();
year: currentDate.year() return filaEsperaData.filter(
}); (item) =>
}, [currentDate]); (item?.Infos?.paciente_nome?.toLowerCase() || "").includes(term) ||
(item?.Infos?.paciente_cpf?.toLowerCase() || "").includes(term) ||
const filaEsperaFiltrada = useMemo(() => { (item?.Infos?.medico_nome?.toLowerCase() || "").includes(term)
if (!waitlistSearch.trim()) return filaEsperaData; );
const term = waitlistSearch.toLowerCase(); }, [waitlistSearch, filaEsperaData]);
return filaEsperaData.filter(item =>
(item?.Infos?.paciente_nome?.toLowerCase() || '').includes(term) ||
(item?.Infos?.paciente_cpf?.toLowerCase() || '').includes(term) ||
(item?.Infos?.medico_nome?.toLowerCase() || '').includes(term)
);
}, [waitlistSearch, filaEsperaData]);
const applySortingWaitlist = useCallback((arr) => { const applySortingWaitlist = useCallback(
if (!Array.isArray(arr) || !waitSortKey) return arr; (arr) => {
const copy = [...arr]; if (!Array.isArray(arr) || !waitSortKey) return arr;
const key = waitSortKey; const copy = [...arr];
const dir = waitSortDir === 'asc' ? 1 : -1; const key = waitSortKey;
copy.sort((a, b) => { const dir = waitSortDir === "asc" ? 1 : -1;
const valA = key === 'data' ? new Date(a.agendamento.scheduled_at) : (a.Infos?.[`${key}_nome`] || ''); copy.sort((a, b) => {
const valB = key === 'data' ? new Date(b.agendamento.scheduled_at) : (b.Infos?.[`${key}_nome`] || ''); const valA =
if (valA < valB) return -1 * dir; key === "data"
if (valA > valB) return 1 * dir; ? new Date(a.agendamento.scheduled_at)
return 0; : a.Infos?.[`${key}_nome`] || "";
}); const valB =
return copy; key === "data"
}, [waitSortKey, waitSortDir]); ? new Date(b.agendamento.scheduled_at)
: b.Infos?.[`${key}_nome`] || "";
if (valA < valB) return -1 * dir;
if (valA > valB) return 1 * dir;
return 0;
});
return copy;
},
[waitSortKey, waitSortDir]
);
const filaEsperaOrdenada = useMemo(() => applySortingWaitlist(filaEsperaFiltrada), [filaEsperaFiltrada, applySortingWaitlist]); const filaEsperaOrdenada = useMemo(
const waitTotalPages = Math.ceil(filaEsperaOrdenada.length / waitPerPage) || 1; () => applySortingWaitlist(filaEsperaFiltrada),
const waitIndiceInicial = (waitPage - 1) * waitPerPage; [filaEsperaFiltrada, applySortingWaitlist]
const waitIndiceFinal = waitIndiceInicial + waitPerPage; );
const filaEsperaPaginada = filaEsperaOrdenada.slice(waitIndiceInicial, waitIndiceFinal); const waitTotalPages =
Math.ceil(filaEsperaOrdenada.length / waitPerPage) || 1;
const waitIndiceInicial = (waitPage - 1) * waitPerPage;
const waitIndiceFinal = waitIndiceInicial + waitPerPage;
const filaEsperaPaginada = filaEsperaOrdenada.slice(
waitIndiceInicial,
waitIndiceFinal
);
const gerarNumerosWaitPages = () => { const gerarNumerosWaitPages = () => {
const paginas = []; const paginas = [];
const paginasParaMostrar = 5; const paginasParaMostrar = 5;
let inicio = Math.max(1, waitPage - Math.floor(paginasParaMostrar / 2)); let inicio = Math.max(1, waitPage - Math.floor(paginasParaMostrar / 2));
let fim = Math.min(waitTotalPages, inicio + paginasParaMostrar - 1); let fim = Math.min(waitTotalPages, inicio + paginasParaMostrar - 1);
inicio = Math.max(1, fim - paginasParaMostrar + 1); inicio = Math.max(1, fim - paginasParaMostrar + 1);
for (let i = inicio; i <= fim; i++) paginas.push(i); for (let i = inicio; i <= fim; i++) paginas.push(i);
return paginas; return paginas;
}; };
useEffect(() => { setWaitPage(1); }, [waitlistSearch, waitSortKey, waitSortDir]); useEffect(() => {
setWaitPage(1);
}, [waitlistSearch, waitSortKey, waitSortDir]);
const handleQuickJumpChange = (type, value) => { const handleQuickJumpChange = (type, value) => {
setQuickJump(prev => ({ ...prev, [type]: Number(value) })); setQuickJump((prev) => ({ ...prev, [type]: Number(value) }));
}; };
const applyQuickJump = () => { const applyQuickJump = () => {
let newDate = dayjs().year(quickJump.year).month(quickJump.month).date(1); let newDate = dayjs().year(quickJump.year).month(quickJump.month).date(1);
setCurrentDate(newDate); setCurrentDate(newDate);
setSelectedDay(newDate); setSelectedDay(newDate);
}; };
return ( return (
<div> <div>

View File

@ -86,7 +86,7 @@ function mockFetchPagamentos() {
{ {
id: "PAY-001", id: "PAY-001",
paciente: { nome: "Sarah Oliveira", convenio: "Unimed" }, paciente: { nome: "Sarah Oliveira", convenio: "Unimed" },
valor: 20000, valor: 20000,
forma_pagamento: "Cartão", forma_pagamento: "Cartão",
data_vencimento: "2025-09-30", data_vencimento: "2025-09-30",
status: "pendente", status: "pendente",
@ -96,7 +96,7 @@ function mockFetchPagamentos() {
{ {
id: "PAY-002", id: "PAY-002",
paciente: { nome: "Laissa Marquetti", convenio: "Bradesco Saúde" }, paciente: { nome: "Laissa Marquetti", convenio: "Bradesco Saúde" },
valor: 15000, valor: 15000,
forma_pagamento: "Dinheiro", forma_pagamento: "Dinheiro",
data_vencimento: "2025-09-15", data_vencimento: "2025-09-15",
status: "pago", status: "pago",
@ -106,14 +106,14 @@ function mockFetchPagamentos() {
{ {
id: "PAY-003", id: "PAY-003",
paciente: { nome: "Vera Santos", convenio: "Particular" }, paciente: { nome: "Vera Santos", convenio: "Particular" },
valor: 30000, valor: 30000,
forma_pagamento: "Pix", forma_pagamento: "Pix",
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",
paciente: { nome: "Carlos Almeida", convenio: "Particular" }, paciente: { nome: "Carlos Almeida", convenio: "Particular" },
valor: 10000, valor: 10000,
@ -169,7 +169,7 @@ export default function FinanceiroDashboard() {
recebido += valorLiquido; recebido += valorLiquido;
descontos += p.desconto; descontos += p.desconto;
} else { } else {
aReceber += p.valor; aReceber += valorLiquido;
} }
}); });
@ -244,8 +244,7 @@ export default function FinanceiroDashboard() {
<option value="vencido">Vencido</option> <option value="vencido">Vencido</option>
</select> </select>
<button <button
className="action-btn" className="btn btn-primary"
style={{ background: "#3b82f6", color: "#fff", borderColor: "#3b82f6" }}
onClick={() => { onClick={() => {
setModalPagamento({ setModalPagamento({
paciente: { nome:"", convenio: CONVENIOS_LIST[0] }, paciente: { nome:"", convenio: CONVENIOS_LIST[0] },
@ -284,7 +283,7 @@ export default function FinanceiroDashboard() {
<tbody> <tbody>
{filteredPagamentos.map(p => ( {filteredPagamentos.map(p => (
<tr key={p.id}> <tr key={p.id}>
<td>{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>
@ -295,16 +294,16 @@ export default function FinanceiroDashboard() {
<td> <td>
<div className="action-group"> <div className="action-group">
<button <button
className="action-btn" className="btn-view"
onClick={() => { setModalPagamento({...p}); setNovoPagamento(false); }} onClick={() => { setModalPagamento({...p}); setNovoPagamento(false); }}
> >
Ver / Editar <i className="bi bi-eye me-1"></i> Ver / Editar
</button> </button>
<button <button
className="action-btn delete" className="btn-delete"
onClick={() => handleDelete(p.id)} onClick={() => handleDelete(p.id)}
> >
Excluir <i className="bi bi-trash me-1"></i> Excluir
</button> </button>
</div> </div>
</td> </td>
@ -416,15 +415,14 @@ export default function FinanceiroDashboard() {
</div> </div>
<div className="modal-footer"> <div className="modal-footer">
<button className="action-btn" onClick={() => handleSave(modalPagamento)}> <button className="btn-view" onClick={() => handleSave(modalPagamento)}>
Salvar <i className="bi bi-check-circle me-1"></i> Salvar
</button> </button>
<button <button
className="action-btn" className="btn-delete"
onClick={closeModal} onClick={closeModal}
style={{ borderColor: '#d1d5db', color: '#4b5563' }}
> >
Cancelar <i className="bi bi-x-circle me-1"></i> Cancelar
</button> </button>
</div> </div>
</div> </div>
@ -432,4 +430,4 @@ export default function FinanceiroDashboard() {
)} )}
</div> </div>
); );
} }

View File

@ -273,6 +273,7 @@
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin: 15px 0 20px; margin: 15px 0 20px;
color: #fff;
} }
.tabs-agenda-fila { .tabs-agenda-fila {

View File

@ -39,6 +39,7 @@
margin: 0 0 8px 0; margin: 0 0 8px 0;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
color: #fff;
opacity: 0.9; opacity: 0.9;
} }
@ -107,35 +108,79 @@
} }
/* Botões de ação */ /* Botões de ação */
.action-group {
display: flex; .btn-view {
gap: 8px; background-color: #E6F2FF !important;
align-items: center; color: #004085 !important;
border: 1px solid #B8D4F0 !important;
padding: 0.375rem 0.75rem;
font-size: 0.875rem;
border-radius: 6px;
cursor: pointer;
transition: all 0.15s ease-in-out;
text-decoration: none;
display: inline-block;
text-align: center;
} }
.action-btn { .btn-view:hover {
cursor: pointer; background-color: #D1E7FF !important;
padding: 6px 12px; border-color: #9EC5FE !important;
border-radius: 6px;
border: 1px solid #d7e6fb;
background: #fff;
transition: all 0.2s ease;
font-size: 13px;
} }
.action-btn:hover { .btn-edit {
background: #f6f9fc; background-color: #FFF3CD !important;
border-color: #93c5fd; color: #856404 !important;
border: 1px solid #FFEAA7 !important;
padding: 0.375rem 0.75rem;
font-size: 0.875rem;
border-radius: 6px;
cursor: pointer;
transition: all 0.15s ease-in-out;
text-decoration: none;
display: inline-block;
text-align: center;
} }
.action-btn.delete { .btn-edit:hover {
border-color: #fca5a5; background-color: #FFEEBA !important;
color: #b91c1c; border-color: #FFE087 !important;
} }
.action-btn.delete:hover { .btn-delete:hover {
background: #fee2e2; background-color: #F1B0B7 !important;
border-color: #ef4444; border-color: #ED969E !important;
}
.btn-delete {
background-color: #F8D7DA !important;
color: #721C24 !important;
border: 1px solid #F5C6CB !important;
padding: 0.375rem 0.75rem;
font-size: 0.875rem;
border-radius: 6px;
cursor: pointer;
transition: all 0.15s ease-in-out;
text-decoration: none;
display: inline-block;
text-align: center;
}
html[data-bs-theme="dark"] .btn-view {
background-color: #1e3a8a !important;
color: #e0e0e0 !important;
border-color: #374151 !important;
}
html[data-bs-theme="dark"] .btn-edit {
background-color: #78350f !important;
color: #fef3c7 !important;
border-color: #374151 !important;
}
html[data-bs-theme="dark"] .btn-delete {
background-color: #7f1d1d !important;
color: #fee2e2 !important;
border-color: #374151 !important;
} }
/* Badges de status */ /* Badges de status */
@ -182,8 +227,8 @@
padding: 24px; padding: 24px;
width: 100%; width: 100%;
max-width: 550px; max-width: 550px;
max-height: 90vh; max-height: 85vh;
overflow-y: auto; 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);
} }
@ -208,6 +253,12 @@
gap: 16px; gap: 16px;
} }
.modal-card .input-field,
.modal-card .select-field,
.modal-card textarea {
width: 100%;
}
.form-group { .form-group {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -241,12 +292,23 @@
gap: 10px; gap: 10px;
margin-top: 24px; margin-top: 24px;
} }
.input-field,
.select-field,
textarea {
padding: 10px 12px;
border: 1px solid #d1d5db;
border-radius: 8px;
box-sizing: border-box;
font-size: 14px;
transition: border-color 0.2s, box-shadow 0.2s;
background-color: #fff;
}
/* Inputs e selects */ /* Inputs e selects */
.input-field, .input-field,
.select-field, .select-field,
textarea { textarea {
width: 100%;
padding: 10px 12px; padding: 10px 12px;
border: 1px solid #d1d5db; border: 1px solid #d1d5db;
border-radius: 8px; border-radius: 8px;
@ -269,6 +331,18 @@ textarea {
min-height: 80px; min-height: 80px;
} }
.financeiro-wrap .input-field:not(.modal-card *),
.financeiro-wrap .select-field:not(.modal-card *),
.financeiro-wrap textarea:not(.modal-card *) {
width: 30%;
}
.modal-card .input-field,
.modal-card .select-field,
.modal-card textarea {
width: 100%;
}
/* Mensagem quando não há pagamentos */ /* Mensagem quando não há pagamentos */
.empty { .empty {
text-align: center; text-align: center;
@ -366,4 +440,4 @@ html[data-bs-theme="dark"] textarea:focus {
border-color: #3b82f6; border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.25); box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.25);
outline: none; outline: none;
} }

View File

@ -70,10 +70,10 @@
} }
/* Cores dos ícones */ /* Cores dos ícones */
.stat-icon-wrapper.blue { background-color: #5d5dff; } .stat-icon-wrapper.blue { background-color: #1D3B88; }
.stat-icon-wrapper.green { background-color: #30d158; } .stat-icon-wrapper.green { background-color: #399CE5; }
.stat-icon-wrapper.purple { background-color: #a272ff; } .stat-icon-wrapper.purple { background-color: #5F5DF2; }
.stat-icon-wrapper.orange { background-color: #f1952e; } .stat-icon-wrapper.orange { background-color: #051AFF; }
/* Seção de Ações Rápidas */ /* Seção de Ações Rápidas */
.quick-actions h2 { .quick-actions h2 {