diff --git a/static/js/widget.js b/static/js/widget.js index ce27f10..2c7dc0b 100644 --- a/static/js/widget.js +++ b/static/js/widget.js @@ -74,27 +74,6 @@ const ComboBoxWidget = (() => { sorted.forEach(option => selectElement.appendChild(option)); } - function handleComboAdd(inputId, listId, stateArray, label = 'entry') { - const input = document.getElementById(inputId); - const value = input.value.trim(); - if (!value) { - alert(`Please enter a ${label}.`); - return; - } - - const select = document.getElementById(listId); - const exists = Array.from(select.options).some(opt => opt.textContent === value); - if (exists) { - alert(`${label.charAt(0).toUpperCase() + label.slice(1)} "${value}" already exists.`); - return; - } - - const option = createOption(value); // Already built to handle temp IDs - select.add(option); - formState[stateArray].push(value); - input.value = ''; - } - function initComboBox(ns, config = {}) { const input = document.querySelector(`#${ns}-input`); const list = document.querySelector(`#${ns}-list`); @@ -168,9 +147,6 @@ const ComboBoxWidget = (() => { list.appendChild(option); const key = config.stateArray ?? `${ns}s`; // fallback to pluralization - if (Array.isArray(formState?.[key])) { - formState[key].push({ name: newItem }); - } if (config.sort !== false) { sortOptions(list); @@ -203,7 +179,6 @@ const ComboBoxWidget = (() => { initComboBox, createOption, sortOptions, - handleComboAdd, createTempId }; })(); diff --git a/templates/fragments/_combobox_fragment.html b/templates/fragments/_combobox_fragment.html index f6ce00a..756a999 100644 --- a/templates/fragments/_combobox_fragment.html +++ b/templates/fragments/_combobox_fragment.html @@ -1,44 +1,40 @@ {% import "fragments/_icon_fragment.html" as icons %} -{% macro render_combobox(id, options, label=none, placeholder=none, onAdd=none, onRemove=none, onEdit=none, data_attributes=none) %} - +{% macro render_combobox(id, options, label=none, placeholder=none, onAdd=none, onRemove=none, onEdit=none, +data_attributes=none) %} + - {% if label %} - - {% endif %} -
-
- - - -
- +{% if label %} + +{% endif %} +
+
+ + +
+ +
- -{% endmacro %} + +{% endmacro %} \ No newline at end of file diff --git a/templates/settings.html b/templates/settings.html index fcc9202..4a350f0 100644 --- a/templates/settings.html +++ b/templates/settings.html @@ -140,39 +140,52 @@ {% endblock %} {% block script %} - const formState = { - brands: {{ brands | tojson }}, - types: {{ types | tojson }}, - sections: {{ sections | tojson }}, - functions: {{ functions | tojson }}, - rooms: {{ rooms | tojson }}, - }; + function isSerializable(obj) { + try { + JSON.stringify(obj); + return true; + } catch { + return false; + } + } function buildFormState() { function extractOptions(id) { const select = document.getElementById(`${id}-list`); - return Array.from(select.options).map(opt => ({ - name: opt.textContent.trim(), - id: opt.value || undefined - })); + return Array.from(select.options).map(opt => { + const name = opt.textContent.trim(); + const rawId = opt.value?.trim(); + return { + name, + ...(rawId ? { id: /^\d+$/.test(rawId) ? parseInt(rawId, 10) : rawId } : {}) + }; + }); } function sanitizeFk(val) { - return val && val !== "null" && val !== "" ? val : null; + if (val && val !== "null" && val !== "") { + return /^\d+$/.test(val) ? parseInt(val, 10) : val; + } + return null; } const roomOptions = Array.from(document.getElementById('room-list').options); + const rooms = roomOptions.map(opt => { - const data = opt.dataset; + const id = opt.value?.trim(); + const name = opt.textContent.trim(); + const sectionId = sanitizeFk(opt.dataset.sectionId); + const functionId = sanitizeFk(opt.dataset.functionId); - return { - id: opt.value || undefined, - name: opt.textContent.trim(), - section_id: sanitizeFk(data.sectionId), - function_id: sanitizeFk(data.functionId), + const result = { + name, + ...(id ? { id } : {}), + ...(sectionId ? { section_id: sectionId } : {}), + ...(functionId ? { function_id: functionId } : {}) }; - }); + return result; + }); return { brands: extractOptions("brand"), @@ -223,9 +236,15 @@ form.addEventListener('submit', async (event) => { event.preventDefault(); + const state = buildFormState(); + + if (!isSerializable(state)) { + console.warn("🚨 Payload is not serializable:", state); + alert("Invalid payload — check console."); + return; + } + try { - const state = buildFormState(); - const response = await fetch('/api/settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -264,42 +283,26 @@ alert('Please enter a room name.'); return; } - + const roomList = document.getElementById('room-list'); 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"); } - + if (!existingOption) { existingOption = ComboBoxWidget.createOption(name, idRaw); roomList.appendChild(existingOption); } - + existingOption.textContent = name; existingOption.value = idRaw; existingOption.dataset.sectionId = sectionVal || ""; existingOption.dataset.functionId = funcVal || ""; - + ComboBoxWidget.sortOptions(roomList); - - // Update formState.rooms - const index = formState.rooms.findIndex(r => r.id === idRaw); - const payload = { - id: idRaw, - name, - 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(); });