merge com perfil de finanças
This commit is contained in:
commit
181a84c9ca
10
src/App.js
10
src/App.js
@ -6,20 +6,25 @@ import Register from "./pages/Register";
|
||||
import Forgot from "./pages/ForgotPassword";
|
||||
import PerfilSecretaria from "./perfis/perfil_secretaria/PerfilSecretaria";
|
||||
import LandingPage from './pages/LandingPage';
|
||||
import PerfilFinanceiro from "./perfis/perfil_financeiro/PerfilFinanceiro";
|
||||
// Mantenha todas as importações de CSS globais aqui se houver!
|
||||
|
||||
|
||||
function App() {
|
||||
// O estado controla qual view mostrar: false = Landing Page, true = Dashboard
|
||||
const [isInternalView, setIsInternalView] = useState(false);
|
||||
// const [isSecretaria, setIsSecretaria] = useState(false);
|
||||
|
||||
const handleEnterSystem = () => {
|
||||
setIsInternalView(true);
|
||||
};
|
||||
|
||||
|
||||
const handleExitSystem = () => {
|
||||
setIsInternalView(false);
|
||||
};
|
||||
|
||||
// if (isSecretaria) {
|
||||
// return <PerfilSecretaria onLogout={() => setIsSecretaria(false)} />;
|
||||
// }
|
||||
|
||||
// Se não estiver na visualização interna, retorna a LandingPage.
|
||||
if (!isInternalView) {
|
||||
@ -31,6 +36,7 @@ function App() {
|
||||
<Route path="/register" element={<Register />} />
|
||||
<Route path="/forgotPassword" element={<Forgot />} />
|
||||
<Route path="/secretaria/*" element={<PerfilSecretaria />} />
|
||||
<Route path="/financeiro/*" element={<PerfilFinanceiro />} />
|
||||
<Route path="*" element={<h2>Página não encontrada</h2>} />
|
||||
</Routes>
|
||||
</Router>
|
||||
|
||||
@ -1,123 +1,133 @@
|
||||
import React, { useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import menuItems from "../data/sidebar-items.json";
|
||||
|
||||
function Sidebar() {
|
||||
const [isActive, setIsActive] = useState(true);
|
||||
const [openSubmenu, setOpenSubmenu] = useState(null);
|
||||
// 1. Recebe 'menuItems' e 'onLogout' como props
|
||||
function Sidebar({ menuItems, onLogout }) {
|
||||
const [isActive, setIsActive] = useState(true);
|
||||
const [openSubmenu, setOpenSubmenu] = useState(null);
|
||||
|
||||
const toggleSidebar = () => {
|
||||
setIsActive(!isActive);
|
||||
};
|
||||
const toggleSidebar = () => {
|
||||
setIsActive(!isActive);
|
||||
};
|
||||
|
||||
const handleSubmenuClick = (submenuName) => {
|
||||
setOpenSubmenu(openSubmenu === submenuName ? null : submenuName);
|
||||
};
|
||||
const handleSubmenuClick = (submenuName) => {
|
||||
setOpenSubmenu(openSubmenu === submenuName ? null : submenuName);
|
||||
};
|
||||
|
||||
const renderLink = (item) => {
|
||||
// Links internos (rotas do React Router)
|
||||
if (item.url && item.url.startsWith("/")) {
|
||||
return (
|
||||
<Link to={item.url} className="sidebar-link">
|
||||
{item.icon && <i className={`bi bi-${item.icon}`}></i>}
|
||||
<span>{item.name}</span>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
// Links externos
|
||||
return (
|
||||
<a
|
||||
href={item.url}
|
||||
className="sidebar-link"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{item.icon && <i className={`bi bi-${item.icon}`}></i>}
|
||||
<span>{item.name}</span>
|
||||
</a>
|
||||
);
|
||||
};
|
||||
const renderLink = (item) => {
|
||||
// Links internos (rotas do React Router)
|
||||
if (item.url && item.url.startsWith("/")) {
|
||||
return (
|
||||
<Link to={item.url} className="sidebar-link">
|
||||
{item.icon && <i className={`bi bi-${item.icon}`}></i>}
|
||||
<span>{item.name}</span>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
// Links externos
|
||||
return (
|
||||
<div id="sidebar" className={isActive ? "active" : ""}>
|
||||
<div className="sidebar-wrapper active">
|
||||
<div className="sidebar-header">
|
||||
<div className="d-flex justify-content-between">
|
||||
<div className="logo">
|
||||
<Link to="/">
|
||||
<h1>MediConnect</h1>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="toggler">
|
||||
<button
|
||||
type="button"
|
||||
className="sidebar-hide d-xl-none d-block btn"
|
||||
onClick={toggleSidebar}
|
||||
>
|
||||
<i className="bi bi-x bi-middle"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="sidebar-menu">
|
||||
<ul className="menu">
|
||||
{menuItems.map((item, index) => {
|
||||
if (item.isTitle) {
|
||||
return (
|
||||
<li key={index} className="sidebar-title">
|
||||
{item.name}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
if (item.submenu) {
|
||||
return (
|
||||
<li
|
||||
key={index}
|
||||
className={`sidebar-item has-sub ${
|
||||
openSubmenu === item.key ? "active" : ""
|
||||
}`}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className="sidebar-link btn"
|
||||
onClick={() => handleSubmenuClick(item.key)}
|
||||
>
|
||||
<i className={`bi bi-${item.icon}`}></i>
|
||||
<span>{item.name}</span>
|
||||
</button>
|
||||
<ul
|
||||
className={`submenu ${
|
||||
openSubmenu === item.key ? "active" : ""
|
||||
}`}
|
||||
>
|
||||
{item.submenu.map((subItem, subIndex) => (
|
||||
<li key={subIndex} className="submenu-item">
|
||||
{renderLink(subItem)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<li key={index} className="sidebar-item">
|
||||
{renderLink(item)}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<button className="sidebar-toggler btn x" onClick={toggleSidebar}>
|
||||
<i data-feather="x"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<a
|
||||
href={item.url}
|
||||
className="sidebar-link"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{item.icon && <i className={`bi bi-${item.icon}`}></i>}
|
||||
<span>{item.name}</span>
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div id="sidebar" className={isActive ? "active" : ""}>
|
||||
<div className="sidebar-wrapper active">
|
||||
<div className="sidebar-header">
|
||||
{/* ... Header... */}
|
||||
<div className="d-flex justify-content-between">
|
||||
<div className="logo">
|
||||
<Link to="/">
|
||||
<h1>MediConnect</h1>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="toggler">
|
||||
<button
|
||||
type="button"
|
||||
className="sidebar-hide d-xl-none d-block btn"
|
||||
onClick={toggleSidebar}
|
||||
>
|
||||
<i className="bi bi-x bi-middle"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="sidebar-menu">
|
||||
<ul className="menu">
|
||||
{menuItems && menuItems.map((item, index) => {
|
||||
if (item.isTitle) {
|
||||
return (
|
||||
<li key={index} className="sidebar-title">
|
||||
{item.name}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
if (item.submenu) {
|
||||
return (
|
||||
<li
|
||||
key={index}
|
||||
className={`sidebar-item has-sub ${
|
||||
openSubmenu === item.key ? "active" : ""
|
||||
}`}
|
||||
>
|
||||
{/* ... Lógica de Submenu ... */}
|
||||
<button
|
||||
type="button"
|
||||
className="sidebar-link btn"
|
||||
onClick={() => handleSubmenuClick(item.key)}
|
||||
>
|
||||
<i className={`bi bi-${item.icon}`}></i>
|
||||
<span>{item.name}</span>
|
||||
</button>
|
||||
<ul
|
||||
className={`submenu ${
|
||||
openSubmenu === item.key ? "active" : ""
|
||||
}`}
|
||||
>
|
||||
{item.submenu.map((subItem, subIndex) => (
|
||||
<li key={subIndex} className="submenu-item">
|
||||
{renderLink(subItem)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<li key={index} className="sidebar-item">
|
||||
{renderLink(item)}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
{/* 3. Adiciona o botão de logout no final do menu */}
|
||||
<li className="sidebar-item" onClick={onLogout}>
|
||||
<button type="button" className="sidebar-link btn">
|
||||
<i className="bi bi-box-arrow-right"></i>
|
||||
<span>Sair (Logout)</span>
|
||||
</button>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<button className="sidebar-toggler btn x" onClick={toggleSidebar}>
|
||||
<i data-feather="x"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Sidebar;
|
||||
export default Sidebar;
|
||||
31
src/data/sidebar-items-financeiro.json
Normal file
31
src/data/sidebar-items-financeiro.json
Normal file
@ -0,0 +1,31 @@
|
||||
[
|
||||
{
|
||||
"name": "Menu-Financeiro",
|
||||
"isTitle": true
|
||||
},
|
||||
|
||||
{
|
||||
"name":"Início",
|
||||
"url": "/financeiro/inicio",
|
||||
"icon": "house"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Lista de Pacientes",
|
||||
"icon": "clipboard-heart-fill",
|
||||
"url": "/financeiro/pacientes"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Lista de Médico",
|
||||
"icon": "hospital-fill",
|
||||
"url": "/financeiro/medicos"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Controle Financeiro",
|
||||
"icon": "cash-coin",
|
||||
"url": "/financeiro/controlefinanceiro"
|
||||
}
|
||||
|
||||
]
|
||||
@ -32,5 +32,4 @@
|
||||
"icon": "table",
|
||||
"url": "/secretaria/laudo"
|
||||
}
|
||||
|
||||
]
|
||||
435
src/pages/FinanceiroDashboard.jsx
Normal file
435
src/pages/FinanceiroDashboard.jsx
Normal file
@ -0,0 +1,435 @@
|
||||
import React, { useState, useEffect, useMemo, useCallback } from "react";
|
||||
import './style/FinanceiroDashboard.css';
|
||||
|
||||
const CONVENIOS_LIST = [
|
||||
"Particular",
|
||||
"Amil",
|
||||
"Bradesco Saúde",
|
||||
"SulAmérica",
|
||||
"Unimed",
|
||||
"Cassio",
|
||||
"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;
|
||||
}
|
||||
|
||||
const integerPart = stringValue.slice(0, -2);
|
||||
const decimalPart = stringValue.slice(-2);
|
||||
|
||||
const formattedInteger = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, '.');
|
||||
|
||||
return `R$ ${formattedInteger},${decimalPart}`;
|
||||
}, [value]);
|
||||
|
||||
const handleKeyDown = useCallback((e) => {
|
||||
const key = e.key;
|
||||
|
||||
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);
|
||||
} else {
|
||||
const newNumericValue = parseInt(newValueString.slice(0, -1)) || 0;
|
||||
onChange(newNumericValue);
|
||||
}
|
||||
}
|
||||
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 newNumericValue = parseInt(newValueString);
|
||||
|
||||
onChange(newNumericValue);
|
||||
}, [value, onChange]);
|
||||
|
||||
return (
|
||||
<div className="form-group">
|
||||
<label htmlFor={id}>{label}</label>
|
||||
<input
|
||||
id={id}
|
||||
className="input-field currency-input"
|
||||
type="text"
|
||||
value={formattedValue}
|
||||
onChange={() => {}}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="R$ 0,00"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function mockFetchPagamentos() {
|
||||
return [
|
||||
{
|
||||
id: "PAY-001",
|
||||
paciente: { nome: "Sarah Oliveira", convenio: "Unimed" },
|
||||
valor: 20000,
|
||||
forma_pagamento: "Cartão",
|
||||
data_vencimento: "2025-09-30",
|
||||
status: "pendente",
|
||||
desconto: 0,
|
||||
observacoes: "Pagamento parcelado em 2x"
|
||||
},
|
||||
{
|
||||
id: "PAY-002",
|
||||
paciente: { nome: "Laissa Marquetti", convenio: "Bradesco Saúde" },
|
||||
valor: 15000,
|
||||
forma_pagamento: "Dinheiro",
|
||||
data_vencimento: "2025-09-15",
|
||||
status: "pago",
|
||||
desconto: 1000,
|
||||
observacoes: ""
|
||||
},
|
||||
{
|
||||
id: "PAY-003",
|
||||
paciente: { nome: "Vera Santos", convenio: "Particular" },
|
||||
valor: 30000,
|
||||
forma_pagamento: "Pix",
|
||||
data_vencimento: "2025-09-20",
|
||||
status: "vencido",
|
||||
desconto: 0,
|
||||
observacoes: "Não respondeu ao contato"
|
||||
},
|
||||
{
|
||||
id: "PAY-004",
|
||||
paciente: { nome: "Carlos Almeida", convenio: "Particular" },
|
||||
valor: 10000,
|
||||
forma_pagamento: "Transferência",
|
||||
data_vencimento: "2025-09-29",
|
||||
status: "pago",
|
||||
desconto: 500,
|
||||
observacoes: "Desconto por pagamento adiantado"
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
export default function FinanceiroDashboard() {
|
||||
const [pagamentos, setPagamentos] = useState([]);
|
||||
const [modalPagamento, setModalPagamento] = useState(null);
|
||||
const [query, setQuery] = useState("");
|
||||
const [filtroStatus, setFiltroStatus] = useState("Todos");
|
||||
const [novoPagamento, setNovoPagamento] = useState(false);
|
||||
const [summary, setSummary] = useState({ totalRecebido: 0, totalAReceber: 0, totalDescontos: 0 });
|
||||
|
||||
useEffect(() => {
|
||||
const data = mockFetchPagamentos();
|
||||
setPagamentos(data);
|
||||
}, []);
|
||||
|
||||
function formatCurrency(centavos) {
|
||||
const valorEmReais = centavos / 100;
|
||||
return "R$ " + valorEmReais.toFixed(2).replace(".", ",").replace(/\B(?=(\d{3})+(?!\d))/g, '.');
|
||||
}
|
||||
|
||||
function getValorLiquido(valor, desconto) {
|
||||
return valor - desconto;
|
||||
}
|
||||
|
||||
const filteredPagamentos = useMemo(() => {
|
||||
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);
|
||||
return statusOk && buscaOk;
|
||||
});
|
||||
}, [pagamentos, query, filtroStatus]);
|
||||
|
||||
useEffect(() => {
|
||||
let recebido = 0;
|
||||
let aReceber = 0;
|
||||
let descontos = 0;
|
||||
|
||||
filteredPagamentos.forEach(p => {
|
||||
const valorLiquido = getValorLiquido(p.valor, p.desconto);
|
||||
if (p.status === 'pago') {
|
||||
recebido += valorLiquido;
|
||||
descontos += p.desconto;
|
||||
} else {
|
||||
aReceber += p.valor;
|
||||
}
|
||||
});
|
||||
|
||||
setSummary({
|
||||
totalRecebido: recebido,
|
||||
totalAReceber: aReceber,
|
||||
totalDescontos: descontos
|
||||
});
|
||||
}, [filteredPagamentos]);
|
||||
|
||||
function handleDelete(id) {
|
||||
if (window.confirm("Tem certeza que deseja excluir este pagamento?")) {
|
||||
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 (novoPagamento) {
|
||||
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));
|
||||
}
|
||||
setModalPagamento(null);
|
||||
setNovoPagamento(false);
|
||||
}
|
||||
|
||||
const closeModal = () => {
|
||||
setModalPagamento(null);
|
||||
setNovoPagamento(false);
|
||||
};
|
||||
|
||||
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>
|
||||
</div>
|
||||
<div className="summary-card red">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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)}
|
||||
style={{ flexGrow: 1 }}
|
||||
/>
|
||||
<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
|
||||
className="action-btn"
|
||||
style={{ background: "#3b82f6", color: "#fff", borderColor: "#3b82f6" }}
|
||||
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);
|
||||
}}
|
||||
>
|
||||
+ Adicionar Pagamento
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{filteredPagamentos.length === 0 ? (
|
||||
<div className="empty">Nenhum pagamento encontrado.</div>
|
||||
) : (
|
||||
<div className="table-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Paciente</th>
|
||||
<th>Convênio</th>
|
||||
<th>Valor Total (R$)</th>
|
||||
<th>Desconto (R$)</th>
|
||||
<th>Valor Líquido (R$)</th>
|
||||
<th>Forma</th>
|
||||
<th>Vencimento</th>
|
||||
<th>Status</th>
|
||||
<th>Ações</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filteredPagamentos.map(p => (
|
||||
<tr key={p.id}>
|
||||
<td>{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>{p.forma_pagamento}</td>
|
||||
<td>{p.data_vencimento.split('-').reverse().join('/')}</td>
|
||||
<td><span className={`badge ${p.status}`}>{p.status.toUpperCase()}</span></td>
|
||||
<td>
|
||||
<div className="action-group">
|
||||
<button
|
||||
className="action-btn"
|
||||
onClick={() => { setModalPagamento({...p}); setNovoPagamento(false); }}
|
||||
>
|
||||
Ver / Editar
|
||||
</button>
|
||||
<button
|
||||
className="action-btn delete"
|
||||
onClick={() => handleDelete(p.id)}
|
||||
>
|
||||
Excluir
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{modalPagamento && (
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div className="modal-body">
|
||||
<div className="form-group">
|
||||
<label htmlFor="paciente_nome">Paciente</label>
|
||||
<input
|
||||
id="paciente_nome"
|
||||
className="input-field"
|
||||
value={modalPagamento.paciente.nome}
|
||||
onChange={e => setModalPagamento({...modalPagamento, paciente:{...modalPagamento.paciente, nome:e.target.value}})}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<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}})}
|
||||
>
|
||||
<option value="">Selecione</option>
|
||||
{CONVENIOS_LIST.map(conv => (
|
||||
<option key={conv} value={conv}>{conv}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<CurrencyInput
|
||||
id="valor"
|
||||
label="Valor da consulta (R$)"
|
||||
value={modalPagamento.valor}
|
||||
onChange={newValue => setModalPagamento({...modalPagamento, valor: newValue})}
|
||||
/>
|
||||
|
||||
<CurrencyInput
|
||||
id="desconto"
|
||||
label="Desconto aplicado (R$)"
|
||||
value={modalPagamento.desconto}
|
||||
onChange={newValue => setModalPagamento({...modalPagamento, desconto: newValue})}
|
||||
/>
|
||||
|
||||
<div className="form-group">
|
||||
<label htmlFor="forma">Forma de pagamento</label>
|
||||
<select
|
||||
id="forma"
|
||||
className="select-field"
|
||||
value={modalPagamento.forma_pagamento}
|
||||
onChange={e => setModalPagamento({...modalPagamento, forma_pagamento:e.target.value})}
|
||||
>
|
||||
<option>Dinheiro</option>
|
||||
<option>Cartão</option>
|
||||
<option>Pix</option>
|
||||
<option>Transferência</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label htmlFor="vencimento">Data de vencimento</label>
|
||||
<input
|
||||
id="vencimento"
|
||||
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
|
||||
id="status"
|
||||
className="select-field"
|
||||
value={modalPagamento.status}
|
||||
onChange={e => setModalPagamento({...modalPagamento, status:e.target.value})}
|
||||
>
|
||||
<option value="pago">Pago</option>
|
||||
<option value="pendente">Pendente</option>
|
||||
<option value="vencido">Vencido</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label htmlFor="observacoes">Observações financeiras</label>
|
||||
<textarea
|
||||
id="observacoes"
|
||||
className="input-field"
|
||||
rows={3}
|
||||
value={modalPagamento.observacoes}
|
||||
onChange={e => setModalPagamento({...modalPagamento, observacoes:e.target.value})}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="modal-footer">
|
||||
<button className="action-btn" onClick={() => handleSave(modalPagamento)}>
|
||||
Salvar
|
||||
</button>
|
||||
<button
|
||||
className="action-btn"
|
||||
onClick={closeModal}
|
||||
style={{ borderColor: '#d1d5db', color: '#4b5563' }}
|
||||
>
|
||||
Cancelar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,7 +1,8 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
|
||||
function Login() {
|
||||
|
||||
function Login({ onEnterSystem }) {
|
||||
const navigate = useNavigate();
|
||||
const [form, setForm] = useState({
|
||||
username: "",
|
||||
@ -16,9 +17,12 @@ function Login() {
|
||||
|
||||
const handleLogin = (e) => {
|
||||
e.preventDefault();
|
||||
console.log("Tentando logar com:", form);
|
||||
if (form.username && form.password) {
|
||||
// ...login logic...
|
||||
navigate('/secretaria/inicio');
|
||||
navigate('/financeiro/inicio');
|
||||
|
||||
|
||||
} else {
|
||||
setAlert("Preencha todos os campos!");
|
||||
}
|
||||
@ -45,7 +49,7 @@ function Login() {
|
||||
{alert}
|
||||
</div>
|
||||
)}
|
||||
<form>
|
||||
<form onSubmit={handleLogin}>
|
||||
<div className="form-group position-relative has-icon-left mb-4">
|
||||
<input
|
||||
type="text"
|
||||
@ -97,8 +101,7 @@ function Login() {
|
||||
Manter-me conectado
|
||||
</label>
|
||||
</div>
|
||||
<button className="btn btn-primary btn-block btn-lg shadow-lg mt-5"
|
||||
onClick={handleLogin}>
|
||||
<button type="submit" className="btn btn-primary btn-block btn-lg shadow-lg mt-5" >
|
||||
Entrar
|
||||
</button>
|
||||
</form>
|
||||
|
||||
278
src/pages/style/FinanceiroDashboard.css
Normal file
278
src/pages/style/FinanceiroDashboard.css
Normal file
@ -0,0 +1,278 @@
|
||||
/* GERAL */
|
||||
.financeiro-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
padding: 18px;
|
||||
font-family: Inter, Roboto, Arial, sans-serif;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.financeiro-wrap h2 {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* Container Cards de Resumo (Fluxo de Caixa) */
|
||||
.summary-card-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
color: #fff;
|
||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.summary-card:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.summary-card h3 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.summary-card .value {
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Cores dos Cards de Resumo */
|
||||
.summary-card.green {
|
||||
background: linear-gradient(45deg, #10b981, #059669); /* Recebido */
|
||||
}
|
||||
.summary-card.red {
|
||||
background: linear-gradient(45deg, #f97316, #ea580c); /* A Receber */
|
||||
}
|
||||
.summary-card.blue {
|
||||
background: linear-gradient(45deg, #3b82f6, #2563eb); /* Descontos */
|
||||
}
|
||||
|
||||
/* Responsiidade básica para o resumo */
|
||||
@media (max-width: 768px) {
|
||||
.summary-card-container {
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
.summary-card {
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* CARD PRINCIPAL (LISTA) */
|
||||
.list-page-card {
|
||||
background-color: #ffffff;
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
margin-top: 16px;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
/* Tabela */
|
||||
.table-container {
|
||||
overflow-x: auto;
|
||||
border-radius: 8px;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.table-container table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.table-container th,
|
||||
.table-container td {
|
||||
padding: 14px 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #eef3f8;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.table-container th {
|
||||
background: #f1f5f9;
|
||||
font-weight: 600;
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
/* Botões de ação */
|
||||
.action-group {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
cursor: pointer;
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #d7e6fb;
|
||||
background: #fff;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
background: #f6f9fc;
|
||||
border-color: #93c5fd;
|
||||
}
|
||||
|
||||
.action-btn.delete {
|
||||
border-color: #fca5a5;
|
||||
color: #b91c1c;
|
||||
}
|
||||
|
||||
.action-btn.delete:hover {
|
||||
background: #fee2e2;
|
||||
border-color: #ef4444;
|
||||
}
|
||||
|
||||
/* Badges de status */
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 4px 10px;
|
||||
border-radius: 9999px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.badge.pago {
|
||||
background: #d1fae5;
|
||||
color: #065f46;
|
||||
}
|
||||
|
||||
.badge.pendente {
|
||||
background: #fef3c7;
|
||||
color: #a16207;
|
||||
}
|
||||
|
||||
.badge.vencido {
|
||||
background: #fee2e2;
|
||||
color: #991b1b;
|
||||
}
|
||||
|
||||
/* Modal */
|
||||
.modal {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 24px 12px;
|
||||
background: rgba(0, 0, 0, 0.55);
|
||||
z-index: 12000;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.modal-card {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
width: 100%;
|
||||
max-width: 550px;
|
||||
max-height: 90vh;
|
||||
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);
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.modal-header h2 {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: #1f2937;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: #6c757d;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.close-btn:hover {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
/* Inputs e selects */
|
||||
.input-field,
|
||||
.select-field,
|
||||
textarea {
|
||||
width: 100%;
|
||||
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;
|
||||
}
|
||||
|
||||
.input-field:focus,
|
||||
.select-field:focus,
|
||||
textarea:focus {
|
||||
border-color: #3b82f6;
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
/* Mensagem quando não há pagamentos */
|
||||
.empty {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: #7d97b4;
|
||||
font-size: 16px;
|
||||
}
|
||||
31
src/perfis/perfil_financeiro/PerfilFinanceiro.jsx
Normal file
31
src/perfis/perfil_financeiro/PerfilFinanceiro.jsx
Normal file
@ -0,0 +1,31 @@
|
||||
import { Routes, Route } from "react-router-dom";
|
||||
import Sidebar from "../../components/Sidebar";
|
||||
import FinanceiroItems from "../../data/sidebar-items-financeiro.json";
|
||||
import Inicio from "../../pages/Inicio";
|
||||
import TablePaciente from "../../pages/TablePaciente";
|
||||
import FinanceiroDashboard from "../../pages/FinanceiroDashboard";
|
||||
import DoctorTable from "../../pages/DoctorTable";
|
||||
import Details from "../../pages/Details";
|
||||
import DoctorDetails from "../../pages/DoctorDetails";
|
||||
|
||||
function PerfilFinanceiro({ onLogout }) {
|
||||
return (
|
||||
<div id="app" className="active">
|
||||
<Sidebar onLogout={onLogout} menuItems={FinanceiroItems} />
|
||||
<div id="main">
|
||||
<Routes>
|
||||
<Route path="/" element={<FinanceiroDashboard/>}/>
|
||||
<Route path="inicio" element={<Inicio />} />
|
||||
<Route path="controlefinanceiro" element={<FinanceiroDashboard/>}/>
|
||||
<Route path="pacientes" element={<TablePaciente />} />
|
||||
<Route path="medicos" element={<DoctorTable />} />
|
||||
<Route path="pacientes/:id" element={<Details />} />
|
||||
<Route path="medicos/:id" element={<DoctorDetails />} />
|
||||
<Route path="*" element={<h2>Página não encontrada</h2>} />
|
||||
</Routes>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PerfilFinanceiro;
|
||||
@ -2,6 +2,8 @@
|
||||
import { Routes, Route } from "react-router-dom";
|
||||
|
||||
import Sidebar from "../../components/Sidebar";
|
||||
import FinanceiroDashboard from "../../pages/FinanceiroDashboard";
|
||||
import SecretariaItems from "../../data/sidebar-items.json";
|
||||
import Inicio from "../../pages/Inicio";
|
||||
import TablePaciente from "../../pages/TablePaciente";
|
||||
import PatientCadastroManager from "../../pages/PatientCadastroManager";
|
||||
@ -14,24 +16,26 @@ import EditPage from "../../pages/EditPage";
|
||||
import DoctorDetails from "../../pages/DoctorDetails";
|
||||
import DoctorEditPage from "../../pages/DoctorEditPage";
|
||||
|
||||
function PerfilSecretaria() {
|
||||
function PerfilSecretaria({ onLogout }) {
|
||||
return (
|
||||
// <Router>
|
||||
<div id="app" className="active">
|
||||
<Sidebar />
|
||||
<Sidebar onLogout={onLogout} menuItems={SecretariaItems} />
|
||||
<div id="main">
|
||||
<Routes>
|
||||
<Route path="/inicio" element={<Inicio />} />
|
||||
<Route path="/pacientes/cadastro" element={<PatientCadastroManager />} />
|
||||
<Route path="/medicos/cadastro" element={<DoctorCadastroManager />} />
|
||||
<Route path="/pacientes" element={<TablePaciente />} />
|
||||
<Route path="/medicos" element={<DoctorTable />} />
|
||||
<Route path="/pacientes/:id" element={<Details />} />
|
||||
<Route path="/pacientes/:id/edit" element={<EditPage />} />
|
||||
<Route path="/medicos/:id" element={<DoctorDetails />} />
|
||||
<Route path="/medicos/:id/edit" element={<DoctorEditPage />} />
|
||||
<Route path="/agendamento" element={<Agendamento />} />
|
||||
<Route path="/laudo" element={<LaudoManager />} />
|
||||
<Route path="/" element={<Inicio/>}/>
|
||||
<Route path="inicio" element={<Inicio />} />
|
||||
<Route path="pacientes/cadastro" element={<PatientCadastroManager />} />
|
||||
<Route path="medicos/cadastro" element={<DoctorCadastroManager />} />
|
||||
<Route path="pacientes" element={<TablePaciente />} />
|
||||
<Route path="medicos" element={<DoctorTable />} />
|
||||
<Route path="pacientes/:id" element={<Details />} />
|
||||
<Route path="pacientes/:id/edit" element={<EditPage />} />
|
||||
<Route path="medicos/:id" element={<DoctorDetails />} />
|
||||
<Route path="medicos/:id/edit" element={<DoctorEditPage />} />
|
||||
<Route path="agendamento" element={<Agendamento />} />
|
||||
<Route path="laudo" element={<LaudoManager />} />
|
||||
|
||||
<Route path="*" element={<h2>Página não encontrada</h2>} />
|
||||
</Routes>
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user