From efb942d5aaf8eae34ad0bd2ff6bafe9e77d75801 Mon Sep 17 00:00:00 2001 From: letvb20-dot Date: Thu, 7 May 2026 01:11:10 -0300 Subject: [PATCH] modified: index.html modified: src/App.jsx modified: src/components/AppShell.jsx modified: src/components/featureStateStyles.js modified: src/config/permissions.js modified: src/hooks/useAgenda.js modified: src/mappers/reportMapper.js modified: src/pages/AgendaPage.jsx modified: src/pages/AnalyticsPage.jsx modified: src/pages/AuthPages.jsx modified: src/pages/HomePage.jsx modified: src/pages/MedicalRecordsPage.jsx modified: src/pages/MessagesPage.jsx modified: src/pages/PatientsPage.jsx modified: src/pages/ReportsPage.jsx modified: src/pages/SettingsPage.jsx deleted: src/pages/TeamPage.jsx modified: src/pages/UsersPage.jsx modified: src/repositories/availabilityRepository.js modified: src/repositories/patientRepository.js modified: src/repositories/professionalRepository.js modified: src/repositories/reportRepository.js modified: src/repositories/settingsRepository.js --- index.html | 2 +- src/App.jsx | 33 +- src/components/AppShell.jsx | 229 ++++++-- src/components/featureStateStyles.js | 8 +- src/config/permissions.js | 33 +- src/hooks/useAgenda.js | 54 +- src/mappers/reportMapper.js | 9 +- src/pages/AgendaPage.jsx | 169 ++++-- src/pages/AnalyticsPage.jsx | 4 +- src/pages/AuthPages.jsx | 65 ++- src/pages/HomePage.jsx | 9 +- src/pages/MedicalRecordsPage.jsx | 22 +- src/pages/MessagesPage.jsx | 150 ++++- src/pages/PatientsPage.jsx | 641 +++++++++++++++++---- src/pages/ReportsPage.jsx | 117 +++- src/pages/SettingsPage.jsx | 2 +- src/pages/TeamPage.jsx | 204 ------- src/pages/UsersPage.jsx | 41 +- src/repositories/availabilityRepository.js | 23 +- src/repositories/patientRepository.js | 210 ++++++- src/repositories/professionalRepository.js | 19 + src/repositories/reportRepository.js | 6 + src/repositories/settingsRepository.js | 2 +- 23 files changed, 1461 insertions(+), 591 deletions(-) delete mode 100644 src/pages/TeamPage.jsx diff --git a/index.html b/index.html index f9a4e5d..be0022d 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ - projeto-residencia + MediConnect
diff --git a/src/App.jsx b/src/App.jsx index fdc85e5..12d715c 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -15,11 +15,16 @@ 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 { TeamPage } from './pages/TeamPage.jsx' import { UsersPage } from './pages/UsersPage.jsx' import { VisitsPage } from './pages/VisitsPage.jsx' import { patientRepository } from './repositories/patientRepository.js' +const PANEL_PATHS = ['/inicio', '/home', '/dashboard'] +const ROLE_HOME_PATHS = { + medico: '/agenda', + secretaria: '/agenda', +} + function App() { const [location, setLocation] = useState(() => readLocation()) const { isAuthenticated, role, loading: authLoading } = useAuth() @@ -77,6 +82,12 @@ function App() { // Usuário autenticado mas sem permissão para a rota if (!role || !canAccess(role, location.pathname)) { + const roleHomePath = ROLE_HOME_PATHS[role] + if (roleHomePath && PANEL_PATHS.includes(location.pathname)) { + navigate(roleHomePath, { replace: true }) + return null + } + return ( @@ -151,7 +162,7 @@ function resolveRoute(pathname, navigate, role) { if (pathname.startsWith('/pacientes/')) { const patientId = pathname.split('/')[2] return { - element: , + element: , title: 'Paciente', withShell: true, } @@ -167,8 +178,8 @@ function resolveRoute(pathname, navigate, role) { if (pathname === '/laudos') { return { - element: , - title: 'Relatórios médicos', + element: , + title: 'Relatórios', withShell: true, } } @@ -176,7 +187,7 @@ function resolveRoute(pathname, navigate, role) { if (pathname === '/relatorios') { return { element: , - title: 'Relatórios', + title: 'Analytics', withShell: true, } } @@ -198,14 +209,6 @@ function resolveRoute(pathname, navigate, role) { } } - if (pathname === '/profissionais') { - return { - element: , - title: 'Profissionais', - withShell: true, - } - } - if (pathname === '/usuarios') { return { element: , @@ -237,7 +240,7 @@ function resolveRoute(pathname, navigate, role) { } } -function PatientDetailRoute({ navigate, patientId }) { +function PatientDetailRoute({ navigate, patientId, role }) { const [patient, setPatient] = useState(null) const [loading, setLoading] = useState(true) @@ -263,7 +266,7 @@ function PatientDetailRoute({ navigate, patientId }) { } return patient ? ( - + ) : ( ) diff --git a/src/components/AppShell.jsx b/src/components/AppShell.jsx index 22b2036..6d01d79 100644 --- a/src/components/AppShell.jsx +++ b/src/components/AppShell.jsx @@ -1,6 +1,7 @@ import { useEffect, useMemo, useState } from 'react' import { ROLE_LABELS, ROLE_NAV_ITEMS } from '../config/permissions.js' +import { authRepository } from '../repositories/authRepository.js' import { profileRepository } from '../repositories/profileRepository.js' import { BrandLogo } from './Brand.jsx' @@ -10,15 +11,14 @@ const ALL_NAV_ITEMS = [ { href: '/agenda', label: 'Agenda', icon: 'calendar' }, { href: '/pacientes', label: 'Pacientes', icon: 'users', exact: true }, { href: '/prontuario', label: 'Prontuário', icon: 'file' }, - { href: '/laudos', label: 'Relatórios médicos', icon: 'clipboard' }, + { href: '/laudos', label: 'Relatórios', icon: 'clipboard' }, { href: '/comunicacao', label: 'Comunicação', icon: 'message', activePaths: ['/comunicacao', '/mensagens'], }, - { href: '/relatorios', label: 'Relatórios', icon: 'chart' }, - { href: '/profissionais', label: 'Profissionais', icon: 'users' }, + { href: '/relatorios', label: 'Analytics', icon: 'chart' }, { href: '/usuarios', label: 'Usuários', icon: 'shield' }, { href: '/configuracoes', label: 'Configurações', icon: 'settings', activePaths: ['/configuracoes', '/config'] }, ] @@ -29,13 +29,12 @@ const titles = { '/dashboard': 'Painel', '/agenda': 'Agenda', '/consultas': 'Consultas', - '/laudos': 'Relatórios médicos', + '/laudos': 'Relatórios', '/pacientes': 'Pacientes', '/prontuario': 'Prontuário', '/comunicacao': 'Comunicação', '/mensagens': 'Comunicação', - '/relatorios': 'Relatórios', - '/profissionais': 'Profissionais', + '/relatorios': 'Analytics', '/perfil': 'Perfil', '/configuracoes': 'Configurações', '/config': 'Configurações', @@ -44,7 +43,8 @@ const titles = { export function AppShell({ children, currentPath, navigate, role, routeTitle }) { const [menuOpen, setMenuOpen] = useState(false) - const [quickSearch, setQuickSearch] = useState('') + const [profileMenuOpen, setProfileMenuOpen] = useState(false) + const [notificationsOpen, setNotificationsOpen] = useState(false) const [viewerProfile, setViewerProfile] = useState({ name: 'Usuário', role: 'Usuário do Sistema' }) const pageTitle = useMemo(() => { @@ -68,6 +68,22 @@ export function AppShell({ children, currentPath, navigate, role, routeTitle }) ) }, [role]) + const canOpenSettings = useMemo( + () => + (ROLE_NAV_ITEMS[role] ?? []).some( + (item) => item.path === '/configuracoes' || item.path === '/config', + ), + [role], + ) + const mockNotifications = useMemo( + () => [ + { id: 'mock-1', title: 'Retorno agendado', detail: 'Paciente Ana Souza às 14:30', time: 'Agora' }, + { id: 'mock-2', title: 'Laudo pendente', detail: 'Hemograma aguardando revisão', time: '12 min' }, + { id: 'mock-3', title: 'Mensagem recebida', detail: 'Resposta via WhatsApp registrada', time: '35 min' }, + ], + [], + ) + useEffect(() => { let active = true @@ -96,11 +112,33 @@ export function AppShell({ children, currentPath, navigate, role, routeTitle }) } }, [role]) + useEffect(() => { + if (!profileMenuOpen && !notificationsOpen) return undefined + + function closeOnEscape(event) { + if (event.key === 'Escape') { + setProfileMenuOpen(false) + setNotificationsOpen(false) + } + } + + window.addEventListener('keydown', closeOnEscape) + return () => window.removeEventListener('keydown', closeOnEscape) + }, [notificationsOpen, profileMenuOpen]) + function goTo(path) { setMenuOpen(false) + setProfileMenuOpen(false) + setNotificationsOpen(false) navigate(path) } + async function handleLogout() { + setProfileMenuOpen(false) + await authRepository.logout() + navigate('/login', { replace: true }) + } + return (
- +
+ + + {notificationsOpen ? ( +
+
+

Notificações

+ + Mock + +
+
+ {mockNotifications.map((notification) => ( + + ))} +
+
+ ) : null} +
- {quickSearch ? ( -
- Busca local ativa por {quickSearch}. -
- ) : null}
@@ -362,19 +475,29 @@ function BellIcon({ className = 'size-5' }) { ) } -function ChevronDownIcon({ className = 'size-4' }) { +function UserIcon({ className = 'size-4' }) { return ( - + + ) } -function SearchIcon({ className = 'size-4' }) { +function LogoutIcon({ className = 'size-4' }) { return ( - - + + + + + ) +} + +function ChevronDownIcon({ className = 'size-4' }) { + return ( + + ) } diff --git a/src/components/featureStateStyles.js b/src/components/featureStateStyles.js index d53333c..57cb266 100644 --- a/src/components/featureStateStyles.js +++ b/src/components/featureStateStyles.js @@ -1,9 +1,9 @@ export const featureStateStyles = { live: { - badge: 'border-emerald-500/40 bg-emerald-500/15 text-emerald-300', - panel: 'border-emerald-500/35 bg-emerald-500/8', - title: 'text-emerald-300', - label: 'Integrado', + badge: 'hidden', + panel: 'border-[#404040] bg-[#262626]', + title: 'text-[#e5e5e5]', + label: '', }, partial: { badge: 'border-sky-500/40 bg-sky-500/15 text-sky-300', diff --git a/src/config/permissions.js b/src/config/permissions.js index 23d61a2..9c9b55d 100644 --- a/src/config/permissions.js +++ b/src/config/permissions.js @@ -40,30 +40,30 @@ const ROLE_ROUTES = { '/laudos', '/relatorios', '/comunicacao', '/mensagens', - '/profissionais', '/configuracoes', '/config', '/consultas', '/usuarios', '/perfil', ], medico: [ - '/inicio', '/home', '/dashboard', '/agenda', + '/pacientes', '/prontuario', '/laudos', '/comunicacao', '/mensagens', - '/relatorios', + '/configuracoes', '/config', '/perfil', ], secretaria: [ - '/inicio', '/home', '/dashboard', '/agenda', '/pacientes', '/comunicacao', '/mensagens', + '/configuracoes', '/config', '/perfil', ], paciente: [ '/inicio', '/home', '/dashboard', + '/configuracoes', '/config', '/perfil', ], } @@ -91,7 +91,7 @@ export const ROLE_CAPABILITIES = { medico: { manageUsers: false, hardDeletePatients: false, - accessSettings: false, + accessSettings: true, ownAppointmentsOnly: true, canEditPatients: false, canViewReports: true, @@ -100,7 +100,7 @@ export const ROLE_CAPABILITIES = { secretaria: { manageUsers: false, hardDeletePatients: false, - accessSettings: false, + accessSettings: true, ownAppointmentsOnly: false, canEditPatients: true, canViewReports: false, @@ -109,7 +109,7 @@ export const ROLE_CAPABILITIES = { paciente: { manageUsers: false, hardDeletePatients: false, - accessSettings: false, + accessSettings: true, ownAppointmentsOnly: false, canEditPatients: false, canViewReports: false, @@ -124,10 +124,9 @@ export const ROLE_NAV_ITEMS = { { path: '/agenda', label: 'Agenda' }, { path: '/pacientes', label: 'Pacientes' }, { path: '/prontuario', label: 'Prontuário' }, - { path: '/laudos', label: 'Laudos' }, - { path: '/relatorios', label: 'Relatórios' }, + { path: '/laudos', label: 'Relatórios' }, + { path: '/relatorios', label: 'Analytics' }, { path: '/comunicacao', label: 'Comunicação' }, - { path: '/profissionais', label: 'Profissionais' }, { path: '/usuarios', label: 'Usuários' }, { path: '/configuracoes', label: 'Configurações' }, ], @@ -136,29 +135,29 @@ export const ROLE_NAV_ITEMS = { { path: '/agenda', label: 'Agenda' }, { path: '/pacientes', label: 'Pacientes' }, { path: '/prontuario', label: 'Prontuário' }, - { path: '/laudos', label: 'Laudos' }, - { path: '/relatorios', label: 'Relatórios' }, + { path: '/laudos', label: 'Relatórios' }, + { path: '/relatorios', label: 'Analytics' }, { path: '/comunicacao', label: 'Comunicação' }, - { path: '/profissionais', label: 'Profissionais' }, { path: '/usuarios', label: 'Usuários' }, { path: '/configuracoes', label: 'Configurações' }, ], medico: [ - { path: '/inicio', label: 'Painel' }, { path: '/agenda', label: 'Agenda' }, + { path: '/pacientes', label: 'Pacientes' }, { path: '/prontuario', label: 'Prontuário' }, - { path: '/laudos', label: 'Laudos' }, + { path: '/laudos', label: 'Relatórios' }, { path: '/comunicacao', label: 'Comunicação' }, - { path: '/relatorios', label: 'Relatórios' }, + { path: '/configuracoes', label: 'Configurações' }, ], secretaria: [ - { path: '/inicio', label: 'Painel' }, { path: '/agenda', label: 'Agenda' }, { path: '/pacientes', label: 'Pacientes' }, { path: '/comunicacao', label: 'Comunicação' }, + { path: '/configuracoes', label: 'Configurações' }, ], paciente: [ { path: '/inicio', label: 'Painel' }, + { path: '/configuracoes', label: 'Configurações' }, ], } diff --git a/src/hooks/useAgenda.js b/src/hooks/useAgenda.js index 509ba9e..8258b33 100644 --- a/src/hooks/useAgenda.js +++ b/src/hooks/useAgenda.js @@ -23,6 +23,9 @@ export function useAgenda() { const [activeView, setActiveView] = useState('Dia') const [baseDate, setBaseDate] = useState(new Date()) const [status, setStatus] = useState('Todos') + const [doctorFilter, setDoctorFilter] = useState('Todos') + const [doctorSearch, setDoctorSearch] = useState('') + const [unitFilter, setUnitFilter] = useState('') const [modalOpen, setModalOpen] = useState(false) const [form, setForm] = useState({ @@ -53,7 +56,7 @@ export function useAgenda() { if (!active) return const agendaScope = currentProfile?.isDoctor ? 'doctor' : 'global' - const resolvedProfessional = resolveCurrentProfessional(currentProfile, professionalsData) + const resolvedProfessional = professionalRepository.resolveCurrentProfessional(currentProfile, professionalsData) const initialProfessionalId = agendaScope === 'doctor' ? resolvedProfessional?.id || '' @@ -128,6 +131,7 @@ export function useAgenda() { const slots = await availabilityRepository.getAvailableSlots({ doctorId: targetProfessionalId, date: formatLocalDateInput(baseDate), + appointmentType: form.mode, }) if (!active) return @@ -156,7 +160,7 @@ export function useAgenda() { return () => { active = false } - }, [agendaScope, baseDate, currentProfessional?.id, form.professionalId, modalOpen]) + }, [agendaScope, baseDate, currentProfessional?.id, form.mode, form.professionalId, modalOpen]) const visibleAppointments = useMemo(() => { let filtered = localAppointments @@ -165,6 +169,30 @@ export function useAgenda() { filtered = filtered.filter((appointment) => appointment.status === status) } + if (agendaScope !== 'doctor' && doctorFilter !== 'Todos') { + filtered = filterAppointmentsByProfessional(filtered, doctorFilter) + } + + if (agendaScope !== 'doctor') { + const normalizedDoctorSearch = normalizeValue(doctorSearch) + const normalizedUnit = normalizeValue(unitFilter) + + if (normalizedDoctorSearch || normalizedUnit) { + filtered = filtered.filter((appointment) => { + const professional = professionals.find( + (item) => normalizeValue(item.id) === normalizeValue(appointment.professionalId), + ) + const professionalName = normalizeValue(professional?.name || appointment.professional) + const professionalUnit = normalizeValue(professional?.unit || appointment.unit) + + return ( + (!normalizedDoctorSearch || professionalName.includes(normalizedDoctorSearch)) && + (!normalizedUnit || professionalUnit === normalizedUnit) + ) + }) + } + } + if (activeView === 'Dia') { filtered = filtered.filter((appointment) => { if (!appointment.date) return false @@ -177,7 +205,7 @@ export function useAgenda() { } return sortAppointmentsByTime(filtered) - }, [localAppointments, status, activeView, baseDate]) + }, [localAppointments, status, agendaScope, doctorFilter, doctorSearch, unitFilter, professionals, activeView, baseDate]) function updateForm(field, value) { setForm((current) => ({ ...current, [field]: value })) @@ -230,6 +258,12 @@ export function useAgenda() { setBaseDate, status, setStatus, + doctorFilter, + setDoctorFilter, + doctorSearch, + setDoctorSearch, + unitFilter, + setUnitFilter, modalOpen, setModalOpen, form, @@ -242,20 +276,6 @@ export function useAgenda() { } } -function resolveCurrentProfessional(profile, professionals) { - const doctorId = normalizeValue(profile?.doctorId) - const userId = normalizeValue(profile?.id) - const email = normalizeValue(profile?.email) - - return ( - professionals.find((professional) => normalizeValue(professional.id) === doctorId) || - professionals.find((professional) => normalizeValue(professional.userId) === userId) || - professionals.find((professional) => normalizeValue(professional.id) === userId) || - professionals.find((professional) => normalizeValue(professional.email) === email) || - null - ) -} - function filterAppointmentsByProfessional(appointments, professionalId) { const normalizedProfessionalId = normalizeValue(professionalId) diff --git a/src/mappers/reportMapper.js b/src/mappers/reportMapper.js index c23fb99..e378126 100644 --- a/src/mappers/reportMapper.js +++ b/src/mappers/reportMapper.js @@ -43,11 +43,16 @@ export const reportMapper = { } function normalizeStatus(status) { - return status === 'draft' ? 'draft' : 'draft' + const normalized = String(status || '').toLowerCase() + if (['finalized', 'finalizado', 'finished', 'completed', 'done'].includes(normalized)) { + return 'finalized' + } + + return 'draft' } function normalizeApiStatus(status) { - return status === 'draft' ? 'draft' : 'draft' + return status === 'finalized' ? 'finalized' : 'draft' } function emptyToUndefined(value) { diff --git a/src/pages/AgendaPage.jsx b/src/pages/AgendaPage.jsx index 2d77fc4..de484be 100644 --- a/src/pages/AgendaPage.jsx +++ b/src/pages/AgendaPage.jsx @@ -10,11 +10,13 @@ import { startOfWeek, } from 'date-fns' import { ptBR } from 'date-fns/locale' +import { useState } from 'react' import { AgendaDailyView } from '../components/calendar/AgendaDailyView.jsx' import { AgendaWeeklyView } from '../components/calendar/AgendaWeeklyView.jsx' import { AgendaMonthlyView } from '../components/calendar/AgendaMonthlyView.jsx' import { useAgenda } from '../hooks/useAgenda.js' +import { formatLocalDateInput, parseLocalDate } from '../utils/agendaDate.js' const statusFilters = [ { label: 'Todos', value: 'Todos' }, @@ -30,6 +32,8 @@ const viewFilters = [ ] export function AgendaPage({ navigate }) { + const [modalPatientSearch, setModalPatientSearch] = useState('') + const [modalDoctorSearch, setModalDoctorSearch] = useState('') const { patients, professionals, @@ -45,6 +49,11 @@ export function AgendaPage({ navigate }) { setBaseDate, status, setStatus, + setDoctorFilter, + doctorSearch, + setDoctorSearch, + unitFilter, + setUnitFilter, modalOpen, setModalOpen, form, @@ -67,6 +76,37 @@ export function AgendaPage({ navigate }) { const weekStart = startOfWeek(baseDate, { weekStartsOn: 0 }) const weekEnd = endOfWeek(baseDate, { weekStartsOn: 0 }) const isDoctorScope = agendaScope === 'doctor' + const unitOptions = [ + ...new Set( + professionals + .map((professional) => professional.unit) + .filter(Boolean), + ), + ].sort((a, b) => a.localeCompare(b, 'pt-BR')) + const filteredPatients = (() => { + const query = modalPatientSearch.trim().toLowerCase() + if (!query) return patients + + return patients.filter((patient) => + [patient.name, patient.full_name, patient.nome, patient.cpf, patient.email] + .filter(Boolean) + .join(' ') + .toLowerCase() + .includes(query), + ) + })() + const filteredProfessionals = (() => { + const query = modalDoctorSearch.trim().toLowerCase() + if (!query) return professionals + + return professionals.filter((professional) => + [professional.name, professional.email, professional.unit] + .filter(Boolean) + .join(' ') + .toLowerCase() + .includes(query), + ) + })() return (
@@ -76,9 +116,7 @@ export function AgendaPage({ navigate }) { Agenda

- {isDoctorScope - ? `Agenda restrita ao médico logado: ${currentProfessional?.name || viewerProfile?.name || 'Médico atual'}.` - : 'Visualização completa da agenda com todos os médicos.'} + Perfil atual: {viewerProfile?.role || (isDoctorScope ? 'Médico' : 'Usuário')}

@@ -126,7 +164,7 @@ export function AgendaPage({ navigate }) { onClick={() => setModalOpen(true)} type="button" > - + Nova consulta + + Novo agendamento @@ -174,26 +212,61 @@ export function AgendaPage({ navigate }) { -
- {statusFilters.map((filter) => ( - - ))} +
+
+ {statusFilters.map((filter) => ( + + ))} +
+ + {!isDoctorScope ? ( +
+ + +
+ ) : null}
{!isDoctorScope && (
- Perfil atual: {viewerProfile?.role || 'Administrador'} | agendamentos exibidos para todos os profissionais. + Perfil atual: {viewerProfile?.role || 'Administrador'}
)} @@ -229,15 +302,34 @@ export function AgendaPage({ navigate }) { )} - setModalOpen(false)} open={modalOpen} title="Nova consulta"> + setModalOpen(false)} open={modalOpen} title="Novo agendamento">
+ + { + const parsedDate = parseLocalDate(event.target.value) + if (parsedDate) setBaseDate(parsedDate) + }} + type="date" + value={formatLocalDateInput(baseDate)} + /> + + + setModalPatientSearch(event.target.value)} + placeholder="Pesquisar paciente" + type="search" + value={modalPatientSearch} + /> updateForm('professionalId', event.target.value)} - value={form.professionalId} - > - {professionals.map((professional) => ( - - ))} - + <> + setModalDoctorSearch(event.target.value)} + placeholder="Pesquisar médico" + type="search" + value={modalDoctorSearch} + /> + + )} @@ -330,7 +431,7 @@ export function AgendaPage({ navigate }) { disabled={!canCreateAppointment} type="submit" > - Salvar consulta + Salvar
diff --git a/src/pages/AnalyticsPage.jsx b/src/pages/AnalyticsPage.jsx index 49e86a5..5fa1b77 100644 --- a/src/pages/AnalyticsPage.jsx +++ b/src/pages/AnalyticsPage.jsx @@ -34,7 +34,7 @@ export function AnalyticsPage() {
-

Relatórios & Analytics

+

Analytics

Dashboard executivo com métricas de desempenho

@@ -70,7 +70,7 @@ export function AnalyticsPage() {
-
+
diff --git a/src/pages/AuthPages.jsx b/src/pages/AuthPages.jsx index 9b37fa5..ef0bf93 100644 --- a/src/pages/AuthPages.jsx +++ b/src/pages/AuthPages.jsx @@ -6,12 +6,20 @@ import { BrandLogo } from '../components/Brand.jsx' import { FeatureBadge, FeatureCallout } from '../components/FeatureState.jsx' import loginClinicImage from '../assets/figma/login-clinic.png' +const mockCredentials = [ + { label: 'Admin', email: 'hugo@popcode.com.br', password: 'hdoria' }, + { label: 'Médico', email: 'leticia.lacerda@souunit.com.br', password: 'Senha@123' }, + { label: 'Secretária', email: 'recepcao@mediconnect.com', password: 'demo12345' }, + { label: 'Gestor', email: 'gestao@mediconnect.com', password: '12345678' }, +] + export function LoginPage({ navigate }) { const [form, setForm] = useState({ email: '', password: '', }) const [showPassword, setShowPassword] = useState(false) + const [credentialsOpen, setCredentialsOpen] = useState(false) const [loading, setLoading] = useState(false) const [error, setError] = useState('') @@ -152,23 +160,46 @@ export function LoginPage({ navigate }) { - +
+ {credentialsOpen ? ( +
+

+ Credenciais mockadas +

+
+ {mockCredentials.map((credential) => ( + + ))} +
+
+ ) : null} + +
diff --git a/src/pages/HomePage.jsx b/src/pages/HomePage.jsx index 8a0ce0a..124da42 100644 --- a/src/pages/HomePage.jsx +++ b/src/pages/HomePage.jsx @@ -32,13 +32,6 @@ export function HomePage({ navigate }) { > Exportar - @@ -120,7 +113,7 @@ export function HomePage({ navigate }) {
-

Relatórios e Análises

+

Analytics

diff --git a/src/pages/MedicalRecordsPage.jsx b/src/pages/MedicalRecordsPage.jsx index cc8ff82..862c1e0 100644 --- a/src/pages/MedicalRecordsPage.jsx +++ b/src/pages/MedicalRecordsPage.jsx @@ -13,7 +13,6 @@ export function MedicalRecordsPage() { const recordTypes = medicalRecordRepository.getRecordTypes() const [records, setRecords] = useState(() => medicalRecordRepository.getInitialRecords()) const [search, setSearch] = useState('') - const [filterType, setFilterType] = useState('') const [editorOpen, setEditorOpen] = useState(false) const filteredRecords = useMemo(() => { @@ -22,11 +21,9 @@ export function MedicalRecordsPage() { .join(' ') .toLowerCase() .includes(search.toLowerCase()) - const matchesType = !filterType || record.type === filterType - - return matchesSearch && matchesType + return matchesSearch }) - }, [filterType, records, search]) + }, [records, search]) function handleCreateRecord(record) { setRecords((currentRecords) => [record, ...currentRecords]) @@ -67,21 +64,6 @@ export function MedicalRecordsPage() { value={search} />
-
- - -
diff --git a/src/pages/MessagesPage.jsx b/src/pages/MessagesPage.jsx index a2fff60..f390301 100644 --- a/src/pages/MessagesPage.jsx +++ b/src/pages/MessagesPage.jsx @@ -1,9 +1,10 @@ -import { useMemo, useState } from 'react' +import { useEffect, useMemo, useState } from 'react' import { normalizeRole } from '../config/permissions.js' import { FeatureCallout } from '../components/FeatureState.jsx' import { featurePanelClass } from '../components/featureStateStyles.js' import { communicationRepository } from '../repositories/communicationRepository.js' +import { patientRepository } from '../repositories/patientRepository.js' const channels = { whatsapp: { label: 'WhatsApp', className: 'bg-emerald-500/20 text-emerald-400', icon: 'message' }, @@ -20,6 +21,7 @@ const statusConfig = { const emptyMessage = { + patientId: '', patient: '', phone: '', channel: 'whatsapp', @@ -51,17 +53,47 @@ export function MessagesPage({ role }) { const campaigns = communicationRepository.getCampaigns() const [messages, setMessages] = useState(() => communicationRepository.getInitialMessages()) const [templates, setTemplates] = useState(() => communicationRepository.getInitialTemplates()) + const [patients, setPatients] = useState([]) const [activeTab, setActiveTab] = useState('historico') const [channelFilter, setChannelFilter] = useState('todos') const [search, setSearch] = useState('') const [composerOpen, setComposerOpen] = useState(false) const [templateEditorOpen, setTemplateEditorOpen] = useState(false) + const [editingTemplateId, setEditingTemplateId] = useState(null) const [composer, setComposer] = useState(emptyMessage) const [templateDraft, setTemplateDraft] = useState(emptyTemplate) const availableTemplates = useMemo( () => templates.filter((template) => allowedChannelKeys.includes(template.channel)), [allowedChannelKeys, templates], ) + const patientOptions = useMemo( + () => + patients.map((patient) => ({ + id: String(patient.detailId || patient.id || ''), + name: patient.name || patient.full_name || patient.nome || 'Paciente', + phone: patient.phone || patient.phone_mobile || patient.telefone || '', + document: patient.cpf || patient.document || '', + })), + [patients], + ) + + useEffect(() => { + let active = true + + patientRepository + .getDirectoryRows() + .then((data) => { + if (active) setPatients(data || []) + }) + .catch((loadError) => { + console.error(loadError) + if (active) setPatients([]) + }) + + return () => { + active = false + } + }, []) const filteredMessages = useMemo( () => @@ -95,6 +127,7 @@ export function MessagesPage({ role }) { if (!allowedChannelKeys.includes(template.channel)) return setComposer({ + patientId: '', patient: '', phone: '', channel: template.channel, @@ -104,6 +137,26 @@ export function MessagesPage({ role }) { setComposerOpen(true) } + function openTemplateEditor(template = null) { + if (template && !allowedChannelKeys.includes(template.channel)) return + + setEditingTemplateId(template?.id || null) + setTemplateDraft( + template + ? { + name: template.name || '', + channel: template.channel || allowedChannelKeys[0], + category: template.category || 'Personalizado', + content: template.content || '', + } + : { + ...emptyTemplate, + channel: allowedChannelKeys[0] || 'whatsapp', + }, + ) + setTemplateEditorOpen(true) + } + async function submitMessage(event) { event.preventDefault() @@ -160,16 +213,20 @@ export function MessagesPage({ role }) { return } - setTemplates((current) => [ - { - id: `template-${Date.now()}`, - name: templateDraft.name.trim(), - channel: templateDraft.channel, - content: templateDraft.content.trim(), - category: templateDraft.category.trim() || 'Personalizado', - }, - ...current, - ]) + const nextTemplate = { + id: editingTemplateId || `template-${Date.now()}`, + name: templateDraft.name.trim(), + channel: templateDraft.channel, + content: templateDraft.content.trim(), + category: templateDraft.category.trim() || 'Personalizado', + } + + setTemplates((current) => + editingTemplateId + ? current.map((template) => (template.id === editingTemplateId ? nextTemplate : template)) + : [nextTemplate, ...current], + ) + setEditingTemplateId(null) setTemplateDraft(emptyTemplate) setTemplateEditorOpen(false) } @@ -309,7 +366,7 @@ export function MessagesPage({ role }) {
@@ -444,7 +505,7 @@ function MessageRow({ message }) { ) } -function TemplateCard({ onUse, template }) { +function TemplateCard({ onEdit, onUse, template }) { const channel = channels[template.channel] return ( @@ -463,6 +524,7 @@ function TemplateCard({ onUse, template }) {