Implement sync_from_state methods for Area, Brand, Item, RoomFunction, and Room models; enhance entity management and foreign key resolution in settings
This commit is contained in:
parent
8a5c5db9e0
commit
7833c4828b
9 changed files with 359 additions and 186 deletions
|
@ -6,6 +6,7 @@ from sqlalchemy import Identity, Integer, Unicode
|
|||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from . import db
|
||||
from ..temp import is_temp_id
|
||||
|
||||
class Area(db.Model):
|
||||
__tablename__ = 'Areas'
|
||||
|
@ -28,31 +29,54 @@ class Area(db.Model):
|
|||
}
|
||||
|
||||
@classmethod
|
||||
def sync_from_state(cls, submitted_items: list[dict]):
|
||||
def sync_from_state(cls, submitted_items: list[dict]) -> dict[str, int]:
|
||||
"""
|
||||
Syncs the Area table with the provided list of dictionaries.
|
||||
Adds new areas and removes areas that are not in the list.
|
||||
Syncs the Area table (aka 'sections') with the submitted list.
|
||||
Supports add, update, and delete.
|
||||
Also returns a mapping of temp IDs to real IDs for resolution.
|
||||
"""
|
||||
submitted = {
|
||||
str(item.get("name", "")).strip()
|
||||
for item in submitted_items
|
||||
if isinstance(item, dict) and str(item.get("name", "")).strip()
|
||||
}
|
||||
submitted_clean = []
|
||||
seen_ids = set()
|
||||
temp_id_map = {}
|
||||
|
||||
for item in submitted_items:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
name = str(item.get("name", "")).strip()
|
||||
if not name:
|
||||
continue
|
||||
submitted_clean.append({"id": item.get("id"), "name": name})
|
||||
if isinstance(item.get("id"), int) and item["id"] >= 0:
|
||||
seen_ids.add(item["id"])
|
||||
|
||||
existing_query = db.session.query(cls)
|
||||
existing = {area.name: area for area in existing_query.all()}
|
||||
existing_by_id = {area.id: area for area in existing_query.all()}
|
||||
existing_ids = set(existing_by_id.keys())
|
||||
|
||||
existing_names = set(existing.keys())
|
||||
print(f"Existing area IDs: {existing_ids}")
|
||||
print(f"Submitted area IDs: {seen_ids}")
|
||||
|
||||
print(f"Existing areas: {existing_names}")
|
||||
print(f"Submitted areas: {submitted}")
|
||||
print(f"Areas to add: {submitted - existing_names}")
|
||||
print(f"Areas to remove: {existing_names - submitted}")
|
||||
for entry in submitted_clean:
|
||||
submitted_id = entry.get("id")
|
||||
submitted_name = entry["name"]
|
||||
|
||||
if is_temp_id(submitted_id):
|
||||
new_area = cls(name=submitted_name)
|
||||
db.session.add(new_area)
|
||||
db.session.flush() # Get the real ID
|
||||
temp_id_map[submitted_id] = new_area.id
|
||||
print(f"➕ Adding area: {submitted_name}")
|
||||
elif submitted_id in existing_by_id:
|
||||
area = existing_by_id[submitted_id]
|
||||
if area.name != submitted_name:
|
||||
print(f"✏️ Updating area {area.id}: '{area.name}' → '{submitted_name}'")
|
||||
area.name = submitted_name
|
||||
|
||||
for existing_id in existing_ids - seen_ids:
|
||||
area = existing_by_id[existing_id]
|
||||
db.session.delete(area)
|
||||
print(f"🗑️ Removing area: {area.name}")
|
||||
|
||||
return temp_id_map
|
||||
|
||||
for name in submitted - existing_names:
|
||||
db.session.add(cls(name=name))
|
||||
print(f"➕ Adding area: {name}")
|
||||
|
||||
for name in existing_names - submitted:
|
||||
db.session.delete(existing[name])
|
||||
print(f"🗑️ Removing area: {name}")
|
||||
|
|
|
@ -6,6 +6,7 @@ from sqlalchemy import Identity, Integer, Unicode
|
|||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from . import db
|
||||
from ..temp import is_temp_id
|
||||
|
||||
class Brand(db.Model):
|
||||
__tablename__ = 'Brands'
|
||||
|
@ -28,31 +29,47 @@ class Brand(db.Model):
|
|||
}
|
||||
|
||||
@classmethod
|
||||
def sync_from_state(cls, submitted_items: list[dict]):
|
||||
"""
|
||||
Syncs the Brand table with the provided list of dictionaries.
|
||||
Adds new brands and removes brands that are not in the list.
|
||||
"""
|
||||
submitted = {
|
||||
str(item.get("name", "")).strip()
|
||||
for item in submitted_items
|
||||
if isinstance(item, dict) and str(item.get("name", "")).strip()
|
||||
}
|
||||
def sync_from_state(cls, submitted_items: list[dict]) -> dict[str, int]:
|
||||
submitted_clean = []
|
||||
seen_ids = set()
|
||||
temp_id_map = {}
|
||||
|
||||
existing_query = db.session.query(cls)
|
||||
existing = {brand.name: brand for brand in existing_query.all()}
|
||||
for item in submitted_items:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
name = str(item.get("name", "")).strip()
|
||||
if not name:
|
||||
continue
|
||||
submitted_clean.append({"id": item.get("id"), "name": name})
|
||||
if isinstance(item.get("id"), int) and item["id"] >= 0:
|
||||
seen_ids.add(item["id"])
|
||||
|
||||
existing_names = set(existing.keys())
|
||||
existing_by_id = {b.id: b for b in db.session.query(cls).all()}
|
||||
existing_ids = set(existing_by_id.keys())
|
||||
|
||||
print(f"Existing brands: {existing_names}")
|
||||
print(f"Submitted brands: {submitted}")
|
||||
print(f"Brands to add: {submitted - existing_names}")
|
||||
print(f"Brands to remove: {existing_names - submitted}")
|
||||
print(f"Existing brand IDs: {existing_ids}")
|
||||
print(f"Submitted brand IDs: {seen_ids}")
|
||||
|
||||
for name in submitted - existing_names:
|
||||
db.session.add(cls(name=name))
|
||||
for entry in submitted_clean:
|
||||
submitted_id = entry.get("id")
|
||||
name = entry["name"]
|
||||
|
||||
if is_temp_id(submitted_id):
|
||||
obj = cls(name=name)
|
||||
db.session.add(obj)
|
||||
db.session.flush()
|
||||
temp_id_map[submitted_id] = obj.id
|
||||
print(f"➕ Adding brand: {name}")
|
||||
elif submitted_id in existing_by_id:
|
||||
obj = existing_by_id[submitted_id]
|
||||
if obj.name != name:
|
||||
print(f"✏️ Updating brand {obj.id}: '{obj.name}' → '{name}'")
|
||||
obj.name = name
|
||||
|
||||
for id_to_remove in existing_ids - seen_ids:
|
||||
db.session.delete(existing_by_id[id_to_remove])
|
||||
print(f"🗑️ Removing brand ID {id_to_remove}")
|
||||
|
||||
return temp_id_map
|
||||
|
||||
|
||||
for name in existing_names - submitted:
|
||||
db.session.delete(existing[name])
|
||||
print(f"🗑️ Removing brand: {name}")
|
||||
|
|
|
@ -6,6 +6,7 @@ from sqlalchemy import Identity, Integer, Unicode
|
|||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from . import db
|
||||
from ..temp import is_temp_id
|
||||
|
||||
class Item(db.Model):
|
||||
__tablename__ = 'Items'
|
||||
|
@ -31,31 +32,46 @@ class Item(db.Model):
|
|||
}
|
||||
|
||||
@classmethod
|
||||
def sync_from_state(cls, submitted_items: list[dict]):
|
||||
"""
|
||||
Syncs the Items table with the provided list of dictionaries.
|
||||
Adds new items and removes items that are not in the list.
|
||||
"""
|
||||
submitted = {
|
||||
str(item.get("name", "")).strip()
|
||||
for item in submitted_items
|
||||
if isinstance(item, dict) and str(item.get("name", "")).strip()
|
||||
}
|
||||
def sync_from_state(cls, submitted_items: list[dict]) -> dict[str, int]:
|
||||
submitted_clean = []
|
||||
seen_ids = set()
|
||||
temp_id_map = {}
|
||||
|
||||
existing_query = db.session.query(cls)
|
||||
existing = {item.description: item for item in existing_query.all()}
|
||||
for item in submitted_items:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
description = str(item.get("name", "")).strip()
|
||||
if not description:
|
||||
continue
|
||||
submitted_clean.append({"id": item.get("id"), "description": description})
|
||||
if isinstance(item.get("id"), int) and item["id"] >= 0:
|
||||
seen_ids.add(item["id"])
|
||||
|
||||
existing_descriptions = set(existing.keys())
|
||||
existing_by_id = {t.id: t for t in db.session.query(cls).all()}
|
||||
existing_ids = set(existing_by_id.keys())
|
||||
|
||||
print(f"Existing items: {existing_descriptions}")
|
||||
print(f"Submitted items: {submitted}")
|
||||
print(f"items to add: {submitted - existing_descriptions}")
|
||||
print(f"items to remove: {existing_descriptions - submitted}")
|
||||
print(f"Existing item IDs: {existing_ids}")
|
||||
print(f"Submitted item IDs: {seen_ids}")
|
||||
|
||||
for description in submitted - existing_descriptions:
|
||||
db.session.add(cls(description=description))
|
||||
print(f"➕ Adding item: {description}")
|
||||
for entry in submitted_clean:
|
||||
submitted_id = entry.get("id")
|
||||
description = entry["description"]
|
||||
|
||||
if is_temp_id(submitted_id):
|
||||
obj = cls(description=description)
|
||||
db.session.add(obj)
|
||||
db.session.flush()
|
||||
temp_id_map[submitted_id] = obj.id
|
||||
print(f"➕ Adding type: {description}")
|
||||
elif submitted_id in existing_by_id:
|
||||
obj = existing_by_id[submitted_id]
|
||||
if obj.description != description:
|
||||
print(f"✏️ Updating type {obj.id}: '{obj.description}' → '{description}'")
|
||||
obj.description = description
|
||||
|
||||
for id_to_remove in existing_ids - seen_ids:
|
||||
db.session.delete(existing_by_id[id_to_remove])
|
||||
print(f"🗑️ Removing type ID {id_to_remove}")
|
||||
|
||||
return temp_id_map
|
||||
|
||||
for description in existing_descriptions - submitted:
|
||||
db.session.delete(existing[description])
|
||||
print(f"🗑️ Removing item: {description}")
|
|
@ -6,6 +6,7 @@ from sqlalchemy import Identity, Integer, Unicode
|
|||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from . import db
|
||||
from ..temp import is_temp_id
|
||||
|
||||
class RoomFunction(db.Model):
|
||||
__tablename__ = 'Room Functions'
|
||||
|
@ -28,31 +29,46 @@ class RoomFunction(db.Model):
|
|||
}
|
||||
|
||||
@classmethod
|
||||
def sync_from_state(cls, submitted_items: list[dict]):
|
||||
"""
|
||||
Syncs the functions table with the provided list of dictionaries.
|
||||
Adds new functions and removes functions that are not in the list.
|
||||
"""
|
||||
submitted = {
|
||||
str(item.get("name", "")).strip()
|
||||
for item in submitted_items
|
||||
if isinstance(item, dict) and str(item.get("name", "")).strip()
|
||||
}
|
||||
def sync_from_state(cls, submitted_items: list[dict]) -> dict[str, int]:
|
||||
submitted_clean = []
|
||||
seen_ids = set()
|
||||
temp_id_map = {}
|
||||
|
||||
existing_query = db.session.query(cls)
|
||||
existing = {item.description: item for item in existing_query.all()}
|
||||
for item in submitted_items:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
description = str(item.get("name", "")).strip()
|
||||
if not description:
|
||||
continue
|
||||
submitted_clean.append({"id": item.get("id"), "description": description})
|
||||
if isinstance(item.get("id"), int) and item["id"] >= 0:
|
||||
seen_ids.add(item["id"])
|
||||
|
||||
existing_descriptions = set(existing.keys())
|
||||
existing_by_id = {f.id: f for f in db.session.query(cls).all()}
|
||||
existing_ids = set(existing_by_id.keys())
|
||||
|
||||
print(f"Existing functions: {existing_descriptions}")
|
||||
print(f"Submitted functions: {submitted}")
|
||||
print(f"Functions to add: {submitted - existing_descriptions}")
|
||||
print(f"Functions to remove: {existing_descriptions - submitted}")
|
||||
print(f"Existing function IDs: {existing_ids}")
|
||||
print(f"Submitted function IDs: {seen_ids}")
|
||||
|
||||
for description in submitted - existing_descriptions:
|
||||
db.session.add(cls(description=description))
|
||||
print(f"➕ Adding item: {description}")
|
||||
for entry in submitted_clean:
|
||||
submitted_id = entry.get("id")
|
||||
description = entry["description"]
|
||||
|
||||
if is_temp_id(submitted_id):
|
||||
obj = cls(description=description)
|
||||
db.session.add(obj)
|
||||
db.session.flush()
|
||||
temp_id_map[submitted_id] = obj.id
|
||||
print(f"➕ Adding function: {description}")
|
||||
elif submitted_id in existing_by_id:
|
||||
obj = existing_by_id[submitted_id]
|
||||
if obj.description != description:
|
||||
print(f"✏️ Updating function {obj.id}: '{obj.description}' → '{description}'")
|
||||
obj.description = description
|
||||
|
||||
for id_to_remove in existing_ids - seen_ids:
|
||||
db.session.delete(existing_by_id[id_to_remove])
|
||||
print(f"🗑️ Removing function ID {id_to_remove}")
|
||||
|
||||
return temp_id_map
|
||||
|
||||
for description in existing_descriptions - submitted:
|
||||
db.session.delete(existing[description])
|
||||
print(f"🗑️ Removing item: {description}")
|
137
models/rooms.py
137
models/rooms.py
|
@ -46,59 +46,116 @@ class Room(db.Model):
|
|||
}
|
||||
|
||||
@classmethod
|
||||
def sync_from_state(cls, submitted_rooms: list[dict], section_map: dict, function_map: dict, section_fallbacks: list, function_fallbacks: list):
|
||||
def sync_from_state(
|
||||
cls,
|
||||
submitted_rooms: list[dict],
|
||||
section_map: dict,
|
||||
function_map: dict,
|
||||
section_fallbacks: list,
|
||||
function_fallbacks: list
|
||||
):
|
||||
"""
|
||||
Syncs the Rooms table with the submitted room list.
|
||||
Resolves foreign keys using section_map and function_map.
|
||||
Supports add, update, and delete.
|
||||
"""
|
||||
|
||||
def resolve_id(raw_id, fallback_list, id_map, label):
|
||||
try:
|
||||
resolved = int(raw_id)
|
||||
if resolved >= 0:
|
||||
if resolved in id_map.values():
|
||||
return resolved
|
||||
raise ValueError(f"ID {resolved} not found in {label}.")
|
||||
except Exception:
|
||||
pass
|
||||
def resolve_fk(raw_id, fallback_list, id_map, label):
|
||||
if not raw_id:
|
||||
return None
|
||||
|
||||
index = abs(resolved + 1)
|
||||
# 🛠 Handle SQLAlchemy model objects and dicts both
|
||||
name_entry = next(
|
||||
(getattr(item, "name", None) or item.get("name")
|
||||
for item in fallback_list
|
||||
if str(getattr(item, "id", None) or item.get("id")) == str(raw_id)),
|
||||
None
|
||||
)
|
||||
|
||||
if name_entry is None:
|
||||
raise ValueError(f"Unable to resolve {label} ID: {raw_id}")
|
||||
|
||||
resolved_id = id_map.get(name_entry)
|
||||
if resolved_id is None:
|
||||
raise ValueError(f"{label.capitalize()} '{name_entry}' not found in ID map.")
|
||||
return resolved_id
|
||||
|
||||
submitted_clean = []
|
||||
seen_ids = set()
|
||||
|
||||
for room in submitted_rooms:
|
||||
if not isinstance(room, dict):
|
||||
continue
|
||||
name = str(room.get("name", "")).strip()
|
||||
if not name:
|
||||
continue
|
||||
|
||||
rid = room.get("id")
|
||||
section_id = room.get("section_id")
|
||||
function_id = room.get("function_id")
|
||||
|
||||
submitted_clean.append({
|
||||
"id": rid,
|
||||
"name": name,
|
||||
"section_id": section_id,
|
||||
"function_id": function_id
|
||||
})
|
||||
|
||||
if rid and not str(rid).startswith("room-"):
|
||||
try:
|
||||
entry = fallback_list[index]
|
||||
key = entry.get("name") if isinstance(entry, dict) else str(entry).strip()
|
||||
final_id = id_map.get(key)
|
||||
if final_id is None:
|
||||
raise ValueError(f"ID for {key} not found in {label}.")
|
||||
return final_id
|
||||
except Exception as e:
|
||||
raise ValueError(f"Failed to resolve {label} ID: {e}") from e
|
||||
seen_ids.add(int(rid))
|
||||
except ValueError:
|
||||
pass # It's an invalid non-temp string
|
||||
|
||||
existing_query = db.session.query(cls)
|
||||
existing = {room.name: room for room in existing_query.all()}
|
||||
existing_by_id = {room.id: room for room in existing_query.all()}
|
||||
existing_ids = set(existing_by_id.keys())
|
||||
|
||||
submitted = {
|
||||
str(room.get("name", "")).strip(): room
|
||||
for room in submitted_rooms
|
||||
if isinstance(room, dict) and str(room.get("name", "")).strip()
|
||||
}
|
||||
print(f"Existing room IDs: {existing_ids}")
|
||||
print(f"Submitted room IDs: {seen_ids}")
|
||||
|
||||
existing_names = set(existing.keys())
|
||||
submitted_names = set(submitted.keys())
|
||||
for entry in submitted_clean:
|
||||
rid = entry.get("id")
|
||||
name = entry["name"]
|
||||
|
||||
print(f"Existing rooms: {existing_names}")
|
||||
print(f"Submitted rooms: {submitted_names}")
|
||||
print(f"Rooms to add: {submitted_names - existing_names}")
|
||||
print(f"Rooms to remove: {existing_names - submitted_names}")
|
||||
resolved_section_id = resolve_fk(entry.get("section_id"), section_fallbacks, section_map, "section")
|
||||
resolved_function_id = resolve_fk(entry.get("function_id"), function_fallbacks, function_map, "function")
|
||||
|
||||
for name in submitted_names - existing_names:
|
||||
room_data = submitted[name]
|
||||
area_id = resolve_id(room_data.get("section_id", -1), section_fallbacks, section_map, "section") if room_data.get("section_id") is not None else None
|
||||
function_id = resolve_id(room_data.get("function_id", -1), function_fallbacks, function_map, "function") if room_data.get("function_id") is not None else None
|
||||
new_room = cls(name=name, area_id=area_id, function_id=function_id)
|
||||
if not rid or str(rid).startswith("room-"):
|
||||
new_room = cls(name=name, area_id=resolved_section_id, function_id=resolved_function_id)
|
||||
db.session.add(new_room)
|
||||
print(f"➕ Adding room: {new_room}")
|
||||
else:
|
||||
try:
|
||||
rid_int = int(rid)
|
||||
except ValueError:
|
||||
print(f"⚠️ Invalid room ID format: {rid}")
|
||||
continue
|
||||
|
||||
room = existing_by_id.get(rid_int)
|
||||
if not room:
|
||||
print(f"⚠️ No matching room in DB for ID: {rid_int}")
|
||||
continue
|
||||
|
||||
updated = False
|
||||
if room.name != name:
|
||||
print(f"✏️ Updating room name {room.id}: '{room.name}' → '{name}'")
|
||||
room.name = name
|
||||
updated = True
|
||||
if room.area_id != resolved_section_id:
|
||||
print(f"✏️ Updating room area {room.id}: {room.area_id} → {resolved_section_id}")
|
||||
room.area_id = resolved_section_id
|
||||
updated = True
|
||||
if room.function_id != resolved_function_id:
|
||||
print(f"✏️ Updating room function {room.id}: {room.function_id} → {resolved_function_id}")
|
||||
room.function_id = resolved_function_id
|
||||
updated = True
|
||||
if not updated:
|
||||
print(f"✅ No changes to room {room.id}")
|
||||
|
||||
for existing_id in existing_ids - seen_ids:
|
||||
room = existing_by_id[existing_id]
|
||||
db.session.delete(room)
|
||||
print(f"🗑️ Removing room: {room.name}")
|
||||
|
||||
|
||||
for name in existing_names - submitted_names:
|
||||
room_to_remove = existing[name]
|
||||
db.session.delete(room_to_remove)
|
||||
print(f"🗑️ Removing room: {room_to_remove}")
|
||||
|
|
30
routes.py
30
routes.py
|
@ -412,21 +412,30 @@ def settings():
|
|||
|
||||
try:
|
||||
with db.session.begin():
|
||||
Brand.sync_from_state(state.get("brands", []))
|
||||
Item.sync_from_state(state.get("types", []))
|
||||
Area.sync_from_state(state.get("sections", []))
|
||||
RoomFunction.sync_from_state(state.get("functions", []))
|
||||
# Sync each table and grab temp ID maps
|
||||
brand_map = Brand.sync_from_state(state.get("brands", []))
|
||||
type_map = Item.sync_from_state(state.get("types", []))
|
||||
section_map = Area.sync_from_state(state.get("sections", []))
|
||||
function_map = RoomFunction.sync_from_state(state.get("functions", []))
|
||||
|
||||
# Refresh maps after inserts
|
||||
section_map = {a.name: a.id for a in db.session.query(Area).all()}
|
||||
function_map = {f.description: f.id for f in db.session.query(RoomFunction).all()}
|
||||
# Fix up room foreign keys based on real IDs
|
||||
submitted_rooms = []
|
||||
for room in state.get("rooms", []):
|
||||
room = dict(room) # shallow copy
|
||||
sid = room.get("section_id")
|
||||
fid = room.get("function_id")
|
||||
if sid in section_map:
|
||||
room["section_id"] = section_map[sid]
|
||||
if fid in function_map:
|
||||
room["function_id"] = function_map[fid]
|
||||
submitted_rooms.append(room)
|
||||
|
||||
Room.sync_from_state(
|
||||
submitted_rooms=state.get("rooms", []),
|
||||
submitted_rooms=submitted_rooms,
|
||||
section_map=section_map,
|
||||
function_map=function_map,
|
||||
section_fallbacks=state.get("sections", []),
|
||||
function_fallbacks=state.get("functions", [])
|
||||
section_fallbacks=[{"id": v, "name": k} for k, v in section_map.items()],
|
||||
function_fallbacks=[{"id": v, "name": k} for k, v in function_map.items()]
|
||||
)
|
||||
|
||||
print("✅ COMMIT executed.")
|
||||
|
@ -454,3 +463,4 @@ def settings():
|
|||
functions=[f.serialize() for f in functions],
|
||||
rooms=[r.serialize() for r in rooms],
|
||||
)
|
||||
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
const ComboBoxWidget = (() => {
|
||||
let tempIdCounter = -1;
|
||||
let tempIdCounter = 1;
|
||||
|
||||
function createTempId(prefix = "temp") {
|
||||
return `${prefix}-${tempIdCounter++}`;
|
||||
}
|
||||
|
||||
function createOption(text, value = null) {
|
||||
const option = document.createElement('option');
|
||||
option.textContent = text;
|
||||
option.value = value ?? (tempIdCounter--);
|
||||
option.value = value ?? createTempId();
|
||||
return option;
|
||||
}
|
||||
|
||||
|
@ -144,6 +148,7 @@ const ComboBoxWidget = (() => {
|
|||
initComboBox,
|
||||
createOption,
|
||||
sortOptions,
|
||||
handleComboAdd
|
||||
handleComboAdd,
|
||||
createTempId
|
||||
};
|
||||
})();
|
||||
|
|
6
temp.py
Normal file
6
temp.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
def is_temp_id(val):
|
||||
return (
|
||||
val is None or
|
||||
(isinstance(val, int) and val < 0) or
|
||||
(isinstance(val, str) and val.startswith("temp-"))
|
||||
)
|
|
@ -59,7 +59,19 @@ submit_button=True
|
|||
{% set room_editor %}
|
||||
const roomEditor = new bootstrap.Modal(document.getElementById('roomEditor'));
|
||||
const roomNameInput = document.getElementById('roomName');
|
||||
roomNameInput.value = document.getElementById('room-input').value;
|
||||
const input = document.getElementById('room-input');
|
||||
const name = input.value.trim();
|
||||
const existingOption = Array.from(document.getElementById('room-list').options)
|
||||
.find(opt => opt.textContent.trim() === name);
|
||||
|
||||
roomNameInput.value = name;
|
||||
document.getElementById('roomId').value = existingOption?.value ?? ''; // this will be the ID or temp ID
|
||||
|
||||
if (existingOption?.dataset.sectionId)
|
||||
document.getElementById('roomSection').value = existingOption.dataset.sectionId;
|
||||
|
||||
if (existingOption?.dataset.functionId)
|
||||
document.getElementById('roomFunction').value = existingOption.dataset.functionId;
|
||||
|
||||
roomEditor.show();
|
||||
|
||||
|
@ -71,7 +83,8 @@ submit_button=True
|
|||
options=rooms,
|
||||
label='Rooms',
|
||||
placeholder='Add a new room',
|
||||
onAdd=room_editor
|
||||
onAdd=room_editor,
|
||||
onEdit=room_editor
|
||||
) }}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -89,6 +102,7 @@ submit_button=True
|
|||
<div class="col">
|
||||
<label for="roomName" class="form-label">Room Name</label>
|
||||
<input type="text" class="form-input" id="roomName" placeholder="Enter room name">
|
||||
<input type="hidden" id="roomId">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
|
@ -134,16 +148,20 @@ submit_button=True
|
|||
function buildFormState() {
|
||||
function extractOptions(id) {
|
||||
const select = document.getElementById(`${id}-list`);
|
||||
return Array.from(select.options).map(opt => ({ name: opt.textContent.trim(), id: parseInt(opt.value) || undefined }));
|
||||
return Array.from(select.options).map(opt => ({
|
||||
name: opt.textContent.trim(),
|
||||
id: opt.value || undefined
|
||||
}));
|
||||
}
|
||||
|
||||
const roomOptions = Array.from(document.getElementById('room-list').options);
|
||||
const rooms = roomOptions.map(opt => {
|
||||
const data = opt.dataset;
|
||||
return {
|
||||
id: opt.value || undefined,
|
||||
name: opt.textContent.trim(),
|
||||
section_id: data.sectionId ? parseInt(data.sectionId) : null,
|
||||
function_id: data.functionId ? parseInt(data.functionId) : null
|
||||
section_id: data.sectionId ?? null,
|
||||
function_id: data.functionId ?? null,
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -192,43 +210,47 @@ submit_button=True
|
|||
const name = document.getElementById('roomName').value.trim();
|
||||
const sectionVal = document.getElementById('roomSection').value;
|
||||
const funcVal = document.getElementById('roomFunction').value;
|
||||
|
||||
const section = sectionVal !== "" ? parseInt(sectionVal) : null;
|
||||
const func = funcVal !== "" ? parseInt(funcVal) : null;
|
||||
|
||||
let idRaw = document.getElementById('roomId').value;
|
||||
|
||||
if (!name) {
|
||||
alert('Please enter a room name.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Avoid duplicate visible names
|
||||
const roomList = document.getElementById('room-list');
|
||||
const exists = Array.from(roomList.options).some(opt => opt.textContent.trim() === name);
|
||||
if (exists) {
|
||||
alert(`Room "${name}" already exists.`);
|
||||
return;
|
||||
let existingOption = Array.from(roomList.options).find(opt => opt.value === idRaw);
|
||||
|
||||
// If it's a brand new ID, generate one (string-based!)
|
||||
if (!idRaw) {
|
||||
idRaw = ComboBoxWidget.createTempId("room");
|
||||
}
|
||||
|
||||
// Add to select box visibly
|
||||
const option = ComboBoxWidget.createOption(name);
|
||||
|
||||
if (section !== null) {
|
||||
option.dataset.sectionId = section;
|
||||
}
|
||||
if (func !== null) {
|
||||
option.dataset.functionId = func;
|
||||
if (!existingOption) {
|
||||
existingOption = ComboBoxWidget.createOption(name, idRaw);
|
||||
roomList.appendChild(existingOption);
|
||||
}
|
||||
|
||||
roomList.appendChild(option);
|
||||
existingOption.textContent = name;
|
||||
existingOption.value = idRaw;
|
||||
existingOption.dataset.sectionId = sectionVal;
|
||||
existingOption.dataset.functionId = funcVal;
|
||||
|
||||
ComboBoxWidget.sortOptions(roomList);
|
||||
|
||||
// Track in state object
|
||||
formState.rooms.push({
|
||||
// Update formState.rooms
|
||||
const index = formState.rooms.findIndex(r => r.id === idRaw);
|
||||
const payload = {
|
||||
id: idRaw,
|
||||
name,
|
||||
section_id: section,
|
||||
function_id: func
|
||||
});
|
||||
section_id: sectionVal !== "" ? sectionVal : null,
|
||||
function_id: funcVal !== "" ? funcVal : null
|
||||
};
|
||||
|
||||
if (index >= 0) {
|
||||
formState.rooms[index] = payload;
|
||||
} else {
|
||||
formState.rooms.push(payload);
|
||||
}
|
||||
|
||||
bootstrap.Modal.getInstance(modal).hide();
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue