diff --git a/static/css/style.css b/static/css/style.css index 1a92e5d..cfb1f02 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -1,43 +1,33 @@ -/** - * Main stylesheet for QR Code Management System - * static/css/style.css - */ - -/* CSS Variables for consistent theming */ +/* CSS Variables - Light Mode Only */ :root { - /* Primary Colors */ + /* Primary color palette */ --primary-color: #2563eb; --primary-hover: #1d4ed8; - --primary-light: #dbeafe; - - /* Secondary Colors */ --secondary-color: #64748b; - --secondary-hover: #475569; - - /* Status Colors */ - --success-color: #059669; - --success-light: #dcfce7; - --warning-color: #d97706; - --warning-light: #fef3c7; - --danger-color: #dc2626; - --danger-light: #fee2e2; - --info-color: #0284c7; - --info-light: #e0f2fe; - - /* Neutral Colors */ + --success-color: #10b981; + --warning-color: #f59e0b; + --danger-color: #ef4444; + --info-color: #0891b2; + + /* Neutral colors - Light mode fixed */ --white: #ffffff; - --gray-50: #f9fafb; - --gray-100: #f3f4f6; - --gray-200: #e5e7eb; - --gray-300: #d1d5db; - --gray-400: #9ca3af; - --gray-500: #6b7280; - --gray-600: #4b5563; - --gray-700: #374151; - --gray-800: #1f2937; - --gray-900: #111827; - - /* Spacing Scale */ + --gray-50: #f8fafc; + --gray-100: #f1f5f9; + --gray-200: #e2e8f0; + --gray-300: #cbd5e1; + --gray-400: #94a3b8; + --gray-500: #64748b; + --gray-600: #475569; + --gray-700: #334155; + --gray-800: #1e293b; + --gray-900: #0f172a; + + /* Layout variables */ + --sidebar-width: 280px; + --sidebar-collapsed-width: 64px; + --header-height: 64px; + + /* Existing spacing and other variables */ --spacing-1: 0.25rem; --spacing-2: 0.5rem; --spacing-3: 0.75rem; @@ -45,29 +35,8 @@ --spacing-5: 1.25rem; --spacing-6: 1.5rem; --spacing-8: 2rem; - --spacing-10: 2.5rem; --spacing-12: 3rem; - --spacing-16: 4rem; - --spacing-20: 5rem; - - /* Border Radius */ - --radius: 0.375rem; - --radius-md: 0.5rem; - --radius-lg: 0.75rem; - --radius-xl: 1rem; - --radius-2xl: 1.5rem; - --radius-full: 9999px; - - /* Shadows */ - --shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); - --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), - 0 2px 4px -1px rgba(0, 0, 0, 0.06); - --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), - 0 4px 6px -2px rgba(0, 0, 0, 0.05); - --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), - 0 10px 10px -5px rgba(0, 0, 0, 0.04); - - /* Typography */ + --font-size-xs: 0.75rem; --font-size-sm: 0.875rem; --font-size-base: 1rem; @@ -76,34 +45,33 @@ --font-size-2xl: 1.5rem; --font-size-3xl: 1.875rem; --font-size-4xl: 2.25rem; - - /* Transitions */ - --transition: all 0.15s ease-in-out; + + --radius: 0.375rem; + --radius-lg: 0.5rem; + --radius-xl: 0.75rem; + --radius-2xl: 1rem; + --radius-full: 9999px; + + --shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); + --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); + + --transition: all 0.2s ease-in-out; --transition-slow: all 0.3s ease-in-out; - - /* Z-Index Scale */ + --z-dropdown: 1000; --z-modal: 1050; - --z-toast: 1100; + --z-sidebar: 1100; + --z-overlay: 1200; } -/* Dark theme variables */ -[data-theme="dark"] { - --white: #1f2937; - --gray-50: #374151; - --gray-100: #4b5563; - --gray-200: #6b7280; - --gray-800: #f9fafb; - --gray-900: #ffffff; -} - -/* Base Styles */ +/* Force Light Mode Only */ * { - margin: 0; - padding: 0; - box-sizing: border-box; + color-scheme: light only !important; } +/* Reset and Base Styles */ html { scroll-behavior: smooth; } @@ -115,10 +83,266 @@ body { color: var(--gray-700); background-color: var(--gray-50); min-height: 100vh; + margin: 0; + padding: 0; +} + +/* App Layout */ +.app-layout { + display: flex; + min-height: 100vh; + background-color: var(--gray-50); +} + +/* Sidebar Styles */ +.sidebar { + width: var(--sidebar-width); + background: linear-gradient( + 180deg, + var(--primary-color), + var(--primary-hover) + ); + color: var(--white); + position: fixed; + left: 0; + top: 0; + height: 100vh; + z-index: var(--z-sidebar); display: flex; flex-direction: column; + transition: var(--transition-slow); + box-shadow: var(--shadow-xl); } +.sidebar.collapsed { + width: var(--sidebar-collapsed-width); +} + +/* Sidebar Brand */ +.sidebar-brand { + padding: var(--spacing-6) var(--spacing-4); + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} + +.brand-content { + display: flex; + align-items: center; + gap: var(--spacing-3); + font-size: var(--font-size-xl); + font-weight: 700; +} + +.brand-content i { + font-size: 1.5rem; +} + +.sidebar.collapsed .brand-text { + display: none; +} + +/* Sidebar Menu */ +.sidebar-menu { + flex: 1; + display: flex; + flex-direction: column; + padding: var(--spacing-4) 0; +} + +.menu-section { + flex: 1; +} + +.menu-items { + display: flex; + flex-direction: column; + gap: var(--spacing-1); + padding: 0 var(--spacing-3); +} + +.menu-item { + display: flex; + align-items: center; + gap: var(--spacing-3); + padding: var(--spacing-3) var(--spacing-4); + color: var(--white); + text-decoration: none; + border-radius: var(--radius-lg); + transition: var(--transition); + position: relative; + border: none; + background: none; + font-size: var(--font-size-base); + cursor: pointer; + width: 100%; +} + +.menu-item:hover { + background-color: rgba(255, 255, 255, 0.1); + transform: translateX(4px); +} + +.menu-item.active, +.menu-item:focus { + background-color: rgba(255, 255, 255, 0.2); + transform: translateX(4px); +} + +.menu-item.logout:hover { + background-color: var(--danger-color); +} + +.menu-item i { + width: 20px; + text-align: center; + flex-shrink: 0; +} + +.sidebar.collapsed .menu-text { + display: none; +} + +.sidebar.collapsed .menu-item { + justify-content: center; + padding: var(--spacing-3); +} + +/* Sidebar Bottom */ +.sidebar-bottom { + border-top: 1px solid rgba(255, 255, 255, 0.1); + padding-top: var(--spacing-4); +} + +/* Sidebar Toggle Button */ +.sidebar-toggle { + position: absolute; + right: -12px; + top: 50%; + transform: translateY(-50%); + width: 24px; + height: 24px; + background: var(--white); + border: 1px solid var(--gray-200); + border-radius: var(--radius-full); + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + color: var(--gray-600); + font-size: 0.75rem; + transition: var(--transition); + z-index: 10; +} + +.sidebar-toggle:hover { + background: var(--gray-50); + color: var(--primary-color); +} + +.sidebar.collapsed .sidebar-toggle i { + transform: rotate(180deg); +} + +/* Sidebar Overlay for Mobile */ +.sidebar-overlay { + display: none; + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.5); + z-index: calc(var(--z-sidebar) - 1); +} + +/* Main Wrapper */ +.main-wrapper { + flex: 1; + margin-left: var(--sidebar-width); + display: flex; + flex-direction: column; + min-height: 100vh; + transition: var(--transition-slow); +} + +.sidebar.collapsed + .sidebar-overlay + .main-wrapper { + margin-left: var(--sidebar-collapsed-width); +} + +/* Top Header */ +.top-header { + height: var(--header-height); + background: var(--white); + border-bottom: 1px solid var(--gray-200); + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 var(--spacing-6); + position: sticky; + top: 0; + z-index: 100; + box-shadow: var(--shadow); +} + +.header-left { + display: flex; + align-items: center; + gap: var(--spacing-4); +} + +.mobile-menu-btn { + display: none; + flex-direction: column; + gap: 4px; + background: none; + border: none; + cursor: pointer; + padding: var(--spacing-2); +} + +.hamburger-line { + width: 24px; + height: 2px; + background: var(--gray-700); + transition: var(--transition); +} + +.page-title { + font-size: var(--font-size-xl); + font-weight: 600; + color: var(--gray-900); + margin: 0; +} + +.header-right { + display: flex; + align-items: center; + gap: var(--spacing-4); +} + +.user-info { + display: flex; + flex-direction: column; + align-items: flex-end; + font-size: var(--font-size-sm); +} + +.user-name { + font-weight: 600; + color: var(--gray-900); +} + +.user-role { + color: var(--gray-500); + font-size: var(--font-size-xs); + text-transform: uppercase; + letter-spacing: 0.05em; +} + +/* Main Content */ +.main-content { + flex: 1; + padding: var(--spacing-8) 0; +} + +/* Keep all existing component styles (buttons, cards, forms, etc.) */ + /* Typography */ h1, h2, @@ -173,252 +397,7 @@ a:hover { padding: 0 var(--spacing-4); } -.main-content { - flex: 1; - padding: var(--spacing-8) 0; -} - -/* Navigation */ -.navbar { - background: linear-gradient( - 135deg, - var(--primary-color), - var(--primary-hover) - ); - color: var(--white); - padding: var(--spacing-4) 0; - box-shadow: var(--shadow-md); - position: sticky; - top: 0; - z-index: var(--z-dropdown); -} - -.nav-container { - max-width: 1200px; - margin: 0 auto; - padding: 0 var(--spacing-4); - display: flex; - justify-content: space-between; - align-items: center; -} - -.nav-brand { - display: flex; - align-items: center; - gap: var(--spacing-3); - font-size: var(--font-size-xl); - font-weight: 700; - color: var(--white); -} - -.nav-menu { - display: flex; - gap: var(--spacing-6); - align-items: center; -} - -.nav-link { - color: var(--white); - padding: var(--spacing-2) var(--spacing-4); - border-radius: var(--radius); - transition: var(--transition); - display: flex; - align-items: center; - gap: var(--spacing-2); -} - -.nav-link:hover { - background-color: rgba(255, 255, 255, 0.1); - text-decoration: none; -} - -.nav-link.logout:hover { - background-color: var(--danger-color); -} - -.nav-toggle { - display: none; - flex-direction: column; - cursor: pointer; - padding: var(--spacing-2); -} - -.nav-toggle .bar { - width: 24px; - height: 3px; - background-color: var(--white); - margin: 3px 0; - transition: var(--transition); - border-radius: var(--radius-full); -} - -/* Buttons */ -.btn { - display: inline-flex; - align-items: center; - gap: var(--spacing-2); - padding: var(--spacing-3) var(--spacing-6); - font-size: var(--font-size-sm); - font-weight: 500; - text-align: center; - text-decoration: none; - border: 1px solid transparent; - border-radius: var(--radius-lg); - cursor: pointer; - transition: var(--transition); - white-space: nowrap; -} - -.btn:focus { - outline: none; - box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1); -} - -.btn:disabled { - opacity: 0.6; - cursor: not-allowed; -} - -/* Button Variants */ -.btn-primary { - background-color: var(--primary-color); - color: var(--white); - border-color: var(--primary-color); -} - -.btn-primary:hover:not(:disabled) { - background-color: var(--primary-hover); - border-color: var(--primary-hover); - color: var(--white); - text-decoration: none; -} - -.btn-secondary { - background-color: var(--gray-100); - color: var(--gray-700); - border-color: var(--gray-200); -} - -.btn-secondary:hover:not(:disabled) { - background-color: var(--gray-200); - color: var(--gray-800); - text-decoration: none; -} - -.btn-success { - background-color: var(--success-color); - color: var(--white); - border-color: var(--success-color); -} - -.btn-success:hover:not(:disabled) { - background-color: #047857; - color: var(--white); - text-decoration: none; -} - -.btn-warning { - background-color: var(--warning-color); - color: var(--white); - border-color: var(--warning-color); -} - -.btn-warning:hover:not(:disabled) { - background-color: #b45309; - color: var(--white); - text-decoration: none; -} - -.btn-danger { - background-color: var(--danger-color); - color: var(--white); - border-color: var(--danger-color); -} - -.btn-danger:hover:not(:disabled) { - background-color: #b91c1c; - color: var(--white); - text-decoration: none; -} - -.btn-outline { - background-color: transparent; - color: var(--primary-color); - border-color: var(--primary-color); -} - -.btn-outline:hover:not(:disabled) { - background-color: var(--primary-color); - color: var(--white); - text-decoration: none; -} - -/* Button Sizes */ -.btn-sm { - padding: var(--spacing-2) var(--spacing-4); - font-size: var(--font-size-xs); -} - -.btn-lg { - padding: var(--spacing-4) var(--spacing-8); - font-size: var(--font-size-lg); -} - -/* Forms */ -.form-group { - margin-bottom: var(--spacing-6); -} - -.form-label { - display: block; - font-size: var(--font-size-sm); - font-weight: 500; - color: var(--gray-700); - margin-bottom: var(--spacing-2); -} - -.form-label.required::after { - content: " *"; - color: var(--danger-color); -} - -.form-input, -.form-select, -.form-textarea { - width: 100%; - padding: var(--spacing-3); - font-size: var(--font-size-sm); - border: 2px solid var(--gray-200); - border-radius: var(--radius-lg); - transition: var(--transition); - background-color: var(--white); -} - -.form-input:focus, -.form-select:focus, -.form-textarea:focus { - outline: none; - border-color: var(--primary-color); - box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1); -} - -.form-input.error, -.form-select.error, -.form-textarea.error { - border-color: var(--danger-color); - box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.1); -} - -.form-textarea { - resize: vertical; - min-height: 100px; -} - -.field-error { - color: var(--danger-color); - font-size: var(--font-size-xs); - margin-top: var(--spacing-1); -} +/* Keep all existing component styles from original CSS */ /* Cards */ .card { @@ -436,7 +415,7 @@ a:hover { .card-header { padding: var(--spacing-6); border-bottom: 1px solid var(--gray-200); - background-color: var(--gray-50); + background: var(--gray-50); } .card-body { @@ -446,130 +425,188 @@ a:hover { .card-footer { padding: var(--spacing-6); border-top: 1px solid var(--gray-200); - background-color: var(--gray-50); + background: var(--gray-50); } -/* Tables */ -.table-container { - overflow-x: auto; - border-radius: var(--radius-xl); - box-shadow: var(--shadow); -} - -.table { - width: 100%; - border-collapse: collapse; - background: var(--white); -} - -.table th, -.table td { - padding: var(--spacing-4); - text-align: left; - border-bottom: 1px solid var(--gray-200); -} - -.table th { - background-color: var(--gray-50); - font-weight: 600; - color: var(--gray-700); +/* Enhanced Dashboard Styling Fixes */ +.search-box input { + padding: var(--spacing-3) var(--spacing-3) var(--spacing-3) var(--spacing-10); + border: 2px solid var(--gray-200); + border-radius: var(--radius-lg); font-size: var(--font-size-sm); -} - -.table tbody tr:hover { - background-color: var(--gray-50); -} - -.table tbody tr:last-child td { - border-bottom: none; -} - -/* Modals */ -.modal { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.5); - display: none; - justify-content: center; - align-items: center; - z-index: var(--z-modal); - opacity: 0; - transition: opacity 0.3s ease-in-out; -} - -.modal.show { - opacity: 1; -} - -.modal-content { - background: var(--white); - border-radius: var(--radius-xl); - box-shadow: var(--shadow-xl); - max-width: 500px; - width: 90%; - max-height: 90vh; - overflow-y: auto; - transform: scale(0.95); - transition: transform 0.3s ease-in-out; -} - -.modal.show .modal-content { - transform: scale(1); -} - -.modal-header { - padding: var(--spacing-6); - border-bottom: 1px solid var(--gray-200); - display: flex; - justify-content: space-between; - align-items: center; -} - -.modal-header h3 { - margin: 0; - display: flex; - align-items: center; - gap: var(--spacing-3); -} - -.modal-close { - background: none; - border: none; - color: var(--gray-400); - cursor: pointer; - padding: var(--spacing-2); - border-radius: var(--radius); + width: 320px; + height: 44px; transition: var(--transition); } -.modal-close:hover { +.filter-select { + padding: var(--spacing-3) var(--spacing-4); + border: 2px solid var(--gray-200); + border-radius: var(--radius-lg); + font-size: var(--font-size-sm); + background-color: var(--white); + cursor: pointer; + transition: var(--transition); + min-width: 150px; + height: 44px; +} + +.results-counter { + padding: var(--spacing-3) var(--spacing-4); + background: var(--gray-100); + border: 2px solid var(--gray-200); + border-radius: var(--radius-lg); + font-size: var(--font-size-sm); color: var(--gray-600); - background-color: var(--gray-100); + font-weight: 500; + height: 44px; + display: flex; + align-items: center; } -.modal-body { - padding: var(--spacing-6); +/* Standardized Action Buttons */ +.action-btn { + padding: var(--spacing-2); + border: 1px solid transparent; + border-radius: var(--radius); + background: transparent; + cursor: pointer; + transition: var(--transition); + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + text-decoration: none; + font-size: var(--font-size-sm); } -.modal-footer { - padding: var(--spacing-6); - border-top: 1px solid var(--gray-200); +.action-btn i { + font-size: 14px; +} + +/* QR Card Actions Container */ +.quick-actions { + display: flex; + gap: var(--spacing-2); + flex-wrap: wrap; + align-items: center; +} + +/* Standard Button Heights */ +.btn { + height: 44px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--spacing-2); + padding: 0 var(--spacing-6); + font-size: var(--font-size-sm); + font-weight: 500; + border-radius: var(--radius-lg); + border: 2px solid transparent; + cursor: pointer; + transition: var(--transition); + text-decoration: none; + line-height: 1; + white-space: nowrap; +} + +.btn-outline { + background: var(--white); + border-color: var(--gray-200); + color: var(--gray-700); + height: 44px; +} + +.btn-outline:hover { + background: var(--gray-50); + border-color: var(--primary-color); + color: var(--primary-color); +} + +/* Section Controls Alignment */ +.section-controls { display: flex; gap: var(--spacing-3); - justify-content: flex-end; + align-items: center; + flex-wrap: wrap; } -/* Alerts */ +.section-controls > * { + height: 44px; +} + +.btn-primary { + background: var(--primary-color); + color: var(--white); +} + +.btn-primary:hover { + background: var(--primary-hover); +} + +.btn-secondary { + background: var(--secondary-color); + color: var(--white); +} + +.btn-success { + background: var(--success-color); + color: var(--white); +} + +.btn-warning { + background: var(--warning-color); + color: var(--white); +} + +.btn-danger { + background: var(--danger-color); + color: var(--white); +} + +.btn-info { + background: var(--info-color); + color: var(--white); +} + +/* Forms */ +.form-group { + margin-bottom: var(--spacing-6); +} + +.form-label { + display: block; + font-weight: 500; + color: var(--gray-700); + margin-bottom: var(--spacing-2); + font-size: var(--font-size-sm); +} + +.form-control { + width: 100%; + padding: var(--spacing-3) var(--spacing-4); + font-size: var(--font-size-base); + border: 1px solid var(--gray-300); + border-radius: var(--radius-lg); + background: var(--white); + transition: var(--transition); +} + +.form-control:focus { + outline: none; + border-color: var(--primary-color); + box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1); +} + +/* Flash Messages */ .flash-messages { position: fixed; top: var(--spacing-4); right: var(--spacing-4); - z-index: var(--z-toast); + z-index: var(--z-modal); max-width: 400px; - width: 100%; } .alert { @@ -584,173 +621,116 @@ a:hover { } .alert-success { - background-color: var(--success-light); - color: var(--success-color); - border-left: 4px solid var(--success-color); + background: var(--success-color); + color: var(--white); } .alert-error { - background-color: var(--danger-light); - color: var(--danger-color); - border-left: 4px solid var(--danger-color); -} - -.alert-warning { - background-color: var(--warning-light); - color: var(--warning-color); - border-left: 4px solid var(--warning-color); + background: var(--danger-color); + color: var(--white); } .alert-info { - background-color: var(--info-light); - color: var(--info-color); - border-left: 4px solid var(--info-color); + background: var(--info-color); + color: var(--white); +} + +.alert-warning { + background: var(--warning-color); + color: var(--white); } .alert-close { background: none; border: none; - color: currentColor; + color: inherit; cursor: pointer; + font-size: var(--font-size-lg); margin-left: auto; - padding: var(--spacing-1); - border-radius: var(--radius); - transition: var(--transition); - opacity: 0.7; } -.alert-close:hover { - opacity: 1; - background-color: rgba(0, 0, 0, 0.1); +/* Footer */ +.footer { + background-color: var(--gray-800); + color: var(--gray-300); + text-align: center; + padding: var(--spacing-6) 0; + margin-top: auto; } -/* Toasts */ -.toast { - position: fixed; - top: var(--spacing-4); - right: var(--spacing-4); - z-index: var(--z-toast); - max-width: 400px; - width: auto; - background: var(--white); - border-radius: var(--radius-lg); - box-shadow: var(--shadow-xl); - animation: slideInRight 0.3s ease-out; -} - -.toast-content { +.footer-content { display: flex; + justify-content: space-between; align-items: center; - gap: var(--spacing-3); - padding: var(--spacing-4); + flex-wrap: wrap; + gap: var(--spacing-4); } -.toast-success { - border-left: 4px solid var(--success-color); +.footer-links { + display: flex; + gap: var(--spacing-6); } -.toast-error { - border-left: 4px solid var(--danger-color); -} - -.toast-warning { - border-left: 4px solid var(--warning-color); -} - -.toast-info { - border-left: 4px solid var(--info-color); -} - -.toast-close { - background: none; - border: none; +.footer-link { color: var(--gray-400); - cursor: pointer; - margin-left: auto; - padding: var(--spacing-1); - border-radius: var(--radius); - transition: var(--transition); + font-size: var(--font-size-sm); } -.toast-close:hover { - color: var(--gray-600); - background-color: var(--gray-100); +.footer-link:hover { + color: var(--white); } -/* Dropdowns */ -.dropdown { - position: relative; - display: inline-block; +/* Animations */ +@keyframes slideInRight { + from { + opacity: 0; + transform: translateX(100%); + } + to { + opacity: 1; + transform: translateX(0); + } } -.dropdown-trigger { - background: none; - border: none; - cursor: pointer; - padding: var(--spacing-2); - border-radius: var(--radius); - transition: var(--transition); +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } } -.dropdown-trigger:hover { - background-color: var(--gray-100); +@keyframes fadeOut { + from { + opacity: 1; + } + to { + opacity: 0; + } } -.dropdown-menu { - position: absolute; - top: 100%; - right: 0; - min-width: 200px; - background: var(--white); - border: 1px solid var(--gray-200); - border-radius: var(--radius-lg); - box-shadow: var(--shadow-lg); - z-index: var(--z-dropdown); - opacity: 0; - visibility: hidden; - transform: translateY(-10px); - transition: all 0.2s ease-in-out; +@keyframes slideUp { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } } -.dropdown-menu.show { - opacity: 1; - visibility: visible; - transform: translateY(0); +.fade-in { + animation: fadeIn 0.3s ease-out; } -.dropdown-item { - display: block; - width: 100%; - padding: var(--spacing-3) var(--spacing-4); - color: var(--gray-700); - text-decoration: none; - border: none; - background: none; - text-align: left; - cursor: pointer; - transition: var(--transition); +.fade-out { + animation: fadeOut 0.3s ease-out; } -.dropdown-item:hover { - background-color: var(--gray-50); - color: var(--gray-900); - text-decoration: none; -} - -.dropdown-item:first-child { - border-top-left-radius: var(--radius-lg); - border-top-right-radius: var(--radius-lg); -} - -.dropdown-item:last-child { - border-bottom-left-radius: var(--radius-lg); - border-bottom-right-radius: var(--radius-lg); -} - -.dropdown-divider { - height: 1px; - background-color: var(--gray-200); - margin: var(--spacing-2) 0; +.slide-up { + animation: slideUp 0.3s ease-out; } /* Utilities */ @@ -878,129 +858,78 @@ a:hover { margin-top: var(--spacing-6); } -/* Animations */ -@keyframes slideInRight { - from { - opacity: 0; - transform: translateX(100%); +/* Responsive Design */ +@media (max-width: 1024px) { + .sidebar { + width: var(--sidebar-collapsed-width); } - to { - opacity: 1; + + .sidebar .menu-text, + .sidebar .brand-text { + display: none; + } + + .main-wrapper { + margin-left: var(--sidebar-collapsed-width); + } +} + +@media (max-width: 768px) { + .sidebar { + width: var(--sidebar-width); + transform: translateX(-100%); + } + + .sidebar.mobile-open { transform: translateX(0); } -} - -@keyframes fadeIn { - from { + + .sidebar-overlay.active { + display: block; + } + + .main-wrapper { + margin-left: 0; + } + + .mobile-menu-btn { + display: flex; + } + + .sidebar-toggle { + display: none; + } + + .mobile-menu-btn.active .hamburger-line:nth-child(1) { + transform: rotate(45deg) translate(5px, 5px); + } + + .mobile-menu-btn.active .hamburger-line:nth-child(2) { opacity: 0; } - to { - opacity: 1; + + .mobile-menu-btn.active .hamburger-line:nth-child(3) { + transform: rotate(-45deg) translate(7px, -6px); } -} -@keyframes fadeOut { - from { - opacity: 1; - } - to { - opacity: 0; - } -} - -@keyframes slideUp { - from { - opacity: 0; - transform: translateY(20px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -.fade-in { - animation: fadeIn 0.3s ease-out; -} - -.fade-out { - animation: fadeOut 0.3s ease-out; -} - -.slide-up { - animation: slideUp 0.3s ease-out; -} - -/* Footer */ -.footer { - background-color: var(--gray-800); - color: var(--gray-300); - text-align: center; - padding: var(--spacing-6) 0; - margin-top: auto; -} - -/* Responsive Design */ -@media (max-width: 768px) { .container { padding: 0 var(--spacing-3); } - .nav-menu { - position: fixed; - top: 0; - right: -100%; - width: 300px; - height: 100vh; - background: linear-gradient( - 135deg, - var(--primary-color), - var(--primary-hover) - ); - flex-direction: column; - padding: var(--spacing-8) var(--spacing-4); - transition: var(--transition-slow); - z-index: 1001; - } - - .nav-menu.active { - right: 0; - } - - .nav-toggle { - display: flex; - z-index: 1002; - } - - .nav-toggle.active .bar:nth-child(1) { - transform: rotate(-45deg) translate(-5px, 6px); - } - - .nav-toggle.active .bar:nth-child(2) { - opacity: 0; - } - - .nav-toggle.active .bar:nth-child(3) { - transform: rotate(45deg) translate(-5px, -6px); - } - - .modal-content { - width: 95%; - margin: var(--spacing-4); - } - - .btn { - width: 100%; - justify-content: center; - } - - .flash-messages, - .toast { + .flash-messages { left: var(--spacing-3); right: var(--spacing-3); max-width: none; } + .header-right .user-info { + display: none; + } + + .page-title { + font-size: var(--font-size-lg); + } + h1 { font-size: var(--font-size-3xl); } @@ -1027,24 +956,34 @@ a:hover { padding: var(--spacing-4); } - .modal-body, - .modal-header, - .modal-footer { - padding: var(--spacing-4); + .top-header { + padding: 0 var(--spacing-3); + } + + .page-title { + font-size: var(--font-size-base); + } + + .btn { + padding: var(--spacing-2) var(--spacing-4); + font-size: var(--font-size-xs); } } /* Print Styles */ @media print { - .navbar, + .sidebar, + .top-header, .footer, .btn, - .dropdown, - .flash-messages, - .toast { + .flash-messages { display: none !important; } + .main-wrapper { + margin-left: 0; + } + .main-content { padding: 0; } @@ -1054,3 +993,244 @@ a:hover { border: 1px solid var(--gray-300); } } + +/* Legacy Navigation Styles - Hidden by default but available for non-authenticated pages */ +.navbar { + display: none; +} + +.nav-container, +.nav-brand, +.nav-menu, +.nav-link, +.nav-toggle { + display: none; +} + +/* Show legacy navbar only for non-authenticated pages */ +body:not(.app-layout) .navbar { + display: block; + background: linear-gradient( + 135deg, + var(--primary-color), + var(--primary-hover) + ); + color: var(--white); + padding: var(--spacing-4) 0; + box-shadow: var(--shadow-md); + position: sticky; + top: 0; + z-index: var(--z-dropdown); +} + +body:not(.app-layout) .nav-container { + display: flex; + max-width: 1200px; + margin: 0 auto; + padding: 0 var(--spacing-4); + justify-content: space-between; + align-items: center; +} + +body:not(.app-layout) .nav-brand { + display: flex; + align-items: center; + gap: var(--spacing-3); + font-size: var(--font-size-xl); + font-weight: 700; + color: var(--white); +} + +body:not(.app-layout) .nav-menu { + display: flex; + gap: var(--spacing-6); + align-items: center; +} + +body:not(.app-layout) .nav-link { + display: flex; + color: var(--white); + padding: var(--spacing-2) var(--spacing-4); + border-radius: var(--radius); + transition: var(--transition); + align-items: center; + gap: var(--spacing-2); +} + +body:not(.app-layout) .nav-link:hover { + background-color: rgba(255, 255, 255, 0.1); + text-decoration: none; +} + +body:not(.app-layout) .nav-link.logout:hover { + background-color: var(--danger-color); +} + +/* Non-authenticated layout adjustments */ +body:not(.has-sidebar) { + display: flex; + flex-direction: column; +} + +body:not(.has-sidebar) .main-content { + flex: 1; + padding: var(--spacing-8) 0; +} + +/* Dropdown Styles */ +.dropdown { + position: relative; + display: inline-block; +} + +.dropdown-trigger { + background: none; + border: none; + cursor: pointer; + display: flex; + align-items: center; + gap: var(--spacing-2); + padding: var(--spacing-2); + border-radius: var(--radius); + transition: var(--transition); +} + +.dropdown-trigger:hover { + background-color: var(--gray-100); +} + +.dropdown-menu { + position: absolute; + top: 100%; + right: 0; + background: var(--white); + border: 1px solid var(--gray-200); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-lg); + min-width: 200px; + z-index: var(--z-dropdown); + opacity: 0; + visibility: hidden; + transform: translateY(-10px); + transition: var(--transition); +} + +.dropdown-menu.show { + opacity: 1; + visibility: visible; + transform: translateY(0); +} + +.dropdown-item { + display: flex; + align-items: center; + gap: var(--spacing-3); + width: 100%; + padding: var(--spacing-3) var(--spacing-4); + color: var(--gray-700); + text-decoration: none; + border: none; + background: none; + cursor: pointer; + font-size: var(--font-size-sm); + transition: var(--transition); +} + +.dropdown-item:hover { + background-color: var(--gray-50); + color: var(--gray-900); +} + +.dropdown-item:first-child { + border-top-left-radius: var(--radius-lg); + border-top-right-radius: var(--radius-lg); +} + +.dropdown-item:last-child { + border-bottom-left-radius: var(--radius-lg); + border-bottom-right-radius: var(--radius-lg); +} + +.dropdown-divider { + height: 1px; + background-color: var(--gray-200); + margin: var(--spacing-2) 0; +} + +/* Modal Styles */ +.modal { + position: fixed; + inset: 0; + z-index: var(--z-modal); + display: flex; + align-items: center; + justify-content: center; + background: rgba(0, 0, 0, 0.5); + opacity: 0; + visibility: hidden; + transition: var(--transition); +} + +.modal.show { + opacity: 1; + visibility: visible; +} + +.modal-content { + background: var(--white); + border-radius: var(--radius-xl); + box-shadow: var(--shadow-xl); + width: 90%; + max-width: 500px; + max-height: 90vh; + overflow-y: auto; + transform: scale(0.95); + transition: var(--transition); +} + +.modal.show .modal-content { + transform: scale(1); +} + +.modal-header { + padding: var(--spacing-6); + border-bottom: 1px solid var(--gray-200); + display: flex; + align-items: center; + justify-content: space-between; +} + +.modal-title { + font-size: var(--font-size-xl); + font-weight: 600; + color: var(--gray-900); + margin: 0; +} + +.modal-close { + background: none; + border: none; + font-size: var(--font-size-xl); + color: var(--gray-400); + cursor: pointer; + padding: var(--spacing-1); + border-radius: var(--radius); + transition: var(--transition); +} + +.modal-close:hover { + background-color: var(--gray-100); + color: var(--gray-600); +} + +.modal-body { + padding: var(--spacing-6); +} + +.modal-footer { + padding: var(--spacing-6); + border-top: 1px solid var(--gray-200); + display: flex; + gap: var(--spacing-3); + justify-content: flex-end; +} \ No newline at end of file diff --git a/static/js/script.js b/static/js/script.js index 7131b2e..0e2644e 100644 --- a/static/js/script.js +++ b/static/js/script.js @@ -1,88 +1,452 @@ -/** - * Main JavaScript functionality for QR Code Management System - * static/js/script.js - */ +// Enhanced JavaScript for Left Sidebar Navigation +class QRManager { + constructor() { + this.initializeApp(); + } -// Global configuration -const QRManager = { - config: { - modalCloseDelay: 300, - searchDelay: 300, - animationDuration: 300, - toastDuration: 5000, + initializeApp() { + this.initSidebar(); + this.initModals(); + this.initDropdowns(); + this.initActiveNavigation(); + this.initFlashMessages(); + } + + // Sidebar Management + initSidebar() { + const sidebar = document.getElementById('sidebar'); + const sidebarToggle = document.getElementById('sidebarToggle'); + const mobileMenuBtn = document.getElementById('mobileMenuBtn'); + const sidebarOverlay = document.getElementById('sidebarOverlay'); + + if (!sidebar) return; + + // Desktop sidebar toggle + if (sidebarToggle) { + sidebarToggle.addEventListener('click', () => { + this.toggleSidebar(); + }); + } + + // Mobile menu toggle + if (mobileMenuBtn) { + mobileMenuBtn.addEventListener('click', () => { + this.toggleMobileSidebar(); + }); + } + + // Close mobile sidebar when clicking overlay + if (sidebarOverlay) { + sidebarOverlay.addEventListener('click', () => { + this.closeMobileSidebar(); + }); + } + + // Close mobile sidebar when clicking menu items + const menuItems = sidebar.querySelectorAll('.menu-item'); + menuItems.forEach(item => { + item.addEventListener('click', () => { + if (window.innerWidth <= 768) { + this.closeMobileSidebar(); + } + }); + }); + + // Handle window resize + window.addEventListener('resize', () => { + this.handleResize(); + }); + + // Initialize sidebar state based on screen size + this.handleResize(); + } + + toggleSidebar() { + const sidebar = document.getElementById('sidebar'); + if (sidebar) { + sidebar.classList.toggle('collapsed'); + this.saveSidebarState(); + } + } + + toggleMobileSidebar() { + const sidebar = document.getElementById('sidebar'); + const overlay = document.getElementById('sidebarOverlay'); + const mobileBtn = document.getElementById('mobileMenuBtn'); + + if (sidebar && overlay && mobileBtn) { + const isOpen = sidebar.classList.contains('mobile-open'); + + if (isOpen) { + this.closeMobileSidebar(); + } else { + this.openMobileSidebar(); + } + } + } + + openMobileSidebar() { + const sidebar = document.getElementById('sidebar'); + const overlay = document.getElementById('sidebarOverlay'); + const mobileBtn = document.getElementById('mobileMenuBtn'); + + if (sidebar && overlay && mobileBtn) { + sidebar.classList.add('mobile-open'); + overlay.classList.add('active'); + mobileBtn.classList.add('active'); + document.body.style.overflow = 'hidden'; + } + } + + closeMobileSidebar() { + const sidebar = document.getElementById('sidebar'); + const overlay = document.getElementById('sidebarOverlay'); + const mobileBtn = document.getElementById('mobileMenuBtn'); + + if (sidebar && overlay && mobileBtn) { + sidebar.classList.remove('mobile-open'); + overlay.classList.remove('active'); + mobileBtn.classList.remove('active'); + document.body.style.overflow = ''; + } + } + + handleResize() { + const sidebar = document.getElementById('sidebar'); + if (!sidebar) return; + + if (window.innerWidth <= 768) { + // Mobile: ensure sidebar is hidden and mobile menu is available + this.closeMobileSidebar(); + } else if (window.innerWidth <= 1024) { + // Tablet: auto-collapse sidebar + sidebar.classList.add('collapsed'); + this.closeMobileSidebar(); + } else { + // Desktop: restore saved state + this.restoreSidebarState(); + this.closeMobileSidebar(); + } + } + + saveSidebarState() { + const sidebar = document.getElementById('sidebar'); + if (sidebar && window.innerWidth > 1024) { + const isCollapsed = sidebar.classList.contains('collapsed'); + localStorage.setItem('sidebarCollapsed', isCollapsed); + } + } + + restoreSidebarState() { + const sidebar = document.getElementById('sidebar'); + if (sidebar && window.innerWidth > 1024) { + const isCollapsed = localStorage.getItem('sidebarCollapsed') === 'true'; + sidebar.classList.toggle('collapsed', isCollapsed); + } + } + + // Active Navigation Highlighting + initActiveNavigation() { + const menuItems = document.querySelectorAll('.menu-item[href]'); + const currentPath = window.location.pathname; + + menuItems.forEach(item => { + const href = item.getAttribute('href'); + if (href === currentPath || (currentPath.startsWith(href) && href !== '/')) { + item.classList.add('active'); + } else { + item.classList.remove('active'); + } + }); + } + + // Theme Management + initTheme() { + const themeToggle = document.getElementById('themeToggle'); + if (!themeToggle) return; + + // Load saved theme + const savedTheme = localStorage.getItem('theme') || 'light'; + this.setTheme(savedTheme); + + themeToggle.addEventListener('click', () => { + const currentTheme = document.body.getAttribute('data-theme') || 'light'; + const newTheme = currentTheme === 'light' ? 'dark' : 'light'; + this.setTheme(newTheme); + localStorage.setItem('theme', newTheme); + }); + } + + setTheme(theme) { + document.body.setAttribute('data-theme', theme); + const themeToggle = document.getElementById('themeToggle'); + + if (themeToggle) { + const icon = themeToggle.querySelector('i'); + const text = themeToggle.querySelector('.menu-text'); + + if (theme === 'dark') { + icon.className = 'fas fa-sun'; + if (text) text.textContent = 'Light Mode'; + } else { + icon.className = 'fas fa-moon'; + if (text) text.textContent = 'Dark Mode'; + } + } + } + + // Modal Management + initModals() { + // Close modal when clicking outside + document.addEventListener('click', (e) => { + if (e.target.classList.contains('modal')) { + this.closeModal(e.target); + } + }); + + // Close modal with close button + document.addEventListener('click', (e) => { + if (e.target.classList.contains('modal-close') || + e.target.closest('.modal-close')) { + const modal = e.target.closest('.modal'); + if (modal) { + this.closeModal(modal); + } + } + }); + + // Close modal with Escape key + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape') { + const openModal = document.querySelector('.modal.show'); + if (openModal) { + this.closeModal(openModal); + } + } + }); + } + + showModal(modalId) { + const modal = document.getElementById(modalId); + if (modal) { + modal.classList.add('show'); + document.body.style.overflow = 'hidden'; + + // Focus first focusable element + const focusableElement = modal.querySelector('input, textarea, select, button, [tabindex]:not([tabindex="-1"])'); + if (focusableElement) { + setTimeout(() => focusableElement.focus(), 100); + } + } + } + + closeModal(modal) { + if (modal) { + modal.classList.remove('show'); + document.body.style.overflow = ''; + } + } + + // Dropdown Management + initDropdowns() { + const dropdowns = document.querySelectorAll('.dropdown'); + + dropdowns.forEach(dropdown => { + const trigger = dropdown.querySelector('.dropdown-trigger'); + const menu = dropdown.querySelector('.dropdown-menu'); + + if (trigger && menu) { + trigger.addEventListener('click', (e) => { + e.stopPropagation(); + this.toggleDropdown(dropdown); + }); + } + }); + + // Close dropdowns when clicking outside + document.addEventListener('click', () => { + this.closeAllDropdowns(); + }); + + // Close dropdowns with Escape key + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape') { + this.closeAllDropdowns(); + } + }); + } + + toggleDropdown(dropdown) { + const menu = dropdown.querySelector('.dropdown-menu'); + const isOpen = menu.classList.contains('show'); + + this.closeAllDropdowns(); + + if (!isOpen) { + menu.classList.add('show'); + } + } + + closeAllDropdowns() { + const openMenus = document.querySelectorAll('.dropdown-menu.show'); + openMenus.forEach(menu => { + menu.classList.remove('show'); + }); + } + + // Flash Messages + initFlashMessages() { + const alerts = document.querySelectorAll('.alert'); + + alerts.forEach(alert => { + // Auto-dismiss after 5 seconds + setTimeout(() => { + this.dismissAlert(alert); + }, 5000); + + // Manual dismiss + const closeBtn = alert.querySelector('.alert-close'); + if (closeBtn) { + closeBtn.addEventListener('click', () => { + this.dismissAlert(alert); + }); + } + }); + } + + dismissAlert(alert) { + alert.style.opacity = '0'; + alert.style.transform = 'translateX(100%)'; + + setTimeout(() => { + if (alert.parentNode) { + alert.parentNode.removeChild(alert); + } + }, 300); + } + + // Utility Methods + showToast(message, type = 'info', duration = 3000) { + const toast = document.createElement('div'); + toast.className = `alert alert-${type}`; + toast.innerHTML = ` + + ${message} + + `; + + const container = document.querySelector('.flash-messages') || document.body; + container.appendChild(toast); + + // Trigger animation + setTimeout(() => { + toast.classList.add('show'); + }, 10); + + // Auto dismiss + setTimeout(() => { + this.dismissAlert(toast); + }, duration); + + return toast; + } + + // Form Validation Helper + validateForm(formElement) { + const requiredFields = formElement.querySelectorAll('[required]'); + let isValid = true; + + requiredFields.forEach(field => { + if (!field.value.trim()) { + this.showFieldError(field, 'This field is required'); + isValid = false; + } else { + this.clearFieldError(field); + } + }); + + return isValid; + } + + showFieldError(field, message) { + this.clearFieldError(field); + + field.classList.add('error'); + const errorElement = document.createElement('div'); + errorElement.className = 'field-error'; + errorElement.textContent = message; + + field.parentNode.appendChild(errorElement); + } + + clearFieldError(field) { + field.classList.remove('error'); + const existingError = field.parentNode.querySelector('.field-error'); + if (existingError) { + existingError.remove(); + } + } + + // AJAX Helper + async makeRequest(url, options = {}) { + const defaultOptions = { + headers: { + 'Content-Type': 'application/json', + 'X-Requested-With': 'XMLHttpRequest' + }, + ...options + }; + + try { + const response = await fetch(url, defaultOptions); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + return await response.json(); + } catch (error) { + console.error('Request failed:', error); + this.showToast('An error occurred. Please try again.', 'error'); + throw error; + } + } +} + +// Keep existing date/time utilities +const DateTimeUtils = { + formatDate: (dateString) => { + const date = new Date(dateString); + return date.toLocaleDateString("en-US", { + year: "numeric", + month: "short", + day: "numeric", + }); }, - // Utility functions - utils: { - // Show toast notification - showToast(message, type = "info") { - const toast = document.createElement("div"); - toast.className = `toast toast-${type}`; - toast.innerHTML = ` -