Correcao Sidebar Classico e Rail, Correcao Layout, Ajuste de Breakpoint para Tailwind, Ajuste AppTopbar, Ajuste Menu PopOver, Recriado Paleta de Cores, Inserido algumas animações leves, Reajuste Cor items NOVOS da tabela, Drawer Ajuda Corrigido no Logout, Whatsapp, sms, email, recursos extras

This commit is contained in:
Leonardo
2026-03-24 21:26:58 -03:00
parent a89d1f5560
commit 53a4980396
453 changed files with 121427 additions and 174407 deletions
+161 -152
View File
@@ -14,44 +14,47 @@
| © 2026 — Todos os direitos reservados
|--------------------------------------------------------------------------
*/
import { computed, reactive, ref, onMounted, onBeforeUnmount } from 'vue'
import { computed, reactive, ref, onMounted, onBeforeUnmount } from 'vue';
// Espelha --breakpoint-xl de src/assets/tailwind.css
const BREAKPOINT_XL = 1280;
// ── resolve variant salvo no localStorage ───────────────────
function _loadVariant () {
try {
const v = localStorage.getItem('layout_variant')
if (v === 'rail' || v === 'classic') return v
} catch {}
return 'rail'
function _loadVariant() {
try {
const v = localStorage.getItem('layout_variant');
if (v === 'rail' || v === 'classic') return v;
} catch {}
return 'rail';
}
const layoutConfig = reactive({
preset: 'Aura',
primary: 'emerald',
surface: null,
darkTheme: false,
menuMode: 'static',
variant: _loadVariant() // 'classic' | 'rail'
})
preset: 'Aura',
primary: 'emerald',
surface: null,
darkTheme: false,
menuMode: 'static',
variant: _loadVariant() // 'classic' | 'rail'
});
const layoutState = reactive({
staticMenuInactive: false,
overlayMenuActive: false,
mobileMenuActive: false,
profileSidebarVisible: false,
configSidebarVisible: false,
sidebarExpanded: false,
menuHoverActive: false,
anchored: false,
activeMenuItem: null,
activePath: null,
// ── Layout 2 (rail) ─────────────────────────────────────
railSectionKey: null, // qual seção está ativa no rail
railPanelOpen: false, // painel lateral expandido
// ── variant dirty: true quando o usuário mudou o variant
// mas ainda não salvou — impede que loadUserSettings reverta
_variantDirty: false
})
staticMenuInactive: false,
overlayMenuActive: false,
mobileMenuActive: false,
profileSidebarVisible: false,
configSidebarVisible: false,
sidebarExpanded: false,
menuHoverActive: false,
anchored: false,
activeMenuItem: null,
activePath: null,
// ── Layout 2 (rail) ─────────────────────────────────────
railSectionKey: null, // qual seção está ativa no rail
railPanelOpen: false, // painel lateral expandido
// ── variant dirty: true quando o usuário mudou o variant
// mas ainda não salvou — impede que loadUserSettings reverta
_variantDirty: false
});
/**
* ✅ Fonte da verdade do dark:
@@ -62,150 +65,156 @@ const layoutState = reactive({
* usa o composable em páginas/Topbar/Configurator. Se não sincronizar,
* isDarkTheme pode ficar "mentindo".
*/
let _syncedDarkFromDomOnce = false
function syncDarkFromDomOnce () {
if (_syncedDarkFromDomOnce) return
_syncedDarkFromDomOnce = true
try {
layoutConfig.darkTheme = document.documentElement.classList.contains('app-dark')
} catch {}
let _syncedDarkFromDomOnce = false;
function syncDarkFromDomOnce() {
if (_syncedDarkFromDomOnce) return;
_syncedDarkFromDomOnce = true;
try {
layoutConfig.darkTheme = document.documentElement.classList.contains('app-dark');
} catch {}
}
// ── reactive mobile state (atualiza no resize) ───────────────
const _isMobileRef = ref(typeof window !== 'undefined' ? window.innerWidth <= 1200 : false)
const _isMobileRef = ref(typeof window !== 'undefined' ? window.innerWidth <= BREAKPOINT_XL : false);
if (typeof window !== 'undefined') {
const _onResize = () => { _isMobileRef.value = window.innerWidth <= 1200 }
window.addEventListener('resize', _onResize, { passive: true })
const _onResize = () => {
_isMobileRef.value = window.innerWidth <= BREAKPOINT_XL;
};
window.addEventListener('resize', _onResize, { passive: true });
}
export function useLayout () {
// ✅ garante coerência sempre que alguém usar useLayout()
syncDarkFromDomOnce()
export function useLayout() {
// ✅ garante coerência sempre que alguém usar useLayout()
syncDarkFromDomOnce();
const executeDarkModeToggle = () => {
layoutConfig.darkTheme = !layoutConfig.darkTheme
const executeDarkModeToggle = () => {
layoutConfig.darkTheme = !layoutConfig.darkTheme;
// ✅ garante consistência (não depende do estado anterior do DOM)
document.documentElement.classList.toggle('app-dark', layoutConfig.darkTheme)
}
// ✅ garante consistência (não depende do estado anterior do DOM)
document.documentElement.classList.toggle('app-dark', layoutConfig.darkTheme);
};
const toggleDarkMode = () => {
if (!document.startViewTransition) {
executeDarkModeToggle()
return
}
const toggleDarkMode = () => {
if (!document.startViewTransition) {
executeDarkModeToggle();
return;
}
// ✅ não usa "event" (undefined) e mantém transição suave quando suportado
document.startViewTransition(() => executeDarkModeToggle())
}
// ✅ não usa "event" (undefined) e mantém transição suave quando suportado
document.startViewTransition(() => executeDarkModeToggle());
};
const isDesktop = () => window.innerWidth > 991
const isDesktop = () => window.innerWidth > BREAKPOINT_XL;
// breakpoint do botão hamburguer no Rail (≤ 1200px)
const isRailMobile = () => window.innerWidth <= 1200
// breakpoint do botão hamburguer no Rail (≤ xl)
const isRailMobile = () => window.innerWidth <= BREAKPOINT_XL;
const toggleMenu = () => {
// No Rail, em desktop, o botão hamburguer controla a sidebar mobile do rail
if (layoutConfig.variant === 'rail' && !_isMobileRef.value) {
layoutState.mobileMenuActive = !layoutState.mobileMenuActive
return
}
const toggleMenu = () => {
// No Rail, em desktop, o botão hamburguer controla a sidebar mobile do rail
if (layoutConfig.variant === 'rail' && !_isMobileRef.value) {
layoutState.mobileMenuActive = !layoutState.mobileMenuActive;
return;
}
// Layout clássico (ou mobile com qualquer variant) — comportamento original
if (isDesktop()) {
if (layoutConfig.menuMode === 'static') {
layoutState.staticMenuInactive = !layoutState.staticMenuInactive
}
if (layoutConfig.menuMode === 'overlay') {
layoutState.overlayMenuActive = !layoutState.overlayMenuActive
}
} else {
layoutState.mobileMenuActive = !layoutState.mobileMenuActive
}
}
// Layout clássico — usa effectiveMenuMode para decidir o estado correto
if (isDesktop()) {
if (effectiveMenuMode.value === 'static') {
layoutState.staticMenuInactive = !layoutState.staticMenuInactive;
}
if (effectiveMenuMode.value === 'overlay') {
layoutState.overlayMenuActive = !layoutState.overlayMenuActive;
}
} else {
// ≤ xl: effectiveMenuMode sempre 'overlay' — usa overlayMenuActive para
// parear corretamente com a classe layout-overlay do containerClass
layoutState.overlayMenuActive = !layoutState.overlayMenuActive;
}
};
const toggleConfigSidebar = () => {
layoutState.configSidebarVisible = !layoutState.configSidebarVisible
}
const toggleConfigSidebar = () => {
layoutState.configSidebarVisible = !layoutState.configSidebarVisible;
};
const hideMobileMenu = () => {
layoutState.mobileMenuActive = false
layoutState.overlayMenuActive = false
layoutState.menuHoverActive = false
}
const hideMobileMenu = () => {
layoutState.mobileMenuActive = false;
layoutState.overlayMenuActive = false;
layoutState.menuHoverActive = false;
};
// ✅ use isso ao navegar: mantém menu aberto no desktop, fecha só no mobile
const closeMenuOnNavigate = () => {
if (!isDesktop()) {
layoutState.mobileMenuActive = false
layoutState.overlayMenuActive = false
layoutState.menuHoverActive = false
}
}
// ✅ use isso ao navegar: mantém menu aberto no desktop, fecha só no mobile
const closeMenuOnNavigate = () => {
if (!isDesktop()) {
layoutState.mobileMenuActive = false;
layoutState.overlayMenuActive = false;
layoutState.menuHoverActive = false;
}
};
/**
* ✅ aceita:
* - changeMenuMode({ value: 'static' })
* - changeMenuMode('static')
*
* Motivo: você chama isso de lugares diferentes (Topbar, Configurator, Profile).
*/
const changeMenuMode = (eventOrValue) => {
const nextMode = typeof eventOrValue === 'string'
? eventOrValue
: eventOrValue?.value
/**
* ✅ aceita:
* - changeMenuMode({ value: 'static' })
* - changeMenuMode('static')
*
* Motivo: você chama isso de lugares diferentes (Topbar, Configurator, Profile).
*/
const changeMenuMode = (eventOrValue) => {
const nextMode = typeof eventOrValue === 'string' ? eventOrValue : eventOrValue?.value;
// ✅ não deixa setar undefined / vazio
if (!nextMode) return
// ✅ não deixa setar undefined / vazio
if (!nextMode) return;
layoutConfig.menuMode = nextMode
layoutConfig.menuMode = nextMode;
// ✅ reset consistente (evita drift quando alterna overlay/static)
layoutState.staticMenuInactive = false
layoutState.overlayMenuActive = false
layoutState.mobileMenuActive = false
layoutState.sidebarExpanded = false
layoutState.menuHoverActive = false
layoutState.anchored = false
}
// ✅ reset consistente (evita drift quando alterna overlay/static)
layoutState.staticMenuInactive = false;
layoutState.overlayMenuActive = false;
layoutState.mobileMenuActive = false;
layoutState.sidebarExpanded = false;
layoutState.menuHoverActive = false;
layoutState.anchored = false;
};
const setVariant = (v, { fromUser = true } = {}) => {
if (v !== 'classic' && v !== 'rail') return
layoutConfig.variant = v
try { localStorage.setItem('layout_variant', v) } catch {}
// reset rail state ao trocar
layoutState.railSectionKey = null
layoutState.railPanelOpen = false
// marca que o usuário fez uma escolha explícita (não restauração do DB)
if (fromUser) layoutState._variantDirty = true
}
const setVariant = (v, { fromUser = true } = {}) => {
if (v !== 'classic' && v !== 'rail') return;
layoutConfig.variant = v;
try {
localStorage.setItem('layout_variant', v);
} catch {}
// reset rail state ao trocar
layoutState.railSectionKey = null;
layoutState.railPanelOpen = false;
// marca que o usuário fez uma escolha explícita (não restauração do DB)
if (fromUser) layoutState._variantDirty = true;
};
const isDarkTheme = computed(() => layoutConfig.darkTheme)
const hasOpenOverlay = computed(() => layoutState.overlayMenuActive)
const isDarkTheme = computed(() => layoutConfig.darkTheme);
const hasOpenOverlay = computed(() => layoutState.overlayMenuActive);
// ── Em mobile (≤ 1200px) sempre usa o layout clássico, ───────
// independente de layoutConfig.variant
const isMobile = computed(() => _isMobileRef.value)
const effectiveVariant = computed(() =>
_isMobileRef.value ? 'classic' : layoutConfig.variant
)
// ── Em mobile (≤ xl / 1280px) sempre usa o layout clássico, ───────
// independente de layoutConfig.variant
const isMobile = computed(() => _isMobileRef.value);
const effectiveVariant = computed(() => (_isMobileRef.value ? 'classic' : layoutConfig.variant));
return {
layoutConfig,
layoutState,
isDarkTheme,
toggleDarkMode,
toggleConfigSidebar,
toggleMenu,
hideMobileMenu,
closeMenuOnNavigate,
changeMenuMode,
setVariant,
isDesktop,
isRailMobile,
isMobile,
effectiveVariant,
hasOpenOverlay
}
}
// ── Em mobile (≤ xl) força overlay, sem alterar o valor salvo ───────
const effectiveMenuMode = computed(() => (_isMobileRef.value ? 'overlay' : layoutConfig.menuMode));
return {
layoutConfig,
layoutState,
isDarkTheme,
toggleDarkMode,
toggleConfigSidebar,
toggleMenu,
hideMobileMenu,
closeMenuOnNavigate,
changeMenuMode,
setVariant,
isDesktop,
isRailMobile,
isMobile,
effectiveVariant,
effectiveMenuMode,
hasOpenOverlay
};
}
@@ -0,0 +1,32 @@
/*
|--------------------------------------------------------------------------
| Agência PSI
|--------------------------------------------------------------------------
| Criado e desenvolvido por Leonardo Nohama
|
| Tecnologia aplicada à escuta.
| Estrutura para o cuidado.
|
| Arquivo: src/layout/composables/useConfiguratorBar.js
| Data: 2026
| Local: São Carlos/SP — Brasil
|--------------------------------------------------------------------------
| © 2026 — Todos os direitos reservados
|--------------------------------------------------------------------------
*/
import { ref } from 'vue';
// Singleton — mesmo estado em qualquer componente que importar
const open = ref(false);
export function useConfiguratorBar() {
return {
open,
toggle: () => {
open.value = !open.value;
},
close: () => {
open.value = false;
}
};
}