feat(newwhats): frontend do plugin (Inbox/Sessions/Secretária) em tela cheia

- /wa-inbox, /wa-sessions e /wa-secretaria rodam sem a sidebar do scoreodonto
  (isStandaloneView); cada tela tem seu "Voltar" (Secretária ganhou botão na ThinNav).
- SessionsView: banner "Você já possui acesso ao WhatsApp" quando há sessão ativa.
- avatares: usa a URL do CDN (contactAvatarUrl/instance.avatar) direto; Avatar
  exibe sem depender de version; self-heal no onError re-busca a foto atual.
- deps: framer-motion, immer. Dev override com HMR (docker-compose.dev.yml).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
VPS 4 Builder
2026-07-01 20:54:24 +02:00
parent 6593993b3d
commit 3042ddca38
54 changed files with 12641 additions and 34 deletions
+13 -4
View File
@@ -13,6 +13,9 @@ 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';
@@ -89,6 +92,7 @@ export type ViewKey =
| 'plugins'
| 'rx-radiografias'
| 'wa-inbox'
| 'wa-sessions'
| 'wa-secretaria'
| 'salas'
| 'minhas-salas'
@@ -149,6 +153,7 @@ const ROUTE_MAP: Record<string, ViewKey> = {
'/plugins': 'plugins',
'/rx-radiografias': 'rx-radiografias',
'/wa-inbox': 'wa-inbox',
'/wa-sessions': 'wa-sessions',
'/wa-secretaria': 'wa-secretaria',
'/salas': 'salas',
'/minhas-salas': 'minhas-salas',
@@ -214,6 +219,7 @@ const VIEW_TO_ROUTE: Record<ViewKey, string> = {
plugins: '/plugins',
'rx-radiografias': '/rx-radiografias',
'wa-inbox': '/wa-inbox',
'wa-sessions': '/wa-sessions',
'wa-secretaria': '/wa-secretaria',
salas: '/salas',
'minhas-salas': '/minhas-salas',
@@ -325,7 +331,7 @@ const App: React.FC = () => {
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-secretaria') return isPluginActive('newwhats');
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.
@@ -517,8 +523,9 @@ const App: React.FC = () => {
case 'contratos': return <ContratosView />;
case 'plugins': return <PluginsView />;
case 'rx-radiografias': return <RXPlugin />;
case 'wa-inbox': return <WaInboxView />;
case 'wa-secretaria': return <WaSecretariaView />;
case 'wa-inbox': return <InboxView onNavigate={handleNavigate} />;
case 'wa-sessions': return <SessionsView onNavigate={handleNavigate} />;
case 'wa-secretaria': return <SecretariaView onNavigate={handleNavigate} />;
case 'salas': return <SalasPlugin />;
case 'minhas-salas': return <SalasPlugin view="minhas" />;
case 'alugar-salas': return <SalasPlugin view="alugar" />;
@@ -554,7 +561,9 @@ const App: React.FC = () => {
}
};
const isStandaloneView = ['landing', 'login', 'public', 'update', 'cadastro-dentista', 'criar-clinica', 'aguardando-convite'].includes(currentView);
// Views do plugin NewWhats rodam em tela cheia (sem sidebar/barra do
// scoreodonto) — cada uma tem seu próprio "Voltar" (onNavigate) para sair.
const isStandaloneView = ['landing', 'login', 'public', 'update', 'cadastro-dentista', 'criar-clinica', 'aguardando-convite', 'wa-inbox', 'wa-sessions', 'wa-secretaria'].includes(currentView);
return (
<>