Addint submit and toast behavior.
This commit is contained in:
parent
fc4d3ebfe6
commit
dbf0d6169a
6 changed files with 137 additions and 22 deletions
|
|
@ -1189,5 +1189,6 @@ def render_form(
|
|||
values=values_map,
|
||||
render_field=render_field,
|
||||
submit_attrs=submit_attrs,
|
||||
submit_label=submit_label
|
||||
submit_label=submit_label,
|
||||
model_name=model_cls.__name__
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<form method="POST">
|
||||
<form method="POST" id="{{ model_name|lower }}_form">
|
||||
{% macro render_row(row) %}
|
||||
<!-- {{ row.name }} (row) -->
|
||||
{% if row.fields or row.children or row.legend %}
|
||||
|
|
|
|||
|
|
@ -96,7 +96,9 @@ def init_entry_routes(app):
|
|||
fields["fields"] = ["id", "contact", "work_item", "start_time", "end_time", "complete"]
|
||||
fields_spec = [
|
||||
{"name": "id", "label": "", "type": "display", "label_spec": "Work Item #{id}",
|
||||
"attrs": {"class": "display-6 mb-3"}, "row": "label"},
|
||||
"attrs": {"class": "display-6 mb-3"}, "row": "label", "wrap": {"class": "col"}},
|
||||
{"name": "submit", "label": "", "row": "label", "type": "template", "template": "submit_button.html",
|
||||
"wrap": {"class": "col text-end me-2"}, "attrs": {"data-model": model}},
|
||||
{"name": "contact", "row": "ownership", "wrap": {"class": "col"}, "label": "Contact",
|
||||
"label_spec": "{label}", "attrs": {"class": "form-control"}, "label_attrs": {"class": "form-label"}},
|
||||
{"name": "work_item", "row": "ownership", "wrap": {"class": "col"}, "label": "Work Item",
|
||||
|
|
@ -111,7 +113,7 @@ def init_entry_routes(app):
|
|||
"type": "template", "template": "update_list.html"},
|
||||
]
|
||||
layout = [
|
||||
{"name": "label", "order": 0},
|
||||
{"name": "label", "order": 0, "attrs": {"class": "row align-items-center"}},
|
||||
{"name": "ownership", "order": 10, "attrs": {"class": "row mb-2"}},
|
||||
{"name": "timestamps", "order": 20, "attrs": {"class": "row d-flex align-items-center"}},
|
||||
{"name": "updates", "order": 30, "attrs": {"class": "row"}},
|
||||
|
|
@ -132,7 +134,6 @@ def init_entry_routes(app):
|
|||
updates_cls.is_deleted == False)
|
||||
.order_by(updates_cls.timestamp.asc()))
|
||||
all_updates = updates_q.all()
|
||||
print(all_updates)
|
||||
|
||||
for f in fields_spec:
|
||||
if f.get("name") == "updates" and f.get("type") == "template":
|
||||
|
|
@ -141,8 +142,6 @@ def init_entry_routes(app):
|
|||
f["template_ctx"] = ctx
|
||||
break
|
||||
|
||||
print(fields_spec)
|
||||
|
||||
form = render_form(
|
||||
cls,
|
||||
obj.as_dict(),
|
||||
|
|
@ -150,13 +149,34 @@ def init_entry_routes(app):
|
|||
instance=obj,
|
||||
fields_spec=fields_spec,
|
||||
layout=layout,
|
||||
submit_attrs={"class": "btn btn-primary mt-3"},
|
||||
submit_attrs={"class": "d-none", "disabled": True},
|
||||
)
|
||||
# sanity log
|
||||
u = getattr(obj, "updates", None)
|
||||
print("WORKLOG UPDATES loaded? ",
|
||||
"None" if u is None else f"len={len(list(u))} ids={[n.id for n in list(u)]}")
|
||||
|
||||
return render_template("entry.html", form=form)
|
||||
|
||||
@bp_entry.post("/entry/<model>/<int:id>")
|
||||
def update_entry(model, id):
|
||||
try:
|
||||
if model not in ["inventory", "user", "worklog"]:
|
||||
raise TypeError("Invalid model.")
|
||||
payload = request.get_json()
|
||||
cls = crudkit.crud.get_model(model)
|
||||
|
||||
if model == "inventory":
|
||||
pass
|
||||
elif model == "user":
|
||||
pass
|
||||
elif model == "worklog":
|
||||
pass
|
||||
else:
|
||||
raise TypeError("Invalid model.")
|
||||
|
||||
service = crudkit.crud.get_service(cls)
|
||||
item = service.get(id)
|
||||
print(item.as_dict(), payload)
|
||||
|
||||
return {"status": "success", "payload": payload}
|
||||
except Exception as e:
|
||||
return {"status": "failure", "error": str(e)}
|
||||
|
||||
app.register_blueprint(bp_entry)
|
||||
|
|
|
|||
|
|
@ -89,8 +89,6 @@ def inventory_spares():
|
|||
)
|
||||
rows = session.execute(stmt).all()
|
||||
|
||||
print(rows)
|
||||
|
||||
items = []
|
||||
for dev, dep, avail in rows:
|
||||
dep = int(dep or 0)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@
|
|||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{% block title %}{{ title if title else "Inventory Manager" }}{% endblock %}</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||
integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
|
||||
<style>
|
||||
{% block style %}
|
||||
{% endblock %}
|
||||
|
|
@ -22,13 +23,16 @@
|
|||
</a>
|
||||
<ul class="navbar-nav me-auto">
|
||||
<li class="nav-item">
|
||||
<a href="{{ url_for('listing.show_list', model='inventory') }}" class="nav-link link-success fw-semibold">Inventory</a>
|
||||
<a href="{{ url_for('listing.show_list', model='inventory') }}"
|
||||
class="nav-link link-success fw-semibold">Inventory</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="{{ url_for('listing.show_list', model='worklog') }}" class="nav-link link-success fw-semibold">Work Log</a>
|
||||
<a href="{{ url_for('listing.show_list', model='worklog') }}"
|
||||
class="nav-link link-success fw-semibold">Work Log</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="{{ url_for('listing.show_list', model='user') }}" class="nav-link link-success fw-semibold">Users</a>
|
||||
<a href="{{ url_for('listing.show_list', model='user') }}"
|
||||
class="nav-link link-success fw-semibold">Users</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% block header %}
|
||||
|
|
@ -52,18 +56,23 @@
|
|||
{% block postmain %}
|
||||
{% endblock %}
|
||||
|
||||
<div class="toast-container position-fixed bottom-0 end p-3" id="toastContainer"></div>
|
||||
|
||||
<footer class="bg-body-tertiary border border-bottom-0 position-fixed bottom-0 w-100 pb-1">
|
||||
<small>
|
||||
<span class="align-middle">© 2025 Conrad Nelson •
|
||||
<a href="/LICENSE" class="link-underline link-underline-opacity-0">AGPL-3.0-or-later</a> •
|
||||
<a href="https://git.kasear.net/yaro/inventory" class="link-underline link-underline-opacity-0">Source Code</a>
|
||||
<a href="https://git.kasear.net/yaro/inventory" class="link-underline link-underline-opacity-0">Source
|
||||
Code</a>
|
||||
</span>
|
||||
{% block footer %}
|
||||
{% endblock %}
|
||||
</small>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI"
|
||||
crossorigin="anonymous"></script>
|
||||
{% block scriptincludes %}
|
||||
{% endblock %}
|
||||
<script>
|
||||
|
|
@ -85,6 +94,33 @@
|
|||
location.href = `{{ url_for('search.search') }}?q=${searchText.value}`;
|
||||
});
|
||||
|
||||
toastNumber = 0;
|
||||
|
||||
window.toastMessage = function (message, title, type = 'info') {
|
||||
const container = document.getElementById('toastContainer');
|
||||
const now = new Date();
|
||||
const timestamp = now.toLocaleString('en-US', { dateStyle: 'medium', timeStyle: 'short' });
|
||||
|
||||
const id = `toast${window.toastNumber++}`;
|
||||
|
||||
const template = `
|
||||
<div class="toast text-bg-${type}" id="${id}" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="toast-header">
|
||||
<strong class="me-auto">${title}</strong>
|
||||
<small class="text-body-secondary">${timestamp}</small>
|
||||
<button type="button" class="btn-close ms-2 mb-1" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="toast-body">${message}</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
container.insertAdjacentHTML('beforeend', template);
|
||||
|
||||
const el = document.getElementById(id);
|
||||
const toast = new bootstrap.Toast(el, { autohide: true, delay: 4000 });
|
||||
toast.show();
|
||||
};
|
||||
|
||||
{% block script %}
|
||||
{% endblock %}
|
||||
})
|
||||
|
|
|
|||
60
inventory/templates/submit_button.html
Normal file
60
inventory/templates/submit_button.html
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
<button type="submit" class="btn btn-primary" id="submit">Save</button>
|
||||
<script>
|
||||
function formToJson(form) {
|
||||
const fd = new FormData(form);
|
||||
const out = {};
|
||||
|
||||
fd.forEach((value, key) => {
|
||||
if (key in out) {
|
||||
if (!Array.isArray(out[key])) out[key] = [out[key]];
|
||||
out[key].push(value);
|
||||
} else {
|
||||
out[key] = value;
|
||||
}
|
||||
});
|
||||
|
||||
form.querySelectorAll('input[type="checkbox"], input[type="radio"]').forEach(el => {
|
||||
if (!el.name) return;
|
||||
|
||||
if (el.type === 'radio') {
|
||||
if (out[el.name] !== undefined) return;
|
||||
const checked = form.querySelector(`input[type="radio"][name="${CSS.escape(el.name)}"]:checked`);
|
||||
if (checked) out[el.name] = checked.value ?? true;
|
||||
}
|
||||
|
||||
if (el.type === 'checkbox') {
|
||||
const group = form.querySelectorAll(`input[type="checkbox"][name="${CSS.escape(el.name)}"]`);
|
||||
if (group.length > 1) {
|
||||
const checkedVals = Array.from(group)
|
||||
.filter(i => i.checked)
|
||||
.map(i => i.value ?? true);
|
||||
out[el.name] = checkedVals;
|
||||
} else {
|
||||
out[el.name] = el.checked;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
document.getElementById("{{ field['attrs']['data-model'] }}_form").addEventListener("submit", async e => {
|
||||
e.preventDefault();
|
||||
const json = formToJson(e.target);
|
||||
|
||||
response = await fetch("{{ url_for('entry.update_entry', id=field['template_ctx']['values']['id'], model=field['attrs']['data-model']) }}", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(json)
|
||||
});
|
||||
|
||||
reply = await response.json();
|
||||
if (reply['status'] === 'success') {
|
||||
console.log("WELL DONE!")
|
||||
} else {
|
||||
console.log("YOU HAVE FAILED!")
|
||||
}
|
||||
});
|
||||
</script>
|
||||
Loading…
Add table
Add a link
Reference in a new issue