From 8f0e616d2bf57c2a3985c04a8bc5f25dcb89d189 Mon Sep 17 00:00:00 2001 From: Squad03_Leticia_Lacerda Date: Mon, 11 May 2026 15:26:55 -0300 Subject: [PATCH] modified: src/App.jsx modified: src/components/AppShell.jsx modified: src/components/Brand.jsx modified: src/index.css modified: src/pages/MedicalRecordsPage.jsx modified: src/pages/PatientsPage.jsx modified: src/pages/ReportsPage.jsx modified: src/pages/SettingsPage.jsx modified: src/repositories/authRepository.js modified: src/repositories/professionalRepository.js modified: src/repositories/repositoryUtils.js modified: src/repositories/settingsRepository.js modified: src/utils/theme.js --- src/App.jsx | 50 +- src/components/AppShell.jsx | 47 +- src/components/Brand.jsx | 24 +- src/index.css | 5 - src/pages/MedicalRecordsPage.jsx | 19 +- src/pages/PatientsPage.jsx | 26 +- src/pages/ReportsPage.jsx | 546 +-------------------- src/pages/SettingsPage.jsx | 162 ------ src/repositories/authRepository.js | 7 +- src/repositories/professionalRepository.js | 6 +- src/repositories/repositoryUtils.js | 62 ++- src/repositories/settingsRepository.js | 11 - src/utils/theme.js | 4 +- 13 files changed, 166 insertions(+), 803 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index da6c9f1..fcd7d78 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,30 +1,36 @@ -import { useCallback, useEffect, useMemo, useState } from 'react' +import { lazy, Suspense, useCallback, useEffect, useMemo, useState } from 'react' import './App.css' import { AppShell } from './components/AppShell.jsx' import { canAccess } from './config/permissions.js' import { useAuth } from './hooks/useAuth.js' -import { AgendaPage } from './pages/AgendaPage.jsx' -import { AnalyticsPage } from './pages/AnalyticsPage.jsx' import { ForgotPasswordPage, LoginPage, RegisterPage } from './pages/AuthPages.jsx' -import { HomePage } from './pages/HomePage.jsx' -import { MedicalRecordsPage } from './pages/MedicalRecordsPage.jsx' -import { MessagesPage } from './pages/MessagesPage.jsx' import { NotFoundPage } from './pages/NotFoundPage.jsx' -import { PatientDetailPage, PatientsPage } from './pages/PatientsPage.jsx' -import { ProfilePage } from './pages/ProfilePage.jsx' -import { ReportsPage } from './pages/ReportsPage.jsx' -import { SettingsPage } from './pages/SettingsPage.jsx' -import { UsersPage } from './pages/UsersPage.jsx' -import { VisitsPage } from './pages/VisitsPage.jsx' import { patientRepository } from './repositories/patientRepository.js' +const AgendaPage = lazyPage(() => import('./pages/AgendaPage.jsx'), 'AgendaPage') +const AnalyticsPage = lazyPage(() => import('./pages/AnalyticsPage.jsx'), 'AnalyticsPage') +const HomePage = lazyPage(() => import('./pages/HomePage.jsx'), 'HomePage') +const MedicalRecordsPage = lazyPage(() => import('./pages/MedicalRecordsPage.jsx'), 'MedicalRecordsPage') +const MessagesPage = lazyPage(() => import('./pages/MessagesPage.jsx'), 'MessagesPage') +const PatientDetailPage = lazyPage(() => import('./pages/PatientsPage.jsx'), 'PatientDetailPage') +const PatientsPage = lazyPage(() => import('./pages/PatientsPage.jsx'), 'PatientsPage') +const ProfilePage = lazyPage(() => import('./pages/ProfilePage.jsx'), 'ProfilePage') +const ReportsPage = lazyPage(() => import('./pages/ReportsPage.jsx'), 'ReportsPage') +const SettingsPage = lazyPage(() => import('./pages/SettingsPage.jsx'), 'SettingsPage') +const UsersPage = lazyPage(() => import('./pages/UsersPage.jsx'), 'UsersPage') +const VisitsPage = lazyPage(() => import('./pages/VisitsPage.jsx'), 'VisitsPage') + const PANEL_PATHS = ['/inicio', '/home', '/dashboard'] const ROLE_HOME_PATHS = { medico: '/agenda', secretaria: '/agenda', } +function lazyPage(loader, exportName) { + return lazy(() => loader().then((module) => ({ default: module[exportName] }))) +} + function App() { const [location, setLocation] = useState(() => readLocation()) const { isAuthenticated, role, loading: authLoading } = useAuth() @@ -72,7 +78,7 @@ function App() { // Rotas públicas (sem shell) if (!route.withShell) { - return route.element + return {route.element} } // Usuário não autenticado @@ -97,11 +103,27 @@ function App() { return ( - {route.element} + {route.element} ) } +function RouteSuspense({ children }) { + return ( + }> + {children} + + ) +} + +function RouteFallback() { + return ( +
+

Carregando...

+
+ ) +} + function resolveRoute(pathname, navigate, role) { if (pathname === '/' || pathname === '/login') { return { diff --git a/src/components/AppShell.jsx b/src/components/AppShell.jsx index ebdd20d..0402cf7 100644 --- a/src/components/AppShell.jsx +++ b/src/components/AppShell.jsx @@ -43,6 +43,7 @@ const titles = { export function AppShell({ children, currentPath, navigate, role, routeTitle }) { const [menuOpen, setMenuOpen] = useState(false) + const [sidebarCollapsed, setSidebarCollapsed] = useState(false) const [profileMenuOpen, setProfileMenuOpen] = useState(false) const [notificationsOpen, setNotificationsOpen] = useState(false) const [viewerProfile, setViewerProfile] = useState({ name: 'Usuário', role: 'Usuário do Sistema' }) @@ -139,6 +140,14 @@ export function AppShell({ children, currentPath, navigate, role, routeTitle }) navigate('/login', { replace: true }) } + function toggleSidebarCollapsed() { + if (typeof window !== 'undefined' && !window.matchMedia('(min-width: 1024px)').matches) { + return + } + + setSidebarCollapsed((current) => !current) + } + return (
@@ -195,7 +218,7 @@ export function AppShell({ children, currentPath, navigate, role, routeTitle }) /> ) : null} -
+
@@ -236,7 +259,7 @@ export function AppShell({ children, currentPath, navigate, role, routeTitle }) >

Notificações

- + Mock
@@ -346,10 +369,11 @@ export function AppShell({ children, currentPath, navigate, role, routeTitle }) ) } -function NavItem({ active, item, onNavigate }) { +function NavItem({ active, item, onNavigate, sidebarCollapsed = false }) { return (
- - {item.label} + + {item.label} ) } diff --git a/src/components/Brand.jsx b/src/components/Brand.jsx index b541cd0..d198458 100644 --- a/src/components/Brand.jsx +++ b/src/components/Brand.jsx @@ -1,14 +1,32 @@ export function BrandLogo({ className = '', iconClassName = 'size-10 rounded-[6px]', + iconButtonLabel = 'MediConnect', markClassName = 'size-6', + onIconClick, textClassName = 'text-2xl font-bold leading-8 tracking-[-0.025em] text-white', }) { + const icon = ( +
+ +
+ ) + return (
-
- -
+ {onIconClick ? ( + + ) : ( + icon + )}

MediConnect

) diff --git a/src/index.css b/src/index.css index 594c7c7..d7d91db 100644 --- a/src/index.css +++ b/src/index.css @@ -336,11 +336,6 @@ button:disabled { background: #ffffff; } -[data-theme='light'] .report-editor-sidebar { - border-color: #c8d4e2; - background: #edf4fb; -} - [data-theme='light'] .report-editor-body { background: #f8fbff; } diff --git a/src/pages/MedicalRecordsPage.jsx b/src/pages/MedicalRecordsPage.jsx index f2d7225..b9da479 100644 --- a/src/pages/MedicalRecordsPage.jsx +++ b/src/pages/MedicalRecordsPage.jsx @@ -575,25 +575,18 @@ function PatientPickList({ items, onSelect, selectedId }) { } function PatientReportHistory({ fallbackReports = [], patientId, patientName }) { - const [reports, setReports] = useState(fallbackReports) - const [loading, setLoading] = useState(Boolean(patientId)) + const [reportState, setReportState] = useState({ patientId: '', reports: [] }) + const reports = patientId && reportState.patientId === patientId ? reportState.reports : fallbackReports + const loading = Boolean(patientId && reportState.patientId !== patientId) useEffect(() => { + if (!patientId) return undefined + let active = true - if (!patientId) { - setReports(fallbackReports) - setLoading(false) - return () => { - active = false - } - } - - setLoading(true) loadReportsForPatient(patientId, patientName).then((data) => { if (!active) return - setReports(data.length ? data : fallbackReports) - setLoading(false) + setReportState({ patientId, reports: data.length ? data : fallbackReports }) }) return () => { diff --git a/src/pages/PatientsPage.jsx b/src/pages/PatientsPage.jsx index 254db76..1727a0f 100644 --- a/src/pages/PatientsPage.jsx +++ b/src/pages/PatientsPage.jsx @@ -796,7 +796,7 @@ export function PatientDetailPage({ navigate, patient, role }) { async function deletePatient() { if (!canHardDeletePatients) return - if (!window.confirm('Tem certeza que deseja excluir este paciente definitivamente?')) { + if (!window.confirm('Tem certeza que deseja excluir este paciente definitivamente? Esta ação não poderá ser desfeita.')) { return } @@ -900,21 +900,15 @@ export function PatientDetailPage({ navigate, patient, role }) { {canHardDeletePatients ? ( -
-
-
-

Zona de exclusão

-

Remove definitivamente o paciente e seus dados locais carregados.

-
- -
-
+
+ +
) : null} {messageShortcutOpen ? ( diff --git a/src/pages/ReportsPage.jsx b/src/pages/ReportsPage.jsx index 7667836..1310fbb 100644 --- a/src/pages/ReportsPage.jsx +++ b/src/pages/ReportsPage.jsx @@ -35,8 +35,6 @@ const orderOptions = [ const inputClass = 'h-10 w-full rounded-lg border border-[#404040] bg-[#1a1a1a] px-3 text-sm text-[#e5e5e5] outline-none transition placeholder:text-[#a3a3a3] focus:border-[#3b82f6] focus:ring-1 focus:ring-[#3b82f6]' -const textareaClass = - 'min-h-24 w-full rounded-lg border border-[#404040] bg-[#1a1a1a] px-3 py-2 text-sm text-[#e5e5e5] outline-none transition placeholder:text-[#a3a3a3] focus:border-[#3b82f6] focus:ring-1 focus:ring-[#3b82f6]' const labelClass = 'mb-1.5 block text-xs font-medium text-[#e5e5e5]' const cardClass = 'rounded-2xl border border-[#404040] bg-[#262626] shadow-sm' @@ -123,8 +121,6 @@ const reportTemplates = [ }, ] -const templateCategories = ['Todos', ...Array.from(new Set(reportTemplates.map((template) => template.category)))] - const emptyEditor = { id: null, orderNumber: '', @@ -618,16 +614,13 @@ function ReportRow({ onEdit, onView, report }) { } function ReportEditorModalV3({ editor, onChange, onClose, onSave, saving }) { - const [templateCategory, setTemplateCategory] = useState('Todos') const [templateSearch, setTemplateSearch] = useState('') const [templatesOpen, setTemplatesOpen] = useState(false) - const [categoriesOpen, setCategoriesOpen] = useState(true) const isValid = isReportEditorValid(editor) const filteredTemplates = reportTemplates.filter((template) => { - const matchesCategory = templateCategory === 'Todos' || template.category === templateCategory const query = normalizeSearch(templateSearch) const matchesSearch = !query || normalizeSearch([template.title, template.description, template.tags.join(' ')].join(' ')).includes(query) - return matchesCategory && matchesSearch + return matchesSearch }) function updateField(field, value) { @@ -672,40 +665,7 @@ function ReportEditorModalV3({ editor, onChange, onClose, onSave, saving }) {
-
- - +
@@ -795,486 +755,6 @@ function ReportEditorModalV3({ editor, onChange, onClose, onSave, saving }) { ) } -function ReportEditorModalV2({ editor, onChange, onClose, onSave, patientOptions, professionalOptions, saving }) { - const [requesterSearch, setRequesterSearch] = useState(editor.requestedBy || '') - const [patientSearch, setPatientSearch] = useState('') - const [templateSearch, setTemplateSearch] = useState('') - const [templateCategory, setTemplateCategory] = useState('Todos') - const [selectedTemplateId, setSelectedTemplateId] = useState('') - const [previewOpen, setPreviewOpen] = useState(false) - const isValid = isReportEditorValid(editor) - const selectedPatient = patientOptions.find((patient) => patient.id === String(editor.patientId)) - const filteredPatients = patientOptions - .filter((patient) => normalizeSearch(patient.name).includes(normalizeSearch(patientSearch))) - .slice(0, 5) - const filteredRequesterOptions = professionalOptions - .filter((professional) => normalizeSearch(professional.name).includes(normalizeSearch(requesterSearch))) - .slice(0, 5) - const filteredTemplates = reportTemplates.filter((template) => { - const matchesCategory = templateCategory === 'Todos' || template.category === templateCategory - const query = normalizeSearch(templateSearch) - const matchesSearch = !query || normalizeSearch([template.title, template.description, template.tags.join(' ')].join(' ')).includes(query) - return matchesCategory && matchesSearch - }) - const selectedTemplate = reportTemplates.find((template) => template.id === selectedTemplateId) - - function updateField(field, value) { - onChange((current) => ({ ...current, [field]: value })) - } - - function applyTemplate(template) { - setSelectedTemplateId(template.id) - setPreviewOpen(true) - onChange((current) => ({ - ...current, - exam: template.exam, - cidCode: template.cidCode, - diagnosis: template.diagnosis, - conclusion: template.conclusion, - contentHtml: template.contentHtml, - contentJson: { - templateId: template.id, - templateTitle: template.title, - appliedAt: new Date().toISOString(), - }, - })) - } - - return ( -
-
event.stopPropagation()} - > -
-
- - - -
-

{editor.id ? 'Editar relatório' : 'Novo relatório'}

-

Selecione um template e finalize o conteúdo no editor rico.

-
-
-
- - -
-
- -
- - -
-
-
- - setTemplateSearch(event.target.value)} - placeholder="Buscar templates..." - value={templateSearch} - /> -
-
- -
- {filteredTemplates.map((template) => ( - - ))} -
- -
-
- - updateField('exam', event.target.value)} - placeholder="Ex: Relatório de consulta médica" - value={editor.exam} - /> - - - -
- setPatientSearch(event.target.value)} - placeholder="Digite o nome do paciente..." - value={patientSearch || selectedPatient?.name || ''} - /> - { - updateField('patientId', patient.id) - setPatientSearch(patient.name) - }} - selectedValue={editor.patientId} - valueKey="id" - /> -
-
-
- -
- -
- { - setRequesterSearch(event.target.value) - updateField('requestedBy', event.target.value) - }} - placeholder="Pesquisar médico" - value={requesterSearch} - /> - { - setRequesterSearch(professional.name) - updateField('requestedBy', professional.name) - }} - selectedValue={editor.requestedBy} - valueKey="name" - /> -
-
- - - - -
- -
- - updateField('cidCode', event.target.value)} placeholder="Ex: Z01.7" value={editor.cidCode} /> - - - updateField('dueAt', event.target.value)} type="datetime-local" value={editor.dueAt} /> - -
- -
- -