From 7690d7b34a5e19e9f3d5ced1bea1fb02517f907b Mon Sep 17 00:00:00 2001 From: Nameru Thapa Date: Sun, 6 Sep 2026 15:14:51 +0200 Subject: [PATCH] Add search and pagination to admin list pages (Feature A) - Add shared list-controls.js for live search filtering and show-5-more pagination - Add list-search-block and list-row styles to style.css - Apply search + pagination to Production Jobs list - Apply search + pagination to Station Inventory list - Order Instruction Photos by most recently uploaded; switch to compact name-only list with click-to-expand photo preview, plus search + pagination - Apply search + pagination to Warehouse Transfer Requests list - Update models.py to support uploaded_at ordering for InstructionPhoto --- app/models.py | 11 ++- app/routes.py | 5 +- app/static/list-controls.js | 107 +++++++++++++++++++++++++ app/static/style.css | 78 +++++++++++++++++- app/templates/admin_inventory.html | 54 +++++++++---- app/templates/admin_jobs.html | 55 ++++++++++--- app/templates/admin_photos.html | 80 +++++++++++++------ app/templates/transfer_requests.html | 114 ++++++++++++++++----------- 8 files changed, 405 insertions(+), 99 deletions(-) create mode 100644 app/static/list-controls.js diff --git a/app/models.py b/app/models.py index eba7e1e..40125e8 100644 --- a/app/models.py +++ b/app/models.py @@ -132,6 +132,8 @@ class StepPart(db.Model): name="uq_step_part", ), ) + + class StationInventory(db.Model): __tablename__ = "station_inventory" @@ -147,6 +149,12 @@ class StationInventory(db.Model): nullable=False, ) quantity = db.Column(db.Integer, nullable=False, default=0) + updated_at = db.Column( + db.DateTime, + nullable=False, + default=lambda: datetime.now(timezone.utc), + onupdate=lambda: datetime.now(timezone.utc), + ) station = db.relationship("Station") part = db.relationship("Part") @@ -207,7 +215,6 @@ class ProductUnit(db.Model): ) unit_number = db.Column(db.Integer, nullable=False) - status = db.Column(db.String(20), nullable=False, default="Pending") job = db.relationship("Job") @@ -255,4 +262,4 @@ class UnitStepProgress(db.Model): "job_step_id", name="uq_unit_step_progress", ), - ) \ No newline at end of file + ) diff --git a/app/routes.py b/app/routes.py index ae9af9e..20ce6bb 100644 --- a/app/routes.py +++ b/app/routes.py @@ -323,7 +323,7 @@ def admin_photos(): ) photos = InstructionPhoto.query.order_by( - InstructionPhoto.name + InstructionPhoto.uploaded_at.desc() ).all() return render_template( @@ -449,8 +449,7 @@ def admin_inventory(): parts = Part.query.order_by(Part.name).all() inventory_rows = StationInventory.query.order_by( - StationInventory.station_id, - StationInventory.part_id, + StationInventory.updated_at.desc() ).all() return render_template( diff --git a/app/static/list-controls.js b/app/static/list-controls.js new file mode 100644 index 0000000..238d626 --- /dev/null +++ b/app/static/list-controls.js @@ -0,0 +1,107 @@ +document.addEventListener("DOMContentLoaded", function () { + document.querySelectorAll(".list-search-block").forEach(function (block) { + var limit = parseInt(block.dataset.limit, 10) || 5; + var increment = parseInt(block.dataset.increment, 10) || 10; + var rows = Array.prototype.slice.call( + block.querySelectorAll(".list-row") + ); + var searchInput = block.querySelector(".list-search-input"); + var toggleButton = block.querySelector(".list-toggle-button"); + var emptyMessage = block.querySelector(".list-empty-message"); + var visibleLimit = limit; + + function applyView() { + var query = ( + (searchInput && searchInput.value) || "" + ).trim().toLowerCase(); + + var visibleCount = 0; + + rows.forEach(function (row) { + var matches = ( + !query + || (row.dataset.search || "").indexOf(query) !== -1 + ); + + if (!matches) { + row.hidden = true; + return; + } + + var withinLimit = ( + Boolean(query) || visibleCount < visibleLimit + ); + + row.hidden = !withinLimit; + + if (withinLimit) { + visibleCount += 1; + } + }); + + if (emptyMessage) { + emptyMessage.hidden = visibleCount !== 0; + } + + if (toggleButton) { + if (query || rows.length <= limit) { + toggleButton.hidden = true; + return; + } + + var remaining = rows.length - visibleLimit; + + toggleButton.hidden = false; + + if (remaining > 0) { + var nextBatch = Math.min(increment, remaining); + + toggleButton.textContent = ( + "Show " + nextBatch + " more (" + + remaining + " remaining)" + ); + + toggleButton.dataset.mode = "more"; + } else { + toggleButton.textContent = "Show fewer"; + toggleButton.dataset.mode = "less"; + } + } + } + + if (searchInput) { + searchInput.addEventListener("input", applyView); + } + + if (toggleButton) { + toggleButton.addEventListener("click", function () { + if (toggleButton.dataset.mode === "less") { + visibleLimit = limit; + } else { + visibleLimit = Math.min( + visibleLimit + increment, + rows.length + ); + } + + applyView(); + }); + } + + applyView(); + }); + + document.querySelectorAll(".photo-preview-toggle").forEach( + function (button) { + button.addEventListener("click", function () { + var target = document.getElementById( + button.dataset.photoTarget + ); + + if (target) { + target.hidden = !target.hidden; + } + }); + } + ); +}); diff --git a/app/static/style.css b/app/static/style.css index 5e7d271..0ec4182 100644 --- a/app/static/style.css +++ b/app/static/style.css @@ -195,4 +195,80 @@ hr { border-radius: 6px; object-fit: contain; background: #ffffff; -} \ No newline at end of file +} + +.list-search-block { + margin: 16px 0; +} + +.list-search-input { + display: block; + width: 100%; + max-width: 420px; + margin-bottom: 10px; + padding: 10px; + border: 1px solid var(--border); + border-radius: 5px; + background: #ffffff; + color: var(--text); +} + +.list-toggle-button { + margin-top: 10px; + padding: 8px 14px; + border: 1px solid var(--primary); + border-radius: 5px; + background: #ffffff; + color: var(--primary); + font-weight: 600; + cursor: pointer; +} + +.list-toggle-button:hover, +.list-toggle-button:focus { + background: var(--primary); + color: #ffffff; +} + +.list-empty-message { + padding: 10px 0; + color: var(--text); + font-style: italic; +} + +.photo-name-list { + list-style: none; + margin: 16px 0; + padding: 0; + border: 1px solid var(--border); + border-radius: 8px; + background: var(--surface); +} + +.photo-name-list .list-row { + border-bottom: 1px solid var(--border); +} + +.photo-name-list .list-row:last-child { + border-bottom: 0; +} + +.photo-preview-toggle { + width: 100%; + padding: 12px 14px; + border: 0; + background: transparent; + color: var(--primary); + font-weight: 600; + text-align: left; + cursor: pointer; +} + +.photo-preview-toggle:hover, +.photo-preview-toggle:focus { + text-decoration: underline; +} + +.photo-preview { + padding: 0 14px 16px; +} diff --git a/app/templates/admin_inventory.html b/app/templates/admin_inventory.html index bf604aa..31f9fb3 100644 --- a/app/templates/admin_inventory.html +++ b/app/templates/admin_inventory.html @@ -11,6 +11,10 @@ rel="stylesheet" href="{{ url_for('static', filename='style.css') }}" > +

The Digital Factory Game

@@ -165,23 +169,43 @@

Current Station Inventory

{% if inventory_rows %} - - - - - - - - - {% for inventory in inventory_rows %} +
+ + +
StationPartUnitQuantity
- - - - + + + + - {% endfor %} -
{{ inventory.station.name }}{{ inventory.part.name }}{{ inventory.part.unit }}{{ inventory.quantity }}StationPartUnitQuantity
+ + {% for inventory in inventory_rows %} + + {{ inventory.station.name }} + {{ inventory.part.name }} + {{ inventory.part.unit }} + {{ inventory.quantity }} + + {% endfor %} + + + + + + {% else %}

No station inventory entered yet.

{% endif %} diff --git a/app/templates/admin_jobs.html b/app/templates/admin_jobs.html index efc3b52..72b5bdd 100644 --- a/app/templates/admin_jobs.html +++ b/app/templates/admin_jobs.html @@ -11,6 +11,10 @@ rel="stylesheet" href="{{ url_for('static', filename='style.css') }}" > +

The Digital Factory Game

@@ -69,20 +73,49 @@ +

Existing Jobs

{% if jobs %} - +
+ + + + + + + + + + {% for job in jobs %} + + + + + + {% endfor %} +
NameQuantityStatus
+ + {{ job.name }} + + {{ job.quantity }}{{ job.status }}
+ + + + +
{% else %}

No production jobs created yet.

{% endif %} diff --git a/app/templates/admin_photos.html b/app/templates/admin_photos.html index 20f099d..721644a 100644 --- a/app/templates/admin_photos.html +++ b/app/templates/admin_photos.html @@ -11,6 +11,10 @@ rel="stylesheet" href="{{ url_for('static', filename='style.css') }}" > +

The Digital Factory Game

@@ -73,32 +77,64 @@

Stored Instruction Photos

{% if photos %} -
- {% for photo in photos %} -
-

{{ photo.name }}

+
+ - {{ photo.name }} + {% for photo in photos %} +
  • + + + +
  • + {% endfor %} + -

    - Original file: - {{ photo.original_filename }} -

    + -

    - Uploaded: - {{ photo.uploaded_at }} -

    -
    - {% endfor %} +
    {% else %}

    No instruction photos uploaded yet.

    diff --git a/app/templates/transfer_requests.html b/app/templates/transfer_requests.html index c1f1b39..e987731 100644 --- a/app/templates/transfer_requests.html +++ b/app/templates/transfer_requests.html @@ -11,6 +11,10 @@ rel="stylesheet" href="{{ url_for('static', filename='style.css') }}" > +

    The Digital Factory Game

    @@ -19,55 +23,75 @@

    Manage station replenishment requests.

    {% if transfer_requests %} - - - - - - - - - - - +
    + - {% for transfer in transfer_requests %} +
    RequestJobStationPartRequestedTransferredStatusAction
    - - - - - - - + + + + + + + + + + + {% for transfer in transfer_requests %} + + + + + + + + - - - {% endfor %} -
    #{{ transfer.id }}{{ transfer.job.name }}{{ transfer.station.name }}{{ transfer.part.name }}{{ transfer.requested_quantity }}{{ transfer.transferred_quantity }}{{ transfer.status }}RequestJobStationPartRequestedTransferredStatusAction
    #{{ transfer.id }}{{ transfer.job.name }}{{ transfer.station.name }}{{ transfer.part.name }}{{ transfer.requested_quantity }}{{ transfer.transferred_quantity }}{{ transfer.status }} - {% if transfer.status in ["Pending", "Partially Fulfilled"] %} -
    - + {% if transfer.status in ["Pending", "Partially Fulfilled"] %} + - -
    - {% else %} - Completed - {% endif %} -
    + + + + {% else %} + Completed + {% endif %} + + + {% endfor %} + + + + + + {% else %}

    No transfer requests.

    {% endif %} -- GitLab