implantação da fase 2 e 3
This commit is contained in:
64
app/views/layouts/_sidebar.html.erb
Normal file
64
app/views/layouts/_sidebar.html.erb
Normal file
@@ -0,0 +1,64 @@
|
||||
<%# app/views/layouts/_sidebar.html.erb %>
|
||||
<div class="flex flex-col h-full">
|
||||
<%# Logo %>
|
||||
<div class="flex items-center gap-3 px-6 py-5 border-b border-white/5">
|
||||
<div class="w-10 h-10 rounded-xl bg-[#f97316] flex items-center justify-center flex-shrink-0">
|
||||
<svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M13 16V6a1 1 0 00-1-1H4a1 1 0 00-1 1v10a1 1 0 001 1h1m8-1a1 1 0 01-1 1H9m4-1V8a1 1 0 011-1h2.586a1 1 0 01.707.293l3.414 3.414a1 1 0 01.293.707V16a1 1 0 01-1 1h-1m-6-1a1 1 0 001 1h1M5 17a2 2 0 104 0m-4 0a2 2 0 114 0m6 0a2 2 0 104 0m-4 0a2 2 0 114 0"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-white font-bold text-sm leading-tight">Gade Logística</p>
|
||||
<p class="text-gray-500 text-xs">Controle de Custos</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%# Navegação %>
|
||||
<nav class="flex-1 px-3 py-4 space-y-1 overflow-y-auto scrollbar-hidden">
|
||||
|
||||
<%# Dashboard (todos exceto motorista) %>
|
||||
<%= nav_link_to 'Dashboard', dashboard_path, icon: '📊' %>
|
||||
|
||||
<%# Consolidações (admin, gerente, operador) %>
|
||||
<% if current_user.pode_criar_consolidacao? %>
|
||||
<%= nav_link_to 'Consolidações', consolidacoes_path, icon: '📋' %>
|
||||
<% end %>
|
||||
|
||||
<%# Separador Admin %>
|
||||
<% if current_user.pode_acessar_admin? %>
|
||||
<div class="pt-3 pb-1">
|
||||
<p class="px-4 text-xs font-semibold text-gray-600 uppercase tracking-wider">Administração</p>
|
||||
</div>
|
||||
|
||||
<%= nav_link_to 'Usuários', usuarios_path, icon: '👥' %>
|
||||
<%= nav_link_to 'Configurações', configuracoes_path, icon: '⚙️' %>
|
||||
<% end %>
|
||||
|
||||
<%# PDFs (fase 7 — link desabilitado por enquanto) %>
|
||||
<% if current_user.pode_acessar_admin? %>
|
||||
<div class="pt-3 pb-1">
|
||||
<p class="px-4 text-xs font-semibold text-gray-600 uppercase tracking-wider">Relatórios</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-medium text-gray-600 cursor-not-allowed">
|
||||
<span class="text-lg w-5 text-center">📄</span>
|
||||
<span>Relatórios PDF</span>
|
||||
<span class="ml-auto text-[10px] bg-gray-800 text-gray-500 px-1.5 py-0.5 rounded">Em breve</span>
|
||||
</div>
|
||||
<% end %>
|
||||
</nav>
|
||||
|
||||
<%# Footer do sidebar - info do usuário %>
|
||||
<div class="px-3 py-4 border-t border-white/5">
|
||||
<div class="flex items-center gap-3 px-3 py-3 rounded-xl bg-white/5">
|
||||
<div class="w-8 h-8 rounded-full bg-[#f97316]/20 border border-[#f97316]/30
|
||||
flex items-center justify-center text-[#f97316] font-bold text-sm flex-shrink-0">
|
||||
<%= current_user.name.to_s[0].upcase %>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-white text-sm font-medium truncate"><%= current_user.name %></p>
|
||||
<p class="text-gray-500 text-xs truncate"><%= current_user.role.humanize %></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,95 +1,169 @@
|
||||
<%# app/views/layouts/application.html.erb %>
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-BR" class="dark">
|
||||
<html lang="pt-BR" class="<%= @tema == 'light' ? '' : 'dark' %>">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="csrf-param" content="<%= request_forgery_protection_token %>">
|
||||
<meta name="csrf-token" content="<%= form_authenticity_token %>">
|
||||
<title><%= content_for?(:title) ? "#{yield(:title)} | Gade Logística" : "Gade Logística" %></title>
|
||||
<title><%= content_for?(:title) ? yield(:title) + ' · ' : '' %>Gade Logística</title>
|
||||
|
||||
<%# Tailwind via CDN em desenvolvimento — compilar em produção %>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
brand: {
|
||||
preto: '#0a0a0a',
|
||||
'preto-card': '#1a1a1a',
|
||||
laranja: '#f97316',
|
||||
'laranja-escuro': '#ea580c',
|
||||
branco: '#ffffff',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<%# Google Fonts — Inter %>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<%= stylesheet_link_tag 'application', data: { turbo_track: 'reload' } %>
|
||||
<%= javascript_importmap_tags %>
|
||||
|
||||
<style>
|
||||
body { font-family: 'Inter', sans-serif; }
|
||||
/* Scrollbar tema escuro */
|
||||
::-webkit-scrollbar { width: 6px; }
|
||||
::-webkit-scrollbar-track { background: #0a0a0a; }
|
||||
::-webkit-scrollbar-thumb { background: #f97316; border-radius: 3px; }
|
||||
/* Loading spinner laranja */
|
||||
.spinner {
|
||||
border: 3px solid #1a1a1a;
|
||||
border-top-color: #f97316;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
:root {
|
||||
--bg-main: #0a0a0a;
|
||||
--bg-card: #1a1a1a;
|
||||
--accent: #f97316;
|
||||
--text-main: #ffffff;
|
||||
--text-muted:#9ca3af;
|
||||
--border: rgba(255,255,255,0.05);
|
||||
}
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
html.light {
|
||||
--bg-main: #f8fafc;
|
||||
--bg-card: #ffffff;
|
||||
--text-main: #0f172a;
|
||||
--text-muted:#64748b;
|
||||
--border: rgba(0,0,0,0.08);
|
||||
}
|
||||
body { background-color: var(--bg-main); color: var(--text-main); }
|
||||
.scrollbar-hidden::-webkit-scrollbar { display: none; }
|
||||
</style>
|
||||
|
||||
<%= yield :head %>
|
||||
</head>
|
||||
|
||||
<body class="bg-[#0a0a0a] text-white min-h-screen">
|
||||
<body class="font-sans antialiased min-h-screen" style="background-color: var(--bg-main)">
|
||||
|
||||
<%# ── Navbar ─────────────────────────────────────────── %>
|
||||
<% if user_signed_in? %>
|
||||
<%= render 'layouts/navbar' %>
|
||||
<% end %>
|
||||
<% if user_signed_in? && !current_user.motorista? %>
|
||||
<%# Layout principal com sidebar %>
|
||||
<div class="flex min-h-screen">
|
||||
|
||||
<%# ── Flash Messages ─────────────────────────────────── %>
|
||||
<% if notice.present? %>
|
||||
<div id="flash-notice"
|
||||
class="fixed top-4 right-4 z-50 bg-orange-500 text-black font-semibold px-5 py-3 rounded-lg shadow-lg flex items-center gap-2 animate-fade-in"
|
||||
data-controller="flash">
|
||||
<span>✅</span> <%= notice %>
|
||||
<%# Sidebar Desktop %>
|
||||
<aside id="sidebar"
|
||||
class="fixed inset-y-0 left-0 z-50 w-64 flex-col hidden lg:flex
|
||||
border-r border-white/5"
|
||||
style="background-color: var(--bg-card)">
|
||||
<%= render 'layouts/sidebar' %>
|
||||
</aside>
|
||||
|
||||
<%# Overlay mobile %>
|
||||
<div id="sidebar-overlay"
|
||||
class="fixed inset-0 z-40 bg-black/60 backdrop-blur-sm hidden lg:hidden"
|
||||
onclick="toggleSidebar()"></div>
|
||||
|
||||
<%# Sidebar Mobile (drawer) %>
|
||||
<aside id="sidebar-mobile"
|
||||
class="fixed inset-y-0 left-0 z-50 w-64 flex flex-col lg:hidden
|
||||
transform -translate-x-full transition-transform duration-300 ease-in-out
|
||||
border-r border-white/5"
|
||||
style="background-color: var(--bg-card)">
|
||||
<%= render 'layouts/sidebar' %>
|
||||
</aside>
|
||||
|
||||
<%# Main content %>
|
||||
<div class="flex-1 lg:ml-64 flex flex-col min-h-screen">
|
||||
<%# Topbar %>
|
||||
<header class="sticky top-0 z-30 px-4 lg:px-6 py-4 flex items-center justify-between
|
||||
border-b border-white/5 backdrop-blur-md"
|
||||
style="background-color: rgba(10,10,10,0.8)">
|
||||
<%# Hamburger mobile %>
|
||||
<button onclick="toggleSidebar()"
|
||||
class="lg:hidden p-2 text-gray-400 hover:text-white rounded-lg hover:bg-white/10 transition-colors">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<%# Breadcrumb / título da página %>
|
||||
<div class="flex-1 lg:ml-0 ml-3">
|
||||
<span class="text-white font-semibold text-sm lg:text-base">
|
||||
<%= content_for?(:page_title) ? yield(:page_title) : 'Dashboard' %>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<%# Ações do header %>
|
||||
<div class="flex items-center gap-3">
|
||||
<%# Toggle tema %>
|
||||
<%= button_to toggle_tema_configuracoes_path, method: :post,
|
||||
class: 'p-2 text-gray-400 hover:text-white hover:bg-white/10
|
||||
rounded-lg transition-colors cursor-pointer',
|
||||
title: @tema == 'dark' ? 'Mudar para tema claro' : 'Mudar para tema escuro' do %>
|
||||
<% if @tema == 'dark' %>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"/>
|
||||
</svg>
|
||||
<% else %>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"/>
|
||||
</svg>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%# Avatar / menu usuário %>
|
||||
<div class="relative" x-data="{ open: false }">
|
||||
<button class="flex items-center gap-2 p-1.5 rounded-xl hover:bg-white/10 transition-colors">
|
||||
<div class="w-8 h-8 rounded-full bg-[#f97316]/20 border border-[#f97316]/30
|
||||
flex items-center justify-center text-[#f97316] font-bold text-sm">
|
||||
<%= current_user.name.to_s[0].upcase %>
|
||||
</div>
|
||||
<span class="hidden sm:block text-white text-sm font-medium pr-1">
|
||||
<%= current_user.nome_display %>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<%# Logout %>
|
||||
<%= button_to destroy_user_session_path, method: :delete,
|
||||
class: 'px-3 py-2 text-gray-500 hover:text-red-400 text-sm
|
||||
hover:bg-red-900/20 rounded-lg transition-colors cursor-pointer' do %>
|
||||
Sair
|
||||
<% end %>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<%# Conteúdo principal %>
|
||||
<main class="flex-1 p-4 lg:p-6">
|
||||
<%= yield %>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if alert.present? %>
|
||||
<div id="flash-alert"
|
||||
class="fixed top-4 right-4 z-50 bg-red-600 text-white font-semibold px-5 py-3 rounded-lg shadow-lg flex items-center gap-2"
|
||||
data-controller="flash">
|
||||
<span>⚠️</span> <%= alert %>
|
||||
<% elsif user_signed_in? && current_user.motorista? %>
|
||||
<%# Layout simplificado para motorista %>
|
||||
<div class="min-h-screen" style="background-color: var(--bg-main)">
|
||||
<main class="max-w-2xl mx-auto px-4 py-6">
|
||||
<%= yield %>
|
||||
</main>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%# ── Conteúdo principal ──────────────────────────────── %>
|
||||
<main class="<%= user_signed_in? ? 'ml-0 md:ml-64 p-4 md:p-8' : '' %> min-h-screen">
|
||||
<% else %>
|
||||
<%# Layout sem sidebar (login, etc) %>
|
||||
<%= yield %>
|
||||
</main>
|
||||
<% end %>
|
||||
|
||||
<%# Auto-hide flash após 4s (JS simples) %>
|
||||
<script>
|
||||
setTimeout(() => {
|
||||
['flash-notice', 'flash-alert'].forEach(id => {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.style.transition = 'opacity 0.5s', el.style.opacity = '0',
|
||||
setTimeout(() => el.remove(), 500);
|
||||
});
|
||||
}, 4000);
|
||||
</script>
|
||||
function toggleSidebar() {
|
||||
const mobile = document.getElementById('sidebar-mobile');
|
||||
const overlay = document.getElementById('sidebar-overlay');
|
||||
const isOpen = !mobile.classList.contains('-translate-x-full');
|
||||
|
||||
if (isOpen) {
|
||||
mobile.classList.add('-translate-x-full');
|
||||
overlay.classList.add('hidden');
|
||||
} else {
|
||||
mobile.classList.remove('-translate-x-full');
|
||||
overlay.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// Fecha sidebar ao pressionar Escape
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
const mobile = document.getElementById('sidebar-mobile');
|
||||
if (mobile && !mobile.classList.contains('-translate-x-full')) toggleSidebar();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user