diff --git a/app.py b/app.py index 9428984..3526b94 100644 --- a/app.py +++ b/app.py @@ -31,6 +31,13 @@ app.config['TEMPLATES_AUTO_RELOAD'] = os.environ.get('TEMPLATES_AUTO_RELOAD') # Initialize database db = SQLAlchemy(app) +@app.context_processor +def inject_company_name(): + """Make COMPANY_NAME available to all templates""" + return { + 'COMPANY_NAME': os.environ.get('COMPANY_NAME', 'QR Code Management System') + } + def create_performance_indexes(): """Create performance optimization indexes""" try: diff --git a/static/css/dashboard.css b/static/css/dashboard.css index a5fb2db..ac777f1 100644 --- a/static/css/dashboard.css +++ b/static/css/dashboard.css @@ -4,31 +4,163 @@ */ /* Dashboard Header */ -.dashboard-header { +.header-stats { display: flex; - justify-content: space-between; - align-items: flex-start; - margin-bottom: var(--spacing-8); - padding: var(--spacing-6); - background: var(--white); - border-radius: var(--radius-xl); - box-shadow: var(--shadow); + align-items: center; +} + +.header-stats-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 1rem; + min-width: 400px; +} + +@media (min-width: 1200px) { + .header-stats-grid { + grid-template-columns: repeat(4, 1fr); + min-width: 500px; + } +} + +.header-stat-card { + background: rgba(255, 255, 255, 0.95); + padding: 1rem; + border-radius: 0.5rem; + border: 1px solid #e2e8f0; + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1); + display: flex; + align-items: center; + gap: 0.75rem; + transition: all 0.2s ease-in-out; + backdrop-filter: blur(10px); position: relative; overflow: hidden; } -.dashboard-header::before { +.header-stat-card::before { content: ""; position: absolute; top: 0; left: 0; right: 0; - height: 4px; - background: linear-gradient( - 90deg, - var(--primary-color), - var(--primary-hover) - ); + height: 2px; +} + +.header-stat-card.primary::before { + background: linear-gradient(90deg, #2563eb, #1d4ed8); +} + +.header-stat-card.success::before { + background: linear-gradient(90deg, #10b981, #047857); +} + +.header-stat-card.danger::before { + background: linear-gradient(90deg, #ef4444, #dc2626); +} + +.header-stat-card.info::before { + background: linear-gradient(90deg, #8b5cf6, #7c3aed); +} + +.header-stat-card:hover { + transform: translateY(-1px); + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.15); +} + +.header-stat-icon { + width: 36px; + height: 36px; + border-radius: 0.375rem; + display: flex; + align-items: center; + justify-content: center; + font-size: 1rem; + color: #ffffff; + flex-shrink: 0; +} + +.header-stat-card.primary .header-stat-icon { + background: linear-gradient(135deg, #2563eb, #1d4ed8); +} + +.header-stat-card.success .header-stat-icon { + background: linear-gradient(135deg, #10b981, #047857); +} + +.header-stat-card.danger .header-stat-icon { + background: linear-gradient(135deg, #ef4444, #dc2626); +} + +.header-stat-card.info .header-stat-icon { + background: linear-gradient(135deg, #8b5cf6, #7c3aed); +} + +.header-stat-content h3 { + font-size: 1.25rem; + font-weight: 700; + color: #0f172a; + margin-bottom: 0.125rem; + line-height: 1; +} + +.header-stat-content p { + color: #64748b; + font-size: 0.75rem; + margin: 0; + line-height: 1.2; +} + +/* Update existing dashboard header to accommodate new layout */ +.dashboard-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 2rem; + background: linear-gradient(135deg, #a0aff1 0%, #ebdef8 100%); + border-radius: 0.75rem; + color: white; + margin-bottom: 2rem; + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); + flex-wrap: wrap; + gap: 2rem; +} + +/* Responsive Design for Header */ +@media (max-width: 1024px) { + .dashboard-header { + flex-direction: column; + text-align: center; + gap: 1.5rem; + } + + .header-stats-grid { + grid-template-columns: repeat(2, 1fr); + min-width: 300px; + } +} + +@media (max-width: 768px) { + .dashboard-header { + padding: 1.5rem; + } + + .header-stats-grid { + grid-template-columns: 1fr; + min-width: 250px; + } + + .header-stat-card { + padding: 0.75rem; + } + + .header-stat-content h3 { + font-size: 1.125rem; + } + + .header-stat-content p { + font-size: 0.6875rem; + } } .welcome-section h1 { @@ -892,7 +1024,7 @@ } .qr-url { - font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + font-family: "Monaco", "Menlo", "Ubuntu Mono", monospace; background: var(--gray-100); padding: var(--spacing-1) var(--spacing-2); border-radius: var(--radius-sm); @@ -927,7 +1059,8 @@ flex-wrap: wrap; } -.qr-status, .qr-project { +.qr-status, +.qr-project { display: flex; align-items: center; gap: var(--spacing-1); @@ -1088,8 +1221,12 @@ /* Animations */ @keyframes fadeIn { - from { opacity: 0; } - to { opacity: 1; } + from { + opacity: 0; + } + to { + opacity: 1; + } } @keyframes slideIn { @@ -1108,26 +1245,26 @@ .detail-grid { grid-template-columns: 1fr; } - + .qr-actions { flex-direction: column; } - + .qr-actions .btn { width: 100%; justify-content: center; } - + .bulk-actions-bar { flex-direction: column; text-align: center; } - + .bulk-actions-buttons { width: 100%; justify-content: center; } - + .modal-content { margin: var(--spacing-4); max-width: calc(100vw - 2rem); diff --git a/templates/base.html b/templates/base.html index 1d018f1..892735e 100644 --- a/templates/base.html +++ b/templates/base.html @@ -3,7 +3,7 @@ - {% block title %}QR Code Management System{% endblock %} + {% block title %}{{ COMPANY_NAME }}{% endblock %} {% if turnstile_enabled %} - + {% endif %} diff --git a/templates/base_authenticated.html b/templates/base_authenticated.html index 624bb3e..525be0d 100644 --- a/templates/base_authenticated.html +++ b/templates/base_authenticated.html @@ -3,7 +3,7 @@ - {% block title %}QR Code Management System{% endblock %} + {% block title %}{{ COMPANY_NAME }}{% endblock %} {% if session.role == 'admin' %} - + Projects @@ -65,8 +67,8 @@ Users - - Employees + + Employees @@ -76,12 +78,17 @@ Payroll - + Statistics - + System Logs @@ -137,9 +144,11 @@ -

{% block page_title %}QR Code Management System{% endblock %}

+

+ {% block page_title %}{{ COMPANY_NAME }}{% endblock %} +

- +
{{ session.full_name or 'User' }} @@ -172,9 +181,7 @@ {% endif %} {% endwith %} -
- {% block content %}{% endblock %} -
+
{% block content %}{% endblock %}
@@ -210,4 +217,4 @@ }; - \ No newline at end of file + diff --git a/templates/dashboard.html b/templates/dashboard.html index 3733f67..c6f90db 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -1,965 +1,968 @@ -{% extends "base_authenticated.html" %} -{% block title %}Dashboard - QR Code Management{% endblock %} - -{% block extra_head %} - +{% extends "base_authenticated.html" %} {% block title %}Dashboard - QR Code +Management{% endblock %} {% block extra_head %} + -{% endblock %} -{% block content %} + .modal-header h3 { + margin: 0; + font-size: var(--font-size-xl); + font-weight: 600; + color: var(--gray-900); + } + + .modal-close { + background: none; + border: none; + font-size: var(--font-size-xl); + color: var(--gray-500); + cursor: pointer; + padding: var(--spacing-2); + border-radius: var(--radius-md); + transition: var(--transition); + } + + .modal-close:hover { + background: var(--gray-100); + color: var(--gray-700); + } + + .qr-modal-body { + padding: var(--spacing-6); + } + + .qr-modal-layout { + display: grid; + grid-template-columns: 300px 1fr; + gap: var(--spacing-8); + align-items: start; + } + + .qr-modal-image { + display: flex; + flex-direction: column; + align-items: center; + gap: var(--spacing-4); + } + + .qr-modal-image img { + width: 280px; + height: 280px; + border: 3px solid var(--gray-200); + border-radius: var(--radius-lg); + background: var(--white); + padding: var(--spacing-2); + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); + } + + .qr-modal-info { + flex: 1; + min-width: 0; + } + + .qr-detail-section { + background: #f8fafc; + border: 1px solid var(--gray-200); + border-radius: var(--radius-lg); + padding: var(--spacing-6); + } + + .qr-detail-section h4 { + display: flex; + align-items: center; + gap: var(--spacing-2); + margin: 0 0 var(--spacing-6) 0; + font-size: var(--font-size-lg); + font-weight: 600; + color: var(--primary-color); + border-bottom: 2px solid var(--primary-color); + padding-bottom: var(--spacing-2); + } + + .qr-detail-section .qr-detail-item { + display: grid; + grid-template-columns: 100px 1fr; + gap: var(--spacing-3); + margin-bottom: var(--spacing-4); + align-items: start; + } + + .qr-detail-section .qr-detail-item:last-child { + margin-bottom: 0; + } + + .qr-detail-section .qr-detail-item label { + font-weight: 600; + color: var(--gray-700); + font-size: var(--font-size-sm); + padding-top: 2px; + } + + .qr-detail-section .qr-detail-item span { + color: var(--gray-900); + font-size: var(--font-size-base); + line-height: 1.5; + word-break: break-word; + } + + .qr-destination-link a { + display: inline-flex; + align-items: center; + gap: var(--spacing-2); + color: var(--primary-color); + text-decoration: none; + font-weight: 500; + padding: var(--spacing-2) var(--spacing-3); + border: 1px solid var(--primary-color); + border-radius: var(--radius-md); + transition: var(--transition); + font-size: var(--font-size-sm); + } + + .qr-destination-link a:hover { + background: var(--primary-color); + color: var(--white); + } + + .qr-modal-footer { + padding: var(--spacing-6); + border-top: 1px solid var(--gray-200); + display: flex; + gap: var(--spacing-3); + justify-content: flex-end; + flex-wrap: wrap; + background: #f8fafc; + } + + .qr-modal-footer .btn { + min-width: 140px; + } + + /* Image Lightbox Styles */ + .image-lightbox { + display: none; + position: fixed; + z-index: 2000; + left: 0; + top: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.9); + align-items: center; + justify-content: center; + animation: fadeIn 0.3s ease; + cursor: pointer; + } + + .lightbox-content { + position: relative; + max-width: 90vw; + max-height: 90vh; + text-align: center; + } + + .lightbox-image { + max-width: 100%; + max-height: 80vh; + border-radius: var(--radius-lg); + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5); + background: var(--white); + padding: var(--spacing-4); + } + + .lightbox-close { + position: absolute; + top: -50px; + right: 0; + background: rgba(255, 255, 255, 0.9); + border: none; + border-radius: 50%; + width: 40px; + height: 40px; + font-size: 1.2rem; + color: var(--gray-700); + cursor: pointer; + transition: var(--transition); + display: flex; + align-items: center; + justify-content: center; + } + + .lightbox-close:hover { + background: var(--white); + color: var(--gray-900); + } + + .lightbox-info { + color: var(--white); + margin-top: var(--spacing-4); + font-size: var(--font-size-lg); + font-weight: 500; + } + + .lightbox-hint { + color: rgba(255, 255, 255, 0.7); + margin-top: var(--spacing-2); + font-size: var(--font-size-sm); + } + + /* Make QR images clickable */ + .qr-preview-large, + .qr-preview-compact { + cursor: zoom-in; + position: relative; + } + + .qr-preview-large::after, + .qr-preview-compact::after { + content: "🔍"; + position: absolute; + top: 2px; + right: 2px; + background: rgba(0, 0, 0, 0.7); + color: white; + border-radius: 50%; + width: 18px; + height: 18px; + font-size: 10px; + display: flex; + align-items: center; + justify-content: center; + opacity: 0; + transition: var(--transition); + } + + .qr-card:hover .qr-preview-large::after, + .qr-card:hover .qr-preview-compact::after { + opacity: 1; + } + + /* Animations */ + @keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } + } + + @keyframes slideIn { + from { + opacity: 0; + transform: translateY(-50px); + } + to { + opacity: 1; + transform: translateY(0); + } + } + + /* Responsive Design */ + @media (max-width: 768px) { + .project-header { + padding: var(--spacing-3) var(--spacing-4); + } + + .project-info { + gap: var(--spacing-3); + } + + .project-icon { + width: 40px; + height: 40px; + font-size: 1rem; + } + + .project-meta { + flex-direction: column; + align-items: flex-end; + gap: var(--spacing-2); + } + + .project-qr-grid { + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: var(--spacing-3); + padding: var(--spacing-4); + } + + .qr-card-compact { + flex-direction: column; + text-align: center; + gap: var(--spacing-2); + padding: var(--spacing-2); + } + + .qr-info-compact { + width: 100%; + } + + .qr-details-row { + flex-direction: column; + gap: var(--spacing-1); + align-items: center; + } + + .qr-status-compact { + margin: 0; + } + + .qr-actions-compact { + justify-content: center; + } + + .qr-card-layout { + flex-direction: column; + text-align: center; + align-items: center; + } + + .qr-preview-large { + width: 70px; + height: 70px; + } + + .qr-details { + align-items: center; + width: 100%; + } + + .qr-detail-item { + justify-content: center; + } + + .unassigned-header { + padding: var(--spacing-3) var(--spacing-4); + flex-direction: column; + align-items: flex-start; + gap: var(--spacing-2); + } + + .qr-modal-content { + width: 95vw; + max-height: 95vh; + } + + .qr-modal-layout { + grid-template-columns: 1fr; + gap: var(--spacing-6); + } + + .qr-modal-image { + order: 1; + } + + .qr-modal-info { + order: 2; + } + + .qr-modal-image img { + width: 240px; + height: 240px; + } + + .qr-modal-footer { + flex-direction: column; + } + + .qr-modal-footer .btn { + width: 100%; + min-width: unset; + } + } + + @media (max-width: 480px) { + .project-description { + white-space: normal; + overflow: visible; + text-overflow: unset; + } + + .project-qr-grid { + grid-template-columns: 1fr; + gap: var(--spacing-2); + padding: var(--spacing-3); + } + + .modal-header { + padding: var(--spacing-4); + } + + .qr-modal-body { + padding: var(--spacing-4); + } + + .qr-detail-section { + padding: var(--spacing-4); + } + + .qr-modal-image img { + width: 200px; + height: 200px; + } + + .qr-modal-footer { + padding: var(--spacing-4); + } + } + +{% endblock %} {% block content %}
@@ -968,87 +971,87 @@

Manage your QR codes organized by projects

-
- - - Create QR Code - - {% if session.role == 'admin' %} - - - Manage Users - - {% endif %} -
-
- - {% if session.role == 'admin' %} -
-
-
-
- + + {% if session.role == 'admin' %} +
+
+
+
+ +
+
+

{{ qr_codes|length }}

+

Total QR Codes

+
-
-

{{ qr_codes|length }}

-

Total QR Codes

+
+
+ +
+
+

+ {{ qr_codes|selectattr('active_status', 'equalto', + True)|list|length }} +

+

Active QR Codes

+
-
-
-
- +
+
+ +
+
+

+ {{ qr_codes|selectattr('active_status', 'equalto', + False)|list|length }} +

+

Inactive QR Codes

+
-
-

{{ qr_codes|selectattr('active_status', 'equalto', True)|list|length }}

-

Active QR Codes

-
-
-
-
- -
-
-

{{ qr_codes|selectattr('active_status', 'equalto', False)|list|length }}

-

Inactive QR Codes

-
-
-
-
- -
-
-

{{ projects|length }}

-

Total Projects

+
+
+ +
+
+

{{ projects|length if projects else 0 }}

+

Total Projects

+
+ {% endif %}
- {% endif %} - {% if projects %}
- {% for project in projects %} - {% set project_qr_codes = qr_codes|selectattr('project_id', 'equalto', project.id)|list %} - + {% for project in projects %} {% set project_qr_codes = + qr_codes|selectattr('project_id', 'equalto', project.id)|list %} +
-
- +
+

{{ project.name }}

@@ -1057,14 +1060,19 @@ {% endif %}
- +
- {{ project_qr_codes|length }} QR Code{{ 's' if project_qr_codes|length != 1 else '' }} + {{ project_qr_codes|length }} QR Code{{ 's' if + project_qr_codes|length != 1 else '' }}
- + {{ 'Active' if project.active_status else 'Inactive' }}
@@ -1073,66 +1081,91 @@
- +
{% if project_qr_codes %}
{% for qr in project_qr_codes %} -
- +
-
- QR Code for {{ qr.name }} +
+ QR Code for {{ qr.name }}
- +

{{ qr.name }}

{{ qr.location }}
- + {% if qr.location_address %}
{{ qr.location_address }}
- {% endif %} - - {% if qr.qr_url %} + {% endif %} {% if qr.qr_url %}
{{ qr.qr_url }}
{% endif %} - - - + + + {{ 'Active' if qr.active_status else 'Inactive' }}
- - - + + - + {% if session.role == 'admin' %} - {% endif %}
@@ -1159,33 +1192,44 @@ {% endif %} - {% set unassigned_qr_codes = qr_codes|selectattr('project_id', 'equalto', None)|list %} - {% if unassigned_qr_codes %} + {% set unassigned_qr_codes = qr_codes|selectattr('project_id', 'equalto', + None)|list %} {% if unassigned_qr_codes %}

Unassigned QR Codes

- {{ unassigned_qr_codes|length }} QR Code{{ 's' if unassigned_qr_codes|length != 1 else '' }} + {{ unassigned_qr_codes|length }} QR Code{{ 's' if + unassigned_qr_codes|length != 1 else '' }}
- +
{% for qr in unassigned_qr_codes %} -
- +
-
- QR Code for {{ qr.name }} +
+ QR Code for {{ qr.name }}
- +

{{ qr.name }}

@@ -1193,42 +1237,62 @@ {{ qr.location }}
- + {% if qr.location_address %}
{{ qr.location_address }}
- {% endif %} - - {% if qr.qr_url %} + {% endif %} {% if qr.qr_url %}
- {{ qr.qr_url[:50] }}{% if qr.qr_url|length > 50 %}...{% endif %} + {{ qr.qr_url[:50] }}{% if qr.qr_url|length > 50 %}...{% endif + %}
{% endif %}
- +
- - + + {{ 'Active' if qr.active_status else 'Inactive' }}
- +
- - - + + - + {% if session.role == 'admin' %} - {% endif %}
@@ -1246,8 +1310,13 @@

Welcome to Your QR Code Dashboard

-

Get started by creating your first project and QR codes to organize your digital assets effectively.

-
+

+ Get started by creating your first project and QR codes to organize your + digital assets effectively. +

+ @@ -1287,33 +1356,33 @@