New combobox placement for room editor.
This commit is contained in:
parent
1ccdf89739
commit
5a6125167c
7 changed files with 104 additions and 28 deletions
|
|
@ -6,7 +6,7 @@ if TYPE_CHECKING:
|
|||
from .users import User
|
||||
|
||||
from sqlalchemy import ForeignKey, Identity, Integer, Unicode
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship, joinedload, selectinload
|
||||
|
||||
from . import db
|
||||
|
||||
|
|
@ -26,6 +26,22 @@ class Room(ValidatableMixin, db.Model):
|
|||
inventory: Mapped[List['Inventory']] = relationship('Inventory', back_populates='location')
|
||||
users: Mapped[List['User']] = relationship('User', back_populates='location')
|
||||
|
||||
ui_label_attr = 'name'
|
||||
ui_eagerload = tuple()
|
||||
ui_extra_attrs = ('area_id', 'function_id')
|
||||
|
||||
@classmethod
|
||||
def ui_update(cls, session, id_, payload):
|
||||
print(payload)
|
||||
obj = session.get(cls, id_)
|
||||
if not obj:
|
||||
return None
|
||||
obj.name = payload.get("name", obj.name)
|
||||
obj.area_id = payload.get("area_id", obj.area_id)
|
||||
obj.function_id = payload.get("function_id", obj.function_id)
|
||||
session.commit()
|
||||
return obj
|
||||
|
||||
def __init__(self, name: Optional[str] = None, area_id: Optional[int] = None, function_id: Optional[int] = None):
|
||||
self.name = name
|
||||
self.area_id = area_id
|
||||
|
|
@ -76,13 +92,13 @@ class Room(ValidatableMixin, db.Model):
|
|||
continue
|
||||
|
||||
rid = room.get("id")
|
||||
section_id = room.get("section_id")
|
||||
area_id = room.get("area_id")
|
||||
function_id = room.get("function_id")
|
||||
|
||||
submitted_clean.append({
|
||||
"id": rid,
|
||||
"name": name,
|
||||
"section_id": section_id,
|
||||
"area_id": area_id,
|
||||
"function_id": function_id
|
||||
})
|
||||
|
||||
|
|
@ -100,11 +116,11 @@ class Room(ValidatableMixin, db.Model):
|
|||
rid = entry.get("id")
|
||||
name = entry["name"]
|
||||
|
||||
resolved_section_id = resolve_fk(entry.get("section_id"), section_map, "section")
|
||||
resolved_area_id = resolve_fk(entry.get("area_id"), section_map, "section")
|
||||
resolved_function_id = resolve_fk(entry.get("function_id"), function_map, "function")
|
||||
|
||||
if not rid or str(rid).startswith("room-"):
|
||||
new_room = cls(name=name, area_id=resolved_section_id, function_id=resolved_function_id)
|
||||
new_room = cls(name=name, area_id=resolved_area_id, function_id=resolved_function_id)
|
||||
db.session.add(new_room)
|
||||
else:
|
||||
try:
|
||||
|
|
@ -120,8 +136,8 @@ class Room(ValidatableMixin, db.Model):
|
|||
|
||||
if room.name != name:
|
||||
room.name = name
|
||||
if room.area_id != resolved_section_id:
|
||||
room.area_id = resolved_section_id
|
||||
if room.area_id != resolved_area_id:
|
||||
room.area_id = resolved_area_id
|
||||
if room.function_id != resolved_function_id:
|
||||
room.function_id = resolved_function_id
|
||||
|
||||
|
|
@ -133,7 +149,7 @@ class Room(ValidatableMixin, db.Model):
|
|||
# Skip if a newly added room matches this one — likely duplicate
|
||||
if any(
|
||||
r["name"] == room.name and
|
||||
resolve_fk(r["section_id"], section_map, "section") == room.area_id and
|
||||
resolve_fk(r["area_id"], section_map, "section") == room.area_id and
|
||||
resolve_fk(r["function_id"], function_map, "function") == room.function_id
|
||||
for r in submitted_clean
|
||||
if r.get("id") is None or str(r.get("id")).startswith("room-")
|
||||
|
|
@ -167,7 +183,7 @@ class Room(ValidatableMixin, db.Model):
|
|||
errors.append(f"{label} has an invalid ID: {raw_id}")
|
||||
|
||||
# These fields are FK IDs, so we're just checking for valid formats here.
|
||||
for fk_field, fk_label in [("section_id", "Section"), ("function_id", "Function")]:
|
||||
for fk_field, fk_label in [("area_id", "Section"), ("function_id", "Function")]:
|
||||
fk_val = item.get(fk_field)
|
||||
|
||||
if fk_val is None:
|
||||
|
|
@ -181,3 +197,10 @@ class Room(ValidatableMixin, db.Model):
|
|||
errors.append(f"{label} has invalid {fk_label} ID: {fk_val}")
|
||||
|
||||
return errors
|
||||
|
||||
Room.ui_eagerload = (
|
||||
joinedload(Room.area),
|
||||
joinedload(Room.room_function),
|
||||
selectinload(Room.inventory),
|
||||
selectinload(Room.users)
|
||||
)
|
||||
|
|
@ -32,13 +32,13 @@ def settings():
|
|||
submitted_rooms = []
|
||||
for room in state.get("rooms", []):
|
||||
room = dict(room) # shallow copy
|
||||
sid = room.get("section_id")
|
||||
sid = room.get("area_id")
|
||||
fid = room.get("function_id")
|
||||
|
||||
if sid is not None:
|
||||
sid_key = str(sid)
|
||||
if sid_key in section_map:
|
||||
room["section_id"] = section_map[sid_key]
|
||||
room["area_id"] = section_map[sid_key]
|
||||
|
||||
if fid is not None:
|
||||
fid_key = str(fid)
|
||||
|
|
|
|||
|
|
@ -172,10 +172,15 @@ function ComboBox(cfg) {
|
|||
} else if (this.createUrl) {
|
||||
const data = await this._post(this.createUrl, { name }, true);
|
||||
const id = (data && data.id) ? data.id : ('temp-' + Math.random().toString(36).slice(2));
|
||||
|
||||
// add option optimistically
|
||||
const opt = document.createElement('option');
|
||||
opt.value = id; opt.textContent = name;
|
||||
opt.value = id; opt.textContent = data?.name || name;
|
||||
this.$refs.list.appendChild(opt);
|
||||
this._sortOptions();
|
||||
|
||||
// ✅ NEW: tell the world we created something
|
||||
this.$dispatch('combobox:item-created', { id, name: data?.name || name });
|
||||
}
|
||||
|
||||
this.query = '';
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ create_url = none, edit_url = none, delete_url = none, refresh_url = none
|
|||
</select>
|
||||
|
||||
{% if refresh_url %}
|
||||
{% set url = refresh_url ~ ('&' if '?' in refresh_url else '?') ~ 'view=option' %}
|
||||
{% set url = refresh_url ~ ('&' if '?' in refresh_url else '?') ~ 'view=option&limit=0' %}
|
||||
<div id="{{ id }}-htmx-refresh" class="d-none" hx-get="{{ url }}"
|
||||
hx-trigger="revealed, combobox:refresh from:#{{ id }}-container" hx-target="#{{ id }}-list" hx-swap="innerHTML">
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@
|
|||
const result = {
|
||||
name,
|
||||
...(id ? { id } : {}),
|
||||
section_id: sectionId,
|
||||
area_id: sectionId,
|
||||
function_id: functionId
|
||||
};
|
||||
|
||||
|
|
@ -167,13 +167,12 @@
|
|||
<div class="col">
|
||||
{{ combos.dynamic_combobox(
|
||||
id='function',
|
||||
options=functions,
|
||||
label='Functions',
|
||||
placeholder='Add a new function',
|
||||
create_url=url_for('ui.create_item', model_name='function'),
|
||||
edit_url=url_for('ui.update_item', model_name='function'),
|
||||
refresh_url=url_for('ui.list_items', model_name='function'),
|
||||
delete_url=url_for('ui.delete_item', model_name='function')
|
||||
create_url=url_for('ui.create_item', model_name='room_function'),
|
||||
edit_url=url_for('ui.update_item', model_name='room_function'),
|
||||
refresh_url=url_for('ui.list_items', model_name='room_function'),
|
||||
delete_url=url_for('ui.delete_item', model_name='room_function')
|
||||
) }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -200,14 +199,15 @@
|
|||
document.getElementById('room-input').value = '';
|
||||
{% endset %}
|
||||
<div class="col">
|
||||
{{ combos.render_combobox(
|
||||
{{ combos.dynamic_combobox(
|
||||
id='room',
|
||||
options=rooms,
|
||||
label='Rooms',
|
||||
placeholder='Add a new room',
|
||||
onAdd=room_editor,
|
||||
onEdit=room_editor,
|
||||
data_attributes={'area_id': 'section-id', 'function_id': 'function-id'}
|
||||
data_attributes={'area_id': 'section-id', 'function_id': 'function-id'},
|
||||
create_url=url_for('ui.create_item', model_name='room'),
|
||||
edit_url=url_for('ui.update_item', model_name='room'),
|
||||
refresh_url=url_for('ui.list_items', model_name='room'),
|
||||
delete_url=url_for('ui.delete_item', model_name='room')
|
||||
) }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -268,6 +268,7 @@
|
|||
<button type="button" class="btn btn-danger" data-bs-dismiss="modal" id="roomEditorCancelButton">{{
|
||||
icons.render_icon('x-lg', 16) }}</button>
|
||||
{% set editorSaveLogic %}
|
||||
{#
|
||||
const modal = document.getElementById('roomEditor');
|
||||
const name = document.getElementById('roomName').value.trim();
|
||||
const sectionVal = document.getElementById('roomSection').value;
|
||||
|
|
@ -299,6 +300,29 @@
|
|||
ComboBoxWidget.sortOptions(roomList);
|
||||
|
||||
bootstrap.Modal.getInstance(modal).hide();
|
||||
#}
|
||||
const modalEl = document.getElementById('roomEditor');
|
||||
const idRaw = document.getElementById('roomId').value;
|
||||
const name = document.getElementById('roomName').value.trim();
|
||||
const sectionId = document.getElementById('roomSection').value || null;
|
||||
const functionId = document.getElementById('roomFunction').value || null;
|
||||
|
||||
if (!name) { alert('Please enter a room name.'); return; }
|
||||
if (!idRaw) { alert('Missing room ID.'); return; }
|
||||
|
||||
(async () => {
|
||||
const res = await fetch('{{ url_for("ui.update_item", model_name="room") }}', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ id: parseInt(idRaw, 10), name, area_id: sectionId, function_id: functionId })
|
||||
});
|
||||
if (!res.ok) {
|
||||
const txt = await res.text().catch(()=> 'Error'); alert(txt); return;
|
||||
}
|
||||
|
||||
htmx.trigger('#room-container', 'combobox:refresh');
|
||||
bootstrap.Modal.getInstance(modalEl).hide();
|
||||
})();
|
||||
{% endset %}
|
||||
{{ buttons.render_button(
|
||||
id='editorSave',
|
||||
|
|
@ -351,4 +375,22 @@
|
|||
cancelButton.addEventListener('click', () => {
|
||||
bootstrap.Modal.getInstance(modal).hide();
|
||||
});
|
||||
|
||||
(function () {
|
||||
const container = document.getElementById('room-container');
|
||||
if (!container) return;
|
||||
|
||||
container.addEventListener('combobox:item-created', (e) => {
|
||||
if (container.id !== 'room-container') return;
|
||||
|
||||
const { id, name } = e.detail || {};
|
||||
const prep = new CustomEvent('roomEditor:prepare', {
|
||||
detail: { id, name, sectionId: '', functionId: '' }
|
||||
});
|
||||
document.getElementById('roomEditor').dispatchEvent(prep);
|
||||
|
||||
const roomEditorModal = new bootstrap.Modal(document.getElementById('roomEditor'));
|
||||
roomEditorModal.show();
|
||||
});
|
||||
})();
|
||||
{% endblock %}
|
||||
|
|
@ -44,7 +44,9 @@ def call(Model, name, *args, **kwargs):
|
|||
def list_items(model_name):
|
||||
Model = get_model_class(model_name)
|
||||
text = (request.args.get("q") or "").strip() or None
|
||||
limit = min(int(request.args.get("limit", 100)), 500)
|
||||
limit_param = request.args.get("limit")
|
||||
limit = None if limit_param in (None, "", "0", "-1") else min(int(limit_param), 500)
|
||||
# limit = min(int(request.args.get("limit", 100)), 500)
|
||||
offset = int(request.args.get("offset", 0))
|
||||
view = (request.args.get("view") or "json").strip()
|
||||
|
||||
|
|
|
|||
|
|
@ -59,7 +59,11 @@ def default_query(session, Model, *, text=None, limit=100, offset=0, filters=Non
|
|||
if col is not None:
|
||||
stmt = stmt.order_by(col)
|
||||
|
||||
stmt = stmt.limit(limit).offset(offset)
|
||||
# stmt = stmt.limit(limit).offset(offset)
|
||||
if limit is not None:
|
||||
stmt = stmt.limit(limit)
|
||||
if offset:
|
||||
stmt = stmt.offset(offset)
|
||||
return session.execute(stmt).scalars().all()
|
||||
|
||||
def default_create(session, Model, payload):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue