From 775f163a1a753d4cbdbf7475bb51ff7cc429f225 Mon Sep 17 00:00:00 2001 From: NguyenND Date: Mon, 1 Dec 2025 16:18:38 -0500 Subject: [PATCH] Updated QR code dashboard with searching --- app.py | 49 ++-- static/css/dashboard.css | 141 +++++++++++ templates/dashboard.html | 415 ++++++++++++++++++++++++++++---- templates/project_qr_codes.html | 164 +++++++------ 4 files changed, 621 insertions(+), 148 deletions(-) diff --git a/app.py b/app.py index 16f7c35..f71a2f8 100644 --- a/app.py +++ b/app.py @@ -1736,34 +1736,51 @@ def dashboard(): @login_required def project_qr_codes(project_id): """ - Paginated view of all QR codes for a specific project - Shows 20 QR codes per page with pagination controls + View all QR codes for a specific project with search filters + Allows filtering by name and status within the project """ try: # Get the project project = Project.query.get_or_404(project_id) - # Get page number from query parameter (default to 1) - page = request.args.get('page', 1, type=int) - per_page = 20 # QR codes per page + # Get search parameters from URL + search_name = request.args.get('search_name', '').strip() + search_status = request.args.get('search_status', '').strip() - # Query QR codes for this project with pagination - pagination = QRCode.query.filter_by(project_id=project_id)\ - .order_by(QRCode.created_date.desc())\ - .paginate(page=page, per_page=per_page, error_out=False) + # Build QR codes query with filters for this project only + qr_query = QRCode.query.filter_by(project_id=project_id) - qr_codes = pagination.items + # Apply name filter if provided + if search_name: + qr_query = qr_query.filter(QRCode.name.ilike(f'%{search_name}%')) - # Log access - logger_handler.logger.info( - f"User {session['username']} viewed project '{project.name}' QR codes page {page}: " - f"{len(qr_codes)} QR codes displayed (total: {pagination.total})" - ) + # Apply status filter if provided + if search_status == 'active': + qr_query = qr_query.filter(QRCode.active_status == True) + elif search_status == 'inactive': + qr_query = qr_query.filter(QRCode.active_status == False) + + # Execute query + qr_codes = qr_query.order_by(QRCode.created_date.desc()).all() + + # Log access with filter info + filter_info = [] + if search_name: + filter_info.append(f"name contains '{search_name}'") + if search_status: + filter_info.append(f"status is {search_status}") + + log_message = f"User {session['username']} viewed project '{project.name}' QR codes: {len(qr_codes)} QR codes" + if filter_info: + log_message += f" (filtered: {', '.join(filter_info)})" + + logger_handler.logger.info(log_message) return render_template('project_qr_codes.html', project=project, qr_codes=qr_codes, - pagination=pagination) + search_name=search_name, + search_status=search_status) except Exception as e: logger_handler.log_database_error('project_qr_codes_view', e) diff --git a/static/css/dashboard.css b/static/css/dashboard.css index 5e345d8..9592e2b 100644 --- a/static/css/dashboard.css +++ b/static/css/dashboard.css @@ -1436,4 +1436,145 @@ .search-actions .btn { 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); + } } \ No newline at end of file diff --git a/templates/dashboard.html b/templates/dashboard.html index 73cb97d..8f0ac0e 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -1032,7 +1032,148 @@ Management{% endblock %} {% block extra_head %} .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); + } +} {% endblock %} {% block content %}
@@ -1164,46 +1305,75 @@ Management{% endblock %} {% block extra_head %}
- + {% if qr_codes %} -
-
-

- - Search Results -

- {{ qr_codes|length }} QR Code{{ 's' if qr_codes|length != 1 else '' }} + + +
+

+ + Search Results +

+
+ + {{ qr_codes|length }} QR code{{ 's' if qr_codes|length != 1 else '' }} found + + {% set projects_with_results = qr_codes|map(attribute='project_id')|select|unique|list|length %} + {% if projects_with_results > 0 %} + across {{ projects_with_results }} project{{ 's' if projects_with_results != 1 else '' }} + {% endif %}
+
-
- {% for qr in qr_codes %} -
-
-
- QR Code for {{ qr.name }} + + {% for project in projects %} + {% set project_search_results = qr_codes|selectattr('project_id', 'equalto', project.id)|list %} + {% if project_search_results %} +
+
+
+
+
+
+

{{ project.name }}

+ {% if project.description %} +

{{ project.description }}

+ {% endif %} +
+
+ + {{ project_search_results|length }} QR code{{ 's' if project_search_results|length != 1 else '' }} + +
-
-

{{ qr.name }}

-
+
+ {% for qr in project_search_results %} +
+
+
+ QR Code for {{ qr.name }} +
+ +
+

{{ qr.name }}

+
{{ qr.location }} @@ -1216,6 +1386,13 @@ Management{% endblock %} {% block extra_head %}
{% endif %} + {% if qr.location_event %} +
+ + {{ qr.location_event }} +
+ {% endif %} +
{{ get_qr_code_checkin_count(qr.id) }} check-ins @@ -1224,24 +1401,22 @@ Management{% endblock %} {% block extra_head %} {% if qr.qr_url %}
- {{ qr.qr_url[:50] }}{% if qr.qr_url|length > 50 %}...{% endif %} + {{ qr.qr_url[:40] }}{% if qr.qr_url|length > 40 %}...{% endif %}
{% endif %} + + + + {{ 'Active' if qr.active_status else 'Inactive' }} +
-
- - - {{ 'Active' if qr.active_status else 'Inactive' }} - -
- -
+
+ {% endfor %} +
+
+ {% endif %} + {% endfor %} + + + {% set unassigned_search_results = qr_codes|selectattr('project_id', 'equalto', None)|list %} + {% if unassigned_search_results %} +
+
+
+
+ +
+
+

Unassigned QR Codes

+

QR codes not assigned to any project

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

{{ qr.name }}

+ +
+ + {{ qr.location }} +
+ + {% if qr.location_address %} +
+ + {{ qr.location_address }} +
+ {% endif %} + + {% if qr.location_event %} +
+ + {{ qr.location_event }} +
+ {% endif %} + +
+ + {{ get_qr_code_checkin_count(qr.id) }} check-ins +
+ + {% if qr.qr_url %} +
+ + {{ qr.qr_url[:40] }}{% if qr.qr_url|length > 40 %}...{% endif %} +
+ {% endif %} + + + + {{ 'Active' if qr.active_status else 'Inactive' }} + +
+
+ +
+ + + + + + + + + + + {% if session.role == 'admin' %} + + {% endif %} +
{% endfor %}
+ {% endif %} + {% else %}
diff --git a/templates/project_qr_codes.html b/templates/project_qr_codes.html index a98c685..ab9a997 100644 --- a/templates/project_qr_codes.html +++ b/templates/project_qr_codes.html @@ -697,7 +697,7 @@
- {{ pagination.total }} Total QR Codes + {{ qr_codes|length }} Total QR Codes
@@ -707,13 +707,73 @@ {{ qr_codes|selectattr('active_status', 'equalto', False)|list|length }} Inactive
-
- - Page {{ pagination.page }} of {{ pagination.pages }} -
+ +
+
+
+
+ + +
+ +
+ + +
+ +
+ + {% if search_name or search_status %} + + + Clear + + {% endif %} +
+
+
+
+ + + {% if search_name or search_status %} +
+
+ + + Showing filtered results + {% if search_name %} - Name: "{{ search_name }}"{% endif %} + {% if search_status %} - Status: {{ search_status|title }}{% endif %} + ({{ qr_codes|length }} QR code{{ 's' if qr_codes|length != 1 else '' }} found) + + + + Clear Filters + +
+
+ {% endif %} + {% if qr_codes %}
@@ -803,87 +863,24 @@
{% endfor %} -
- - - {% if pagination.pages > 1 %} -
-
- Showing {{ (pagination.page - 1) * pagination.per_page + 1 }} to {{ [pagination.page * pagination.per_page, pagination.total]|min }} of {{ pagination.total }} QR codes -
- -
    - -
  • - {% if pagination.page == 1 %} - - {% else %} - - - - {% endif %} -
  • - - -
  • - {% if pagination.has_prev %} - - - - {% else %} - - {% endif %} -
  • - - - {% for page_num in pagination.iter_pages(left_edge=1, right_edge=1, left_current=2, right_current=2) %} - {% if page_num %} - {% if page_num == pagination.page %} -
  • - {{ page_num }} -
  • - {% else %} -
  • - - {{ page_num }} - -
  • - {% endif %} - {% else %} -
  • - ... -
  • - {% endif %} - {% endfor %} - - -
  • - {% if pagination.has_next %} - - - - {% else %} - - {% endif %} -
  • - - -
  • - {% if pagination.page == pagination.pages %} - - {% else %} - - - - {% endif %} -
  • -
-
- {% endif %} - +
{% else %}
+ {% if search_name or search_status %} + +

No Matching QR Codes

+

+ No QR codes in this project 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 %}. +

+ + + Clear Filters + + {% else %}

No QR Codes Found

This project doesn't have any QR codes yet.

@@ -892,6 +889,7 @@ Create QR Code {% endif %} + {% endif %}
{% endif %}