Compare commits
6 Commits
5c48c1ab3e
...
e6234093de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6234093de | ||
|
|
f3ce9e2bb0 | ||
|
|
181a84c9ca | ||
| d1a8eab04c | |||
|
|
0673a4e319 | ||
|
|
0b1d04b7e6 |
15
src/App.js
15
src/App.js
@ -6,20 +6,26 @@ import Register from "./pages/Register";
|
||||
import Forgot from "./pages/ForgotPassword";
|
||||
import PerfilSecretaria from "./perfis/perfil_secretaria/PerfilSecretaria";
|
||||
import LandingPage from './pages/LandingPage';
|
||||
// Mantenha todas as importações de CSS globais aqui se houver!
|
||||
|
||||
import PerfilFinanceiro from "./perfis/perfil_financeiro/PerfilFinanceiro";
|
||||
import Perfiladm from "./perfis/Perfil_adm/Perfiladm";
|
||||
import PerfilMedico from "./perfis/Perfil_medico/PerfilMedico";
|
||||
|
||||
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 +37,9 @@ function App() {
|
||||
<Route path="/register" element={<Register />} />
|
||||
<Route path="/forgotPassword" element={<Forgot />} />
|
||||
<Route path="/secretaria/*" element={<PerfilSecretaria />} />
|
||||
<Route path="/financeiro/*" element={<PerfilFinanceiro />} />
|
||||
<Route path="/medico/*" element={<PerfilMedico />} />
|
||||
<Route path="/admin/*" element={<Perfiladm />} />
|
||||
<Route path="*" element={<h2>Página não encontrada</h2>} />
|
||||
</Routes>
|
||||
</Router>
|
||||
|
||||
255
src/PagesAdm/gestao.css
Normal file
255
src/PagesAdm/gestao.css
Normal file
@ -0,0 +1,255 @@
|
||||
|
||||
.dashboard-container {
|
||||
padding: 2rem;
|
||||
font-family: 'Arial', sans-serif;
|
||||
background-color: #f5f6fa;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
|
||||
.dashboard-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.dashboard-title {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.dashboard-subtitle {
|
||||
margin: 0.2rem 0 0 0;
|
||||
color: #000;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.new-user-btn {
|
||||
background-color: #1e3a8a;
|
||||
color: white;
|
||||
padding: 0.6rem 1.2rem;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s, transform 0.25s ease, box-shadow 0.25s ease;
|
||||
}
|
||||
|
||||
.new-user-btn:hover {
|
||||
background-color: #162d6b;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0px 4px 12px rgba(30, 58, 138, 0.3);
|
||||
}
|
||||
|
||||
|
||||
.filters-container {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
margin-bottom: 2rem;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.filters-container:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.filters-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 0.3rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.filters-subtitle {
|
||||
font-size: 0.9rem;
|
||||
color: #666;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.filters-content {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filters-input {
|
||||
flex: 1;
|
||||
padding: 0.6rem 1rem;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 8px;
|
||||
font-size: 0.95rem;
|
||||
color: #333;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.filters-input:focus {
|
||||
border-color: #1e3a8a;
|
||||
box-shadow: 0px 0px 0px 3px rgba(30, 58, 138, 0.2);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.filters-select {
|
||||
padding: 0.6rem 1rem;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 8px;
|
||||
font-size: 0.95rem;
|
||||
background: #fff;
|
||||
color: #333;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.filters-select:focus {
|
||||
border-color: #1e3a8a;
|
||||
box-shadow: 0px 0px 0px 3px rgba(30, 58, 138, 0.2);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
|
||||
.cards-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: white;
|
||||
padding: 1.5rem;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid transparent;
|
||||
transition: transform 0.25s ease, box-shadow 0.25s ease, border 0.25s ease, background 0.25s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-6px);
|
||||
box-shadow: 0 8px 20px rgba(30, 58, 138, 0.2);
|
||||
background: #f8faff;
|
||||
border: 1px solid #1e3a8a33;
|
||||
}
|
||||
|
||||
.card-label {
|
||||
font-size: 0.9rem;
|
||||
color: #999;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.card-value {
|
||||
font-size: 1.8rem;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.card-extra {
|
||||
font-size: 0.85rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.card-extra.positive {
|
||||
color: #1e3a8a;
|
||||
}
|
||||
|
||||
|
||||
.user-table-container {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||
margin-top: 2rem;
|
||||
transition: transform 0.25s ease, box-shadow 0.25s ease;
|
||||
}
|
||||
|
||||
.user-table-container:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 6px 14px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.user-table-container h2 {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 0.3rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.user-table-container p {
|
||||
font-size: 0.9rem;
|
||||
color: #666;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.user-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.user-table th,
|
||||
.user-table td {
|
||||
padding: 12px 15px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.user-table th {
|
||||
background-color: #f3f4f6;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.user-table tr {
|
||||
transition: background-color 0.25s ease;
|
||||
}
|
||||
|
||||
.user-table tr:hover {
|
||||
background-color: #f0f4ff;
|
||||
}
|
||||
|
||||
.profile-badge {
|
||||
background-color: #1e3a8a;
|
||||
color: #f7f7f7;
|
||||
padding: 3px 8px;
|
||||
border-radius: 8px;
|
||||
font-size: 0.85rem;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 3px 8px;
|
||||
border-radius: 8px;
|
||||
font-size: 0.85rem;
|
||||
color: #fff;
|
||||
display: inline-block;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.status-badge.ativo {
|
||||
background-color: #28a745;
|
||||
}
|
||||
|
||||
.status-badge.inativo {
|
||||
background-color: #dc3545;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.action-icon {
|
||||
cursor: pointer;
|
||||
color: #555;
|
||||
transition: color 0.2s, transform 0.2s;
|
||||
}
|
||||
|
||||
.action-icon:hover {
|
||||
color: #1e3a8a;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
142
src/PagesAdm/gestao.jsx
Normal file
142
src/PagesAdm/gestao.jsx
Normal file
@ -0,0 +1,142 @@
|
||||
|
||||
import React from "react";
|
||||
import "./gestao.css";
|
||||
import { FaEdit, FaTrash } from "react-icons/fa";
|
||||
|
||||
|
||||
function UserDashboard() {
|
||||
return (
|
||||
|
||||
<div className="dashboard-container">
|
||||
|
||||
<div className="dashboard-header">
|
||||
<div>
|
||||
<h1 className="dashboard-title">Gestão de Usuários</h1>
|
||||
<p className="dashboard-subtitle">
|
||||
Gerencie usuários, perfis e permissões do sistema
|
||||
</p>
|
||||
</div>
|
||||
<button className="new-user-btn">+ Novo Usuário</button>
|
||||
</div>
|
||||
|
||||
<div className="cards-container">
|
||||
<div className="card">
|
||||
<p className="card-label">Total de Usuários</p>
|
||||
<p className="card-value">15</p>
|
||||
<p className="card-extra positive">+3 este mês</p>
|
||||
</div>
|
||||
<div className="card">
|
||||
<p className="card-label">Usuários Ativos</p>
|
||||
<p className="card-value">12</p>
|
||||
<p className="card-extra">80.0% do total</p>
|
||||
</div>
|
||||
<div className="card">
|
||||
<p className="card-label">Tempo Médio Sessão</p>
|
||||
<p className="card-value">2h 30min</p>
|
||||
<p className="card-extra">Última semana</p>
|
||||
</div>
|
||||
<div className="card">
|
||||
<p className="card-label">Usuários Hoje</p>
|
||||
<p className="card-value">10</p>
|
||||
<p className="card-extra positive">+2 desde ontem</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="filters-container">
|
||||
<p className="filters-title">Filtros</p>
|
||||
<p className="filters-subtitle">
|
||||
Use os filtros abaixo para encontrar usuários específicos
|
||||
</p>
|
||||
<div className="filters-content">
|
||||
<input
|
||||
type="text"
|
||||
className="filters-input"
|
||||
placeholder="Buscar por nome ou email..."
|
||||
/>
|
||||
<select className="filters-select">
|
||||
<option>Todos os perfis</option>
|
||||
<option>Médico</option>
|
||||
<option>Secretaria</option>
|
||||
<option>Gestão</option>
|
||||
</select>
|
||||
<select className="filters-select">
|
||||
<option>Todos</option>
|
||||
<option>Ativos</option>
|
||||
<option>Inativos</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="user-table-container">
|
||||
<h2>Usuários do Sistema</h2>
|
||||
<p>Lista completa de usuários e suas permissões</p>
|
||||
<table className="user-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nome</th>
|
||||
<th>Email</th>
|
||||
<th>Perfil</th>
|
||||
<th>Departamento</th>
|
||||
<th>Status</th>
|
||||
<th>Último Acesso</th>
|
||||
<th>Ações</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Ana Silva</td>
|
||||
<td>ana.silva@mediconnect.com</td>
|
||||
<td><span className="profile-badge">Gestão / Coordenação</span></td>
|
||||
<td>Administração</td>
|
||||
<td><span className="status-badge ativo">Ativo</span></td>
|
||||
<td>20/12/2024, 08:30</td>
|
||||
<td className="actions">
|
||||
<span className="action-icon"></span>
|
||||
<span className="action-icon"></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Dr. Carlos Santos</td>
|
||||
<td>carlos.santos@mediconnect.com</td>
|
||||
<td><span className="profile-badge">Médico</span></td>
|
||||
<td>Cardiologia</td>
|
||||
<td><span className="status-badge ativo">Ativo</span></td>
|
||||
<td>19/12/2024, 14:20</td>
|
||||
<td className="actions">
|
||||
<span className="action-icon"></span>
|
||||
<span className="action-icon"></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Maria Oliveira</td>
|
||||
<td>maria.oliveira@mediconnect.com</td>
|
||||
<td><span className="profile-badge">Secretária</span></td>
|
||||
<td>Recepção</td>
|
||||
<td><span className="status-badge ativo">Ativo</span></td>
|
||||
<td>20/12/2024, 07:45</td>
|
||||
<td className="actions">
|
||||
<span className="action-icon"></span>
|
||||
<span className="action-icon"></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Dr. João Pereira</td>
|
||||
<td>joao.pereira@mediconnect.com</td>
|
||||
<td><span className="profile-badge">Médico</span></td>
|
||||
<td>Ortopedia</td>
|
||||
<td><span className="status-badge inativo">Inativo</span></td>
|
||||
<td>15/12/2024, 16:30</td>
|
||||
<td className="actions">
|
||||
<span className="action-icon"></span>
|
||||
<span className="person-badge-fill"></span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default UserDashboard;
|
||||
214
src/PagesAdm/painel.css
Normal file
214
src/PagesAdm/painel.css
Normal file
@ -0,0 +1,214 @@
|
||||
.painel-container {
|
||||
padding: 2rem;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.painel-titulo {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.painel-subtitulo {
|
||||
color: #666;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.painel-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.painel-card {
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
padding: 1rem;
|
||||
box-shadow: 0px 2px 6px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.25s ease, box-shadow 0.25s ease, background 0.25s ease;
|
||||
cursor: pointer;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
|
||||
.painel-card:hover {
|
||||
transform: translateY(-6px);
|
||||
box-shadow: 0px 8px 20px rgba(0, 102, 255, 0.2);
|
||||
background: #f8faff;
|
||||
border: 1px solid #0066ff33;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 14px;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.badge {
|
||||
background: #0066ff;
|
||||
color: white;
|
||||
padding: 2px 6px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.card-numero {
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
margin: 0.3rem 0;
|
||||
}
|
||||
|
||||
.card-info {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
background: #eee;
|
||||
border-radius: 8px;
|
||||
height: 8px;
|
||||
margin-top: 0.5rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
background: #0066ff;
|
||||
height: 100%;
|
||||
width: 80%;
|
||||
border-radius: 8px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
|
||||
.painel-card:hover .progress-fill {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.painel-graficos {
|
||||
margin-top: 2rem;
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.grafico-card {
|
||||
flex: 1;
|
||||
min-width: 300px;
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
padding: 1rem;
|
||||
box-shadow: 0px 2px 6px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.25s ease, box-shadow 0.25s ease;
|
||||
}
|
||||
|
||||
|
||||
.grafico-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0px 6px 16px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.grafico-titulo {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 0.2rem;
|
||||
}
|
||||
|
||||
.grafico-subtitulo {
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
|
||||
.performance-container {
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0px 2px 6px rgba(0, 0, 0, 0.1);
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.performance-titulo {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 0.2rem;
|
||||
}
|
||||
|
||||
.performance-subtitulo {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.departamento {
|
||||
margin-bottom: 1.2rem;
|
||||
}
|
||||
|
||||
.departamento-label {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 14px;
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
.departamento-barra {
|
||||
background: #f0f2f5;
|
||||
border-radius: 6px;
|
||||
height: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.departamento-fill {
|
||||
background: #001a40;
|
||||
height: 100%;
|
||||
width: 0%;
|
||||
border-radius: 6px;
|
||||
transition: width 0.4s ease;
|
||||
}
|
||||
|
||||
|
||||
.departamento-fill.ativo {
|
||||
width: var(--percent, 0%);
|
||||
}
|
||||
.department-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.department-name {
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.progress-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
flex: 1;
|
||||
background: #eee;
|
||||
border-radius: 8px;
|
||||
height: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
background: #0066ff;
|
||||
height: 100%;
|
||||
border-radius: 8px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.department-info {
|
||||
font-size: 13px;
|
||||
color: #333;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
193
src/PagesAdm/painel.jsx
Normal file
193
src/PagesAdm/painel.jsx
Normal file
@ -0,0 +1,193 @@
|
||||
import React from "react";
|
||||
import {
|
||||
PieChart,
|
||||
Pie,
|
||||
Cell,
|
||||
Tooltip,
|
||||
BarChart,
|
||||
Bar,
|
||||
XAxis,
|
||||
YAxis,
|
||||
CartesianGrid,
|
||||
ResponsiveContainer
|
||||
} from "recharts";
|
||||
import "./painel.css";
|
||||
|
||||
export default function PainelAdministrativo() {
|
||||
|
||||
const profileData = [
|
||||
{ name: "Médico", value: 8 },
|
||||
{ name: "Secretária", value: 5 },
|
||||
{ name: "Gestão / Coordena", value: 3 },
|
||||
];
|
||||
const COLORS = ["#1e3a8a", "#051AFF", "#0066ff"];
|
||||
|
||||
|
||||
const activityData = [
|
||||
{ name: "Hoje", value: 10 },
|
||||
{ name: "Esta Semana", value: 14 },
|
||||
{ name: "Este Mês", value: 15 },
|
||||
];
|
||||
|
||||
|
||||
const departamentos = [
|
||||
{ nome: "Cardiologia", ativos: 3, total: 3 },
|
||||
{ nome: "Ortopedia", ativos: 1, total: 2 },
|
||||
{ nome: "Neurologia", ativos: 2, total: 2 },
|
||||
{ nome: "Administração", ativos: 2, total: 2 },
|
||||
{ nome: "Recepção", ativos: 5, total: 6 },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="painel-container">
|
||||
<h1 className="painel-titulo">Painel Administrativo</h1>
|
||||
<p className="painel-subtitulo">
|
||||
Visão geral completa do sistema MediConnect
|
||||
</p>
|
||||
|
||||
|
||||
<div className="painel-cards">
|
||||
|
||||
<div className="painel-card">
|
||||
<div className="card-header">
|
||||
<span>Total de Usuários</span>
|
||||
<span className="badge">15</span>
|
||||
</div>
|
||||
<h2 className="card-numero">12</h2>
|
||||
<p className="card-info">3 inativos</p>
|
||||
<div className="progress-bar">
|
||||
<div className="progress-fill"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="painel-card">
|
||||
<div className="card-header">
|
||||
<span>Novos Usuários</span>
|
||||
<span className="badge">+3</span>
|
||||
</div>
|
||||
<h2 className="card-numero">Este Mês</h2>
|
||||
<p className="card-info">+20% comparado ao mês anterior</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="painel-card">
|
||||
<div className="card-header">
|
||||
<span>Tempo Médio de Sessão</span>
|
||||
</div>
|
||||
<h2 className="card-numero">2h 30min</h2>
|
||||
<p className="card-info">+5% comparado à semana anterior</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="painel-card">
|
||||
<div className="card-header">
|
||||
<span>Taxa de Atividade</span>
|
||||
</div>
|
||||
<h2 className="card-numero">80.0%</h2>
|
||||
<p className="card-info">Usuários ativos vs total</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="painel-graficos">
|
||||
<div className="grafico-card">
|
||||
<h3 className="grafico-titulo">Distribuição por Perfil</h3>
|
||||
<p className="grafico-subtitulo">
|
||||
Quantidade de usuários por tipo de perfil
|
||||
</p>
|
||||
<ResponsiveContainer width="100%" height={250}>
|
||||
<PieChart>
|
||||
<Pie
|
||||
data={profileData}
|
||||
dataKey="value"
|
||||
outerRadius={80}
|
||||
label={({ name, value }) => `${name}: ${value}`}
|
||||
>
|
||||
{profileData.map((entry, index) => (
|
||||
<Cell key={index} fill={COLORS[index]} />
|
||||
))}
|
||||
</Pie>
|
||||
<Tooltip />
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="grafico-card">
|
||||
<h3 className="grafico-titulo">Atividade de Usuários</h3>
|
||||
<p className="grafico-subtitulo">Usuários ativos por período</p>
|
||||
<ResponsiveContainer width="100%" height={250}>
|
||||
<BarChart data={activityData}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="name" />
|
||||
<YAxis />
|
||||
<Tooltip />
|
||||
<Bar dataKey="value" fill="#0066ff" />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="grafico-card">
|
||||
<h3 className="grafico-titulo">Performance por Departamento</h3>
|
||||
<p className="grafico-subtitulo">
|
||||
Atividade e engajamento dos usuários por departamento
|
||||
</p>
|
||||
{departamentos.map((dep, index) => {
|
||||
const percentual = Math.round((dep.ativos / dep.total) * 100);
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className="department-row"
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
marginBottom: "14px",
|
||||
}}
|
||||
>
|
||||
|
||||
<span
|
||||
className="department-name"
|
||||
style={{ flex: 1, fontWeight: "600", color: "#000" }}
|
||||
>
|
||||
{dep.nome}
|
||||
</span>
|
||||
|
||||
|
||||
<div
|
||||
className="progress-bar"
|
||||
style={{
|
||||
flex: 3,
|
||||
height: "13px",
|
||||
backgroundColor: "#eee",
|
||||
borderRadius: "10px",
|
||||
margin: "0 12px",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="progress-fill"
|
||||
style={{
|
||||
width: `${percentual}%`,
|
||||
height: "100%",
|
||||
backgroundColor: "#0066ff",
|
||||
borderRadius: "10px",
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
|
||||
|
||||
<span
|
||||
className="department-info"
|
||||
style={{ minWidth: "120px", fontWeight: "500", color: "#333" }}
|
||||
>
|
||||
{dep.ativos}/{dep.total} ({percentual}%)
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
209
src/PagesMedico/Agendamento.jsx
Normal file
209
src/PagesMedico/Agendamento.jsx
Normal file
@ -0,0 +1,209 @@
|
||||
import React, { useState, useMemo } from "react";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
// Importe os componentes que você está usando
|
||||
import TabelaAgendamentoDia from "../components/AgendarConsulta/TabelaAgendamentoDia";
|
||||
import TabelaAgendamentoSemana from "../components/AgendarConsulta/TabelaAgendamentoSemana";
|
||||
import TabelaAgendamentoMes from "../components/AgendarConsulta/TabelaAgendamentoMes";
|
||||
import FormNovaConsulta from "../components/AgendarConsulta/FormNovaConsulta";
|
||||
|
||||
// Importe os estilos
|
||||
import "./styleMedico/Agendamento.css";
|
||||
import "./styleMedico/FilaEspera.css";
|
||||
|
||||
// --- DADOS E FUNÇÕES FORA DO COMPONENTE ---
|
||||
|
||||
const filaEsperaData = [
|
||||
{ nome: 'Ricardo Pereira', email: 'ricardo.pereira@gmail.com', cpf: '444.777.666-55', telefone: '(79) 99123-4567', entrada: '25/09/2025 às 08:00' },
|
||||
{ nome: 'Ana Costa', email: 'ana.costa@gmail.com', cpf: '321.654.987-00', telefone: '(79) 97777-3333', entrada: '25/09/2025 às 08:30' },
|
||||
{ nome: 'Lucas Martins', email: 'lucas.martins@gmail.com', cpf: '777.666.555-33', telefone: '(79) 99654-3210', entrada: '25/09/2025 às 09:00' },
|
||||
{ nome: 'João Souza', email: 'joao.souza@gmail.com', cpf: '987.654.321-00', telefone: '(79) 98888-2222', entrada: '25/09/2025 às 14:00' },
|
||||
{ nome: 'Maria Silva', email: 'maria.silva@gmail.com', cpf: '123.456.789-00', telefone: '(79) 99999-1111', entrada: '25/09/2025 às 14:30' },
|
||||
{ nome: 'Fernanda Lima', email: 'fernanda.lima@gmail.com', cpf: '888.999.000-22', telefone: '(79) 98877-6655', entrada: '26/09/2025 às 09:30' },
|
||||
{ nome: 'Carlos Andrade', email: 'carlos.andrade@gmail.com', cpf: '222.555.888-11', telefone: '(79) 99876-5432', entrada: '26/09/2025 às 10:00' },
|
||||
{ nome: 'Juliana Oliveira', email: 'juliana.o@gmail.com', cpf: '111.222.333-44', telefone: '(79) 98765-1234', entrada: '26/09/2025 às 11:30' },
|
||||
];
|
||||
|
||||
const ListarDiasdoMes = (ano, mes) => {
|
||||
const diasDaSemana = [[], [], [], [], [], [], []]; // 0: Domingo, 1: Segunda, ...
|
||||
const base = dayjs(`${ano}-${mes}-01`);
|
||||
const diasNoMes = base.daysInMonth();
|
||||
|
||||
for (let d = 1; d <= diasNoMes; d++) {
|
||||
const data = dayjs(`${ano}-${mes}-${d}`);
|
||||
const diaDaSemana = data.day(); // Retorna um número de 0 (Dom) a 6 (Sáb)
|
||||
diasDaSemana[diaDaSemana].push(d);
|
||||
}
|
||||
|
||||
// Retornando apenas os dias úteis (Segunda a Sexta)
|
||||
return [
|
||||
diasDaSemana[1], // Segundas
|
||||
diasDaSemana[2], // Terças
|
||||
diasDaSemana[3], // Quartas
|
||||
diasDaSemana[4], // Quintas
|
||||
diasDaSemana[5], // Sextas
|
||||
];
|
||||
};
|
||||
|
||||
|
||||
// --- COMPONENTE PRINCIPAL ---
|
||||
|
||||
const Agendamento = () => {
|
||||
const [FiladeEspera, setFiladeEspera] = useState(false);
|
||||
const [tabela, setTabela] = useState('diario');
|
||||
const [PageNovaConsulta, setPageConsulta] = useState(false);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
|
||||
const filteredFila = useMemo(() =>
|
||||
filaEsperaData.filter(item =>
|
||||
item.nome.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
item.email.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
item.cpf.includes(searchTerm) ||
|
||||
item.telefone.includes(searchTerm)
|
||||
), [searchTerm]);
|
||||
|
||||
const handleClickAgendamento = (agendamento) => {
|
||||
if (agendamento.status !== 'vazio') return;
|
||||
setPageConsulta(true);
|
||||
};
|
||||
|
||||
const handleClickCancel = () => setPageConsulta(false);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Agendar nova consulta</h1>
|
||||
|
||||
{!PageNovaConsulta ? (
|
||||
<div className='atendimento-eprocura'>
|
||||
|
||||
{/* ✅ BARRA DE BUSCA E FILTRO FOI MOVIDA PARA DENTRO DO CALENDÁRIO */}
|
||||
|
||||
{/* ✅ BARRA DE UNIDADE E PROFISSIONAL REMOVIDA (COMENTADA) */}
|
||||
{/*
|
||||
<div className='unidade-selecionarprofissional'>
|
||||
<select defaultValue="">
|
||||
<option value="" disabled >Unidade</option>
|
||||
<option value="central">Unidade Central</option>
|
||||
<option value="norte">Unidade Zona Norte</option>
|
||||
<option value="oeste">Unidade Zona Oeste</option>
|
||||
</select>
|
||||
<input type="text" placeholder='Selecionar profissional' />
|
||||
</div>
|
||||
*/}
|
||||
|
||||
{/* Botões para alternar Agenda / Fila de Espera */}
|
||||
<div className='container-btns-agenda-fila_esepera'>
|
||||
<button
|
||||
className={`btn-agenda ${!FiladeEspera ? "opc-agenda-ativo" : ""}`}
|
||||
onClick={() => setFiladeEspera(false)}
|
||||
>
|
||||
Agenda
|
||||
</button>
|
||||
<button
|
||||
className={`btn-fila-espera ${FiladeEspera ? "opc-filaespera-ativo" : ""}`}
|
||||
onClick={() => setFiladeEspera(true)}
|
||||
>
|
||||
Fila de espera
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<section className='calendario-ou-filaespera'>
|
||||
{!FiladeEspera ? (
|
||||
<div className='calendario'>
|
||||
<div>
|
||||
<section className='btns-e-legenda-container'>
|
||||
<div>
|
||||
<button
|
||||
className={`btn-selecionar-tabeladia ${tabela === "diario" ? "ativo" : ""}`}
|
||||
onClick={() => setTabela("diario")}
|
||||
>
|
||||
<i className="fa-solid fa-calendar-day"></i> Dia
|
||||
</button>
|
||||
<button
|
||||
className={`btn-selecionar-tabelasemana ${tabela === 'semanal' ? 'ativo' : ""}`}
|
||||
onClick={() => setTabela("semanal")}
|
||||
>
|
||||
<i className="fa-solid fa-calendar-day"></i> Semana
|
||||
</button>
|
||||
<button
|
||||
className={`btn-selecionar-tabelames ${tabela === 'mensal' ? 'ativo' : ''}`}
|
||||
onClick={() => setTabela("mensal")}
|
||||
>
|
||||
<i className="fa-solid fa-calendar-day"></i> Mês
|
||||
</button>
|
||||
</div>
|
||||
<div className='legenda-tabela'>
|
||||
<div className='legenda-item-realizado'><span>Realizado</span></div>
|
||||
<div className='legenda-item-confirmado'><span>Confirmado</span></div>
|
||||
<div className='legenda-item-agendado'><span>Agendado</span></div>
|
||||
<div className='legenda-item-cancelado'><span>Cancelado</span></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ✅ BARRA DE BUSCA MOVIDA PARA CÁ */}
|
||||
<div className='busca-atendimento'>
|
||||
<div>
|
||||
<i className="fa-solid fa-calendar-day"></i>
|
||||
<input type="text" placeholder="Buscar atendimento" />
|
||||
</div>
|
||||
<div>
|
||||
<select defaultValue="" >
|
||||
<option value="" disabled>Agendar</option>
|
||||
<option value="atendimento">Atendimento</option>
|
||||
<option value="sessoes">Sessões</option>
|
||||
<option value="urgencia">Urgência</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{tabela === "diario" && <TabelaAgendamentoDia handleClickAgendamento={handleClickAgendamento} />}
|
||||
{tabela === 'semanal' && <TabelaAgendamentoSemana />}
|
||||
{tabela === 'mensal' && <TabelaAgendamentoMes ListarDiasdoMes={ListarDiasdoMes} aplicarCores={true} />}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="fila-container">
|
||||
<div className="fila-header">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Pesquisar..."
|
||||
className="busca-fila-espera"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
<h2 className="fila-titulo">Fila de Espera</h2>
|
||||
</div>
|
||||
<table className="fila-tabela">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nome</th>
|
||||
<th>Email</th>
|
||||
<th>CPF</th>
|
||||
<th>Telefone</th>
|
||||
<th>Entrou na fila de espera</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filteredFila.map((item, index) => (
|
||||
<tr key={index}>
|
||||
<td>{item.nome}</td>
|
||||
<td>{item.email}</td>
|
||||
<td>{item.cpf}</td>
|
||||
<td>{item.telefone}</td>
|
||||
<td>{item.entrada}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
) : (
|
||||
<FormNovaConsulta onCancel={handleClickCancel} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Agendamento;
|
||||
204
src/PagesMedico/Chat.jsx
Normal file
204
src/PagesMedico/Chat.jsx
Normal file
@ -0,0 +1,204 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import './styleMedico/chat.css'; // Importa o ficheiro de estilos
|
||||
|
||||
// --- DADOS MOCK (Simulação de um banco de dados) ---
|
||||
const conversationsData = [
|
||||
{
|
||||
id: 1,
|
||||
patientName: 'Ana Costa',
|
||||
avatarUrl: 'https://placehold.co/100x100/E2D9FF/6B46C1?text=AC',
|
||||
lastMessage: 'Ok, doutor. Muito obrigada!',
|
||||
timestamp: '10:45',
|
||||
unread: 2,
|
||||
messages: [
|
||||
{ id: 1, sender: 'Ana Costa', text: 'Boa tarde, doutor. Estou sentindo uma dor de cabeça persistente desde ontem.', time: '09:15' },
|
||||
{ id: 2, sender: 'Você', text: 'Olá, Ana. Além da dor de cabeça, você tem algum outro sintoma, como febre ou enjoo?', time: '09:17' },
|
||||
{ id: 3, sender: 'Ana Costa', text: 'Não, apenas a dor de cabeça mesmo. É uma pressão na parte da frente.', time: '09:20' },
|
||||
{ id: 4, sender: 'Você', text: 'Entendido. Por favor, continue monitorando e me avise se piorar. Recomendo repouso e boa hidratação.', time: '09:22' },
|
||||
{ id: 5, sender: 'Ana Costa', text: 'Ok, doutor. Muito obrigada!', time: '10:45' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
patientName: 'Carlos Andrade',
|
||||
avatarUrl: 'https://placehold.co/100x100/D1E7DD/146C43?text=CA',
|
||||
lastMessage: 'Amanhã, às 14h, está ótimo.',
|
||||
timestamp: 'Ontem',
|
||||
unread: 0,
|
||||
messages: [
|
||||
{ id: 1, sender: 'Carlos Andrade', text: 'Doutor, preciso remarcar minha consulta de amanhã.', time: 'Ontem' },
|
||||
{ id: 2, sender: 'Você', text: 'Claro, Carlos. Qual seria o melhor horário para você?', time: 'Ontem' },
|
||||
{ id: 3, sender: 'Carlos Andrade', text: 'Amanhã, às 14h, está ótimo.', time: 'Ontem' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
patientName: 'Juliana Oliveira',
|
||||
avatarUrl: 'https://placehold.co/100x100/F8D7DA/842029?text=JO',
|
||||
lastMessage: 'O resultado do exame ficou pronto.',
|
||||
timestamp: 'Sexta-feira',
|
||||
unread: 0,
|
||||
messages: [
|
||||
{ id: 1, sender: 'Juliana Oliveira', text: 'O resultado do exame ficou pronto.', time: 'Sexta-feira' }
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
patientName: 'Ricardo Pereira',
|
||||
avatarUrl: 'https://placehold.co/100x100/FFF3CD/856404?text=RP',
|
||||
lastMessage: 'Estou me sentindo muito melhor, obrigado!',
|
||||
timestamp: 'Quinta-feira',
|
||||
unread: 0,
|
||||
messages: [
|
||||
{ id: 1, sender: 'Ricardo Pereira', text: 'Estou me sentindo muito melhor, obrigado!', time: 'Quinta-feira' }
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// --- COMPONENTES ---
|
||||
|
||||
const ConversationListItem = ({ conversation, isActive, onClick }) => (
|
||||
<div
|
||||
onClick={onClick}
|
||||
className={`conversation-item ${isActive ? 'active' : ''}`}
|
||||
>
|
||||
<img src={conversation.avatarUrl} alt={conversation.patientName} className="avatar" />
|
||||
<div className="conversation-details">
|
||||
<div className="conversation-header">
|
||||
<p className="patient-name">{conversation.patientName}</p>
|
||||
<span className="timestamp">{conversation.timestamp}</span>
|
||||
</div>
|
||||
<div className="conversation-body">
|
||||
<p className="last-message">{conversation.lastMessage}</p>
|
||||
{conversation.unread > 0 && (
|
||||
<span className="unread-badge">{conversation.unread}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const ChatMessage = ({ message, isDoctor }) => (
|
||||
<div className={`message-container ${isDoctor ? 'sent' : 'received'}`}>
|
||||
<div className="message-bubble">
|
||||
<p className="message-text">{message.text}</p>
|
||||
<p className="message-time">{message.time}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const App = () => {
|
||||
const [conversations, setConversations] = useState(conversationsData);
|
||||
const [activeConversationId, setActiveConversationId] = useState(1);
|
||||
const [newMessage, setNewMessage] = useState('');
|
||||
const chatEndRef = useRef(null);
|
||||
|
||||
const activeConversation = conversations.find(c => c.id === activeConversationId);
|
||||
|
||||
useEffect(() => {
|
||||
chatEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||
}, [activeConversation]);
|
||||
|
||||
const handleSendMessage = (e) => {
|
||||
e.preventDefault();
|
||||
if (newMessage.trim() === '') return;
|
||||
|
||||
const messageToSend = {
|
||||
id: Date.now(),
|
||||
sender: 'Você',
|
||||
text: newMessage,
|
||||
time: new Date().toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' }),
|
||||
};
|
||||
|
||||
const updatedConversations = conversations.map(convo => {
|
||||
if (convo.id === activeConversationId) {
|
||||
return {
|
||||
...convo,
|
||||
messages: [...convo.messages, messageToSend],
|
||||
lastMessage: newMessage,
|
||||
timestamp: 'Agora'
|
||||
};
|
||||
}
|
||||
return convo;
|
||||
});
|
||||
|
||||
setConversations(updatedConversations);
|
||||
setNewMessage('');
|
||||
};
|
||||
|
||||
const handleConversationClick = (id) => {
|
||||
setActiveConversationId(id);
|
||||
const updatedConversations = conversations.map(convo =>
|
||||
convo.id === id ? { ...convo, unread: 0 } : convo
|
||||
);
|
||||
setConversations(updatedConversations);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="chat-app-container">
|
||||
{/* Barra Lateral de Conversas */}
|
||||
<aside className="sidebar">
|
||||
<header className="sidebar-header">
|
||||
<h1>Mensagens</h1>
|
||||
<div className="search-container">
|
||||
<input type="text" placeholder="Pesquisar paciente..." className="search-input" />
|
||||
<svg className="search-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
|
||||
</div>
|
||||
</header>
|
||||
<div className="conversation-list">
|
||||
{conversations.map(convo => (
|
||||
<ConversationListItem
|
||||
key={convo.id}
|
||||
conversation={convo}
|
||||
isActive={convo.id === activeConversationId}
|
||||
onClick={() => handleConversationClick(convo.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
{/* Painel Principal do Chat */}
|
||||
<main className="main-chat">
|
||||
{activeConversation ? (
|
||||
<>
|
||||
<header className="chat-header">
|
||||
<img src={activeConversation.avatarUrl} alt={activeConversation.patientName} className="avatar" />
|
||||
<div>
|
||||
<h2 className="chat-patient-name">{activeConversation.patientName}</h2>
|
||||
<p className="chat-status">Online</p>
|
||||
</div>
|
||||
</header>
|
||||
<div className="messages-body">
|
||||
{activeConversation.messages.map(msg => (
|
||||
<ChatMessage key={msg.id} message={msg} isDoctor={msg.sender === 'Você'} />
|
||||
))}
|
||||
<div ref={chatEndRef} />
|
||||
</div>
|
||||
<footer className="message-footer">
|
||||
<form onSubmit={handleSendMessage} className="message-form">
|
||||
<input
|
||||
type="text"
|
||||
value={newMessage}
|
||||
onChange={(e) => setNewMessage(e.target.value)}
|
||||
placeholder="Digite sua mensagem..."
|
||||
className="message-input"
|
||||
autoComplete="off"
|
||||
/>
|
||||
<button type="submit" className="send-button">
|
||||
<svg className="send-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"></path></svg>
|
||||
</button>
|
||||
</form>
|
||||
</footer>
|
||||
</>
|
||||
) : (
|
||||
<div className="no-conversation-selected">
|
||||
<p>Selecione uma conversa para começar.</p>
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
|
||||
139
src/PagesMedico/InicioMedico.jsx
Normal file
139
src/PagesMedico/InicioMedico.jsx
Normal file
@ -0,0 +1,139 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { FaUser, FaUserPlus, FaCalendarAlt, FaCalendarCheck } from 'react-icons/fa';
|
||||
import './style/Inicio.css';
|
||||
|
||||
function Inicio({ setCurrentPage }) {
|
||||
const [pacientes, setPacientes] = useState([]);
|
||||
const [agendamentos, setAgendamentos] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchPacientes = async () => {
|
||||
try {
|
||||
const res = await fetch("https://mock.apidog.com/m1/1053378-0-default/pacientes");
|
||||
const data = await res.json();
|
||||
console.log(data)
|
||||
//setPacientes(data.data);
|
||||
} catch (error) {
|
||||
console.error("Erro ao buscar pacientes:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchAgendamentos = async () => {
|
||||
return; // <===serve para que nao cause erro
|
||||
// try {
|
||||
// const res = await fetch();
|
||||
// const data = await res.json();
|
||||
// setAgendamentos(data.data);
|
||||
// } catch (error) {
|
||||
// console.error("Erro ao buscar agendamentos:", error);
|
||||
// }
|
||||
};
|
||||
|
||||
fetchPacientes();
|
||||
fetchAgendamentos();
|
||||
}, []);
|
||||
|
||||
const totalPacientes = pacientes.length;
|
||||
const novosEsseMes = pacientes.filter(p => p.createdAt && new Date(p.createdAt).getMonth() === new Date().getMonth()).length;
|
||||
|
||||
const hoje = new Date();
|
||||
const agendamentosDoDia = agendamentos.filter(
|
||||
a => a.data && new Date(a.data).getDate() === hoje.getDate()
|
||||
);
|
||||
const agendamentosHoje = agendamentosDoDia.length;
|
||||
|
||||
return (
|
||||
<div className="dashboard-container">
|
||||
<div className="dashboard-header">
|
||||
|
||||
<h1>Bem-vindo ao MediConnect</h1><br></br>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="stats-grid">
|
||||
<div className="stat-card">
|
||||
<div className="stat-info">
|
||||
<span className="stat-label">TOTAL DE PACIENTES</span>
|
||||
<span className="stat-value">{totalPacientes}</span>
|
||||
</div>
|
||||
<div className="stat-icon-wrapper blue"><FaUser className="stat-icon" /></div>
|
||||
</div>
|
||||
|
||||
<div className="stat-card">
|
||||
<div className="stat-info">
|
||||
<span className="stat-label">NOVOS ESTE MÊS</span>
|
||||
<span className="stat-value">{novosEsseMes}</span>
|
||||
</div>
|
||||
<div className="stat-icon-wrapper green"><FaUserPlus className="stat-icon" /></div>
|
||||
</div>
|
||||
|
||||
<div className="stat-card">
|
||||
<div className="stat-info">
|
||||
<span className="stat-label">AGENDAMENTOS HOJE</span>
|
||||
<span className="stat-value">{agendamentosHoje}</span>
|
||||
</div>
|
||||
<div className="stat-icon-wrapper purple"><FaCalendarCheck className="stat-icon" /></div>
|
||||
</div>
|
||||
|
||||
<div className="stat-card">
|
||||
<div className="stat-info">
|
||||
<span className="stat-label">PENDÊNCIAS</span>
|
||||
<span className="stat-value">0</span>
|
||||
</div>
|
||||
<div className="stat-icon-wrapper orange"><FaCalendarAlt className="stat-icon" /></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="quick-actions">
|
||||
<h2>Ações Rápidas</h2>
|
||||
<div className="actions-grid">
|
||||
<div className="action-button" onClick={() => setCurrentPage('form-layout')}>
|
||||
<FaUserPlus className="action-icon" />
|
||||
<div className="action-info">
|
||||
<span className="action-title">Novo Paciente</span>
|
||||
<span className="action-desc">Cadastrar um novo paciente</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="action-button" onClick={() => setCurrentPage('table')}>
|
||||
<FaUser className="action-icon" />
|
||||
<div className="action-info">
|
||||
<span className="action-title">Lista de Pacientes</span>
|
||||
<span className="action-desc">Ver todos os pacientes</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="action-button" onClick={() => setCurrentPage('agendamento')}>
|
||||
<FaCalendarCheck className="action-icon" />
|
||||
<div className="action-info">
|
||||
<span className="action-title">Agendamentos</span>
|
||||
<span className="action-desc">Gerenciar consultas</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="appointments-section">
|
||||
<h2>Próximos Agendamentos</h2>
|
||||
{agendamentosHoje > 0 ? (
|
||||
<div>
|
||||
{agendamentosDoDia.map(agendamento => (
|
||||
<div key={agendamento.id} className="agendamento-item">
|
||||
<p>{agendamento.nomePaciente}</p>
|
||||
<p>{new Date(agendamento.data).toLocaleTimeString()}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="no-appointments-content">
|
||||
<FaCalendarCheck className="no-appointments-icon" />
|
||||
<p>Nenhum agendamento para hoje</p>
|
||||
<button className="manage-button" onClick={() => setCurrentPage('agendamento')}>
|
||||
Gerenciar Agendamentos
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Inicio;
|
||||
123
src/PagesMedico/prontuario.jsx
Normal file
123
src/PagesMedico/prontuario.jsx
Normal file
@ -0,0 +1,123 @@
|
||||
import React from "react";
|
||||
import "./styleMedico/geral.css";
|
||||
|
||||
const pacientes = [
|
||||
{
|
||||
id: 1,
|
||||
nome: "Maria Oliveira",
|
||||
idade: 45,
|
||||
sexo: "Feminino",
|
||||
alergias: "Penicilina",
|
||||
historico: [
|
||||
{ data: "2025-09-15", descricao: "Consulta de acompanhamento de hipertensão. Pressão arterial controlada." },
|
||||
{ data: "2025-07-20", descricao: "Relatou dores de cabeça frequentes." },
|
||||
],
|
||||
medicamentos: [
|
||||
{ nome: "Losartana 50mg", uso: "1x ao dia pela manhã" },
|
||||
{ nome: "Dipirona 500mg", uso: "Em caso de dor" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
nome: "Carlos Souza",
|
||||
idade: 62,
|
||||
sexo: "Masculino",
|
||||
alergias: "Nenhuma conhecida",
|
||||
historico: [
|
||||
{ data: "2025-09-01", descricao: "Check-up anual. Glicemia de jejum elevada." },
|
||||
{ data: "2025-03-12", descricao: "Queixa de dor no joelho direito após esforço." },
|
||||
],
|
||||
medicamentos: [
|
||||
{ nome: "Metformina 850mg", uso: "2x ao dia, após as refeições" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
nome: "Ana Costa",
|
||||
idade: 28,
|
||||
sexo: "Feminino",
|
||||
alergias: "Pólen e poeira",
|
||||
historico: [
|
||||
{ data: "2025-08-22", descricao: "Crise de rinite alérgica. Prescrito novo anti-histamínico." },
|
||||
{ data: "2025-01-30", descricao: "Consulta de rotina ginecológica." },
|
||||
],
|
||||
medicamentos: [
|
||||
{ nome: "Loratadina 10mg", uso: "1x ao dia durante crises alérgicas" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
nome: "João da Silva",
|
||||
idade: 35,
|
||||
sexo: "Masculino",
|
||||
alergias: "Nenhuma conhecida",
|
||||
historico: [
|
||||
{ data: "2025-09-10", descricao: "Consulta de rotina – tudo normal" },
|
||||
{ data: "2025-08-05", descricao: "Exame de sangue – colesterol levemente alto" },
|
||||
],
|
||||
medicamentos: [
|
||||
{ nome: "Atorvastatina 10mg", uso: "1x ao dia" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
nome: "Pedro Rocha",
|
||||
idade: 55,
|
||||
sexo: "Masculino",
|
||||
alergias: "Frutos do mar",
|
||||
historico: [
|
||||
{ data: "2025-09-18", descricao: "Relatou dor e inchaço no tornozelo esquerdo." },
|
||||
{ data: "2025-04-11", descricao: "Exames de rotina, sem alterações significativas." },
|
||||
],
|
||||
medicamentos: [
|
||||
{ nome: "Nimesulida 100mg", uso: "2x ao dia por 5 dias" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
function Prontuario() {
|
||||
return (
|
||||
<>
|
||||
{pacientes.map((paciente) => (
|
||||
<div key={paciente.id} className="prontuario-container">
|
||||
<h1>Prontuário Médico de {paciente.nome}</h1>
|
||||
|
||||
{/* ---- Agrupe as seções em um container flex ---- */}
|
||||
<div className="prontuario-sections">
|
||||
<section className="prontuario-section">
|
||||
<h2>Dados do Paciente</h2>
|
||||
<p><strong>Nome:</strong> {paciente.nome}</p>
|
||||
<p><strong>Idade:</strong> {paciente.idade}</p>
|
||||
<p><strong>Sexo:</strong> {paciente.sexo}</p>
|
||||
<p><strong>Alergias:</strong> {paciente.alergias}</p>
|
||||
</section>
|
||||
|
||||
<section className="prontuario-section">
|
||||
<h2>Histórico de Consultas</h2>
|
||||
<ul>
|
||||
{paciente.historico.map((item, i) => (
|
||||
<li key={i}>
|
||||
<strong>{item.data}:</strong> {item.descricao}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className="prontuario-section">
|
||||
<h2>Medicamentos em Uso</h2>
|
||||
<ul>
|
||||
{paciente.medicamentos.map((med, i) => (
|
||||
<li key={i}>
|
||||
<strong>{med.nome}</strong> – {med.uso}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Prontuario;
|
||||
115
src/PagesMedico/relatorio.jsx
Normal file
115
src/PagesMedico/relatorio.jsx
Normal file
@ -0,0 +1,115 @@
|
||||
import React, { useState } from 'react';
|
||||
import './styleMedico/geral.css';
|
||||
|
||||
const mockData = {
|
||||
atendimentos: [
|
||||
{ id: 1, paciente: 'Carlos Andrade', data: '2025-09-25', motivo: 'Consulta de rotina', medico: 'Dr. House' },
|
||||
{ id: 2, paciente: 'Beatriz Costa', data: '2025-09-24', motivo: 'Retorno', medico: 'Dr. Wilson' },
|
||||
{ id: 3, paciente: 'Juliana Ferreira', data: '2025-09-23', motivo: 'Exames de sangue', medico: 'Dr. House' },
|
||||
{ id: 4, paciente: 'Marcos Souza', data: '2025-09-22', motivo: 'Consulta de rotina', medico: 'Dr. Cuddy' },
|
||||
],
|
||||
pacientes: [
|
||||
{ id: 1, nome: 'Carlos Andrade', idade: 45, cadastro: '2024-03-10' },
|
||||
{ id: 2, nome: 'Beatriz Costa', idade: 29, cadastro: '2023-11-20' },
|
||||
{ id: 3, nome: 'Juliana Ferreira', idade: 34, cadastro: '2025-01-15' },
|
||||
{ id: 4, nome: 'Marcos Souza', idade: 52, cadastro: '2022-07-01' },
|
||||
{ id: 5, nome: 'Fernanda Lima', idade: 25, cadastro: '2025-08-05' },
|
||||
],
|
||||
};
|
||||
|
||||
function Relatorio() {
|
||||
// ...restante do código igual...
|
||||
const [tipoRelatorio, setTipoRelatorio] = useState('');
|
||||
const [dados, setDados] = useState(null);
|
||||
const [dataInicio, setDataInicio] = useState('');
|
||||
const [dataFim, setDataFim] = useState('');
|
||||
|
||||
const handleGerarRelatorio = () => {
|
||||
if (!tipoRelatorio) {
|
||||
alert('Por favor, selecione um tipo de relatório.');
|
||||
return;
|
||||
}
|
||||
setDados(mockData[tipoRelatorio] || []);
|
||||
};
|
||||
|
||||
const renderizarTabela = () => {
|
||||
if (!dados) {
|
||||
return <p className="info-text">Selecione os filtros e clique em "Gerar Relatório" para começar.</p>;
|
||||
}
|
||||
|
||||
if (dados.length === 0) {
|
||||
return <p className="info-text">Nenhum dado encontrado para os filtros selecionados.</p>;
|
||||
}
|
||||
|
||||
const headers = Object.keys(dados[0]);
|
||||
|
||||
return (
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
{headers.map(header => <th key={header}>{header.toUpperCase()}</th>)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{dados.map(item => (
|
||||
<tr key={item.id}>
|
||||
{headers.map(header => <td key={`${item.id}-${header}`}>{item[header]}</td>)}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relatorios-container">
|
||||
<h1>Página de Relatórios</h1>
|
||||
|
||||
<div className="filtros-container">
|
||||
<div className="filtro-item">
|
||||
<label htmlFor="tipoRelatorio">Tipo de Relatório</label>
|
||||
<select
|
||||
id="tipoRelatorio"
|
||||
value={tipoRelatorio}
|
||||
onChange={(e) => setTipoRelatorio(e.target.value)}
|
||||
>
|
||||
<option value="">Selecione...</option>
|
||||
<option value="atendimentos">Relatório de Atendimentos</option>
|
||||
<option value="pacientes">Relatório de Pacientes</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="filtro-item">
|
||||
<label htmlFor="dataInicio">Data de Início</label>
|
||||
<input
|
||||
type="date"
|
||||
id="dataInicio"
|
||||
value={dataInicio}
|
||||
onChange={(e) => setDataInicio(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="filtro-item">
|
||||
<label htmlFor="dataFim">Data Final</label>
|
||||
<input
|
||||
type="date"
|
||||
id="dataFim"
|
||||
value={dataFim}
|
||||
onChange={(e) => setDataFim(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button onClick={handleGerarRelatorio} className="btn-gerar">
|
||||
Gerar Relatório
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="resultado-container">
|
||||
<h2>Resultado</h2>
|
||||
{renderizarTabela()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Relatorio;
|
||||
151
src/PagesMedico/styleMedico/Agendamento.css
Normal file
151
src/PagesMedico/styleMedico/Agendamento.css
Normal file
@ -0,0 +1,151 @@
|
||||
/* --- Esconde a barra de unidade e profissional --- */
|
||||
.unidade-selecionarprofissional {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* --- Posiciona a barra de busca corretamente --- */
|
||||
.busca-atendimento {
|
||||
display: flex;
|
||||
align-items: center; /* Alinha os itens verticalmente */
|
||||
margin-top: 20px; /* Espaço acima da barra de busca */
|
||||
padding: 0 10px; /* Adiciona um padding lateral para alinhar com o resto */
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.busca-atendimento > div:first-child {
|
||||
width: 400px; /* Define um tamanho para a barra de pesquisa */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.busca-atendimento input {
|
||||
margin-left: 8px;
|
||||
border-radius: 8px;
|
||||
padding: 8px;
|
||||
width: 100%;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.busca-atendimento select {
|
||||
padding: 8px;
|
||||
border-radius: 8px;
|
||||
background-color: #0078d7;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* --- Estilos dos botões de Dia, Semana, Mês --- */
|
||||
.btn-selecionar-tabeladia,
|
||||
.btn-selecionar-tabelasemana,
|
||||
.btn-selecionar-tabelames {
|
||||
background-color: rgba(231, 231, 231, 0.808);
|
||||
padding: 8px 10px;
|
||||
font-size: larger;
|
||||
font-weight: bold;
|
||||
border-style: hidden;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-selecionar-tabeladia {
|
||||
border-radius: 10px 0px 0px 10px;
|
||||
}
|
||||
|
||||
.btn-selecionar-tabelames {
|
||||
border-radius: 0px 10px 10px 0px;
|
||||
}
|
||||
|
||||
.btn-selecionar-tabeladia.ativo,
|
||||
.btn-selecionar-tabelasemana.ativo,
|
||||
.btn-selecionar-tabelames.ativo {
|
||||
background-color: lightcyan;
|
||||
border-color: darkcyan;
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
/* --- Container dos botões e legenda --- */
|
||||
.btns-e-legenda-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 10px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
/* --- Legendas --- */
|
||||
.legenda-tabela {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
#status-card-consulta-realizado, .legenda-item-realizado {
|
||||
background-color: #b7ffbd;
|
||||
border: 3px solid #91d392;
|
||||
padding: 5px;
|
||||
font-weight: bold;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#status-card-consulta-cancelado, .legenda-item-cancelado {
|
||||
background-color: #ffb7cc;
|
||||
border: 3px solid #ff6c84;
|
||||
padding: 5px;
|
||||
font-weight: bold;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#status-card-consulta-confirmado, .legenda-item-confirmado {
|
||||
background-color: #eef8fb;
|
||||
border: 3px solid #d8dfe7;
|
||||
padding: 5px;
|
||||
font-weight: bold;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#status-card-consulta-agendado, .legenda-item-agendado {
|
||||
background-color: #f7f7c4;
|
||||
border: 3px solid #f3ce67;
|
||||
padding: 5px;
|
||||
font-weight: bold;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
/* --- Estrutura Geral --- */
|
||||
.calendario {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
border: 1px solid #eee;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.calendario-ou-filaespera {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.container-btns-agenda-fila_esepera {
|
||||
margin-top: 30px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 20px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.btn-fila-espera,
|
||||
.btn-agenda {
|
||||
background-color: transparent;
|
||||
border: 0px;
|
||||
border-bottom: 3px solid transparent;
|
||||
padding: 8px;
|
||||
border-radius: 10px 10px 0px 0px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.opc-filaespera-ativo,
|
||||
.opc-agenda-ativo {
|
||||
color: white;
|
||||
background-color: #5980fd;
|
||||
}
|
||||
97
src/PagesMedico/styleMedico/FilaEspera.css
Normal file
97
src/PagesMedico/styleMedico/FilaEspera.css
Normal file
@ -0,0 +1,97 @@
|
||||
/* ===== Fila de Espera ===== */
|
||||
.fila-container {
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
margin: 0; /* >>> sem espaço para encostar no topo <<< */
|
||||
background: #ffffff;
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
|
||||
border: 10px solid #ffffff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.fila-titulo {
|
||||
text-align: center;
|
||||
font-size: 1.8rem;
|
||||
font-weight: bold;
|
||||
margin-bottom: 20px;
|
||||
color: #333;
|
||||
border-bottom: 2px solid #e0e0e0;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.fila-tabela {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.fila-tabela thead {
|
||||
background-color: #f7fbff;
|
||||
}
|
||||
|
||||
.fila-tabela th,
|
||||
.fila-tabela td {
|
||||
padding: 12px 16px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.fila-tabela th {
|
||||
font-weight: bold;
|
||||
color: #444;
|
||||
background-color: #f1f1f1
|
||||
}
|
||||
|
||||
/* --- Linhas alternadas cinza/branco --- */
|
||||
.fila-tabela tbody tr:nth-child(even) {
|
||||
background-color: #f9f9f9; /* cinza clarinho */
|
||||
}
|
||||
.fila-tabela tbody tr:nth-child(odd) {
|
||||
background-color: #ffffff; /* branco */
|
||||
}
|
||||
|
||||
.fila-tabela tbody tr:hover {
|
||||
background-color: #f1f6fa; /* hover sutil */
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.fila-tabela th, .fila-tabela td {
|
||||
padding: 8px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.fila-titulo {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
.fila-header {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center; /* centraliza o título */
|
||||
margin-bottom: 16px;
|
||||
height: 40px; /* altura da linha */
|
||||
}
|
||||
|
||||
.busca-fila-espera {
|
||||
position: absolute;
|
||||
left: 0; /* barra na esquerda */
|
||||
padding: 6px 12px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 6px;
|
||||
font-size: 1rem;
|
||||
width: 350px;
|
||||
outline: none;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.busca-fila-espera:focus {
|
||||
border-color: #888;
|
||||
}
|
||||
|
||||
.fila-header h2 {
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
298
src/PagesMedico/styleMedico/chat.css
Normal file
298
src/PagesMedico/styleMedico/chat.css
Normal file
@ -0,0 +1,298 @@
|
||||
/* --- Variáveis de Estilo Globais --- */
|
||||
:root {
|
||||
--cor-fundo-app: #F9FAFB;
|
||||
--cor-fundo-sidebar: #FFFFFF;
|
||||
--cor-fundo-chat: #F9FAFB;
|
||||
--cor-borda: #E5E7EB;
|
||||
--cor-texto-principal: #1F2937;
|
||||
--cor-texto-secundario: #6B7280;
|
||||
--cor-primaria: #3B82F6; /* Azul */
|
||||
--cor-primaria-hover: #2563EB;
|
||||
--cor-destaque: #EF4444; /* Vermelho para notificações */
|
||||
--cor-online: #10B981; /* Verde */
|
||||
|
||||
--font-principal: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
}
|
||||
|
||||
/* --- Estilo Base e Container Principal --- */
|
||||
.chat-app-container {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
background-color: var(--cor-fundo-app);
|
||||
font-family: var(--font-principal);
|
||||
color: var(--cor-texto-principal);
|
||||
}
|
||||
|
||||
/* --- Barra Lateral (Sidebar) --- */
|
||||
.sidebar {
|
||||
width: 30%;
|
||||
min-width: 320px;
|
||||
max-width: 400px;
|
||||
height: 100%;
|
||||
background-color: var(--cor-fundo-sidebar);
|
||||
border-right: 1px solid var(--cor-borda);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid var(--cor-borda);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background-color: var(--cor-fundo-sidebar);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.sidebar-header h1 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
position: relative;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
padding: 0.5rem 0.5rem 0.5rem 2.5rem; /* Espaço para o ícone */
|
||||
border: 1px solid #D1D5DB;
|
||||
border-radius: 0.5rem;
|
||||
outline: none;
|
||||
transition: box-shadow 0.2s;
|
||||
}
|
||||
.search-input:focus {
|
||||
box-shadow: 0 0 0 2px var(--cor-primaria);
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
color: var(--cor-texto-secundario);
|
||||
position: absolute;
|
||||
left: 0.75rem;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.conversation-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
/* --- Item da Lista de Conversas --- */
|
||||
.conversation-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.75rem;
|
||||
cursor: pointer;
|
||||
border-radius: 0.5rem;
|
||||
transition: background-color 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.conversation-item:hover {
|
||||
background-color: #F3F4F6;
|
||||
}
|
||||
|
||||
.conversation-item.active {
|
||||
background-color: var(--cor-primaria);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
border-radius: 9999px;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.conversation-details {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.conversation-header, .conversation-body {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.patient-name {
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.timestamp {
|
||||
font-size: 0.75rem;
|
||||
color: var(--cor-texto-secundario);
|
||||
}
|
||||
.conversation-item.active .timestamp {
|
||||
color: #D1D5DB;
|
||||
}
|
||||
|
||||
.last-message {
|
||||
font-size: 0.875rem;
|
||||
color: var(--cor-texto-secundario);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
.conversation-item.active .last-message {
|
||||
color: #E5E7EB;
|
||||
}
|
||||
|
||||
.unread-badge {
|
||||
background-color: var(--cor-destaque);
|
||||
color: white;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
border-radius: 9999px;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* --- Janela Principal do Chat --- */
|
||||
.main-chat {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.chat-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid var(--cor-borda);
|
||||
background-color: var(--cor-fundo-sidebar);
|
||||
}
|
||||
.chat-patient-name {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
.chat-status {
|
||||
font-size: 0.875rem;
|
||||
color: var(--cor-online);
|
||||
}
|
||||
|
||||
.messages-body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 1.5rem;
|
||||
background-color: var(--cor-fundo-chat);
|
||||
}
|
||||
|
||||
.message-footer {
|
||||
padding: 1rem;
|
||||
background-color: var(--cor-fundo-sidebar);
|
||||
border-top: 1px solid var(--cor-borda);
|
||||
}
|
||||
|
||||
.message-form {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.message-input {
|
||||
flex: 1;
|
||||
padding: 0.75rem 1rem;
|
||||
border: 1px solid #D1D5DB;
|
||||
border-radius: 9999px;
|
||||
outline: none;
|
||||
transition: box-shadow 0.2s;
|
||||
}
|
||||
.message-input:focus {
|
||||
box-shadow: 0 0 0 2px var(--cor-primaria);
|
||||
}
|
||||
|
||||
.send-button {
|
||||
background-color: var(--cor-primaria);
|
||||
color: white;
|
||||
padding: 0.75rem;
|
||||
border: none;
|
||||
border-radius: 9999px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s, transform 0.1s;
|
||||
}
|
||||
.send-button:hover {
|
||||
background-color: var(--cor-primaria-hover);
|
||||
}
|
||||
.send-button:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.send-icon {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
/* --- Balões de Mensagem --- */
|
||||
.message-container {
|
||||
display: flex;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
.message-container.sent {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.message-container.received {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.message-bubble {
|
||||
max-width: 75%;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 1.25rem;
|
||||
}
|
||||
.message-text {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
.message-time {
|
||||
font-size: 0.75rem;
|
||||
text-align: right;
|
||||
margin-top: 0.25rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.message-container.sent .message-bubble {
|
||||
background-color: var(--cor-primaria);
|
||||
color: white;
|
||||
border-bottom-right-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.message-container.received .message-bubble {
|
||||
background-color: #E5E7EB;
|
||||
color: var(--cor-texto-principal);
|
||||
border-bottom-left-radius: 0.25rem;
|
||||
}
|
||||
|
||||
/* --- Estado Vazio --- */
|
||||
.no-conversation-selected {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
color: var(--cor-texto-secundario);
|
||||
}
|
||||
|
||||
/* --- Responsividade --- */
|
||||
@media (max-width: 768px) {
|
||||
.main-chat {
|
||||
display: none;
|
||||
}
|
||||
.sidebar {
|
||||
width: 100%;
|
||||
min-width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
189
src/PagesMedico/styleMedico/geral.css
Normal file
189
src/PagesMedico/styleMedico/geral.css
Normal file
@ -0,0 +1,189 @@
|
||||
/* --- Estilos Gerais e Reset Básico --- */
|
||||
:root {
|
||||
--cor-primaria: #0078d7; /* Azul principal */
|
||||
--cor-fundo: #f4f7f9; /* Cinza bem claro para o fundo da página */
|
||||
--cor-card: #ffffff; /* Branco para os cards */
|
||||
--cor-texto: #333333; /* Cor principal do texto */
|
||||
--cor-borda: #e1e5e8; /* Cor suave para bordas */
|
||||
--sombra-card: 0 4px 12px rgba(0, 0, 0, 0.08); /* Sombra sutil */
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
background-color: var(--cor-fundo);
|
||||
color: var(--cor-texto);
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--cor-primaria);
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid var(--cor-borda);
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: var(--cor-texto);
|
||||
margin-bottom: 15px;
|
||||
font-size: 1.3rem;
|
||||
border-bottom: 1px solid #eee;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
/* --- Estilos da Página de Prontuários --- */
|
||||
|
||||
.prontuario-container {
|
||||
background-color: var(--cor-card);
|
||||
border-radius: 10px;
|
||||
box-shadow: var(--sombra-card);
|
||||
margin: 2rem;
|
||||
padding: 2rem;
|
||||
border: 1px solid var(--cor-borda);
|
||||
}
|
||||
|
||||
.prontuario-sections {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
flex-wrap: wrap; /* Permite que as seções quebrem a linha em telas menores */
|
||||
}
|
||||
|
||||
.prontuario-section {
|
||||
flex: 1; /* Faz com que as seções dividam o espaço igualmente */
|
||||
min-width: 300px; /* Largura mínima antes de quebrar a linha */
|
||||
background-color: #fdfdfd;
|
||||
border: 1px solid #f0f0f0;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.prontuario-section ul {
|
||||
list-style-type: none; /* Remove os marcadores da lista */
|
||||
}
|
||||
|
||||
.prontuario-section li {
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid #f0f0f0; /* Linha separadora entre os itens */
|
||||
}
|
||||
|
||||
.prontuario-section li:last-child {
|
||||
border-bottom: none; /* Remove a linha do último item */
|
||||
}
|
||||
|
||||
.prontuario-section p {
|
||||
line-height: 1.6;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.prontuario-section strong {
|
||||
color: #555;
|
||||
}
|
||||
|
||||
|
||||
/* --- Estilos da Página de Relatórios --- */
|
||||
|
||||
.relatorios-container {
|
||||
background-color: var(--cor-card);
|
||||
border-radius: 10px;
|
||||
box-shadow: var(--sombra-card);
|
||||
margin: 2rem;
|
||||
padding: 2rem;
|
||||
border: 1px solid var(--cor-borda);
|
||||
}
|
||||
|
||||
.filtros-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
align-items: flex-end; /* Alinha os itens na base, fica ótimo com o botão */
|
||||
background-color: #f8f9fa;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 2rem;
|
||||
border: 1px solid var(--cor-borda);
|
||||
}
|
||||
|
||||
.filtro-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
flex: 1;
|
||||
min-width: 180px;
|
||||
}
|
||||
|
||||
.filtro-item label {
|
||||
font-weight: 600;
|
||||
font-size: 0.9em;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.filtro-item input[type="date"],
|
||||
.filtro-item select {
|
||||
padding: 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
font-size: 1rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.btn-gerar {
|
||||
padding: 10px 25px;
|
||||
background-color: var(--cor-primaria);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-gerar:hover {
|
||||
background-color: #005a9e; /* Um tom de azul mais escuro */
|
||||
}
|
||||
|
||||
.resultado-container {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.info-text {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
color: #777;
|
||||
font-style: italic;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* Estilização da Tabela de Resultados */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
thead th {
|
||||
background-color: var(--cor-primaria);
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
tbody tr:nth-child(even) {
|
||||
background-color: #f8f9fa; /* Linhas zebradas */
|
||||
}
|
||||
|
||||
tbody tr:hover {
|
||||
background-color: #e9ecef; /* Efeito ao passar o mouse */
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 12px;
|
||||
border-bottom: 1px solid var(--cor-borda);
|
||||
}
|
||||
@ -1,123 +1,134 @@
|
||||
import React, { useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import menuItems from "../data/sidebar-items.json";
|
||||
import menuItems from "../data/sidebar-items-medico.json"; // Use "sidebar-items-secretaria.json" para secretaria e "sidebar-items-adm.json" para ADM
|
||||
|
||||
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;
|
||||
60
src/data/sidebar-items-adm.json
Normal file
60
src/data/sidebar-items-adm.json
Normal file
@ -0,0 +1,60 @@
|
||||
[
|
||||
{
|
||||
"name": "Menu",
|
||||
"isTitle": true
|
||||
},
|
||||
|
||||
{
|
||||
"name":"Início",
|
||||
"url": "/admin/",
|
||||
"icon": "house"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Cadastro de Pacientes",
|
||||
"url": "/admin/pacientes/cadastro",
|
||||
"icon": "heart-pulse-fill"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Cadastro do Médico",
|
||||
"url": "/admin/medicos/cadastro",
|
||||
"icon": "capsule"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Lista de Pacientes",
|
||||
"icon": "clipboard-heart-fill",
|
||||
"url": "/admin/pacientes"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Lista de Médico",
|
||||
"icon": "hospital-fill",
|
||||
"url": "/admin/medicos"
|
||||
},
|
||||
{
|
||||
"name": "Agendar consulta",
|
||||
"icon": "calendar-plus-fill",
|
||||
"url": "/admin/agendamento"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Gestão de Usuários",
|
||||
"icon": "person-badge-fill",
|
||||
"url": "/admin/gestao"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Painel Administrativo",
|
||||
"icon": "file-bar-graph-fill",
|
||||
"url": "/admin/painel"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Laudo do Paciente",
|
||||
"icon": "table",
|
||||
"url": "/admin/laudo"
|
||||
}
|
||||
|
||||
]
|
||||
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"
|
||||
}
|
||||
|
||||
]
|
||||
43
src/data/sidebar-items-medico.json
Normal file
43
src/data/sidebar-items-medico.json
Normal file
@ -0,0 +1,43 @@
|
||||
[
|
||||
{
|
||||
"name": "Menu",
|
||||
"isTitle": true
|
||||
},
|
||||
|
||||
{
|
||||
"name":"Início",
|
||||
"url": "/medico/",
|
||||
"icon": "house"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Prontuário",
|
||||
"icon": "calendar-plus-fill",
|
||||
"url": "/medico/prontuario"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Laudos",
|
||||
"icon": "table",
|
||||
"url": "/medico/laudo"
|
||||
},
|
||||
{
|
||||
"name": "Seus Agendamentos",
|
||||
"icon": "calendar",
|
||||
"url": "/medico/agendamentoMedico"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Relatórios",
|
||||
"icon": "file-earmark-text-fill",
|
||||
"url": "/medico/relatorios"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Chat com pacientes",
|
||||
"icon": "chat-dots-fill",
|
||||
"url": "/medico/chat"
|
||||
}
|
||||
|
||||
|
||||
]
|
||||
@ -1,36 +1,35 @@
|
||||
[
|
||||
{
|
||||
"name": "Menu",
|
||||
"isTitle": true
|
||||
},
|
||||
|
||||
{
|
||||
"name":"Início",
|
||||
"url": "/secretaria/inicio",
|
||||
"icon": "house"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Lista de Pacientes",
|
||||
"icon": "clipboard-heart-fill",
|
||||
"url": "/secretaria/pacientes"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Lista de Médico",
|
||||
"icon": "hospital-fill",
|
||||
"url": "/secretaria/medicos"
|
||||
},
|
||||
{
|
||||
"name": "Agendar consulta",
|
||||
"icon": "calendar-plus-fill",
|
||||
"url": "/secretaria/agendamento"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Laudo do Paciente",
|
||||
"icon": "table",
|
||||
"url": "/secretaria/laudo"
|
||||
}
|
||||
|
||||
[
|
||||
{
|
||||
"name": "Menu",
|
||||
"isTitle": true
|
||||
},
|
||||
|
||||
{
|
||||
"name":"Início",
|
||||
"url": "/secretaria/inicio",
|
||||
"icon": "house"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Lista de Pacientes",
|
||||
"icon": "clipboard-heart-fill",
|
||||
"url": "/secretaria/pacientes"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Lista de Médico",
|
||||
"icon": "hospital-fill",
|
||||
"url": "/secretaria/medicos"
|
||||
},
|
||||
{
|
||||
"name": "Agendar consulta",
|
||||
"icon": "calendar-plus-fill",
|
||||
"url": "/secretaria/agendamento"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Laudo do Paciente",
|
||||
"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('/admin/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>
|
||||
|
||||
@ -39,7 +39,7 @@
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin:10px;
|
||||
justify-content: flex-end;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.busca-atendimento select{
|
||||
|
||||
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;
|
||||
}
|
||||
47
src/perfis/Perfil_adm/Perfiladm.jsx
Normal file
47
src/perfis/Perfil_adm/Perfiladm.jsx
Normal file
@ -0,0 +1,47 @@
|
||||
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
|
||||
import Sidebar from "../../components/Sidebar";
|
||||
import Inicio from "../../pages/Inicio";
|
||||
import TablePaciente from "../../pages/TablePaciente";
|
||||
import PatientCadastroManager from "../../pages/PatientCadastroManager";
|
||||
import DoctorCadastroManager from "../../pages/DoctorCadastroManager";
|
||||
import DoctorTable from "../../pages/DoctorTable";
|
||||
import Agendamento from "../../pages/Agendamento";
|
||||
import LaudoManager from "../../pages/LaudoManager";
|
||||
import Details from "../../pages/Details";
|
||||
import EditPage from "../../pages/EditPage";
|
||||
import DoctorDetails from "../../pages/DoctorDetails";
|
||||
import DoctorEditPage from "../../pages/DoctorEditPage";
|
||||
import UserDashboard from '../../PagesAdm/gestao.jsx';
|
||||
import PainelAdministrativo from '../../PagesAdm/painel.jsx';
|
||||
import admItems from "../../data/sidebar-items-adm.json";
|
||||
// ...restante do código...
|
||||
function Perfiladm() {
|
||||
return (
|
||||
|
||||
<div id="app" className="active">
|
||||
<Sidebar menuItems={admItems} />
|
||||
<div id="main">
|
||||
<Routes>
|
||||
<Route path="/" 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="/laudo" element={<LaudoManager />} />
|
||||
<Route path="/gestao" element={<UserDashboard />} />
|
||||
<Route path="/painel" element={<PainelAdministrativo />} />
|
||||
<Route path="*" element={<h2>Página não encontrada</h2>} />
|
||||
</Routes>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
export default Perfiladm;
|
||||
33
src/perfis/Perfil_medico/PerfilMedico.jsx
Normal file
33
src/perfis/Perfil_medico/PerfilMedico.jsx
Normal file
@ -0,0 +1,33 @@
|
||||
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
|
||||
import Sidebar from "../../components/Sidebar";
|
||||
|
||||
import Inicio from "../../pages/Inicio";
|
||||
import LaudoManager from "../../pages/LaudoManager";
|
||||
import Prontuario from "../../PagesMedico/prontuario";
|
||||
import Relatorio from "../../PagesMedico/relatorio";
|
||||
import Agendamento from "../../PagesMedico/Agendamento";
|
||||
import Chat from "../../PagesMedico/Chat";
|
||||
import DoctorItems from "../../data/sidebar-items-medico.json";
|
||||
// ...existing code...
|
||||
|
||||
function PerfilMedico() {
|
||||
return (
|
||||
|
||||
<div id="app" className="active">
|
||||
<Sidebar menuItems={DoctorItems} />
|
||||
<div id="main">
|
||||
<Routes>
|
||||
<Route path="/" element={<Inicio />} />
|
||||
<Route path="/laudo" element={<LaudoManager />} />
|
||||
<Route path="/prontuario" element={<Prontuario />} />
|
||||
<Route path="/relatorios" element={<Relatorio />} />
|
||||
<Route path="/agendamentoMedico" element={<Agendamento />} />
|
||||
<Route path="/chat" element={<Chat />} /> {/* <-- nova rota */}
|
||||
</Routes>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
export default PerfilMedico;
|
||||
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-secretaria.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