Implement search functionality with pagination and enhance templates for improved user experience

This commit is contained in:
Yaro Kasear 2025-06-13 15:29:35 -05:00
parent 67c85a4569
commit eb7e446e56
7 changed files with 193 additions and 43 deletions

Binary file not shown.

View file

@ -1,7 +1,8 @@
from flask import Blueprint, render_template, url_for, request from flask import Blueprint, render_template, url_for, request, redirect
from .models import Area, Brand, Item, Inventory, RoomFunction, User, WorkLog, Room from .models import Area, Brand, Item, Inventory, RoomFunction, User, WorkLog, Room
import html import html
from sqlalchemy.orm import joinedload from sqlalchemy import or_
from sqlalchemy.orm import aliased
from typing import Callable, Any, List from typing import Callable, Any, List
from . import db from . import db
from .utils import eager_load_user_relationships, eager_load_inventory_relationships, eager_load_room_relationships, eager_load_worklog_relationships, chunk_list from .utils import eager_load_user_relationships, eager_load_inventory_relationships, eager_load_room_relationships, eager_load_worklog_relationships, chunk_list
@ -307,4 +308,59 @@ def worklog_entry(id):
@main.route("/search") @main.route("/search")
def search(): def search():
return render_template('search.html', title="Database Search") query = request.args.get('q', '').strip()
inventory_page = request.args.get('inventory_page', default=1, type=int)
user_page = request.args.get('user_page', default=1, type=int)
worklog_page = request.args.get('worklog_page', default=1, type=int)
if not query:
return redirect(url_for('index'))
UserAlias = aliased(User)
inventory_query = eager_load_inventory_relationships(db.session.query(Inventory).join(UserAlias, Inventory.owner)).filter(
or_(
Inventory.inventory_name.ilike(f"%{query}%"),
Inventory.serial.ilike(f"%{query}%"),
Inventory.barcode.ilike(f"%{query}%"),
Inventory.notes.ilike(f"%{query}%"),
UserAlias.first_name.ilike(f"%{query}%"),
UserAlias.last_name.ilike(f"%{query}%")
))
inventory_pagination = make_paginated_data(inventory_query, inventory_page)
user_query = eager_load_user_relationships(db.session.query(User)).filter(
or_(
User.first_name.ilike(f"%{query}%"),
User.last_name.ilike(f"%{query}%")
))
user_pagination = make_paginated_data(user_query, user_page)
worklog_query = eager_load_worklog_relationships(db.session.query(WorkLog).join(UserAlias, WorkLog.contact)).filter(
or_(
WorkLog.notes.ilike(f"%{query}%"),
UserAlias.first_name.ilike(f"%{query}%"),
UserAlias.last_name.ilike(f"%{query}%")
))
worklog_pagination = make_paginated_data(worklog_query, worklog_page)
results = {
'inventory': {
'results': inventory_query,
'headers': inventory_headers,
'rows': [{"id": item.id, "cells": [fn(item) for fn in inventory_headers.values()]} for item in inventory_pagination['items']],
'pagination': inventory_pagination
},
'users': {
'results': user_query,
'headers': user_headers,
'rows': [{"id": user.id, "cells": [fn(user) for fn in user_headers.values()]} for user in user_pagination['items']],
'pagination': user_pagination
},
'worklog': {
'results': worklog_query,
'headers': worklog_headers,
'rows': [{"id": log.id, "cells": [fn(log) for fn in worklog_headers.values()]} for log in worklog_pagination['items']],
'pagination': worklog_pagination
}
}
return render_template('search.html', title="Database Search", results=results, query=query)

View file

@ -1,4 +1,4 @@
{% macro category_link(endpoint, label, icon_html=None, arguments={}) %} {% macro category_link(endpoint, label, icon_html=none, arguments={}) %}
<div class="col text-center"> <div class="col text-center">
<a href="{{ url_for('main.' + endpoint, **arguments) }}" <a href="{{ url_for('main.' + endpoint, **arguments) }}"
class="d-flex flex-column justify-content-center link-success link-underline-opacity-0"> class="d-flex flex-column justify-content-center link-success link-underline-opacity-0">
@ -9,3 +9,14 @@
</a> </a>
</div> </div>
{% endmacro %} {% endmacro %}
{% macro navigation_link(endpoint, label, icon_html=none, arguments={}, active=false) %}
<li class="nav-item">
<a href="{{ url_for('main.' + endpoint, **arguments) }}" class="nav-link{% if active %} active{% endif %}">
{% if icon_html %}
{{ icon_html | safe }}
{% endif %}
{{ label }}
</a>
</li>
{% endmacro %}

View file

@ -4,10 +4,8 @@
{% block title %}{{ title }}{% endblock %} {% block title %}{{ title }}{% endblock %}
{% block content %} {% block content %}
<h1 class="text-center display-3">Table of Contents</h1> <div class="container text-center">
<div class="row text-center m-5"> <h1 class="display-4">Welcome to Inventory Manager</h1>
{{ links.category_link(endpoint = 'inventory_index', label = 'Inventory', icon_html = icons.inventory) }} <p class="lead">Find out about all of your assets.</p>
{{ links.category_link(endpoint = 'list_users', label = "Users", icon_html = icons.user) }}
{{ links.category_link(endpoint = 'list_worklog', label = 'Worklog', icon_html = icons.log) }}
</div> </div>
{% endblock %} {% endblock %}

View file

@ -16,7 +16,7 @@ title=title
</div> </div>
</div> </div>
<div class="row text-center"> <div class="row text-center">
{{ links.category_link(endpoint = 'search', label = 'Search', icon_html = icons.search, arguments={'category': 'inventory'}) }} {{ links.category_link(endpoint = 'search', label = 'Search', icon_html = icons.search) }}
{{ links.category_link(endpoint = 'list_inventory', label = "List", icon_html = icons.table) }} {{ links.category_link(endpoint = 'list_inventory', label = "List", icon_html = icons.table) }}
</div> </div>
<div class="row"> <div class="row">

View file

@ -14,7 +14,10 @@
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.6/dist/css/bootstrap.min.css" rel="stylesheet" <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.6/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-4Q6Gf2aSP4eDXB8Miphtr37CMZZQ5oXLH2yaXMJ2w8e2ZtHTl7GptT4jmndRuHDT" crossorigin="anonymous"> integrity="sha384-4Q6Gf2aSP4eDXB8Miphtr37CMZZQ5oXLH2yaXMJ2w8e2ZtHTl7GptT4jmndRuHDT" crossorigin="anonymous">
<style> <style>
{% block style %} {
% block style %
}
.sticky-top { .sticky-top {
position: sticky; position: sticky;
top: 0; top: 0;
@ -39,21 +42,50 @@
input[type="checkbox"][disabled]::-moz-checkbox { input[type="checkbox"][disabled]::-moz-checkbox {
background-color: white; background-color: white;
} }
{% endblock %}
{
% endblock %
}
</style> </style>
</head> </head>
<body class="bg-tertiary text-primary-emphasis"> <body class="bg-tertiary text-primary-emphasis">
<header class="bg-success text-light p-2"> <nav class="navbar navbar-expand bg-body-tertiary border-bottom">
<h1>{{ title }}</h1> <div class="container-fluid">
</header> <span class="navbar-brand">
Inventory Manager
</span>
<button class="navbar-toggler">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
{{ links.navigation_link(endpoint = 'inventory_index', label = 'Inventory') }}
{{ links.navigation_link(endpoint = 'list_users', label = 'Users') }}
{{ links.navigation_link(endpoint = 'list_worklog', label = 'Worklog') }}
</ul>
<form class="d-flex" method="GET" action="{{ url_for('main.search') }}">
<input type="text" class="form-control me-2" placeholder="Search" name="q" id="search" />
<button class="btn btn-primary" type="submit" id="searchButton" disabled>Search</button>
</form>
</div>
</div>
</nav>
<main class="container-flex m-5">{% block content %}{% endblock %}</main> <main class="container-flex m-5">{% block content %}{% endblock %}</main>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.6/dist/js/bootstrap.bundle.min.js" <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.6/dist/js/bootstrap.bundle.min.js"
integrity="sha384-j1CDi7MgGQ12Z7Qab0qlWQ/Qqz24Gc6BM0thvEMVjHnfYGF0rmFCozFSxQBxwHKO" integrity="sha384-j1CDi7MgGQ12Z7Qab0qlWQ/Qqz24Gc6BM0thvEMVjHnfYGF0rmFCozFSxQBxwHKO"
crossorigin="anonymous"></script> crossorigin="anonymous"></script>
<script src="{{ url_for('static', filename='js/datalist.js') }}"></script> <script src="{{ url_for('static', filename='js/datalist.js') }}"></script>
<script>{% block script %}{% endblock %}</script> <script>
const searchInput = document.querySelector('#search');
const searchButton = document.querySelector('#searchButton');
searchInput.addEventListener('input', () => {
searchButton.disabled = searchInput.value.trim() === '';
});
{% block script %} {% endblock %}
</script>
</body> </body>
</html> </html>

View file

@ -1,4 +1,4 @@
<!-- templates/user.html --> <!-- templates/search.html -->
{% extends "layout.html" %} {% extends "layout.html" %}
{% block title %}{{ title }}{% endblock %} {% block title %}{{ title }}{% endblock %}
@ -10,11 +10,64 @@ title=title,
) }} ) }}
<div class="container"> <div class="container">
<h1 class="display-1 text-center">Search Categories</h1> <div>
<div class="row text-center m-5"> {{ tables.render_table(
{{ links.category_link(endpoint = 'search', label = 'Inventory', icon_html = icons.laptop, arguments = {'category': 'inventory'}) }} headers = results['inventory']['headers'],
{{ links.category_link(endpoint = 'search', label = 'Users', icon_html = icons.user, arguments = {'category': 'users'}) }} rows = results['inventory']['rows'],
{{ links.category_link(endpoint = 'search', label = 'Work Logs', icon_html = icons.log, arguments = {'category': 'logs'}) }} entry_route = 'inventory_item',
title='Inventory Results'
)}}
{{ tables.render_pagination(
endpoint = 'main.search',
page = results['inventory']['pagination']['page'],
has_prev = results['inventory']['pagination']['has_prev'],
has_next = results['inventory']['pagination']['has_next'],
total_pages = results['inventory']['pagination']['total_pages'],
page_variable = 'inventory_page',
extra_args = {
'q': query,
'user_page': results['users']['pagination']['page'],
'worklog_page': results['worklog']['pagination']['page']
})}}
</div>
<div>
{{ tables.render_table(
headers = results['users']['headers'],
rows = results['users']['rows'],
entry_route = 'user', title='User Results'
)}}
{{ tables.render_pagination(
endpoint = 'main.search',
page = results['users']['pagination']['page'],
has_prev = results['users']['pagination']['has_prev'],
has_next = results['users']['pagination']['has_next'],
total_pages = results['users']['pagination']['total_pages'],
page_variable = 'user_page',
extra_args = {
'q': query,
'inventory_page': results['inventory']['pagination']['page'],
'worklog_page': results['worklog']['pagination']['page']
})}}
</div>
<div>
{{ tables.render_table(
headers = results['worklog']['headers'],
rows = results['worklog']['rows'],
entry_route = 'worklog_entry',
title='Worklog Results'
)}}
{{ tables.render_pagination(
endpoint = 'main.search',
page = results['worklog']['pagination']['page'],
has_prev = results['worklog']['pagination']['has_prev'],
has_next = results['worklog']['pagination']['has_next'],
total_pages = results['worklog']['pagination']['total_pages'],
page_variable = 'worklog_page',
extra_args = {
'q': query,
'inventory_page': results['inventory']['pagination']['page'],
'user_page': results['users']['pagination']['page']
})}}
</div> </div>
</div> </div>
{% endblock %} {% endblock %}