Implement search functionality with pagination and enhance templates for improved user experience
This commit is contained in:
parent
67c85a4569
commit
eb7e446e56
7 changed files with 193 additions and 43 deletions
Binary file not shown.
62
routes.py
62
routes.py
|
@ -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)
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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 %}
|
|
@ -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">
|
||||||
|
|
|
@ -14,46 +14,78 @@
|
||||||
<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 %}
|
{
|
||||||
.sticky-top {
|
% block style %
|
||||||
position: sticky;
|
}
|
||||||
top: 0;
|
|
||||||
z-index: 1020;
|
|
||||||
}
|
|
||||||
|
|
||||||
table td,
|
.sticky-top {
|
||||||
th {
|
position: sticky;
|
||||||
white-space: nowrap;
|
top: 0;
|
||||||
}
|
z-index: 1020;
|
||||||
|
}
|
||||||
|
|
||||||
input[type="checkbox"][disabled] {
|
table td,
|
||||||
pointer-events: none;
|
th {
|
||||||
opacity: 1;
|
white-space: nowrap;
|
||||||
filter: none;
|
}
|
||||||
appearance: auto;
|
|
||||||
-webkit-appearance: checkbox;
|
|
||||||
background-color: white !important;
|
|
||||||
color: black !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="checkbox"][disabled]::-moz-checkbox {
|
input[type="checkbox"][disabled] {
|
||||||
background-color: white;
|
pointer-events: none;
|
||||||
}
|
opacity: 1;
|
||||||
{% endblock %}
|
filter: none;
|
||||||
|
appearance: auto;
|
||||||
|
-webkit-appearance: checkbox;
|
||||||
|
background-color: white !important;
|
||||||
|
color: black !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"][disabled]::-moz-checkbox {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
% 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>
|
|
@ -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 %}
|
Loading…
Add table
Add a link
Reference in a new issue