import React, { useState, useEffect } from 'react'; import { HybridBackend } from './services/backend.ts'; import { Sidebar } from './components/Sidebar.tsx'; import { Menu, LayoutDashboard, Users, Calendar as CalendarIcon, DollarSign, Activity, ClipboardList, Bell, Settings, MoreHorizontal, Eye, X, PanelLeftOpen } from 'lucide-react'; import { ToastContainer } from './components/Toast.tsx'; import { useAppVersion } from './buildInfo.ts'; import { Dashboard } from './views/Dashboard.tsx'; import { LeadsView } from './views/LeadsView.tsx'; import { WaInboxView } from './views/WaInboxView.tsx'; import { SessionsView } from './views/newwhats/SessionsView.tsx'; import { InboxView } from './views/newwhats/InboxView.tsx'; import { SecretariaView } from './views/newwhats/SecretariaView.tsx'; import { WaSecretariaView } from './views/WaSecretariaView.tsx'; import { PublicContactForm } from './views/PublicContactForm.tsx'; import { PatientsView } from './views/PatientsView.tsx'; import { AgendaView } from './views/AgendaView.tsx'; import { OrthoView } from './views/OrthoView.tsx'; import { FinanceiroView } from './views/FinanceiroView.tsx'; import { MeusTratamentos } from './views/MeusTratamentos'; import { LancarGTO } from './views/LancarGTO'; import { LoginView } from './views/LoginView.tsx'; import { ChangePasswordModal } from './components/ChangePasswordModal.tsx'; import { LandingPage } from './views/LandingPage.tsx'; import { UpdateDbView } from './views/UpdateDbView.tsx'; import { ClinicasView } from './views/ClinicasView.tsx'; import { DentistRegisterView } from './views/DentistRegisterView.tsx'; import { DentistasView, EspecialidadesView, PlanosView, SyncView, ProcedimentosView } from './views/AdminViews.tsx'; import { NotificationsView } from './views/NotificationsView.tsx'; import { ReportsView } from './views/ReportsView.tsx'; import { ComissoesView } from './views/ComissoesView.tsx'; import { GlosasView } from './views/GlosasView.tsx'; import { SalaHomeView } from './views/SalaHomeView.tsx'; import { LabHomeView } from './views/LabHomeView.tsx'; import { LabAgendaView } from './views/LabAgendaView.tsx'; import { LabClientesView } from './views/LabClientesView.tsx'; import { LabCotacoesView } from './views/LabCotacoesView.tsx'; import { LabEquipeView } from './views/LabEquipeView.tsx'; import { LabIndicadoresView } from './views/LabIndicadoresView.tsx'; import { ConfiguracoesView } from './views/ConfiguracoesView.tsx'; import { OrcamentosView } from './views/OrcamentosView.tsx'; import { ContratosView } from './views/ContratosView.tsx'; import { PluginsView } from './views/PluginsView.tsx'; import { RXPlugin } from './views/plugins/RXPlugin.tsx'; import { SalasPlugin } from './views/plugins/SalasPlugin.tsx'; import { ProfissionaisPlugin } from './views/plugins/ProfissionaisPlugin.tsx'; import { isPluginActive } from './views/plugins/pluginRegistry.ts'; import { CreateClinicView } from './views/CreateClinicView.tsx'; import { RxConfigView } from './views/RxConfigView.tsx'; import { GestaoEquipeView } from './views/GestaoEquipeView.tsx'; import { TutorPainelView } from './views/TutorPainelView.tsx'; import { TutorCandidaturaView } from './views/TutorCandidaturaView.tsx'; import { AdminGestaoTutoresView } from './views/AdminGestaoTutoresView.tsx'; import { WaitingInviteView } from './views/WaitingInviteView.tsx'; import { SuperAdminView } from './views/SuperAdminView.tsx'; import { DiretorioProfissionaisView } from './views/DiretorioProfissionaisView.tsx'; import { ProteseView } from './views/ProteseView.tsx'; export type ViewKey = | 'landing' | 'dashboard' | 'leads' | 'public' | 'pacientes' | 'agenda' | 'ortodontia' | 'financeiro' | 'comissoes' | 'glosas' | 'proteses' | 'login' | 'update' | 'dentistas' | 'especialidades' | 'procedimentos' | 'planos' | 'tratamentos' | 'lancar-gto' | 'notificacoes' | 'clinicas' | 'sync' | 'relatorios' | 'cadastro-dentista' | 'configuracoes' | 'orcamentos' | 'contratos' | 'plugins' | 'rx-radiografias' | 'wa-inbox' | 'wa-sessions' | 'wa-secretaria' | 'salas' | 'minhas-salas' | 'alugar-salas' | 'sala-home' | 'lab-home' | 'lab-bancada' | 'lab-agenda' | 'lab-clientes' | 'lab-financeiro' | 'lab-cotacoes' | 'lab-equipe' | 'lab-indicadores' | 'marketplace-profissionais' | 'criar-clinica' | 'rx-config' | 'gestao-equipe' | 'tutoria-painel' | 'aguardando-convite' | 'candidatura-tutor' | 'admin-gestao-tutores' | 'superadmin' | 'superadmin-overview' | 'superadmin-users' | 'superadmin-clinicas' | 'superadmin-logs' | 'superadmin-financeiro' | 'superadmin-notificacoes' | 'superadmin-secretaria' | 'diretorio-profissionais'; // ------- URL <-> VIEW mapping ------- const ROUTE_MAP: Record = { '/': 'landing', '/dashboard': 'dashboard', '/landing': 'landing', '/leads': 'leads', '/contato': 'public', '/pacientes': 'pacientes', '/agenda': 'agenda', '/ortodontia': 'ortodontia', '/financeiro': 'financeiro', '/login': 'login', '/update': 'update', '/clinicas': 'clinicas', '/admin/dentistas': 'dentistas', '/admin/especialidades': 'especialidades', '/admin/procedimentos': 'procedimentos', '/admin/planos': 'planos', '/admin/sync': 'sync', '/tratamentos': 'tratamentos', '/lancar-gto': 'lancar-gto', '/notificacoes': 'notificacoes', '/relatorios': 'relatorios', '/cadastro-dentista': 'cadastro-dentista', '/configuracoes': 'configuracoes', '/orcamentos': 'orcamentos', '/contratos': 'contratos', '/plugins': 'plugins', '/rx-radiografias': 'rx-radiografias', '/wa-inbox': 'wa-inbox', '/wa-sessions': 'wa-sessions', '/wa-secretaria': 'wa-secretaria', '/salas': 'salas', '/minhas-salas': 'minhas-salas', '/alugar-salas': 'alugar-salas', '/sala-home': 'sala-home', '/lab-home': 'lab-home', '/lab-bancada': 'lab-bancada', '/lab-agenda': 'lab-agenda', '/lab-clientes': 'lab-clientes', '/lab-financeiro': 'lab-financeiro', '/lab-cotacoes': 'lab-cotacoes', '/lab-equipe': 'lab-equipe', '/lab-indicadores': 'lab-indicadores', '/marketplace-profissionais': 'marketplace-profissionais', '/criar-clinica': 'criar-clinica', '/rx-config': 'rx-config', '/gestao-equipe': 'gestao-equipe', '/tutoria-painel': 'tutoria-painel', '/candidatura-tutor': 'candidatura-tutor', '/admin-gestao-tutores': 'admin-gestao-tutores', '/superadmin': 'superadmin-overview', '/superadmin/overview': 'superadmin-overview', '/superadmin/users': 'superadmin-users', '/superadmin/clinicas': 'superadmin-clinicas', '/superadmin/logs': 'superadmin-logs', '/superadmin/financeiro': 'superadmin-financeiro', '/superadmin/notificacoes': 'superadmin-notificacoes', '/superadmin/secretaria': 'superadmin-secretaria', '/aguardando-convite': 'aguardando-convite', '/diretorio-profissionais': 'diretorio-profissionais', '/proteses': 'proteses', // Corrige deep-link/refresh: existiam em VIEW_TO_ROUTE mas faltavam aqui (caíam em dashboard). '/comissoes': 'comissoes', '/glosas': 'glosas', }; const VIEW_TO_ROUTE: Record = { landing: '/', dashboard: '/dashboard', leads: '/leads', public: '/contato', pacientes: '/pacientes', agenda: '/agenda', ortodontia: '/ortodontia', financeiro: '/financeiro', comissoes: '/comissoes', glosas: '/glosas', login: '/login', update: '/update', clinicas: '/clinicas', dentistas: '/admin/dentistas', especialidades: '/admin/especialidades', procedimentos: '/admin/procedimentos', planos: '/admin/planos', tratamentos: '/tratamentos', 'lancar-gto': '/lancar-gto', notificacoes: '/notificacoes', relatorios: '/relatorios', sync: '/admin/sync', 'cadastro-dentista': '/cadastro-dentista', configuracoes: '/configuracoes', orcamentos: '/orcamentos', contratos: '/contratos', plugins: '/plugins', 'rx-radiografias': '/rx-radiografias', 'wa-inbox': '/wa-inbox', 'wa-sessions': '/wa-sessions', 'wa-secretaria': '/wa-secretaria', salas: '/salas', 'minhas-salas': '/minhas-salas', 'alugar-salas': '/alugar-salas', 'sala-home': '/sala-home', 'lab-home': '/lab-home', 'lab-bancada': '/lab-bancada', 'lab-agenda': '/lab-agenda', 'lab-clientes': '/lab-clientes', 'lab-financeiro': '/lab-financeiro', 'lab-cotacoes': '/lab-cotacoes', 'lab-equipe': '/lab-equipe', 'lab-indicadores': '/lab-indicadores', 'marketplace-profissionais': '/marketplace-profissionais', 'criar-clinica': '/criar-clinica', 'rx-config': '/rx-config', 'gestao-equipe': '/gestao-equipe', 'tutoria-painel': '/tutoria-painel', 'candidatura-tutor': '/candidatura-tutor', 'admin-gestao-tutores': '/admin-gestao-tutores', 'aguardando-convite': '/aguardando-convite', superadmin: '/superadmin', 'superadmin-overview': '/superadmin/overview', 'superadmin-users': '/superadmin/users', 'superadmin-clinicas': '/superadmin/clinicas', 'superadmin-logs': '/superadmin/logs', 'superadmin-financeiro': '/superadmin/financeiro', 'superadmin-notificacoes': '/superadmin/notificacoes', 'superadmin-secretaria': '/superadmin/secretaria', 'diretorio-profissionais': '/diretorio-profissionais', proteses: '/proteses', }; /** Resolve current URL to a ViewKey */ function resolveViewFromUrl(): ViewKey { const path = window.location.pathname || '/'; return ROUTE_MAP[path] ?? 'dashboard'; } /** Push a new URL without reloading the page */ function navigate(view: ViewKey) { const route = VIEW_TO_ROUTE[view]; history.pushState({}, '', route); } // ------- Mobile Bottom Nav ------- const BOTTOM_TABS: Record> = { admin: [{ id: 'dashboard', icon: LayoutDashboard, label: 'Início' }, { id: 'pacientes', icon: Users, label: 'Pacientes' }, { id: 'agenda', icon: CalendarIcon, label: 'Agenda' }, { id: 'financeiro', icon: DollarSign, label: 'Financeiro' }, { more: true }], donoclinica: [{ id: 'dashboard', icon: LayoutDashboard, label: 'Início' }, { id: 'pacientes', icon: Users, label: 'Pacientes' }, { id: 'agenda', icon: CalendarIcon, label: 'Agenda' }, { id: 'financeiro', icon: DollarSign, label: 'Financeiro' }, { more: true }], donoconsultorio: [{ id: 'dashboard', icon: LayoutDashboard, label: 'Início' }, { id: 'pacientes', icon: Users, label: 'Pacientes' }, { id: 'agenda', icon: CalendarIcon, label: 'Agenda' }, { id: 'financeiro', icon: DollarSign, label: 'Financeiro' }, { more: true }], funcionario: [{ id: 'dashboard', icon: LayoutDashboard, label: 'Início' }, { id: 'pacientes', icon: Users, label: 'Pacientes' }, { id: 'agenda', icon: CalendarIcon, label: 'Agenda' }, { id: 'financeiro', icon: DollarSign, label: 'Financeiro' }, { more: true }], dentista: [{ id: 'dashboard', icon: LayoutDashboard, label: 'Início' }, { id: 'agenda', icon: CalendarIcon, label: 'Agenda' }, { id: 'ortodontia', icon: Activity, label: 'Orto' }, { more: true }], biomedico: [{ id: 'dashboard', icon: LayoutDashboard, label: 'Início' }, { id: 'agenda', icon: CalendarIcon, label: 'Agenda' }, { id: 'tratamentos', icon: ClipboardList, label: 'Tratam.' }, { more: true }], protetico: [{ id: 'dashboard', icon: LayoutDashboard, label: 'Início' }, { id: 'agenda', icon: CalendarIcon, label: 'Agenda' }, { id: 'lancar-gto', icon: ClipboardList, label: 'GTO' }, { more: true }], paciente: [{ id: 'tratamentos', icon: ClipboardList, label: 'Tratamentos' }, { id: 'notificacoes', icon: Bell, label: 'Notif.' }, { id: 'configuracoes', icon: Settings, label: 'Config.' }], }; const MobileBottomNav: React.FC<{ activeTab: ViewKey; role: string; clinColor: string; onNavigate: (v: ViewKey) => void; onMore: () => void; }> = ({ activeTab, role, clinColor, onNavigate, onMore }) => { const tabs = BOTTOM_TABS[role] || BOTTOM_TABS['funcionario']; return ( ); }; // ------- App ------- const App: React.FC = () => { const ver = useAppVersion(); const [isSidebarOpen, setSidebarOpen] = useState(false); // Evita que a sidebar ANIME (slide-in) a cada reload no desktop: a transição só liga // após o 1º paint, então no load ela já nasce posicionada; toggles do usuário animam. const [uiMounted, setUiMounted] = useState(false); useEffect(() => { setUiMounted(true); }, []); // Gaveta de controles da Agenda no MOBILE (título/AGENDAR/ações) — comporta-se como // a sidebar: some por padrão p/ dar o máximo de espaço ao calendário; abre pelo ⋯. const [agendaControlsOpen, setAgendaControlsOpen] = useState(false); // Recolher a sidebar no DESKTOP (persistente) — dá largura ao conteúdo (ex.: agenda). const [isSidebarCollapsed, setSidebarCollapsed] = useState(() => { try { return localStorage.getItem('scoreodonto:sidebar-collapsed') === '1'; } catch { return false; } }); const toggleSidebarCollapsed = (v: boolean) => { setSidebarCollapsed(v); try { localStorage.setItem('scoreodonto:sidebar-collapsed', v ? '1' : '0'); } catch { /* ignore */ } }; const [, forcePwRerender] = useState(0); // re-avalia o modal de troca obrigatória de senha const currentRole = HybridBackend.getCurrentRole(); const activeWorkspace = HybridBackend.getActiveWorkspace(); const clinColor = activeWorkspace?.cor || '#2563eb'; const isViewAllowed = (view: ViewKey, role: string): boolean => { if (['landing', 'login', 'public'].includes(view)) return true; // /update (migração de banco) é EXCLUSIVA do superadmin (liberado na linha seguinte). if (role === 'superadmin') return true; // Sync GLOBAL removido do app (substituído pelo backup por clínica em Configurações). if (view === 'sync') return false; // Locação de Salas: capability PESSOAL (add-on por conta), liberada a qualquer // usuário com o plugin ativo na conta — independe de ter clínica/cargo. if (view === 'salas' || view === 'minhas-salas' || view === 'alugar-salas') return isPluginActive('locacao-salas'); // NewWhats: Inbox e Secretária liberadas a qualquer usuário com o plugin ativo // (a CONFIG do plugin é exclusiva do superadmin via view 'plugins'). if (view === 'wa-inbox' || view === 'wa-sessions' || view === 'wa-secretaria') return isPluginActive('newwhats'); // Contexto de SALA: isola da clínica — só painel da sala/salas/conta/notificações. if ((HybridBackend.getActiveWorkspace() as any)?.tipo === 'sala') return ['sala-home', 'configuracoes', 'notificacoes', 'clinicas'].includes(view); // Contexto de LABORATÓRIO: área do protético — painel do lab + bancada/conta/notificações. if ((HybridBackend.getActiveWorkspace() as any)?.tipo === 'laboratorio') return ['lab-home', 'lab-bancada', 'lab-agenda', 'lab-clientes', 'lab-cotacoes', 'lab-financeiro', 'lab-equipe', 'lab-indicadores', 'marketplace-profissionais', 'diretorio-profissionais', 'configuracoes', 'notificacoes', 'clinicas'].includes(view); // Catálogo de PLUGINS: EXCLUSIVO do superadmin (já retorna true acima); ninguém mais. if (view === 'plugins') return false; // Admin/donos têm acesso amplo, MAS as telas de superadmin são exclusivas do superadmin. if (role === 'admin' || role === 'donoclinica' || role === 'donoconsultorio') return !view.startsWith('superadmin'); // Equipe: acesso por CARGO (RBAC) no workspace ativo. const ws: any = HybridBackend.getActiveWorkspace(); if (ws && ['donoclinica', 'donoconsultorio', 'admin'].includes(ws.role)) return !view.startsWith('superadmin'); // Páginas sempre disponíveis a qualquer membro (navegação/conta). if (['configuracoes', 'notificacoes', 'clinicas'].includes(view)) return true; // Só gateia por cargo quando há permissões EXPLÍCITAS. Cargo sem permissões // (ou membro sem cargo) herda o menu padrão do role (fallback abaixo). if (Array.isArray(ws?.permissoes) && ws.permissoes.length > 0) return (ws.permissoes as string[]).includes(view); const permissions: Record = { paciente: ['tratamentos', 'notificacoes', 'configuracoes'], dentista: ['dashboard', 'agenda', 'ortodontia', 'tratamentos', 'lancar-gto', 'proteses', 'notificacoes', 'clinicas', 'configuracoes', 'plugins', 'rx-radiografias', 'salas', 'marketplace-profissionais', 'candidatura-tutor', 'tutoria-painel', 'diretorio-profissionais'], biomedico: ['dashboard', 'agenda', 'tratamentos', 'lancar-gto', 'especialidades', 'procedimentos', 'notificacoes', 'clinicas', 'configuracoes', 'plugins', 'salas', 'marketplace-profissionais', 'diretorio-profissionais'], protetico: ['lab-home', 'lab-bancada', 'lab-agenda', 'lab-clientes', 'lab-cotacoes', 'lab-financeiro', 'lab-equipe', 'lab-indicadores', 'notificacoes', 'clinicas', 'configuracoes', 'plugins', 'salas', 'marketplace-profissionais', 'diretorio-profissionais'], funcionario: ['dashboard', 'leads', 'pacientes', 'agenda', 'financeiro', 'tratamentos', 'lancar-gto', 'proteses', 'relatorios', 'notificacoes', 'clinicas', 'configuracoes', 'orcamentos', 'contratos', 'plugins', 'rx-radiografias', 'salas', 'marketplace-profissionais', 'candidatura-tutor', 'diretorio-profissionais'], }; return permissions[role]?.includes(view) || false; }; const getDefaultViewForRole = (role: string): ViewKey => { if (role === 'superadmin') return 'superadmin-overview'; // Contexto de SALA (workspace tipo='sala') sempre cai no painel da sala. if ((HybridBackend.getActiveWorkspace() as any)?.tipo === 'sala') return 'sala-home'; // Contexto de LABORATÓRIO (workspace tipo='laboratorio') cai no painel do lab. if ((HybridBackend.getActiveWorkspace() as any)?.tipo === 'laboratorio') return 'lab-home'; if (role === 'paciente') return 'tratamentos'; if (role === 'donosala') return 'minhas-salas'; return 'dashboard'; }; const getStoredUserRole = (): string => { try { const raw = localStorage.getItem('SCOREODONTO_USER_DATA'); return raw ? (JSON.parse(raw).role ?? 'paciente') : 'paciente'; } catch { return 'paciente'; } }; const getInitialView = (): ViewKey => { const fromUrl = resolveViewFromUrl(); const isAuthed = HybridBackend.isAuthenticated(); // Superadmin pode acessar páginas públicas (landing / contato) diretamente, // sem ser rebotado de volta para o painel. if (isAuthed && getStoredUserRole() === 'superadmin' && ['landing', 'public'].includes(fromUrl)) { return fromUrl; } // Authenticated but no workspace → route to the right post-registration screen if (isAuthed && !HybridBackend.getActiveWorkspace()) { const storedRole = getStoredUserRole(); if (storedRole === 'superadmin') return 'superadmin-overview'; if (storedRole === 'admin' || storedRole === 'donoclinica' || storedRole === 'donoconsultorio') return 'criar-clinica'; if (storedRole === 'donosala') return 'minhas-salas'; if (storedRole === 'dentista' || storedRole === 'biomedico' || storedRole === 'protetico') return 'aguardando-convite'; return 'aguardando-convite'; } const role = HybridBackend.getCurrentRole(); // Authenticated visiting landing/login → default for role if (isAuthed && ['landing', 'login'].includes(fromUrl)) return getDefaultViewForRole(role); // Protected route without auth → landing const isProtected = !['landing', 'login', 'public'].includes(fromUrl); if (isProtected && !isAuthed) return 'landing'; // Authenticated but lacking permission for the requested view if (isAuthed && !isViewAllowed(fromUrl, role)) return getDefaultViewForRole(role); return fromUrl; }; const [currentView, setCurrentView] = useState(getInitialView); const handleNavigate = (view: ViewKey) => { const freshRole = HybridBackend.getCurrentRole(); if (isViewAllowed(view, freshRole)) { setCurrentView(view); navigate(view); setSidebarOpen(false); } }; useEffect(() => { const onHashChange = () => { const fromUrl = resolveViewFromUrl(); const role = HybridBackend.getCurrentRole(); const isProtected = !['landing', 'login', 'public'].includes(fromUrl); if (isProtected && !HybridBackend.isAuthenticated()) { setCurrentView('landing'); navigate('landing'); return; } if (HybridBackend.isAuthenticated() && !isViewAllowed(fromUrl, role)) { const defView = getDefaultViewForRole(role); setCurrentView(defView); navigate(defView); return; } setCurrentView(fromUrl); }; window.addEventListener('popstate', onHashChange); return () => window.removeEventListener('popstate', onHashChange); }, []); useEffect(() => { document.documentElement.style.setProperty('--brand-color', clinColor); document.documentElement.style.setProperty('--brand-color-soft', `${clinColor}11`); }, [clinColor]); useEffect(() => { navigate(currentView); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // Sincroniza os plugins ativados na conta (servidor → cache local) ao montar, // para o menu de Salas refletir a ativação mesmo em outro dispositivo. useEffect(() => { if (HybridBackend.isAuthenticated() && !HybridBackend.isPreviewMode()) { HybridBackend.hydrateMyPlugins().then(() => forcePwRerender(t => t + 1)); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // Roteamento pós-login/cadastro. const onAuthSuccess = (result?: { noWorkspace?: boolean; userRole?: string }) => { if (result?.noWorkspace) { const userRole = result.userRole || 'dentista'; if (userRole === 'superadmin') { setCurrentView('superadmin'); navigate('superadmin'); } else if (userRole === 'donosala') { setCurrentView('minhas-salas'); navigate('minhas-salas'); } else if (userRole === 'admin' || userRole === 'donoclinica' || userRole === 'donoconsultorio') { setCurrentView('criar-clinica'); navigate('criar-clinica'); } else { setCurrentView('aguardando-convite'); navigate('aguardando-convite'); } return; } const storedRole = getStoredUserRole(); if (storedRole === 'superadmin') { setCurrentView('superadmin-overview'); navigate('superadmin-overview'); return; } if (storedRole === 'donoconsultorio') { setCurrentView('dashboard'); navigate('dashboard'); return; } const role = HybridBackend.getCurrentRole(); const view = getDefaultViewForRole(role); setCurrentView(view); navigate(view); }; const renderView = () => { switch (currentView) { case 'landing': return handleNavigate('login')} isAuthenticated={HybridBackend.isAuthenticated()} userName={HybridBackend.getCurrentUserName()} onGoToArea={() => handleNavigate(getDefaultViewForRole(HybridBackend.getCurrentRole()))} />; case 'dashboard': return ; case 'leads': return ; case 'public': return ; case 'pacientes': return ; case 'agenda': return toggleSidebarCollapsed(!isSidebarCollapsed)} controlsOpen={agendaControlsOpen} onCloseControls={() => setAgendaControlsOpen(false)} />; case 'ortodontia': return ; case 'financeiro': return ; case 'dentistas': return ; case 'especialidades': return ; case 'procedimentos': return ; case 'planos': return ; case 'sync': return ; case 'tratamentos': return ; case 'lancar-gto': return ; case 'update': return ; case 'clinicas': return ; case 'notificacoes': return ; case 'relatorios': return ; case 'comissoes': return ; case 'glosas': return ; case 'proteses': return ; case 'cadastro-dentista': return ; case 'configuracoes': return ; case 'orcamentos': return ; case 'contratos': return ; case 'plugins': return ; case 'rx-radiografias': return ; case 'wa-inbox': return ; case 'wa-sessions': return ; case 'wa-secretaria': return ; case 'salas': return ; case 'minhas-salas': return ; case 'alugar-salas': return ; case 'sala-home': return ; case 'lab-home': return ; case 'lab-bancada': return ; case 'lab-agenda': return ; case 'lab-clientes': return ; case 'lab-financeiro': return ; case 'lab-cotacoes': return ; case 'lab-equipe': return ; case 'lab-indicadores': return ; case 'marketplace-profissionais': return ; case 'criar-clinica': return { window.location.reload(); }} />; case 'rx-config': return ; case 'gestao-equipe': return ; case 'tutoria-painel': return ; case 'candidatura-tutor': return ; case 'admin-gestao-tutores': return ; case 'aguardando-convite': return ; case 'superadmin': return ; case 'superadmin-overview': return ; case 'superadmin-users': return ; case 'superadmin-clinicas': return ; case 'superadmin-logs': return ; case 'superadmin-financeiro': return ; case 'superadmin-notificacoes': return ; case 'superadmin-secretaria': return ; // Plugin Profissionais ativo absorve o diretório antigo (mesma rota, view nova). case 'diretorio-profissionais': return isPluginActive('profissionais-marketplace') ? : ; case 'login': default: return ; } }; // Views do plugin NewWhats rodam em tela cheia (sem sidebar/barra do // scoreodonto) — cada uma tem seu próprio "Voltar" (onNavigate) para sair. // /wa-sessions (gestão de sessões WhatsApp) roda COM a sidebar do scoreodonto // (é área de administração). wa-inbox/wa-secretaria seguem em tela cheia. const isStandaloneView = ['landing', 'login', 'public', 'update', 'cadastro-dentista', 'criar-clinica', 'aguardando-convite', 'wa-inbox', 'wa-secretaria'].includes(currentView); return ( <> {HybridBackend.isAuthenticated() && HybridBackend.mustChangePassword() && ( forcePwRerender(t => t + 1)} /> )}
{!isStandaloneView && ( <> {isSidebarOpen && (
setSidebarOpen(false)} >
)}
handleNavigate(t as ViewKey)} />
)} {/* Botão flutuante para reexibir a sidebar (desktop, quando recolhida). Na agenda NÃO aparece — ela já tem o próprio toggle ao lado do título. */} {!isStandaloneView && isSidebarCollapsed && currentView !== 'agenda' && ( )}
{HybridBackend.isPreviewMode() && (
Modo visualização — vendo a área como {HybridBackend.getPreviewRole()}
)} {!isStandaloneView && ( <>
S
SCOREODONTO
{/* ⋯ controles da Agenda (só nesta view) — abre a gaveta; senão, espaçador */} {currentView === 'agenda' ? ( ) : (
)}
)}
{isStandaloneView ? ( renderView() ) : (
{renderView()}
)}
{/* Versão sempre visível no canto inferior direito (lê /api/version; não bloqueia cliques) */}
{ver.version}
); }; export default App;