feat(plugins): Salas como add-on por conta (pessoa-cêntrico) + Visualizar como biomédico

Superadmin / Visualizar como:
- adiciona BIOMÉDICO(A) à lista de preview do superadmin
- enterPreview ativa os plugins (RX/Salas/Profissionais) p/ teste; exitPreview restaura

Locação de Salas — ativação POR USUÁRIO (independe de clínica):
- backend: tabela usuario_plugins + GET/POST /api/me/plugins (authGuard, sem tenant)
- sala continua dona=owner_usuario_id (pessoa) + salaID, clinica_id opcional
- backend.ts: getMyPlugins/setMyPlugin/hydrateMyPlugins; login + App montam o hydrate
- isViewAllowed: 'salas' liberado se plugin ativo na conta; catálogo 'plugins' p/ não-paciente
- Sidebar: entrada ADD-ONS/PLUGINS p/ papéis operacionais ativarem
- PluginsView: toggle de locacao-salas persiste no servidor
- escopo: só locacao-salas é server-backed (RX/Profissionais seguem localStorage)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
VPS 4 Builder
2026-06-13 15:04:25 +02:00
parent 98728b4e57
commit ca7eba4531
5 changed files with 108 additions and 1 deletions
+14
View File
@@ -251,6 +251,11 @@ const App: React.FC = () => {
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') return isPluginActive('locacao-salas');
// Catálogo de add-ons: qualquer usuário (menos paciente) pode abrir p/ ativar.
if (view === 'plugins') return role !== 'paciente';
// 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');
@@ -366,6 +371,15 @@ const App: React.FC = () => {
// 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
}, []);
const renderView = () => {
switch (currentView) {
case 'landing': return <LandingPage