Enhance inventory management by updating timestamp type to DateTime, adding stale worklog display on the index page, and improving template styles and scripts for better user experience.

This commit is contained in:
Yaro Kasear 2025-06-16 10:26:30 -05:00
parent 0835248f34
commit 58754c71bd
7 changed files with 73 additions and 16 deletions

View file

@ -6,7 +6,7 @@ if TYPE_CHECKING:
from .work_log import WorkLog from .work_log import WorkLog
from .rooms import Room from .rooms import Room
from sqlalchemy import Boolean, ForeignKeyConstraint, ForeignKey, Identity, Index, Integer, PrimaryKeyConstraint, String, Unicode, text from sqlalchemy import Boolean, ForeignKeyConstraint, ForeignKey, Identity, Index, Integer, PrimaryKeyConstraint, String, Unicode, DateTime, text
from sqlalchemy.dialects.mssql import DATETIME2, MONEY from sqlalchemy.dialects.mssql import DATETIME2, MONEY
from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.orm import Mapped, mapped_column, relationship
import datetime import datetime
@ -20,7 +20,7 @@ class Inventory(db.Model):
) )
id: Mapped[int] = mapped_column("ID", Integer, Identity(start=1, increment=1), primary_key=True) id: Mapped[int] = mapped_column("ID", Integer, Identity(start=1, increment=1), primary_key=True)
timestamp: Mapped[datetime.datetime] = mapped_column('Date Entered', DATETIME2) timestamp: Mapped[datetime.datetime] = mapped_column('Date Entered', DateTime)
condition: Mapped[str] = mapped_column('Working Condition', Unicode(255)) condition: Mapped[str] = mapped_column('Working Condition', Unicode(255))
needed: Mapped[str] = mapped_column("Needed", Unicode(255)) needed: Mapped[str] = mapped_column("Needed", Unicode(255))
type_id: Mapped[int] = mapped_column('Item Type', Integer, ForeignKey("Items.ID")) type_id: Mapped[int] = mapped_column('Item Type', Integer, ForeignKey("Items.ID"))

View file

@ -1,11 +1,11 @@
from flask import Blueprint, render_template, url_for, request, redirect from flask import Blueprint, render_template, url_for, request, redirect
from .models import Area, Brand, Item, Inventory, RoomFunction, User, WorkLog, Room from .models import Brand, Item, Inventory, RoomFunction, User, WorkLog, Room
import html
from sqlalchemy import or_ from sqlalchemy import or_
from sqlalchemy.orm import aliased 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
from datetime import datetime, timedelta
main = Blueprint('main', __name__) main = Blueprint('main', __name__)
@ -130,7 +130,27 @@ def render_paginated_table(
@main.route("/") @main.route("/")
def index(): def index():
return render_template("index.html", title="Inventory Manager") stale_worklog_page = request.args.get('stale_worklog_page', 1, int)
cutoff = datetime.utcnow() - timedelta(days=14)
worklog_query = eager_load_worklog_relationships(
db.session.query(WorkLog)
).filter(
(WorkLog.start_time < cutoff) & (WorkLog.complete == False)
)
stale_pagination = make_paginated_data(worklog_query, stale_worklog_page, 3)
stale_count = len(worklog_query.all())
stale_worklog_headers = {k: v for k, v in worklog_headers.items() if k not in ['End Time', 'Quick Analysis?', 'Complete?', 'Follow Up?']}
return render_template(
"index.html",
title="Inventory Manager",
stale_pagination=stale_pagination,
stale_count=stale_count,
stale_worklog_headers=stale_worklog_headers,
stale_worklog_rows=[{"id": log.id, "cells": [fn(log) for fn in stale_worklog_headers.values()]} for log in stale_pagination['items']],
)
def link(text, endpoint, **values): def link(text, endpoint, **values):
return {"text": text, "url": url_for(endpoint, **values)} return {"text": text, "url": url_for(endpoint, **values)}
@ -338,10 +358,12 @@ def search():
UserAlias.last_name.ilike(f"%{query}%") UserAlias.last_name.ilike(f"%{query}%")
)) ))
inventory_pagination = make_paginated_data(inventory_query, inventory_page) inventory_pagination = make_paginated_data(inventory_query, inventory_page)
user_query = eager_load_user_relationships(db.session.query(User)).filter( user_query = eager_load_user_relationships(db.session.query(User).join(UserAlias, User.supervisor)).filter(
or_( or_(
User.first_name.ilike(f"%{query}%"), User.first_name.ilike(f"%{query}%"),
User.last_name.ilike(f"%{query}%") User.last_name.ilike(f"%{query}%"),
UserAlias.first_name.ilike(f"%{query}%"),
UserAlias.last_name.ilike(f"%{query}%")
)) ))
user_pagination = make_paginated_data(user_query, user_page) user_pagination = make_paginated_data(user_query, user_page)
worklog_query = eager_load_worklog_relationships(db.session.query(WorkLog).join(UserAlias, WorkLog.contact).join(InventoryAlias, WorkLog.work_item)).filter( worklog_query = eager_load_worklog_relationships(db.session.query(WorkLog).join(UserAlias, WorkLog.contact).join(InventoryAlias, WorkLog.work_item)).filter(

View file

@ -22,7 +22,7 @@
{% if cell.type == 'bool' %} {% if cell.type == 'bool' %}
{{ cell.html | safe }} {{ cell.html | safe }}
{% elif cell.url %} {% elif cell.url %}
<a href="{{ cell.url }}">{{ cell.text }}</a> <a class="link-success link-underline-opacity-0" href="{{ cell.url }}">{{ cell.text }}</a>
{% else %} {% else %}
{{ cell.text or '-' }} {{ cell.text or '-' }}
{% endif %} {% endif %}

View file

@ -7,5 +7,31 @@
<div class="container text-center"> <div class="container text-center">
<h1 class="display-4">Welcome to Inventory Manager</h1> <h1 class="display-4">Welcome to Inventory Manager</h1>
<p class="lead">Find out about all of your assets.</p> <p class="lead">Find out about all of your assets.</p>
<div class="row">
{% if stale_pagination['items'] %}
<div class="col">
<div class="card">
<div class="card-body">
<h5 class="card-title">Stale Worklogs</h5>
<h6 class="card-subtitle mb-2 text-body-secondary">You have {{ stale_count }} worklogs
that need attention!</h6>
{{ tables.render_table(
stale_worklog_headers,
stale_worklog_rows,
'index'
)}}
{{ tables.render_pagination(
'index',
stale_pagination['page'],
stale_pagination['has_prev'],
stale_pagination['has_next'],
stale_pagination['total_pages'],
page_variable='stale_worklog_page'
)}}
</div>
</div>
</div>
{% endif %}
</div>
</div> </div>
{% endblock %} {% endblock %}

View file

@ -14,9 +14,7 @@
<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;
@ -43,18 +41,16 @@
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">
<nav class="navbar navbar-expand bg-body-tertiary border-bottom"> <nav class="navbar navbar-expand bg-body-tertiary border-bottom">
<div class="container-fluid"> <div class="container-fluid">
<span class="navbar-brand"> <a class="navbar-brand" href="{{ url_for('index') }}">
Inventory Manager Inventory Manager
</span> </a>
<button class="navbar-toggler"> <button class="navbar-toggler">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
@ -76,6 +72,9 @@
<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="https://cdnjs.cloudflare.com/ajax/libs/mark.js/8.11.1/mark.min.js"
integrity="sha512-5CYOlHXGh6QpOFA/TeTylKLWfB3ftPsde7AnmhuitiTX4K5SqCLBeKro6sPS8ilsz1Q4NRx3v8Ko2IBiszzdww=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="{{ url_for('static', filename='js/datalist.js') }}"></script> <script src="{{ url_for('static', filename='js/datalist.js') }}"></script>
<script> <script>
const searchInput = document.querySelector('#search'); const searchInput = document.querySelector('#search');

View file

@ -76,4 +76,14 @@ title=title,
</div> </div>
{% endif %} {% endif %}
</div> </div>
{% endblock %}
{% block script %}
{% if query %}
const query = "{{ query|e }}";
if (query) {
const instance = new Mark(document.querySelector("main"));
instance.mark(query);
}
{% endif %}
{% endblock %} {% endblock %}