Files
LT_QR_Codes_Management/templates/dashboard.html
T
2026-09-11 22:42:45 -04:00

2071 lines
54 KiB
HTML

{% extends "base_authenticated.html" %} {% block title %}Dashboard - QR Code
Management{% endblock %} {% block extra_head %}
<link
rel="stylesheet"
href="{{ url_for('static', filename='css/dashboard.css') }}"
/>
<style>
/* Project-Centric Dashboard Styles */
.projects-dashboard {
background: var(--white);
border: 1px solid var(--gray-200);
border-radius: var(--radius-xl);
overflow: hidden;
}
.project-list-item {
border-bottom: 1px solid var(--gray-200);
transition: var(--transition);
}
.project-list-item:last-child {
border-bottom: none;
}
.project-header {
padding: var(--spacing-4) var(--spacing-6);
cursor: pointer;
display: flex;
align-items: center;
justify-content: space-between;
background: var(--white);
transition: var(--transition);
position: relative;
}
.project-header:hover {
background: #f8fafc;
}
.project-header.expanded {
background: linear-gradient(135deg, #f0f9ff, #e0f2fe);
border-left: 4px solid var(--primary-color);
}
.project-info {
display: flex;
align-items: center;
gap: var(--spacing-4);
flex: 1;
}
.project-icon {
width: 48px;
height: 48px;
background: linear-gradient(135deg, var(--primary-color), #1d4ed8);
border-radius: var(--radius-lg);
display: flex;
align-items: center;
justify-content: center;
color: var(--white);
font-size: 1.25rem;
flex-shrink: 0;
}
.project-icon.inactive {
background: linear-gradient(135deg, #6b7280, #4b5563);
}
.project-details {
flex: 1;
min-width: 0;
}
.project-name {
font-size: var(--font-size-lg);
font-weight: 600;
color: var(--gray-900);
margin: 0 0 var(--spacing-1) 0;
line-height: 1.4;
}
.project-description {
color: var(--gray-600);
font-size: var(--font-size-sm);
line-height: 1.4;
margin: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.project-meta {
display: flex;
align-items: center;
gap: var(--spacing-4);
flex-shrink: 0;
}
.project-stats {
display: flex;
align-items: center;
gap: var(--spacing-3);
font-size: var(--font-size-sm);
color: var(--gray-600);
}
.project-qr-count {
display: flex;
align-items: center;
gap: var(--spacing-1);
font-weight: 500;
color: var(--primary-color);
}
.project-status {
padding: 0.25rem 0.75rem;
border-radius: var(--radius-full);
font-size: var(--font-size-xs);
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.project-status.active {
background: rgba(16, 185, 129, 0.1);
color: #047857;
border: 1px solid rgba(16, 185, 129, 0.2);
}
.project-status.inactive {
background: rgba(107, 114, 128, 0.1);
color: #4b5563;
border: 1px solid rgba(107, 114, 128, 0.2);
}
.project-toggle {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border-radius: var(--radius-md);
background: transparent;
border: none;
color: var(--gray-500);
cursor: pointer;
transition: var(--transition);
}
.project-toggle:hover {
background: var(--gray-100);
color: var(--primary-color);
}
.project-toggle i {
transition: transform 0.3s ease;
}
.project-toggle.expanded i {
transform: rotate(180deg);
}
.view-all-qr-btn {
display: inline-flex;
align-items: center;
gap: 0.375rem;
padding: 0.375rem 0.75rem;
font-size: 0.75rem;
font-weight: 500;
white-space: nowrap;
height: 20px;
}
.view-all-qr-btn i {
font-size: 0.7rem;
}
.view-all-qr-btn:hover {
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(37, 99, 235, 0.2);
}
.project-qr-codes {
max-height: 0;
overflow: hidden;
transition: max-height 0.4s ease-in-out;
background: #fafbfc;
}
.project-qr-codes.expanded {
max-height: 2000px;
border-top: 1px solid var(--gray-200);
}
.project-qr-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: var(--spacing-4);
padding: var(--spacing-6);
}
/* QR Card Base Styles */
.qr-card {
background: var(--white);
border: 1px solid var(--gray-200);
border-radius: var(--radius-lg);
padding: var(--spacing-4);
transition: var(--transition);
cursor: pointer;
position: relative;
}
.qr-card:hover {
border-color: var(--primary-color);
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.1);
transform: translateY(-2px);
}
.qr-card.inactive {
opacity: 0.7;
background: linear-gradient(135deg, var(--white), #f8fafc);
}
/* Compact QR Card Layout for Unassigned Cards */
.qr-card-compact {
display: flex;
align-items: center;
gap: var(--spacing-3);
width: 100%;
padding: var(--spacing-3);
min-height: 80px;
}
.qr-preview-compact {
width: 60px;
height: 60px;
flex-shrink: 0;
border-radius: var(--radius-md);
overflow: hidden;
border: 2px solid var(--gray-200);
transition: var(--transition);
}
.qr-preview-compact img {
width: 100%;
height: 100%;
object-fit: cover;
}
.qr-card:hover .qr-preview-compact {
border-color: var(--primary-color);
}
.qr-info-compact {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: var(--spacing-1);
}
.qr-name-compact {
font-size: var(--font-size-sm);
font-weight: 600;
color: var(--gray-900);
margin: 0;
line-height: 1.3;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.qr-details-row {
display: flex;
gap: var(--spacing-3);
flex-wrap: wrap;
align-items: center;
}
.qr-url-short {
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.qr-status-compact {
flex-shrink: 0;
}
.qr-actions-compact {
display: flex;
gap: var(--spacing-1);
flex-shrink: 0;
}
/* Standard QR Card Layout for Project Cards */
.qr-card-layout {
display: flex;
gap: var(--spacing-3);
margin-bottom: var(--spacing-3);
align-items: flex-start;
}
.qr-preview-large {
width: 80px;
height: 80px;
flex-shrink: 0;
border-radius: var(--radius-md);
overflow: hidden;
border: 2px solid var(--gray-200);
transition: var(--transition);
}
.qr-preview-large img {
width: 100%;
height: 100%;
object-fit: cover;
}
.qr-card:hover .qr-preview-large {
border-color: var(--primary-color);
}
.qr-details {
flex: 1;
display: flex;
flex-direction: column;
gap: var(--spacing-1);
min-width: 0;
}
.qr-name {
font-size: var(--font-size-sm);
font-weight: 600;
color: var(--gray-900);
margin: 0 0 var(--spacing-1) 0;
line-height: 1.3;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.qr-detail-item {
display: flex;
align-items: center;
gap: 0.4rem;
font-size: var(--font-size-xs);
color: var(--gray-600);
min-width: 0;
}
.qr-detail-item i {
font-size: 0.65rem;
width: auto;
flex-shrink: 0;
color: var(--gray-400);
}
.qr-detail-item span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
}
.qr-status-badge {
display: inline-flex;
align-items: center;
gap: var(--spacing-1);
padding: 0.2rem 0.4rem;
border-radius: var(--radius-full);
font-size: 0.65rem;
font-weight: 500;
margin-top: var(--spacing-1);
}
.qr-status-badge.active {
background: rgba(16, 185, 129, 0.1);
color: #047857;
}
.qr-status-badge.inactive {
background: rgba(239, 68, 68, 0.1);
color: #dc2626;
}
.qr-card-actions {
display: flex;
gap: var(--spacing-1);
justify-content: flex-start;
padding-top: var(--spacing-2);
border-top: 1px solid var(--gray-200);
margin-top: var(--spacing-2);
}
.qr-action-btn {
width: 28px;
height: 28px;
border-radius: var(--radius-md);
border: none;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: var(--transition);
font-size: 0.75rem;
text-decoration: none;
}
.qr-action-btn.edit {
background: rgba(37, 99, 235, 0.1);
color: var(--primary-color);
}
.qr-action-btn.edit:hover {
background: var(--primary-color);
color: var(--white);
}
.qr-action-btn.download {
background: rgba(16, 185, 129, 0.1);
color: #059669;
}
.qr-action-btn.download:hover {
background: #059669;
color: var(--white);
}
.qr-action-btn.toggle {
background: rgba(245, 158, 11, 0.1);
color: #d97706;
}
.qr-action-btn.toggle:hover {
background: #d97706;
color: var(--white);
}
/* Empty States */
.empty-project-qr {
text-align: center;
padding: var(--spacing-8) var(--spacing-4);
color: var(--gray-600);
}
.empty-project-qr .empty-icon {
width: 64px;
height: 64px;
margin: 0 auto var(--spacing-4);
background: var(--gray-100);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
color: var(--gray-400);
}
.empty-project-qr h4 {
font-size: var(--font-size-lg);
font-weight: 600;
color: var(--gray-900);
margin-bottom: var(--spacing-2);
}
.empty-project-qr p {
font-size: var(--font-size-sm);
margin-bottom: var(--spacing-4);
}
.dashboard-empty {
text-align: center;
padding: 4rem 2rem;
color: var(--gray-600);
}
.dashboard-empty .empty-icon {
width: 120px;
height: 120px;
margin: 0 auto 2rem;
background: var(--gray-100);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 3rem;
color: var(--gray-400);
}
.dashboard-empty h3 {
font-size: 2rem;
font-weight: 600;
color: var(--gray-900);
margin-bottom: 1rem;
}
.dashboard-empty p {
font-size: 1.125rem;
margin-bottom: 2rem;
max-width: 500px;
margin-left: auto;
margin-right: auto;
}
/* Unassigned QR Section */
.unassigned-qr-section {
background: var(--white);
border: 1px solid var(--gray-200);
border-radius: var(--radius-xl);
margin-top: var(--spacing-6);
overflow: hidden;
}
.unassigned-header {
padding: var(--spacing-4) var(--spacing-6);
background: linear-gradient(135deg, #fef3c7, #fde68a);
border-bottom: 1px solid #f59e0b;
display: flex;
align-items: center;
justify-content: space-between;
}
.unassigned-title {
display: flex;
align-items: center;
gap: var(--spacing-3);
font-size: var(--font-size-lg);
font-weight: 600;
color: #92400e;
margin: 0;
}
.unassigned-count {
background: rgba(245, 158, 11, 0.2);
color: #92400e;
padding: 0.25rem 0.75rem;
border-radius: var(--radius-full);
font-size: var(--font-size-sm);
font-weight: 600;
}
/* Modal Styles */
.modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
align-items: center;
justify-content: center;
animation: fadeIn 0.3s ease;
}
.modal-content {
background: var(--white);
border-radius: var(--radius-xl);
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
max-width: 90vw;
max-height: 90vh;
overflow: auto;
animation: slideIn 0.3s ease;
}
.qr-modal-content {
max-width: 900px;
width: 90vw;
max-height: 80vh;
}
.modal-header {
padding: var(--spacing-6);
border-bottom: 1px solid var(--gray-200);
display: flex;
align-items: center;
justify-content: space-between;
}
.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);
}
}
/* ============================================
Search Results Banner
============================================ */
.search-results-banner {
background: linear-gradient(135deg, #dbeafe, #bfdbfe);
border: 1px solid #3b82f6;
border-radius: var(--radius-lg);
padding: var(--spacing-4) var(--spacing-6);
margin-bottom: var(--spacing-6);
animation: slideIn 0.3s ease;
}
.search-results-content {
display: flex;
align-items: center;
gap: var(--spacing-3);
color: #1e40af;
flex-wrap: wrap;
}
.search-results-content i {
font-size: 1.2rem;
}
.search-results-content span {
flex: 1;
font-weight: 500;
min-width: 250px;
}
.search-results-content .btn-sm {
padding: 0.5rem 1rem;
font-size: var(--font-size-sm);
height: auto;
white-space: nowrap;
}
@media (max-width: 768px) {
.search-results-banner {
padding: var(--spacing-3) var(--spacing-4);
}
.search-results-content {
flex-direction: column;
align-items: flex-start;
}
.search-results-content .btn-sm {
width: 100%;
}
}
/* ============================================
Search Results Categorization Styles
============================================ */
/* Overall Search Results Header */
.search-results-header {
background: linear-gradient(135deg, #f0f9ff, #dbeafe);
border: 1px solid #3b82f6;
border-radius: var(--radius-xl);
padding: var(--spacing-6);
margin-bottom: var(--spacing-6);
}
.search-results-title {
font-size: var(--font-size-2xl);
font-weight: 700;
color: #1e40af;
margin: 0 0 var(--spacing-2) 0;
display: flex;
align-items: center;
gap: var(--spacing-3);
}
.search-results-summary {
display: flex;
align-items: center;
gap: var(--spacing-4);
flex-wrap: wrap;
color: #1e40af;
font-size: var(--font-size-base);
}
.result-count {
font-weight: 600;
font-size: var(--font-size-lg);
}
.project-count {
color: #3b82f6;
font-weight: 500;
}
/* Project Section in Search Results */
.search-results-project-section {
background: var(--white);
border: 1px solid var(--gray-200);
border-radius: var(--radius-xl);
padding: var(--spacing-5);
margin-bottom: var(--spacing-5);
transition: var(--transition);
}
.search-results-project-section:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
border-color: var(--primary-color);
}
.search-results-project-section.unassigned {
border-color: #f59e0b;
background: linear-gradient(135deg, #fffbeb, #fef3c7);
}
.search-project-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-4);
padding-bottom: var(--spacing-3);
border-bottom: 2px solid var(--gray-200);
}
.search-project-info {
display: flex;
align-items: center;
gap: var(--spacing-3);
}
.search-project-icon {
width: 48px;
height: 48px;
background: linear-gradient(135deg, var(--primary-color), #1d4ed8);
border-radius: var(--radius-lg);
display: flex;
align-items: center;
justify-content: center;
color: var(--white);
font-size: 1.25rem;
flex-shrink: 0;
}
.search-project-icon.unassigned-icon {
background: linear-gradient(135deg, #f59e0b, #d97706);
}
.search-project-name {
font-size: var(--font-size-lg);
font-weight: 700;
color: var(--gray-900);
margin: 0;
}
.search-project-description {
font-size: var(--font-size-sm);
color: var(--gray-600);
margin: 0.25rem 0 0 0;
}
.search-project-count {
background: var(--primary-color);
color: var(--white);
padding: 0.5rem 1rem;
border-radius: var(--radius-full);
font-size: var(--font-size-sm);
font-weight: 600;
white-space: nowrap;
}
.search-results-project-section.unassigned .search-project-count {
background: #f59e0b;
}
/* Responsive Design */
@media (max-width: 768px) {
.search-project-header {
flex-direction: column;
align-items: flex-start;
gap: var(--spacing-3);
}
.search-project-count {
align-self: stretch;
text-align: center;
}
.search-results-summary {
flex-direction: column;
align-items: flex-start;
gap: var(--spacing-2);
}
}
</style>
{% endblock %} {% block content %}
<div class="dashboard-container">
<!-- Dashboard Header -->
<div class="dashboard-header">
<div class="welcome-section">
<h1>Welcome back, {{ session.full_name }}!</h1>
<p>Manage your QR codes organized by projects</p>
<div class="user-info-badge">
<div class="role-indicator {{ session.role }}">
<i
class="fas {{ 'fa-crown' if session.role == 'admin' else 'fa-user' }}"
></i>
{{ session.role.title() }}
</div>
{% if session.last_login_date %}
<div class="last-login">
<i class="fas fa-clock"></i>
Last login: {{ session.last_login_date.strftime('%b %d, %Y at %H:%M')
}}
</div>
{% endif %}
</div>
</div>
<!-- QR Statistics in Header -->
{% if session.role == 'admin' %}
<div class="header-stats">
<div class="header-stats-grid">
<div class="header-stat-card primary">
<div class="header-stat-icon">
<i class="fas fa-qrcode"></i>
</div>
<div class="header-stat-content">
<h3>{{ qr_codes|length }}</h3>
<p>Total QR Codes</p>
</div>
</div>
<div class="header-stat-card success">
<div class="header-stat-icon">
<i class="fas fa-check-circle"></i>
</div>
<div class="header-stat-content">
<h3>
{{ qr_codes|selectattr('active_status', 'equalto',
True)|list|length }}
</h3>
<p>Active QR Codes</p>
</div>
</div>
<div class="header-stat-card danger">
<div class="header-stat-icon">
<i class="fas fa-pause-circle"></i>
</div>
<div class="header-stat-content">
<h3>
{{ qr_codes|selectattr('active_status', 'equalto',
False)|list|length }}
</h3>
<p>Inactive QR Codes</p>
</div>
</div>
<div class="header-stat-card info">
<div class="header-stat-icon">
<i class="fas fa-folder"></i>
</div>
<div class="header-stat-content">
<h3>{{ projects|length if projects else 0 }}</h3>
<p>Total Projects</p>
</div>
</div>
</div>
</div>
{% endif %}
</div>
<!-- QR Code Search Section -->
<div class="search-section">
<form action="{{ url_for('dashboard.dashboard') }}" method="GET" class="search-form">
<div class="search-inputs">
<div class="search-input-group">
<label for="search_name">
<i class="fas fa-search"></i> Search by Name
</label>
<input
type="text"
id="search_name"
name="search_name"
value="{{ search_name }}"
placeholder="Enter QR code name..."
class="search-input"
/>
</div>
<div class="search-input-group">
<label for="search_status">
<i class="fas fa-filter"></i> Filter by Status
</label>
<select id="search_status" name="search_status" class="search-select">
<option value="" {% if not search_status %}selected{% endif %}>All Statuses</option>
<option value="active" {% if search_status == 'active' %}selected{% endif %}>Active</option>
<option value="inactive" {% if search_status == 'inactive' %}selected{% endif %}>Inactive</option>
</select>
</div>
<div class="search-actions">
<button type="submit" class="btn btn-primary">
<i class="fas fa-search"></i>
Search
</button>
</div>
</div>
</form>
</div>
<!-- Search Results Banner and Section -->
{% if search_name or search_status %}
<div class="search-results-banner">
<div class="search-results-content">
<i class="fas fa-filter"></i>
<span>
Showing {{ qr_codes|length }} results
{% if search_name %}matching "{{ search_name }}"{% endif %}
{% if search_status %}with status: {{ search_status }}{% endif %}
</span>
<a href="{{ url_for('dashboard.dashboard') }}" class="btn btn-sm btn-secondary">
<i class="fas fa-times"></i>
Clear Filters
</a>
</div>
</div>
<!-- Search Results Display - Categorized by Project -->
{% if qr_codes %}
<!-- Overall Search Results Header -->
<div class="search-results-header">
<h2 class="search-results-title">
<i class="fas fa-search"></i>
Search Results
</h2>
<div class="search-results-summary">
<span class="result-count">
{{ qr_codes|length }} QR code{{ 's' if qr_codes|length != 1 else '' }} found
</span>
{% set projects_with_results = qr_codes|map(attribute='project_id')|select|unique|list|length %}
{% if projects_with_results > 0 %}
<span class="project-count">across {{ projects_with_results }} project{{ 's' if projects_with_results != 1 else '' }}</span>
{% endif %}
</div>
</div>
<!-- Group QR Codes by Project -->
{% for project in projects %}
{% set project_search_results = qr_codes|selectattr('project_id', 'equalto', project.id)|list %}
{% if project_search_results %}
<div class="search-results-project-section">
<div class="search-project-header">
<div class="search-project-info">
<div class="search-project-icon">
<i class="fas fa-folder-open"></i>
</div>
<div>
<h3 class="search-project-name">{{ project.name }}</h3>
{% if project.description %}
<p class="search-project-description">{{ project.description }}</p>
{% endif %}
</div>
</div>
<span class="search-project-count">
{{ project_search_results|length }} QR code{{ 's' if project_search_results|length != 1 else '' }}
</span>
</div>
<div class="project-qr-grid">
{% for qr in project_search_results %}
<div
class="qr-card {{ 'inactive' if not qr.active_status }}"
data-qr-id="{{ qr.id }}"
data-qr-name="{{ qr.name }}"
data-qr-location="{{ qr.location }}"
data-qr-address="{{ qr.location_address }}"
data-qr-event="{{ qr.location_event }}"
data-qr-url="{{ qr.qr_url if qr.qr_url else '' }}"
onclick="openQRModalFromData(this)"
>
<div class="qr-card-layout">
<div
class="qr-preview-large"
onclick="event.stopPropagation(); openImageLightbox(this, '{{ qr.name }}')"
>
<img
src="data:image/png;base64,{{ qr.qr_code_image }}"
alt="QR Code for {{ qr.name }}"
loading="lazy"
/>
</div>
<div class="qr-details">
<h4 class="qr-name">{{ qr.name }}</h4>
<div class="qr-detail-item">
<i class="fas fa-map-marker-alt"></i>
<span>{{ qr.location }}</span>
</div>
{% if qr.location_address %}
<div class="qr-detail-item">
<i class="fas fa-home"></i>
<span>{{ qr.location_address }}</span>
</div>
{% endif %}
{% if qr.location_event %}
<div class="qr-detail-item">
<i class="fas fa-calendar"></i>
<span>{{ qr.location_event }}</span>
</div>
{% endif %}
<div class="qr-detail-item qr-checkins-count">
<i class="fas fa-users"></i>
<span>{{ get_qr_code_checkin_count(qr.id) }} check-ins</span>
</div>
{% if qr.qr_url %}
<div class="qr-detail-item">
<i class="fas fa-link"></i>
<span class="qr-url-short">{{ qr.qr_url[:40] }}{% if qr.qr_url|length > 40 %}...{% endif %}</span>
</div>
{% endif %}
<span
class="qr-status-badge {{ 'active' if qr.active_status else 'inactive' }}"
>
<i
class="fas {{ 'fa-check-circle' if qr.active_status else 'fa-pause-circle' }}"
></i>
{{ 'Active' if qr.active_status else 'Inactive' }}
</span>
</div>
</div>
<div class="qr-card-actions">
<button
class="qr-action-btn copy"
onclick="event.stopPropagation(); copyQRUrl({{ qr.id }})"
title="Copy QR Code URL"
>
<i class="fas fa-copy"></i>
</button>
<button
class="qr-action-btn link"
onclick="event.stopPropagation(); openQRLink({{ qr.id }})"
title="Open QR Code Link"
>
<i class="fas fa-external-link-alt"></i>
</button>
<button
class="qr-action-btn download"
onclick="event.stopPropagation(); downloadQRFromCard(this)"
title="Download QR Code"
>
<i class="fas fa-download"></i>
</button>
<a
href="{{ url_for('qr_codes.edit_qr_code', qr_id=qr.id) }}"
class="qr-action-btn edit"
onclick="event.stopPropagation()"
title="Edit QR Code"
>
<i class="fas fa-edit"></i>
</a>
{% if session.role == 'admin' %}
<button
class="qr-action-btn toggle"
onclick="event.stopPropagation(); toggleQRCodeStatus({{ qr.id }})"
title="{{ 'Deactivate' if qr.active_status else 'Activate' }} QR Code"
>
<i
class="fas {{ 'fa-pause' if qr.active_status else 'fa-play' }}"
></i>
</button>
{% endif %}
</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}
{% endfor %}
<!-- Unassigned QR Codes in Search Results -->
{% set unassigned_search_results = qr_codes|selectattr('project_id', 'equalto', None)|list %}
{% if unassigned_search_results %}
<div class="search-results-project-section unassigned">
<div class="search-project-header">
<div class="search-project-info">
<div class="search-project-icon unassigned-icon">
<i class="fas fa-exclamation-triangle"></i>
</div>
<div>
<h3 class="search-project-name">Unassigned QR Codes</h3>
<p class="search-project-description">QR codes not assigned to any project</p>
</div>
</div>
<span class="search-project-count">
{{ unassigned_search_results|length }} QR code{{ 's' if unassigned_search_results|length != 1 else '' }}
</span>
</div>
<div class="project-qr-grid">
{% for qr in unassigned_search_results %}
<div
class="qr-card {{ 'inactive' if not qr.active_status }}"
data-qr-id="{{ qr.id }}"
data-qr-name="{{ qr.name }}"
data-qr-location="{{ qr.location }}"
data-qr-address="{{ qr.location_address }}"
data-qr-event="{{ qr.location_event }}"
data-qr-url="{{ qr.qr_url if qr.qr_url else '' }}"
onclick="openQRModalFromData(this)"
>
<div class="qr-card-layout">
<div
class="qr-preview-large"
onclick="event.stopPropagation(); openImageLightbox(this, '{{ qr.name }}')"
>
<img
src="data:image/png;base64,{{ qr.qr_code_image }}"
alt="QR Code for {{ qr.name }}"
loading="lazy"
/>
</div>
<div class="qr-details">
<h4 class="qr-name">{{ qr.name }}</h4>
<div class="qr-detail-item">
<i class="fas fa-map-marker-alt"></i>
<span>{{ qr.location }}</span>
</div>
{% if qr.location_address %}
<div class="qr-detail-item">
<i class="fas fa-home"></i>
<span>{{ qr.location_address }}</span>
</div>
{% endif %}
{% if qr.location_event %}
<div class="qr-detail-item">
<i class="fas fa-calendar"></i>
<span>{{ qr.location_event }}</span>
</div>
{% endif %}
<div class="qr-detail-item qr-checkins-count">
<i class="fas fa-users"></i>
<span>{{ get_qr_code_checkin_count(qr.id) }} check-ins</span>
</div>
{% if qr.qr_url %}
<div class="qr-detail-item">
<i class="fas fa-link"></i>
<span class="qr-url-short">{{ qr.qr_url[:40] }}{% if qr.qr_url|length > 40 %}...{% endif %}</span>
</div>
{% endif %}
<span
class="qr-status-badge {{ 'active' if qr.active_status else 'inactive' }}"
>
<i
class="fas {{ 'fa-check-circle' if qr.active_status else 'fa-pause-circle' }}"
></i>
{{ 'Active' if qr.active_status else 'Inactive' }}
</span>
</div>
</div>
<div class="qr-card-actions">
<button
class="qr-action-btn copy"
onclick="event.stopPropagation(); copyQRUrl({{ qr.id }})"
title="Copy QR Code URL"
>
<i class="fas fa-copy"></i>
</button>
<button
class="qr-action-btn link"
onclick="event.stopPropagation(); openQRLink({{ qr.id }})"
title="Open QR Code Link"
>
<i class="fas fa-external-link-alt"></i>
</button>
<button
class="qr-action-btn download"
onclick="event.stopPropagation(); downloadQRFromCard(this)"
title="Download QR Code"
>
<i class="fas fa-download"></i>
</button>
<a
href="{{ url_for('qr_codes.edit_qr_code', qr_id=qr.id) }}"
class="qr-action-btn edit"
onclick="event.stopPropagation()"
title="Edit QR Code"
>
<i class="fas fa-edit"></i>
</a>
{% if session.role == 'admin' %}
<button
class="qr-action-btn toggle"
onclick="event.stopPropagation(); toggleQRCodeStatus({{ qr.id }})"
title="{{ 'Deactivate' if qr.active_status else 'Activate' }} QR Code"
>
<i
class="fas {{ 'fa-pause' if qr.active_status else 'fa-play' }}"
></i>
</button>
{% endif %}
</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}
{% else %}
<!-- No Search Results -->
<div class="dashboard-empty">
<div class="empty-icon">
<i class="fas fa-search"></i>
</div>
<h3>No QR Codes Found</h3>
<p>No QR codes match your search criteria. Try adjusting your filters.</p>
<a href="{{ url_for('dashboard.dashboard') }}" class="btn btn-primary">
<i class="fas fa-times"></i>
Clear Filters
</a>
</div>
{% endif %}
{% else %}
<div class="dashboard-projects-wrapper">
{% if projects %}
<div class="projects-dashboard">
{% for project in projects %} {% set project_qr_codes =
qr_codes|selectattr('project_id', 'equalto', project.id)|list %}
<div class="project-list-item">
<!-- Project Header -->
<div class="project-header" onclick="toggleProject({{ project.id }})">
<div class="project-info">
<div
class="project-icon {{ 'inactive' if not project.active_status }}"
>
<i
class="fas fa-folder{{ '-open' if project.active_status else '' }}"
></i>
</div>
<div class="project-details">
<h3 class="project-name">{{ project.name }}</h3>
{% if project.description %}
<p class="project-description">{{ project.description }}</p>
{% endif %}
</div>
</div>
<div class="project-meta">
<div class="project-stats">
<div class="project-qr-count">
<i class="fas fa-qrcode"></i>
<span
>{{ project_qr_codes|length }} QR Code{{ 's' if
project_qr_codes|length != 1 else '' }}</span
>
</div>
<span
class="project-status {{ 'active' if project.active_status else 'inactive' }}"
>
{{ 'Active' if project.active_status else 'Inactive' }}
</span>
</div>
<button class="project-toggle" id="toggle-{{ project.id }}">
<i class="fas fa-chevron-down"></i>
</button>
</div>
</div>
<!-- Project QR Codes -->
<div class="project-qr-codes" id="project-qr-{{ project.id }}">
{% if project_qr_codes %}
<div class="project-qr-grid">
{% for qr in project_qr_codes[:12] %}
<div
class="qr-card {{ 'inactive' if not qr.active_status }}"
data-qr-id="{{ qr.id }}"
data-qr-name="{{ qr.name }}"
data-qr-location="{{ qr.location }}"
data-qr-address="{{ qr.location_address }}"
data-qr-event="{{ qr.location_event }}"
data-qr-url="{{ qr.qr_url if qr.qr_url else '' }}"
onclick="openQRModalFromData(this)"
>
<div class="qr-card-layout">
<div
class="qr-preview-large"
onclick="event.stopPropagation(); openImageLightbox(this, '{{ qr.name }}')"
>
<img
src="data:image/png;base64,{{ qr.qr_code_image }}"
alt="QR Code for {{ qr.name }}"
loading="lazy"
/>
</div>
<div class="qr-details">
<h4 class="qr-name">{{ qr.name }}</h4>
<div class="qr-detail-item">
<i class="fas fa-map-marker-alt"></i>
<span>{{ qr.location }}</span>
</div>
{% if qr.location_address %}
<div class="qr-detail-item">
<i class="fas fa-home"></i>
<span>{{ qr.location_address }}</span>
</div>
{% endif %}
<div class="qr-detail-item qr-checkins-count">
<i class="fas fa-users"></i>
<span>{{ get_qr_code_checkin_count(qr.id) }} check-ins</span>
</div>
<span
class="qr-status-badge {{ 'active' if qr.active_status else 'inactive' }}"
>
<i
class="fas {{ 'fa-check-circle' if qr.active_status else 'fa-pause-circle' }}"
></i>
{{ 'Active' if qr.active_status else 'Inactive' }}
</span>
</div>
</div>
<div class="qr-card-actions">
<button
class="qr-action-btn copy"
onclick="event.stopPropagation(); copyQRUrl({{ qr.id }})"
title="Copy QR Code URL"
>
<i class="fas fa-copy"></i>
</button>
<button
class="qr-action-btn link"
onclick="event.stopPropagation(); openQRLink({{ qr.id }})"
title="Open QR Code Link"
>
<i class="fas fa-external-link-alt"></i>
</button>
<button
class="qr-action-btn download"
onclick="event.stopPropagation(); downloadQRFromCard(this)"
title="Download QR Code"
>
<i class="fas fa-download"></i>
</button>
<a
href="{{ url_for('qr_codes.edit_qr_code', qr_id=qr.id) }}"
class="qr-action-btn edit"
onclick="event.stopPropagation()"
title="Edit QR Code"
>
<i class="fas fa-edit"></i>
</a>
{% if session.role == 'admin' %}
<button
class="qr-action-btn toggle"
onclick="event.stopPropagation(); toggleQRCodeStatus({{ qr.id }})"
title="{{ 'Deactivate' if qr.active_status else 'Activate' }} QR Code"
>
<i
class="fas {{ 'fa-pause' if qr.active_status else 'fa-play' }}"
></i>
</button>
{% endif %}
</div>
</div>
{% endfor %}
<!-- Show "View All" button if project has more than 20 QR codes -->
{% if project_qr_codes|length > 12 %}
<a
href="{{ url_for('dashboard.project_qr_codes', project_id=project.id) }}"
class="btn btn-sm btn-primary view-all-qr-btn"
onclick="event.stopPropagation()"
>
<i class="fas fa-th-large"></i>
View All Project's QR Codes
</a>
{% endif %}
</div>
{% else %}
<div class="empty-project-qr">
<div class="empty-icon">
<i class="fas fa-qrcode"></i>
</div>
<h4>No QR Codes in this Project</h4>
<p>Create your first QR code for this project</p>
<a href="{{ url_for('qr_codes.create_qr_code') }}" class="btn btn-primary">
<i class="fas fa-plus"></i>
Create QR Code
</a>
</div>
{% endif %}
</div>
</div>
{% endfor %}
</div>
{% endif %}
</div>
<!-- Unassigned QR Codes Section -->
{% set unassigned_qr_codes = qr_codes|selectattr('project_id', 'equalto',
None)|list %} {% if unassigned_qr_codes %}
<div class="unassigned-qr-section">
<div class="unassigned-header">
<h3 class="unassigned-title">
<i class="fas fa-exclamation-triangle"></i>
Unassigned QR Codes
</h3>
<span class="unassigned-count"
>{{ unassigned_qr_codes|length }} QR Code{{ 's' if
unassigned_qr_codes|length != 1 else '' }}</span
>
</div>
<div class="project-qr-grid">
{% for qr in unassigned_qr_codes %}
<div
class="qr-card {{ 'inactive' if not qr.active_status }}"
data-qr-id="{{ qr.id }}"
data-qr-name="{{ qr.name }}"
data-qr-location="{{ qr.location }}"
data-qr-address="{{ qr.location_address }}"
data-qr-event="{{ qr.location_event }}"
data-qr-url="{{ qr.qr_url if qr.qr_url else '' }}"
onclick="openQRModalFromData(this)"
>
<div class="qr-card-compact">
<div
class="qr-preview-compact"
onclick="event.stopPropagation(); openImageLightbox(this, '{{ qr.name }}')"
>
<img
src="data:image/png;base64,{{ qr.qr_code_image }}"
alt="QR Code for {{ qr.name }}"
loading="lazy"
/>
</div>
<div class="qr-info-compact">
<h4 class="qr-name-compact">{{ qr.name }}</h4>
<div class="qr-details-row">
<div class="qr-detail-item">
<i class="fas fa-map-marker-alt"></i>
<span>{{ qr.location }}</span>
</div>
{% if qr.location_address %}
<div class="qr-detail-item">
<i class="fas fa-home"></i>
<span>{{ qr.location_address }}</span>
</div>
{% endif %}
<div class="qr-detail-item qr-checkins-count">
<i class="fas fa-users"></i>
<span>{{ get_qr_code_checkin_count(qr.id) }} check-ins</span>
</div>
{% if qr.qr_url %}
<div class="qr-detail-item">
<i class="fas fa-link"></i>
<span class="qr-url-short"
>{{ qr.qr_url[:50] }}{% if qr.qr_url|length > 50 %}...{% endif
%}</span
>
</div>
{% endif %}
</div>
</div>
<div class="qr-status-compact">
<span
class="qr-status-badge {{ 'active' if qr.active_status else 'inactive' }}"
>
<i
class="fas {{ 'fa-check-circle' if qr.active_status else 'fa-pause-circle' }}"
></i>
{{ 'Active' if qr.active_status else 'Inactive' }}
</span>
</div>
<div class="qr-actions-compact">
<button
class="qr-action-btn copy"
onclick="event.stopPropagation(); copyQRUrl({{ qr.id }})"
title="Copy QR Code URL"
>
<i class="fas fa-copy"></i>
</button>
<button
class="qr-action-btn link"
onclick="event.stopPropagation(); openQRLink({{ qr.id }})"
title="Open QR Code Link"
>
<i class="fas fa-external-link-alt"></i>
</button>
<button
class="qr-action-btn download"
onclick="event.stopPropagation(); downloadQRFromCard(this)"
title="Download QR Code"
>
<i class="fas fa-download"></i>
</button>
<a
href="{{ url_for('qr_codes.edit_qr_code', qr_id=qr.id) }}"
class="qr-action-btn edit"
onclick="event.stopPropagation()"
title="Edit QR Code"
>
<i class="fas fa-edit"></i>
</a>
{% if session.role == 'admin' %}
<button
class="qr-action-btn toggle"
onclick="event.stopPropagation(); toggleQRCodeStatus({{ qr.id }})"
title="{{ 'Deactivate' if qr.active_status else 'Activate' }} QR Code"
>
<i
class="fas {{ 'fa-pause' if qr.active_status else 'fa-play' }}"
></i>
</button>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}
<!-- Empty State -->
{% if not qr_codes and not projects %}
<div class="dashboard-empty">
{% if search_name or search_status %}
<div class="empty-icon">
<i class="fas fa-search"></i>
</div>
<h3>No QR Codes Found</h3>
<p>
No QR codes match your search criteria.
{% if search_name %}Try a different name{% endif %}
{% if search_name and search_status %} or {% endif %}
{% if search_status %}try changing the status filter{% endif %}.
</p>
<a href="{{ url_for('dashboard.dashboard') }}" class="btn btn-primary">
<i class="fas fa-times"></i>
Clear Filters
</a>
{% elif not projects %}
<div class="empty-icon">
<i class="fas fa-folder-open"></i>
</div>
<h3>Welcome to Your QR Code Dashboard</h3>
<p>
Get started by creating your first project and QR codes to organize your
digital assets effectively.
</p>
<a href="{{ url_for('projects.projects') }}" class="btn btn-primary">
<i class="fas fa-plus"></i>
Create Your First Project
</a>
{% endif %}
</div>
{% endif %}
</div>
{% endif %}
<!-- Image Lightbox -->
<div id="imageLightbox" class="image-lightbox" onclick="closeImageLightbox()">
<div class="lightbox-content" onclick="event.stopPropagation()">
<button class="lightbox-close" onclick="closeImageLightbox()">
<i class="fas fa-times"></i>
</button>
<img id="lightboxImage" src="" alt="" class="lightbox-image" />
<div class="lightbox-info" id="lightboxInfo"></div>
<div class="lightbox-hint">Click anywhere outside the image to close</div>
</div>
</div>
<!-- Enhanced QR Code Modal -->
<div id="qrModal" class="modal" onclick="closeQRModal()">
<div class="modal-content qr-modal-content" onclick="event.stopPropagation()">
<div class="modal-header">
<h3 id="modalTitle">QR Code Details</h3>
<button class="modal-close" onclick="closeQRModal()">
<i class="fas fa-times"></i>
</button>
</div>
<div class="modal-body qr-modal-body">
<div class="qr-modal-layout">
<div class="qr-modal-image">
<img id="modalQRImage" src="" alt="QR Code" />
</div>
<div class="qr-modal-info">
<div class="qr-detail-section">
<h4><i class="fas fa-qrcode"></i> QR Code Information</h4>
<div class="qr-detail-item">
<label>Name:</label>
<span id="modalQRName">-</span>
</div>
<div class="qr-detail-item">
<label>Location:</label>
<span id="modalQRLocation">-</span>
</div>
<div class="qr-detail-item">
<label>Address:</label>
<span id="modalQRAddress">-</span>
</div>
<div class="qr-detail-item">
<label>Event:</label>
<span id="modalQREvent">-</span>
</div>
<div class="qr-detail-item">
<label>QR Destination:</label>
<span id="modalQRDestination" class="qr-destination-link">
<a href="#" target="_blank" rel="noopener noreferrer">
<i class="fas fa-external-link-alt"></i>
View QR Destination
</a>
</span>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer qr-modal-footer">
<button class="btn btn-primary" onclick="downloadModalQR()">
<i class="fas fa-download"></i>
Download QR Code
</button>
<button class="btn btn-outline" onclick="copyModalQRData()">
<i class="fas fa-copy"></i>
Copy Information
</button>
<button class="btn btn-secondary" onclick="copyQRDestination()">
<i class="fas fa-link"></i>
Copy QR Link
</button>
<button class="btn btn-outline" onclick="closeQRModal()">
<i class="fas fa-times"></i>
Close
</button>
</div>
</div>
</div>
{% endblock %} {% block extra_scripts %}
<script src="{{ url_for('static', filename='js/dashboard.js') }}"></script>
{% endblock %}