diff --git a/static/js/consumption.js b/static/js/consumption.js deleted file mode 100644 index 2866dd9..0000000 --- a/static/js/consumption.js +++ /dev/null @@ -1,205 +0,0 @@ -document.addEventListener("DOMContentLoaded", () => { - const dataElement = document.getElementById("consumption-data"); - let data = { scenarios: [], consumption: {}, unit_options: [] }; - - if (dataElement) { - try { - const parsed = JSON.parse(dataElement.textContent || "{}"); - if (parsed && typeof parsed === "object") { - data = { - scenarios: Array.isArray(parsed.scenarios) ? parsed.scenarios : [], - consumption: - parsed.consumption && typeof parsed.consumption === "object" - ? parsed.consumption - : {}, - unit_options: Array.isArray(parsed.unit_options) - ? parsed.unit_options - : [], - }; - } - } catch (error) { - console.error("Unable to parse consumption data", error); - } - } - - const consumptionByScenario = data.consumption; - const filterSelect = document.getElementById("consumption-scenario-filter"); - const tableWrapper = document.getElementById("consumption-table-wrapper"); - const tableBody = document.getElementById("consumption-table-body"); - const emptyState = document.getElementById("consumption-empty"); - const form = document.getElementById("consumption-form"); - const feedbackEl = document.getElementById("consumption-feedback"); - const unitSelect = document.getElementById("consumption-form-unit"); - const unitSymbolInput = document.getElementById( - "consumption-form-unit-symbol" - ); - - const showFeedback = (message, type = "success") => { - if (!feedbackEl) { - return; - } - feedbackEl.textContent = message; - feedbackEl.classList.remove("hidden", "success", "error"); - feedbackEl.classList.add(type); - }; - - const hideFeedback = () => { - if (!feedbackEl) { - return; - } - feedbackEl.classList.add("hidden"); - feedbackEl.textContent = ""; - }; - - const formatAmount = (value) => - Number(value).toLocaleString(undefined, { - minimumFractionDigits: 2, - maximumFractionDigits: 2, - }); - - const formatMeasurement = (amount, symbol, name) => { - if (symbol) { - return `${formatAmount(amount)} ${symbol}`; - } - if (name) { - return `${formatAmount(amount)} ${name}`; - } - return formatAmount(amount); - }; - - const renderConsumptionRows = (scenarioId) => { - if (!tableBody || !tableWrapper || !emptyState) { - return; - } - - const key = String(scenarioId); - const records = consumptionByScenario[key] || []; - - tableBody.innerHTML = ""; - - if (!records.length) { - emptyState.textContent = "No consumption records for this scenario yet."; - emptyState.classList.remove("hidden"); - tableWrapper.classList.add("hidden"); - return; - } - - emptyState.classList.add("hidden"); - tableWrapper.classList.remove("hidden"); - - records.forEach((record) => { - const row = document.createElement("tr"); - row.innerHTML = ` - ${formatMeasurement( - record.amount, - record.unit_symbol, - record.unit_name - )} - ${record.description || "—"} - `; - tableBody.appendChild(row); - }); - }; - - if (filterSelect) { - filterSelect.addEventListener("change", (event) => { - const value = event.target.value; - if (!value) { - if (emptyState && tableWrapper && tableBody) { - emptyState.textContent = - "Choose a scenario to review its consumption records."; - emptyState.classList.remove("hidden"); - tableWrapper.classList.add("hidden"); - tableBody.innerHTML = ""; - } - return; - } - renderConsumptionRows(value); - }); - } - - const submitConsumption = async (event) => { - event.preventDefault(); - hideFeedback(); - - if (!form) { - return; - } - - const formData = new FormData(form); - const scenarioId = formData.get("scenario_id"); - const unitName = formData.get("unit_name"); - const unitSymbol = formData.get("unit_symbol"); - const payload = { - scenario_id: scenarioId ? Number(scenarioId) : null, - amount: Number(formData.get("amount")), - description: formData.get("description") || null, - unit_name: unitName ? String(unitName) : null, - unit_symbol: unitSymbol ? String(unitSymbol) : null, - }; - - try { - const response = await fetch("/api/consumption/", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(payload), - }); - - if (!response.ok) { - const errorDetail = await response.json().catch(() => ({})); - throw new Error( - errorDetail.detail || "Unable to add consumption record." - ); - } - - const result = await response.json(); - const mapKey = String(result.scenario_id); - - if (!Array.isArray(consumptionByScenario[mapKey])) { - consumptionByScenario[mapKey] = []; - } - consumptionByScenario[mapKey].push(result); - - form.reset(); - syncUnitSelection(); - showFeedback("Consumption record saved.", "success"); - - if (filterSelect && filterSelect.value === String(result.scenario_id)) { - renderConsumptionRows(filterSelect.value); - } - } catch (error) { - showFeedback(error.message || "An unexpected error occurred.", "error"); - } - }; - - if (form) { - form.addEventListener("submit", submitConsumption); - } - - const syncUnitSelection = () => { - if (!unitSelect || !unitSymbolInput) { - return; - } - if (!unitSelect.value && unitSelect.options.length > 0) { - const firstOption = Array.from(unitSelect.options).find( - (option) => option.value - ); - if (firstOption) { - firstOption.selected = true; - } - } - const selectedOption = unitSelect.options[unitSelect.selectedIndex]; - unitSymbolInput.value = selectedOption - ? selectedOption.getAttribute("data-symbol") || "" - : ""; - }; - - if (unitSelect) { - unitSelect.addEventListener("change", syncUnitSelection); - syncUnitSelection(); - } - - if (filterSelect && filterSelect.value) { - renderConsumptionRows(filterSelect.value); - } -}); diff --git a/static/js/costs.js b/static/js/costs.js deleted file mode 100644 index d75139c..0000000 --- a/static/js/costs.js +++ /dev/null @@ -1,339 +0,0 @@ -document.addEventListener("DOMContentLoaded", () => { - const dataElement = document.getElementById("costs-payload"); - let capexByScenario = {}; - let opexByScenario = {}; - let currencyOptions = []; - - if (dataElement) { - try { - const parsed = JSON.parse(dataElement.textContent || "{}"); - if (parsed && typeof parsed === "object") { - if (parsed.capex && typeof parsed.capex === "object") { - capexByScenario = parsed.capex; - } - if (parsed.opex && typeof parsed.opex === "object") { - opexByScenario = parsed.opex; - } - if (Array.isArray(parsed.currency_options)) { - currencyOptions = parsed.currency_options; - } - } - } catch (error) { - console.error("Unable to parse cost data", error); - } - } - - const filterSelect = document.getElementById("costs-scenario-filter"); - const costsEmptyState = document.getElementById("costs-empty"); - const costsDataWrapper = document.getElementById("costs-data"); - const capexTableBody = document.getElementById("capex-table-body"); - const opexTableBody = document.getElementById("opex-table-body"); - const capexEmpty = document.getElementById("capex-empty"); - const opexEmpty = document.getElementById("opex-empty"); - const capexTotal = document.getElementById("capex-total"); - const opexTotal = document.getElementById("opex-total"); - const capexForm = document.getElementById("capex-form"); - const opexForm = document.getElementById("opex-form"); - const capexFeedback = document.getElementById("capex-feedback"); - const opexFeedback = document.getElementById("opex-feedback"); - const capexFormScenario = document.getElementById("capex-form-scenario"); - const opexFormScenario = document.getElementById("opex-form-scenario"); - const capexCurrencySelect = document.getElementById("capex-form-currency"); - const opexCurrencySelect = document.getElementById("opex-form-currency"); - - // If no currency options were injected server-side, fetch from API - const fetchCurrencyOptions = async () => { - try { - const resp = await fetch("/api/currencies/"); - if (!resp.ok) return; - const list = await resp.json(); - if (Array.isArray(list) && list.length) { - currencyOptions = list; - populateCurrencySelects(); - } - } catch (err) { - console.warn("Unable to fetch currency options", err); - } - }; - - const populateCurrencySelects = () => { - const selectElements = [capexCurrencySelect, opexCurrencySelect].filter(Boolean); - selectElements.forEach((sel) => { - if (!sel) return; - // Clear non-empty options except the empty placeholder - const placeholder = sel.querySelector("option[value='']"); - sel.innerHTML = ""; - if (placeholder) sel.appendChild(placeholder); - currencyOptions.forEach((opt) => { - const option = document.createElement("option"); - option.value = opt.id; - option.textContent = opt.name || opt.id; - sel.appendChild(option); - }); - }); - }; - - // populate from injected options first, then fetch to refresh - if (currencyOptions && currencyOptions.length) populateCurrencySelects(); - else fetchCurrencyOptions(); - - const showFeedback = (element, message, type = "success") => { - if (!element) { - return; - } - element.textContent = message; - element.classList.remove("hidden", "success", "error"); - element.classList.add(type); - }; - - const hideFeedback = (element) => { - if (!element) { - return; - } - element.classList.add("hidden"); - element.textContent = ""; - }; - - const formatAmount = (value) => - Number(value).toLocaleString(undefined, { - minimumFractionDigits: 2, - maximumFractionDigits: 2, - }); - - const formatCurrencyAmount = (value, currencyCode) => { - if (!currencyCode) { - return formatAmount(value); - } - try { - return new Intl.NumberFormat(undefined, { - style: "currency", - currency: currencyCode, - minimumFractionDigits: 2, - maximumFractionDigits: 2, - }).format(Number(value)); - } catch (error) { - return `${currencyCode} ${formatAmount(value)}`; - } - }; - - const sumAmount = (records) => - records.reduce((total, record) => total + Number(record.amount || 0), 0); - - const describeTotal = (records) => { - if (!records || records.length === 0) { - return "—"; - } - const total = sumAmount(records); - const currencyCodes = Array.from( - new Set( - records - .map((record) => (record.currency_code || "").trim().toUpperCase()) - .filter(Boolean) - ) - ); - - if (currencyCodes.length === 1) { - return formatCurrencyAmount(total, currencyCodes[0]); - } - return `${formatAmount(total)} (mixed)`; - }; - - const renderCostTables = (scenarioId) => { - if ( - !capexTableBody || - !opexTableBody || - !capexEmpty || - !opexEmpty || - !capexTotal || - !opexTotal - ) { - return; - } - - const capexRecords = capexByScenario[String(scenarioId)] || []; - const opexRecords = opexByScenario[String(scenarioId)] || []; - - capexTableBody.innerHTML = ""; - opexTableBody.innerHTML = ""; - - if (!capexRecords.length) { - capexEmpty.classList.remove("hidden"); - } else { - capexEmpty.classList.add("hidden"); - capexRecords.forEach((record) => { - const row = document.createElement("tr"); - row.innerHTML = ` - ${formatCurrencyAmount(record.amount, record.currency_code)} - ${record.description || "—"} - `; - capexTableBody.appendChild(row); - }); - } - - if (!opexRecords.length) { - opexEmpty.classList.remove("hidden"); - } else { - opexEmpty.classList.add("hidden"); - opexRecords.forEach((record) => { - const row = document.createElement("tr"); - row.innerHTML = ` - ${formatCurrencyAmount(record.amount, record.currency_code)} - ${record.description || "—"} - `; - opexTableBody.appendChild(row); - }); - } - - capexTotal.textContent = describeTotal(capexRecords); - opexTotal.textContent = describeTotal(opexRecords); - }; - - const toggleCostView = (show) => { - if ( - !costsEmptyState || - !costsDataWrapper || - !capexTableBody || - !opexTableBody - ) { - return; - } - - if (show) { - costsEmptyState.classList.add("hidden"); - costsDataWrapper.classList.remove("hidden"); - } else { - costsEmptyState.classList.remove("hidden"); - costsDataWrapper.classList.add("hidden"); - capexTableBody.innerHTML = ""; - opexTableBody.innerHTML = ""; - if (capexTotal) { - capexTotal.textContent = "—"; - } - if (opexTotal) { - opexTotal.textContent = "—"; - } - if (capexEmpty) { - capexEmpty.classList.add("hidden"); - } - if (opexEmpty) { - opexEmpty.classList.add("hidden"); - } - } - }; - - const syncFormSelections = (value) => { - if (capexFormScenario) { - capexFormScenario.value = value || ""; - } - if (opexFormScenario) { - opexFormScenario.value = value || ""; - } - }; - - const ensureCurrencySelection = (selectElement) => { - if (!selectElement || selectElement.value) { - return; - } - const firstOption = selectElement.querySelector( - "option[value]:not([value=''])" - ); - if (firstOption && firstOption.value) { - selectElement.value = firstOption.value; - } - }; - - if (filterSelect) { - filterSelect.addEventListener("change", (event) => { - const value = event.target.value; - if (!value) { - toggleCostView(false); - syncFormSelections(""); - return; - } - toggleCostView(true); - renderCostTables(value); - syncFormSelections(value); - }); - } - - const submitCostEntry = async (event, targetUrl, storageMap, feedbackEl) => { - event.preventDefault(); - hideFeedback(feedbackEl); - - const formData = new FormData(event.target); - const scenarioId = formData.get("scenario_id"); - const currencyCode = formData.get("currency_code"); - const payload = { - scenario_id: scenarioId ? Number(scenarioId) : null, - amount: Number(formData.get("amount")), - description: formData.get("description") || null, - currency_code: currencyCode ? String(currencyCode).toUpperCase() : null, - }; - - if (!payload.scenario_id) { - showFeedback(feedbackEl, "Select a scenario before submitting.", "error"); - return; - } - - if (!payload.currency_code) { - showFeedback(feedbackEl, "Choose a currency before submitting.", "error"); - return; - } - - try { - const response = await fetch(targetUrl, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(payload), - }); - - if (!response.ok) { - const errorDetail = await response.json().catch(() => ({})); - throw new Error(errorDetail.detail || "Unable to save cost entry."); - } - - const result = await response.json(); - const mapKey = String(result.scenario_id); - - if (!Array.isArray(storageMap[mapKey])) { - storageMap[mapKey] = []; - } - - storageMap[mapKey].push(result); - - event.target.reset(); - ensureCurrencySelection(event.target.querySelector("select[name='currency_code']")); - showFeedback(feedbackEl, "Entry saved successfully.", "success"); - - if (filterSelect && filterSelect.value === mapKey) { - renderCostTables(mapKey); - } - } catch (error) { - showFeedback( - feedbackEl, - error.message || "An unexpected error occurred.", - "error" - ); - } - }; - - if (capexForm) { - ensureCurrencySelection(capexCurrencySelect); - capexForm.addEventListener("submit", (event) => - submitCostEntry(event, "/api/costs/capex", capexByScenario, capexFeedback) - ); - } - - if (opexForm) { - ensureCurrencySelection(opexCurrencySelect); - opexForm.addEventListener("submit", (event) => - submitCostEntry(event, "/api/costs/opex", opexByScenario, opexFeedback) - ); - } - - if (filterSelect && filterSelect.value) { - toggleCostView(true); - renderCostTables(filterSelect.value); - syncFormSelections(filterSelect.value); - } -}); diff --git a/static/js/currencies.js b/static/js/currencies.js deleted file mode 100644 index 86557f7..0000000 --- a/static/js/currencies.js +++ /dev/null @@ -1,537 +0,0 @@ -document.addEventListener("DOMContentLoaded", () => { - const dataElement = document.getElementById("currencies-data"); - const editorSection = document.getElementById("currencies-editor"); - const tableBody = document.getElementById("currencies-table-body"); - const tableEmptyState = document.getElementById("currencies-table-empty"); - const metrics = { - total: document.getElementById("currency-metric-total"), - active: document.getElementById("currency-metric-active"), - inactive: document.getElementById("currency-metric-inactive"), - }; - - const form = document.getElementById("currency-form"); - const existingSelect = document.getElementById("currency-form-existing"); - const codeInput = document.getElementById("currency-form-code"); - const nameInput = document.getElementById("currency-form-name"); - const symbolInput = document.getElementById("currency-form-symbol"); - const statusSelect = document.getElementById("currency-form-status"); - const resetButton = document.getElementById("currency-form-reset"); - const feedbackElement = document.getElementById("currency-form-feedback"); - - const saveButton = form ? form.querySelector("button[type='submit']") : null; - - const uppercaseCode = (value) => - (value || "").toString().trim().toUpperCase(); - const normalizeSymbol = (value) => { - if (value === undefined || value === null) { - return null; - } - const trimmed = String(value).trim(); - return trimmed ? trimmed : null; - }; - - const normalizeApiBase = (value) => { - if (!value || typeof value !== "string") { - return "/api/currencies"; - } - return value.endsWith("/") ? value.slice(0, -1) : value; - }; - - let currencies = []; - let apiBase = "/api/currencies"; - let defaultCurrencyCode = "USD"; - - const buildCurrencyRecord = (record) => { - if (!record || typeof record !== "object") { - return null; - } - const code = uppercaseCode(record.code); - return { - id: record.id ?? null, - code, - name: record.name || "", - symbol: record.symbol || "", - is_active: Boolean(record.is_active), - is_default: code === defaultCurrencyCode, - }; - }; - - const findCurrencyIndex = (code) => { - return currencies.findIndex((item) => item.code === code); - }; - - const upsertCurrency = (record) => { - const normalized = buildCurrencyRecord(record); - if (!normalized) { - return null; - } - const existingIndex = findCurrencyIndex(normalized.code); - if (existingIndex >= 0) { - currencies[existingIndex] = normalized; - } else { - currencies.push(normalized); - } - currencies.sort((a, b) => a.code.localeCompare(b.code)); - return normalized; - }; - - const replaceCurrencyList = (records) => { - if (!Array.isArray(records)) { - return; - } - currencies = records - .map((record) => buildCurrencyRecord(record)) - .filter((record) => record !== null) - .sort((a, b) => a.code.localeCompare(b.code)); - }; - - const applyPayload = () => { - if (!dataElement) { - return; - } - try { - const parsed = JSON.parse(dataElement.textContent || "{}"); - if (parsed && typeof parsed === "object") { - if (parsed.default_currency_code) { - defaultCurrencyCode = uppercaseCode(parsed.default_currency_code); - } - if (parsed.currency_api_base) { - apiBase = normalizeApiBase(parsed.currency_api_base); - } - if (Array.isArray(parsed.currencies)) { - replaceCurrencyList(parsed.currencies); - } - } - } catch (error) { - console.error("Unable to parse currencies payload", error); - } - }; - - const showFeedback = (message, type = "success") => { - if (!feedbackElement) { - return; - } - feedbackElement.textContent = message; - feedbackElement.classList.remove("hidden", "success", "error"); - feedbackElement.classList.add(type); - }; - - const hideFeedback = () => { - if (!feedbackElement) { - return; - } - feedbackElement.classList.add("hidden"); - feedbackElement.classList.remove("success", "error"); - feedbackElement.textContent = ""; - }; - - const setButtonLoading = (button, isLoading) => { - if (!button) { - return; - } - button.disabled = isLoading; - button.classList.toggle("is-loading", isLoading); - }; - - const updateMetrics = () => { - const total = currencies.length; - const active = currencies.filter((item) => item.is_active).length; - const inactive = total - active; - if (metrics.total) { - metrics.total.textContent = String(total); - } - if (metrics.active) { - metrics.active.textContent = String(active); - } - if (metrics.inactive) { - metrics.inactive.textContent = String(inactive); - } - }; - - const renderExistingOptions = ( - selectedCode = existingSelect ? existingSelect.value : "" - ) => { - if (!existingSelect) { - return; - } - const placeholder = existingSelect.querySelector("option[value='']"); - const placeholderClone = placeholder ? placeholder.cloneNode(true) : null; - existingSelect.innerHTML = ""; - if (placeholderClone) { - existingSelect.appendChild(placeholderClone); - } - const fragment = document.createDocumentFragment(); - currencies.forEach((currency) => { - const option = document.createElement("option"); - option.value = currency.code; - option.textContent = currency.name - ? `${currency.name} (${currency.code})` - : currency.code; - if (selectedCode === currency.code) { - option.selected = true; - } - fragment.appendChild(option); - }); - existingSelect.appendChild(fragment); - if ( - selectedCode && - !currencies.some((item) => item.code === selectedCode) - ) { - existingSelect.value = ""; - } - }; - - const renderTable = () => { - if (!tableBody) { - return; - } - tableBody.innerHTML = ""; - if (!currencies.length) { - if (tableEmptyState) { - tableEmptyState.classList.remove("hidden"); - } - return; - } - if (tableEmptyState) { - tableEmptyState.classList.add("hidden"); - } - const fragment = document.createDocumentFragment(); - currencies.forEach((currency) => { - const row = document.createElement("tr"); - - const codeCell = document.createElement("td"); - codeCell.textContent = currency.code; - row.appendChild(codeCell); - - const nameCell = document.createElement("td"); - nameCell.textContent = currency.name || "—"; - row.appendChild(nameCell); - - const symbolCell = document.createElement("td"); - symbolCell.textContent = currency.symbol || "—"; - row.appendChild(symbolCell); - - const statusCell = document.createElement("td"); - statusCell.textContent = currency.is_active ? "Active" : "Inactive"; - if (currency.is_default) { - statusCell.textContent += " (Default)"; - } - row.appendChild(statusCell); - - const actionsCell = document.createElement("td"); - const editButton = document.createElement("button"); - editButton.type = "button"; - editButton.className = "btn"; - editButton.dataset.action = "edit"; - editButton.dataset.code = currency.code; - editButton.textContent = "Edit"; - editButton.style.marginRight = "0.5rem"; - - const toggleButton = document.createElement("button"); - toggleButton.type = "button"; - toggleButton.className = "btn"; - toggleButton.dataset.action = "toggle"; - toggleButton.dataset.code = currency.code; - toggleButton.textContent = currency.is_active ? "Deactivate" : "Activate"; - if (currency.is_default && currency.is_active) { - toggleButton.disabled = true; - toggleButton.title = "The default currency must remain active."; - } - - actionsCell.appendChild(editButton); - actionsCell.appendChild(toggleButton); - - row.appendChild(actionsCell); - fragment.appendChild(row); - }); - tableBody.appendChild(fragment); - }; - - const refreshUI = (selectedCode) => { - currencies.sort((a, b) => a.code.localeCompare(b.code)); - renderTable(); - renderExistingOptions(selectedCode); - updateMetrics(); - }; - - const findCurrency = (code) => - currencies.find((item) => item.code === code) || null; - - const setFormForCurrency = (currency) => { - if (!form || !codeInput || !nameInput || !symbolInput || !statusSelect) { - return; - } - if (!currency) { - form.reset(); - if (existingSelect) { - existingSelect.value = ""; - } - codeInput.readOnly = false; - codeInput.value = ""; - nameInput.value = ""; - symbolInput.value = ""; - statusSelect.disabled = false; - statusSelect.value = "true"; - statusSelect.title = ""; - return; - } - - if (existingSelect) { - existingSelect.value = currency.code; - } - codeInput.readOnly = true; - codeInput.value = currency.code; - nameInput.value = currency.name || ""; - symbolInput.value = currency.symbol || ""; - statusSelect.value = currency.is_active ? "true" : "false"; - if (currency.is_default) { - statusSelect.disabled = true; - statusSelect.value = "true"; - statusSelect.title = "The default currency must remain active."; - } else { - statusSelect.disabled = false; - statusSelect.title = ""; - } - }; - - const resetFormState = () => { - setFormForCurrency(null); - }; - - const parseError = async (response, fallbackMessage) => { - try { - const detail = await response.json(); - if (detail && typeof detail === "object" && detail.detail) { - return detail.detail; - } - } catch (error) { - // ignore JSON parse errors - } - return fallbackMessage; - }; - - const fetchCurrenciesFromApi = async () => { - const url = `${apiBase}/?include_inactive=true`; - try { - const response = await fetch(url); - if (!response.ok) { - return; - } - const list = await response.json(); - if (Array.isArray(list)) { - replaceCurrencyList(list); - refreshUI(existingSelect ? existingSelect.value : undefined); - } - } catch (error) { - console.warn("Unable to refresh currency list", error); - } - }; - - const handleSubmit = async (event) => { - event.preventDefault(); - hideFeedback(); - if (!form || !codeInput || !nameInput || !statusSelect) { - return; - } - - const editingCode = existingSelect - ? uppercaseCode(existingSelect.value) - : ""; - const codeValue = uppercaseCode(codeInput.value); - const nameValue = (nameInput.value || "").trim(); - const symbolValue = normalizeSymbol(symbolInput ? symbolInput.value : ""); - const isActive = statusSelect.value !== "false"; - - if (!nameValue) { - showFeedback("Provide a currency name.", "error"); - return; - } - - if (!editingCode) { - if (!codeValue || codeValue.length !== 3) { - showFeedback("Provide a three-letter currency code.", "error"); - return; - } - } - - const payload = editingCode - ? { - name: nameValue, - symbol: symbolValue, - is_active: isActive, - } - : { - code: codeValue, - name: nameValue, - symbol: symbolValue, - is_active: isActive, - }; - - const targetCode = editingCode || codeValue; - const url = editingCode - ? `${apiBase}/${encodeURIComponent(editingCode)}` - : `${apiBase}/`; - - setButtonLoading(saveButton, true); - try { - const response = await fetch(url, { - method: editingCode ? "PUT" : "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(payload), - }); - - if (!response.ok) { - const message = await parseError( - response, - editingCode - ? "Unable to update the currency." - : "Unable to create the currency." - ); - throw new Error(message); - } - - const result = await response.json(); - const updated = upsertCurrency(result); - defaultCurrencyCode = uppercaseCode(defaultCurrencyCode); - refreshUI(updated ? updated.code : targetCode); - - if (editingCode) { - showFeedback("Currency updated successfully."); - if (updated) { - setFormForCurrency(updated); - } - } else { - showFeedback("Currency created successfully."); - resetFormState(); - } - } catch (error) { - showFeedback(error.message || "An unexpected error occurred.", "error"); - } finally { - setButtonLoading(saveButton, false); - } - }; - - const handleToggle = async (code, button) => { - const record = findCurrency(code); - if (!record) { - return; - } - hideFeedback(); - const nextState = !record.is_active; - const url = `${apiBase}/${encodeURIComponent(code)}/activation`; - setButtonLoading(button, true); - try { - const response = await fetch(url, { - method: "PATCH", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ is_active: nextState }), - }); - - if (!response.ok) { - const message = await parseError( - response, - nextState - ? "Unable to activate the currency." - : "Unable to deactivate the currency." - ); - throw new Error(message); - } - - const result = await response.json(); - const updated = upsertCurrency(result); - refreshUI(updated ? updated.code : code); - if (existingSelect && existingSelect.value === code && updated) { - setFormForCurrency(updated); - } - const actionMessage = nextState - ? `Currency ${code} activated.` - : `Currency ${code} deactivated.`; - showFeedback(actionMessage); - } catch (error) { - showFeedback(error.message || "An unexpected error occurred.", "error"); - } finally { - setButtonLoading(button, false); - } - }; - - const handleTableClick = (event) => { - const button = event.target.closest("button[data-action]"); - if (!button) { - return; - } - const code = uppercaseCode(button.dataset.code); - const action = button.dataset.action; - if (!code || !action) { - return; - } - if (action === "edit") { - const currency = findCurrency(code); - if (currency) { - setFormForCurrency(currency); - hideFeedback(); - if (nameInput) { - nameInput.focus(); - } - } - } else if (action === "toggle") { - handleToggle(code, button); - } - }; - - applyPayload(); - if (editorSection && editorSection.dataset.defaultCode) { - defaultCurrencyCode = uppercaseCode(editorSection.dataset.defaultCode); - currencies = currencies.map((record) => { - return record - ? { - ...record, - is_default: record.code === defaultCurrencyCode, - } - : record; - }); - } - apiBase = normalizeApiBase(apiBase); - - refreshUI(); - - if (form) { - form.addEventListener("submit", handleSubmit); - } - - if (existingSelect) { - existingSelect.addEventListener("change", (event) => { - const selectedCode = uppercaseCode(event.target.value); - if (!selectedCode) { - hideFeedback(); - resetFormState(); - return; - } - const currency = findCurrency(selectedCode); - if (currency) { - setFormForCurrency(currency); - hideFeedback(); - } - }); - } - - if (resetButton) { - resetButton.addEventListener("click", (event) => { - event.preventDefault(); - hideFeedback(); - resetFormState(); - }); - } - - if (codeInput) { - codeInput.addEventListener("input", () => { - const value = uppercaseCode(codeInput.value).slice(0, 3); - codeInput.value = value; - }); - } - - if (tableBody) { - tableBody.addEventListener("click", handleTableClick); - } - - fetchCurrenciesFromApi(); -}); diff --git a/static/js/dashboard.js b/static/js/dashboard.js deleted file mode 100644 index 5cec954..0000000 --- a/static/js/dashboard.js +++ /dev/null @@ -1,289 +0,0 @@ -(() => { - const dataElement = document.getElementById("dashboard-data"); - if (!dataElement) { - return; - } - - let state = {}; - try { - state = JSON.parse(dataElement.textContent || "{}"); - } catch (error) { - console.error("Failed to parse dashboard data", error); - return; - } - - const statusElement = document.getElementById("dashboard-status"); - const summaryContainer = document.getElementById("summary-metrics"); - const summaryEmpty = document.getElementById("summary-empty"); - const scenarioTableBody = document.querySelector("#scenario-table tbody"); - const scenarioEmpty = document.getElementById("scenario-table-empty"); - const overallMetricsList = document.getElementById("overall-metrics"); - const overallMetricsEmpty = document.getElementById("overall-metrics-empty"); - const recentList = document.getElementById("recent-simulations"); - const recentEmpty = document.getElementById("recent-simulations-empty"); - const maintenanceList = document.getElementById("upcoming-maintenance"); - const maintenanceEmpty = document.getElementById( - "upcoming-maintenance-empty" - ); - const refreshButton = document.getElementById("refresh-dashboard"); - const costChartCanvas = document.getElementById("cost-chart"); - const costChartEmpty = document.getElementById("cost-chart-empty"); - const activityChartCanvas = document.getElementById("activity-chart"); - const activityChartEmpty = document.getElementById("activity-chart-empty"); - - let costChartInstance = null; - let activityChartInstance = null; - - const setStatus = (message, variant = "success") => { - if (!statusElement) { - return; - } - if (!message) { - statusElement.hidden = true; - statusElement.textContent = ""; - statusElement.classList.remove("success", "error"); - return; - } - statusElement.textContent = message; - statusElement.hidden = false; - statusElement.classList.toggle("success", variant === "success"); - statusElement.classList.toggle("error", variant !== "success"); - }; - - const renderSummaryMetrics = () => { - if (!summaryContainer || !summaryEmpty) { - return; - } - summaryContainer.innerHTML = ""; - const metrics = Array.isArray(state.summary_metrics) - ? state.summary_metrics - : []; - metrics.forEach((metric) => { - const card = document.createElement("article"); - card.className = "metric-card"; - card.innerHTML = ` - ${metric.label} - ${metric.value} - `; - summaryContainer.appendChild(card); - }); - summaryEmpty.hidden = metrics.length > 0; - }; - - const renderScenarioTable = () => { - if (!scenarioTableBody || !scenarioEmpty) { - return; - } - scenarioTableBody.innerHTML = ""; - const rows = Array.isArray(state.scenario_rows) ? state.scenario_rows : []; - rows.forEach((row) => { - const tr = document.createElement("tr"); - tr.innerHTML = ` - ${row.scenario_name} - ${row.parameter_display} - ${row.equipment_display} - ${row.capex_display} - ${row.opex_display} - ${row.production_display} - ${row.consumption_display} - ${row.maintenance_display} - ${row.iterations_display} - ${row.simulation_mean_display} - `; - scenarioTableBody.appendChild(tr); - }); - scenarioEmpty.hidden = rows.length > 0; - }; - - const renderOverallMetrics = () => { - if (!overallMetricsList || !overallMetricsEmpty) { - return; - } - overallMetricsList.innerHTML = ""; - const items = Array.isArray(state.overall_report_metrics) - ? state.overall_report_metrics - : []; - items.forEach((item) => { - const li = document.createElement("li"); - li.className = "metric-list-item"; - li.textContent = `${item.label}: ${item.value}`; - overallMetricsList.appendChild(li); - }); - overallMetricsEmpty.hidden = items.length > 0; - }; - - const renderRecentSimulations = () => { - if (!recentList || !recentEmpty) { - return; - } - recentList.innerHTML = ""; - const runs = Array.isArray(state.recent_simulations) - ? state.recent_simulations - : []; - runs.forEach((run) => { - const item = document.createElement("li"); - item.className = "metric-list-item"; - item.textContent = `${run.scenario_name} · ${run.iterations_display} iterations · ${run.mean_display}`; - recentList.appendChild(item); - }); - recentEmpty.hidden = runs.length > 0; - }; - - const renderMaintenanceReminders = () => { - if (!maintenanceList || !maintenanceEmpty) { - return; - } - maintenanceList.innerHTML = ""; - const items = Array.isArray(state.upcoming_maintenance) - ? state.upcoming_maintenance - : []; - items.forEach((item) => { - const li = document.createElement("li"); - li.innerHTML = ` - ${item.equipment_name} · ${item.scenario_name} - ${item.date_display} · ${item.cost_display} · ${item.description} - `; - maintenanceList.appendChild(li); - }); - maintenanceEmpty.hidden = items.length > 0; - }; - - const buildChartConfig = (dataset, overrides = {}) => ({ - type: dataset.type || "bar", - data: { - labels: dataset.labels || [], - datasets: dataset.datasets || [], - }, - options: Object.assign( - { - responsive: true, - maintainAspectRatio: false, - plugins: { - legend: { position: "top" }, - tooltip: { enabled: true }, - }, - scales: { - x: { stacked: dataset.stacked ?? false }, - y: { stacked: dataset.stacked ?? false, beginAtZero: true }, - }, - }, - overrides.options || {} - ), - }); - - const renderCharts = () => { - if (costChartInstance) { - costChartInstance.destroy(); - } - if (activityChartInstance) { - activityChartInstance.destroy(); - } - - const costData = state.scenario_cost_chart || {}; - const activityData = state.scenario_activity_chart || {}; - - if (costChartCanvas && state.cost_chart_has_data) { - costChartInstance = new Chart( - costChartCanvas, - buildChartConfig(costData, { - options: { - scales: { - y: { - beginAtZero: true, - ticks: { - callback: (value) => - typeof value === "number" - ? value.toLocaleString(undefined, { - maximumFractionDigits: 0, - }) - : value, - }, - }, - }, - }, - }) - ); - if (costChartEmpty) { - costChartEmpty.hidden = true; - } - costChartCanvas.classList.remove("hidden"); - } else if (costChartEmpty && costChartCanvas) { - costChartEmpty.hidden = false; - costChartCanvas.classList.add("hidden"); - } - - if (activityChartCanvas && state.activity_chart_has_data) { - activityChartInstance = new Chart( - activityChartCanvas, - buildChartConfig(activityData, { - options: { - scales: { - y: { - beginAtZero: true, - ticks: { - callback: (value) => - typeof value === "number" - ? value.toLocaleString(undefined, { - maximumFractionDigits: 0, - }) - : value, - }, - }, - }, - }, - }) - ); - if (activityChartEmpty) { - activityChartEmpty.hidden = true; - } - activityChartCanvas.classList.remove("hidden"); - } else if (activityChartEmpty && activityChartCanvas) { - activityChartEmpty.hidden = false; - activityChartCanvas.classList.add("hidden"); - } - }; - - const renderView = () => { - renderSummaryMetrics(); - renderScenarioTable(); - renderOverallMetrics(); - renderRecentSimulations(); - renderMaintenanceReminders(); - renderCharts(); - }; - - const refreshDashboard = async () => { - setStatus("Refreshing dashboard…", "success"); - if (refreshButton) { - refreshButton.classList.add("is-loading"); - } - - try { - const response = await fetch("/ui/dashboard/data", { - headers: { "X-Requested-With": "XMLHttpRequest" }, - }); - - if (!response.ok) { - throw new Error("Unable to refresh dashboard data."); - } - - const payload = await response.json(); - state = payload || {}; - renderView(); - setStatus("Dashboard updated.", "success"); - } catch (error) { - console.error(error); - setStatus(error.message || "Failed to refresh dashboard.", "error"); - } finally { - if (refreshButton) { - refreshButton.classList.remove("is-loading"); - } - } - }; - - renderView(); - - if (refreshButton) { - refreshButton.addEventListener("click", refreshDashboard); - } -})(); diff --git a/static/js/equipment.js b/static/js/equipment.js deleted file mode 100644 index cf2c56d..0000000 --- a/static/js/equipment.js +++ /dev/null @@ -1,145 +0,0 @@ -document.addEventListener("DOMContentLoaded", () => { - const dataElement = document.getElementById("equipment-data"); - let equipmentByScenario = {}; - - if (dataElement) { - try { - const parsed = JSON.parse(dataElement.textContent || "{}"); - if (parsed && typeof parsed === "object") { - if (parsed.equipment && typeof parsed.equipment === "object") { - equipmentByScenario = parsed.equipment; - } - } - } catch (error) { - console.error("Unable to parse equipment data", error); - } - } - - const filterSelect = document.getElementById("equipment-scenario-filter"); - const tableWrapper = document.getElementById("equipment-table-wrapper"); - const tableBody = document.getElementById("equipment-table-body"); - const emptyState = document.getElementById("equipment-empty"); - const form = document.getElementById("equipment-form"); - const feedbackEl = document.getElementById("equipment-feedback"); - - const showFeedback = (message, type = "success") => { - if (!feedbackEl) { - return; - } - feedbackEl.textContent = message; - feedbackEl.classList.remove("hidden", "success", "error"); - feedbackEl.classList.add(type); - }; - - const hideFeedback = () => { - if (!feedbackEl) { - return; - } - feedbackEl.classList.add("hidden"); - feedbackEl.textContent = ""; - }; - - const renderEquipmentRows = (scenarioId) => { - if (!tableBody || !tableWrapper || !emptyState) { - return; - } - - const key = String(scenarioId); - const records = equipmentByScenario[key] || []; - - tableBody.innerHTML = ""; - - if (!records.length) { - emptyState.textContent = "No equipment recorded for this scenario yet."; - emptyState.classList.remove("hidden"); - tableWrapper.classList.add("hidden"); - return; - } - - emptyState.classList.add("hidden"); - tableWrapper.classList.remove("hidden"); - - records.forEach((record) => { - const row = document.createElement("tr"); - row.innerHTML = ` - ${record.name || "—"} - ${record.description || "—"} - `; - tableBody.appendChild(row); - }); - }; - - if (filterSelect) { - filterSelect.addEventListener("change", (event) => { - const value = event.target.value; - if (!value) { - if (emptyState && tableWrapper && tableBody) { - emptyState.textContent = - "Choose a scenario to review the equipment list."; - emptyState.classList.remove("hidden"); - tableWrapper.classList.add("hidden"); - tableBody.innerHTML = ""; - } - return; - } - renderEquipmentRows(value); - }); - } - - const submitEquipment = async (event) => { - event.preventDefault(); - hideFeedback(); - - if (!form) { - return; - } - - const formData = new FormData(form); - const scenarioId = formData.get("scenario_id"); - const payload = { - scenario_id: scenarioId ? Number(scenarioId) : null, - name: formData.get("name"), - description: formData.get("description") || null, - }; - - try { - const response = await fetch("/api/equipment/", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(payload), - }); - - if (!response.ok) { - const errorDetail = await response.json().catch(() => ({})); - throw new Error( - errorDetail.detail || "Unable to add equipment record." - ); - } - - const result = await response.json(); - const mapKey = String(result.scenario_id); - - if (!Array.isArray(equipmentByScenario[mapKey])) { - equipmentByScenario[mapKey] = []; - } - equipmentByScenario[mapKey].push(result); - - form.reset(); - showFeedback("Equipment saved.", "success"); - - if (filterSelect && filterSelect.value === String(result.scenario_id)) { - renderEquipmentRows(filterSelect.value); - } - } catch (error) { - showFeedback(error.message || "An unexpected error occurred.", "error"); - } - }; - - if (form) { - form.addEventListener("submit", submitEquipment); - } - - if (filterSelect && filterSelect.value) { - renderEquipmentRows(filterSelect.value); - } -}); diff --git a/static/js/maintenance.js b/static/js/maintenance.js deleted file mode 100644 index 9ec5f4a..0000000 --- a/static/js/maintenance.js +++ /dev/null @@ -1,243 +0,0 @@ -document.addEventListener("DOMContentLoaded", () => { - const dataElement = document.getElementById("maintenance-data"); - let equipmentByScenario = {}; - let maintenanceByScenario = {}; - - if (dataElement) { - try { - const parsed = JSON.parse(dataElement.textContent || "{}"); - if (parsed && typeof parsed === "object") { - if (parsed.equipment && typeof parsed.equipment === "object") { - equipmentByScenario = parsed.equipment; - } - if (parsed.maintenance && typeof parsed.maintenance === "object") { - maintenanceByScenario = parsed.maintenance; - } - } - } catch (error) { - console.error("Unable to parse maintenance data", error); - } - } - - const filterSelect = document.getElementById("maintenance-scenario-filter"); - const tableWrapper = document.getElementById("maintenance-table-wrapper"); - const tableBody = document.getElementById("maintenance-table-body"); - const emptyState = document.getElementById("maintenance-empty"); - const form = document.getElementById("maintenance-form"); - const feedbackEl = document.getElementById("maintenance-feedback"); - const formScenarioSelect = document.getElementById( - "maintenance-form-scenario" - ); - const equipmentSelect = document.getElementById("maintenance-form-equipment"); - const equipmentEmptyState = document.getElementById( - "maintenance-equipment-empty" - ); - - const showFeedback = (message, type = "success") => { - if (!feedbackEl) { - return; - } - feedbackEl.textContent = message; - feedbackEl.classList.remove("hidden", "success", "error"); - feedbackEl.classList.add(type); - }; - - const hideFeedback = () => { - if (!feedbackEl) { - return; - } - feedbackEl.classList.add("hidden"); - feedbackEl.textContent = ""; - }; - - const formatCost = (value) => - Number(value).toLocaleString(undefined, { - minimumFractionDigits: 2, - maximumFractionDigits: 2, - }); - - const formatDate = (value) => { - if (!value) { - return "—"; - } - const parsed = new Date(value); - if (Number.isNaN(parsed.getTime())) { - return value; - } - return parsed.toLocaleDateString(); - }; - - const renderMaintenanceRows = (scenarioId) => { - if (!tableBody || !tableWrapper || !emptyState) { - return; - } - - const key = String(scenarioId); - const records = maintenanceByScenario[key] || []; - - tableBody.innerHTML = ""; - - if (!records.length) { - emptyState.textContent = - "No maintenance entries recorded for this scenario yet."; - emptyState.classList.remove("hidden"); - tableWrapper.classList.add("hidden"); - return; - } - - emptyState.classList.add("hidden"); - tableWrapper.classList.remove("hidden"); - - records.forEach((record) => { - const row = document.createElement("tr"); - row.innerHTML = ` - ${formatDate(record.maintenance_date)} - ${record.equipment_name || "—"} - ${formatCost(record.cost)} - ${record.description || "—"} - `; - tableBody.appendChild(row); - }); - }; - - const populateEquipmentOptions = (scenarioId) => { - if (!equipmentSelect) { - return; - } - - equipmentSelect.innerHTML = - ''; - equipmentSelect.disabled = true; - - if (equipmentEmptyState) { - equipmentEmptyState.classList.add("hidden"); - } - - if (!scenarioId) { - return; - } - - const list = equipmentByScenario[String(scenarioId)] || []; - if (!list.length) { - if (equipmentEmptyState) { - equipmentEmptyState.textContent = - "Add equipment for this scenario before scheduling maintenance."; - equipmentEmptyState.classList.remove("hidden"); - } - return; - } - - list.forEach((item) => { - const option = document.createElement("option"); - option.value = item.id; - option.textContent = item.name || `Equipment ${item.id}`; - equipmentSelect.appendChild(option); - }); - - equipmentSelect.disabled = false; - }; - - if (filterSelect) { - filterSelect.addEventListener("change", (event) => { - const value = event.target.value; - if (!value) { - if (emptyState && tableWrapper && tableBody) { - emptyState.textContent = - "Choose a scenario to review upcoming or completed maintenance."; - emptyState.classList.remove("hidden"); - tableWrapper.classList.add("hidden"); - tableBody.innerHTML = ""; - } - return; - } - renderMaintenanceRows(value); - }); - } - - if (formScenarioSelect) { - formScenarioSelect.addEventListener("change", (event) => { - const value = event.target.value; - populateEquipmentOptions(value); - }); - } - - const submitMaintenance = async (event) => { - event.preventDefault(); - hideFeedback(); - - if (!form) { - return; - } - - const formData = new FormData(form); - const scenarioId = formData.get("scenario_id"); - const equipmentId = formData.get("equipment_id"); - const payload = { - scenario_id: scenarioId ? Number(scenarioId) : null, - equipment_id: equipmentId ? Number(equipmentId) : null, - maintenance_date: formData.get("maintenance_date"), - cost: Number(formData.get("cost")), - description: formData.get("description") || null, - }; - - if (!payload.scenario_id || !payload.equipment_id) { - showFeedback( - "Select a scenario and equipment before submitting.", - "error" - ); - return; - } - - try { - const response = await fetch("/api/maintenance/", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(payload), - }); - - if (!response.ok) { - const errorDetail = await response.json().catch(() => ({})); - throw new Error( - errorDetail.detail || "Unable to add maintenance entry." - ); - } - - const result = await response.json(); - const mapKey = String(result.scenario_id); - - if (!Array.isArray(maintenanceByScenario[mapKey])) { - maintenanceByScenario[mapKey] = []; - } - - const equipmentList = equipmentByScenario[mapKey] || []; - const matchedEquipment = equipmentList.find( - (item) => Number(item.id) === Number(result.equipment_id) - ); - result.equipment_name = matchedEquipment ? matchedEquipment.name : ""; - - maintenanceByScenario[mapKey].push(result); - - form.reset(); - populateEquipmentOptions(null); - showFeedback("Maintenance entry saved.", "success"); - - if (filterSelect && filterSelect.value === String(result.scenario_id)) { - renderMaintenanceRows(filterSelect.value); - } - } catch (error) { - showFeedback(error.message || "An unexpected error occurred.", "error"); - } - }; - - if (form) { - form.addEventListener("submit", submitMaintenance); - } - - if (filterSelect && filterSelect.value) { - renderMaintenanceRows(filterSelect.value); - } - - if (formScenarioSelect && formScenarioSelect.value) { - populateEquipmentOptions(formScenarioSelect.value); - } -}); diff --git a/static/js/parameters.js b/static/js/parameters.js deleted file mode 100644 index b96d207..0000000 --- a/static/js/parameters.js +++ /dev/null @@ -1,124 +0,0 @@ -document.addEventListener("DOMContentLoaded", () => { - const dataElement = document.getElementById("parameters-data"); - let parametersByScenario = {}; - - if (dataElement) { - try { - const parsed = JSON.parse(dataElement.textContent || "{}"); - if (parsed && typeof parsed === "object") { - parametersByScenario = parsed; - } - } catch (error) { - console.error("Unable to parse parameter data", error); - } - } - - const form = document.getElementById("parameter-form"); - const scenarioSelect = /** @type {HTMLSelectElement | null} */ ( - document.getElementById("scenario_id") - ); - const nameInput = /** @type {HTMLInputElement | null} */ ( - document.getElementById("name") - ); - const valueInput = /** @type {HTMLInputElement | null} */ ( - document.getElementById("value") - ); - const feedback = document.getElementById("parameter-feedback"); - const tableBody = document.getElementById("parameter-table-body"); - - const setFeedback = (message, variant) => { - if (!feedback) { - return; - } - feedback.textContent = message; - feedback.classList.remove("success", "error"); - if (variant) { - feedback.classList.add(variant); - } - }; - - const renderTable = (scenarioId) => { - if (!tableBody) { - return; - } - tableBody.innerHTML = ""; - const rows = parametersByScenario[String(scenarioId)] || []; - if (!rows.length) { - const emptyRow = document.createElement("tr"); - emptyRow.id = "parameter-empty-state"; - emptyRow.innerHTML = - 'No parameters recorded for this scenario yet.'; - tableBody.appendChild(emptyRow); - return; - } - rows.forEach((row) => { - const tr = document.createElement("tr"); - tr.innerHTML = ` - ${row.name} - ${row.value} - ${row.distribution_type ?? "—"} - ${ - row.distribution_parameters - ? JSON.stringify(row.distribution_parameters) - : "—" - } - `; - tableBody.appendChild(tr); - }); - }; - - if (scenarioSelect) { - renderTable(scenarioSelect.value); - scenarioSelect.addEventListener("change", () => - renderTable(scenarioSelect.value) - ); - } - - if (!form || !scenarioSelect || !nameInput || !valueInput) { - return; - } - - form.addEventListener("submit", async (event) => { - event.preventDefault(); - - const scenarioId = scenarioSelect.value; - const payload = { - scenario_id: Number(scenarioId), - name: nameInput.value.trim(), - value: Number(valueInput.value), - }; - - if (!payload.name) { - setFeedback("Parameter name is required.", "error"); - return; - } - - if (!Number.isFinite(payload.value)) { - setFeedback("Enter a numeric value.", "error"); - return; - } - - const response = await fetch("/api/parameters/", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(payload), - }); - - if (!response.ok) { - const errorText = await response.text(); - setFeedback(`Error saving parameter: ${errorText}`, "error"); - return; - } - - const data = await response.json(); - const scenarioKey = String(scenarioId); - parametersByScenario[scenarioKey] = parametersByScenario[scenarioKey] || []; - parametersByScenario[scenarioKey].push(data); - - form.reset(); - scenarioSelect.value = scenarioKey; - renderTable(scenarioKey); - nameInput.focus(); - setFeedback("Parameter saved.", "success"); - }); -}); diff --git a/static/js/production.js b/static/js/production.js deleted file mode 100644 index 9d19a41..0000000 --- a/static/js/production.js +++ /dev/null @@ -1,204 +0,0 @@ -document.addEventListener("DOMContentLoaded", () => { - const dataElement = document.getElementById("production-data"); - let data = { scenarios: [], production: {}, unit_options: [] }; - - if (dataElement) { - try { - const parsed = JSON.parse(dataElement.textContent || "{}"); - if (parsed && typeof parsed === "object") { - data = { - scenarios: Array.isArray(parsed.scenarios) ? parsed.scenarios : [], - production: - parsed.production && typeof parsed.production === "object" - ? parsed.production - : {}, - unit_options: Array.isArray(parsed.unit_options) - ? parsed.unit_options - : [], - }; - } - } catch (error) { - console.error("Unable to parse production data", error); - } - } - - const productionByScenario = data.production; - const filterSelect = document.getElementById("production-scenario-filter"); - const tableWrapper = document.getElementById("production-table-wrapper"); - const tableBody = document.getElementById("production-table-body"); - const emptyState = document.getElementById("production-empty"); - const form = document.getElementById("production-form"); - const feedbackEl = document.getElementById("production-feedback"); - const unitSelect = document.getElementById("production-form-unit"); - const unitSymbolInput = document.getElementById("production-form-unit-symbol"); - - const showFeedback = (message, type = "success") => { - if (!feedbackEl) { - return; - } - feedbackEl.textContent = message; - feedbackEl.classList.remove("hidden", "success", "error"); - feedbackEl.classList.add(type); - }; - - const hideFeedback = () => { - if (!feedbackEl) { - return; - } - feedbackEl.classList.add("hidden"); - feedbackEl.textContent = ""; - }; - - const formatAmount = (value) => - Number(value).toLocaleString(undefined, { - minimumFractionDigits: 2, - maximumFractionDigits: 2, - }); - - const formatMeasurement = (amount, symbol, name) => { - if (symbol) { - return `${formatAmount(amount)} ${symbol}`; - } - if (name) { - return `${formatAmount(amount)} ${name}`; - } - return formatAmount(amount); - }; - - const renderProductionRows = (scenarioId) => { - if (!tableBody || !tableWrapper || !emptyState) { - return; - } - - const key = String(scenarioId); - const records = productionByScenario[key] || []; - - tableBody.innerHTML = ""; - - if (!records.length) { - emptyState.textContent = - "No production output recorded for this scenario yet."; - emptyState.classList.remove("hidden"); - tableWrapper.classList.add("hidden"); - return; - } - - emptyState.classList.add("hidden"); - tableWrapper.classList.remove("hidden"); - - records.forEach((record) => { - const row = document.createElement("tr"); - row.innerHTML = ` - ${formatMeasurement( - record.amount, - record.unit_symbol, - record.unit_name - )} - ${record.description || "—"} - `; - tableBody.appendChild(row); - }); - }; - - if (filterSelect) { - filterSelect.addEventListener("change", (event) => { - const value = event.target.value; - if (!value) { - if (emptyState && tableWrapper && tableBody) { - emptyState.textContent = - "Choose a scenario to review its production output."; - emptyState.classList.remove("hidden"); - tableWrapper.classList.add("hidden"); - tableBody.innerHTML = ""; - } - return; - } - renderProductionRows(value); - }); - } - - const submitProduction = async (event) => { - event.preventDefault(); - hideFeedback(); - - if (!form) { - return; - } - - const formData = new FormData(form); - const scenarioId = formData.get("scenario_id"); - const unitName = formData.get("unit_name"); - const unitSymbol = formData.get("unit_symbol"); - const payload = { - scenario_id: scenarioId ? Number(scenarioId) : null, - amount: Number(formData.get("amount")), - description: formData.get("description") || null, - unit_name: unitName ? String(unitName) : null, - unit_symbol: unitSymbol ? String(unitSymbol) : null, - }; - - try { - const response = await fetch("/api/production/", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(payload), - }); - - if (!response.ok) { - const errorDetail = await response.json().catch(() => ({})); - throw new Error( - errorDetail.detail || "Unable to add production output record." - ); - } - - const result = await response.json(); - const mapKey = String(result.scenario_id); - - if (!Array.isArray(productionByScenario[mapKey])) { - productionByScenario[mapKey] = []; - } - productionByScenario[mapKey].push(result); - - form.reset(); - syncUnitSelection(); - showFeedback("Production output saved.", "success"); - - if (filterSelect && filterSelect.value === String(result.scenario_id)) { - renderProductionRows(filterSelect.value); - } - } catch (error) { - showFeedback(error.message || "An unexpected error occurred.", "error"); - } - }; - - if (form) { - form.addEventListener("submit", submitProduction); - } - - const syncUnitSelection = () => { - if (!unitSelect || !unitSymbolInput) { - return; - } - if (!unitSelect.value && unitSelect.options.length > 0) { - const firstOption = Array.from(unitSelect.options).find( - (option) => option.value - ); - if (firstOption) { - firstOption.selected = true; - } - } - const selectedOption = unitSelect.options[unitSelect.selectedIndex]; - unitSymbolInput.value = selectedOption - ? selectedOption.getAttribute("data-symbol") || "" - : ""; - }; - - if (unitSelect) { - unitSelect.addEventListener("change", syncUnitSelection); - syncUnitSelection(); - } - - if (filterSelect && filterSelect.value) { - renderProductionRows(filterSelect.value); - } -}); diff --git a/static/js/reporting.js b/static/js/reporting.js deleted file mode 100644 index 3ca2f64..0000000 --- a/static/js/reporting.js +++ /dev/null @@ -1,149 +0,0 @@ -document.addEventListener("DOMContentLoaded", () => { - const dataElement = document.getElementById("reporting-data"); - let reportingSummaries = []; - - if (dataElement) { - try { - const parsed = JSON.parse(dataElement.textContent || "[]"); - if (Array.isArray(parsed)) { - reportingSummaries = parsed; - } - } catch (error) { - console.error("Unable to parse reporting data", error); - } - } - - const REPORT_FIELDS = [ - { key: "iterations", label: "Iterations", decimals: 0 }, - { key: "mean", label: "Mean Result", decimals: 2 }, - { key: "variance", label: "Variance", decimals: 2 }, - { key: "std_dev", label: "Std. Dev", decimals: 2 }, - { key: "percentile_5", label: "Percentile 5", decimals: 2 }, - { key: "percentile_95", label: "Percentile 95", decimals: 2 }, - { key: "value_at_risk_95", label: "Value at Risk (95%)", decimals: 2 }, - { - key: "expected_shortfall_95", - label: "Expected Shortfall (95%)", - decimals: 2, - }, - ]; - - const tableWrapper = document.getElementById("reporting-table-wrapper"); - const tableBody = document.getElementById("reporting-table-body"); - const emptyState = document.getElementById("reporting-empty"); - const refreshButton = document.getElementById("report-refresh"); - const feedbackEl = document.getElementById("report-feedback"); - - const formatNumber = (value, decimals = 2) => { - if (value === null || value === undefined || Number.isNaN(Number(value))) { - return "—"; - } - return Number(value).toLocaleString(undefined, { - minimumFractionDigits: decimals, - maximumFractionDigits: decimals, - }); - }; - - const showFeedback = (message, type = "success") => { - if (!feedbackEl) { - return; - } - feedbackEl.textContent = message; - feedbackEl.classList.remove("hidden", "success", "error"); - feedbackEl.classList.add(type); - }; - - const hideFeedback = () => { - if (!feedbackEl) { - return; - } - feedbackEl.classList.add("hidden"); - feedbackEl.textContent = ""; - }; - - const renderReportingTable = (summaryData) => { - if (!tableBody || !tableWrapper || !emptyState) { - return; - } - - tableBody.innerHTML = ""; - - if (!summaryData.length) { - emptyState.classList.remove("hidden"); - tableWrapper.classList.add("hidden"); - return; - } - - emptyState.classList.add("hidden"); - tableWrapper.classList.remove("hidden"); - - summaryData.forEach((entry) => { - const row = document.createElement("tr"); - const scenarioCell = document.createElement("td"); - scenarioCell.textContent = entry.scenario_name; - row.appendChild(scenarioCell); - - REPORT_FIELDS.forEach((field) => { - const cell = document.createElement("td"); - const source = field.key === "iterations" ? entry : entry.summary || {}; - cell.textContent = formatNumber(source[field.key], field.decimals); - row.appendChild(cell); - }); - - tableBody.appendChild(row); - }); - }; - - const refreshMetrics = async () => { - hideFeedback(); - showFeedback("Refreshing metrics…", "success"); - - try { - const response = await fetch("/ui/reporting", { - method: "GET", - headers: { "X-Requested-With": "XMLHttpRequest" }, - }); - - if (!response.ok) { - throw new Error("Unable to refresh reporting data."); - } - - const text = await response.text(); - const parser = new DOMParser(); - const doc = parser.parseFromString(text, "text/html"); - const newTable = doc.querySelector("#reporting-table-wrapper"); - const newFeedback = doc.querySelector("#report-feedback"); - - if (!newTable) { - throw new Error("Unexpected response while refreshing."); - } - - const newEmptyState = doc.querySelector("#reporting-empty"); - - if (emptyState && newEmptyState) { - emptyState.className = newEmptyState.className; - emptyState.textContent = newEmptyState.textContent; - } - - if (tableWrapper) { - tableWrapper.className = newTable.className; - tableWrapper.innerHTML = newTable.innerHTML; - } - - if (newFeedback && feedbackEl) { - feedbackEl.className = newFeedback.className; - feedbackEl.textContent = newFeedback.textContent; - } - - showFeedback("Metrics refreshed.", "success"); - } catch (error) { - showFeedback(error.message || "An unexpected error occurred.", "error"); - } - }; - - renderReportingTable(reportingSummaries); - - if (refreshButton) { - refreshButton.addEventListener("click", refreshMetrics); - } -}); diff --git a/static/js/scenario-form.js b/static/js/scenario-form.js deleted file mode 100644 index f27722a..0000000 --- a/static/js/scenario-form.js +++ /dev/null @@ -1,78 +0,0 @@ -document.addEventListener("DOMContentLoaded", () => { - const form = document.getElementById("scenario-form"); - if (!form) { - return; - } - - const nameInput = /** @type {HTMLInputElement | null} */ ( - document.getElementById("name") - ); - const descriptionInput = /** @type {HTMLInputElement | null} */ ( - document.getElementById("description") - ); - const table = document.getElementById("scenario-table"); - const tableBody = document.getElementById("scenario-table-body"); - const emptyState = document.getElementById("empty-state"); - - form.addEventListener("submit", async (event) => { - event.preventDefault(); - - if (!nameInput || !descriptionInput) { - return; - } - - const payload = { - name: nameInput.value.trim(), - description: descriptionInput.value.trim() || null, - }; - - if (!payload.name) { - return; - } - - const response = await fetch("/api/scenarios/", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(payload), - }); - - if (!response.ok) { - const errorText = await response.text(); - console.error("Scenario creation failed", errorText); - return; - } - - const data = await response.json(); - const row = document.createElement("tr"); - row.dataset.scenarioId = String(data.id); - row.innerHTML = ` - ${data.name} - ${data.description ?? "—"} - `; - - if (emptyState) { - emptyState.remove(); - } - - if (table) { - table.classList.remove("hidden"); - table.removeAttribute("aria-hidden"); - } - - if (tableBody) { - tableBody.appendChild(row); - } - - form.reset(); - nameInput.focus(); - - const feedback = document.getElementById("feedback"); - if (feedback) { - feedback.textContent = `Scenario "${data.name}" created successfully.`; - feedback.classList.remove("hidden"); - setTimeout(() => { - feedback.classList.add("hidden"); - }, 3000); - } - }); -}); diff --git a/static/js/settings.js b/static/js/settings.js deleted file mode 100644 index 8d7d5c6..0000000 --- a/static/js/settings.js +++ /dev/null @@ -1,200 +0,0 @@ -(function () { - const dataScript = document.getElementById("theme-settings-data"); - const form = document.getElementById("theme-settings-form"); - const feedbackEl = document.getElementById("theme-settings-feedback"); - const resetBtn = document.getElementById("theme-settings-reset"); - const panel = document.getElementById("theme-settings"); - - if (!dataScript || !form || !feedbackEl || !panel) { - return; - } - - const apiUrl = panel.getAttribute("data-api"); - if (!apiUrl) { - return; - } - - const parsed = JSON.parse(dataScript.textContent || "{}"); - const currentValues = { ...(parsed.variables || {}) }; - const defaultValues = parsed.defaults || {}; - let envOverrides = { ...(parsed.envOverrides || {}) }; - - const previewElements = new Map(); - const inputs = Array.from(form.querySelectorAll(".color-value-input")); - - inputs.forEach((input) => { - const key = input.name; - const field = input.closest(".color-form-field"); - const preview = field ? field.querySelector(".color-preview") : null; - if (preview) { - previewElements.set(input, preview); - } - - if (Object.prototype.hasOwnProperty.call(envOverrides, key)) { - const overrideValue = envOverrides[key]; - input.value = overrideValue; - input.disabled = true; - input.setAttribute("aria-disabled", "true"); - input.dataset.envOverride = "true"; - if (field) { - field.classList.add("is-env-override"); - } - if (preview) { - preview.style.background = overrideValue; - } - return; - } - - input.addEventListener("input", () => { - const previewEl = previewElements.get(input); - if (previewEl) { - previewEl.style.background = input.value || defaultValues[key] || ""; - } - }); - }); - - function setFeedback(message, type) { - feedbackEl.textContent = message; - feedbackEl.classList.remove("hidden", "success", "error"); - if (type) { - feedbackEl.classList.add(type); - } - } - - function clearFeedback() { - feedbackEl.textContent = ""; - feedbackEl.classList.add("hidden"); - feedbackEl.classList.remove("success", "error"); - } - - function updateRootVariables(values) { - if (!values) { - return; - } - const root = document.documentElement; - Object.entries(values).forEach(([key, value]) => { - if (typeof key === "string" && typeof value === "string") { - root.style.setProperty(key, value); - } - }); - } - - function resetTo(source) { - inputs.forEach((input) => { - const key = input.name; - if (input.disabled) { - const previewEl = previewElements.get(input); - const fallback = envOverrides[key] || currentValues[key]; - if (previewEl && fallback) { - previewEl.style.background = fallback; - } - return; - } - if (Object.prototype.hasOwnProperty.call(source, key)) { - input.value = source[key]; - const previewEl = previewElements.get(input); - if (previewEl) { - previewEl.style.background = source[key]; - } - } - }); - } - - // Initialize previews to current values after page load. - resetTo(currentValues); - - resetBtn?.addEventListener("click", () => { - resetTo(defaultValues); - clearFeedback(); - setFeedback("Reverted to default values. Submit to save.", "success"); - }); - - form.addEventListener("submit", async (event) => { - event.preventDefault(); - clearFeedback(); - - const payload = {}; - inputs.forEach((input) => { - if (input.disabled) { - return; - } - payload[input.name] = input.value.trim(); - }); - - try { - const response = await fetch(apiUrl, { - method: "PUT", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ variables: payload }), - }); - - if (!response.ok) { - let detail = "Unable to save theme settings."; - try { - const errorData = await response.json(); - if (errorData?.detail) { - detail = Array.isArray(errorData.detail) - ? errorData.detail.map((item) => item.msg || item).join("; ") - : errorData.detail; - } - } catch (parseError) { - // Ignore JSON parse errors and use default detail message. - } - setFeedback(detail, "error"); - return; - } - - const data = await response.json(); - const variables = data?.variables || {}; - const responseOverrides = data?.env_overrides || {}; - - Object.assign(currentValues, variables); - envOverrides = { ...responseOverrides }; - - inputs.forEach((input) => { - const key = input.name; - const field = input.closest(".color-form-field"); - const previewEl = previewElements.get(input); - const isOverride = Object.prototype.hasOwnProperty.call( - envOverrides, - key, - ); - - if (isOverride) { - const overrideValue = envOverrides[key]; - input.value = overrideValue; - if (!input.disabled) { - input.disabled = true; - input.setAttribute("aria-disabled", "true"); - } - if (field) { - field.classList.add("is-env-override"); - } - if (previewEl) { - previewEl.style.background = overrideValue; - } - } else if (input.disabled) { - input.disabled = false; - input.removeAttribute("aria-disabled"); - if (field) { - field.classList.remove("is-env-override"); - } - if ( - previewEl && - Object.prototype.hasOwnProperty.call(variables, key) - ) { - previewEl.style.background = variables[key]; - } - } - }); - - updateRootVariables(variables); - resetTo(variables); - setFeedback("Theme colors updated successfully.", "success"); - } catch (error) { - setFeedback("Network error: unable to save settings.", "error"); - } - }); -})(); diff --git a/static/js/simulations.js b/static/js/simulations.js deleted file mode 100644 index 9e9c8d6..0000000 --- a/static/js/simulations.js +++ /dev/null @@ -1,354 +0,0 @@ -document.addEventListener("DOMContentLoaded", () => { - const dataElement = document.getElementById("simulations-data"); - let simulationScenarios = []; - let initialRuns = []; - - if (dataElement) { - try { - const parsed = JSON.parse(dataElement.textContent || "{}"); - if (parsed && typeof parsed === "object") { - if (Array.isArray(parsed.scenarios)) { - simulationScenarios = parsed.scenarios; - } - if (Array.isArray(parsed.runs)) { - initialRuns = parsed.runs; - } - } - } catch (error) { - console.error("Unable to parse simulations data", error); - } - } - - const SUMMARY_FIELDS = [ - { key: "count", label: "Iterations", decimals: 0 }, - { key: "mean", label: "Mean Result", decimals: 2 }, - { key: "median", label: "Median Result", decimals: 2 }, - { key: "min", label: "Minimum", decimals: 2 }, - { key: "max", label: "Maximum", decimals: 2 }, - { key: "variance", label: "Variance", decimals: 2 }, - { key: "std_dev", label: "Standard Deviation", decimals: 2 }, - { key: "percentile_5", label: "Percentile 5", decimals: 2 }, - { key: "percentile_95", label: "Percentile 95", decimals: 2 }, - { key: "value_at_risk_95", label: "Value at Risk (95%)", decimals: 2 }, - { - key: "expected_shortfall_95", - label: "Expected Shortfall (95%)", - decimals: 2, - }, - ]; - const SAMPLE_RESULT_LIMIT = 20; - - const filterSelect = document.getElementById("simulations-scenario-filter"); - const overviewWrapper = document.getElementById( - "simulations-overview-wrapper" - ); - const overviewBody = document.getElementById("simulations-overview-body"); - const overviewEmpty = document.getElementById("simulations-overview-empty"); - const emptyState = document.getElementById("simulations-empty"); - const summaryWrapper = document.getElementById("simulations-summary-wrapper"); - const summaryBody = document.getElementById("simulations-summary-body"); - const summaryEmpty = document.getElementById("simulations-summary-empty"); - const resultsWrapper = document.getElementById("simulations-results-wrapper"); - const resultsBody = document.getElementById("simulations-results-body"); - const resultsEmpty = document.getElementById("simulations-results-empty"); - const simulationForm = document.getElementById("simulation-run-form"); - const simulationFeedback = document.getElementById("simulation-feedback"); - const formScenarioSelect = document.getElementById( - "simulation-form-scenario" - ); - - const simulationRunsMap = Object.create(null); - - const getScenarioName = (id) => { - const match = simulationScenarios.find( - (scenario) => String(scenario.id) === String(id) - ); - return match ? match.name : `Scenario ${id}`; - }; - - const formatNumber = (value, decimals = 2) => { - if (value === null || value === undefined || Number.isNaN(Number(value))) { - return "—"; - } - return Number(value).toLocaleString(undefined, { - minimumFractionDigits: decimals, - maximumFractionDigits: decimals, - }); - }; - - const showFeedback = (element, message, type = "success") => { - if (!element) { - return; - } - element.textContent = message; - element.classList.remove("hidden", "success", "error"); - element.classList.add(type); - }; - - const hideFeedback = (element) => { - if (!element) { - return; - } - element.classList.add("hidden"); - element.textContent = ""; - }; - - const initializeRunsMap = () => { - simulationScenarios.forEach((scenario) => { - const key = String(scenario.id); - simulationRunsMap[key] = { - scenario_id: scenario.id, - scenario_name: scenario.name, - iterations: 0, - summary: null, - sample_results: [], - }; - }); - - initialRuns.forEach((run) => { - const key = String(run.scenario_id); - simulationRunsMap[key] = { - scenario_id: run.scenario_id, - scenario_name: run.scenario_name || getScenarioName(key), - iterations: run.iterations || 0, - summary: run.summary || null, - sample_results: Array.isArray(run.sample_results) - ? run.sample_results - : [], - }; - }); - }; - - const renderOverviewTable = () => { - if (!overviewBody) { - return; - } - - overviewBody.innerHTML = ""; - - if (!simulationScenarios.length) { - if (overviewWrapper) { - overviewWrapper.classList.add("hidden"); - } - if (overviewEmpty) { - overviewEmpty.classList.remove("hidden"); - } - return; - } - - if (overviewWrapper) { - overviewWrapper.classList.remove("hidden"); - } - if (overviewEmpty) { - overviewEmpty.classList.add("hidden"); - } - - simulationScenarios.forEach((scenario) => { - const key = String(scenario.id); - const run = simulationRunsMap[key]; - const iterations = run && run.iterations ? run.iterations : 0; - const meanValue = - iterations && run && run.summary ? run.summary.mean : null; - - const row = document.createElement("tr"); - row.innerHTML = ` - ${scenario.name} - ${iterations || 0} - ${iterations ? formatNumber(meanValue) : "—"} - `; - overviewBody.appendChild(row); - }); - }; - - const renderScenarioDetails = (scenarioId) => { - if (!scenarioId) { - if (emptyState) { - emptyState.classList.remove("hidden"); - } - if (summaryWrapper) { - summaryWrapper.classList.add("hidden"); - } - if (summaryEmpty) { - summaryEmpty.classList.add("hidden"); - } - if (resultsWrapper) { - resultsWrapper.classList.add("hidden"); - } - if (resultsEmpty) { - resultsEmpty.classList.add("hidden"); - } - return; - } - - if (emptyState) { - emptyState.classList.add("hidden"); - } - - const run = simulationRunsMap[String(scenarioId)]; - const summary = run ? run.summary : null; - const samples = run ? run.sample_results || [] : []; - - if (!summary) { - if (summaryWrapper) { - summaryWrapper.classList.add("hidden"); - } - if (summaryEmpty) { - summaryEmpty.classList.remove("hidden"); - } - } else { - if (summaryWrapper) { - summaryWrapper.classList.remove("hidden"); - } - if (summaryEmpty) { - summaryEmpty.classList.add("hidden"); - } - - if (summaryBody) { - summaryBody.innerHTML = ""; - SUMMARY_FIELDS.forEach((field) => { - const row = document.createElement("tr"); - row.innerHTML = ` - ${field.label} - ${formatNumber(summary[field.key], field.decimals)} - `; - summaryBody.appendChild(row); - }); - } - } - - if (!samples.length) { - if (resultsWrapper) { - resultsWrapper.classList.add("hidden"); - } - if (resultsEmpty) { - resultsEmpty.classList.remove("hidden"); - } - } else { - if (resultsWrapper) { - resultsWrapper.classList.remove("hidden"); - } - if (resultsEmpty) { - resultsEmpty.classList.add("hidden"); - } - - if (resultsBody) { - resultsBody.innerHTML = ""; - samples.slice(0, SAMPLE_RESULT_LIMIT).forEach((item, index) => { - const row = document.createElement("tr"); - row.innerHTML = ` - ${index + 1} - ${formatNumber(item)} - `; - resultsBody.appendChild(row); - }); - } - } - }; - - const runSimulation = async (event) => { - event.preventDefault(); - hideFeedback(simulationFeedback); - - if (!simulationForm) { - return; - } - - const formData = new FormData(simulationForm); - const scenarioId = formData.get("scenario_id"); - const payload = { - scenario_id: scenarioId ? Number(scenarioId) : null, - iterations: Number(formData.get("iterations")), - seed: formData.get("seed") ? Number(formData.get("seed")) : null, - }; - - if (!payload.scenario_id) { - showFeedback( - simulationFeedback, - "Select a scenario before running a simulation.", - "error" - ); - return; - } - - try { - const response = await fetch("/api/simulations/", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(payload), - }); - - if (!response.ok) { - const errorDetail = await response.json().catch(() => ({})); - throw new Error(errorDetail.detail || "Unable to run simulation."); - } - - const result = await response.json(); - const mapKey = String(result.scenario_id); - const summary = - result.summary && typeof result.summary === "object" - ? result.summary - : null; - const iterations = - summary && typeof summary.count === "number" - ? summary.count - : payload.iterations || 0; - - simulationRunsMap[mapKey] = { - scenario_id: result.scenario_id, - scenario_name: getScenarioName(mapKey), - iterations, - summary, - sample_results: Array.isArray(result.sample_results) - ? result.sample_results - : [], - }; - - renderOverviewTable(); - renderScenarioDetails(mapKey); - - if (filterSelect) { - filterSelect.value = mapKey; - } - if (formScenarioSelect) { - formScenarioSelect.value = mapKey; - } - - simulationForm.reset(); - showFeedback(simulationFeedback, "Simulation completed.", "success"); - } catch (error) { - showFeedback( - simulationFeedback, - error.message || "An unexpected error occurred.", - "error" - ); - } - }; - - initializeRunsMap(); - renderOverviewTable(); - - if (filterSelect) { - filterSelect.addEventListener("change", (event) => { - const value = event.target.value; - renderScenarioDetails(value); - }); - } - - if (formScenarioSelect) { - formScenarioSelect.addEventListener("change", (event) => { - const value = event.target.value; - if (filterSelect) { - filterSelect.value = value; - } - renderScenarioDetails(value); - }); - } - - if (simulationForm) { - simulationForm.addEventListener("submit", runSimulation); - } - - if (filterSelect && filterSelect.value) { - renderScenarioDetails(filterSelect.value); - } -});