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:
@@ -75,6 +75,9 @@ export const HybridBackend = {
|
||||
localStorage.setItem('SCOREODONTO_ACTIVE_WORKSPACE', JSON.stringify(data.workspaces[0]));
|
||||
}
|
||||
|
||||
// Hidrata os plugins ativados na conta do usuário (fonte da verdade no servidor).
|
||||
await HybridBackend.hydrateMyPlugins();
|
||||
|
||||
if (data.noWorkspace) {
|
||||
return { ok: true, noWorkspace: true, userRole: data.userRole };
|
||||
}
|
||||
@@ -256,6 +259,12 @@ export const HybridBackend = {
|
||||
getPreviewRole: (): string | null => localStorage.getItem('SCOREODONTO_PREVIEW_ROLE'),
|
||||
|
||||
enterPreview: (role: string) => {
|
||||
// Na primeira entrada do preview, guarda os plugins reais do superadmin p/ restaurar no exit.
|
||||
// (não sobrescreve o backup se já estiver previewando e só trocando de papel)
|
||||
if (!localStorage.getItem('SCOREODONTO_PREVIEW_ROLE')) {
|
||||
const prevPlugins = localStorage.getItem('SCOREODONTO_ACTIVE_PLUGINS');
|
||||
localStorage.setItem('SCOREODONTO_PREVIEW_PREV_PLUGINS', prevPlugins === null ? '__none__' : prevPlugins);
|
||||
}
|
||||
const synthetic = {
|
||||
id: '00000000-0000-0000-0000-000000000000', // UUID inexistente → consultas retornam vazio
|
||||
nome: `VISUALIZAÇÃO: ${role.toUpperCase()}`,
|
||||
@@ -266,6 +275,8 @@ export const HybridBackend = {
|
||||
localStorage.setItem('SCOREODONTO_PREVIEW_ROLE', role);
|
||||
localStorage.setItem('SCOREODONTO_ACTIVE_WORKSPACE', JSON.stringify(synthetic));
|
||||
localStorage.setItem('SCOREODONTO_WORKSPACES', JSON.stringify([synthetic]));
|
||||
// Preview ativa os plugins para o superadmin ver/testar as telas (RX, Salas, Profissionais).
|
||||
localStorage.setItem('SCOREODONTO_ACTIVE_PLUGINS', JSON.stringify(['rx-scoreodonto', 'locacao-salas', 'profissionais-marketplace']));
|
||||
const path = role === 'paciente' ? '/tratamentos' : '/dashboard';
|
||||
window.location.assign(path);
|
||||
},
|
||||
@@ -274,9 +285,48 @@ export const HybridBackend = {
|
||||
localStorage.removeItem('SCOREODONTO_PREVIEW_ROLE');
|
||||
localStorage.removeItem('SCOREODONTO_ACTIVE_WORKSPACE');
|
||||
localStorage.removeItem('SCOREODONTO_WORKSPACES');
|
||||
// Restaura os plugins que o superadmin tinha antes do preview.
|
||||
const prev = localStorage.getItem('SCOREODONTO_PREVIEW_PREV_PLUGINS');
|
||||
if (prev === null || prev === '__none__') localStorage.removeItem('SCOREODONTO_ACTIVE_PLUGINS');
|
||||
else localStorage.setItem('SCOREODONTO_ACTIVE_PLUGINS', prev);
|
||||
localStorage.removeItem('SCOREODONTO_PREVIEW_PREV_PLUGINS');
|
||||
window.location.assign('/superadmin/overview');
|
||||
},
|
||||
|
||||
// --- PLUGINS POR USUÁRIO (add-on por conta; fonte da verdade no servidor) ---
|
||||
getMyPlugins: async (): Promise<string[]> => {
|
||||
try {
|
||||
const res = await apiFetch(`${API_URL}/me/plugins`);
|
||||
if (!res.ok) return [];
|
||||
const d = await res.json();
|
||||
return Array.isArray(d?.plugins) ? d.plugins : [];
|
||||
} catch { return []; }
|
||||
},
|
||||
|
||||
setMyPlugin: async (pluginId: string, ativo: boolean): Promise<boolean> => {
|
||||
try {
|
||||
const res = await apiFetch(`${API_URL}/me/plugins`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ pluginId, ativo }),
|
||||
});
|
||||
return res.ok;
|
||||
} catch { return false; }
|
||||
},
|
||||
|
||||
// Reconcilia o cache local (SCOREODONTO_ACTIVE_PLUGINS) com o servidor.
|
||||
// Escopo atual: só 'locacao-salas' é server-backed; demais plugins seguem locais.
|
||||
// Não mexe durante o modo visualização (preview sobrescreve os plugins temporariamente).
|
||||
hydrateMyPlugins: async (): Promise<void> => {
|
||||
if (localStorage.getItem('SCOREODONTO_PREVIEW_ROLE')) return;
|
||||
const server = await HybridBackend.getMyPlugins();
|
||||
let local: string[] = [];
|
||||
try { local = JSON.parse(localStorage.getItem('SCOREODONTO_ACTIVE_PLUGINS') || '[]'); } catch { local = []; }
|
||||
const next = local.filter(p => p !== 'locacao-salas');
|
||||
if (server.includes('locacao-salas')) next.push('locacao-salas');
|
||||
localStorage.setItem('SCOREODONTO_ACTIVE_PLUGINS', JSON.stringify(next));
|
||||
},
|
||||
|
||||
isAuthenticated: (): boolean => {
|
||||
return !!localStorage.getItem('SCOREODONTO_AUTH_TOKEN');
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user