primeiro commit

This commit is contained in:
eugabrielnc 2025-09-19 00:26:07 -03:00
commit 6f481b4fda
92 changed files with 17818 additions and 0 deletions

24
.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

12
README.md Normal file
View File

@ -0,0 +1,12 @@
# React + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
## Expanding the ESLint configuration
If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.

29
eslint.config.js Normal file
View File

@ -0,0 +1,29 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import { defineConfig, globalIgnores } from 'eslint/config'
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{js,jsx}'],
extends: [
js.configs.recommended,
reactHooks.configs['recommended-latest'],
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: 'latest',
ecmaFeatures: { jsx: true },
sourceType: 'module',
},
},
rules: {
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
},
},
])

13
index.html Normal file
View File

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>MediConnect</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

4591
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

44
package.json Normal file
View File

@ -0,0 +1,44 @@
{
"name": "medconnect",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@fortawesome/fontawesome-free": "^7.0.0",
"@fullcalendar/core": "^6.1.19",
"@fullcalendar/daygrid": "^6.1.19",
"@fullcalendar/interaction": "^6.1.19",
"@fullcalendar/react": "^6.1.19",
"@fullcalendar/timegrid": "^6.1.19",
"@supabase/supabase-js": "^2.57.0",
"@tiptap/extension-image": "^3.4.2",
"@tiptap/pm": "^3.4.2",
"@tiptap/react": "^3.4.2",
"@tiptap/starter-kit": "^3.4.2",
"bootstrap": "^5.3.8",
"react": "^19.1.1",
"react-bootstrap": "^2.10.10",
"react-dom": "^19.1.1",
"react-icons": "^5.5.0",
"react-router-dom": "^7.8.2",
"recharts": "^3.1.2",
"use-mask-input": "^3.5.0"
},
"devDependencies": {
"@eslint/js": "^9.32.0",
"@types/react": "^19.1.9",
"@types/react-dom": "^19.1.7",
"@vitejs/plugin-react": "^4.7.0",
"eslint": "^9.32.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.20",
"globals": "^16.3.0",
"vite": "^7.1.4"
}
}

BIN
public/img/attachment.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 396 B

BIN
public/img/blog/blog-01.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

BIN
public/img/blog/blog-02.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

BIN
public/img/blog/blog-03.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

BIN
public/img/blog/blog-04.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

BIN
public/img/calander.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 B

BIN
public/img/clock.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
public/img/doctor-03.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
public/img/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

BIN
public/img/logo-dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

BIN
public/img/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

BIN
public/img/logoof.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 523 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 976 B

BIN
public/img/placeholder.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

BIN
public/img/sent.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
public/img/user-02.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

BIN
public/img/user-03.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
public/img/user-04.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
public/img/user-05.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
public/img/user-06.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
public/img/user.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
public/img/video-call.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

1
public/vite.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

3
settings.json Normal file
View File

@ -0,0 +1,3 @@
{
"liveServer.settings.port": 5501
}

17
src/App.jsx Normal file
View File

@ -0,0 +1,17 @@
// src/App.jsx
import './assets/css/index.css'
import Navbar from './components/Navbar'
import Sidebar from './components/Sidebar'
import { Outlet } from 'react-router-dom'
function App() {
return (
<div>
<Navbar />
<Sidebar />
<Outlet />
</div>
)
}
export default App

10
src/Supabase.js Normal file
View File

@ -0,0 +1,10 @@
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = "https://pxhmxgotbfwypaqwpcmh.supabase.co"
const supabaseAnonKey = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InB4aG14Z290YmZ3eXBhcXdwY21oIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTY1NjU2MjAsImV4cCI6MjA3MjE0MTYyMH0.Yu2C0MZ-f4EaFGeJ03YmDtT7m539Q84JfqULqwe2XUI"
const supabase = createClient(supabaseUrl, supabaseAnonKey)
export default supabase

File diff suppressed because one or more lines are too long

7
src/assets/css/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4
src/assets/css/font-awesome.min.css vendored Normal file

File diff suppressed because one or more lines are too long

5
src/assets/css/fullcalendar.min.css vendored Normal file

File diff suppressed because one or more lines are too long

152
src/assets/css/index.css Normal file
View File

@ -0,0 +1,152 @@
/* Bootstrap */
@import './bootstrap-datetimepicker.min.css';
@import './bootstrap.min.css';
/* DataTables */
@import './dataTables.bootstrap4.min.css';
/* Font Awesome */
@import './font-awesome.min.css';
/* FullCalendar */
@import './fullcalendar.min.css';
/* Select2 */
@import './select2.min.css';
/* Tags Input */
@import './tagsinput.css';
/* Estilos próprios */
@import './style.css';
@import './tiptap.css';
/* Estilos para cards de eventos */
.event-card {
padding: 6px 10px;
border-radius: 8px;
color: white;
font-size: 0.9em;
margin: 2px 0;
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
font-weight: 500;
}
.event-card .event-type {
font-size: 0.75em;
background: rgba(255, 255, 255, 0.2);
padding: 2px 6px;
border-radius: 12px;
}
.event-card .event-time {
font-weight: bold;
margin-right: 6px;
}
.event-card .event-title {
flex-grow: 1;
margin-left: 6px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Estilos para dropdown menus */
.dropdown-menu.dropdown-menu-right.show {
min-width: 120px;
max-width: 220px;
width: max-content;
padding: 5px 0;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
background-color: #fff;
border: 1px solid #ddd;
position: absolute;
right: 0;
z-index: 100;
}
.dropdown-item-custom {
display: flex;
align-items: center;
padding: 8px 12px;
font-weight: 500;
color: #007bff;
text-decoration: none;
width: 100%;
cursor: pointer;
transition: background 0.2s;
}
.dropdown-item-custom:hover {
background-color: #f0f0f0;
}
.dropdown-item-delete {
display: flex;
align-items: center;
padding: 8px 12px;
font-weight: 500;
color: #dc3545;
width: 100%;
border: none;
background: none;
cursor: pointer;
transition: background 0.2s;
}
.dropdown-item-delete:hover {
background-color: #f8d7da;
}
.dropdown-item-custom i,
.dropdown-item-delete i {
margin-right: 5px;
}
.action-icon {
background: transparent;
border: none;
cursor: pointer;
font-size: 18px;
}
.quick-filter {
display: flex;
gap: 0.25rem;
}
.btn-filter {
padding: 0.15rem 0.3rem;
font-size: 0.7rem;
min-width: 35px;
border-radius: 4px;
border: 1px solid #ccc;
cursor: pointer;
}
.btn-filter.active {
background-color: #007bff;
color: white;
border-color: #007bff;
}
.date-filter {
display: flex;
align-items: center;
gap: 0.25rem;
}
.date-filter label {
font-size: 0.7rem;
}
.date-filter input[type="date"] {
padding: 0.15rem 0.25rem;
font-size: 0.7rem;
min-width: 80px;
}

1
src/assets/css/select2.min.css vendored Normal file

File diff suppressed because one or more lines are too long

4915
src/assets/css/style.css Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,66 @@
/*
* bootstrap-tagsinput v0.8.0
*
*/
.bootstrap-tagsinput {
background-color: #fff;
border: 1px solid #ccc;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
display: inline-block;
padding: 4px 6px;
color: #555;
vertical-align: middle;
border-radius: 4px;
width: 100%;
line-height: 22px;
cursor: text;
}
.bootstrap-tagsinput input {
border: none;
box-shadow: none;
outline: none;
background-color: transparent;
padding: 0 6px;
margin: 0;
width: auto;
max-width: inherit;
}
.bootstrap-tagsinput.form-control input::-moz-placeholder {
color: #777;
opacity: 1;
}
.bootstrap-tagsinput.form-control input:-ms-input-placeholder {
color: #777;
}
.bootstrap-tagsinput.form-control input::-webkit-input-placeholder {
color: #777;
}
.bootstrap-tagsinput input:focus {
border: none;
box-shadow: none;
}
.bootstrap-tagsinput .badge {
margin-right: 2px;
color: white;
background-color:#0275d8;
padding:5px 8px;border-radius:3px;
border:1px solid #01649e
}
.bootstrap-tagsinput .badge [data-role="remove"] {
margin-left: 8px;
cursor: pointer;
}
.bootstrap-tagsinput .badge [data-role="remove"]:after {
content: "×";
padding: 0px 4px;
background-color:rgba(0, 0, 0, 0.1);
border-radius:50%;
font-size:13px
}
.bootstrap-tagsinput .badge [data-role="remove"]:hover:after {
background-color:rgba(0, 0, 0, 0.62);}
.bootstrap-tagsinput .badge [data-role="remove"]:hover:active {
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
}

84
src/assets/css/tiptap.css Normal file
View File

@ -0,0 +1,84 @@
.tiptap {
width: 80vw;
max-width: 1000px;
height: 90vh;
overflow: auto;
margin: 40px auto;
margin-top: 30px;
padding: 20px;
background-color: white;
min-height: 50vh;
z-index: 0;
p {
color: black;
padding: 8px;
font-size: 20px;
border-radius: 4px;
}
ul,
li {
margin-left: 20px;
}
img {
max-width: 50%;
border-radius: 5px;
}
}
.toolbar {
background: rgb(243, 241, 241);
position: flex;
padding-top: 30px;
z-index: 1;
width: 100%;
display: flex;
justify-content: space-between;
padding: 10px;
box-shadow: rgba(99, 99, 99, 0.2) 0px 2px 8px 0px;
border-radius: 10px;
.left,
.right {
display: flex;
gap: 4px;
button {
background: none;
color: black;
border: none;
cursor: pointer;
padding: 10px;
border-radius: 5px;
display: flex;
align-items: center;
svg {
width: 16px;
}
&:hover {
background: #c4e3ff;
}
&.active {
background: #3b82f6;
color: blue;
border-color: #3b82f6;
}
}
.btnGuardar {
background: #0e8eff;
color: #fff;
&:hover {
background: #007ae6;
}
}
}
}

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

82
src/components/Bar.jsx Normal file
View File

@ -0,0 +1,82 @@
import { Link } from 'react-router-dom';
import { useState } from 'react';
function Bar({ comandos }) {
return (
<>
<div className="toolbar">
<div className="left">
<button onClick={comandos.toggleBold} >
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M8 11H12.5C13.8807 11 15 9.88071 15 8.5C15 7.11929 13.8807 6 12.5 6H8V11ZM18 15.5C18 17.9853 15.9853 20 13.5 20H6V4H12.5C14.9853 4 17 6.01472 17 8.5C17 9.70431 16.5269 10.7981 15.7564 11.6058C17.0979 12.3847 18 13.837 18 15.5ZM8 13V18H13.5C14.8807 18 16 16.8807 16 15.5C16 14.1193 14.8807 13 13.5 13H8Z"></path>
</svg>
</button>
<button onClick={comandos.toggleItalic} >
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M15 20H7V18H9.92661L12.0425 6H9V4H17V6H14.0734L11.9575 18H15V20Z"></path>
</svg>
</button>
<button onClick={comandos.toggleUnderline} >
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M8 3V12C8 14.2091 9.79086 16 12 16C14.2091 16 16 14.2091 16 12V3H18V12C18 15.3137 15.3137 18 12 18C8.68629 18 6 15.3137 6 12V3H8ZM4 20H20V22H4V20Z"></path>
</svg>
</button>
{/*<button onClick={comandos.toggleCodeBlock} >
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M23 12L15.9289 19.0711L14.5147 17.6569L20.1716 12L14.5147 6.34317L15.9289 4.92896L23 12ZM3.82843 12L9.48528 17.6569L8.07107 19.0711L1 12L8.07107 4.92896L9.48528 6.34317L3.82843 12Z"></path>
</svg>
</button> */}
<button onClick={comandos.toggleH1} >
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M13 20H11V13H4V20H2V4H4V11H11V4H13V20ZM21.0005 8V20H19.0005L19 10.204L17 10.74V8.67L19.5005 8H21.0005Z"></path>
</svg>
</button>
<button onClick={comandos.toggleH2} >
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M4 4V11H11V4H13V20H11V13H4V20H2V4H4ZM18.5 8C20.5711 8 22.25 9.67893 22.25 11.75C22.25 12.6074 21.9623 13.3976 21.4781 14.0292L21.3302 14.2102L18.0343 18H22V20H15L14.9993 18.444L19.8207 12.8981C20.0881 12.5908 20.25 12.1893 20.25 11.75C20.25 10.7835 19.4665 10 18.5 10C17.5818 10 16.8288 10.7071 16.7558 11.6065L16.75 11.75H14.75C14.75 9.67893 16.4289 8 18.5 8Z"></path>
</svg>
</button>
<button onClick={comandos.toggleH3} >
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M22 8L21.9984 10L19.4934 12.883C21.0823 13.3184 22.25 14.7728 22.25 16.5C22.25 18.5711 20.5711 20.25 18.5 20.25C16.674 20.25 15.1528 18.9449 14.8184 17.2166L16.7821 16.8352C16.9384 17.6413 17.6481 18.25 18.5 18.25C19.4665 18.25 20.25 17.4665 20.25 16.5C20.25 15.5335 19.4665 14.75 18.5 14.75C18.214 14.75 17.944 14.8186 17.7056 14.9403L16.3992 13.3932L19.3484 10H15V8H22ZM4 4V11H11V4H13V20H11V13H4V20H2V4H4Z"></path>
</svg>
</button>
<button onClick={comandos.toggleParrafo} >
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 6V21H10V16C6.68629 16 4 13.3137 4 10C4 6.68629 6.68629 4 10 4H20V6H17V21H15V6H12ZM10 6C7.79086 6 6 7.79086 6 10C6 12.2091 7.79086 14 10 14V6Z"></path>
</svg>
</button>
<button onClick={comandos.toggleListaOrdenada} >
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M8 4H21V6H8V4ZM5 3V6H6V7H3V6H4V4H3V3H5ZM3 14V11.5H5V11H3V10H6V12.5H4V13H6V14H3ZM5 19.5H3V18.5H5V18H3V17H6V21H3V20H5V19.5ZM8 11H21V13H8V11ZM8 18H21V20H8V18Z"></path>
</svg>
</button>
<button onClick={comandos.toggleListaPuntos} >
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M8 4H21V6H8V4ZM4.5 6.5C3.67157 6.5 3 5.82843 3 5C3 4.17157 3.67157 3.5 4.5 3.5C5.32843 3.5 6 4.17157 6 5C6 5.82843 5.32843 6.5 4.5 6.5ZM4.5 13.5C3.67157 13.5 3 12.8284 3 12C3 11.1716 3.67157 10.5 4.5 10.5C5.32843 10.5 6 11.1716 6 12C6 12.8284 5.32843 13.5 4.5 13.5ZM4.5 20.4C3.67157 20.4 3 19.7284 3 18.9C3 18.0716 3.67157 17.4 4.5 17.4C5.32843 17.4 6 18.0716 6 18.9C6 19.7284 5.32843 20.4 4.5 20.4ZM8 11H21V13H8V11ZM8 18H21V20H8V18Z"></path>
</svg>
</button>
<button onClick={comandos.agregarImagen} >
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M2.9918 21C2.44405 21 2 20.5551 2 20.0066V3.9934C2 3.44476 2.45531 3 2.9918 3H21.0082C21.556 3 22 3.44495 22 3.9934V20.0066C22 20.5552 21.5447 21 21.0082 21H2.9918ZM20 15V5H4V19L14 9L20 15ZM20 17.8284L14 11.8284L6.82843 19H20V17.8284ZM8 11C6.89543 11 6 10.1046 6 9C6 7.89543 6.89543 7 8 7C9.10457 7 10 7.89543 10 9C10 10.1046 9.10457 11 8 11Z"></path>
</svg>
</button>
{/*<button onClick={comandos.agregarLink} >
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M18.3638 15.5355L16.9496 14.1213L18.3638 12.7071C20.3164 10.7545 20.3164 7.58866 18.3638 5.63604C16.4112 3.68341 13.2453 3.68341 11.2927 5.63604L9.87849 7.05025L8.46428 5.63604L9.87849 4.22182C12.6122 1.48815 17.0443 1.48815 19.778 4.22182C22.5117 6.95549 22.5117 11.3876 19.778 14.1213L18.3638 15.5355ZM15.5353 18.364L14.1211 19.7782C11.3875 22.5118 6.95531 22.5118 4.22164 19.7782C1.48797 17.0445 1.48797 12.6123 4.22164 9.87868L5.63585 8.46446L7.05007 9.87868L5.63585 11.2929C3.68323 13.2455 3.68323 16.4113 5.63585 18.364C7.58847 20.3166 10.7543 20.3166 12.7069 18.364L14.1211 16.9497L15.5353 18.364ZM14.8282 7.75736L16.2425 9.17157L9.17139 16.2426L7.75717 14.8284L14.8282 7.75736Z"></path>
</svg>
</button> */}
</div>
<div className="right">
<Link to="/laudolist"><button onClick={comandos.guardarContenido} className="btnGuardar">
<span>Enviar laudo</span>
</button></Link>
</div>
</div>
</>
);
};
export default Bar;

117
src/components/Navbar.jsx Normal file
View File

@ -0,0 +1,117 @@
// src/components/Navbar.jsx
import "../assets/css/index.css";
import { Link, useLocation, useNavigate } from "react-router-dom";
import { useEffect, useRef, useState } from "react";
function Navbar() {
const location = useLocation();
const navigate = useNavigate();
const [openNotif, setOpenNotif] = useState(false);
const [openProfile, setOpenProfile] = useState(false);
const notifRef = useRef(null);
const profileRef = useRef(null);
const isDoctor = location.pathname.startsWith("/doctor");
const profileName = isDoctor ? "Médico" : "Admin";
// Fecha dropdowns ao clicar fora
useEffect(() => {
function handleClickOutside(e) {
if (notifRef.current && !notifRef.current.contains(e.target)) {
setOpenNotif(false);
}
if (profileRef.current && !profileRef.current.contains(e.target)) {
setOpenProfile(false);
}
}
document.addEventListener("click", handleClickOutside);
return () => document.removeEventListener("click", handleClickOutside);
}, []);
const goToOtherRole = () => {
if (isDoctor) navigate("/");
else navigate("/doctor");
setOpenProfile(false);
};
return (
<div className="header">
<div className="header-left">
{/* Logo dinâmica */}
<Link to={isDoctor ? "/doctor" : "/"} className="logo">
<img src="/img/logomedconnect.png" width="35" height="35" alt="" />{" "}
<span>MediConnect</span>
</Link>
</div>
<a id="mobile_btn" className="mobile_btn float-left" href="#sidebar">
<i className="fa fa-bars"></i>
</a>
<ul className="nav user-menu float-right">
{/* 🔔 Notificações */}
<li className="nav-item dropdown d-none d-sm-block" ref={notifRef}>
<a
href="#!"
className="dropdown-toggle nav-link"
onClick={(e) => {
e.preventDefault();
setOpenNotif((v) => !v);
setOpenProfile(false);
}}
>
<i className="fa fa-bell-o"></i>
<span className="badge badge-pill bg-danger float-right">2</span>
</a>
<div className={`dropdown-menu notifications${openNotif ? " show" : ""}`}>
<div className="topnav-dropdown-header">
<span>Cadastrado</span>
</div>
{/* Aqui você pode listar notificações reais */}
<div className="topnav-dropdown-footer">
<a href="#!">Mensagem</a>
</div>
</div>
</li>
{/* 👤 Perfil */}
<li className="nav-item dropdown has-arrow" ref={profileRef}>
<a
href="#!"
className="dropdown-toggle nav-link user-link"
onClick={(e) => {
e.preventDefault();
setOpenProfile((v) => !v);
setOpenNotif(false);
}}
>
<span className="user-img">
<span className="status online"></span>
</span>
<span>{profileName}</span>
<i className=""></i>
</a>
<div className={`dropdown-menu${openProfile ? " show" : ""}`}>
{/* Opções padrão */}
<a className="dropdown-item" href="#!">
Paciente
</a>
<div className="dropdown-divider"></div>
{/* Troca de perfil */}
<button className="dropdown-item" onClick={goToOtherRole}>
{isDoctor ? "Admin" : "Médico"}
</button>
</div>
</li>
</ul>
</div>
);
}
export default Navbar;

View File

@ -0,0 +1,68 @@
import '../assets/css/index.css'
import { Link } from 'react-router-dom';
function Sidebar() {
return (
<div>
<div className="sidebar" id="sidebar">
<div className="sidebar-inner slimscroll">
<div id="sidebar-menu" className="sidebar-menu">
<ul>
<li className="menu-title">Main</li>
{/*<li>
<a href="index-2.html">
<i className="fa fa-dashboard" /> <span>Dashboard</span>
</a>
</li>*/}
<li>
<Link to="/doctorlist">
<i className="fa fa-user-md" /> <span>Médicos</span>
</Link>
</li>
<li>
<Link to="/patientlist">
<i className="fa fa-wheelchair" /> <span>Pacientes</span>
</Link>
</li>
<li>
<Link to="/calendar">
<i className="fa fa-calendar" /> <span>Calendario</span>
</Link>
</li>
<li>
<Link to="/doctorschedule">
<i className="fa fa-calendar-check-o" /> <span>Agenda Médica</span>
</Link>
</li>
<li>
<Link to="/agendalist">
<i className="fa fa-stethoscope" /> <span>Consultas</span>
</Link>
</li>
{/* 🆕 Nova aba Laudo */}
<li>
<Link to="/laudolist">
<i className="fa fa-file-text" /> <span>Laudos</span>
</Link>
</li>
{/*<li>
<a href="settings.html">
<i className="fa fa-cog" /> <span>Configurações</span>
</a>
</li>*/}
</ul>
</div>
</div>
</div>
</div>
);
}
export default Sidebar;

81
src/main.jsx Normal file
View File

@ -0,0 +1,81 @@
// src/main.jsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import "./assets/css/index.css";
// Layouts
import App from "./App.jsx"; // Layout Admin
import DoctorApp from "./pages/DoctorApp/DoctorApp.jsx"; // Layout Médico
// Páginas Admin
import Patientform from "./pages/Patient/Patientform.jsx";
import PatientList from "./pages/Patient/PatientList.jsx";
import Doctorlist from "./pages/Doctor/DoctorList.jsx";
import DoctorForm from "./pages/Doctor/DoctorForm.jsx";
import Doctorschedule from "./pages/Schedule/DoctorSchedule.jsx";
import AddSchedule from "./pages/Schedule/AddSchedule.jsx";
import Calendar from "./pages/calendar/Calendar.jsx";
import EditDoctor from "./pages/Doctor/DoctorEdit.jsx";
import PatientEdit from "./pages/Patient/PatientEdit.jsx";
import DoctorProfile from "./pages/Doctor/DoctorProfile.jsx";
// Páginas Médico
import DoctorDashboard from "./pages/DoctorApp/DoctorDashboard.jsx";
import DoctorCalendar from "./pages/DoctorApp/DoctorCalendar.jsx";
import DoctorPatientList from "./pages/DoctorApp/DoctorPatientList.jsx";
import AgendaList from "./pages/Agendar/AgendaList.jsx";
import AgendaForm from "./pages/Agendar/AgendaForm.jsx";
import AgendaEdit from "./pages/Agendar/AgendaEdit.jsx";
import LaudoList from "./pages/laudos/LaudosList.jsx"
import Laudo from "./pages/laudos/Laudo.jsx";
// Criando o router com todas as rotas
const router = createBrowserRouter([
// Rotas Admin
{
path: "/",
element: <App />,
children: [
// Rota inicial do Admin: apenas mostra layout com Navbar e Sidebar
{ path: "patient", element: <Patientform /> },
{ path: "patientlist", element: <PatientList /> },
{ path: "doctorlist", element: <Doctorlist /> },
{ path: "doctorform", element: <DoctorForm /> },
{ path: "doctorschedule", element: <Doctorschedule /> },
{ path: "addschedule", element: <AddSchedule /> },
{ path: "calendar", element: <Calendar /> },
{ path: "profiledoctor/:id", element: <DoctorProfile /> },
{ path: "editdoctor/:id", element: <EditDoctor /> },
{ path: "editpatient/:id", element: <PatientEdit /> },
{ path: "agendaform", element: <AgendaForm />},
{ path: "agendaedit", element: <AgendaEdit />},
{ path: "agendalist", element: <AgendaList />},
{ path: "laudolist", element: <LaudoList /> },
{ path: "laudo", element: <Laudo />}
],
},
// Rotas Médico
{
path: "/doctor",
element: <DoctorApp />,
children: [
{ index: true, element: <DoctorDashboard /> }, // Rota inicial médico
{ path: "dashboard", element: <DoctorDashboard /> },
{ path: "calendar", element: <DoctorCalendar /> },
{ path: "patients", element: <DoctorPatientList /> },
],
},
]);
// Renderizando a aplicação
createRoot(document.getElementById("root")).render(
<StrictMode>
<RouterProvider router={router} />
</StrictMode>
);

View File

@ -0,0 +1,213 @@
import { useState, useEffect } from "react";
import { withMask } from "use-mask-input";
import { Link } from "react-router-dom";
import "../../assets/css/index.css";
function AgendaEdit() {
const [minDate, setMinDate] = useState("");
useEffect(() => {
const getToday = () => {
const today = new Date();
const offset = today.getTimezoneOffset();
today.setMinutes(today.getMinutes() - offset);
return today.toISOString().split("T")[0];
};
setMinDate(getToday());
}, []);
return (
<div className="main-wrapper">
<div className="page-wrapper">
<div className="content">
<div className="row">
<div className="col-lg-8 offset-lg-2">
<h1>Editar consulta</h1>
<hr />
<h3>Informações do paciente</h3>
</div>
</div>
<div className="row">
<div className="col-lg-8 offset-lg-2">
<form>
<div className="row">
<div className="col-md-6">
<div className="form-group">
<label>ID da consulta</label>
<input
className="form-control"
type="text"
value="APT-0001"
readOnly
/>
</div>
</div>
<div className="col-md-6">
<div className="form-group">
<label>Nome do paciente<span className="text-danger">*</span></label>
<input type="text" className="form-control" />
</div>
</div>
<div className="col-md-6">
<div className="form-group">
<label>RG</label>
<input type="text" className="form-control" />
</div>
</div>
<div className="col-md-6">
<div className="form-group">
<label>CPF<span className="text-danger">*</span></label>
<input type="text" className="form-control" />
</div>
</div>
<div className="col-md-6">
<div className="form-group">
<label>Email do paciente</label>
<input className="form-control" type="email" />
</div>
</div>
<div className="col-md-6">
<div className="form-group">
<label>Número de telefone do paciente<span className="text-danger">*</span></label>
<input className="form-control" type="text" ref={withMask('+55 (99) 99999-9999')} />
</div>
</div>
<div className="col-md-6">
<div className="form-group">
<label>Data de nascimento<span className="text-danger">*</span></label>
<input className="form-control" type="date" />
</div>
</div>
<div className="form-group gender-select col-md-6">
<label className="gen-label">Sexo:<span className="text-danger">*</span></label>
<div className="form-check-inline">
<label className="form-check-label">
<input type="radio" name="sexo" className="form-check-input"
/> Masculino
</label>
</div>
<div className="form-check-inline">
<label className="form-check-label">
<input type="radio" name="sexo" className="form-check-input"
/> Feminino
</label>
</div>
<div className="form-check-inline">
<label className="form-check-label">
<input type="radio" name="sexo" className="form-check-input"
/> Outro
</label>
</div>
</div>
</div>
<hr />
<h3>Informações do atendimento</h3>
<div className="row">
<div className="col-md-6">
<div className="form-group">
<label>Especialidade<span className="text-danger">*</span></label>
<select className="select form-control">
<option>Selecione</option>
<option>Cardiologia</option>
<option>Pediatria</option>
<option>Dermatologia</option>
<option>Ginecologia</option>
<option>Neurologia</option>
<option>Psiquiatria</option>
<option>Ortopedia</option>
</select>
</div>
</div>
<div className="col-md-6">
<div className="form-group">
<label>Médico<span className="text-danger">*</span></label>
<select className="select form-control">
<option>Selecione</option>
<option>Davi Andrade</option>
<option>Caio Pereira</option>
<option>Paulo Lucas</option>
</select>
</div>
</div>
</div>
<div className="row">
<div className="col-md-6">
<div className="form-group">
<label>Data<span className="text-danger">*</span></label>
<div>
<input
type="date"
className="form-control"
min={minDate}
/>
</div>
</div>
</div>
<div className="col-md-6">
<div className="form-group">
<label>Horas<span className="text-danger">*</span></label>
<div>
<input type="time" className="form-control" />
</div>
</div>
</div>
</div>
<div className="form-group">
<label>Observação</label>
<textarea cols="30" rows="4" className="form-control"></textarea>
</div>
<div className="form-group">
<label className="display-block">Status da consulta</label>
<div className="form-check form-check-inline">
<input
className="form-check-input"
type="radio"
name="status"
id="product_active"
value="option1"
defaultChecked
/>
<label
className="form-check-label"
htmlFor="product_active"
>
Ativo
</label>
</div>
<div className="form-check form-check-inline">
<input
className="form-check-input"
type="radio"
name="status"
id="product_inactive"
value="option2"
/>
<label
className="form-check-label"
htmlFor="product_inactive"
>
Inativo
</label>
</div>
</div>
<div className="m-t-20 text-center">
<Link to="/agendalist">
<button className="btn btn-primary submit-btn" type="button">
Salvar
</button>
</Link>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
);
}
export default AgendaEdit;

View File

@ -0,0 +1,213 @@
import { useState, useEffect } from "react";
import { withMask } from "use-mask-input";
import { Link } from "react-router-dom";
import "../../assets/css/index.css";
function AgendaForm() {
const [minDate, setMinDate] = useState("");
useEffect(() => {
const getToday = () => {
const today = new Date();
const offset = today.getTimezoneOffset();
today.setMinutes(today.getMinutes() - offset);
return today.toISOString().split("T")[0];
};
setMinDate(getToday());
}, []);
return (
<div className="main-wrapper">
<div className="page-wrapper">
<div className="content">
<div className="row">
<div className="col-lg-8 offset-lg-2">
<h1>Nova consulta</h1>
<hr />
<h3>Informações do paciente</h3>
</div>
</div>
<div className="row">
<div className="col-lg-8 offset-lg-2">
<form>
<div className="row">
<div className="col-md-6">
<div className="form-group">
<label>ID da consulta</label>
<input
className="form-control"
type="text"
value="APT-0001"
readOnly
/>
</div>
</div>
<div className="col-md-6">
<div className="form-group">
<label>Nome do paciente<span className="text-danger">*</span></label>
<input type="text" className="form-control" />
</div>
</div>
<div className="col-md-6">
<div className="form-group">
<label>RG</label>
<input type="text" className="form-control" />
</div>
</div>
<div className="col-md-6">
<div className="form-group">
<label>CPF<span className="text-danger">*</span></label>
<input type="text" className="form-control" />
</div>
</div>
<div className="col-md-6">
<div className="form-group">
<label>Email do paciente</label>
<input className="form-control" type="email" />
</div>
</div>
<div className="col-md-6">
<div className="form-group">
<label>Número de telefone do paciente<span className="text-danger">*</span></label>
<input className="form-control" type="text" ref={withMask('+55 (99) 99999-9999')} />
</div>
</div>
<div className="col-md-6">
<div className="form-group">
<label>Data de nascimento<span className="text-danger">*</span></label>
<input className="form-control" type="date" />
</div>
</div>
<div className="form-group gender-select col-md-6">
<label className="gen-label">Sexo:<span className="text-danger">*</span></label>
<div className="form-check-inline">
<label className="form-check-label">
<input type="radio" name="sexo" className="form-check-input"
/> Masculino
</label>
</div>
<div className="form-check-inline">
<label className="form-check-label">
<input type="radio" name="sexo" className="form-check-input"
/> Feminino
</label>
</div>
<div className="form-check-inline">
<label className="form-check-label">
<input type="radio" name="sexo" className="form-check-input"
/> Outro
</label>
</div>
</div>
</div>
<hr />
<h3>Informações do atendimento</h3>
<div className="row">
<div className="col-md-6">
<div className="form-group">
<label>Especialidade<span className="text-danger">*</span></label>
<select className="select form-control">
<option>Selecione</option>
<option>Cardiologia</option>
<option>Pediatria</option>
<option>Dermatologia</option>
<option>Ginecologia</option>
<option>Neurologia</option>
<option>Psiquiatria</option>
<option>Ortopedia</option>
</select>
</div>
</div>
<div className="col-md-6">
<div className="form-group">
<label>Médico<span className="text-danger">*</span></label>
<select className="select form-control">
<option>Selecione</option>
<option>Davi Andrade</option>
<option>Caio Pereira</option>
<option>Paulo Lucas</option>
</select>
</div>
</div>
</div>
<div className="row">
<div className="col-md-6">
<div className="form-group">
<label>Data<span className="text-danger">*</span></label>
<div>
<input
type="date"
className="form-control"
min={minDate}
/>
</div>
</div>
</div>
<div className="col-md-6">
<div className="form-group">
<label>Horas<span className="text-danger">*</span></label>
<div>
<input type="time" className="form-control" />
</div>
</div>
</div>
</div>
<div className="form-group">
<label>Observação</label>
<textarea cols="30" rows="4" className="form-control"></textarea>
</div>
<div className="form-group">
<label className="display-block">Status da consulta</label>
<div className="form-check form-check-inline">
<input
className="form-check-input"
type="radio"
name="status"
id="product_active"
value="option1"
defaultChecked
/>
<label
className="form-check-label"
htmlFor="product_active"
>
Ativo
</label>
</div>
<div className="form-check form-check-inline">
<input
className="form-check-input"
type="radio"
name="status"
id="product_inactive"
value="option2"
/>
<label
className="form-check-label"
htmlFor="product_inactive"
>
Inativo
</label>
</div>
</div>
<div className="m-t-20 text-center">
<Link to="/agendalist">
<button className="btn btn-primary submit-btn" type="button">
Criar consulta
</button>
</Link>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
);
}
export default AgendaForm;

View File

@ -0,0 +1,231 @@
import "../../assets/css/index.css"
import { Link } from "react-router-dom";
import { useState, useEffect, useRef, useLayoutEffect } from "react";
import { createPortal } from "react-dom";
function DropdownPortal({ anchorEl, isOpen, onClose, className, children }) {
const menuRef = useRef(null);
const [stylePos, setStylePos] = useState({
position: "absolute",
top: 0,
left: 0,
visibility: "hidden",
zIndex: 1000,
});
// Posiciona o menu após renderar (medir tamanho do menu)
useLayoutEffect(() => {
if (!isOpen) return;
if (!anchorEl || !menuRef.current) return;
const anchorRect = anchorEl.getBoundingClientRect();
const menuRect = menuRef.current.getBoundingClientRect();
const scrollY = window.scrollY || window.pageYOffset;
const scrollX = window.scrollX || window.pageXOffset;
// tenta alinhar à direita do botão (como dropdown-menu-right)
let left = anchorRect.right + scrollX - menuRect.width;
let top = anchorRect.bottom + scrollY;
// evita sair da esquerda da tela
if (left < 0) left = scrollX + 4;
// se extrapolar bottom, abre para cima
if (top + menuRect.height > window.innerHeight + scrollY) {
top = anchorRect.top + scrollY - menuRect.height;
}
setStylePos({
position: "absolute",
top: `${Math.round(top)}px`,
left: `${Math.round(left)}px`,
visibility: "visible",
zIndex: 1000,
});
}, [isOpen, anchorEl, children]);
// fecha ao clicar fora / ao rolar
useEffect(() => {
if (!isOpen) return;
function handleDocClick(e) {
const menu = menuRef.current;
if (menu && !menu.contains(e.target) && anchorEl && !anchorEl.contains(e.target)) {
onClose();
}
}
function handleScroll() {
onClose();
}
document.addEventListener("mousedown", handleDocClick);
// captura scroll em qualquer elemento (true)
document.addEventListener("scroll", handleScroll, true);
return () => {
document.removeEventListener("mousedown", handleDocClick);
document.removeEventListener("scroll", handleScroll, true);
};
}, [isOpen, onClose, anchorEl]);
if (!isOpen) return null;
return createPortal(
<div
ref={menuRef}
className={className} // mantém as classes que você já usa no CSS
style={stylePos}
onClick={(e) => e.stopPropagation()}
>
{children}
</div>,
document.body
);
}
function AgendaList() {
const [openDropdown, setOpenDropdown] = useState(null);
const anchorRefs = useRef({});
return (
<div className="main-wrapper">
<div className="page-wrapper">
<div className="content">
<div className="row">
<div className="col-sm-4 col-3">
<h4 className="page-title">Lista de consultas</h4>
<input
type="text"
className="form-control"
placeholder="🔍 Buscar consulta"
style={{ minWidth: "200px" }}
/>
<br />
</div>
<div className="col-sm-8 col-9 text-right m-b-20">
<Link to="/agendaform" className="btn btn-primary btn-rounded">
<i className="fa fa-plus"></i> Adicionar consulta
</Link>
</div>
</div>
<div className="row">
<div className="col-md-12">
<div className="table-responsive">
<table className="table table-striped custom-table">
<thead>
<tr>
<th>ID da cosulta</th>
<th>Nome do Paciente</th>
<th>Idade</th>
<th>Nome do médico</th>
<th>Especialidade</th>
<th>Data da consulta</th>
<th>Hora da consulta</th>
<th>Status</th>
<th className="text-right">Ação</th>
</tr>
</thead>
<tbody>
<tr>
<td>APT0001</td>
<td>João Miguel</td>
<td>18</td>
<td>Davi Andrade</td>
<td>Cardiologista</td>
<td>25 Set 2025</td>
<td>10:00am - 11:00am</td>
<td>
<span className="custom-badge status-green">
Ativo
</span>
</td>
<td className="text-right">
<div className="dropdown dropdown-action" style={{ display: "inline-block" }}>
<button
type="button"
ref={(el) => (anchorRefs.current["menu"] = el)}
className="action-icon"
onClick={(e) => {
e.stopPropagation();
setOpenDropdown(openDropdown === "menu" ? null : "menu");
}}
>
<i className="fa fa-ellipsis-v"></i>
</button>
<DropdownPortal
anchorEl={anchorRefs.current["menu"]}
isOpen={openDropdown === "menu"}
onClose={() => setOpenDropdown(null)}
className="dropdown-menu dropdown-menu-right show"
>
{/*<Link
className="dropdown-item-custom"
to={`/profilepatient`}
onClick={(e) => {
e.stopPropagation();
setOpenDropdown(null);
}}
>
<i className="fa fa-eye"></i> Ver Detalhes
</Link>*/}
<Link
className="dropdown-item-custom"
to={`/agendaedit`}
onClick={(e) => {
e.stopPropagation();
setOpenDropdown(null);
}}
>
<i className="fa fa-pencil m-r-5"></i> Editar
</Link>
<button
className="dropdown-item-custom dropdown-item-delete"
onClick={() => handleDelete()}
>
<i className="fa fa-trash-o m-r-5"></i> Excluir
</button>
</DropdownPortal>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
{/* Modal delete */}
<div
id="delete_appointment"
className="modal fade delete-modal"
role="dialog"
>
<div className="modal-dialog modal-dialog-centered">
<div className="modal-content">
<div className="modal-body text-center">
<img src="assets/img/sent.png" alt="" width="50" height="46" />
<h3>Are you sure want to delete this Appointment?</h3>
<div className="m-t-20">
<a
href="#"
className="btn btn-white"
data-dismiss="modal"
>
Close
</a>
<button type="submit" className="btn btn-danger">
Delete
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
export default AgendaList;

View File

@ -0,0 +1,422 @@
import "../../assets/css/index.css"
import { withMask } from "use-mask-input";
import { useState } from "react";
import supabase from "../../Supabase"
import { Link } from "react-router-dom";
import { useParams } from "react-router-dom";
import { useEffect } from "react";
function EditDoctor() {
const [doctors, setdoctors] = useState([]);
const {id} = useParams()
useEffect(() => {
const fetchDoctors = async () => {
const {data, error} = await supabase
.from('Doctor')
.select('*')
.eq ('id', id)
.single()
if(error){
console.error("Erro ao buscar pacientes:", error);
}else{
setdoctors(data);
}
};
fetchDoctors();
} , []);
const handleChange = (e) => {
const { name, value } = e.target;
setdoctors((prev) => ({
...prev,
[name]: value
}));
}
const handleEdit = async (e) => {
const { data, error } = await supabase
.from("Doctor")
.update([doctors])
.eq ('id', id)
.single()
};
const buscarCep = (e) => {
const cep = doctors.cep.replace(/\D/g, '');
console.log(cep);
fetch(`https://viacep.com.br/ws/${cep}/json/`)
.then(response => response.json())
.then(data => {
console.log(data)
// salvando os valores para depois colocar nos inputs
setValuesFromCep(data)
// estou salvando os valoeres no patientData
setdoctors((prev) => ({
...prev,
cidade: data.localidade || '',
estado: data.estado || '',
logradouro: data.logradouro || "",
bairro: data.bairro || '',
}));
})
}
const setValuesFromCep = (data) => {
document.getElementById('cidade').value = data.localidade || '';
document.getElementById('estado').value = data.uf || '';
document.getElementById('logradouro').value= data.logradouro || '';
document.getElementById('bairro').value= data.bairro || '';
}
return (
<div className="main-wrapper">
{/* FORMULÁRIO*/}
<div className="page-wrapper">
<div className="content">
<div className="row">
<div className="col-lg-8 offset-lg-2">
<h4 className="page-title">Editar Médico</h4>
</div>
</div>
<div className="row">
<div className="col-lg-8 offset-lg-2">
<form>
<div className="row">
<div className="col-sm-6">
<div className="form-group">
<label>
Nome <span className="text-danger">*</span>
</label>
<input className="form-control" type="text"
name="nome"
value={doctors.nome}
onChange={handleChange}
/>
</div>
</div>
<div className="col-sm-6">
<div className="form-group">
<label>Sobrenome</label>
<input className="form-control" type="text"
name="sobrenome"
value={doctors.sobrenome}
onChange={handleChange}
/>
</div>
</div>
<div className="col-sm-6">
<div className="form-group">
<label>CPF <span className="text-danger">*</span></label>
<input className="form-control" type="text" ref={withMask('cpf')}
name="cpf"
value={doctors.cpf}
onChange={handleChange}
/>
</div>
</div>
<div className="col-sm-6">
<div className="form-group">
<label>CRM<span className="text-danger">*</span></label>
<input className="form-control" type="text"
name="crm"
value={doctors.crm}
onChange={handleChange}
/>
</div>
</div>
<div className="col-sm-6">
<label>Especialidade</label>
<select
name="especialidade"
id="especialidade"
className="form-control"
value={doctors.especialidade}
onChange={handleChange}
>
<option value="">Selecionar</option>
<option value="cardiologista">Cardiologista</option>
<option value="Pediatria">Pediatria</option>
<option value="Dermatologia">Dermatologia</option>
<option value="Ginecologia">Ginecologia</option>
<option value="Neurologia">Neurologia</option>
<option value="Psiquiatria">Psiquiatria</option>
<option value="Ortopedia">Ortopedia</option>
</select>
</div>
<div className="col-sm-6">
<div className="form-group">
<label>Senha <span className="text-danger">*</span></label>
<input className="form-control" type="password"
name="senha"
value={doctors.senha}
onChange={handleChange} />
</div>
</div>
<div className="col-sm-6">
<div className="form-group">
<label>Email</label>
<input className="form-control" type="email" ref={withMask('email')}
name="email"
value={doctors.email}
onChange={handleChange}
/>
</div>
</div>
<div className="col-sm-6">
<div className="form-group">
<label>Confirmar Senha</label>
<input className="form-control" type="password"
name="confirmarSenha"
value={doctors.confirmarSenha}
onChange={handleChange}
/>
</div>
</div>
<div className="col-sm-6">
<div className="form-group">
<label>Data de Nascimento</label>
<div className="">
<input type="date" className="form-control"
name="data_nascimento"
value={doctors.data_nascimento}
onChange={handleChange}
/>
</div>
</div>
</div>
<div className="col-sm-6">
<div className="form-group">
<label>Telefone </label>
<input className="form-control" type="text" ref={withMask('+99 (99)99999-9999')}
name="telefone"
value={doctors.telefone}
onChange={handleChange}
/>
</div>
</div>
<div className="col-sm-6">
<div className="form-group gender-select">
<label className="gen-label">Sexo:</label>
<div className="form-check-inline">
<label className="form-check-label">
<input type="radio" name="sexo" className="form-check-input"
value={"Masculino"}
checked={doctors.sexo === "Masculino"}
onChange={handleChange}
/>Masculino
</label>
</div>
<div className="form-check-inline">
<label className="form-check-label">
<input type="radio" name="sexo" className="form-check-input"
value={"Feminino"}
checked={doctors.sexo === "Feminino"}
onChange={handleChange}
/>Feminino
</label>
</div>
<div className="form-check-inline">
<label className="form-check-label">
<input type="radio" name="sexo" className="form-check-input"
value={"Outro"}
checked={doctors.sexo === "Outro"}
onChange={handleChange}
/>Outro
</label>
</div>
</div>
</div>
<div className="col-sm-12">
<hr />
<h2>Endereço</h2>
</div>
<div className="col-sm-12">
<div className="row">
<div className="col-sm-6 col-md-6 col-lg-3">
<div className="form-group">
<label>CEP</label>
<input type="text" className="form-control "
id="cep"
name="cep"
value={doctors.cep}
onChange={handleChange}
onBlur={buscarCep}
/>
</div>
</div>
<div className="col-sm-6 col-md-6 col-lg-3">
<div className="form-group">
<label>Bairro</label>
<input type="text" className="form-control "
id="bairro"
name="bairro"
value={doctors.bairro}
onChange={handleChange}
/>
</div>
</div>
<div className="col-sm-6 col-md-6 col-lg-3">
<div className="form-group">
<label>Referência</label>
<input type="text" className="form-control "
id="referencia"
name="referencia"
Referência
value={doctors.referencia}
onChange={handleChange}
/>
</div>
</div>
<div className="col-sm-6 col-md-6 col-lg-3">
<div className="form-group">
<label>Logradouro</label>
<input type="text" className="form-control "
id="logradouro"
name="logradouro"
Referência
value={doctors.logradouro}
onChange={handleChange}
/>
</div>
</div>
<div className="col-sm-6 col-md-6 col-lg-3">
<div className="form-group">
<label>Complemento</label>
<input type="text" className="form-control "
id="complemento"
name="complemento"
Referência
value={doctors.complemento}
onChange={handleChange}
/>
</div>
</div>
<div className="col-sm-6 col-md-6 col-lg-3">
<div className="form-group">
<label>Cidade</label>
<input type="text" className="form-control"
id="cidade"
name="cidade"
value={doctors.cidade}
onChange={handleChange}
/>
</div>
</div>
<div className="col-sm-6 col-md-6 col-lg-3">
<div className="form-group">
<label>Estado</label>
<input type="text" className="form-control"
id="estado"
name="estado"
value={doctors.estado}
onChange={handleChange}
/>
</div>
</div>
<div className="col-sm-6 col-md-6 col-lg-3">
<div className="form-group">
<label>Número</label>
<input type="text" className="form-control"
id="numero"
name="numero"
value={doctors.numero}
onChange={handleChange}
/>
</div>
</div>
</div>
</div>
</div>
<div className="col-sm-6">
<div className="form-group">
<label>Anexo</label>
<div className="profile-upload">
<div className="upload-img">
<img alt="" src="assets/img/user.jpg" />
</div>
<div className="upload-input">
<input type="file" multiple accept="image/png, image/jpeg" className="form-control" />
</div>
</div>
</div>
</div>
<div className="col-sm-6">
<div className="form-group">
<label>Foto</label>
<div className="profile-upload">
<div className="upload-img">
<img alt="" src="assets/img/user.jpg" />
</div>
<div className="upload-input">
<input type="file" accept="image/png, image/jpeg" className="form-control" />
</div>
</div>
</div>
</div>
<div className="form-group">
<label>Biografia</label>
<textarea
className="form-control"
rows="3"
cols="30"
name="biografia"
value={doctors.biografia}
onChange={handleChange}
></textarea>
</div>
<div className="form-group">
<label className="display-block">Status</label>
<div className="form-check form-check-inline">
<input
className="form-check-input"
type="radio"
name="status"
id="status"
value="ativo"
checked={doctors.status === "ativo"}
onChange={handleChange}
/>
<label
className="form-check-label"
htmlFor="doctor_active"
>
Ativo
</label>
</div>
<div className="form-check form-check-inline">
<input
className="form-check-input"
type="radio"
name="status"
id="status"
value="inativo"
checked={doctors.status === "inativo"}
onChange={handleChange}
/>
<label
className="form-check-label"
htmlFor="doctor_inactive"
>
Inativo
</label>
</div>
</div>
<div className="m-t-20 text-center">
<Link to="/doctorlist"><button
className="btn btn-primary submit-btn"
onClick={handleEdit}>
Editar
</button></Link>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
);
}
export default EditDoctor

View File

@ -0,0 +1,448 @@
import "../../assets/css/index.css"
import { withMask } from "use-mask-input";
import { useState } from "react";
import supabase from "../../Supabase"
import { Link } from "react-router-dom";
import { useNavigate } from "react-router-dom";
function DoctorForm() {
const [doctorData, setdoctorData] = useState({
nome: "",
sobrenome: "",
cpf: "",
crm: "",
senha: "",
confirmarsenha: "",
email: "",
data_nascimento: "",
telefone: "",
sexo: "",
endereco: "",
numero: "",
cidade: "",
estado: "",
cep: "",
biografia: "",
status: "inativo",
especialidade: "",
bairro:"",
referencia:"",
logradouro:"",
complemento:""
});
const handleChange = (e) => {
const { name, value } = e.target;
setdoctorData((prev) => ({
...prev,
[name]: value
}));
}
const navigate = useNavigate();
const handleSubmit = async (e) => {
e.preventDefault();
const requiredFields = ["nome","cpf","crm","senha","confirmarsenha","data_nascimento","sexo","cep","logradouro","numero","bairro","cidade","estado","especialidade","email","telefone","data_nascimento"];
const missing = requiredFields.filter(f => !doctorData[f] || doctorData[f].toString().trim() === "");
if (missing.length > 0) {
alert("Preencha todos os campos obrigatórios.");
return;
}
// Verificar se senha e confirmarSenha são iguais
if (doctorData.senha !== doctorData.confirmarsenha) {
alert("Senha e Confirmar Senha não coincidem.");
return;
}
const { data, error } = await supabase
.from("Doctor")
.insert([doctorData]);
if (error) {
console.error("Erro ao cadastrar doutor:", error);
alert(`Erro ao cadastrar doutor: ${error.message}`);
} else {
alert("Doutor cadastrado com sucesso!");
navigate("/doctorlist");
}
};
const buscarCep = (e) => {
const cep = doctorData.cep.replace(/\D/g, '');
console.log(cep);
fetch(`https://viacep.com.br/ws/${cep}/json/`)
.then(response => response.json())
.then(data => {
console.log(data)
// salvando os valores para depois colocar nos inputs
setValuesFromCep(data)
// estou salvando os valoeres no patientData
setdoctorData((prev) => ({
...prev,
cidade: data.localidade || '',
estado: data.estado || '',
logradouro: data.logradouro || "",
bairro: data.bairro || '',
}));
})
}
const setValuesFromCep = (data) => {
document.getElementById('cidade').value = data.localidade || '';
document.getElementById('estado').value = data.uf || '';
document.getElementById('logradouro').value= data.logradouro || '';
document.getElementById('bairro').value= data.bairro || '';
}
return (
<div className="main-wrapper">
{/* FORMULÁRIO*/}
<div className="page-wrapper">
<div className="content">
<div className="row">
<div className="col-lg-8 offset-lg-2">
<h4 className="page-title">Cadastrar Médico</h4>
</div>
</div>
<div className="row">
<div className="col-lg-8 offset-lg-2">
<form onSubmit={handleSubmit}>
<div className="row">
<div className="col-sm-6">
<div className="form-group">
<label>
Nome <span className="text-danger">*</span>
</label>
<input className="form-control" type="text"
name="nome"
value={doctorData.nome}
onChange={handleChange}
/>
</div>
</div>
<div className="col-sm-6">
<div className="form-group">
<label>Sobrenome</label>
<input className="form-control" type="text"
name="sobrenome"
value={doctorData.sobrenome}
onChange={handleChange}
/>
</div>
</div>
<div className="col-sm-6">
<div className="form-group">
<label>CPF<span className="text-danger">*</span></label>
<input className="form-control" type="text" ref={withMask('cpf')}
name="cpf"
value={doctorData.cpf}
onChange={handleChange}
/>
</div>
</div>
<div className="col-sm-6">
<div className="form-group">
<label>CRM<span className="text-danger">*</span></label>
<input className="form-control" type="text"
name="crm"
value={doctorData.crm}
onChange={handleChange}
/>
</div>
</div>
<div className="col-sm-6">
<label>Especialidade<span className="text-danger">*</span></label>
<select
name="especialidade"
id="especialidade"
className="form-control"
value={doctorData.especialidade}
onChange={handleChange}
>
<option value="">Selecionar</option>
<option value="cardiologista">Cardiologista</option>
<option value="Pediatria">Pediatria</option>
<option value="Dermatologia">Dermatologia</option>
<option value="Ginecologia">Ginecologia</option>
<option value="Neurologia">Neurologia</option>
<option value="Psiquiatria">Psiquiatria</option>
<option value="Ortopedia">Ortopedia</option>
</select>
</div>
<div className="col-sm-6">
<div className="form-group">
<label>Senha <span className="text-danger">*</span></label>
<input className="form-control" type="password"
name="senha"
value={doctorData.senha}
onChange={handleChange} />
</div>
</div>
<div className="col-sm-6">
<div className="form-group">
<label>Email<span className="text-danger">*</span></label>
<input className="form-control" type="email" ref={withMask('email')}
name="email"
value={doctorData.email}
onChange={handleChange}
/>
</div>
</div>
<div className="col-sm-6">
<div className="form-group">
<label>Confirmar Senha<span className="text-danger">*</span></label>
<input className="form-control" type="password"
name="confirmarsenha"
value={doctorData.confirmarsenha}
onChange={handleChange}
/>
</div>
</div>
<div className="col-sm-6">
<div className="form-group">
<label>Data de Nascimento<span className="text-danger">*</span></label>
<div className="">
<input type="date" className="form-control"
name="data_nascimento"
value={doctorData.data_nascimento}
onChange={handleChange}
/>
</div>
</div>
</div>
<div className="col-sm-6">
<div className="form-group">
<label>Telefone<span className="text-danger">*</span></label>
<input className="form-control" type="text" ref={withMask('+99 (99)99999-9999')}
name="telefone"
value={doctorData.telefone}
onChange={handleChange}
/>
</div>
</div>
<div className="col-sm-6">
<div className="form-group gender-select">
<label className="gen-label">Sexo:<span className="text-danger">*</span></label>
<div className="form-check-inline">
<label className="form-check-label">
<input type="radio" name="sexo" className="form-check-input"
value={"Masculino"}
checked={doctorData.sexo === "Masculino"}
onChange={handleChange}
/>Masculino
</label>
</div>
<div className="form-check-inline">
<label className="form-check-label">
<input type="radio" name="sexo" className="form-check-input"
value={"Feminino"}
checked={doctorData.sexo === "Feminino"}
onChange={handleChange}
/>Feminino
</label>
</div>
<div className="form-check-inline">
<label className="form-check-label">
<input type="radio" name="sexo" className="form-check-input"
value={"Outro"}
checked={doctorData.sexo === "Outro"}
onChange={handleChange}
/>Outro
</label>
</div>
</div>
</div>
<div className="col-sm-12">
<hr />
<h2>Endereço</h2>
</div>
<div className="col-sm-12">
<div className="row">
<div className="col-sm-6 col-md-6 col-lg-3">
<div className="form-group">
<label>CEP<span className="text-danger">*</span></label>
<input type="text" className="form-control "
id="cep"
name="cep"
value={doctorData.cep}
onChange={handleChange}
onBlur={buscarCep}
/>
</div>
</div>
<div className="col-sm-6 col-md-6 col-lg-3">
<div className="form-group">
<label>Bairro<span className="text-danger">*</span></label>
<input type="text" className="form-control "
id="bairro"
name="bairro"
value={doctorData.bairro}
onChange={handleChange}
/>
</div>
</div>
<div className="col-sm-6 col-md-6 col-lg-3">
<div className="form-group">
<label>Referência</label>
<input type="text" className="form-control "
id="referencia"
name="referencia"
Referência
value={doctorData.referencia}
onChange={handleChange}
/>
</div>
</div>
<div className="col-sm-6 col-md-6 col-lg-3">
<div className="form-group">
<label>Logradouro<span className="text-danger">*</span></label>
<input type="text" className="form-control "
id="logradouro"
name="logradouro"
Referência
value={doctorData.logradouro}
onChange={handleChange}
/>
</div>
</div>
<div className="col-sm-6 col-md-6 col-lg-3">
<div className="form-group">
<label>Complemento</label>
<input type="text" className="form-control "
id="complemento"
name="complemento"
Referência
value={doctorData.complemento}
onChange={handleChange}
/>
</div>
</div>
<div className="col-sm-6 col-md-6 col-lg-3">
<div className="form-group">
<label>Cidade<span className="text-danger">*</span></label>
<input type="text" className="form-control"
id="cidade"
name="cidade"
value={doctorData.cidade}
onChange={handleChange}
/>
</div>
</div>
<div className="col-sm-6 col-md-6 col-lg-3">
<div className="form-group">
<label>Estado<span className="text-danger">*</span></label>
<input type="text" className="form-control"
id="estado"
name="estado"
value={doctorData.estado}
onChange={handleChange}
/>
</div>
</div>
<div className="col-sm-6 col-md-6 col-lg-3">
<div className="form-group">
<label>Número<span className="text-danger">*</span></label>
<input type="text" className="form-control"
id="numero"
name="numero"
value={doctorData.numero}
onChange={handleChange}
/>
</div>
</div>
</div>
</div>
</div>
<div className="col-sm-6">
<div className="form-group">
<label>Anexo</label>
<div className="profile-upload">
<div className="upload-img">
<img alt="" src="assets/img/user.jpg" />
</div>
<div className="upload-input">
<input type="file" multiple accept="image/png, image/jpeg" className="form-control" />
</div>
</div>
</div>
</div>
<div className="col-sm-6">
<div className="form-group">
<label>Foto</label>
<div className="profile-upload">
<div className="upload-img">
<img alt="" src="assets/img/user.jpg" />
</div>
<div className="upload-input">
<input type="file" accept="image/png, image/jpeg" className="form-control" />
</div>
</div>
</div>
</div>
<div className="form-group">
<label>Biografia</label>
<textarea
className="form-control"
rows="3"
cols="30"
name="biografia"
value={doctorData.biografia}
onChange={handleChange}
></textarea>
</div>
<div className="form-group">
<label className="display-block">Status</label>
<div className="form-check form-check-inline">
<input
className="form-check-input"
type="radio"
name="status"
id="status"
value="ativo"
checked={doctorData.status === "ativo"}
onChange={handleChange}
/>
<label
className="form-check-label"
htmlFor="doctor_active"
>
Ativo
</label>
</div>
<div className="form-check form-check-inline">
<input
className="form-check-input"
type="radio"
name="status"
id="status"
value="inativo"
checked={doctorData.status === "inativo"}
onChange={handleChange}
/>
<label
className="form-check-label"
htmlFor="doctor_inactive"
>
Inativo
</label>
</div>
</div>
<div className="m-t-20 text-center">
<button
className="btn btn-primary submit-btn"
type="submit"
>
Cadastrar Médico
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
);
}
export default DoctorForm

View File

@ -0,0 +1,142 @@
import "../../assets/css/index.css";
import { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import supabase from "../../Supabase";
function Doctors() {
const [doctors, setDoctors] = useState([]);
const [openDropdown, setOpenDropdown] = useState(null);
useEffect(() => {
const fetchDoctors = async () => {
const { data, error } = await supabase
.from("Doctor")
.select("*");
if (error) {
console.error("Erro ao buscar pacientes:", error);
} else {
setDoctors(data);
}
};
fetchDoctors();
}, []);
const handleDelete = async (id) => {
if (window.confirm("Tem certeza que deseja excluir este médico?")) {
const { error } = await supabase.from("Doctor").delete().eq("id", id);
if (error) console.error("Erro ao deletar médico:", error);
else setDoctors(doctors.filter((doc) => doc.id !== id));
}
};
return (
<div className="page-wrapper">
<div className="content">
<div className="row">
<div className="col-sm-4 col-3">
<h4 className="page-title">Médicos</h4>
</div>
<div className="col-sm-8 col-9 text-right m-b-20">
<Link
to="/doctorform"
className="btn btn-primary btn-rounded float-right"
>
<i className="fa fa-plus"></i> Adicionar Médico
</Link>
</div>
</div>
<div className="row doctor-grid">
{doctors.map((doctor) => (
<div key={doctor.id} className="col-md-4 col-sm-4 col-lg-3">
<div className="profile-widget">
<div className="doctor-img">
<div className="avatar">
<img alt="" src="/img/doctor-thumb-03.jpg" />
</div>
</div>
{/* Dropdown estilizado */}
<div className="dropdown profile-action">
<button
type="button"
className="action-icon"
onClick={(e) => {
e.stopPropagation();
setOpenDropdown(openDropdown === doctor.id ? null : doctor.id);
}}
>
<i className="fa fa-ellipsis-v"></i>
</button>
{openDropdown === doctor.id && (
<div
className="dropdown-menu dropdown-menu-right show"
style={{ position: "absolute", zIndex: 1000 }}
>
{/* Ver Detalhes */}
<Link
className="dropdown-item-custom"
to={`/profiledoctor/${doctor.id}`}
onClick={(e) => e.stopPropagation()}
>
<i className="fa fa-eye"></i> Ver Detalhes
</Link>
{/* Edit */}
<Link
className="dropdown-item-custom"
to={`/editdoctor/${doctor.id}`}
>
<i className="fa fa-pencil m-r-5"></i> Editar
</Link>
{/* Delete */}
<button
className="dropdown-item-custom dropdown-item-delete"
onClick={() => handleDelete(doctor.id)}
>
<i className="fa fa-trash-o m-r-5"></i> Delete
</button>
</div>
)}
</div>
<h4 className="doctor-name text-ellipsis">
<Link to={`/profiledoctor/${doctor.id}`}>
{doctor.nome} {doctor.sobrenome}
</Link>
</h4>
<div className="doc-prof">{doctor.especialidade}</div>
<div className="user-country">
<i className="fa fa-map-marker"></i> {doctor.cidade}
</div>
</div>
</div>
))}
</div>
</div>
{/* Modal delete (não alterado) */}
<div id="delete_doctor" className="modal fade delete-modal" role="dialog">
<div className="modal-dialog modal-dialog-centered">
<div className="modal-content">
<div className="modal-body text-center">
<img src="assets/img/sent.png" alt="" width="50" height="46" />
<h3>Are you sure want to delete this Doctor?</h3>
<div className="m-t-20">
<a href="#" className="btn btn-white" data-dismiss="modal">
Close
</a>
<button type="submit" className="btn btn-danger">
Delete
</button>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
export default Doctors;

View File

@ -0,0 +1,127 @@
import "../../assets/css/index.css";
import supabase from "../../Supabase";
import { useState } from "react";
import { useEffect } from "react";
import { useParams } from "react-router-dom";
function DoctorProfile() {
const [doctorData, setdoctorData] = useState([]);
const {id} = useParams()
useEffect(() => {
const fetchDoctors = async () => {
const {data, error} = await supabase
.from('Doctor')
.select('*')
.eq ('id', id)
.single()
if(error){
console.error("Erro ao buscar pacientes:", error);
}else{
setdoctorData(data);
}
};
fetchDoctors();
} , []);
return (
<div className="main-wrapper">
{/* Page Content */}
<div className="page-wrapper">
<div className="content">
<div className="row">
<div className="col-sm-7 col-6">
<h4 className="page-title">Perfil Médico</h4>
</div>
</div>
{/* Profile Header */}
<div className="card-box profile-header">
<div className="row">
<div className="col-md-12">
<div className="profile-view">
<div className="profile-img-wrap">
<div className="profile-img">
<a href="#">
<img src="/img/doctor-thumb-03.jpg" />
</a>
</div>
</div>
<div className="profile-basic">
<div className="row">
<div className="col-md-5">
<div className="profile-info-left">
<h3 className="user-name m-t-0 mb-0">{doctorData.nome} {doctorData.sobrenome}</h3>
<a className="text">{doctorData.especialidade}</a>
<div className="staff-id"></div>
</div>
</div>
<div className="col-md-7">
<ul className="personal-info">
<li>
<span className="title">Phone:</span>
<span className="text"><a href="#">{doctorData.telefone}</a></span>
</li>
<li>
<span className="title">Email:</span>
<span className="text"><a href="#">{doctorData.email}</a></span>
</li>
<li>
<span className="title">Data de nascimento:</span>
<span className="text">{doctorData.data_nascimento}</span>
</li>
<li>
<span className="title">Região</span>
<span className="text">{doctorData.cidade}, {doctorData.estado}, Brasil</span>
</li>
<li>
<span className="title">Sexo</span>
<span className="text">{doctorData.sexo}</span>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{/* Tabs */}
<div className="profile-tabs">
<ul className="nav nav-tabs nav-tabs-bottom">
<li className="nav-item">
<a className="nav-link active" href="#about-cont" data-toggle="tab">Sobre</a>
</li>
</ul>
<div className="tab-content">
<div className="tab-pane show active" id="about-cont">
<div className="row">
<div className="col-md-12">
<div className="card-box">
<h3 className="card-title">Biografia</h3>
<div className="experience-box">
<div className="experience-content">
<p>{doctorData.biografia}</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
export default DoctorProfile

View File

@ -0,0 +1,100 @@
import { Outlet, NavLink } from "react-router-dom";
import "../../assets/css/index.css";
function DoctorApp() {
return (
<div className="main-wrapper">
{/* Header */}
<div className="header">
<div className="header-left">
<a href="/" className="logo">
<img src="/img/logomedconnect.png" width="35" height="35" alt="" />
<span>MediConnect</span>
</a>
</div>
<a id="mobile_btn" className="mobile_btn float-left" href="#sidebar">
<i className="fa fa-bars"></i>
</a>
<ul className="nav user-menu float-right">
<li className="nav-item dropdown has-arrow">
<a
href="#!"
className="dropdown-toggle nav-link user-link"
data-toggle="dropdown"
>
<span className="user-img">
<span className="status online"></span>
</span>
<span>Médico</span>
</a>
<div className="dropdown-menu">
<a className="dropdown-item" href="#profile">
Meu Perfil
</a>
<a className="dropdown-item" href="#settings">
Configurações
</a>
<a className="dropdown-item" href="#logout">
Sair
</a>
</div>
</li>
</ul>
</div>
{/* Sidebar */}
<div className="sidebar" id="sidebar">
<div className="sidebar-inner slimscroll">
<div id="sidebar-menu" className="sidebar-menu">
<ul>
<li className="menu-title">
<span>Painel do Médico</span>
</li>
<li>
<NavLink
to="/doctor/patients"
className={({ isActive }) => (isActive ? "active" : "")}
>
<i className="fa fa-users"></i>
<span>Pacientes</span>
</NavLink>
</li>
<li>
<NavLink
to="/doctor/calendar"
className={({ isActive }) => (isActive ? "active" : "")}
>
<i className="fa fa-calendar"></i>
<span>Calendário</span>
</NavLink>
</li>
<li>
<NavLink
to="/doctor/dashboard"
className={({ isActive }) => (isActive ? "active" : "")}
>
<i className="fa fa-bar-chart"></i>
<span>Dashboard</span>
</NavLink>
</li>
</ul>
</div>
</div>
</div>
{/* Conteúdo */}
<div className="page-wrapper">
<div className="content">
<Outlet />
</div>
</div>
</div>
);
}
export default DoctorApp;

View File

@ -0,0 +1,55 @@
import FullCalendar from "@fullcalendar/react";
import dayGridPlugin from "@fullcalendar/daygrid";
import timeGridPlugin from "@fullcalendar/timegrid";
import interactionPlugin from "@fullcalendar/interaction";
import ptBrLocale from "@fullcalendar/core/locales/pt-br";
function DoctorCalendar() {
return (
<div className="doctor-calendar-container">
<h2 className="calendar-title">Calendário do Médico</h2>
<div className="calendar-wrapper">
<FullCalendar
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]}
initialView={"dayGridMonth"}
locale={ptBrLocale}
headerToolbar={{
start: "today prev,next",
center: "title",
end: "dayGridMonth,timeGridWeek,timeGridDay",
}}
height="auto"
/>
</div>
{/* CSS inline para centralizar */}
<style jsx>{`
.doctor-calendar-container {
display: flex;
flex-direction: column;
align-items: center; /* centraliza horizontal */
justify-content: center; /* centraliza vertical se precisar */
width: 100%;
padding: 20px;
}
.calendar-title {
margin-bottom: 20px;
text-align: center;
}
.calendar-wrapper {
max-width: 900px; /* largura máxima do calendário */
width: 100%;
background: #fff;
padding: 20px;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
`}</style>
</div>
);
}
export default DoctorCalendar;

View File

@ -0,0 +1,68 @@
import { PieChart, Pie, Cell, ResponsiveContainer, BarChart, Bar, XAxis, YAxis, Tooltip, Legend } from "recharts";
const consultsData = [
{ name: "Consultas", value: 45 },
{ name: "Exames", value: 20 },
{ name: "Laudos", value: 15 },
{ name: "Receitas", value: 25 },
];
const COLORS = ["#0088FE", "#00C49F", "#FFBB28", "#FF8042"];
function DoctorDashboard() {
return (
<div className="main-wrapper">
<div className="page-wrapper">
<div className="content">
<h2 className="mb-4">📊 Dashboard do Médico</h2>
<div className="row">
{/* Gráfico de Pizza */}
<div className="col-md-6">
<h5>Distribuição de Atividades</h5>
<ResponsiveContainer width="100%" height={300}>
<PieChart>
<Pie
data={consultsData}
dataKey="value"
nameKey="name"
cx="50%"
cy="50%"
outerRadius={100}
label
>
{consultsData.map((entry, index) => (
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
))}
</Pie>
<Tooltip />
</PieChart>
</ResponsiveContainer>
</div>
{/* Gráfico de Barras */}
<div className="col-md-6">
<h5>Consultas por Semana</h5>
<ResponsiveContainer width="100%" height={300}>
<BarChart data={[
{ semana: "Semana 1", consultas: 12 },
{ semana: "Semana 2", consultas: 18 },
{ semana: "Semana 3", consultas: 9 },
{ semana: "Semana 4", consultas: 15 },
]}>
<XAxis dataKey="semana" />
<YAxis />
<Tooltip />
<Legend />
<Bar dataKey="consultas" fill="#82ca9d" />
</BarChart>
</ResponsiveContainer>
</div>
</div>
</div>
</div>
</div>
);
}
export default DoctorDashboard;

View File

@ -0,0 +1,66 @@
import React, { useState } from "react";
function PatientList() {
const [searchTerm, setSearchTerm] = useState("");
const patients = [
{
nome: "João Miguel",
cpf: "091.959.495-69",
telefone: "+55 (75) 99961-7296",
email: "Joaomiguel80@gmail.com",
},
];
const filteredPatients = patients.filter(
(p) =>
p.nome.toLowerCase().includes(searchTerm.toLowerCase()) ||
p.cpf.toLowerCase().includes(searchTerm.toLowerCase()) ||
p.email.toLowerCase().includes(searchTerm.toLowerCase())
);
return (
<div className="main-content">
{/* Barra de Pesquisa */}
<input
type="text"
placeholder="Pesquisar por nome, CPF ou e-mail"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="search-bar"
/>
{/* Tabela */}
<div className="table-container">
<h3 className="text-center mb-4">Lista de Pacientes</h3>
<table className="table table-bordered">
<thead>
<tr>
<th>Nome</th>
<th>CPF</th>
<th>Telefone</th>
<th>Email</th>
<th>Ações</th>
</tr>
</thead>
<tbody>
{filteredPatients.map((p, idx) => (
<tr key={idx}>
<td>{p.nome}</td>
<td>{p.cpf}</td>
<td>{p.telefone}</td>
<td>{p.email}</td>
<td>
<button className="btn btn-primary btn-sm mr-2">Laudo</button>
<button className="btn btn-success btn-sm">Receita</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
}
export default PatientList;

View File

@ -0,0 +1,608 @@
import { useState } from "react";
import { useEffect } from "react";
import "../../assets/css/index.css"
import { withMask } from "use-mask-input";
import supabase from "../../Supabase"
import { Link } from "react-router-dom";
import { useParams } from "react-router-dom";
function PatientEdit() {
const [patients, setpatients] = useState([""])
const{id} = useParams()
// carregando a lista e adicionando no usestate
useEffect(() => {
fetch(`https://mock.apidog.com/m1/1053378-0-default/pacientes/${id}`)
.then((response) => response.json())
.then((result) => setpatients(result.data || {}))
.catch((error) => console.log("error", error));
}, [id]);
const [preview, setPreview] = useState(null);
useEffect(() => {
if (patients.foto_url) {
setPreview('https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSr6OBNqnFlVKC6fAk-mzSuzmOKgjWMYq9y0g&s');
}
}, [patients.foto_url]);
const handleEdit = async (e) => {
const requestOptions = {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(patients),
redirect: "follow",
};
fetch(`https://mock.apidog.com/m1/1053378-0-default/pacientes/${id}`, requestOptions)
.then((response) => response.json())
.then((result) => {
alert("Paciente editado com sucesso!");
// redireciona após editar
})
.catch((error) => console.log("error", error));
};
// aqui eu fiz uma funçao onde atualiza o estado do paciente, eu poderia ir mudando com o onchange em cada input mas assim ficou melhor
// e como se fosse 'onChange={(e) => setpatientData({ ...patientData, rg: e.target.value })}'
// prev= pega o valor anterio
const handleChange = (e) => {
const { name, value } = e.target;
setpatients((prev) => ({
...prev,
[name]: value
}));
};
const buscarCep = (e) => {
const cep = patients.cep.replace(/\D/g, '');
console.log(cep);
fetch(`https://viacep.com.br/ws/${cep}/json/`)
.then(response => response.json())
.then(data => {
console.log(data)
// salvando os valores para depois colocar nos inputs
setValuesFromCep(data)
// estou salvando os valoeres no patientData
setpatients((prev) => ({
...prev,
cidade: data.localidade || '',
logradouro: data.logradouro || '',
bairro: data.bairro || '',
estado: data.estado || ''
}));
})
}
// aqui esta sentando os valores nos inputs
const setValuesFromCep = (data) => {
document.getElementById('logradouro').value = data.logradouro || '';
document.getElementById('bairro').value = data.bairro || '';
document.getElementById('cidade').value = data.localidade || '';
document.getElementById('estado').value = data.uf || '';
}
const validarCpf = async (cpf) => {
const cpfLimpo = cpf.replace(/\D/g, "");
try {
const response = await fetch("https://mock.apidog.com/m1/1053378-0-default/pacientes/validar-cpf", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ cpf: cpfLimpo })
});
const data = await response.json();
if (data.valido === false) {
alert("CPF inválido!");
return false;
} else if (data.valido === true) {
return true;
} else {
//alert("Não foi possível validar o CPF.");
return false;
}
} catch (error) {
alert("Erro ao validar CPF.");
return false;
}
};
const handleSubmit = async (e) => {
e.preventDefault();
const cpfValido = await validarCpf(patientData.cpf);
// Calcula idade a partir da data de nascimento
const hoje = new Date();
const nascimento = new Date(patientData.data_nascimento);
let idade = hoje.getFullYear() - nascimento.getFullYear();
const m = hoje.getMonth() - nascimento.getMonth();
if (m < 0 || (m === 0 && hoje.getDate() < nascimento.getDate())) {
idade--;
}
let cpfRespValido = true;
if (idade < 18) {
cpfRespValido = await validarCpf(patientData.cpf_responsavel);
}
if (!cpfValido || !cpfRespValido) {
console.log("CPF inválido. Não enviando o formulário.");
// Não envia se algum CPF for inválido
return;
}
// aqui estou fazendo o update
};
return (
<div className="main-wrapper">
<div className="page-wrapper">
<div className="content">
<div className="row">
<div className="col-lg-8 offset-lg-2">
<h2 className="">Dados pessoais</h2>
<hr />
</div>
</div>
<div className="row">
<div className="col-lg-8 offset-lg-2">
<form>
<div className="row">
<div className="col-sm-6">
<div className="form-group">
<label>Avatar</label>
<div className="profile-upload">
<div className="upload-img">
<img alt="" src={preview || "assets/img/user.jpg"} />
</div>
<div className="row">
<div className="col-md-9">
<div className="upload-input">
<input
name="foto_url"
onChange={(e) => {
handleChange(e);
setPreview(URL.createObjectURL(e.target.files[0]));
}}
type="file"
accept="image/png, image/jpeg"
className="form-control"
/>
</div>
</div>
<div className="col-md-3">
<div
className="btn btn-primary"
onClick={async () => {
// Remove no Frontend
setpatients(prev => ({ ...prev, foto_url: "" }));
setPreview(null); // Limpa a pré-visualização
document.getElementsByName('foto_url')[0].value = null;
// Remove na API e mostra resposta no console
try {
const response = await fetch(`https://mock.apidog.com/m1/1053378-0-default/pacientes/${id}/foto`, {
method: "DELETE",
});
const data = await response.json();
console.log("Resposta da API ao remover foto:", data);
if (response.ok || response.status === 200) {
alert("Foto removida com sucesso!");
}
} catch (error) {
console.log("Erro ao remover foto:", error);
}
}}
>
Limpar
</div>
</div>
</div>
</div>
</div>
<div className="form-group">
<label>
Nome completo<span className="text-danger">*</span>
</label>
<input className="form-control" type="text"
required
id="nome"
name="nome"
value={patients.nome}
onChange={handleChange}
/>
</div>
<div className="form-group">
<label>RG</label>
<input className="form-control" type="text"
name="rg"
value={patients.rg}
onChange={handleChange}
/>
</div>
<div className="form-group">
<label>Outros documentos de identidade </label>
<select id="outrosdoc" className="form-control"
name="outros_documentos"
value={patients.outros_documentos}
onChange={handleChange}
>
<option value="">Selecionar</option>
<option value="cnh">CNH</option>
<option value="passaporte">Passaporte</option>
</select>
</div>
<div className="form-group">
<label>Raça</label>
<select
name="raça"
id="raça"
className="form-control"
value={patients.raça}
onChange={handleChange}
>
<option value="">Selecionar</option>
<option value="casa">Preta</option>
<option value="branca">Branca</option>
<option value="parda">Parda</option>
<option value="amarela">Amarela</option>
<option value="Indigena">Indígena</option>
</select>
</div>
<div className="form-group">
<label>Profissão</label>
<input className="form-control" type="text"
name="profissao"
value={patients.profissao}
onChange={handleChange}
/>
</div>
<div className="form-group">
<label>Nome da mãe</label>
<input className="form-control" type="text"
name="nome_mae"
value={patients.nome_mae}
onChange={handleChange}
/>
</div>
<div className="form-group">
<label>Profissão da mãe</label>
<input className="form-control" type="text"
name="profissao_mae"
value={patients.profissao_mae}
onChange={handleChange}
/>
</div>
<div className="form-group">
<label>Nome do responsável</label>
<input className="form-control" type="text"
name="nome_responsavel"
value={patients.nome_responsavel}
onChange={handleChange}
/>
</div>
<div className="form-check-inline">
<label className="form-check-label">
<input type="checkbox" name="rn" className="form-check-input"
value={true}
checked={patients.rn === true}
onChange={(e) => setpatients({ ...patients, rn: e.target.checked })}
/>
RN na Guia do convênio
</label>
</div>
</div>
<div className="col-sm-6">
<div className="form-group">
<label>Nome social</label>
<input className="form-control" type="text"
name="nome_social"
value={patients.nome_social}
onChange={handleChange}
/>
</div>
<div className="form-group">
<label>CPF</label>
<input className="form-control" type="text" ref={withMask('cpf')}
name="cpf"
value={patients.cpf}
onChange={handleChange}
/>
</div>
<div className="form-group">
<label>Número do documento</label>
<input className="form-control" type="text"
name="numero_documento"
value={patients.numero_documento}
onChange={handleChange} />
</div>
<div className="form-group">
<label>Estado civil</label>
<select id="civil" className="form-control"
name="estado_civil"
value={patients.estado_civil}
onChange={handleChange}
>
<option value="">Selecionar</option>
<option value="solteiro">Solteiro(a)</option>
<option value="casado">Casado(a)</option>
<option value="viúvo">Viúvo(a)</option>
<option value="amarela">Separado(a)</option>
</select>
</div>
<div className="form-group">
<label>Data de Nascimento</label>
<input type="date" className="form-control"
name="data_nascimento"
value={patients.data_nascimento}
onChange={handleChange}
/>
</div>
<div className="form-group">
<label>Nome do pai</label>
<input className="form-control" type="text"
name="nome_pai"
value={patients.nome_pai}
onChange={handleChange} />
</div>
<div className="form-group">
<label>Profissão do pai</label>
<input className="form-control" type="text"
name="profissao_pai"
value={patients.profissao_pai}
onChange={handleChange}
/>
</div>
<div className="form-group">
<label>CPF do responsável</label>
<input className="form-control" type="text" ref={withMask('cpf')}
name="cpf_responsavel"
value={patients.cpf_responsavel}
onChange={handleChange}
/>
</div>
<div className="form-group">
<label>Código legado</label>
<input className="form-control" type="text"
name="codigo_legado"
value={patients.codigo_legado}
onChange={handleChange}
/>
</div>
<div className="form-group gender-select">
<label className="gen-label">Sexo:</label>
<div className="form-check-inline">
<label className="form-check-label">
<input type="radio" name="sexo" className="form-check-input"
value={"masculino"}
checked={patients.sexo === "masculino"}
onChange={handleChange}
/> Masculino
</label>
</div>
<div className="form-check-inline">
<label className="form-check-label">
<input type="radio" name="sexo" className="form-check-input"
value={"feminino"}
checked={patients.sexo === "feminino"}
onChange={handleChange}
/> Feminino
</label>
</div>
<div className="form-check-inline">
<label className="form-check-label">
<input type="radio" name="sexo" className="form-check-input"
value={"outro"}
checked={patients.sexo === "outro"}
onChange={handleChange}
/> Outro
</label>
</div>
</div>
</div>
<div className="col-sm-12">
<hr />
<h2>Contato</h2>
</div>
<div className="col-sm-6">
<div className="form-group">
<label>Celular</label>
<input className="form-control" type="text" ref={withMask('+55 (99) 99999-9999')}
name="celular"
value={patients.celular}
onChange={handleChange} />
</div>
<div className="form-group">
<label>Telefone 1</label>
<input className="form-control" type="text" ref={withMask('+55 (99) 99999-9999')}
name="telefone1"
value={patients.telefone1}
onChange={handleChange}
/>
</div>
</div>
<div className="col-sm-6">
<div className="form-group">
<label>Email</label>
<input className="form-control" type="email"
name="email"
value={patients.email}
onChange={handleChange}
/>
</div>
<div className="form-group">
<label>Telefone 2</label>
<input className="form-control" type="text" ref={withMask('+55 (99) 99999-9999')}
name="telefone2"
value={patients.telefone2}
onChange={handleChange}
/>
</div>
</div>
<div className="col-sm-12">
<hr />
<h2>Endereço</h2>
</div>
<div className="col-sm-6">
<div className="form-group">
<label>CEP</label>
<input className="form-control" type="text"
name="cep"
value={patients.cep}
onChange={handleChange}
onBlur={buscarCep}
/>
</div>
<div className="form-group">
<label>Cidade</label>
<input className="form-control" type="text"
id="cidade"
name="cidade"
value={patients.cidade}
onChange={handleChange}
/>
</div>
<div className="form-group">
<label>Logradouro</label>
<input className="form-control" type="text"
id="logradouro"
name="logradouro"
value={patients.logradouro}
onChange={handleChange}
/>
</div>
<div className="form-group">
<label>Complemento</label>
<input className="form-control" type="text"
id="complemento"
name="complemento"
value={patients.complemento}
onChange={handleChange}
/>
</div>
</div>
<div className="col-sm-6">
<div className="form-group">
<label>Estado</label>
<input className="form-control" type="text"
id="estado"
name="estado"
value={patients.estado}
onChange={handleChange}
/>
</div>
<div className="form-group">
<label>Número</label>
<input className="form-control" type="text"
name="numero"
value={patients.numero}
onChange={handleChange}
/>
</div>
<div className="form-group">
<label>Bairro</label>
<input className="form-control" type="text"
id="bairro"
name="bairro"
value={patients.bairro}
onChange={handleChange}
/>
</div>
<div className="form-group">
<label>Referência </label>
<input className="form-control" type="text"
name="referencia"
value={patients.referencia}
onChange={handleChange}
/>
</div>
</div>
</div>
<div className="col-sm-12">
<hr />
</div>
<div className="form-group">
<label className="display-block">Status</label>
<div className="form-check form-check-inline">
<input
className="form-check-input"
type="radio"
name="status"
id="patient_active"
value={"ativo"}
checked={patients.status === "ativo"}
onChange={handleChange}
/>
<label className="form-check-label" htmlFor="patient_active">
Ativo
</label>
</div>
<div className="form-check form-check-inline">
<input
className="form-check-input"
type="radio"
name="status"
id="patient_inactive"
value="inativo"
checked={patients.status === "inativo"}
onChange={handleChange}
/>
<label className="form-check-label" htmlFor="patient_inactive">
Inativo
</label>
</div>
</div>
<div className="form-group">
<label>Observação</label>
<textarea className="form-control" rows="3"
name="observaçao"
value={patients.observaçao}
onChange={handleChange}
></textarea>
</div>
<div className="form-group">
<label>Documentos</label>
<div className="profile-upload">
<div className="upload-img">
<img alt="" src="assets/img/user.jpg" />
</div>
<div className="upload-input">
<input type="file" accept="image/png, image/jpeg" className="form-control" />
</div>
</div>
</div>
<div className="m-t-20 text-center">
<Link to="/patientlist">
<button
className="btn btn-primary submit-btn"
onClick={handleEdit}
>Editar Paciente</button>
</Link>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
);
};
export default PatientEdit;

View File

@ -0,0 +1,264 @@
// PatientList.jsx
import { Link } from "react-router-dom";
import "../../assets/css/index.css";
import React, { useState, useEffect, useRef, useLayoutEffect } from "react";
import { createPortal } from "react-dom";
import supabase from "../../Supabase"; // se for usar supabase para delete, senão pode remover
// Componente que renderiza o menu em um portal (document.body) e posiciona em relação ao botão
function DropdownPortal({ anchorEl, isOpen, onClose, className, children }) {
const menuRef = useRef(null);
const [stylePos, setStylePos] = useState({
position: "absolute",
top: 0,
left: 0,
visibility: "hidden",
zIndex: 1000,
});
// Posiciona o menu após renderar (medir tamanho do menu)
useLayoutEffect(() => {
if (!isOpen) return;
if (!anchorEl || !menuRef.current) return;
const anchorRect = anchorEl.getBoundingClientRect();
const menuRect = menuRef.current.getBoundingClientRect();
const scrollY = window.scrollY || window.pageYOffset;
const scrollX = window.scrollX || window.pageXOffset;
// tenta alinhar à direita do botão (como dropdown-menu-right)
let left = anchorRect.right + scrollX - menuRect.width;
let top = anchorRect.bottom + scrollY;
// evita sair da esquerda da tela
if (left < 0) left = scrollX + 4;
// se extrapolar bottom, abre para cima
if (top + menuRect.height > window.innerHeight + scrollY) {
top = anchorRect.top + scrollY - menuRect.height;
}
setStylePos({
position: "absolute",
top: `${Math.round(top)}px`,
left: `${Math.round(left)}px`,
visibility: "visible",
zIndex: 1000,
});
}, [isOpen, anchorEl, children]);
// fecha ao clicar fora / ao rolar
useEffect(() => {
if (!isOpen) return;
function handleDocClick(e) {
const menu = menuRef.current;
if (menu && !menu.contains(e.target) && anchorEl && !anchorEl.contains(e.target)) {
onClose();
}
}
function handleScroll() {
onClose();
}
document.addEventListener("mousedown", handleDocClick);
// captura scroll em qualquer elemento (true)
document.addEventListener("scroll", handleScroll, true);
return () => {
document.removeEventListener("mousedown", handleDocClick);
document.removeEventListener("scroll", handleScroll, true);
};
}, [isOpen, onClose, anchorEl]);
if (!isOpen) return null;
return createPortal(
<div
ref={menuRef}
className={className} // mantém as classes que você já usa no CSS
style={stylePos}
onClick={(e) => e.stopPropagation()}
>
{children}
</div>,
document.body
);
}
function PatientList() {
const [search, setSearch] = useState("");
const [patients, setPatients] = useState([]);
const [openDropdown, setOpenDropdown] = useState(null);
const anchorRefs = useRef({}); // guarda referência do botão de cada linha
var requestOptions = {
method: "GET",
redirect: "follow",
};
useEffect(() => {
fetch("https://mock.apidog.com/m1/1053378-0-default/pacientes", requestOptions)
.then((response) => response.json())
.then((result) => {
console.log("API result:", result);
setPatients(result.data || []);
})
.catch((error) => console.log("error", error));
}, []);
// Exemplo simples de delete local (confirmação + remove do state)
const handleDelete = async (id) => {
const confirmDel = window.confirm("Tem certeza que deseja excluir este paciente?");
if (!confirmDel) return;
const requestOptions = {
method: 'DELETE',
redirect: 'follow'
};
fetch("https://mock.apidog.com/m1/1053378-0-default/pacientes/", requestOptions)
.then(response => response.text())
.then(result => console.log(result))
.catch(error => console.log('error', error));
// Se quiser apagar no supabase, faça a chamada aqui.
// const { error } = await supabase.from("Patient").delete().eq("id", id);
// if (error) { console.error(error); return; }
setPatients((prev) => prev.filter((p) => p.id !== id));
setOpenDropdown(null);
};
const filteredPatients = patients.filter((p) => {
if (!p) return false;
const nome = (p.nome || "").toLowerCase();
const cpf = (p.cpf || "").toLowerCase();
const email = (p.email || "").toLowerCase();
const q = search.toLowerCase();
return nome.includes(q) || cpf.includes(q) || email.includes(q);
});
const mascararCPF = (cpf = "") => {
if (cpf.length < 5) return cpf;
const inicio = cpf.slice(0, 3);
const fim = cpf.slice(-2);
return `${inicio}.***.***-${fim}`;
};
return (
<div className="main-wrapper">
<div className="page-wrapper">
<div className="content">
<div className="row ">
<div className="col-sm-4 col-3">
<h4 className="page-title">Lista de Pacientes</h4>
<input
type="text"
className="form-control"
placeholder="🔍 Buscar pacientes"
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
<br />
</div>
<div className="col-sm-8 col-9 text-right m-b-20">
<Link to="/patient" className="btn btn-primary btn-rounded">
<i className="fa fa-plus"></i> Adicionar Paciente
</Link>
</div>
</div>
<div className="row">
<div className="col-md-12">
<div className="table-responsive">
<table className="table table-border table-striped custom-table datatable mb-0">
<thead>
<tr>
<th>Nome</th>
<th>Cpf</th>
<th>Data de Nascimento</th>
<th>Telefone</th>
<th>Email</th>
<th>Status</th>
<th className="text-right">Ações</th>
</tr>
</thead>
<tbody>
{filteredPatients.length > 0 ? (
filteredPatients.map((p) => (
<tr key={p.id}>
<td>{p.nome}</td>
<td>{mascararCPF(p.cpf)}</td>
<td>{p.data_nascimento}</td>
<td>{p.telefone}</td>
<td>{p.email}</td>
<td>{p.status}</td>
<td className="text-right">
<div className="dropdown dropdown-action" style={{ display: "inline-block" }}>
<button
type="button"
ref={(el) => (anchorRefs.current[p.id] = el)}
className="action-icon"
onClick={(e) => {
e.stopPropagation();
setOpenDropdown(openDropdown === p.id ? null : p.id);
}}
>
<i className="fa fa-ellipsis-v"></i>
</button>
<DropdownPortal
anchorEl={anchorRefs.current[p.id]}
isOpen={openDropdown === p.id}
onClose={() => setOpenDropdown(null)}
className="dropdown-menu dropdown-menu-right show"
>
{/*<Link
className="dropdown-item-custom"
to={`/profilepatient/${p.id}`}
onClick={(e) => {
e.stopPropagation();
setOpenDropdown(null);
}}
>
<i className="fa fa-eye"></i> Ver Detalhes
</Link>*/}
<Link
className="dropdown-item-custom"
to={`/editpatient/${p.id}`}
onClick={(e) => {
e.stopPropagation();
setOpenDropdown(null);
}}
>
<i className="fa fa-pencil m-r-5"></i> Editar
</Link>
<button
className="dropdown-item-custom dropdown-item-delete"
onClick={() => handleDelete(p.id)}
>
<i className="fa fa-trash-o m-r-5"></i> Excluir
</button>
</DropdownPortal>
</div>
</td>
</tr>
))
) : (
<tr>
<td colSpan="7" className="text-center text-muted">
Nenhum paciente encontrado
</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
export default PatientList;
;

View File

@ -0,0 +1,794 @@
import { useState, useEffect,useRef } from "react";
import "../../assets/css/index.css"
import { withMask } from "use-mask-input";
import supabase from "../../Supabase"
import { useNavigate } from "react-router-dom";
function Patientform() {
const [patientData, setpatientData] = useState({
nome: "",
nome_social: "",
cpf: "",
rg: "",
outros_documentos: "",
numero_documento: "",
estado_civil: "",
raça: "",
data_nascimento: null,
profissao: "",
nome_pai: "",
profissao_pai: "",
nome_mae: "",
profissao_mae: "",
nome_responsavel: "",
codigo_legado: "",
foto_url: "",
rn: "false",
sexo: "",
celular: "",
email: "",
telefone1: "",
telefone2: "",
cep: "",
estado: "",
logradouro: "",
bairro: "",
numero: "",
complemento: "",
referencia: "",
status: "inativo",
observaçao: ""
})
const [fotoFile, setFotoFile] = useState(null);
const fileRef = useRef(null);
useEffect(() => {
console.log("Estado atualizado:", patientData);
}, [patientData]);
// aqui eu fiz uma funçao onde atualiza o estado do paciente, eu poderia ir mudando com o onchange em cada input mas assim ficou melhor
// e como se fosse 'onChange={(e) => setpatientData({ ...patientData, rg: e.target.value })}'
// prev= pega o valor anterior
const handleChange = (e) => {
const { name, value } = e.target;
setpatientData((prev) => ({
...prev,
[name]: value
}));
};
// aqui esta sentando os valores nos inputs
const setValuesFromCep = (data) => {
document.getElementById('logradouro').value = data.logradouro || '';
document.getElementById('bairro').value = data.bairro || '';
document.getElementById('cidade').value = data.localidade || '';
document.getElementById('estado').value = data.uf || '';
}
const buscarCep = (e) => {
const cep = patientData.cep.replace(/\D/g, '');
console.log(cep);
fetch(`https://viacep.com.br/ws/${cep}/json/`)
.then(response => response.json())
.then(data => {
console.log(data)
// salvando os valores para depois colocar nos inputs
setValuesFromCep(data)
// estou salvando os valoeres no patientData
setpatientData((prev) => ({
...prev,
cidade: data.localidade || '',
logradouro: data.logradouro || '',
bairro: data.bairro || '',
estado: data.estado || ''
}));
})
}
const navigate = useNavigate();
// enviando para o supabase
const handleSubmit = async (e) => {
e.preventDefault();
//const cpfValido = await validarCpf(patientData.cpf);
/*
// Verifica se já existe paciente com o CPF
const cpfExiste = await verificarCpfExistente(patientData.cpf);
if (cpfExiste) {
alert("Já existe um paciente cadastrado com este CPF!");
return;
}
*/
// Calcula idade a partir da data de nascimento
/*
const hoje = new Date();
const nascimento = new Date(patientData.data_nascimento);
let idade = hoje.getFullYear() - nascimento.getFullYear();
const m = hoje.getMonth() - nascimento.getMonth();
if (m < 0 || (m === 0 && hoje.getDate() < nascimento.getDate())) {
idade--;
}
let cpfRespValido = true;
if (idade < 18) {
cpfRespValido = await validarCpf(patientData.cpf_responsavel);
}
if (!cpfValido || !cpfRespValido) {
console.log("CPF inválido. Não enviando o formulário.");
// Não envia se algum CPF for inválido
return;
}*/
// Campos obrigatórios
const requiredFields = [
"nome",
"cpf",
"data_nascimento",
"sexo",
"celular",
"cep",
"logradouro",
"numero",
"bairro",
"estado",
"status",
"email"
];
const missingFields = requiredFields.filter(
(field) => !patientData[field] || patientData[field].toString().trim() === ""
);
if (missingFields.length > 0) {
alert("Por favor, preencha todos os campos obrigatórios.");
return;
}
const myHeaders = new Headers();
myHeaders.append("Authorization", "Bearer <token>");
myHeaders.append("Content-Type", "application/json");
const raw = JSON.stringify(patientData);
var requestOptions = {
method: 'POST',
headers: myHeaders,
body: raw,
redirect: 'follow'
};
fetch("https://mock.apidog.com/m1/1053378-0-default/pacientes", requestOptions)
.then(response => response.json())
.then(async (result) => {
setpatientData(result)
console.log(result)
if (fotoFile && result?.id) {
const formData = new FormData();
formData.append("foto", fotoFile);
try {
const res = await fetch(`https://mock.apidog.com/m1/1053378-0-default/pacientes/${result.id}/foto`, {
method: "POST",
headers: {
"Authorization": "Bearer <token>"
},
body: formData
});
const uploadResult = await res.json();
console.log("Foto enviada com sucesso:", uploadResult);
} catch (error) {
console.error("Erro no upload da foto:", error);
}
}
if (patientData.documentoFile && result?.id) {
const formData = new FormData();
formData.append("anexo", patientData.documentoFile);
try {
const resAnexo = await fetch(`https://mock.apidog.com/m1/1053378-0-default/pacientes/${result.id}/anexos`, {
method: "POST",
headers: {
"Authorization": "Bearer <token>"
},
body: formData
});
const novoAnexo = await resAnexo.json();
console.log("Anexo enviado com sucesso:", novoAnexo);
setpatientData((prev) => ({
...prev,
anexos: [...(prev.anexos || []), novoAnexo]
}));
} catch (error) {
console.error("Erro no upload do anexo:", error);
}
}
alert("paciente cadastrado")
navigate("/patientlist")
})
.catch(error => console.log('error', error));
/*console.log(patientData);
const{data, error} = await supabase
.from("Patient")
.insert([patientData])
.select()
if(error){
console.log("Erro ao inserir paciente:", error);
}else{
console.log("Paciente inserido com sucesso:", data);
}*/
};
const validarCpf = async (cpf) => {
const cpfLimpo = cpf.replace(/\D/g, "");
try {
const response = await fetch("https://mock.apidog.com/m1/1053378-0-default/pacientes/validar-cpf", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ cpf: cpfLimpo })
});
const data = await response.json();
if (data.valido === false) {
alert("CPF inválido!");
return false;
} else if (data.valido === true) {
return true;
} else {
return false;
}
} catch (error) {
alert("Erro ao validar CPF.");
return false;
}
};
const verificarCpfExistente = async (cpf) => {
const cpfLimpo = cpf.replace(/\D/g, "");
try {
const response = await fetch(`https://mock.apidog.com/m1/1053378-0-default/pacientes?cpf=${cpfLimpo}`);
const data = await response.json();
// Ajuste conforme o formato de resposta da sua API
if (data && data.data && data.data.length > 0) {
return true; // Já existe paciente com esse CPF
}
return false;
} catch (error) {
console.log("Erro ao verificar CPF existente.");
return false;
}
};
return (
<div className="main-wrapper">
<div className="page-wrapper">
<div className="content">
<div className="row">
<div className="col-lg-8 offset-lg-2">
<h2 className="">Dados pessoais</h2>
<hr />
</div>
</div>
<div className="row">
<div className="col-lg-8 offset-lg-2">
<form onSubmit={handleSubmit}>
<div className="row">
<div className="col-sm-6">
<div className="form-group">
<label>Avatar</label>
<div className="profile-upload">
<div className="upload-img">
<img alt="" src="assets/img/user.jpg" />
</div>
<div className="row">
<div className="col-md-9">
<div className="upload-input">
<input
name="foto_url"
type="file"
ref={fileRef}
accept="image/png, image/jpeg"
className="form-control"
onChange={(e) => setFotoFile(e.target.files[0])} />
</div>
</div>
<div className="col-md-3">
<div
className="btn btn-primary"
onClick={async () => {
// Remove no frontend
setpatientData(prev => ({ ...prev, foto_url: "" }));
setFotoFile(null); // Limpa no preview
if (fileRef.current) fileRef.current.value = null;
// Remove no backend e mostra resposta no console
if (patientData.id) {
try {
const response = await fetch(`https://mock.apidog.com/m1/1053378-0-default/pacientes/${patientData.id}/foto`, {
method: "DELETE",
});
const data = await response.json();
console.log("Resposta da API ao remover foto:", data);
if (response.ok || response.status === 200) {
console.log("Foto removida com sucesso na API.");
}
} catch (error) {
console.log("Erro ao remover foto:", error);
}
} else {
console.log("Ainda não existe paciente cadastrado para remover foto na API.");
}
}}
>
Limpar
</div>
</div>
</div>
</div>
</div>
<div className="form-group">
<label>
Nome completo<span className="text-danger">*</span>
</label>
<input className="form-control" type="text"
required
name="nome"
value={patientData.nome}
onChange={handleChange}
/>
</div>
<div className="form-group">
<label>RG</label>
<input className="form-control" type="text"
name="rg"
value={patientData.rg}
onChange={handleChange}
/>
</div>
<div className="form-group">
<label>Outros documentos de identidade </label>
<select id="outrosdoc" className="form-control"
name="outros_documentos"
value={patientData.outros_documentos}
onChange={handleChange}
>
<option value="">Selecionar</option>
<option value="cnh">CNH</option>
<option value="passaporte">Passaporte</option>
</select>
</div>
<div className="form-group">
<label>Raça</label>
<select
name="raça"
id="raça"
className="form-control"
value={patientData.raça}
onChange={handleChange}
>
<option value="">Selecionar</option>
<option value="casa">Preta</option>
<option value="branca">Branca</option>
<option value="parda">Parda</option>
<option value="amarela">Amarela</option>
<option value="Indigena">Indígena</option>
</select>
</div>
<div className="form-group">
<label>Profissão</label>
<input className="form-control" type="text"
name="profissao"
value={patientData.profissao}
onChange={handleChange}
/>
</div>
<div className="form-group">
<label>Nome da mãe</label>
<input className="form-control" type="text"
name="nome_mae"
value={patientData.nome_mae}
onChange={handleChange}
/>
</div>
<div className="form-group">
<label>Profissão da mãe</label>
<input className="form-control" type="text"
name="profissao_mae"
value={patientData.profissao_mae}
onChange={handleChange}
/>
</div>
<div className="form-group">
<label>Nome do responsável</label>
<input className="form-control" type="text"
name="nome_responsavel"
value={patientData.nome_responsavel}
onChange={handleChange}
/>
</div>
<div className="form-check-inline">
<label className="form-check-label">
<input type="checkbox" name="rn" className="form-check-input"
value={true}
checked={patientData.rn === true}
onChange={(e) => setpatientData({ ...patientData, rn: e.target.checked })}
/>
RN na Guia do convênio
</label>
</div>
</div>
<div className="col-sm-6">
<div className="form-group">
<label>Nome social</label>
<input className="form-control" type="text"
name="nome_social"
value={patientData.nome_social}
onChange={handleChange}
/>
</div>
<div className="form-group">
<label>CPF</label><span className="text-danger">*</span>
<input className="form-control" type="text" ref={withMask('cpf')}
name="cpf"
value={patientData.cpf}
onChange={handleChange}
/>
</div>
<div className="form-group">
<label>Número do documento</label>
<input className="form-control" type="text"
name="numero_documento"
value={patientData.numero_documento}
onChange={handleChange} />
</div>
<div className="form-group">
<label>Estado civil</label>
<select id="civil" className="form-control"
name="estado_civil"
value={patientData.estado_civil}
onChange={handleChange}
>
<option value="">Selecionar</option>
<option value="solteiro">Solteiro(a)</option>
<option value="casado">Casado(a)</option>
<option value="viúvo">Viúvo(a)</option>
<option value="amarela">Separado(a)</option>
</select>
</div>
<div className="form-group">
<label>Data de Nascimento</label><span className="text-danger">*</span>
<input type="date" className="form-control"
name="data_nascimento"
value={patientData.data_nascimento}
onChange={handleChange}
/>
</div>
<div className="form-group">
<label>Nome do pai</label>
<input className="form-control" type="text"
name="nome_pai"
value={patientData.nome_pai}
onChange={handleChange} />
</div>
<div className="form-group">
<label>Profissão do pai</label>
<input className="form-control" type="text"
name="profissao_pai"
value={patientData.profissao_pai}
onChange={handleChange}
/>
</div>
<div className="form-group">
<label>CPF do responsável</label>
<input className="form-control" type="text" ref={withMask('cpf')}
name="cpf_responsavel"
value={patientData.cpf_responsavel}
onChange={handleChange}
/>
</div>
<div className="form-group">
<label>Código legado</label>
<input className="form-control" type="text"
name="codigo_legado"
value={patientData.codigo_legado}
onChange={handleChange}
/>
</div>
<div className="form-group gender-select">
<label className="gen-label">Sexo:<span className="text-danger">*</span></label>
<div className="form-check-inline">
<label className="form-check-label">
<input type="radio" name="sexo" className="form-check-input"
value={"masculino"}
checked={patientData.sexo === "masculino"}
onChange={handleChange}
/> Masculino
</label>
</div>
<div className="form-check-inline">
<label className="form-check-label">
<input type="radio" name="sexo" className="form-check-input"
value={"feminino"}
checked={patientData.sexo === "feminino"}
onChange={handleChange}
/> Feminino
</label>
</div>
<div className="form-check-inline">
<label className="form-check-label">
<input type="radio" name="sexo" className="form-check-input"
value={"outro"}
checked={patientData.sexo === "outro"}
onChange={handleChange}
/> Outro
</label>
</div>
</div>
</div>
<div className="col-sm-12">
<hr />
<h2>Contato</h2>
</div>
<div className="col-sm-6">
<div className="form-group">
<label>Celular</label><span className="text-danger">*</span>
<input className="form-control" type="text" ref={withMask('+55 (99) 99999-9999')}
name="celular"
value={patientData.celular}
onChange={handleChange} />
</div>
<div className="form-group">
<label>Telefone 1</label>
<input className="form-control" type="text" ref={withMask('+55 (99) 99999-9999')}
name="telefone1"
value={patientData.telefone1}
onChange={handleChange}
/>
</div>
</div>
<div className="col-sm-6">
<div className="form-group">
<label>Email</label><span className="text-danger">*</span>
<input className="form-control" type="email"
name="email"
value={patientData.email}
onChange={handleChange}
/>
</div>
<div className="form-group">
<label>Telefone 2</label>
<input className="form-control" type="text" ref={withMask('+55 (99) 99999-9999')}
name="telefone2"
value={patientData.telefone2}
onChange={handleChange}
/>
</div>
</div>
<div className="col-sm-12">
<hr />
<h2>Endereço</h2>
</div>
<div className="col-sm-6">
<div className="form-group">
<label>CEP</label><span className="text-danger">*</span>
<input className="form-control" type="text"
name="cep"
value={patientData.cep}
onChange={handleChange}
onBlur={buscarCep}
/>
</div>
<div className="form-group">
<label>Cidade</label><span className="text-danger">*</span>
<input className="form-control" type="text"
id="cidade"
name="cidade"
value={patientData.cidade}
onChange={handleChange}
/>
</div>
<div className="form-group">
<label>Logradouro<span className="text-danger">*</span></label>
<input className="form-control" type="text"
id="logradouro"
name="logradouro"
value={patientData.logradouro}
onChange={handleChange}
/>
</div>
<div className="form-group">
<label>Complemento</label>
<input className="form-control" type="text"
id="complemento"
name="complemento"
value={patientData.complemento}
onChange={handleChange}
/>
</div>
</div>
<div className="col-sm-6">
<div className="form-group">
<label>Estado<span className="text-danger">*</span></label>
<input className="form-control" type="text"
id="estado"
name="estado"
value={patientData.estado}
onChange={handleChange}
/>
</div>
<div className="form-group">
<label>Número<span className="text-danger">*</span></label>
<input className="form-control" type="text"
name="numero"
value={patientData.numero}
onChange={handleChange}
/>
</div>
<div className="form-group">
<label>Bairro</label>
<input className="form-control" type="text"
id="bairro"
name="bairro"
value={patientData.bairro}
onChange={handleChange}
/>
</div>
<div className="form-group">
<label>Referência </label>
<input className="form-control" type="text"
name="referencia"
value={patientData.referencia}
onChange={handleChange}
/>
</div>
</div>
</div>
<div className="col-sm-12">
<hr />
</div>
<div className="form-group">
<label className="display-block" >Status<span className="text-danger">*</span></label>
<div className="form-check form-check-inline">
<input
className="form-check-input"
type="radio"
name="status"
id="patient_active"
value={"ativo"}
checked={patientData.status === "ativo"}
onChange={handleChange}
/>
<label className="form-check-label" htmlFor="patient_active">
Ativo
</label>
</div>
<div className="form-check form-check-inline">
<input
className="form-check-input"
type="radio"
name="status"
id="patient_inactive"
value="inativo"
checked={patientData.status === "inativo"}
onChange={handleChange}
/>
<label className="form-check-label" htmlFor="patient_inactive">
Inativo
</label>
</div>
</div>
<div className="form-group">
<label>Observação</label>
<textarea className="form-control" rows="3"
name="observaçao"
value={patientData.observaçao}
onChange={handleChange}
></textarea>
</div>
<div className="form-group">
<label>Documentos</label>
<div className="profile-upload">
<div className="upload-img">
<img alt="" src="assets/img/user.jpg" />
</div>
<div className="row">
<div className="col-md-9">
<div className="upload-input">
<input
type="file"
accept="image/png, image/jpeg, application/pdf"
className="form-control"
onChange={(e) => {
const file = e.target.files[0];
if (file) {
setpatientData((prev) => ({ ...prev, documentoFile: file }));
}
}}
/>
</div>
</div>
<div className="col-md-3">
{patientData.anexos?.length > 0 && (
<button
type="button"
className="btn btn-danger"
onClick={async () => {
// Remove o primeiro anexo (ou adapte para remover específico)
const anexoId = patientData.anexos[0].id;
try {
await fetch(
`https://mock.apidog.com/m1/1053378-0-default/pacientes/${patientData.id}/anexos/${anexoId}`,
{ method: "DELETE", headers: { Authorization: "Bearer <token>" } }
);
setpatientData((prev) => ({
...prev,
anexos: prev.anexos.filter((a) => a.id !== anexoId)
}));
alert("Anexo removido com sucesso!");
} catch (err) {
console.error("Erro ao remover anexo:", err);
}
}}
>
Remover
</button>
)}
</div>
</div>
{/* Lista anexos */}
{patientData.anexos?.length > 0 &&
patientData.anexos.map((anexo) => (
<div key={anexo.id} className="mt-2">
<a href={anexo.url} target="_blank" rel="noreferrer">
{anexo.nome || "Documento"}
</a>
</div>
))}
</div>
</div>
<div className="m-t-20 text-center">
<button
className="btn btn-primary submit-btn"
type="submit"
>Criar Paciente</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
);
};
export default Patientform;

View File

@ -0,0 +1,242 @@
import "../../assets/css/index.css"
function AddSchedule(){
return (
<div className="main-wrapper">
<div className="header">
<a id="toggle_btn" href="#">
<i className="fa fa-bars"></i>
</a>
<a id="mobile_btn" className="mobile_btn float-left" href="#sidebar">
<i className="fa fa-bars"></i>
</a>
<ul className="nav user-menu float-right">
<li className="nav-item dropdown d-none d-sm-block">
<a
href="#"
className="dropdown-toggle nav-link"
data-toggle="dropdown"
>
<i className="fa fa-bell-o"></i>{" "}
<span className="badge badge-pill bg-danger float-right">3</span>
</a>
<div className="dropdown-menu notifications">
<div className="topnav-dropdown-header">
<span>Notifications</span>
</div>
<div className="drop-scroll">
<ul className="notification-list">
<li className="notification-message">
<a href="activities.html">
<div className="media">
<span className="avatar">
<img
alt="John Doe"
src="assets/img/user.jpg"
className="img-fluid rounded-circle"
/>
</span>
<div className="media-body">
<p className="noti-details">
<span className="noti-title">John Doe</span> added
new task{" "}
<span className="noti-title">
Patient appointment booking
</span>
</p>
<p className="noti-time">
<span className="notification-time">4 mins ago</span>
</p>
</div>
</div>
</a>
</li>
{/* ... outras notificações */}
</ul>
</div>
<div className="topnav-dropdown-footer">
<a href="activities.html">View all Notifications</a>
</div>
</div>
</li>
<li className="nav-item dropdown has-arrow">
<a
href="#"
className="dropdown-toggle nav-link user-link"
data-toggle="dropdown"
>
<span className="user-img">
<img
className="rounded-circle"
src="assets/img/user.jpg"
width="40"
alt="Admin"
/>
<span className="status online"></span>
</span>
<span>Admin</span>
</a>
<div className="dropdown-menu">
<a className="dropdown-item" href="profile.html">
My Profile
</a>
<a className="dropdown-item" href="edit-profile.html">
Edit Profile
</a>
<a className="dropdown-item" href="settings.html">
Settings
</a>
<a className="dropdown-item" href="login.html">
Logout
</a>
</div>
</li>
</ul>
<div className="dropdown mobile-user-menu float-right">
<a
href="#"
className="nav-link dropdown-toggle"
data-toggle="dropdown"
aria-expanded="false"
>
<i className="fa fa-ellipsis-v"></i>
</a>
<div className="dropdown-menu dropdown-menu-right">
<a className="dropdown-item" href="profile.html">
My Profile
</a>
<a className="dropdown-item" href="edit-profile.html">
Edit Profile
</a>
<a className="dropdown-item" href="settings.html">
Settings
</a>
<a className="dropdown-item" href="login.html">
Logout
</a>
</div>
</div>
</div>
{/* Conteúdo da página */}
<div className="page-wrapper">
<div className="content">
<div className="row">
<div className="col-lg-8 offset-lg-2">
<h4 className="page-title">Adicionar Agenda</h4>
</div>
</div>
<div className="row">
<div className="col-lg-8 offset-lg-2">
<form>
<div className="row">
<div className="col-md-6">
<div className="form-group">
<label>Nome</label>
<select className="select form-control">
<option>Selecionar</option>
<option>Doctor Name 1</option>
<option>Doctor Name 2</option>
</select>
</div>
</div>
<div className="col-md-6">
<div className="form-group">
<label>Dias disponíveis</label>
<select className="form-control">
<option>Selecione</option>
<option>Segunda-feira</option>
<option>Terça-feira</option>
<option>Quarta-feira</option>
<option>Quinta-feira</option>
<option>Sexta-feira</option>
<option>Sábado</option>
</select>
</div>
</div>
</div>
<div className="row">
<div className="col-md-6">
<div className="form-group">
<label>Hora</label>
<input
type="time"
className="form-control"
id="datetimepicker3"
/>
</div>
</div>
<div className="col-md-6">
<div className="form-group">
<label>Fim</label>
<input
type="time"
className="form-control"
id="datetime"
/>
</div>
</div>
</div>
<div className="form-group">
<label>Mensagem</label>
<textarea
cols="30"
rows="4"
className="form-control"
></textarea>
</div>
<div className="form-group">
<label className="display-block">Status</label>
<div className="form-check form-check-inline">
<input
className="form-check-input"
type="radio"
name="status"
id="product_active"
value="option1"
defaultChecked
/>
<label className="form-check-label" htmlFor="product_active">
Ativo
</label>
</div>
<div className="form-check form-check-inline">
<input
className="form-check-input"
type="radio"
name="status"
id="product_inactive"
value="option2"
/>
<label
className="form-check-label"
htmlFor="product_inactive"
>
Inativo
</label>
</div>
</div>
<div className="m-t-20 text-center">
<button className="btn btn-primary submit-btn">
Criar agenda
</button>
</div>
</form>
</div>
</div>
</div>
</div>
<div className="sidebar-overlay" data-reff=""></div>
</div>
);
};
export default AddSchedule;

View File

@ -0,0 +1,107 @@
import "../../assets/css/index.css"
import { Link } from "react-router-dom";
function Doctorschedule() {
return (
<div className="main-wrapper">
<div className="page-wrapper">
<div className="content">
<div className="row">
<div className="col-sm-4 col-3">
<h4 className="page-title">Agenda médica</h4>
</div>
<div className="col-sm-8 col-9 text-right m-b-20">
<Link
to ="/addschedule"
className="btn btn-primary btn-rounded float-right"
>
<i className="fa fa-plus"></i> Adicionar agenda
</Link>
</div>
</div>
<div className="row">
<div className="col-md-12">
<div className="table-responsive">
<table className="table table-border table-striped custom-table mb-0">
<thead>
<tr>
<th>Nome</th>
<th>Departamento</th>
<th>Dias disponíveis</th>
<th>Horário disponível</th>
<th>Status</th>
<th className="text-right">Ação</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<img
width="28"
height="28"
src="/img/user.jpg"
className="rounded-circle m-r-5"
alt="user"
/>{" "}
Henry Daniels
</td>
<td>Cardiologista</td>
<td>Segunda-feira, Terça-feira, Quinta-feira</td>
<td>10:00 AM - 7:00 PM</td>
<td>
<span className="custom-badge status-green">Ativo</span>
</td>
<td className="text-right">
<div className="dropdown dropdown-action">
<a
href="#"
className="action-icon dropdown-toggle"
>
<i className="fa fa-ellipsis-v"></i>
</a>
<div className="dropdown-menu dropdown-menu-right">
<a className="dropdown-item" href="edit-schedule.html">
<i className="fa fa-pencil m-r-5"></i> Editar
</a>
<a
className="dropdown-item"
href="#"
>
<i className="fa fa-trash-o m-r-5"></i> Deletar
</a>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
{/* Modal de exclusão */}
<div id="delete_schedule" className="modal fade delete-modal" role="dialog">
<div className="modal-dialog modal-dialog-centered">
<div className="modal-content">
<div className="modal-body text-center">
<img src="assets/img/sent.png" alt="" width="50" height="46" />
<h3>Você tem certeza que deseja deletar essa agenda?</h3>
<div className="m-t-20">
<a href="#" className="btn btn-white">
Fechar
</a>
<button type="submit" className="btn btn-danger">
Deletar
</button>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
export default Doctorschedule

View File

@ -0,0 +1,365 @@
import React, { useState } from "react";
import FullCalendar from "@fullcalendar/react";
import dayGridPlugin from "@fullcalendar/daygrid";
import timeGridPlugin from "@fullcalendar/timegrid";
import interactionPlugin from "@fullcalendar/interaction";
import ptBrLocale from "@fullcalendar/core/locales/pt-br";
import "../../assets/css/index.css";
function Calendar1() {
const [events, setEvents] = useState([]);
const [editingEvent, setEditingEvent] = useState(null);
const [showPopup, setShowPopup] = useState(false);
const [showActionModal, setShowActionModal] = useState(false);
const [step, setStep] = useState(1);
const [newEvent, setNewEvent] = useState({ title: "", time: "" });
const [selectedDate, setSelectedDate] = useState(null);
const [selectedEvent, setSelectedEvent] = useState(null);
const colorsByType = {
Rotina: "#4dabf7",
Cardiologia: "#f76c6c",
Otorrino: "#f7b84d",
Pediatria: "#6cf78b"
};
// Clicar em um dia -> abrir popup 3 etapas
const handleDateClick = (arg) => {
setSelectedDate(arg.dateStr);
setNewEvent({ title: "", time: "" });
setStep(1);
setEditingEvent(null);
setShowPopup(true);
};
// Adicionar nova consulta
const handleAddEvent = () => {
const eventToAdd = {
id: Date.now(), // number
title: newEvent.title,
time: newEvent.time,
date: selectedDate,
color: colorsByType[newEvent.type] || "#4dabf7"
};
setEvents((prev) => [...prev, eventToAdd]);
setShowPopup(false);
};
// Editar consulta existente
const handleEditEvent = () => {
setEvents((prevEvents) =>
prevEvents.map((ev) =>
ev.id.toString() === editingEvent.id.toString()
? {
...ev,
title: newEvent.title,
time: newEvent.time,
color: colorsByType[newEvent.type] || "#4dabf7"
}
: ev
)
);
setEditingEvent(null);
setShowPopup(false);
setShowActionModal(false);
};
// Próxima etapa no popup
const handleNextStep = () => {
if (step < 2) setStep(step + 1);
else editingEvent ? handleEditEvent() : handleAddEvent();
};
// Clicar em uma consulta -> abre modal de ação (Editar/Apagar)
const handleEventClick = (clickInfo) => {
setSelectedEvent(clickInfo.event);
setShowActionModal(true);
};
// Apagar consulta
const handleDeleteEvent = () => {
if (!selectedEvent) return;
setEvents((prevEvents) =>
prevEvents.filter(
(ev) => ev.id.toString() !== selectedEvent.id.toString()
)
);
setShowActionModal(false);
};
// Começar a editar
const handleStartEdit = () => {
if (!selectedEvent) return;
setEditingEvent(selectedEvent);
setNewEvent({
title: selectedEvent.title,
time: selectedEvent.extendedProps.time
});
setStep(1);
setShowActionModal(false);
setShowPopup(true);
};
// Aparência da consulta dentro do calendário
const renderEventContent = (eventInfo) => {
const bg =
eventInfo.event.backgroundColor ||
eventInfo.event.extendedProps?.color ||
"#4dabf7";
return (
<div
style={{
display: "flex",
alignItems: "center",
gap: 6,
fontSize: "0.72rem",
padding: "1px 6px",
lineHeight: 1.1,
borderRadius: 4,
backgroundColor: bg,
color: "#fff",
cursor: "pointer",
// Mantém o retângulo pequeno e evita estourar a célula:
maxWidth: "100%",
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis"
}}
title={`${eventInfo.event.title}${eventInfo.event.extendedProps.type}${eventInfo.event.extendedProps.time}`} // tooltip
>
<span style={{ minWidth: 0, overflow: "hidden", textOverflow: "ellipsis" }}>
{eventInfo.event.title}
</span>
<span></span>
<span>{eventInfo.event.extendedProps.time}</span>
</div>
);
};
return (
<div
className="calendar-container"
style={{
marginLeft: "250px",
paddingTop: 60, // empurra o calendário p/ baixo
display: "flex",
justifyContent: "center"
}}
>
<div style={{ width: "95%", maxWidth: "none" }}>
<FullCalendar
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]}
initialView="dayGridMonth"
locale={ptBrLocale}
height="80vh"
dateClick={handleDateClick}
events={events.map((ev) => ({
id: ev.id,
title: ev.title,
date: ev.date,
color: ev.color, // para o FullCalendar
extendedProps: {
type: ev.type,
time: ev.time,
color: ev.color // para o nosso renderEventContent
}
}))}
eventContent={renderEventContent}
eventClick={handleEventClick}
dayCellClassNames="calendar-day-cell"
/>
</div>
{/* POPUP 3 etapas (Adicionar/Editar) */}
{showPopup && (
<div
style={{
position: "fixed",
inset: 0,
backgroundColor: "rgba(0,0,0,0.5)",
display: "flex",
justifyContent: "center",
alignItems: "center",
zIndex: 1000
}}
>
<div
style={{
backgroundColor: "#fff",
padding: 20,
borderRadius: 8,
width: 320
}}
>
{step === 1 && (
<>
<h3 style={{ marginBottom: 8 }}>Nome do paciente</h3>
<input
type="text"
value={newEvent.title}
onChange={(e) =>
setNewEvent({ ...newEvent, title: e.target.value })
}
placeholder="Digite o nome"
style={{
width: "100%",
marginBottom: 12,
padding: 8,
borderRadius: 6,
border: "1px solid #ddd"
}}
/>
<button
onClick={handleNextStep}
disabled={!newEvent.title}
style={{
width: "100%",
padding: 10,
borderRadius: 6,
border: "none",
background: newEvent.title ? "#4dabf7" : "#c7dbf8",
color: "#fff",
cursor: newEvent.title ? "pointer" : "not-allowed"
}}
>
Próximo
</button>
</>
)}
{step === 2 && (
<>
<h3 style={{ marginBottom: 8 }}>Horário</h3>
<input
type="time"
value={newEvent.time}
onChange={(e) =>
setNewEvent({ ...newEvent, time: e.target.value })
}
style={{
width: "100%",
marginBottom: 12,
padding: 8,
borderRadius: 6,
border: "1px solid #ddd"
}}
/>
<button
onClick={handleNextStep}
disabled={!newEvent.time}
style={{
width: "100%",
padding: 10,
borderRadius: 6,
border: "none",
background: newEvent.time ? "#4dabf7" : "#c7dbf8",
color: "#fff",
cursor: newEvent.time ? "pointer" : "not-allowed"
}}
>
{editingEvent ? "Salvar Alterações" : "Adicionar"}
</button>
</>
)}
<button
onClick={() => setShowPopup(false)}
style={{
marginTop: 10,
width: "100%",
padding: 10,
borderRadius: 6,
border: "none",
background: "#ccc",
color: "#222",
cursor: "pointer"
}}
>
Cancelar
</button>
</div>
</div>
)}
{/* MODAL de ação ao clicar em consulta */}
{showActionModal && selectedEvent && (
<div
style={{
position: "fixed",
inset: 0,
backgroundColor: "rgba(0,0,0,0.5)",
display: "flex",
justifyContent: "center",
alignItems: "center",
zIndex: 1100
}}
>
<div
style={{
backgroundColor: "#fff",
padding: 20,
borderRadius: 8,
width: 320,
textAlign: "center"
}}
>
<h3 style={{ marginBottom: 4 }}>Consulta de {selectedEvent.title}</h3>
<p style={{ margin: 0 }}>
{selectedEvent.extendedProps.type} às {selectedEvent.extendedProps.time}
</p>
<div style={{ display: "flex", gap: 10, marginTop: 16 }}>
<button
onClick={handleStartEdit}
style={{
flex: 1,
padding: 10,
borderRadius: 6,
border: "none",
backgroundColor: "#4dabf7",
color: "#fff",
cursor: "pointer"
}}
>
Editar
</button>
<button
onClick={handleDeleteEvent}
style={{
flex: 1,
padding: 10,
borderRadius: 6,
border: "none",
backgroundColor: "#f76c6c",
color: "#fff",
cursor: "pointer"
}}
>
Apagar
</button>
</div>
<button
onClick={() => setShowActionModal(false)}
style={{
marginTop: 10,
width: "100%",
padding: 10,
borderRadius: 6,
border: "none",
background: "#ccc",
color: "#222",
cursor: "pointer"
}}
>
Cancelar
</button>
</div>
</div>
)}
</div>
);
}
export default Calendar1;

View File

@ -0,0 +1,50 @@
// PatientList.jsx
import { useEditor, EditorContent } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
import Bar from '../../components/Bar';
import Image from '@tiptap/extension-image';
function Laudo() {
const editor = useEditor({
extensions: [StarterKit, Image],
content: ""
})
const comandos = {
toggleBold: () => editor.chain().focus().toggleBold().run(),
toggleItalic: () => editor.chain().focus().toggleItalic().run(),
toggleUnderline: () => editor.chain().focus().toggleUnderline().run(),
toggleCodeBlock: () => editor.chain().focus().toggleCodeBlock().run(),
toggleH1: () => editor.chain().focus().toggleHeading({ level: 1 }).run(),
toggleH2: () => editor.chain().focus().toggleHeading({ level: 2 }).run(),
toggleH3: () => editor.chain().focus().toggleHeading({ level: 3 }).run(),
toggleParrafo: () => editor.chain().focus().setParagraph().run(),
toggleListaOrdenada: () => editor.chain().focus().toggleOrderedList().run(),
toggleListaPuntos: () => editor.chain().focus().toggleBulletList().run(),
agregarImagen: () => {
const url = window.prompt('URL da imagem')
editor.chain().focus().setImage({ src: url }).run();
},
agregarLink: () => {
const url = window.prompt('URL do link')
if (url) {
editor.chain().focus().setLink({ href: url }).run()
}
}
}
return (
<div className="main-wrapper">
<div className="page-wrapper">
<div className="content">
<h4 className="page-title">Laudo Médico</h4>
<Bar comandos={comandos} />
<EditorContent editor={editor} />
</div>
</div>
</div>
);
}
export default Laudo;
;

View File

@ -0,0 +1,288 @@
import { Link } from "react-router-dom";
import "../../assets/css/index.css";
import React, { useState, useRef, useLayoutEffect, useEffect } from "react";
import { createPortal } from "react-dom";
function DropdownPortal({ anchorEl, isOpen, onClose, className, children }) {
const menuRef = useRef(null);
const [stylePos, setStylePos] = useState({
position: "absolute",
top: 0,
left: 0,
visibility: "hidden",
zIndex: 1000,
});
useLayoutEffect(() => {
if (!isOpen || !anchorEl || !menuRef.current) return;
const anchorRect = anchorEl.getBoundingClientRect();
const menuRect = menuRef.current.getBoundingClientRect();
const scrollY = window.scrollY || window.pageYOffset;
const scrollX = window.scrollX || window.pageXOffset;
let left = anchorRect.right + scrollX - menuRect.width;
let top = anchorRect.bottom + scrollY;
if (left < 0) left = scrollX + 4;
if (top + menuRect.height > window.innerHeight + scrollY) {
top = anchorRect.top + scrollY - menuRect.height;
}
setStylePos({
position: "absolute",
top: `${Math.round(top)}px`,
left: `${Math.round(left)}px`,
visibility: "visible",
zIndex: 1000,
});
}, [isOpen, anchorEl, children]);
useEffect(() => {
if (!isOpen) return;
const handleDocClick = (e) => {
if (menuRef.current && !menuRef.current.contains(e.target) &&
anchorEl && !anchorEl.contains(e.target)) {
onClose();
}
};
const handleScroll = () => onClose();
document.addEventListener("mousedown", handleDocClick);
document.addEventListener("scroll", handleScroll, true);
return () => {
document.removeEventListener("mousedown", handleDocClick);
document.removeEventListener("scroll", handleScroll, true);
};
}, [isOpen, onClose, anchorEl]);
if (!isOpen) return null;
return createPortal(
<div ref={menuRef} className={className} style={stylePos} onClick={(e) => e.stopPropagation()}>
{children}
</div>,
document.body
);
}
function LaudoList() {
const [search, setSearch] = useState("");
const [period, setPeriod] = useState(""); // "", "today", "week", "month"
const [startDate, setStartDate] = useState("");
const [endDate, setEndDate] = useState("");
const [laudos, setLaudos] = useState([
{
id: 1,
pedido: 12345,
data: "2024-10-01",
prazo: "2024-10-05",
paciente: "Davi Andrade",
cpf: "12345678900",
tipo: "Radiologia",
status: "Pendente",
executante: "Dr. Silva",
exame: "Raio-X de Tórax"
},
{
id: 2,
pedido: 12346,
data: "2024-10-02",
prazo: "2024-10-06",
paciente: "Maria Souza",
cpf: "98765432100",
tipo: "Cardiologia",
status: "Concluído",
executante: "Dra. Lima",
exame: "Eletrocardiograma"
},
{
id: 3,
pedido: 12347,
data: "2024-10-03",
prazo: "2024-10-07",
paciente: "João Pereira",
cpf: "45678912300",
tipo: "Neurologia",
status: "Em Andamento",
executante: "Dr. Costa",
exame: "Ressonância Magnética"
},
{
id: 4,
pedido: 12348,
data: "2024-10-04",
prazo: "2024-10-08",
paciente: "Ana Oliveira",
cpf: "32165498700",
tipo: "Ortopedia",
status: "Pendente",
executante: "Dra. Fernandes",
exame: "Tomografia Computadorizada"
},
]);
const [openDropdown, setOpenDropdown] = useState(null);
const anchorRefs = useRef({});
const handleDelete = (id) => {
if (!window.confirm("Tem certeza que deseja excluir este laudo?")) return;
setLaudos(prev => prev.filter(l => l.id !== id));
setOpenDropdown(null);
};
const filteredLaudos = laudos.filter(l => {
const q = search.toLowerCase();
const textMatch =
(l.paciente || "").toLowerCase().includes(q) ||
(l.cpf || "").toLowerCase().includes(q) ||
(l.tipo || "").toLowerCase().includes(q) ||
(l.status || "").toLowerCase().includes(q) ||
(l.pedido || "").toString().toLowerCase().includes(q) ||
(l.prazo || "").toLowerCase().includes(q) ||
(l.executante || "").toLowerCase().includes(q) ||
(l.exame || "").toLowerCase().includes(q) ||
(l.data || "").toLowerCase().includes(q);
let dateMatch = true;
const today = new Date();
const laudoDate = new Date(l.data);
if (period === "today") {
dateMatch = laudoDate.toDateString() === today.toDateString();
} else if (period === "week") {
const startOfWeek = new Date(today);
startOfWeek.setDate(today.getDate() - today.getDay());
const endOfWeek = new Date(startOfWeek);
endOfWeek.setDate(startOfWeek.getDate() + 6);
dateMatch = laudoDate >= startOfWeek && laudoDate <= endOfWeek;
} else if (period === "month") {
dateMatch = laudoDate.getMonth() === today.getMonth() && laudoDate.getFullYear() === today.getFullYear();
}
if (startDate && endDate) {
dateMatch = dateMatch && l.data >= startDate && l.data <= endDate;
} else if (startDate) {
dateMatch = dateMatch && l.data >= startDate;
} else if (endDate) {
dateMatch = dateMatch && l.data <= endDate;
}
return textMatch && dateMatch;
});
const mascararCPF = (cpf = "") => {
if (cpf.length < 5) return cpf;
return `${cpf.slice(0,3)}.***.***-${cpf.slice(-2)}`;
};
return (
<div className="main-wrapper">
<div className="page-wrapper">
<div className="content">
<h4 className="page-title">Laudos</h4>
{/* Linha de pesquisa e filtros */}
<div className="row align-items-center mb-2">
{/* Esquerda: pesquisa */}
<div className="col d-flex align-items-center">
<input
type="text"
className="form-control"
placeholder="🔍 Buscar laudo"
value={search}
onChange={e => setSearch(e.target.value)}
style={{ minWidth: "200px" }}
/>
</div>
{/* Direita: filtros de data + botões */}
<div className="col-auto d-flex align-items-center" style={{ gap: "0.5rem", justifyContent: "flex-end" }}>
{/* Filtros de data primeiro */}
<div className="date-filter">
<label>De:</label>
<input type="date" value={startDate} onChange={e => setStartDate(e.target.value)} />
<label>Até:</label>
<input type="date" value={endDate} onChange={e => setEndDate(e.target.value)} />
</div>
{/* Botões rápidos */}
<div className="quick-filter">
<button className={`btn-filter ${period==="today"?"active":""}`} onClick={()=>setPeriod("today")}>Hoje</button>
<button className={`btn-filter ${period==="week"?"active":""}`} onClick={()=>setPeriod("week")}>Semana</button>
<button className={`btn-filter ${period==="month"?"active":""}`} onClick={()=>setPeriod("month")}>Mês</button>
</div>
</div>
</div>
{/* Tabela */}
<div className="row">
<div className="col-12">
<div className="table-responsive">
<table className="table table-border table-striped custom-table datatable mb-0">
<thead>
<tr>
<th>Pedido</th>
<th>Data</th>
<th>Prazo</th>
<th>Paciente</th>
<th>CPF</th>
<th>Tipo</th>
<th>Status</th>
<th>Executante</th>
<th>Exame</th>
<th className="text-right">Ações</th>
</tr>
</thead>
<tbody>
{filteredLaudos.length>0 ? filteredLaudos.map(l=>(
<tr key={l.id}>
<td className="nowrap">{l.pedido}</td>
<td className="nowrap">{l.data}</td>
<td className="nowrap">{l.prazo}</td>
<td>{l.paciente}</td>
<td className="nowrap">{mascararCPF(l.cpf)}</td>
<td>{l.tipo}</td>
<td>{l.status}</td>
<td>{l.executante}</td>
<td className="ellipsis">{l.exame}</td>
<td className="text-right">
<div className="dropdown dropdown-action">
<button type="button" ref={el=>anchorRefs.current[l.id]=el} className="action-icon"
onClick={e=>{e.stopPropagation(); setOpenDropdown(openDropdown===l.id?null:l.id);}}>
<i className="fa fa-ellipsis-v"></i>
</button>
<DropdownPortal anchorEl={anchorRefs.current[l.id]} isOpen={openDropdown===l.id}
onClose={()=>setOpenDropdown(null)} className="dropdown-menu dropdown-menu-right show">
<Link className="dropdown-item-custom" to={`/laudo`} onClick={e=>{e.stopPropagation(); setOpenDropdown(null);}}>
<i className="fa fa-file-text"></i> Laudo
</Link>
<button className="dropdown-item-custom dropdown-item-delete" onClick={()=>handleDelete(l.id)}>
<i className="fa fa-trash-o m-r-5"></i> Excluir
</button>
</DropdownPortal>
</div>
</td>
</tr>
)) : (
<tr>
<td colSpan="10" className="text-center text-muted">Nenhum laudo encontrado</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
export default LaudoList;

5
vercel.json Normal file
View File

@ -0,0 +1,5 @@
{
"rewrites": [
{ "source": "/(.*)", "destination": "/" }
]
}

7
vite.config.js Normal file
View File

@ -0,0 +1,7 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
})