Refactor templates to externalize JavaScript: Moved inline scripts to separate JS files and added JSON data attributes for better maintainability and performance. Updated consumption, costs, equipment, maintenance, production, reporting, and simulations templates accordingly.
This commit is contained in:
240
static/js/costs.js
Normal file
240
static/js/costs.js
Normal file
@@ -0,0 +1,240 @@
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const dataElement = document.getElementById("costs-payload");
|
||||
let capexByScenario = {};
|
||||
let opexByScenario = {};
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
} 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 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 sumAmount = (records) =>
|
||||
records.reduce((total, record) => total + Number(record.amount || 0), 0);
|
||||
|
||||
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 = `
|
||||
<td>${formatAmount(record.amount)}</td>
|
||||
<td>${record.description || "—"}</td>
|
||||
`;
|
||||
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 = `
|
||||
<td>${formatAmount(record.amount)}</td>
|
||||
<td>${record.description || "—"}</td>
|
||||
`;
|
||||
opexTableBody.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
capexTotal.textContent = formatAmount(sumAmount(capexRecords));
|
||||
opexTotal.textContent = formatAmount(sumAmount(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 || "";
|
||||
}
|
||||
};
|
||||
|
||||
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 payload = {
|
||||
scenario_id: scenarioId ? Number(scenarioId) : null,
|
||||
amount: Number(formData.get("amount")),
|
||||
description: formData.get("description") || null,
|
||||
};
|
||||
|
||||
if (!payload.scenario_id) {
|
||||
showFeedback(feedbackEl, "Select a scenario 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();
|
||||
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) {
|
||||
capexForm.addEventListener("submit", (event) =>
|
||||
submitCostEntry(event, "/api/costs/capex", capexByScenario, capexFeedback)
|
||||
);
|
||||
}
|
||||
|
||||
if (opexForm) {
|
||||
opexForm.addEventListener("submit", (event) =>
|
||||
submitCostEntry(event, "/api/costs/opex", opexByScenario, opexFeedback)
|
||||
);
|
||||
}
|
||||
|
||||
if (filterSelect && filterSelect.value) {
|
||||
toggleCostView(true);
|
||||
renderCostTables(filterSelect.value);
|
||||
syncFormSelections(filterSelect.value);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user