diff --git a/requirements.txt b/requirements.txt index 6358c0f..543b2cd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,6 +9,7 @@ SQLAlchemy==1.4.23 python-dotenv==0.19.0 pyotp==2.6.0 qrcode[pil]==7.3.1 +numpy==1.26.4 pandas==1.5.3 xlsxwriter==3.1.2 Flask-Mail==0.9.1 diff --git a/static/css/style.css b/static/css/style.css index ca5efbc..2fec1c7 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -8,90 +8,258 @@ body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; + margin: 0; + padding: 0; } -header { +/* Mobile Header Styles */ +.mobile-header { + display: none; background-color: #4CAF50; padding: 1rem; + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 1001; + justify-content: space-between; + align-items: center; } -nav ul { +.mobile-nav-brand a { + color: white; + text-decoration: none; + font-size: 1.5rem; + font-weight: bold; +} + +.mobile-nav-toggle { + background: none; + border: none; + cursor: pointer; + padding: 0; + width: 30px; + height: 24px; display: flex; - justify-content: flex-start; + flex-direction: column; + justify-content: space-between; +} + +.mobile-nav-toggle span { + display: block; + height: 3px; + width: 100%; + background-color: white; + border-radius: 1px; + transition: all 0.3s ease; +} + +.mobile-nav-toggle.active span:nth-child(1) { + transform: rotate(45deg) translate(6px, 6px); +} + +.mobile-nav-toggle.active span:nth-child(2) { + opacity: 0; +} + +.mobile-nav-toggle.active span:nth-child(3) { + transform: rotate(-45deg) translate(6px, -6px); +} + +/* Sidebar Styles */ +.sidebar { + position: fixed; + top: 0; + left: 0; + width: 280px; + height: 100vh; + background-color: #3d9c17; + color: white; + overflow-y: auto; + transition: transform 0.3s ease; + z-index: 1000; + box-shadow: 2px 0 10px rgba(0,0,0,0.1); +} + +.sidebar.collapsed { + width: 60px; +} + +.sidebar-header { + padding: 1.5rem; + background-color: #34495e; + display: flex; + justify-content: space-between; align-items: center; + border-bottom: 1px solid #3a5269; +} + +.sidebar-header h2 { + margin: 0; + color: white; + font-size: 1.5rem; + font-weight: bold; +} + +.sidebar-header h2 a { + color: white; + text-decoration: none; +} + +.sidebar-toggle { + background: none; + border: none; + cursor: pointer; + padding: 0; + width: 24px; + height: 18px; + display: flex; + flex-direction: column; + justify-content: space-between; +} + +.sidebar-toggle span { + display: block; + height: 2px; + width: 100%; + background-color: white; + border-radius: 1px; + transition: all 0.3s ease; +} + +.sidebar-nav { + padding: 0; + font-weight: bold; +} + +.sidebar-nav ul { list-style: none; padding: 0; margin: 0; } -nav ul li { - margin: 0 1rem; +.sidebar-nav li { + margin: 0; } -nav ul li a { - color: white; +.sidebar-nav li a { + display: flex; + align-items: center; + padding: 1rem 1.5rem; + color: #ecf0f1; text-decoration: none; + transition: all 0.3s ease; + border-left: 3px solid transparent; } -/* Dropdown menu styles */ -nav ul li.dropdown { +.sidebar-nav li a:hover { + background-color: #34495e; + border-left-color: #4CAF50; + color: white; +} + +.sidebar-nav li a.active { + background-color: #4CAF50; + border-left-color: #45a049; + color: white; +} + +.nav-icon { + margin-right: 1rem; + font-size: 1.2rem; + min-width: 24px; + text-align: center; +} + +.nav-divider { + padding: 0.75rem 1.5rem 0.25rem; + font-size: 0.85rem; + color: #d9ffd1; + text-transform: uppercase; + font-weight: 600; + letter-spacing: 0.5px; + border-top: 1px solid #3a5269; + margin-top: 0.5rem; +} + +.nav-divider:first-child { + border-top: none; + margin-top: 0; +} + +/* Collapsed sidebar styles */ +.sidebar.collapsed .sidebar-header { + padding: 1rem 0.5rem; +} + +.sidebar.collapsed .sidebar-header h2 { + display: none; +} + +.sidebar.collapsed .nav-divider { + display: none; +} + +.sidebar.collapsed .sidebar-nav li a { + padding: 1rem 0.5rem; + justify-content: center; position: relative; } -nav ul li.admin-dropdown { - margin-left: auto; /* Push to the right */ +.sidebar.collapsed .nav-icon { + margin-right: 0; + font-size: 1.1rem; } -nav ul li.dropdown .dropdown-toggle { - cursor: pointer; - padding: 10px 15px; - display: block; - color: #fff; - text-decoration: none; -} - -nav ul li.dropdown .dropdown-menu { +/* Hide text in collapsed state */ +.sidebar.collapsed .sidebar-nav li a .nav-text { display: none; +} + +/* Tooltip for collapsed sidebar */ +.sidebar.collapsed .sidebar-nav li a:hover::after { + content: attr(data-tooltip); position: absolute; - right: 0; /* Align to the right for admin dropdown */ - background-color: #333; - min-width: 180px; - box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); - z-index: 1000; - padding: 10px 0; + left: 100%; + top: 50%; + transform: translateY(-50%); + background-color: #2c3e50; + color: white; + padding: 0.5rem 1rem; border-radius: 4px; - margin-top: 5px; + white-space: nowrap; + z-index: 1001; + margin-left: 0.5rem; + box-shadow: 0 2px 8px rgba(0,0,0,0.2); } -/* Show dropdown on hover and keep it visible when hovering over the menu */ -nav ul li.dropdown:hover .dropdown-menu, -nav ul li.dropdown .dropdown-menu:hover { - display: block; -} - -nav ul li.dropdown .dropdown-menu li { - display: block; - padding: 0; +/* Mobile overlay */ +.mobile-overlay { + display: none; + position: fixed; + top: 0; + left: 0; width: 100%; + height: 100%; + background-color: rgba(0,0,0,0.5); + z-index: 999; } -nav ul li.dropdown .dropdown-menu li a { - padding: 10px 20px; - display: block; - text-align: left; - color: #fff; - text-decoration: none; - transition: background-color 0.2s ease; +.main-content { + margin-left: 280px; + padding: 2rem; + min-height: 100vh; + transition: margin-left 0.3s ease; } -nav ul li.dropdown .dropdown-menu li a:hover { - background-color: #444; +/* Main content adjustments for collapsed sidebar */ +body:has(.sidebar.collapsed) .main-content { + margin-left: 60px; } -main { - max-width: 1200px; - margin: 2rem auto; - padding: 0 1rem; +/* Fallback for browsers that don't support :has() */ +.sidebar.collapsed ~ .main-content { + margin-left: 60px; } .hero { @@ -206,6 +374,18 @@ footer { padding: 1rem; background-color: #f4f4f4; margin-top: 2rem; + margin-left: 280px; + transition: margin-left 0.3s ease; +} + +/* Footer adjustments for collapsed sidebar */ +body:has(.sidebar.collapsed) footer { + margin-left: 60px; +} + +/* Fallback for browsers that don't support :has() */ +.sidebar.collapsed ~ footer { + margin-left: 60px; } /* Time tracking specific styles */ @@ -863,3 +1043,88 @@ input[type="time"]::-webkit-datetime-edit { transform: translateY(-1px); box-shadow: 0 3px 10px rgba(76, 175, 80, 0.3); } +/* Responsive Design for Sidebar Navigation */ +@media (max-width: 1024px) { + .sidebar { + transform: translateX(-100%); + } + + .sidebar.mobile-open { + transform: translateX(0); + } + + .mobile-header { + display: flex; + } + + .main-content { + margin-left: 0; + padding-top: 5rem; + } + + footer { + margin-left: 0; + } + + .mobile-overlay.active { + display: block; + } + + .sidebar-toggle { + display: none; + } +} + +@media (max-width: 768px) { + .main-content { + padding: 1rem; + padding-top: 5rem; + } + + .sidebar { + width: 100%; + max-width: 300px; + } + + .timetrack-container { + padding: 0; + } + + .admin-panel { + flex-direction: column; + } + + .admin-card { + width: 100%; + } + + .features { + grid-template-columns: 1fr; + } + + .export-options { + grid-template-columns: 1fr; + } + + .quick-export-buttons { + grid-template-columns: 1fr; + } + + #export-buttons .quick-export-buttons { + flex-direction: column; + } +} + +@media (min-width: 1025px) { + .mobile-header { + display: none; + } + + .sidebar { + transform: translateX(0); + } + + .mobile-overlay { + display: none !important; + } +} \ No newline at end of file diff --git a/static/js/sidebar.js b/static/js/sidebar.js new file mode 100644 index 0000000..dbf66f7 --- /dev/null +++ b/static/js/sidebar.js @@ -0,0 +1,67 @@ +// Sidebar functionality +document.addEventListener('DOMContentLoaded', function() { + const sidebar = document.getElementById('sidebar'); + const sidebarToggle = document.getElementById('sidebar-toggle'); + const mobileNavToggle = document.getElementById('mobile-nav-toggle'); + const mobileOverlay = document.getElementById('mobile-overlay'); + + // Desktop sidebar toggle + if (sidebarToggle) { + sidebarToggle.addEventListener('click', function() { + sidebar.classList.toggle('collapsed'); + + // Update main content and footer margins for browsers that don't support :has() + const mainContent = document.querySelector('.main-content'); + const footer = document.querySelector('footer'); + + if (sidebar.classList.contains('collapsed')) { + if (mainContent) mainContent.style.marginLeft = '60px'; + if (footer) footer.style.marginLeft = '60px'; + } else { + if (mainContent) mainContent.style.marginLeft = '280px'; + if (footer) footer.style.marginLeft = '280px'; + } + }); + } + + // Mobile navigation toggle + if (mobileNavToggle) { + mobileNavToggle.addEventListener('click', function() { + sidebar.classList.toggle('mobile-open'); + mobileOverlay.classList.toggle('active'); + mobileNavToggle.classList.toggle('active'); + }); + } + + // Close mobile sidebar when clicking overlay + if (mobileOverlay) { + mobileOverlay.addEventListener('click', function() { + sidebar.classList.remove('mobile-open'); + mobileOverlay.classList.remove('active'); + if (mobileNavToggle) mobileNavToggle.classList.remove('active'); + }); + } + + // Close mobile sidebar when clicking on navigation links + if (sidebar) { + const navLinks = sidebar.querySelectorAll('a'); + navLinks.forEach(link => { + link.addEventListener('click', function() { + if (window.innerWidth <= 1024) { + sidebar.classList.remove('mobile-open'); + mobileOverlay.classList.remove('active'); + if (mobileNavToggle) mobileNavToggle.classList.remove('active'); + } + }); + }); + } + + // Handle window resize + window.addEventListener('resize', function() { + if (window.innerWidth > 1024) { + if (sidebar) sidebar.classList.remove('mobile-open'); + if (mobileOverlay) mobileOverlay.classList.remove('active'); + if (mobileNavToggle) mobileNavToggle.classList.remove('active'); + } + }); +}); \ No newline at end of file diff --git a/templates/layout.html b/templates/layout.html index f419a65..efddf23 100644 --- a/templates/layout.html +++ b/templates/layout.html @@ -7,69 +7,79 @@
-