diff --git a/src/App.js b/src/App.js index 98be1df..c74aabd 100644 --- a/src/App.js +++ b/src/App.js @@ -1,26 +1,34 @@ // src/App.js //import PerfilSecretaria from "./perfis/perfil_secretaria/PerfilSecretaria"; -import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; +import { BrowserRouter as Router, Routes, Route, Navigate } from "react-router-dom"; import { useState } from "react"; import Login from "./pages/Login"; import Register from "./pages/Register"; import Forgot from "./pages/ForgotPassword"; import PerfilSecretaria from "./perfis/perfil_secretaria/PerfilSecretaria"; +import PerfilFinanceiro from "./perfis/perfil_financeiro/PerfilFinanceiro"; import LandingPage from './pages/LandingPage'; // 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 [userProfile, setUserProfile] = useState( + localStorage.getItem('userProfile') || null + ); - const handleEnterSystem = () => { - setIsInternalView(true); + const handleEnterSystem = (profile, keepConnected) => { + setUserProfile(profile); + + if (keepConnected) { + localStorage.setItem('userProfile', profile); + } else { + + localStorage.removeItem('userProfile'); + } }; - const handleExitSystem = () => { - setIsInternalView(false); + setUserProfile(null); + localStorage.removeItem('userProfile'); }; // if (isSecretaria) { @@ -28,26 +36,45 @@ function App() { // } // Se não estiver na visualização interna, retorna a LandingPage. - if (!isInternalView) { - return ( + + return ( - } /> - } /> - } /> + } /> + } /> + } /> } /> - } /> - Página não encontrada} /> + + ) : ( + + ) + } + /> + + ) : ( + + ) + } + /> + : + // Se não logado, volta para a Landing Page + } + /> - ) + ); } - - // Se estiver na visualização interna, retorna o PerfilSecretaria - return ( - // Passamos a função de saída (logout) - - ); -} export default App; \ No newline at end of file diff --git a/src/components/Sidebar.jsx b/src/components/Sidebar.jsx index 0820c1c..43b5c5b 100644 --- a/src/components/Sidebar.jsx +++ b/src/components/Sidebar.jsx @@ -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 ( - - {item.icon && } - {item.name} - - ); - } - - // Links externos - return ( - - {item.icon && } - {item.name} - - ); - }; + const renderLink = (item) => { + // Links internos (rotas do React Router) + if (item.url && item.url.startsWith("/")) { + return ( + + {item.icon && } + {item.name} + + ); + } + // Links externos return ( - + + {item.icon && } + {item.name} + ); + }; + + return ( + + ); } -export default Sidebar; +export default Sidebar; \ No newline at end of file diff --git a/src/data/sidebar-items-financeiro.json b/src/data/sidebar-items-financeiro.json new file mode 100644 index 0000000..8183087 --- /dev/null +++ b/src/data/sidebar-items-financeiro.json @@ -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" + } + +] \ No newline at end of file diff --git a/src/data/sidebar-items.json b/src/data/sidebar-items.json index f6e1519..8c87d01 100644 --- a/src/data/sidebar-items.json +++ b/src/data/sidebar-items.json @@ -43,6 +43,12 @@ "name": "Laudo do Paciente", "icon": "table", "url": "/secretaria/laudo" + }, + + { + "name": "Controle Financeiro", + "icon": "cash-coin", + "url": "/secretaria/controlefinanceiro" } ] \ No newline at end of file diff --git a/src/pages/FinanceiroDashboard.jsx b/src/pages/FinanceiroDashboard.jsx new file mode 100644 index 0000000..b3768ce --- /dev/null +++ b/src/pages/FinanceiroDashboard.jsx @@ -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 ( +
+ + {}} + onKeyDown={handleKeyDown} + placeholder="R$ 0,00" + /> +
+ ); +} + +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 ( +
+

Controle Financeiro

+ +
+
+

Total Recebido (Filtrado)

+

{formatCurrency(summary.totalRecebido)}

+
+
+

Total a Receber (Filtrado)

+

{formatCurrency(summary.totalAReceber)}

+
+
+

Descontos Aplicados

+

{formatCurrency(summary.totalDescontos)}

+
+
+ +
+
+ setQuery(e.target.value)} + style={{ flexGrow: 1 }} + /> + + +
+ + {filteredPagamentos.length === 0 ? ( +
Nenhum pagamento encontrado.
+ ) : ( +
+ + + + + + + + + + + + + + + + {filteredPagamentos.map(p => ( + + + + + + + + + + + + ))} + +
PacienteConvênioValor Total (R$)Desconto (R$)Valor Líquido (R$)FormaVencimentoStatusAções
{p.paciente.nome}{p.paciente.convenio}{formatCurrency(p.valor)}{formatCurrency(p.desconto)}{formatCurrency(getValorLiquido(p.valor, p.desconto))}{p.forma_pagamento}{p.data_vencimento.split('-').reverse().join('/')}{p.status.toUpperCase()} +
+ + +
+
+
+ )} +
+ + {modalPagamento && ( +
e.target.classList.contains('modal') && closeModal()}> +
+
+

{novoPagamento ? "Adicionar Pagamento" : `Editar Pagamento - ${modalPagamento.paciente.nome}`}

+ +
+ +
+
+ + setModalPagamento({...modalPagamento, paciente:{...modalPagamento.paciente, nome:e.target.value}})} + /> +
+ +
+ + +
+ setModalPagamento({...modalPagamento, valor: newValue})} + /> + + setModalPagamento({...modalPagamento, desconto: newValue})} + /> + +
+ + +
+ +
+ + setModalPagamento({...modalPagamento, data_vencimento:e.target.value})} + /> +
+ +
+ + +
+ +
+ +