ZERADO
This commit is contained in:
+191
-40
@@ -4,42 +4,175 @@
|
||||
// 📦 Importação dos menus base por área
|
||||
// ======================================================
|
||||
|
||||
import adminMenu from './menus/admin.menu'
|
||||
import adminMenu from './menus/clinic.menu'
|
||||
import therapistMenu from './menus/therapist.menu'
|
||||
import supervisorMenu from './menus/supervisor.menu'
|
||||
import editorMenu from './menus/editor.menu'
|
||||
import portalMenu from './menus/portal.menu'
|
||||
import sakaiDemoMenu from './menus/sakai.demo.menu'
|
||||
import saasMenu from './menus/saas.menu'
|
||||
|
||||
import { useSaasHealthStore } from '@/stores/saasHealthStore'
|
||||
import { useTenantFeaturesStore } from '@/stores/tenantFeaturesStore'
|
||||
import { useEntitlementsStore } from '@/stores/entitlementsStore'
|
||||
|
||||
// ======================================================
|
||||
// 🎭 Mapeamento de role → menu base
|
||||
// ======================================================
|
||||
|
||||
const MENUS = {
|
||||
// ✅ role real do tenant
|
||||
clinic_admin: adminMenu,
|
||||
tenant_admin: adminMenu, // alias
|
||||
therapist: therapistMenu,
|
||||
supervisor: supervisorMenu,
|
||||
editor: editorMenu,
|
||||
patient: portalMenu,
|
||||
|
||||
// ✅ compatibilidade profiles.role
|
||||
admin: adminMenu,
|
||||
|
||||
// ✅ legado
|
||||
tenant_admin: adminMenu
|
||||
portal_user: portalMenu, // alias (globalRole do paciente)
|
||||
saas_admin: saasMenu
|
||||
}
|
||||
|
||||
// ======================================================
|
||||
// 🧠 Função utilitária
|
||||
// Permite que o menu seja:
|
||||
// - Array direto
|
||||
// - ou função (ctx) => Array
|
||||
// 🧠 Helpers
|
||||
// ======================================================
|
||||
|
||||
function resolveMenu (builder, ctx) {
|
||||
if (!builder) return []
|
||||
return typeof builder === 'function' ? builder(ctx) : builder
|
||||
try {
|
||||
return typeof builder === 'function' ? builder(ctx) : builder
|
||||
} catch (e) {
|
||||
// se um builder estourar, não derruba o app: cai no fallback
|
||||
console.warn('[NAV] menu builder error:', e)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
// core menu anti-“sumir”
|
||||
function coreMenu () {
|
||||
return [
|
||||
{
|
||||
label: 'Geral',
|
||||
items: [
|
||||
{ label: 'Início', icon: 'pi pi-home', to: '/' },
|
||||
{ label: 'Assinatura', icon: 'pi pi-credit-card', to: '/billing' },
|
||||
{ label: 'Perfil', icon: 'pi pi-user', to: '/profile' }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
function safeHasFeature (fn, key) {
|
||||
try { return !!fn?.(key) } catch { return false }
|
||||
}
|
||||
|
||||
// ======================================================
|
||||
// 🧩 Decorator do menu
|
||||
// - NÃO remove itens
|
||||
// - Apenas calcula badge PRO dinâmico (com base em entitlements)
|
||||
// ======================================================
|
||||
|
||||
function decorateMenu (menu, hasFeature) {
|
||||
const arr = Array.isArray(menu) ? menu : []
|
||||
return arr.map((group) => {
|
||||
if (group?.items && Array.isArray(group.items)) {
|
||||
return { ...group, items: decorateItems(group.items, hasFeature) }
|
||||
}
|
||||
return group
|
||||
})
|
||||
}
|
||||
|
||||
function decorateItems (items, hasFeature) {
|
||||
return (items || []).map((it) => {
|
||||
if (it?.items && Array.isArray(it.items)) {
|
||||
return { ...it, items: decorateItems(it.items, hasFeature) }
|
||||
}
|
||||
|
||||
const featureKey = it?.feature ? String(it.feature) : null
|
||||
const showPro = !!it?.proBadge && !!featureKey && !safeHasFeature(hasFeature, featureKey)
|
||||
|
||||
return { ...it, __showProBadge: showPro }
|
||||
})
|
||||
}
|
||||
|
||||
// ======================================================
|
||||
// ✅ Normalização de role (evita menu vazio)
|
||||
// ======================================================
|
||||
|
||||
function normalizeRole (role) {
|
||||
const r = String(role || '').trim()
|
||||
if (!r) return null
|
||||
|
||||
// aliases comuns (blindagem)
|
||||
if (r === 'tenant_admin') return 'clinic_admin'
|
||||
if (r === 'admin') return 'clinic_admin'
|
||||
if (r === 'clinic') return 'clinic_admin'
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// ======================================================
|
||||
// ✅ hasFeature robusto
|
||||
// - Prioriza sessionCtx (evita flicker)
|
||||
// - Senão tenta deduzir do store com vários formatos comuns
|
||||
// ======================================================
|
||||
|
||||
function buildHasFeature (sessionCtx, entitlementsStore) {
|
||||
// 1) se já veio pronto, usa
|
||||
if (typeof sessionCtx?.hasFeature === 'function') return sessionCtx.hasFeature
|
||||
|
||||
// 2) se veio entitlements como objeto { key: true }
|
||||
if (sessionCtx?.entitlements && typeof sessionCtx.entitlements === 'object') {
|
||||
const bag = sessionCtx.entitlements
|
||||
return (k) => !!bag[String(k || '').trim()]
|
||||
}
|
||||
|
||||
// 3) se veio allowedFeatures como array ou Set
|
||||
if (sessionCtx?.allowedFeatures) {
|
||||
const af = sessionCtx.allowedFeatures
|
||||
if (Array.isArray(af)) {
|
||||
const s = new Set(af.map((x) => String(x).trim()).filter(Boolean))
|
||||
return (k) => s.has(String(k || '').trim())
|
||||
}
|
||||
if (af instanceof Set) {
|
||||
return (k) => af.has(String(k || '').trim())
|
||||
}
|
||||
}
|
||||
|
||||
// 4) fallback no store
|
||||
return (featureKey) => {
|
||||
const k = String(featureKey || '').trim()
|
||||
if (!k) return false
|
||||
|
||||
try {
|
||||
if (typeof entitlementsStore?.hasFeature === 'function') return !!entitlementsStore.hasFeature(k)
|
||||
if (typeof entitlementsStore?.has === 'function') return !!entitlementsStore.has(k)
|
||||
|
||||
// formatos comuns:
|
||||
// - entitlements: { key: true }
|
||||
// - entitlements: Set([...])
|
||||
// - entitlements: Array([...])
|
||||
// - byOwner: { [ownerId]: { key: true } }
|
||||
const bag =
|
||||
entitlementsStore?.entitlements ||
|
||||
entitlementsStore?.features ||
|
||||
entitlementsStore?.data ||
|
||||
entitlementsStore?.state
|
||||
|
||||
if (bag instanceof Set) return bag.has(k)
|
||||
if (Array.isArray(bag)) return bag.includes(k)
|
||||
if (bag && typeof bag === 'object') {
|
||||
// tenta direto
|
||||
if (Object.prototype.hasOwnProperty.call(bag, k)) return !!bag[k]
|
||||
|
||||
// tenta byOwner (caso exista)
|
||||
const ownerId = sessionCtx?.ownerId || sessionCtx?.activeTenantId || sessionCtx?.tenantId
|
||||
if (ownerId && bag[ownerId] && typeof bag[ownerId] === 'object') {
|
||||
return !!bag[ownerId][k]
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// ======================================================
|
||||
@@ -47,50 +180,68 @@ function resolveMenu (builder, ctx) {
|
||||
// ======================================================
|
||||
|
||||
export function getMenuByRole (role, sessionCtx = {}) {
|
||||
// 🔹 Store de health do SaaS (badge dinâmica)
|
||||
// ⚠️ Não faz fetch aqui. O AppMenu carrega o store.
|
||||
const saasHealthStore = useSaasHealthStore()
|
||||
const mismatchCount = saasHealthStore?.mismatchCount || 0
|
||||
|
||||
// 🔹 Store de módulos por tenant (tenant_features)
|
||||
// ⚠️ Não faz fetch aqui. O guard/app deve carregar. Aqui só lemos cache.
|
||||
const tenantFeaturesStore = useTenantFeaturesStore()
|
||||
const entitlementsStore = useEntitlementsStore()
|
||||
|
||||
// 🔹 SaaS overlay aparece somente para SaaS master
|
||||
const isSaas = sessionCtx?.isSaasAdmin === true
|
||||
|
||||
// ctx que será passado pros menu builders
|
||||
const tenantFeatureEnabled =
|
||||
typeof sessionCtx?.tenantFeatureEnabled === 'function'
|
||||
? sessionCtx.tenantFeatureEnabled
|
||||
: (key) => {
|
||||
try { return !!tenantFeaturesStore?.isEnabled?.(key) } catch { return false }
|
||||
}
|
||||
|
||||
const tenantLoading =
|
||||
typeof sessionCtx?.tenantLoading === 'function'
|
||||
? sessionCtx.tenantLoading
|
||||
: () => false
|
||||
|
||||
const tenantFeaturesLoading =
|
||||
typeof sessionCtx?.tenantFeaturesLoading === 'function'
|
||||
? sessionCtx.tenantFeaturesLoading
|
||||
: () => false
|
||||
|
||||
const hasFeature = buildHasFeature(sessionCtx, entitlementsStore)
|
||||
|
||||
const ctx = {
|
||||
...sessionCtx,
|
||||
mismatchCount,
|
||||
tenantFeaturesStore,
|
||||
tenantFeatureEnabled: (key) => {
|
||||
try { return !!tenantFeaturesStore?.isEnabled?.(key) } catch { return false }
|
||||
}
|
||||
tenantFeatureEnabled,
|
||||
tenantLoading,
|
||||
tenantFeaturesLoading,
|
||||
entitlementsStore,
|
||||
hasFeature
|
||||
}
|
||||
|
||||
// 🔹 Menu base da role
|
||||
const base = resolveMenu(MENUS[role], ctx)
|
||||
// ✅ role normalizado
|
||||
const r = normalizeRole(role)
|
||||
|
||||
// 🔹 Resolve menu SaaS (array ou função)
|
||||
const saas = typeof saasMenu === 'function'
|
||||
const baseRaw = resolveMenu(MENUS[r], ctx)
|
||||
const base = decorateMenu(baseRaw, hasFeature)
|
||||
|
||||
const saasRaw = typeof saasMenu === 'function'
|
||||
? saasMenu(ctx, { mismatchCount })
|
||||
: saasMenu
|
||||
const saas = decorateMenu(saasRaw, hasFeature)
|
||||
|
||||
// ======================================================
|
||||
// 🚀 Menu final
|
||||
// - base sempre
|
||||
// - overlay SaaS só para SaaS master
|
||||
// - Demo Sakai só para SaaS master em DEV
|
||||
// ======================================================
|
||||
// 🔒 SaaS master: somente área SaaS
|
||||
if (isSaas) {
|
||||
const out = [
|
||||
...(saas.length ? saas : coreMenu()),
|
||||
...(import.meta.env.DEV ? [{ separator: true }, ...sakaiDemoMenu] : [])
|
||||
]
|
||||
return out
|
||||
}
|
||||
|
||||
return [
|
||||
...base,
|
||||
// ✅ fallback: nunca retorna vazio
|
||||
if (!base || !base.length) {
|
||||
return coreMenu()
|
||||
}
|
||||
|
||||
...(isSaas && saas.length ? [{ separator: true }, ...saas] : []),
|
||||
|
||||
...(isSaas && import.meta.env.DEV
|
||||
? [{ separator: true }, ...sakaiDemoMenu]
|
||||
: [])
|
||||
]
|
||||
return [...base]
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
// src/navigation/menus/admin.menu.js
|
||||
|
||||
export default function adminMenu (ctx = {}) {
|
||||
const patientsOn = !!ctx?.tenantFeatureEnabled?.('patients')
|
||||
|
||||
const menu = [
|
||||
// =====================================================
|
||||
// 📊 OPERAÇÃO
|
||||
// =====================================================
|
||||
{
|
||||
label: 'Operação',
|
||||
items: [
|
||||
{
|
||||
label: 'Dashboard',
|
||||
icon: 'pi pi-fw pi-home',
|
||||
to: '/admin'
|
||||
},
|
||||
{
|
||||
label: 'Agenda do Terapeuta',
|
||||
icon: 'pi pi-fw pi-sitemap',
|
||||
to: '/therapist/agenda',
|
||||
feature: 'agenda.view'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
// =====================================================
|
||||
// 👥 PACIENTES (somente se módulo ativo)
|
||||
// =====================================================
|
||||
if (patientsOn) {
|
||||
menu.push({
|
||||
label: 'Pacientes',
|
||||
items: [
|
||||
{
|
||||
label: 'Lista de Pacientes',
|
||||
icon: 'pi pi-fw pi-users',
|
||||
to: '/admin/pacientes'
|
||||
},
|
||||
{
|
||||
label: 'Grupos',
|
||||
icon: 'pi pi-fw pi-users',
|
||||
to: '/admin/pacientes/grupos'
|
||||
},
|
||||
{
|
||||
label: 'Tags',
|
||||
icon: 'pi pi-fw pi-tags',
|
||||
to: '/admin/pacientes/tags'
|
||||
},
|
||||
{
|
||||
label: 'Link Externo',
|
||||
icon: 'pi pi-fw pi-link',
|
||||
to: '/admin/pacientes/link-externo'
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// ⚙️ GESTÃO DA CLÍNICA
|
||||
// =====================================================
|
||||
menu.push({
|
||||
label: 'Gestão',
|
||||
items: [
|
||||
{
|
||||
label: 'Profissionais',
|
||||
icon: 'pi pi-fw pi-id-card',
|
||||
to: '/admin/clinic/professionals'
|
||||
},
|
||||
{
|
||||
label: 'Módulos da Clínica',
|
||||
icon: 'pi pi-fw pi-sliders-h',
|
||||
to: '/admin/clinic/features'
|
||||
},
|
||||
{
|
||||
label: 'Assinatura',
|
||||
icon: 'pi pi-fw pi-credit-card',
|
||||
to: '/admin/billing'
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// =====================================================
|
||||
// 🔒 SISTEMA
|
||||
// =====================================================
|
||||
menu.push({
|
||||
label: 'Sistema',
|
||||
items: [
|
||||
{
|
||||
label: 'Segurança',
|
||||
icon: 'pi pi-fw pi-shield',
|
||||
to: '/admin/settings/security'
|
||||
},
|
||||
{
|
||||
label: 'Agendamento Online (PRO)',
|
||||
icon: 'pi pi-fw pi-calendar-plus',
|
||||
to: '/admin/online-scheduling',
|
||||
feature: 'online_scheduling.manage',
|
||||
proBadge: true
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
return menu
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
// src/navigation/menus/clinic.menu.js
|
||||
export default function adminMenu (ctx = {}) {
|
||||
const menu = [
|
||||
{
|
||||
label: 'Clínica',
|
||||
items: [
|
||||
// ✅ usar name real da rota (evita /admin cair em redirect estranho)
|
||||
{ label: 'Dashboard', icon: 'pi pi-fw pi-home', to: { name: 'admin.dashboard' } },
|
||||
|
||||
{
|
||||
label: 'Agenda da Clínica',
|
||||
icon: 'pi pi-fw pi-calendar',
|
||||
to: { name: 'admin-agenda-clinica' },
|
||||
feature: 'agenda.view'
|
||||
},
|
||||
|
||||
// ✅ Compromissos determinísticos (tipos)
|
||||
{
|
||||
label: 'Compromissos',
|
||||
icon: 'pi pi-fw pi-clock',
|
||||
to: { name: 'admin-agenda-compromissos' },
|
||||
feature: 'agenda.view'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// ✅ SEM IF: sempre existe, só fica visível quando a feature estiver ligada
|
||||
{
|
||||
label: 'Pacientes',
|
||||
visible: () => {
|
||||
// 1) enquanto tenant/features estão carregando, NÃO some (evita piscar ao trocar de aba)
|
||||
if (ctx?.tenantLoading?.()) return true
|
||||
if (ctx?.tenantFeaturesLoading?.()) return true
|
||||
|
||||
// 2) quando estabilizou, aí sim decide pela feature
|
||||
return !!ctx?.tenantFeatureEnabled?.('patients')
|
||||
},
|
||||
items: [
|
||||
// ✅ usar name real das rotas (você já tem todas no routes.clinic.js)
|
||||
{ label: 'Lista de Pacientes', icon: 'pi pi-fw pi-users', to: { name: 'admin-pacientes' } },
|
||||
{ label: 'Grupos', icon: 'pi pi-fw pi-sitemap', to: { name: 'admin-pacientes-grupos' } },
|
||||
{ label: 'Tags', icon: 'pi pi-fw pi-tags', to: { name: 'admin-pacientes-tags' } },
|
||||
{ label: 'Link Externo', icon: 'pi pi-fw pi-link', to: { name: 'admin-pacientes-link-externo' } },
|
||||
{ label: 'Cadastros recebidos', icon: 'pi pi-inbox', to: { name: 'admin-pacientes-recebidos' } }
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
label: 'Gestão',
|
||||
items: [
|
||||
{ label: 'Profissionais', icon: 'pi pi-fw pi-id-card', to: { name: 'admin-clinic-professionals' } },
|
||||
{
|
||||
label: 'Tipos de Clínicas',
|
||||
icon: 'pi pi-fw pi-sliders-h',
|
||||
to: { name: 'admin-clinic-features' },
|
||||
visible: () => {
|
||||
if (ctx?.tenantLoading?.()) return true // ← true durante loading (evita piscar)
|
||||
const role = ctx?.role?.()
|
||||
return role === 'clinic_admin' // tenant_admin normaliza para clinic_admin
|
||||
}
|
||||
},
|
||||
{ label: 'Meu plano', icon: 'pi pi-fw pi-credit-card', to: { name: 'admin-meu-plano' } }
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
label: 'Sistema',
|
||||
items: [
|
||||
{ label: 'Segurança', icon: 'pi pi-fw pi-shield', to: { name: 'admin-settings-security' } },
|
||||
{
|
||||
label: 'Agendamento Online (PRO)',
|
||||
icon: 'pi pi-fw pi-calendar-plus',
|
||||
to: { name: 'admin-online-scheduling' },
|
||||
feature: 'online_scheduling.manage',
|
||||
proBadge: true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
return menu
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// src/navigation/menus/editor.menu.js
|
||||
//
|
||||
// Menu da área de Editor de Conteúdo (plataforma de microlearning).
|
||||
// O Editor é um papel de PLATAFORMA (não de tenant).
|
||||
// Indicado pelo saas_admin via platform_roles[] na tabela profiles.
|
||||
//
|
||||
|
||||
export default [
|
||||
{
|
||||
label: 'Editor',
|
||||
items: [
|
||||
// ======================================================
|
||||
// 📊 DASHBOARD
|
||||
// ======================================================
|
||||
{ label: 'Dashboard', icon: 'pi pi-fw pi-home', to: '/editor' },
|
||||
|
||||
// ======================================================
|
||||
// 📚 CONTEÚDO
|
||||
// ======================================================
|
||||
{ label: 'Cursos', icon: 'pi pi-fw pi-book', to: '/editor/cursos' },
|
||||
{ label: 'Módulos', icon: 'pi pi-fw pi-th-large', to: '/editor/modulos' },
|
||||
{ label: 'Publicados', icon: 'pi pi-fw pi-check-circle', to: '/editor/publicados' },
|
||||
|
||||
// ======================================================
|
||||
// 👤 CONTA
|
||||
// ======================================================
|
||||
{ label: 'Meu plano', icon: 'pi pi-fw pi-credit-card', to: '/editor/meu-plano' },
|
||||
{ label: 'Meu Perfil', icon: 'pi pi-fw pi-user', to: '/account/profile' },
|
||||
{ label: 'Segurança', icon: 'pi pi-fw pi-shield', to: '/account/security' }
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -6,10 +6,11 @@ export default [
|
||||
// ✅ Básico (sempre)
|
||||
// ======================
|
||||
{ label: 'Dashboard', icon: 'pi pi-fw pi-home', to: '/portal' },
|
||||
{ label: 'Minha Agenda', icon: 'pi pi-fw pi-calendar-plus', to: '/portal/agenda' },
|
||||
{ label: 'Agendar Sessão', icon: 'pi pi-fw pi-user', to: '/portal/agenda/new' },
|
||||
{ label: 'Minhas sessões', icon: 'pi pi-fw pi-user', to: '/portal/sessoes' },
|
||||
// ✅ Conta é global, não do portal
|
||||
{ label: 'My Account', icon: 'pi pi-fw pi-user', to: '/account/profile' }
|
||||
{ label: 'Meu plano', icon: 'pi pi-fw pi-credit-card', to: '/portal/meu-plano' },
|
||||
{ label: 'Minha Conta', icon: 'pi pi-fw pi-user', to: '/account/profile' },
|
||||
{ label: 'Segurança', icon: 'pi pi-fw pi-shield', to: '/account/security' }
|
||||
|
||||
// =====================================================
|
||||
// 🔒 PRO (exemplos futuros no portal do paciente)
|
||||
|
||||
@@ -5,7 +5,6 @@ export default function saasMenu (sessionCtx, opts = {}) {
|
||||
|
||||
const mismatchCount = Number(opts?.mismatchCount || 0)
|
||||
|
||||
// ✅ helper p/ evitar repetir spread + manter comentários intactos
|
||||
const mismatchBadge = mismatchCount > 0
|
||||
? { badge: String(mismatchCount), badgeClass: 'p-badge p-badge-danger' }
|
||||
: {}
|
||||
@@ -14,49 +13,40 @@ export default function saasMenu (sessionCtx, opts = {}) {
|
||||
{
|
||||
label: 'SaaS',
|
||||
icon: 'pi pi-building',
|
||||
path: '/saas', // ✅ necessário p/ expandir e controlar activePath
|
||||
path: '/saas',
|
||||
items: [
|
||||
{ label: 'Dashboard', icon: 'pi pi-chart-bar', to: '/saas' },
|
||||
|
||||
{
|
||||
label: 'Planos',
|
||||
icon: 'pi pi-star',
|
||||
path: '/saas/plans', // ✅ absoluto (mais confiável p/ active/expand)
|
||||
path: '/saas/plans',
|
||||
items: [
|
||||
{ label: 'Listagem de Planos', icon: 'pi pi-list', to: '/saas/plans' },
|
||||
|
||||
// ✅ vitrine pública (pricing page)
|
||||
{ label: 'Vitrine Pública', icon: 'pi pi-megaphone', to: '/saas/plans-public' },
|
||||
|
||||
{ label: 'Recursos', icon: 'pi pi-bolt', to: '/saas/features' },
|
||||
{ label: 'Controle de Recursos', icon: 'pi pi-th-large', to: '/saas/plan-features' }
|
||||
{ label: 'Planos e Preços', icon: 'pi pi-list', to: '/saas/plans' },
|
||||
{ label: 'Vitrine Pública', icon: 'pi pi-megaphone', to: '/saas/plans-public' },
|
||||
{ label: 'Recursos', icon: 'pi pi-bolt', to: '/saas/features' },
|
||||
{ label: 'Controle de Recursos', icon: 'pi pi-th-large', to: '/saas/plan-features' },
|
||||
{ label: 'Limites por Plano', icon: 'pi pi-sliders-h', to: '/saas/plan-limits' }
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
label: 'Assinaturas',
|
||||
icon: 'pi pi-credit-card',
|
||||
path: '/saas/subscriptions', // ✅ absoluto
|
||||
path: '/saas/subscriptions',
|
||||
items: [
|
||||
{ label: 'Listagem de Assinaturas', icon: 'pi pi-list', to: '/saas/subscriptions' },
|
||||
{ label: 'Histórico', icon: 'pi pi-history', to: '/saas/subscription-events' },
|
||||
{ label: 'Listagem de Assinaturas', icon: 'pi pi-list', to: '/saas/subscriptions' },
|
||||
{ label: 'Intenções', icon: 'pi pi-inbox', to: '/saas/subscription-intents' },
|
||||
{ label: 'Histórico', icon: 'pi pi-history', to: '/saas/subscription-events' },
|
||||
{
|
||||
label: 'Saúde das Assinaturas',
|
||||
icon: 'pi pi-shield',
|
||||
to: '/saas/subscription-health',
|
||||
...(mismatchBadge
|
||||
? mismatchBadge
|
||||
: {})
|
||||
...mismatchBadge
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
label: 'Intenções de Assinatura',
|
||||
icon: 'pi pi-inbox',
|
||||
to: '/saas/subscription-intents'
|
||||
},
|
||||
|
||||
{ label: 'Clínicas (Tenants)', icon: 'pi pi-users', to: '/saas/tenants' }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
export default [
|
||||
{
|
||||
label: 'Home',
|
||||
items: [{ label: 'Dashboard', icon: 'pi pi-fw pi-home', to: '/' }]
|
||||
},
|
||||
{
|
||||
label: 'UI Components',
|
||||
path: '/uikit',
|
||||
@@ -29,7 +25,6 @@ export default [
|
||||
icon: 'pi pi-fw pi-prime',
|
||||
path: '/blocks',
|
||||
items: [
|
||||
{ label: 'Free Blocks', icon: 'pi pi-fw pi-eye', to: '/utilities' },
|
||||
{ label: 'All Blocks', icon: 'pi pi-fw pi-globe', url: 'https://blocks.primevue.org/', target: '_blank' }
|
||||
]
|
||||
},
|
||||
@@ -49,68 +44,15 @@ export default [
|
||||
{ label: 'Access Denied', icon: 'pi pi-fw pi-lock', to: '/auth/access' }
|
||||
]
|
||||
},
|
||||
{ label: 'Crud', icon: 'pi pi-fw pi-pencil', to: '/pages/crud' },
|
||||
{ label: 'Not Found', icon: 'pi pi-fw pi-exclamation-circle', to: '/pages/notfound' },
|
||||
{ label: 'Empty', icon: 'pi pi-fw pi-circle-off', to: '/pages/empty' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Hierarchy',
|
||||
icon: 'pi pi-fw pi-align-left',
|
||||
path: '/hierarchy',
|
||||
items: [
|
||||
{
|
||||
label: 'Submenu 1',
|
||||
icon: 'pi pi-fw pi-align-left',
|
||||
path: '/submenu_1',
|
||||
items: [
|
||||
{
|
||||
label: 'Submenu 1.1',
|
||||
icon: 'pi pi-fw pi-align-left',
|
||||
path: '/submenu_1_1',
|
||||
items: [
|
||||
{ label: 'Submenu 1.1.1', icon: 'pi pi-fw pi-align-left' },
|
||||
{ label: 'Submenu 1.1.2', icon: 'pi pi-fw pi-align-left' },
|
||||
{ label: 'Submenu 1.1.3', icon: 'pi pi-fw pi-align-left' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Submenu 1.2',
|
||||
icon: 'pi pi-fw pi-align-left',
|
||||
path: '/submenu_1_2',
|
||||
items: [{ label: 'Submenu 1.2.1', icon: 'pi pi-fw pi-align-left' }]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Submenu 2',
|
||||
icon: 'pi pi-fw pi-align-left',
|
||||
path: '/submenu_2',
|
||||
items: [
|
||||
{
|
||||
label: 'Submenu 2.1',
|
||||
icon: 'pi pi-fw pi-align-left',
|
||||
path: '/submenu_2_1',
|
||||
items: [
|
||||
{ label: 'Submenu 2.1.1', icon: 'pi pi-fw pi-align-left' },
|
||||
{ label: 'Submenu 2.1.2', icon: 'pi pi-fw pi-align-left' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Submenu 2.2',
|
||||
icon: 'pi pi-fw pi-align-left',
|
||||
path: '/submenu_2_2',
|
||||
items: [{ label: 'Submenu 2.2.1', icon: 'pi pi-fw pi-align-left' }]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Get Started',
|
||||
path: '/start',
|
||||
items: [
|
||||
{ label: 'Documentation', icon: 'pi pi-fw pi-book', to: '/pages' },
|
||||
{ label: 'Documentation', icon: 'pi pi-fw pi-book', url: 'https://sakai.primevue.org/documentation', target: '_blank' },
|
||||
{ label: 'View Source', icon: 'pi pi-fw pi-github', url: 'https://github.com/primefaces/sakai-vue', target: '_blank' }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
// src/navigation/menus/supervisor.menu.js
|
||||
|
||||
export default [
|
||||
{
|
||||
label: 'Supervisão',
|
||||
items: [
|
||||
// ======================================================
|
||||
// 📊 DASHBOARD
|
||||
// ======================================================
|
||||
{ label: 'Dashboard', icon: 'pi pi-fw pi-home', to: '/supervisor' },
|
||||
|
||||
// ======================================================
|
||||
// 🎓 SALA DE SUPERVISÃO
|
||||
// ======================================================
|
||||
{
|
||||
label: 'Sala de Supervisão',
|
||||
icon: 'pi pi-fw pi-users',
|
||||
to: '/supervisor/sala',
|
||||
feature: 'supervisor.access'
|
||||
},
|
||||
|
||||
// ======================================================
|
||||
// 💳 PLANO / CONTA
|
||||
// ======================================================
|
||||
{ label: 'Meu plano', icon: 'pi pi-fw pi-credit-card', to: '/supervisor/meu-plano' },
|
||||
{ label: 'Meu Perfil', icon: 'pi pi-fw pi-user', to: '/account/profile' },
|
||||
{ label: 'Segurança', icon: 'pi pi-fw pi-shield', to: '/account/security' }
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
export default [
|
||||
{
|
||||
label: 'Therapist',
|
||||
label: 'Terapeuta',
|
||||
items: [
|
||||
// ======================================================
|
||||
// 📊 DASHBOARD
|
||||
@@ -12,7 +12,10 @@ export default [
|
||||
// ======================================================
|
||||
// 📅 AGENDA
|
||||
// ======================================================
|
||||
{ label: 'Agenda', icon: 'pi pi-fw pi-calendar', to: '/therapist/agenda' },
|
||||
{ label: 'Agenda', icon: 'pi pi-fw pi-calendar', to: '/therapist/agenda', feature: 'agenda.view', proBadge: true },
|
||||
|
||||
// ✅ NOVO: Compromissos determinísticos (tipos)
|
||||
{ label: 'Compromissos', icon: 'pi pi-fw pi-clock', to: '/therapist/agenda/compromissos', feature: 'agenda.view', proBadge: true },
|
||||
|
||||
// ======================================================
|
||||
// 👥 PATIENTS
|
||||
@@ -34,14 +37,16 @@ export default [
|
||||
label: 'Online Scheduling',
|
||||
icon: 'pi pi-fw pi-globe',
|
||||
to: '/therapist/online-scheduling',
|
||||
feature: 'online_scheduling.manage',
|
||||
feature: 'online_scheduling',
|
||||
proBadge: true
|
||||
},
|
||||
|
||||
// ======================================================
|
||||
// 👤 ACCOUNT
|
||||
// ======================================================
|
||||
{ label: 'Meu Perfil', icon: 'pi pi-fw pi-user', to: '/account/profile' }
|
||||
{ label: 'Meu plano', icon: 'pi pi-fw pi-credit-card', to: '/therapist/meu-plano' },
|
||||
{ label: 'Meu Perfil', icon: 'pi pi-fw pi-user', to: '/account/profile' },
|
||||
{ label: 'Segurança', icon: 'pi pi-fw pi-shield', to: '/account/security' }
|
||||
]
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user