Entry creation!
This commit is contained in:
parent
53efc8551d
commit
2502375d32
6 changed files with 252 additions and 178 deletions
|
|
@ -9,6 +9,130 @@ from crudkit.core import normalize_payload
|
|||
|
||||
bp_entry = Blueprint("entry", __name__)
|
||||
|
||||
def _fields_for_model(model: str):
|
||||
fields: list[str] = []
|
||||
fields_spec = []
|
||||
layout = []
|
||||
|
||||
if model == "inventory":
|
||||
fields = ["label", "name", "serial", "barcode", "brand", "model",
|
||||
"device_type", "owner", "location", "condition", "image", "notes"]
|
||||
fields_spec = [
|
||||
{"name": "label", "type": "display", "label": "", "row": "label",
|
||||
"attrs": {"class": "display-6 mb-3"}, "wrap": {"class": "col"}},
|
||||
{"name": "submit", "label": "", "row": "label", "type": "template", "template": "submit_button.html",
|
||||
"wrap": {"class": "col-auto text-end me-2"}, "attrs": {"data-model": model}},
|
||||
{"name": "name", "row": "names", "label": "Name", "wrap": {"class": "col-3"},
|
||||
"attrs": {"class": "form-control"}, "label_attrs": {"class": "form-label"}},
|
||||
{"name": "serial", "row": "names", "label": "Serial #", "wrap": {"class": "col"},
|
||||
"attrs": {"class": "form-control"}, "label_attrs": {"class": "form-label"}},
|
||||
{"name": "barcode", "row": "names", "label": "Barcode #", "wrap": {"class": "col"},
|
||||
"attrs": {"class": "form-control"}, "label_attrs": {"class": "form-label"}},
|
||||
{"name": "brand", "label_spec": "{name}", "row": "device", "wrap": {"class": "col"},
|
||||
"attrs": {"class": "form-control"}, "label": "Brand", "label_attrs": {"class": "form-label"}},
|
||||
{"name": "model", "row": "device", "wrap": {"class": "col"},
|
||||
"attrs": {"class": "form-control"}, "label": "Model #", "label_attrs": {"class": "form-label"}},
|
||||
{"name": "device_type", "label_spec": "{description}", "row": "device", "wrap": {"class": "col"},
|
||||
"attrs": {"class": "form-control"}, "label": "Device Type", "label_attrs": {"class": "form-label"}},
|
||||
{"name": "owner", "row": "status", "label": "Contact", "wrap": {"class": "col"},
|
||||
"attrs": {"class": "form-control"}, "label_attrs": {"class": "form-label"},
|
||||
"label_spec": "{label}"},
|
||||
{"name": "location", "row": "status", "label": "Location", "wrap": {"class": "col"},
|
||||
"attrs": {"class": "form-control"}, "label_attrs": {"class": "form-label"},
|
||||
"label_spec": "{name} - {room_function.description}"},
|
||||
{"name": "condition", "row": "status", "label": "Condition", "wrap": {"class": "col"},
|
||||
"attrs": {"class": "form-control"}, "label_attrs": {"class": "form-label"}},
|
||||
{"name": "image", "label": "", "row": "image", "type": "template", "label_spec": "{filename}",
|
||||
"template": "image_display.html", "attrs": {"class": "img-fluid img-thumbnail h-auto"},
|
||||
"wrap": {"class": "h-100 w-100"}},
|
||||
{"name": "notes", "type": "template", "label": "Notes", "row": "notes", "wrap": {"class": "col"},
|
||||
"template": "inventory_note.html"},
|
||||
]
|
||||
layout = [
|
||||
{"name": "label", "order": 5, "attrs": {"class": "row align-items-center"}},
|
||||
{"name": "kitchen_sink", "order": 6, "attrs": {"class": "row"}},
|
||||
{"name": "everything", "order": 10, "attrs": {"class": "col"}, "parent": "kitchen_sink"},
|
||||
{"name": "names", "order": 20, "attrs": {"class": "row"}, "parent": "everything"},
|
||||
{"name": "device", "order": 30, "attrs": {"class": "row mt-2"}, "parent": "everything"},
|
||||
{"name": "status", "order": 40, "attrs": {"class": "row mt-2"}, "parent": "everything"},
|
||||
{"name": "notes", "order": 45, "attrs": {"class": "row mt-2"}, "parent": "everything"},
|
||||
{"name": "image", "order": 50, "attrs": {"class": "col-4"}, "parent": "kitchen_sink"},
|
||||
]
|
||||
|
||||
elif model.lower() == 'user':
|
||||
fields = []
|
||||
fields_spec = [
|
||||
{"name": "label", "row": "label", "label": "", "type": "display",
|
||||
"attrs": {"class": "display-6 mb-3"}, "wrap": {"class": "col"}},
|
||||
{"name": "submit", "label": "", "row": "label", "type": "template", "template": "submit_button.html",
|
||||
"wrap": {"class": "col-auto text-end me-2"}, "attrs": {"data-model": model}},
|
||||
{"name": "last_name", "label": "Last Name", "label_attrs": {"class": "form-label"},
|
||||
"attrs": {"placeholder": "Doe", "class": "form-control"}, "row": "name", "wrap": {"class": "col-3"}},
|
||||
{"name": "first_name", "label": "First Name", "label_attrs": {"class": "form-label"},
|
||||
"attrs": {"placeholder": "John", "class": "form-control"}, "row": "name", "wrap": {"class": "col-3"}},
|
||||
{"name": "title", "label": "Title", "label_attrs": {"class": "form-label"},
|
||||
"attrs": {"placeholder": "President of the Universe", "class": "form-control"},
|
||||
"row": "name", "wrap": {"class": "col-3"}},
|
||||
{"name": "supervisor", "label": "Supervisor", "label_attrs": {"class": "form-label"},
|
||||
"label_spec": "{label}", "row": "details", "wrap": {"class": "col-3"},
|
||||
"attrs": {"class": "form-control"}},
|
||||
{"name": "location", "label": "Room", "label_attrs": {"class": "form-label"},
|
||||
"label_spec": "{name} - {room_function.description}",
|
||||
"row": "details", "wrap": {"class": "col-3"}, "attrs": {"class": "form-control"}},
|
||||
{"name": "active", "label": "Active", "label_attrs": {"class": "form-check-label"},
|
||||
"row": "checkboxes", "attrs": {"class": "form-check-input"}, "wrap": {"class": "form-check"}},
|
||||
{"name": "staff", "label": "Staff Member", "label_attrs": {"class": "form-check-label"},
|
||||
"row": "checkboxes", "attrs": {"class": "form-check-input"}, "wrap": {"class": "form-check"}},
|
||||
]
|
||||
layout = [
|
||||
{"name": "label", "order": 0, "attrs": {"class": "row align-items-center"}},
|
||||
{"name": "name", "order": 10, "attrs": {"class": "row"}},
|
||||
{"name": "details", "order": 20, "attrs": {"class": "row mt-2"}},
|
||||
{"name": "checkboxes", "order": 30, "parent": "details",
|
||||
"attrs": {"class": "col d-flex flex-column justify-content-end"}},
|
||||
]
|
||||
|
||||
elif model == "worklog":
|
||||
# tell the service to eager-load precisely what the template needs
|
||||
fields = [
|
||||
"id",
|
||||
"contact.label",
|
||||
"work_item.label",
|
||||
"start_time",
|
||||
"end_time",
|
||||
"complete",
|
||||
"updates.id",
|
||||
"updates.content",
|
||||
"updates.timestamp",
|
||||
"updates.is_deleted",
|
||||
]
|
||||
fields_spec = [
|
||||
{"name": "id", "label": "", "type": "display", "label_spec": "Work Item #{id}",
|
||||
"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-auto 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",
|
||||
"label_spec": "{label}", "attrs": {"class": "form-control"}, "label_attrs": {"class": "form-label"}},
|
||||
{"name": "start_time", "type": "datetime", "attrs": {"class": "form-control"}, "row": "timestamps",
|
||||
"wrap": {"class": "col"}, "label_attrs": {"class": "form-label"}, "label": "Start"},
|
||||
{"name": "end_time", "type": "datetime", "attrs": {"class": "form-control"}, "row": "timestamps",
|
||||
"wrap": {"class": "col"}, "label_attrs": {"class": "form-label"}, "label": "End"},
|
||||
{"name": "complete", "label": "Complete", "label_attrs": {"class": "form-check-label"},
|
||||
"attrs": {"class": "form-check-input"}, "row": "timestamps", "wrap": {"class": "col form-check"}},
|
||||
{"name": "updates", "label": "Updates", "row": "updates", "label_attrs": {"class": "form-label"},
|
||||
"type": "template", "template": "update_list.html"},
|
||||
]
|
||||
layout = [
|
||||
{"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"}},
|
||||
]
|
||||
|
||||
return (fields, fields_spec, layout)
|
||||
|
||||
def _apply_worklog_updates(worklog, updates, delete_ids):
|
||||
note_cls = type(worklog).updates.property.mapper.class_
|
||||
note_svc = crudkit.crud.get_service(note_cls)
|
||||
|
|
@ -39,150 +163,109 @@ def init_entry_routes(app):
|
|||
if cls is None or model not in ["inventory", "worklog", "user"]:
|
||||
abort(404)
|
||||
|
||||
fields = {}
|
||||
fields_spec = []
|
||||
layout = []
|
||||
|
||||
if model == "inventory":
|
||||
fields["fields"] = ["label", "name", "serial", "barcode", "brand", "model",
|
||||
"device_type", "owner", "location", "condition", "image", "notes"]
|
||||
fields_spec = [
|
||||
{"name": "label", "type": "display", "label": "", "row": "label",
|
||||
"attrs": {"class": "display-6 mb-3"}, "wrap": {"class": "col"}},
|
||||
{"name": "submit", "label": "", "row": "label", "type": "template", "template": "submit_button.html",
|
||||
"wrap": {"class": "col-auto text-end me-2"}, "attrs": {"data-model": model}},
|
||||
{"name": "name", "row": "names", "label": "Name", "wrap": {"class": "col-3"},
|
||||
"attrs": {"class": "form-control"}, "label_attrs": {"class": "form-label"}},
|
||||
{"name": "serial", "row": "names", "label": "Serial #", "wrap": {"class": "col"},
|
||||
"attrs": {"class": "form-control"}, "label_attrs": {"class": "form-label"}},
|
||||
{"name": "barcode", "row": "names", "label": "Barcode #", "wrap": {"class": "col"},
|
||||
"attrs": {"class": "form-control"}, "label_attrs": {"class": "form-label"}},
|
||||
{"name": "brand", "label_spec": "{name}", "row": "device", "wrap": {"class": "col"},
|
||||
"attrs": {"class": "form-control"}, "label": "Brand", "label_attrs": {"class": "form-label"}},
|
||||
{"name": "model", "row": "device", "wrap": {"class": "col"},
|
||||
"attrs": {"class": "form-control"}, "label": "Model #", "label_attrs": {"class": "form-label"}},
|
||||
{"name": "device_type", "label_spec": "{description}", "row": "device", "wrap": {"class": "col"},
|
||||
"attrs": {"class": "form-control"}, "label": "Device Type", "label_attrs": {"class": "form-label"}},
|
||||
{"name": "owner", "row": "status", "label": "Contact", "wrap": {"class": "col"},
|
||||
"attrs": {"class": "form-control"}, "label_attrs": {"class": "form-label"},
|
||||
"label_spec": "{label}"},
|
||||
{"name": "location", "row": "status", "label": "Location", "wrap": {"class": "col"},
|
||||
"attrs": {"class": "form-control"}, "label_attrs": {"class": "form-label"},
|
||||
"label_spec": "{name} - {room_function.description}"},
|
||||
{"name": "condition", "row": "status", "label": "Condition", "wrap": {"class": "col"},
|
||||
"attrs": {"class": "form-control"}, "label_attrs": {"class": "form-label"}},
|
||||
{"name": "image", "label": "", "row": "image", "type": "template", "label_spec": "{filename}",
|
||||
"template": "image_display.html", "attrs": {"class": "img-fluid img-thumbnail h-auto"},
|
||||
"wrap": {"class": "h-100 w-100"}},
|
||||
{"name": "notes", "type": "template", "label": "Notes", "row": "notes", "wrap": {"class": "col"},
|
||||
"template": "inventory_note.html"},
|
||||
]
|
||||
layout = [
|
||||
{"name": "label", "order": 5, "attrs": {"class": "row align-items-center"}},
|
||||
{"name": "kitchen_sink", "order": 6, "attrs": {"class": "row"}},
|
||||
{"name": "everything", "order": 10, "attrs": {"class": "col"}, "parent": "kitchen_sink"},
|
||||
{"name": "names", "order": 20, "attrs": {"class": "row"}, "parent": "everything"},
|
||||
{"name": "device", "order": 30, "attrs": {"class": "row mt-2"}, "parent": "everything"},
|
||||
{"name": "status", "order": 40, "attrs": {"class": "row mt-2"}, "parent": "everything"},
|
||||
{"name": "notes", "order": 45, "attrs": {"class": "row mt-2"}, "parent": "everything"},
|
||||
{"name": "image", "order": 50, "attrs": {"class": "col-4"}, "parent": "kitchen_sink"},
|
||||
]
|
||||
|
||||
elif model.lower() == 'user':
|
||||
fields["fields"] = ["label", "first_name", "last_name", "title", "active", "staff", "location", "supervisor"]
|
||||
fields_spec = [
|
||||
{"name": "label", "row": "label", "label": "", "type": "display",
|
||||
"attrs": {"class": "display-6 mb-3"}, "wrap": {"class": "col"}},
|
||||
{"name": "submit", "label": "", "row": "label", "type": "template", "template": "submit_button.html",
|
||||
"wrap": {"class": "col-auto text-end me-2"}, "attrs": {"data-model": model}},
|
||||
{"name": "last_name", "label": "Last Name", "label_attrs": {"class": "form-label"},
|
||||
"attrs": {"placeholder": "Doe", "class": "form-control"}, "row": "name", "wrap": {"class": "col-3"}},
|
||||
{"name": "first_name", "label": "First Name", "label_attrs": {"class": "form-label"},
|
||||
"attrs": {"placeholder": "John", "class": "form-control"}, "row": "name", "wrap": {"class": "col-3"}},
|
||||
{"name": "title", "label": "Title", "label_attrs": {"class": "form-label"},
|
||||
"attrs": {"placeholder": "President of the Universe", "class": "form-control"},
|
||||
"row": "name", "wrap": {"class": "col-3"}},
|
||||
{"name": "supervisor", "label": "Supervisor", "label_attrs": {"class": "form-label"},
|
||||
"label_spec": "{label}", "row": "details", "wrap": {"class": "col-3"},
|
||||
"attrs": {"class": "form-control"}},
|
||||
{"name": "location", "label": "Room", "label_attrs": {"class": "form-label"},
|
||||
"label_spec": "{name} - {room_function.description}",
|
||||
"row": "details", "wrap": {"class": "col-3"}, "attrs": {"class": "form-control"}},
|
||||
{"name": "active", "label": "Active", "label_attrs": {"class": "form-check-label"},
|
||||
"row": "checkboxes", "attrs": {"class": "form-check-input"}, "wrap": {"class": "form-check"}},
|
||||
{"name": "staff", "label": "Staff Member", "label_attrs": {"class": "form-check-label"},
|
||||
"row": "checkboxes", "attrs": {"class": "form-check-input"}, "wrap": {"class": "form-check"}},
|
||||
]
|
||||
layout = [
|
||||
{"name": "label", "order": 0, "attrs": {"class": "row align-items-center"}},
|
||||
{"name": "name", "order": 10, "attrs": {"class": "row"}},
|
||||
{"name": "details", "order": 20, "attrs": {"class": "row mt-2"}},
|
||||
{"name": "checkboxes", "order": 30, "parent": "details",
|
||||
"attrs": {"class": "col d-flex flex-column justify-content-end"}},
|
||||
]
|
||||
|
||||
elif model == "worklog":
|
||||
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", "wrap": {"class": "col"}},
|
||||
{"name": "submit", "label": "", "row": "label", "type": "template", "template": "submit_button.html",
|
||||
"wrap": {"class": "col-auto 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",
|
||||
"label_spec": "{label}", "attrs": {"class": "form-control"}, "label_attrs": {"class": "form-label"}},
|
||||
{"name": "start_time", "type": "datetime", "attrs": {"class": "form-control"}, "row": "timestamps",
|
||||
"wrap": {"class": "col"}, "label_attrs": {"class": "form-label"}, "label": "Start"},
|
||||
{"name": "end_time", "type": "datetime", "attrs": {"class": "form-control"}, "row": "timestamps",
|
||||
"wrap": {"class": "col"}, "label_attrs": {"class": "form-label"}, "label": "End"},
|
||||
{"name": "complete", "label": "Complete", "label_attrs": {"class": "form-check-label"},
|
||||
"attrs": {"class": "form-check-input"}, "row": "timestamps", "wrap": {"class": "col form-check"}},
|
||||
{"name": "updates", "label": "Updates", "row": "updates", "label_attrs": {"class": "form-label"},
|
||||
"type": "template", "template": "update_list.html"},
|
||||
]
|
||||
layout = [
|
||||
{"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"}},
|
||||
]
|
||||
fields, fields_spec, layout = _fields_for_model(model)
|
||||
|
||||
svc = crudkit.crud.get_service(cls)
|
||||
obj = svc.get(id, fields)
|
||||
params = {"fields": fields} if fields else {}
|
||||
obj = svc.get(id, params)
|
||||
if obj is None:
|
||||
abort(404)
|
||||
|
||||
# Use the scoped_session proxy so teardown .remove() cleans it up
|
||||
ScopedSession = current_app.extensions["crudkit"]["Session"]
|
||||
|
||||
if model == "worklog":
|
||||
updates_cls = type(obj).updates.property.mapper.class_
|
||||
updates_q = (ScopedSession.query(updates_cls)
|
||||
.filter(updates_cls.work_log_id == obj.id,
|
||||
updates_cls.is_deleted == False)
|
||||
.order_by(updates_cls.timestamp.asc()))
|
||||
all_updates = updates_q.all()
|
||||
|
||||
for f in fields_spec:
|
||||
if f.get("name") == "updates" and f.get("type") == "template":
|
||||
ctx = dict(f.get("template_ctx") or {})
|
||||
ctx["updates"] = all_updates
|
||||
f["template_ctx"] = ctx
|
||||
break
|
||||
|
||||
form = render_form(
|
||||
cls,
|
||||
obj.as_dict(),
|
||||
session=ScopedSession, # ← fixed: pass scoped proxy, not svc.session
|
||||
session=ScopedSession,
|
||||
instance=obj,
|
||||
fields_spec=fields_spec,
|
||||
layout=layout,
|
||||
submit_attrs={"class": "d-none", "disable": True}
|
||||
)
|
||||
|
||||
return render_template("entry.html", form=form)
|
||||
|
||||
@bp_entry.get("/entry/<model>/new")
|
||||
def entry_new(model: str):
|
||||
cls = crudkit.crud.get_model(model)
|
||||
if cls is None or model not in ["inventory", "worklog", "user"]:
|
||||
abort(404)
|
||||
|
||||
fields, fields_spec, layout = _fields_for_model(model)
|
||||
|
||||
if model == "worklog":
|
||||
for f in fields_spec:
|
||||
if f.get("name") == "id" and f.get("type") == "display":
|
||||
f["label_spec"] = "New Work Item"
|
||||
break
|
||||
elif model == "inventory":
|
||||
for f in fields_spec:
|
||||
if f.get("name") == "label" and f.get("type") == "display":
|
||||
f["label"] = ""
|
||||
f["label_spec"] = "New Inventory Item"
|
||||
break
|
||||
elif model == "user":
|
||||
for f in fields_spec:
|
||||
if f.get("name") == "label" and f.get("type") == "display":
|
||||
f["label"] = ""
|
||||
f["label_spec"] = "New User"
|
||||
break
|
||||
|
||||
obj = cls()
|
||||
ScopedSession = current_app.extensions["crudkit"]["Session"]
|
||||
|
||||
form = render_form(
|
||||
cls,
|
||||
obj.as_dict() if hasattr(obj, "as_dict") else {},
|
||||
session=ScopedSession,
|
||||
instance=obj,
|
||||
fields_spec=fields_spec,
|
||||
layout=layout,
|
||||
submit_attrs={"class": "d-none", "disabled": True},
|
||||
)
|
||||
|
||||
return render_template("entry.html", form=form)
|
||||
|
||||
@bp_entry.post("/entry/<model>")
|
||||
def create_entry(model: str):
|
||||
try:
|
||||
if model not in ["inventory", "user", "worklog"]:
|
||||
raise TypeError("Invalid model.")
|
||||
cls = crudkit.crud.get_model(model)
|
||||
svc = crudkit.crud.get_service(cls)
|
||||
sess = svc.session
|
||||
|
||||
payload = normalize_payload(request.get_json(force=True) or {}, cls)
|
||||
|
||||
# Child mutations and friendly-to-FK mapping
|
||||
updates = payload.pop("updates", []) or []
|
||||
payload.pop("delete_update_ids", None) # irrelevant on create
|
||||
|
||||
if model == "worklog":
|
||||
if "contact" in payload and "contact_id" not in payload:
|
||||
payload["contact_id"] = payload.pop("contact")
|
||||
if "work_item" in payload and "work_item_id" not in payload:
|
||||
payload["work_item_id"] = payload.pop("work_item")
|
||||
|
||||
# Parent first, no commit yet
|
||||
obj = svc.create(payload, actor="create_entry", commit=False)
|
||||
|
||||
# Children
|
||||
if model == "worklog" and updates:
|
||||
note_cls = type(obj).updates.property.mapper.class_
|
||||
for item in updates:
|
||||
content = (item.get("content") or "").trim() if hasattr(str, 'trim') else (item.get("content") or "").strip()
|
||||
if content:
|
||||
sess.add(note_cls(work_log_id=obj.id, content=content))
|
||||
|
||||
sess.commit()
|
||||
return {"status": "success", "id": obj.id}
|
||||
except Exception as e:
|
||||
try:
|
||||
crudkit.crud.get_service(crudkit.crud.get_model(model)).session.rollback()
|
||||
except Exception:
|
||||
pass
|
||||
return {"status": "failure", "error": str(e)}, 400
|
||||
|
||||
@bp_entry.post("/entry/<model>/<int:id>")
|
||||
def update_entry(model, id):
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<label class="form-label">Notes</label>
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div class="me-3 w-100" id="editContainer"></div>
|
||||
<div class="me-3 w-100 markdown-body" id="editContainer"></div>
|
||||
<script type="application/json" id="noteContent">{{ field['template_ctx']['values']['notes'] | tojson }}</script>
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" class="form-check-input" id="editSwitch" onchange="changeMode()" role="switch"
|
||||
|
|
@ -9,6 +9,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/github-markdown-css@5/github-markdown.min.css">
|
||||
<style>
|
||||
textarea.auto-md {
|
||||
box-sizing: border-box;
|
||||
|
|
@ -45,7 +46,7 @@
|
|||
function renderView(md) {
|
||||
const container = document.getElementById('editContainer');
|
||||
if (!container) return;
|
||||
const html = marked.parse(md || "");
|
||||
const html = marked.parse(md || "", {gfm: true});
|
||||
container.innerHTML = DOMPurify.sanitize(html, { ADD_ATTR: ['target', 'rel'] });
|
||||
for (const a of container.querySelectorAll('a[href]')) {
|
||||
a.setAttribute('target', '_blank');
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@
|
|||
function formToJson(form) {
|
||||
const fd = new FormData(form);
|
||||
const out = {};
|
||||
|
||||
// base values
|
||||
fd.forEach((value, key) => {
|
||||
if (key in out) {
|
||||
if (!Array.isArray(out[key])) out[key] = [out[key]];
|
||||
|
|
@ -16,31 +14,21 @@
|
|||
out[key] = value;
|
||||
}
|
||||
});
|
||||
|
||||
// normalize radios and checkboxes
|
||||
form.querySelectorAll('input[type="checkbox"], input[type="radio"]').forEach(el => {
|
||||
if (!el.name) return;
|
||||
|
||||
if (el.type === 'radio') {
|
||||
if (out[el.name] !== undefined) return; // already set for this group
|
||||
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;
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
out[el.name] = group.length > 1
|
||||
? Array.from(group).filter(i => i.checked).map(i => i.value ?? true)
|
||||
: el.checked;
|
||||
}
|
||||
});
|
||||
|
||||
console.log(out);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
|
@ -49,58 +37,61 @@
|
|||
.map(el => Number(el.id.slice(3)))
|
||||
.filter(Number.isFinite);
|
||||
}
|
||||
|
||||
function collectEditedUpdates() {
|
||||
const updates = [];
|
||||
for (const id of collectExistingUpdateIds()) {
|
||||
updates.push({ id, content: getMarkdown(id) }); // ensure getMarkdown exists
|
||||
}
|
||||
for (const md of (window.newDrafts || [])) {
|
||||
if ((md ?? '').trim()) updates.push({ content: md });
|
||||
}
|
||||
for (const id of collectExistingUpdateIds()) updates.push({ id, content: getMarkdown(id) });
|
||||
for (const md of (window.newDrafts || [])) if ((md ?? '').trim()) updates.push({ content: md });
|
||||
return updates;
|
||||
}
|
||||
function collectDeletedIds() { return (window.deletedIds || []).filter(Number.isFinite); }
|
||||
|
||||
function collectDeletedIds() {
|
||||
return (window.deletedIds || []).filter(Number.isFinite);
|
||||
}
|
||||
// much simpler, and correct
|
||||
const formEl = document.getElementById({{ (field['attrs']['data-model'] ~ '_form') | tojson }});
|
||||
const model = {{ field['attrs']['data-model'] | tojson }};
|
||||
const idVal = {{ field['template_ctx']['values'].get('id') | tojson }};
|
||||
const hasId = idVal !== null && idVal !== undefined;
|
||||
|
||||
document.getElementById("{{ field['attrs']['data-model'] }}_form").addEventListener("submit", async e => {
|
||||
// Never call url_for for update on the "new" page.
|
||||
// Create URL is fine to build server-side:
|
||||
const createUrl = {{ url_for('entry.create_entry', model=field['attrs']['data-model']) | tojson }};
|
||||
// Update URL is assembled on the client to avoid BuildError on "new":
|
||||
const updateUrl = hasId ? `/entry/${model}/${idVal}` : null;
|
||||
|
||||
formEl.addEventListener("submit", async e => {
|
||||
e.preventDefault();
|
||||
|
||||
const json = formToJson(e.target);
|
||||
json.id = {{ field['template_ctx']['values']['id'] }};
|
||||
const json = formToJson(formEl);
|
||||
|
||||
const model_name = {{ field['attrs']['data-model'] | tojson }};
|
||||
|
||||
if(model_name === 'inventory') {
|
||||
if (model === 'inventory' && typeof getMarkdown === 'function') {
|
||||
json.notes = getMarkdown().trim();
|
||||
} else if (model_name === 'worklog') {
|
||||
// child mutations
|
||||
} else if (model === 'worklog') {
|
||||
json.updates = collectEditedUpdates();
|
||||
json.delete_update_ids = collectDeletedIds();
|
||||
}
|
||||
|
||||
try {
|
||||
const 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),
|
||||
}
|
||||
);
|
||||
if (hasId) json.id = idVal;
|
||||
|
||||
const reply = await response.json();
|
||||
const url = hasId ? updateUrl : createUrl;
|
||||
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(json),
|
||||
});
|
||||
const reply = await res.json();
|
||||
if (reply.status === 'success') {
|
||||
toastMessage('This entry has been successfully saved!', 'success');
|
||||
window.newDrafts = [];
|
||||
window.deletedIds = [];
|
||||
if (!hasId && reply.id) {
|
||||
window.location.href = `/entry/${model}/${reply.id}?pretty=1`;
|
||||
}
|
||||
} else {
|
||||
toastMessage(`Unable to save entry: ${reply.error}`, 'danger');
|
||||
}
|
||||
} catch (err) {
|
||||
toastMessage(`Network error: ${String(err)}`, 'danger');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
{% for n in items %}
|
||||
<li class="list-group-item">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div class="me-3 w-100" id="editContainer{{ n.id }}"></div>
|
||||
<div class="me-3 w-100 markdown-body" id="editContainer{{ n.id }}"></div>
|
||||
<script type="application/json" id="md-{{ n.id }}">{{ n.content | tojson }}</script>
|
||||
|
||||
<div class="d-flex flex-column align-items-end">
|
||||
|
|
@ -25,6 +25,7 @@
|
|||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/github-markdown-css@5/github-markdown.min.css">
|
||||
<style>
|
||||
textarea.auto-md {
|
||||
box-sizing: border-box;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue