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

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,5 +1,5 @@
import React, { useState, useEffect, useMemo, useCallback } from "react";
import './style/FinanceiroDashboard.css';
import "./style/FinanceiroDashboard.css";
const CONVENIOS_LIST = [
"Particular",
@ -8,72 +8,77 @@ const CONVENIOS_LIST = [
"SulAmérica",
"Unimed",
"Cassio",
"Outro"
"Outro",
];
function CurrencyInput({ value, onChange, label, id }) {
const formattedValue = useMemo(() => {
let numericValue = Number(value) || 0;
let stringValue = String(numericValue);
while (stringValue.length < 3) {
stringValue = '0' + stringValue;
stringValue = "0" + stringValue;
}
const integerPart = stringValue.slice(0, -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}`;
}, [value]);
const handleKeyDown = useCallback((e) => {
const key = e.key;
const handleKeyDown = useCallback(
(e) => {
const key = e.key;
if (['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab'].includes(key)) {
if (key === 'Backspace' || key === 'Delete') {
if (
["Backspace", "Delete", "ArrowLeft", "ArrowRight", "Tab"].includes(key)
) {
if (key === "Backspace" || key === "Delete") {
e.preventDefault();
const numericValue = value || 0;
let newValueString = String(numericValue);
if (newValueString.length <= 1) {
onChange(0);
onChange(0);
} else {
const newNumericValue = parseInt(newValueString.slice(0, -1)) || 0;
onChange(newNumericValue);
const newNumericValue = parseInt(newValueString.slice(0, -1)) || 0;
onChange(newNumericValue);
}
}
return;
}
if (!/^\d$/.test(key)) {
e.preventDefault();
return;
}
return;
}
if (!/^\d$/.test(key)) {
e.preventDefault();
return;
}
e.preventDefault();
const digit = key;
const numericValue = value || 0;
let newValueString = String(numericValue) + digit;
if (newValueString.length > 10) return;
const digit = key;
const numericValue = value || 0;
const newNumericValue = parseInt(newValueString);
let newValueString = String(numericValue) + digit;
onChange(newNumericValue);
}, [value, onChange]);
if (newValueString.length > 10) return;
const newNumericValue = parseInt(newValueString);
onChange(newNumericValue);
},
[value, onChange]
);
return (
<div className="form-group">
<label htmlFor={id}>{label}</label>
<input
<input
id={id}
className="input-field currency-input"
type="text"
value={formattedValue}
onChange={() => {}}
className="input-field currency-input"
type="text"
value={formattedValue}
onChange={() => {}}
onKeyDown={handleKeyDown}
placeholder="R$ 0,00"
/>
@ -91,27 +96,27 @@ function mockFetchPagamentos() {
data_vencimento: "2025-09-30",
status: "pendente",
desconto: 0,
observacoes: "Pagamento parcelado em 2x"
observacoes: "Pagamento parcelado em 2x",
},
{
id: "PAY-002",
paciente: { nome: "Laissa Marquetti", convenio: "Bradesco Saúde" },
valor: 15000,
valor: 15000,
forma_pagamento: "Dinheiro",
data_vencimento: "2025-09-15",
status: "pago",
desconto: 1000,
observacoes: ""
desconto: 1000,
observacoes: "",
},
{
id: "PAY-003",
paciente: { nome: "Vera Santos", convenio: "Particular" },
valor: 30000,
valor: 30000,
forma_pagamento: "Pix",
data_vencimento: "2025-09-20",
status: "vencido",
status: "vencido",
desconto: 0,
observacoes: "Não respondeu ao contato"
observacoes: "Não respondeu ao contato",
},
{
id: "PAY-004",
@ -120,9 +125,9 @@ function mockFetchPagamentos() {
forma_pagamento: "Transferência",
data_vencimento: "2025-09-29",
status: "pago",
desconto: 500,
observacoes: "Desconto por pagamento adiantado"
}
desconto: 500,
observacoes: "Desconto por pagamento adiantado",
},
];
}
@ -132,7 +137,11 @@ export default function FinanceiroDashboard() {
const [query, setQuery] = useState("");
const [filtroStatus, setFiltroStatus] = useState("Todos");
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(() => {
const data = mockFetchPagamentos();
@ -141,7 +150,13 @@ export default function FinanceiroDashboard() {
function formatCurrency(centavos) {
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) {
@ -149,11 +164,12 @@ export default function FinanceiroDashboard() {
}
const filteredPagamentos = useMemo(() => {
return pagamentos.filter(p => {
return pagamentos.filter((p) => {
const q = query.toLowerCase();
const statusOk = filtroStatus === "Todos" || p.status === filtroStatus;
const buscaOk = p.paciente.nome.toLowerCase().includes(q) ||
p.id.toLowerCase().includes(q);
const buscaOk =
p.paciente.nome.toLowerCase().includes(q) ||
p.id.toLowerCase().includes(q);
return statusOk && buscaOk;
});
}, [pagamentos, query, filtroStatus]);
@ -163,47 +179,55 @@ export default function FinanceiroDashboard() {
let aReceber = 0;
let descontos = 0;
filteredPagamentos.forEach(p => {
filteredPagamentos.forEach((p) => {
const valorLiquido = getValorLiquido(p.valor, p.desconto);
if (p.status === 'pago') {
if (p.status === "pago") {
recebido += valorLiquido;
descontos += p.desconto;
} else {
aReceber += valorLiquido;
aReceber += valorLiquido;
}
});
setSummary({
totalRecebido: recebido,
totalAReceber: aReceber,
totalDescontos: descontos
totalDescontos: descontos,
});
}, [filteredPagamentos]);
function handleDelete(id) {
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);
}
}
function handleSave(pagamento) {
if (!pagamento.paciente.nome || !pagamento.valor || !pagamento.data_vencimento || !pagamento.paciente.convenio) {
alert("Preencha Paciente, Convênio, Valor e Data de Vencimento.");
return;
if (
!pagamento.paciente.nome ||
!pagamento.valor ||
!pagamento.data_vencimento ||
!pagamento.paciente.convenio
) {
alert("Preencha Paciente, Convênio, Valor e Data de Vencimento.");
return;
}
if (novoPagamento) {
const newId = "PAY-" + (pagamentos.length + 1).toString().padStart(3, "0");
pagamento.id = newId;
setPagamentos(prev => [...prev, pagamento]);
const newId =
"PAY-" + (pagamentos.length + 1).toString().padStart(3, "0");
pagamento.id = newId;
setPagamentos((prev) => [...prev, pagamento]);
} 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);
setNovoPagamento(false);
}
const closeModal = () => {
setModalPagamento(null);
setNovoPagamento(false);
@ -212,50 +236,54 @@ export default function FinanceiroDashboard() {
return (
<div className="financeiro-wrap">
<h2>Controle Financeiro</h2>
<div className="summary-card-container">
<div className="summary-card green">
<h3>Total Recebido (Filtrado)</h3>
<p className="value">{formatCurrency(summary.totalRecebido)}</p>
<h3>Total Recebido (Filtrado)</h3>
<p className="value">{formatCurrency(summary.totalRecebido)}</p>
</div>
<div className="summary-card red">
<h3>Total a Receber (Filtrado)</h3>
<p className="value">{formatCurrency(summary.totalAReceber)}</p>
<h3>Total a Receber (Filtrado)</h3>
<p className="value">{formatCurrency(summary.totalAReceber)}</p>
</div>
<div className="summary-card blue">
<h3>Descontos Aplicados</h3>
<p className="value">{formatCurrency(summary.totalDescontos)}</p>
<h3>Descontos Aplicados</h3>
<p className="value">{formatCurrency(summary.totalDescontos)}</p>
</div>
</div>
<div className="list-page-card">
<div style={{ display:"flex", gap:12, marginBottom:20 }}>
<input
<div className="list-page-card">
<div style={{ display: "flex", gap: 12, marginBottom: 20 }}>
<input
className="input-field"
placeholder="Buscar paciente"
value={query}
onChange={e => setQuery(e.target.value)}
placeholder="Buscar paciente"
value={query}
onChange={(e) => setQuery(e.target.value)}
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="pago">Pago</option>
<option value="pendente">Pendente</option>
<option value="vencido">Vencido</option>
</select>
<button
<button
className="btn btn-primary"
onClick={() => {
onClick={() => {
setModalPagamento({
paciente: { nome:"", convenio: CONVENIOS_LIST[0] },
valor:0,
forma_pagamento:"Dinheiro",
data_vencimento: new Date().toISOString().split('T')[0],
status:"pendente",
desconto:0,
observacoes:""
});
setNovoPagamento(true);
paciente: { nome: "", convenio: CONVENIOS_LIST[0] },
valor: 0,
forma_pagamento: "Dinheiro",
data_vencimento: new Date().toISOString().split("T")[0],
status: "pendente",
desconto: 0,
observacoes: "",
});
setNovoPagamento(true);
}}
>
+ Adicionar Pagamento
@ -268,12 +296,12 @@ export default function FinanceiroDashboard() {
<div className="table-container">
<table>
<thead>
<tr>
<tr>
<th>Paciente</th>
<th>Convênio</th>
<th>Valor Total (R$)</th>
<th>Desconto (R$)</th>
<th>Valor Líquido (R$)</th>
<th>Valor Líquido (R$)</th>
<th>Forma</th>
<th>Vencimento</th>
<th>Status</th>
@ -281,26 +309,35 @@ export default function FinanceiroDashboard() {
</tr>
</thead>
<tbody>
{filteredPagamentos.map(p => (
{filteredPagamentos.map((p) => (
<tr key={p.id}>
<td style={{ fontWeight: 600 }}>{p.paciente.nome}</td>
<td>{p.paciente.convenio}</td>
<td>{formatCurrency(p.valor)}</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.data_vencimento.split('-').reverse().join('/')}</td>
<td><span className={`badge ${p.status}`}>{p.status.toUpperCase()}</span></td>
<td>{p.data_vencimento.split("-").reverse().join("/")}</td>
<td>
<div className="action-group">
<button
className="btn-view"
onClick={() => { setModalPagamento({...p}); setNovoPagamento(false); }}
<span className={`badge ${p.status}`}>
{p.status.toUpperCase()}
</span>
</td>
<td>
<div className="action-group">
<button
className="btn-view"
onClick={() => {
setModalPagamento({ ...p });
setNovoPagamento(false);
}}
>
<i className="bi bi-eye me-1"></i> Ver / Editar
</button>
<button
className="btn-delete"
<button
className="btn-delete"
onClick={() => handleDelete(p.id)}
>
<i className="bi bi-trash me-1"></i> Excluir
@ -316,21 +353,38 @@ export default function FinanceiroDashboard() {
</div>
{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-header">
<h2>{novoPagamento ? "Adicionar Pagamento" : `Editar Pagamento - ${modalPagamento.paciente.nome}`}</h2>
<button className="close-btn" onClick={closeModal}>×</button>
<h2>
{novoPagamento
? "Adicionar Pagamento"
: `Editar Pagamento - ${modalPagamento.paciente.nome}`}
</h2>
<button className="close-btn" onClick={closeModal}>
×
</button>
</div>
<div className="modal-body">
<div className="form-group">
<label htmlFor="paciente_nome">Paciente</label>
<input
<input
id="paciente_nome"
className="input-field"
value={modalPagamento.paciente.nome}
onChange={e => setModalPagamento({...modalPagamento, paciente:{...modalPagamento.paciente, nome:e.target.value}})}
className="input-field"
value={modalPagamento.paciente.nome}
onChange={(e) =>
setModalPagamento({
...modalPagamento,
paciente: {
...modalPagamento.paciente,
nome: e.target.value,
},
})
}
/>
</div>
@ -338,13 +392,23 @@ export default function FinanceiroDashboard() {
<label htmlFor="convenio">Convênio</label>
<select
id="convenio"
className="select-field"
value={modalPagamento.paciente.convenio}
onChange={e => setModalPagamento({...modalPagamento, paciente:{...modalPagamento.paciente, convenio:e.target.value}})}
className="select-field"
value={modalPagamento.paciente.convenio}
onChange={(e) =>
setModalPagamento({
...modalPagamento,
paciente: {
...modalPagamento.paciente,
convenio: e.target.value,
},
})
}
>
<option value="">Selecione</option>
{CONVENIOS_LIST.map(conv => (
<option key={conv} value={conv}>{conv}</option>
{CONVENIOS_LIST.map((conv) => (
<option key={conv} value={conv}>
{conv}
</option>
))}
</select>
</div>
@ -352,23 +416,32 @@ export default function FinanceiroDashboard() {
id="valor"
label="Valor da consulta (R$)"
value={modalPagamento.valor}
onChange={newValue => setModalPagamento({...modalPagamento, valor: newValue})}
onChange={(newValue) =>
setModalPagamento({ ...modalPagamento, valor: newValue })
}
/>
<CurrencyInput
id="desconto"
label="Desconto aplicado (R$)"
value={modalPagamento.desconto}
onChange={newValue => setModalPagamento({...modalPagamento, desconto: newValue})}
onChange={(newValue) =>
setModalPagamento({ ...modalPagamento, desconto: newValue })
}
/>
<div className="form-group">
<label htmlFor="forma">Forma de pagamento</label>
<select
<select
id="forma"
className="select-field"
value={modalPagamento.forma_pagamento}
onChange={e => setModalPagamento({...modalPagamento, forma_pagamento:e.target.value})}
className="select-field"
value={modalPagamento.forma_pagamento}
onChange={(e) =>
setModalPagamento({
...modalPagamento,
forma_pagamento: e.target.value,
})
}
>
<option>Dinheiro</option>
<option>Cartão</option>
@ -379,22 +452,32 @@ export default function FinanceiroDashboard() {
<div className="form-group">
<label htmlFor="vencimento">Data de vencimento</label>
<input
<input
id="vencimento"
className="input-field"
type="date"
value={modalPagamento.data_vencimento}
onChange={e => setModalPagamento({...modalPagamento, data_vencimento:e.target.value})}
className="input-field"
type="date"
value={modalPagamento.data_vencimento}
onChange={(e) =>
setModalPagamento({
...modalPagamento,
data_vencimento: e.target.value,
})
}
/>
</div>
<div className="form-group">
<label htmlFor="status">Status do pagamento</label>
<select
<select
id="status"
className="select-field"
value={modalPagamento.status}
onChange={e => setModalPagamento({...modalPagamento, status:e.target.value})}
className="select-field"
value={modalPagamento.status}
onChange={(e) =>
setModalPagamento({
...modalPagamento,
status: e.target.value,
})
}
>
<option value="pago">Pago</option>
<option value="pendente">Pendente</option>
@ -404,30 +487,34 @@ export default function FinanceiroDashboard() {
<div className="form-group">
<label htmlFor="observacoes">Observações financeiras</label>
<textarea
<textarea
id="observacoes"
className="input-field"
rows={3}
value={modalPagamento.observacoes}
onChange={e => setModalPagamento({...modalPagamento, observacoes:e.target.value})}
className="input-field"
rows={3}
value={modalPagamento.observacoes}
onChange={(e) =>
setModalPagamento({
...modalPagamento,
observacoes: e.target.value,
})
}
/>
</div>
</div>
<div className="modal-footer">
<button className="btn-view" onClick={() => handleSave(modalPagamento)}>
<i className="bi bi-check-circle me-1"></i> Salvar
</button>
<button
className="btn-delete"
onClick={closeModal}
>
<i className="bi bi-x-circle me-1"></i> Cancelar
</button>
<div className="modal-footer">
<button
className="btn-view"
onClick={() => handleSave(modalPagamento)}
>
<i className="bi bi-check-circle me-1"></i> Salvar
</button>
<button className="btn-delete" onClick={closeModal}>
<i className="bi bi-x-circle me-1"></i> Cancelar
</button>
</div>
</div>
</div>
</div>
)}
</div>
);
}
}

View File

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

View File

@ -25,7 +25,7 @@
/* Estatísticas - Paciente */
.stats-paciente-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 1.5rem;
margin-bottom: 2.5rem;
}
@ -81,10 +81,10 @@
}
/* Cores dos ícones - Paciente */
.stat-paciente-icon-wrapper.blue { background-color: #5d5dff; }
.stat-paciente-icon-wrapper.green { background-color: #30d158; }
.stat-paciente-icon-wrapper.blue { background-color: #051AFF; }
.stat-paciente-icon-wrapper.green { background-color: #5F5DF2; }
.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 */
.quick-actions-paciente h2 {
@ -96,7 +96,7 @@
.actions-paciente-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1.5rem;
margin-bottom: 2.5rem;
}
@ -180,7 +180,7 @@
display: flex;
align-items: center;
gap: 2rem;
flex-wrap: wrap;
flex-wrap: wrap;
}
.consulta-paciente-time-date {
@ -432,14 +432,6 @@ html[data-bs-theme="dark"] .view-all-paciente-button:hover {
padding: 1rem;
}
.stats-paciente-grid {
grid-template-columns: 1fr;
}
.actions-paciente-grid {
grid-template-columns: 1fr;
}
.consulta-paciente-info {
flex-direction: column;
align-items: flex-start;
@ -451,4 +443,4 @@ html[data-bs-theme="dark"] .view-all-paciente-button:hover {
flex-direction: row;
justify-content: space-around;
}
}
}