MelissaLayout: sidebar global de configs em qualquer rota de config
Antes cada pagina nativa de config tinha seu proprio chrome 2-col, e quando o usuario navegava entre Perfil/Plano/Negocio/Seguranca/Agenda Config/Bloqueios/Agendador/Pagamento, perdia o contexto do menu. Agora: - Catalogo unico em composables/melissaConfigGrupos.js (MELISSA_CONFIG_ GRUPOS + isMelissaConfigSlug helper) - MelissaConfigSidebar.vue componente standalone com accordion + navegacao via router.push + destaque do item ativo - MelissaLayout renderiza `<MelissaConfigSidebar>` em qualquer slug que esteja em MELISSA_CONFIG_GRUPOS (computed showConfigSidebar) - CSS var --m-config-aside-left no .win11-root: 296px quando sidebar visivel, 6px caso contrario - Todas as 9 paginas nativas (Perfil, Plano, AlterarPlano, Negocio, Seguranca, Bloqueios, AgendaConfig, Agendador, Pagamento) + MelissaConfiguracoes ajustam left do inset usando a var Sidebar tem entrada animada (lift + slide) e usa o pattern do .mcfg- accordion (head com icone primary + label + desc 2-linhas + badge; items com hover/active color-mix primary 12-16%). Proximo passo: limpar o aside redundante interno do MelissaConfiguracoes + ajustar MelissaSeguranca pra considerar o aside no min-width 1000. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1431,7 +1431,7 @@ onBeforeUnmount(() => {
|
|||||||
/* ═══════ Page chrome ═══════ */
|
/* ═══════ Page chrome ═══════ */
|
||||||
.mac-page {
|
.mac-page {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 6px 6px calc(var(--m-dock-h, 76px) + 6px) 6px;
|
inset: 6px 6px calc(var(--m-dock-h, 76px) + 6px) var(--m-config-aside-left, 6px);
|
||||||
z-index: 40;
|
z-index: 40;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -1381,7 +1381,7 @@ const summaryItems = computed(() => [
|
|||||||
/* ═══════ Page chrome ═══════ */
|
/* ═══════ Page chrome ═══════ */
|
||||||
.mag-page {
|
.mag-page {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 6px 6px calc(var(--m-dock-h, 76px) + 6px) 6px;
|
inset: 6px 6px calc(var(--m-dock-h, 76px) + 6px) var(--m-config-aside-left, 6px);
|
||||||
z-index: 40;
|
z-index: 40;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -542,7 +542,7 @@ onMounted(async () => {
|
|||||||
/* ═══════ Page chrome ═══════ */
|
/* ═══════ Page chrome ═══════ */
|
||||||
.map-page {
|
.map-page {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 6px 6px calc(var(--m-dock-h, 76px) + 6px) 6px;
|
inset: 6px 6px calc(var(--m-dock-h, 76px) + 6px) var(--m-config-aside-left, 6px);
|
||||||
z-index: 40;
|
z-index: 40;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -837,7 +837,7 @@ onBeforeUnmount(() => {
|
|||||||
/* ═══════ Page chrome ═══════ */
|
/* ═══════ Page chrome ═══════ */
|
||||||
.mbq-page {
|
.mbq-page {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 6px 6px calc(var(--m-dock-h, 76px) + 6px) 6px;
|
inset: 6px 6px calc(var(--m-dock-h, 76px) + 6px) var(--m-config-aside-left, 6px);
|
||||||
z-index: 40;
|
z-index: 40;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -0,0 +1,323 @@
|
|||||||
|
<script setup>
|
||||||
|
/*
|
||||||
|
* MelissaConfigSidebar — Sidebar de navegacao das configuracoes Melissa.
|
||||||
|
*
|
||||||
|
* Componente standalone que renderiza o accordion de grupos + items
|
||||||
|
* e navega via router.push pra /melissa/<slug>. Usado em:
|
||||||
|
* - MelissaLayout (renderiza globalmente em qualquer rota de config,
|
||||||
|
* ao lado das paginas nativas)
|
||||||
|
* - MelissaConfiguracoes (substitui o aside inline antigo)
|
||||||
|
*
|
||||||
|
* O catalogo vive em ./composables/melissaConfigGrupos.js — fonte unica.
|
||||||
|
*/
|
||||||
|
import { ref, computed, watch } from 'vue';
|
||||||
|
import { useRouter, useRoute } from 'vue-router';
|
||||||
|
import {
|
||||||
|
MELISSA_CONFIG_GRUPOS,
|
||||||
|
findMelissaConfigItem
|
||||||
|
} from './composables/melissaConfigGrupos.js';
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
// Slug ativo lido direto da rota — sempre em sync com o que esta sendo
|
||||||
|
// renderizado no main pelo MelissaLayout
|
||||||
|
const activeSlug = computed(() => String(route.params?.secao || ''));
|
||||||
|
|
||||||
|
// Accordion: comeca com o grupo do item ativo aberto
|
||||||
|
const grupoDoSlug = (slug) => findMelissaConfigItem(slug)?.grupo?.key || null;
|
||||||
|
const openGroups = ref([grupoDoSlug(activeSlug.value)].filter(Boolean));
|
||||||
|
|
||||||
|
// Sincroniza accordion quando troca de pagina
|
||||||
|
watch(activeSlug, (slug) => {
|
||||||
|
const gk = grupoDoSlug(slug);
|
||||||
|
if (gk && !openGroups.value.includes(gk)) {
|
||||||
|
openGroups.value = [...openGroups.value, gk];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function selecionar(item) {
|
||||||
|
if (route.params?.secao === item.key) return; // ja esta nesta pagina
|
||||||
|
router.push({ name: 'Melissa', params: { secao: item.key } });
|
||||||
|
}
|
||||||
|
|
||||||
|
function isActive(itemKey) {
|
||||||
|
return activeSlug.value === itemKey;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<aside class="mcs-aside">
|
||||||
|
<div class="mcs-aside__head">
|
||||||
|
<i class="pi pi-cog mcs-aside__icon" />
|
||||||
|
<div class="mcs-aside__title-text">
|
||||||
|
<div class="mcs-aside__title">Configurações</div>
|
||||||
|
<div class="mcs-aside__sub">Layout, conta, agenda e muito mais</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mcs-aside__body">
|
||||||
|
<Accordion v-model:value="openGroups" multiple class="mcs-accordion">
|
||||||
|
<AccordionPanel
|
||||||
|
v-for="g in MELISSA_CONFIG_GRUPOS"
|
||||||
|
:key="g.key"
|
||||||
|
:value="g.key"
|
||||||
|
>
|
||||||
|
<AccordionHeader>
|
||||||
|
<div class="mcs-grp-head">
|
||||||
|
<i :class="g.icon" class="mcs-grp-icon" />
|
||||||
|
<div class="mcs-grp-text">
|
||||||
|
<span class="mcs-grp-label">{{ g.label }}</span>
|
||||||
|
<span class="mcs-grp-desc">{{ g.desc }}</span>
|
||||||
|
</div>
|
||||||
|
<span class="mcs-grp-badge">{{ g.items.length }}</span>
|
||||||
|
</div>
|
||||||
|
</AccordionHeader>
|
||||||
|
<AccordionContent>
|
||||||
|
<div class="mcs-nav-list">
|
||||||
|
<button
|
||||||
|
v-for="s in g.items"
|
||||||
|
:key="s.key"
|
||||||
|
type="button"
|
||||||
|
class="mcs-nav-item"
|
||||||
|
:class="{ 'is-active': isActive(s.key) }"
|
||||||
|
@click="selecionar(s)"
|
||||||
|
>
|
||||||
|
<i :class="s.icon" class="mcs-nav-item__icon" />
|
||||||
|
<div class="mcs-nav-item__text">
|
||||||
|
<span class="mcs-nav-item__label">{{ s.label }}</span>
|
||||||
|
<span class="mcs-nav-item__desc">{{ s.desc }}</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionPanel>
|
||||||
|
</Accordion>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.mcs-aside {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: var(--m-bg-medium);
|
||||||
|
border: 1px solid var(--m-border);
|
||||||
|
border-radius: 18px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.4);
|
||||||
|
backdrop-filter: blur(32px) saturate(160%);
|
||||||
|
-webkit-backdrop-filter: blur(32px) saturate(160%);
|
||||||
|
color: var(--m-text);
|
||||||
|
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mcs-aside__head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
border-bottom: 1px solid var(--m-border);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.mcs-aside__icon {
|
||||||
|
color: var(--p-primary-color);
|
||||||
|
font-size: 1.1rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.mcs-aside__title-text {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.mcs-aside__title {
|
||||||
|
font-size: 0.92rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--m-text);
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
.mcs-aside__sub {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: var(--m-text-muted);
|
||||||
|
margin-top: 2px;
|
||||||
|
line-height: 1.3;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mcs-aside__body {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
padding: 8px;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: var(--m-border-strong) transparent;
|
||||||
|
}
|
||||||
|
.mcs-aside__body::-webkit-scrollbar { width: 5px; }
|
||||||
|
.mcs-aside__body::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--m-border-strong);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Accordion (estilo Melissa) */
|
||||||
|
.mcs-accordion :deep(.p-accordion) {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.mcs-accordion :deep(.p-accordionpanel) {
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
.mcs-accordion :deep(.p-accordionheader) {
|
||||||
|
padding: 6px 8px;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: var(--m-text);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
.mcs-accordion :deep(.p-accordionheader:hover) {
|
||||||
|
background: var(--m-bg-soft-hover);
|
||||||
|
}
|
||||||
|
.mcs-accordion :deep(.p-accordionheader-toggle-icon) {
|
||||||
|
color: var(--m-text-muted);
|
||||||
|
font-size: 0.72rem;
|
||||||
|
}
|
||||||
|
.mcs-accordion :deep(.p-accordioncontent),
|
||||||
|
.mcs-accordion :deep(.p-accordioncontent-content) {
|
||||||
|
padding: 0;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
.mcs-accordion :deep(.p-accordioncontent-content) {
|
||||||
|
padding: 2px 0 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Group head */
|
||||||
|
.mcs-grp-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.mcs-grp-icon {
|
||||||
|
width: 18px;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--p-primary-color);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.mcs-grp-text {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.mcs-grp-label {
|
||||||
|
font-size: 0.86rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--m-text);
|
||||||
|
line-height: 1.25;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.mcs-grp-desc {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: var(--m-text-muted);
|
||||||
|
line-height: 1.3;
|
||||||
|
overflow: hidden;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
}
|
||||||
|
.mcs-grp-badge {
|
||||||
|
flex-shrink: 0;
|
||||||
|
min-width: 20px;
|
||||||
|
height: 18px;
|
||||||
|
padding: 0 6px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: var(--m-bg-soft);
|
||||||
|
border: 1px solid var(--m-border);
|
||||||
|
color: var(--m-text-muted);
|
||||||
|
font-size: 0.66rem;
|
||||||
|
font-weight: 700;
|
||||||
|
border-radius: 999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Items */
|
||||||
|
.mcs-nav-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
padding-left: 24px;
|
||||||
|
padding-right: 4px;
|
||||||
|
}
|
||||||
|
.mcs-nav-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: var(--m-text);
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: left;
|
||||||
|
font-family: inherit;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 44px;
|
||||||
|
}
|
||||||
|
.mcs-nav-item:hover {
|
||||||
|
background: color-mix(in srgb, var(--p-primary-color) 12%, transparent);
|
||||||
|
color: var(--p-primary-color);
|
||||||
|
}
|
||||||
|
.mcs-nav-item:hover .mcs-nav-item__icon { color: var(--p-primary-color); }
|
||||||
|
.mcs-nav-item.is-active {
|
||||||
|
background: color-mix(in srgb, var(--p-primary-color) 16%, transparent);
|
||||||
|
color: var(--p-primary-color);
|
||||||
|
}
|
||||||
|
.mcs-nav-item.is-active .mcs-nav-item__icon { color: var(--p-primary-color); }
|
||||||
|
.mcs-nav-item__icon {
|
||||||
|
width: 16px;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--m-text-muted);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.mcs-nav-item__text {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1px;
|
||||||
|
}
|
||||||
|
.mcs-nav-item__label {
|
||||||
|
font-size: 0.84rem;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.2;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.mcs-nav-item__desc {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: var(--m-text-muted);
|
||||||
|
line-height: 1.3;
|
||||||
|
overflow: hidden;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
}
|
||||||
|
.mcs-nav-item:hover .mcs-nav-item__desc,
|
||||||
|
.mcs-nav-item.is-active .mcs-nav-item__desc {
|
||||||
|
color: color-mix(in srgb, var(--p-primary-color) 70%, var(--m-text-muted));
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -593,7 +593,7 @@ function resetCores() {
|
|||||||
/* ═════ Container ═════ */
|
/* ═════ Container ═════ */
|
||||||
.mcfg-page {
|
.mcfg-page {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 6px 6px calc(var(--m-dock-h, 76px) + 6px) 6px;
|
inset: 6px 6px calc(var(--m-dock-h, 76px) + 6px) var(--m-config-aside-left, 6px);
|
||||||
z-index: 40;
|
z-index: 40;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ import MelissaBloqueios from './MelissaBloqueios.vue';
|
|||||||
import MelissaAgendador from './MelissaAgendador.vue';
|
import MelissaAgendador from './MelissaAgendador.vue';
|
||||||
import MelissaAgendaConfig from './MelissaAgendaConfig.vue';
|
import MelissaAgendaConfig from './MelissaAgendaConfig.vue';
|
||||||
import MelissaPagamento from './MelissaPagamento.vue';
|
import MelissaPagamento from './MelissaPagamento.vue';
|
||||||
|
import MelissaConfigSidebar from './MelissaConfigSidebar.vue';
|
||||||
|
import { isMelissaConfigSlug } from './composables/melissaConfigGrupos.js';
|
||||||
import MelissaEmbed from './MelissaEmbed.vue';
|
import MelissaEmbed from './MelissaEmbed.vue';
|
||||||
import MelissaCadastrosRecebidos from './MelissaCadastrosRecebidos.vue';
|
import MelissaCadastrosRecebidos from './MelissaCadastrosRecebidos.vue';
|
||||||
import MelissaAgendamentosRecebidos from './MelissaAgendamentosRecebidos.vue';
|
import MelissaAgendamentosRecebidos from './MelissaAgendamentosRecebidos.vue';
|
||||||
@@ -248,6 +250,17 @@ const secaoAberta = computed(() => {
|
|||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Mostra o sidebar lateral de configuracoes (MelissaConfigSidebar)
|
||||||
|
// quando a rota atual e um slug do catalogo de configs (any item em
|
||||||
|
// MELISSA_CONFIG_GRUPOS).
|
||||||
|
const showConfigSidebar = computed(() => isMelissaConfigSlug(secaoAberta.value));
|
||||||
|
|
||||||
|
// CSS var pras paginas nativas saberem que precisam abrir espaco a
|
||||||
|
// esquerda pro sidebar (296px = 280 width + 16 gap).
|
||||||
|
const configAsideLeftStyle = computed(() => ({
|
||||||
|
'--m-config-aside-left': showConfigSidebar.value ? '296px' : '6px'
|
||||||
|
}));
|
||||||
|
|
||||||
// Quando o usuário fecha a seção e volta pro resumo, garante que os
|
// Quando o usuário fecha a seção e volta pro resumo, garante que os
|
||||||
// dados estão prontos (caso o idle callback ainda não tenha disparado
|
// dados estão prontos (caso o idle callback ainda não tenha disparado
|
||||||
// no fluxo deep-link). fetchCached é idempotente: cache hit → instant,
|
// no fluxo deep-link). fetchCached é idempotente: cache hit → instant,
|
||||||
@@ -1388,7 +1401,7 @@ function onKeydown(e) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="win11-root" :class="{ 'win11-has-photo': !!bgUrl }" :style="defaultBgStyle">
|
<div class="win11-root" :class="{ 'win11-has-photo': !!bgUrl }" :style="[defaultBgStyle, configAsideLeftStyle]">
|
||||||
<!-- Camada da foto custom (acima do gradiente, abaixo do dim).
|
<!-- Camada da foto custom (acima do gradiente, abaixo do dim).
|
||||||
Opacidade controlada pelo slider — permite blend com o gradiente
|
Opacidade controlada pelo slider — permite blend com o gradiente
|
||||||
default que vive no .win11-root. -->
|
default que vive no .win11-root. -->
|
||||||
@@ -2228,6 +2241,19 @@ function onKeydown(e) {
|
|||||||
@close="fecharSecao"
|
@close="fecharSecao"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- ═══════════════════════════════════════════════════════ -->
|
||||||
|
<!-- SIDEBAR DE CONFIGURACOES — fixo a esquerda em qualquer
|
||||||
|
rota de config (perfil, plano, agenda-config, pagamento, etc) -->
|
||||||
|
<!-- ═══════════════════════════════════════════════════════ -->
|
||||||
|
<Transition name="lift">
|
||||||
|
<div
|
||||||
|
v-if="layoutReady && showConfigSidebar"
|
||||||
|
class="melissa-config-aside-host"
|
||||||
|
>
|
||||||
|
<MelissaConfigSidebar />
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
|
||||||
<MelissaPerfil
|
<MelissaPerfil
|
||||||
v-if="layoutReady && secaoAberta === 'perfil'"
|
v-if="layoutReady && secaoAberta === 'perfil'"
|
||||||
@close="fecharSecao"
|
@close="fecharSecao"
|
||||||
@@ -2444,6 +2470,26 @@ function onKeydown(e) {
|
|||||||
transition: background-color 200ms ease;
|
transition: background-color 200ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ─── Sidebar global de configuracoes ──────────────────────────
|
||||||
|
Renderizado em qualquer rota de config (qualquer slug em
|
||||||
|
MELISSA_CONFIG_GRUPOS). Fica na lateral esquerda; as paginas
|
||||||
|
nativas usam var(--m-config-aside-left) no left do inset pra
|
||||||
|
abrir espaco automatico. */
|
||||||
|
.melissa-config-aside-host {
|
||||||
|
position: absolute;
|
||||||
|
top: 6px;
|
||||||
|
bottom: calc(var(--m-dock-h, 76px) + 6px);
|
||||||
|
left: 6px;
|
||||||
|
width: 280px;
|
||||||
|
z-index: 41;
|
||||||
|
pointer-events: auto;
|
||||||
|
animation: mcs-aside-enter 240ms cubic-bezier(0.2, 0.7, 0.3, 1);
|
||||||
|
}
|
||||||
|
@keyframes mcs-aside-enter {
|
||||||
|
from { opacity: 0; transform: translateX(-8px); }
|
||||||
|
to { opacity: 1; transform: translateX(0); }
|
||||||
|
}
|
||||||
|
|
||||||
/* ─── Plano de trás (resumo) ───────────────────────────────── */
|
/* ─── Plano de trás (resumo) ───────────────────────────────── */
|
||||||
.win11-summary {
|
.win11-summary {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
@@ -913,7 +913,7 @@ onBeforeUnmount(() => {
|
|||||||
/* ═══════ Page chrome (mesmo pattern do MelissaPerfil) ═══════ */
|
/* ═══════ Page chrome (mesmo pattern do MelissaPerfil) ═══════ */
|
||||||
.mng-page {
|
.mng-page {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 6px 6px calc(var(--m-dock-h, 76px) + 6px) 6px;
|
inset: 6px 6px calc(var(--m-dock-h, 76px) + 6px) var(--m-config-aside-left, 6px);
|
||||||
z-index: 40;
|
z-index: 40;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -669,7 +669,7 @@ onBeforeUnmount(() => {
|
|||||||
/* ═══════ Page chrome ═══════ */
|
/* ═══════ Page chrome ═══════ */
|
||||||
.mpg-page {
|
.mpg-page {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 6px 6px calc(var(--m-dock-h, 76px) + 6px) 6px;
|
inset: 6px 6px calc(var(--m-dock-h, 76px) + 6px) var(--m-config-aside-left, 6px);
|
||||||
z-index: 40;
|
z-index: 40;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -917,7 +917,7 @@ onBeforeUnmount(() => {
|
|||||||
/* ═══════ Page chrome ═══════ */
|
/* ═══════ Page chrome ═══════ */
|
||||||
.mpr-page {
|
.mpr-page {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 6px 6px calc(var(--m-dock-h, 76px) + 6px) 6px;
|
inset: 6px 6px calc(var(--m-dock-h, 76px) + 6px) var(--m-config-aside-left, 6px);
|
||||||
z-index: 40;
|
z-index: 40;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -674,7 +674,7 @@ onMounted(async () => {
|
|||||||
/* ═══════ Page chrome (mesmo pattern do MelissaPerfil) ═══════ */
|
/* ═══════ Page chrome (mesmo pattern do MelissaPerfil) ═══════ */
|
||||||
.mpl-page {
|
.mpl-page {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 6px 6px calc(var(--m-dock-h, 76px) + 6px) 6px;
|
inset: 6px 6px calc(var(--m-dock-h, 76px) + 6px) var(--m-config-aside-left, 6px);
|
||||||
z-index: 40;
|
z-index: 40;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -433,7 +433,7 @@ onMounted(async () => {
|
|||||||
/* ═══════ Page chrome ═══════ */
|
/* ═══════ Page chrome ═══════ */
|
||||||
.mse-page {
|
.mse-page {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 6px 6px calc(var(--m-dock-h, 76px) + 6px) 6px;
|
inset: 6px 6px calc(var(--m-dock-h, 76px) + 6px) var(--m-config-aside-left, 6px);
|
||||||
z-index: 40;
|
z-index: 40;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
/*
|
||||||
|
* melissaConfigGrupos — fonte unica do catalogo de configs Melissa.
|
||||||
|
*
|
||||||
|
* Usado por:
|
||||||
|
* - MelissaConfigSidebar (renderiza o accordion + items)
|
||||||
|
* - MelissaLayout (decide quando mostrar o sidebar de config)
|
||||||
|
* - MelissaConfiguracoes (re-usa o mesmo catalogo via componente)
|
||||||
|
*
|
||||||
|
* Cada item tem `key` que e o slug usado em /melissa/<slug>. Se a key
|
||||||
|
* comeca com 'cfg-', a pagina e embed (renderizada pelo
|
||||||
|
* MelissaConfiguracoes via COMPONENT_MAP). Se e slug "limpo" (perfil,
|
||||||
|
* plano, agenda-config, pagamento, etc), e pagina nativa Melissa.
|
||||||
|
*/
|
||||||
|
export const MELISSA_CONFIG_GRUPOS = [
|
||||||
|
{
|
||||||
|
key: 'layout-melissa',
|
||||||
|
label: 'Layout Melissa',
|
||||||
|
desc: 'Aparência, plano de fundo, relógio e cronômetro do resumo.',
|
||||||
|
icon: 'pi pi-palette',
|
||||||
|
items: [
|
||||||
|
{ key: 'aparencia', label: 'Layout Melissa', desc: 'Tema, cor primária, surface, plano de fundo, relógio e cronômetro — tudo numa tela só.', icon: 'pi pi-palette' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'conta',
|
||||||
|
label: 'Conta',
|
||||||
|
desc: 'Perfil, plano, negócio e segurança.',
|
||||||
|
icon: 'pi pi-user',
|
||||||
|
items: [
|
||||||
|
{ key: 'perfil', label: 'Meu Perfil', desc: 'Identidade, contato, bio, redes — gamificação no aside.', icon: 'pi pi-user' },
|
||||||
|
{ key: 'plano', label: 'Meu Plano', desc: 'Assinatura, recursos liberados e histórico de mudanças.', icon: 'pi pi-credit-card' },
|
||||||
|
{ key: 'negocio', label: 'Meu Negócio', desc: 'Identidade, fiscal, endereço, contato, redes.', icon: 'pi pi-briefcase' },
|
||||||
|
{ key: 'seguranca', label: 'Segurança', desc: 'Trocar senha + boas práticas + estado da sessão.', icon: 'pi pi-shield' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'agenda',
|
||||||
|
label: 'Agenda',
|
||||||
|
desc: 'Horários, bloqueios e agendador público.',
|
||||||
|
icon: 'pi pi-calendar',
|
||||||
|
items: [
|
||||||
|
{ key: 'agenda-config', label: 'Configurações da Agenda', desc: 'Jornada (dias e horários), ritmo das sessões e agendamento online.', icon: 'pi pi-calendar' },
|
||||||
|
{ key: 'bloqueios', label: 'Bloqueios e Feriados', desc: 'Feriados nacionais (auto), municipais e bloqueios manuais.', icon: 'pi pi-ban' },
|
||||||
|
{ key: 'online-scheduling', label: 'Agendador Online', desc: 'Link público, identidade visual, fluxo, pagamento e textos.', icon: 'pi pi-calendar-clock' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'financeiro',
|
||||||
|
label: 'Financeiro',
|
||||||
|
desc: 'Formas de pagamento, valores, descontos e convênios.',
|
||||||
|
icon: 'pi pi-wallet',
|
||||||
|
items: [
|
||||||
|
{ key: 'pagamento', label: 'Formas de Pagamento', desc: 'Pix, depósito, dinheiro, cartão e convênio.', icon: 'pi pi-wallet' },
|
||||||
|
{ key: 'cfg-precificacao', label: 'Precificação', desc: 'Valor padrão da sessão e preços por tipo de compromisso.', icon: 'pi pi-tag' },
|
||||||
|
{ key: 'cfg-descontos', label: 'Descontos por Paciente', desc: 'Descontos recorrentes aplicados automaticamente.', icon: 'pi pi-percentage' },
|
||||||
|
{ key: 'cfg-excecoes', label: 'Exceções Financeiras', desc: 'O que cobrar em faltas, cancelamentos e situações excepcionais.', icon: 'pi pi-exclamation-triangle' },
|
||||||
|
{ key: 'cfg-convenios', label: 'Convênios', desc: 'Cadastre os convênios que você atende e seus valores.', icon: 'pi pi-id-card' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'whatsapp',
|
||||||
|
label: 'WhatsApp & Conversas',
|
||||||
|
desc: 'Canal, tags, auto-reply, lembretes e créditos.',
|
||||||
|
icon: 'pi pi-whatsapp',
|
||||||
|
items: [
|
||||||
|
{ key: 'cfg-wa', label: 'Canal WhatsApp', desc: 'Escolha o canal (oficial AgenciaPSI ou pessoal) e configure a integração.', icon: 'pi pi-whatsapp' },
|
||||||
|
{ key: 'cfg-wa-templates', label: 'Templates WhatsApp', desc: 'Personalize os textos enviados ou volte ao padrão da plataforma.', icon: 'pi pi-file-edit' },
|
||||||
|
{ key: 'cfg-conversas-tags', label: 'Tags de Conversa', desc: 'Etiquetas custom pra classificar threads no CRM (urgente, remarcação…).', icon: 'pi pi-tag' },
|
||||||
|
{ key: 'cfg-conversas-autoreply', label: 'Auto-reply WhatsApp', desc: 'Resposta automática quando paciente escreve fora do horário.', icon: 'pi pi-reply' },
|
||||||
|
{ key: 'cfg-conversas-optouts', label: 'Opt-outs (LGPD)', desc: 'Números que pediram pra não receber mensagens. Direito de oposição LGPD.', icon: 'pi pi-ban' },
|
||||||
|
{ key: 'cfg-conversas-sla', label: 'SLA de resposta', desc: 'Tempo máximo pra responder. Alerta quando estourar.', icon: 'pi pi-stopwatch' },
|
||||||
|
{ key: 'cfg-conversas-bots', label: 'Bot de triagem', desc: 'Coleta nome e motivo via WhatsApp antes do fluxo humano.', icon: 'pi pi-android' },
|
||||||
|
{ key: 'cfg-lembretes', label: 'Lembretes de Sessão', desc: 'WhatsApp automático 24h e 2h antes das sessões agendadas.', icon: 'pi pi-bell' },
|
||||||
|
{ key: 'cfg-creditos-wa', label: 'Créditos WhatsApp', desc: 'Compre pacotes de mensagens, veja saldo e extrato.', icon: 'pi pi-credit-card' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'comunicacao',
|
||||||
|
label: 'Comunicação',
|
||||||
|
desc: 'SMS e templates de e-mail enviados aos pacientes.',
|
||||||
|
icon: 'pi pi-send',
|
||||||
|
items: [
|
||||||
|
{ key: 'cfg-sms', label: 'SMS', desc: 'Gerencie créditos SMS e personalize as mensagens enviadas.', icon: 'pi pi-comment' },
|
||||||
|
{ key: 'cfg-email-templates', label: 'Templates de E-mail', desc: 'Personalize os e-mails enviados aos pacientes.', icon: 'pi pi-envelope' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'plataforma',
|
||||||
|
label: 'Empresa & Plataforma',
|
||||||
|
desc: 'Dados da empresa, recursos extras e auditoria.',
|
||||||
|
icon: 'pi pi-building',
|
||||||
|
items: [
|
||||||
|
{ key: 'cfg-empresa', label: 'Minha Empresa', desc: 'CNPJ, endereço, logomarca e redes sociais.', icon: 'pi pi-building' },
|
||||||
|
{ key: 'cfg-recursos-extras', label: 'Recursos Extras', desc: 'Amplíe as funcionalidades com recursos adicionais.', icon: 'pi pi-box' },
|
||||||
|
{ key: 'cfg-auditoria', label: 'Auditoria', desc: 'Registro imutável de operações (LGPD Art. 37).', icon: 'pi pi-shield' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// Set de slugs que sao "config routes" — mostram o sidebar de config
|
||||||
|
export const MELISSA_CONFIG_SLUGS = new Set(
|
||||||
|
MELISSA_CONFIG_GRUPOS.flatMap((g) => g.items.map((i) => i.key))
|
||||||
|
);
|
||||||
|
|
||||||
|
export function isMelissaConfigSlug(slug) {
|
||||||
|
return MELISSA_CONFIG_SLUGS.has(slug);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Acha o item por slug (pra destacar ativo)
|
||||||
|
export function findMelissaConfigItem(slug) {
|
||||||
|
for (const g of MELISSA_CONFIG_GRUPOS) {
|
||||||
|
const item = g.items.find((i) => i.key === slug);
|
||||||
|
if (item) return { grupo: g, item };
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user