194 lines
9.5 KiB
Plaintext
194 lines
9.5 KiB
Plaintext
<%# app/views/layouts/_navbar.html.erb %>
|
||
<%# Sidebar lateral — responsiva (esconde no mobile, hamburguer abre) %>
|
||
|
||
<%# Overlay mobile %>
|
||
<div id="sidebar-overlay"
|
||
class="fixed inset-0 bg-black/70 z-30 hidden md:hidden"
|
||
onclick="toggleSidebar()"></div>
|
||
|
||
<%# Sidebar
|
||
`flex flex-col` NÃO é decoração: é o que faz o menu ROLAR. O <nav> do meio
|
||
pede `flex-1 overflow-y-auto`, e `flex-1` só tem efeito dentro de um flex
|
||
container — sem isto o <nav> crescia com o conteúdo, nunca ganhava altura
|
||
limitada, o `overflow-y-auto` nunca ativava e a lista simplesmente
|
||
transbordava o painel sem barra de rolagem.
|
||
|
||
Caso real (28/08/2026, iPhone 393×852): 1.194px de menu dentro de 852px de
|
||
tela, com o último item terminando 324px ABAIXO da borda. Contatos, Grupos,
|
||
Envios, Eventos, Variáveis, WhatsApp e o **Sair** ficavam inalcançáveis no
|
||
celular — e no notebook de tela baixa também. %>
|
||
<aside id="sidebar"
|
||
class="fixed top-0 left-0 h-full w-64 bg-[#111111] border-r border-[#2a2a2a] z-40
|
||
flex flex-col
|
||
transform -translate-x-full md:translate-x-0 transition-transform duration-300">
|
||
|
||
<%# Logo / Marca — `shrink-0`: num flex-col, um bloco de altura fixa ainda é
|
||
espremido quando o irmão rolável é grande demais. %>
|
||
<div class="px-4 py-5 border-b border-[#2a2a2a] shrink-0">
|
||
<%= image_tag '/logo-reem.png', alt: 'Reem Transporte',
|
||
class: 'h-14 w-auto mx-auto object-contain' %>
|
||
</div>
|
||
|
||
<%# Usuário logado %>
|
||
<div class="px-6 py-4 border-b border-[#2a2a2a] shrink-0">
|
||
<p class="text-xs text-gray-500 mb-1">Logado como</p>
|
||
<p class="text-white font-semibold text-sm truncate"><%= current_user.nome_display %></p>
|
||
<span class="inline-block mt-1 px-2 py-0.5 text-xs rounded-full
|
||
<%= current_user.admin? ? 'bg-orange-500 text-black' :
|
||
current_user.gerente? ? 'bg-orange-800 text-white' :
|
||
current_user.operador? ? 'bg-gray-700 text-white' :
|
||
current_user.externo? ? 'bg-blue-900 text-blue-200 border border-blue-500' :
|
||
'bg-gray-900 text-orange-400 border border-orange-500' %>">
|
||
<%= current_user.role_label %>
|
||
</span>
|
||
</div>
|
||
|
||
<%# Navegação %>
|
||
<nav class="px-3 py-4 space-y-1 flex-1 overflow-y-auto">
|
||
|
||
<%# Cada item pergunta por PERMISSÃO, não por papel: o menu é o espelho do
|
||
perfil que o ADM montou (Permissao::TODAS). Item sem permissão SOME — não
|
||
fica desabilitado, para não anunciar o que a pessoa não pode fazer. %>
|
||
<% if current_user.pode?('dashboard.ver') %>
|
||
<%= nav_link_to 'Dashboard', dashboard_path, icon: :dashboard %>
|
||
<% end %>
|
||
<% if current_user.pode?('dashboard.operacoes') %>
|
||
<%= nav_link_to 'Operações', operacoes_dashboard_path, icon: :operacoes %>
|
||
<% end %>
|
||
|
||
<% if current_user.pode?('consolidacao.ver') %>
|
||
<%= nav_link_to 'Consolidações', consolidacoes_path, icon: :consolidacoes %>
|
||
<%= nav_link_to 'Arquivadas', arquivadas_consolidacoes_path, icon: :arquivadas %>
|
||
<% end %>
|
||
|
||
<%# Motorista %>
|
||
<% if current_user.motorista? %>
|
||
<%= nav_link_to 'Meu Painel', motorista_dashboard_path, icon: :painel %>
|
||
<% end %>
|
||
|
||
<%# Administração — a seção inteira some quando nenhum item dela está liberado. %>
|
||
<% itens_admin = %w[admin.usuarios admin.perfis admin.configuracoes admin.auditoria
|
||
admin.planilha_simpli_route admin.edicao_lancamento
|
||
admin.romaneio] %>
|
||
<% if itens_admin.any? { |chave| current_user.pode?(chave) } %>
|
||
<div class="pt-4 mt-4 border-t border-[#2a2a2a]">
|
||
<p class="px-3 text-xs text-gray-600 uppercase tracking-wider mb-2">Administração</p>
|
||
<% if current_user.pode?('admin.usuarios') %>
|
||
<%= nav_link_to 'Usuários', admin_usuarios_path, icon: :usuarios %>
|
||
<% end %>
|
||
<% if current_user.pode?('admin.perfis') %>
|
||
<%= nav_link_to 'Perfis de acesso', admin_perfis_path, icon: :chave %>
|
||
<% end %>
|
||
<% if current_user.pode?('admin.configuracoes') %>
|
||
<%= nav_link_to 'Configurações', admin_configuracoes_path, icon: :configuracoes %>
|
||
<% end %>
|
||
<% if current_user.pode?('admin.auditoria') %>
|
||
<%= nav_link_to 'Auditoria', admin_auditoria_logs_path, icon: :auditoria %>
|
||
<% end %>
|
||
<% if current_user.pode?('admin.planilha_simpli_route') %>
|
||
<%= nav_link_to 'Planilha SimpliRoute', admin_planilha_simpli_route_path, icon: :planilha %>
|
||
<% end %>
|
||
<% if current_user.pode?('admin.romaneio') %>
|
||
<%= nav_link_to 'Romaneio de entrega', admin_romaneios_path, icon: :imprimir %>
|
||
<% end %>
|
||
<% if current_user.pode?('admin.edicao_lancamento') %>
|
||
<%= nav_link_to 'Editar Lançamento', admin_edicao_lancamento_path, icon: :editar_lancamento %>
|
||
<% end %>
|
||
</div>
|
||
|
||
<% end %>
|
||
|
||
<%# Notificações — cada item com sua chave: ver contatos é uma coisa; ligar o
|
||
WhatsApp e disparar mensagem em nome da empresa é outra. %>
|
||
<% itens_notif = %w[notificacao.contatos notificacao.envios notificacao.eventos
|
||
notificacao.variaveis notificacao.whatsapp_sessao] %>
|
||
<% if itens_notif.any? { |chave| current_user.pode?(chave) } %>
|
||
<div class="pt-4 mt-4 border-t border-[#2a2a2a]">
|
||
<p class="px-3 text-xs text-gray-600 uppercase tracking-wider mb-2">Notificações</p>
|
||
<% if current_user.pode?('notificacao.contatos') %>
|
||
<%= nav_link_to 'Contatos', admin_contatos_path, icon: :usuarios %>
|
||
<%= nav_link_to 'Grupos', admin_grupos_path, icon: :tag %>
|
||
<% end %>
|
||
<% if current_user.pode?('notificacao.envios') %>
|
||
<%= nav_link_to 'Envios', admin_envios_path, icon: :historico %>
|
||
<% end %>
|
||
<% if current_user.pode?('notificacao.eventos') %>
|
||
<%= nav_link_to 'Eventos', admin_eventos_path, icon: :diario %>
|
||
<% end %>
|
||
<% if current_user.pode?('notificacao.variaveis') %>
|
||
<%= nav_link_to 'Variáveis', admin_variaveis_path, icon: :tag %>
|
||
<% end %>
|
||
<% if current_user.pode?('notificacao.whatsapp_sessao') %>
|
||
<%= nav_link_to 'WhatsApp', admin_whatsapp_sessao_path, icon: :telefone %>
|
||
<% end %>
|
||
</div>
|
||
<% end %>
|
||
</nav>
|
||
|
||
<%# Footer da sidebar — sair %>
|
||
<%# Rodapé com o "Sair": shrink-0 para ele ficar ancorado embaixo, sempre
|
||
visível, em vez de rolar junto com a lista. %>
|
||
<div class="px-6 py-4 border-t border-[#2a2a2a] shrink-0">
|
||
<%= link_to destroy_user_session_path, data: { turbo_method: :delete, turbo_confirm: 'Deseja sair?' },
|
||
class: "flex items-center gap-2 text-gray-400 hover:text-red-400 text-sm transition-colors" do %>
|
||
<%= icone :sair, cor: nil, tamanho: 'w-5 h-5' %> Sair
|
||
<% end %>
|
||
</div>
|
||
</aside>
|
||
|
||
<%# Botão flutuante (desktop) — esconde/mostra a sidebar do site inteiro.
|
||
Em repouso mostra uma ALÇA fina laranja (indicador de que dá p/ recolher);
|
||
ao chegar perto com o mouse, ela expande no botão com a seta. %>
|
||
<div id="sidebar-edge" class="hidden md:flex group fixed top-1/2 -translate-y-1/2 z-40 h-32 w-12 items-center">
|
||
<button id="sidebar-toggle-btn" type="button" onclick="toggleSidebarCollapse()"
|
||
class="flex items-center justify-center rounded-r-lg shadow-lg transition-all duration-200
|
||
w-1.5 h-14 bg-orange-500/60 text-transparent
|
||
group-hover:w-7 group-hover:h-16 group-hover:bg-[#1a1a1a] group-hover:text-orange-500
|
||
group-hover:border group-hover:border-orange-500/70
|
||
focus:w-7 focus:h-16 focus:bg-[#1a1a1a] focus:text-orange-500"
|
||
title="Esconder/mostrar menu" aria-label="Esconder ou mostrar o menu">
|
||
<%# Gira 180° quando a sidebar está recolhida (ver syncSidebarToggleIcon) %>
|
||
<span id="sidebar-toggle-icon" class="leading-none transition-transform duration-200">
|
||
<%= icone :recolher, cor: nil, tamanho: 'w-5 h-5', espaco: false %>
|
||
</span>
|
||
</button>
|
||
</div>
|
||
|
||
<%# Topbar mobile %>
|
||
<header class="md:hidden fixed top-0 left-0 right-0 z-30 bg-[#111111] border-b border-[#2a2a2a] px-4 py-3 flex items-center justify-between">
|
||
<button onclick="toggleSidebar()"
|
||
class="text-orange-500 min-h-[48px] min-w-[48px] flex items-center justify-center"
|
||
aria-label="Abrir menu">
|
||
<%= icone :menu, cor: nil, tamanho: 'w-7 h-7', espaco: false %>
|
||
</button>
|
||
<div class="flex items-center gap-2">
|
||
<%= image_tag '/logo-reem.png', alt: 'Reem Transporte', class: 'h-8 w-auto object-contain' %>
|
||
</div>
|
||
<div class="w-12"></div>
|
||
</header>
|
||
<%# Espaço para topbar no mobile %>
|
||
<div class="h-14 md:hidden"></div>
|
||
|
||
<script>
|
||
// Mobile: abre/fecha a sidebar por cima do conteúdo (hamburguer).
|
||
function toggleSidebar() {
|
||
const s = document.getElementById('sidebar');
|
||
const o = document.getElementById('sidebar-overlay');
|
||
s.classList.toggle('-translate-x-full');
|
||
o.classList.toggle('hidden');
|
||
}
|
||
|
||
// Desktop: recolhe/expande a sidebar (estado salvo p/ o site inteiro).
|
||
function toggleSidebarCollapse() {
|
||
const collapsed = document.documentElement.classList.toggle('sidebar-collapsed');
|
||
try { localStorage.setItem('sidebarCollapsed', collapsed ? '1' : '0'); } catch (e) {}
|
||
syncSidebarToggleIcon();
|
||
}
|
||
function syncSidebarToggleIcon() {
|
||
const icon = document.getElementById('sidebar-toggle-icon');
|
||
if (icon) icon.classList.toggle('rotate-180',
|
||
document.documentElement.classList.contains('sidebar-collapsed'));
|
||
}
|
||
syncSidebarToggleIcon();
|
||
</script>
|