Files
Reem-Notas/app/views/layouts/_navbar.html.erb
2026-08-28 16:14:38 -03:00

194 lines
9.5 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<%# 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>