primeiro commit
24
.gitignore
vendored
Normal 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
@ -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
@ -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
@ -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
44
package.json
Normal 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
|
After Width: | Height: | Size: 396 B |
BIN
public/img/blog/blog-01.jpg
Normal file
|
After Width: | Height: | Size: 109 KiB |
BIN
public/img/blog/blog-02.jpg
Normal file
|
After Width: | Height: | Size: 145 KiB |
BIN
public/img/blog/blog-03.jpg
Normal file
|
After Width: | Height: | Size: 101 KiB |
BIN
public/img/blog/blog-04.jpg
Normal file
|
After Width: | Height: | Size: 118 KiB |
BIN
public/img/blog/blog-thumb-01.jpg
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
public/img/blog/blog-thumb-02.jpg
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
public/img/blog/blog-thumb-03.jpg
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
public/img/blog/blog-thumb-04.jpg
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
public/img/calander.png
Normal file
|
After Width: | Height: | Size: 249 B |
BIN
public/img/clock.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
public/img/doctor-03.jpg
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
public/img/doctor-thumb-01.jpg
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
public/img/doctor-thumb-02.jpg
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
BIN
public/img/doctor-thumb-03.jpg
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
public/img/doctor-thumb-04.jpg
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
public/img/doctor-thumb-05.jpg
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
public/img/doctor-thumb-06.jpg
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
public/img/doctor-thumb-07.jpg
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
public/img/doctor-thumb-08.jpg
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
public/img/doctor-thumb-09.jpg
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
public/img/doctor-thumb-10.jpg
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
public/img/doctor-thumb-11.jpg
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
public/img/doctor-thumb-12.jpg
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
public/img/favicon.ico
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
public/img/logo-dark.png
Normal file
|
After Width: | Height: | Size: 9.9 KiB |
BIN
public/img/logo.png
Normal file
|
After Width: | Height: | Size: 9.4 KiB |
BIN
public/img/logomedconnect.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
public/img/logoof.png
Normal file
|
After Width: | Height: | Size: 523 KiB |
BIN
public/img/patient-thumb-01.jpg
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
public/img/patient-thumb-02.jpg
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
public/img/placeholder-thumb.jpg
Normal file
|
After Width: | Height: | Size: 976 B |
BIN
public/img/placeholder.jpg
Normal file
|
After Width: | Height: | Size: 8.9 KiB |
BIN
public/img/sent.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
public/img/user-02.jpg
Normal file
|
After Width: | Height: | Size: 9.3 KiB |
BIN
public/img/user-03.jpg
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
public/img/user-04.jpg
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
public/img/user-05.jpg
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
public/img/user-06.jpg
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/img/user.jpg
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
public/img/video-call.jpg
Normal file
|
After Width: | Height: | Size: 90 KiB |
1
public/vite.svg
Normal 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
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"liveServer.settings.port": 5501
|
||||||
|
}
|
||||||
17
src/App.jsx
Normal 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
@ -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
|
||||||
|
|
||||||
|
|
||||||
5
src/assets/css/bootstrap-datetimepicker.min.css
vendored
Normal file
7
src/assets/css/bootstrap.min.css
vendored
Normal file
1
src/assets/css/dataTables.bootstrap4.min.css
vendored
Normal file
4
src/assets/css/font-awesome.min.css
vendored
Normal file
5
src/assets/css/fullcalendar.min.css
vendored
Normal file
152
src/assets/css/index.css
Normal 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
4915
src/assets/css/style.css
Normal file
66
src/assets/css/tagsinput.css
Normal 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
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
src/assets/fonts/fontawesome-webfont3e6e.eot
Normal file
BIN
src/assets/fonts/fontawesome-webfont3e6e.html
Normal file
2671
src/assets/fonts/fontawesome-webfont3e6e.svg
Normal file
|
After Width: | Height: | Size: 434 KiB |
BIN
src/assets/fonts/fontawesome-webfont3e6e.ttf
Normal file
BIN
src/assets/fonts/fontawesome-webfont3e6e.woff
Normal file
BIN
src/assets/fonts/fontawesome-webfontd41d.eot
Normal file
82
src/components/Bar.jsx
Normal 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
@ -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;
|
||||||
|
|
||||||
68
src/components/Sidebar.jsx
Normal 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
@ -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>
|
||||||
|
);
|
||||||
213
src/pages/Agendar/AgendaEdit.jsx
Normal 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;
|
||||||
213
src/pages/Agendar/AgendaForm.jsx
Normal 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;
|
||||||
231
src/pages/Agendar/AgendaList.jsx
Normal 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;
|
||||||
422
src/pages/Doctor/DoctorEdit.jsx
Normal 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
|
||||||
448
src/pages/Doctor/DoctorForm.jsx
Normal 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
|
||||||
142
src/pages/Doctor/DoctorList.jsx
Normal 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;
|
||||||
127
src/pages/Doctor/DoctorProfile.jsx
Normal 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
|
||||||
100
src/pages/DoctorApp/DoctorApp.jsx
Normal 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;
|
||||||
55
src/pages/DoctorApp/DoctorCalendar.jsx
Normal 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;
|
||||||
|
|
||||||
68
src/pages/DoctorApp/DoctorDashboard.jsx
Normal 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;
|
||||||
66
src/pages/DoctorApp/DoctorPatientList.jsx
Normal 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;
|
||||||
608
src/pages/Patient/PatientEdit.jsx
Normal 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;
|
||||||
264
src/pages/Patient/PatientList.jsx
Normal 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;
|
||||||
|
;
|
||||||
794
src/pages/Patient/Patientform.jsx
Normal 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;
|
||||||
242
src/pages/Schedule/AddSchedule.jsx
Normal 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;
|
||||||
107
src/pages/Schedule/DoctorSchedule.jsx
Normal 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
|
||||||
365
src/pages/calendar/Calendar.jsx
Normal 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;
|
||||||
50
src/pages/laudos/Laudo.jsx
Normal 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;
|
||||||
|
;
|
||||||
288
src/pages/laudos/LaudosList.jsx
Normal 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
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"rewrites": [
|
||||||
|
{ "source": "/(.*)", "destination": "/" }
|
||||||
|
]
|
||||||
|
}
|
||||||
7
vite.config.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
})
|
||||||