From 79d94ff9501d23886782f36d3e1fa33b72863bdd Mon Sep 17 00:00:00 2001 From: Yaro Kasear Date: Thu, 21 Aug 2025 11:57:09 -0500 Subject: [PATCH] Enhance table rendering functionality: refactor headers and rows handling, add dynamic table support, and implement refresh capabilities. Update related templates and JavaScript for improved data management. --- inventory/routes/helpers.py | 40 +++++++++---------- inventory/routes/inventory.py | 9 ++++- inventory/routes/user.py | 11 ++--- inventory/routes/worklog.py | 11 ++--- inventory/static/js/table.js | 31 ++++++++++++++ .../fragments/_table_data_fragment.html | 29 ++++++++++++++ .../templates/fragments/_table_fragment.html | 19 ++++++--- inventory/templates/inventory.html | 4 +- inventory/templates/layout.html | 1 + inventory/templates/table.html | 10 ++++- inventory/templates/user.html | 4 +- inventory/templates/worklog.html | 2 +- inventory/ui/blueprint.py | 4 ++ 13 files changed, 132 insertions(+), 43 deletions(-) create mode 100644 inventory/static/js/table.js create mode 100644 inventory/templates/fragments/_table_data_fragment.html diff --git a/inventory/routes/helpers.py b/inventory/routes/helpers.py index afb4ccb..79ff297 100644 --- a/inventory/routes/helpers.py +++ b/inventory/routes/helpers.py @@ -36,18 +36,18 @@ ROUTE_BREADCRUMBS = { } inventory_headers = { - "Date Entered": lambda i: {"text": i.timestamp.strftime("%Y-%m-%d") if i.timestamp else None}, - "Identifier": lambda i: {"text": i.identifier}, - "Name": lambda i: {"text": i.name}, - "Serial Number": lambda i: {"text": i.serial}, - "Bar Code": lambda i: {"text": i.barcode}, - "Brand": lambda i: {"text": i.brand.name} if i.brand else {"text": None}, - "Model": lambda i: {"text": i.model}, - "Item Type": lambda i: {"text": i.device_type.description} if i.device_type else {"text": None}, - "Shared?": lambda i: {"text": i.shared, "type": "bool", "html": checked_box if i.shared else unchecked_box}, - "Owner": lambda i: {"text": i.owner.identifier, "url": url_for("main.user", id=i.owner.id)} if i.owner else {"text": None}, - "Location": lambda i: {"text": i.location.identifier} if i.location else {"Text": None}, - "Condition": lambda i: {"text": i.condition} + "Date Entered": lambda i: {"field": "timestamp", "text": i.timestamp.strftime("%Y-%m-%d") if i.timestamp else None}, + "Identifier": lambda i: {"field": "identifier", "text": i.identifier}, + "Name": lambda i: {"field": "name", "text": i.name}, + "Serial Number": lambda i: {"field": "serial", "text": i.serial}, + "Bar Code": lambda i: {"field": "barcode", "text": i.barcode}, + "Brand": lambda i: {"field": "brand.name", "text": i.brand.name} if i.brand else {"text": None}, + "Model": lambda i: {"field": "model", "text": i.model}, + "Item Type": lambda i: {"field": "device_type.description", "text": i.device_type.description} if i.device_type else {"text": None}, + "Shared?": lambda i: {"field": "shared", "text": i.shared, "type": "bool", "html": checked_box if i.shared else unchecked_box}, + "Owner": lambda i: {"field": "owner.identifier", "text": i.owner.identifier, "url": url_for("main.user", id=i.owner.id)} if i.owner else {"text": None}, + "Location": lambda i: {"field": "location.identifier", "text": i.location.identifier} if i.location else {"Text": None}, + "Condition": lambda i: {"field": "condition", "text": i.condition} } checked_box = ''' @@ -75,17 +75,17 @@ FILTER_MAP = { } user_headers = { - "Last Name": lambda i: {"text": i.last_name}, - "First Name": lambda i: {"text": i.first_name}, - "Title": lambda i: {"text": i.title}, - "Supervisor": lambda i: {"text": i.supervisor.identifier, "url": url_for("main.user", id=i.supervisor.id)} if i.supervisor else {"text": None}, - "Location": lambda i: {"text": i.location.identifier} if i.location else {"text": None}, - "Staff?": lambda i: {"text": i.staff, "type": "bool", "html": checked_box if i.staff else unchecked_box}, - "Active?": lambda i: {"text": i.active, "type": "bool", "html": checked_box if i.active else unchecked_box} + "Last Name": lambda i: {"field": "last_name","text": i.last_name}, + "First Name": lambda i: {"field": "first_name","text": i.first_name}, + "Title": lambda i: {"field": "title","text": i.title}, + "Supervisor": lambda i: {"field": "supervisor,identifier","text": i.supervisor.identifier, "url": url_for("main.user", id=i.supervisor.id)} if i.supervisor else {"text": None}, + "Location": lambda i: {"field": "location,identifier","text": i.location.identifier} if i.location else {"text": None}, + "Staff?": lambda i: {"field": "staff","text": i.staff, "type": "bool", "html": checked_box if i.staff else unchecked_box}, + "Active?": lambda i: {"field": "active","text": i.active, "type": "bool", "html": checked_box if i.active else unchecked_box} } worklog_headers = { - "Contact": lambda i: {"text": i.contact.identifier, "url": url_for("main.user", id=i.contact.id)} if i.contact else {"Text": None}, + "Contact": lambda i: {"text": i.contact.identifier, "url": url_for("main.user_item", id=i.contact.id)} if i.contact else {"Text": None}, "Work Item": lambda i: {"text": i.work_item.identifier, "url": url_for('main.inventory_item',id=i.work_item.id)} if i.work_item else {"text": None}, "Start Time": lambda i: {"text": i.start_time.strftime("%Y-%m-%d")}, "End Time": lambda i: {"text": i.end_time.strftime("%Y-%m-%d")} if i.end_time else {"text": None}, diff --git a/inventory/routes/inventory.py b/inventory/routes/inventory.py index 79b3f79..40b80f5 100644 --- a/inventory/routes/inventory.py +++ b/inventory/routes/inventory.py @@ -43,13 +43,18 @@ def list_inventory(): inventory = query.all() inventory = sorted(inventory, key=lambda i: i.identifier) + rows=[{"id": item.id, "cells": [row_fn(item) for row_fn in inventory_headers.values()]} for item in inventory] + fields = [d['field'] for d in rows[0]['cells']] + return render_template( 'table.html', title=f"Inventory Listing ({filter_name})" if filter_by else "Inventory Listing", header=inventory_headers, - rows=[{"id": item.id, "cells": [row_fn(item) for row_fn in inventory_headers.values()]} for item in inventory], + fields=fields, + rows=rows, entry_route = 'inventory_item', - csv_route = 'inventory' + csv_route = 'inventory', + model_name = 'inventory' ) @main.route("/inventory/index") diff --git a/inventory/routes/user.py b/inventory/routes/user.py index 583dadb..5dc6726 100644 --- a/inventory/routes/user.py +++ b/inventory/routes/user.py @@ -17,18 +17,19 @@ def list_users(): return render_template( 'table.html', header = user_headers, - rows = [{"id": user.id, "cells": [fn(user) for fn in user_headers.values()]} for user in users], + model_name = 'user', title = "Users", - entry_route = 'user', - csv_route = 'user' + entry_route = 'user_item', + csv_route = 'user', + fields = ['last_name', 'first_name', 'title', 'supervisor.identifier', 'location.identifier', 'staff', 'active'], ) @main.route("/user/") -def user(id): +def user_item(id): try: id = int(id) except ValueError: - return render_template('error.html', title='Bad ID', message='ID must be an integer.', endpoint='user', endpoint_args={'id': -1}) + return render_template('error.html', title='Bad ID', message='ID must be an integer.', endpoint='user_item', endpoint_args={'id': -1}) users_query = db.session.query(User).order_by(User.first_name, User.last_name) users = eager_load_user_relationships(users_query).all() diff --git a/inventory/routes/worklog.py b/inventory/routes/worklog.py index 6327c41..6e9d91f 100644 --- a/inventory/routes/worklog.py +++ b/inventory/routes/worklog.py @@ -17,18 +17,19 @@ def list_worklog(): return render_template( 'table.html', header=worklog_headers, - rows=[{"id": log.id, "cells": [fn(log) for fn in worklog_headers.values()]} for log in query.all()], + model_name='worklog', title="Work Log", - entry_route='worklog_entry', + fields = ['contact.identifier', 'work_item.identifier', 'start_time', 'end_time', 'complete', 'followup', 'analysis'], + entry_route='worklog_item', csv_route='worklog' ) @main.route("/worklog/") -def worklog_entry(id): +def worklog_item(id): try: id = int(id) except ValueError: - return render_template('error.html', title='Bad ID', message='ID must be an integer.', endpoint='worklog_entry', endpoint_args={'id': -1}) + return render_template('error.html', title='Bad ID', message='ID must be an integer.', endpoint='worklog_item', endpoint_args={'id': -1}) log = eager_load_worklog_relationships(db.session.query(WorkLog)).get(id) user_query = db.session.query(User).order_by(User.first_name) @@ -55,7 +56,7 @@ def worklog_entry(id): items=items ) -@main.route("/worklog_entry/new", methods=["GET"]) +@main.route("/worklog_item/new", methods=["GET"]) def new_worklog(): items = eager_load_inventory_relationships(db.session.query(Inventory)).all() users = eager_load_user_relationships(db.session.query(User).order_by(User.first_name)).all() diff --git a/inventory/static/js/table.js b/inventory/static/js/table.js new file mode 100644 index 0000000..3b5d52b --- /dev/null +++ b/inventory/static/js/table.js @@ -0,0 +1,31 @@ +function Table(cfg) { + return { + id: cfg.id, + refreshUrl: cfg.refreshUrl, + headers: cfg.headers || [], + perPage: cfg.perPage || 10, + offset: cfg.offset || 0, + fields: cfg.fields || [], + + init() { + if (this.refreshUrl) this.refresh(); + }, + + buildRefreshUrl() { + if (!this.refreshUrl) return null; + const u = new URL(this.refreshUrl, window.location.origin); + u.search = new URLSearchParams({ view: 'table', offset: this.offset, 'limit': this.perPage, 'fields': this.fields }).toString(); + return u.toString(); + }, + + async refresh() { + const url = this.buildRefreshUrl(); + if (!url) return; + const res = await fetch(url, { headers: { 'HX-Request': 'true' } }); + const text = await res.text(); + if (this.$refs.body) { + this.$refs.body.innerHTML = text; + } + } + }; +} \ No newline at end of file diff --git a/inventory/templates/fragments/_table_data_fragment.html b/inventory/templates/fragments/_table_data_fragment.html new file mode 100644 index 0000000..928a69c --- /dev/null +++ b/inventory/templates/fragments/_table_data_fragment.html @@ -0,0 +1,29 @@ + + {# + + + {% if headers %} + + {% for header in headers %} + + {% endfor %} + {% else %} + {% for col in rows[0].keys() %} + + {% endfor %} + {% endif %} + + + + #} + {% for r in rows %} + + {% for key, val in r.items() if not key == 'id' %} + + {% endfor %} + + {% endfor %} + {# + +
{{ header }}{{ col }}
{{ val if val else '-' }}
+ #} \ No newline at end of file diff --git a/inventory/templates/fragments/_table_fragment.html b/inventory/templates/fragments/_table_fragment.html index ec6ad68..033c2ba 100644 --- a/inventory/templates/fragments/_table_fragment.html +++ b/inventory/templates/fragments/_table_fragment.html @@ -51,14 +51,21 @@ {% endif %} {% endmacro %} -{% macro dynamic_table(id, headers=none, rows=none, entry_route=None, title=None, per_page=15) %} +{% macro dynamic_table(id, headers=none, rows=none, fields=none, entry_route=None, title=None, per_page=15, offset=0, refresh_url=none) %} -{% if rows %} +{% if rows or refresh_url %} {% if title %} {% endif %} -
+
@@ -68,7 +75,8 @@ {% endfor %} - + + {% if rows %} {% for row in rows %} @@ -85,10 +93,11 @@ {% endfor %} {% endfor %} + {% endif %}
{% else %}
No data.
{% endif %} -{% endmacro %} \ No newline at end of file +{% endmacro %} diff --git a/inventory/templates/inventory.html b/inventory/templates/inventory.html index 7d24ba1..4327504 100644 --- a/inventory/templates/inventory.html +++ b/inventory/templates/inventory.html @@ -198,7 +198,7 @@ id='owner', label='Contact', current_item=item.owner, - entry_link='user', + entry_link='user_item', enabled=item.condition not in ["Removed", "Disposed"], refresh_url=url_for('ui.list_items', model_name='user'), select_url=url_for('ui.update_item', model_name='inventory'), @@ -278,7 +278,7 @@
{% for note in notes %} {% set title %} - {{ note.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}{{ links.entry_link('worklog_entry', note.work_log_id) }} + {{ note.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}{{ links.entry_link('worklog_item', note.work_log_id) }} {% endset %} {{ editor.render_editor( id = 'updates' + (note.id | string), diff --git a/inventory/templates/layout.html b/inventory/templates/layout.html index 16f01d7..eb3d108 100644 --- a/inventory/templates/layout.html +++ b/inventory/templates/layout.html @@ -72,6 +72,7 @@ +