- Introduced a new template for listing scenarios associated with a project. - Added metrics for total, active, draft, and archived scenarios. - Implemented quick actions for creating new scenarios and reviewing project overview. - Enhanced navigation with breadcrumbs for better user experience. refactor: update Opex and Profitability templates for consistency - Changed titles and button labels for clarity in Opex and Profitability templates. - Updated form IDs and action URLs for better alignment with new naming conventions. - Improved navigation links to include scenario and project overviews. test: add integration tests for Opex calculations - Created new tests for Opex calculation HTML and JSON flows. - Validated successful calculations and ensured correct data persistence. - Implemented tests for currency mismatch and unsupported frequency scenarios. test: enhance project and scenario route tests - Added tests to verify scenario list rendering and calculator shortcuts. - Ensured scenario detail pages link back to the portfolio correctly. - Validated project detail pages show associated scenarios accurately.
138 lines
4.2 KiB
JavaScript
138 lines
4.2 KiB
JavaScript
document.addEventListener("DOMContentLoaded", () => {
|
|
const container = document.querySelector("[data-project-table]");
|
|
const filterInput = document.querySelector("[data-project-filter]");
|
|
|
|
const resolveFilterItems = () => {
|
|
if (!container) {
|
|
return [];
|
|
}
|
|
|
|
const entries = Array.from(
|
|
container.querySelectorAll("[data-project-entry]")
|
|
);
|
|
|
|
if (entries.length) {
|
|
return entries;
|
|
}
|
|
|
|
if (container.tagName === "TABLE") {
|
|
return Array.from(container.querySelectorAll("tbody tr"));
|
|
}
|
|
|
|
return [];
|
|
};
|
|
|
|
const filterItems = resolveFilterItems();
|
|
|
|
if (container && filterInput && filterItems.length) {
|
|
filterInput.addEventListener("input", () => {
|
|
const query = filterInput.value.trim().toLowerCase();
|
|
filterItems.forEach((item) => {
|
|
const match = item.textContent.toLowerCase().includes(query);
|
|
item.style.display = match ? "" : "none";
|
|
});
|
|
});
|
|
}
|
|
|
|
const sidebar = document.querySelector(".app-sidebar");
|
|
const appMain = document.querySelector(".app-main");
|
|
if (!sidebar || !appMain) {
|
|
return;
|
|
}
|
|
|
|
const body = document.body;
|
|
const mobileQuery = window.matchMedia("(max-width: 900px)");
|
|
let toggleButton = document.querySelector("[data-sidebar-toggle]");
|
|
|
|
if (!toggleButton) {
|
|
toggleButton = document.createElement("button");
|
|
toggleButton.type = "button";
|
|
toggleButton.className = "sidebar-toggle";
|
|
toggleButton.setAttribute("data-sidebar-toggle", "");
|
|
toggleButton.setAttribute("aria-expanded", "false");
|
|
toggleButton.setAttribute("aria-label", "Toggle primary navigation");
|
|
toggleButton.hidden = true;
|
|
toggleButton.innerHTML = [
|
|
'<span class="sidebar-toggle-icon" aria-hidden="true"></span>',
|
|
'<span class="sidebar-toggle-label">Menu</span>',
|
|
].join("");
|
|
appMain.insertBefore(toggleButton, appMain.firstChild);
|
|
}
|
|
|
|
let overlay = document.querySelector("[data-sidebar-overlay]");
|
|
if (!overlay) {
|
|
overlay = document.createElement("div");
|
|
overlay.className = "sidebar-overlay";
|
|
overlay.setAttribute("data-sidebar-overlay", "");
|
|
overlay.setAttribute("aria-hidden", "true");
|
|
document.body.appendChild(overlay);
|
|
}
|
|
|
|
const primaryNav = document.querySelector(".sidebar-nav");
|
|
if (primaryNav) {
|
|
if (!primaryNav.id) {
|
|
primaryNav.id = "primary-navigation";
|
|
}
|
|
toggleButton.setAttribute("aria-controls", primaryNav.id);
|
|
}
|
|
|
|
const openSidebar = () => {
|
|
body.classList.remove("sidebar-collapsed");
|
|
body.classList.add("sidebar-open");
|
|
toggleButton.setAttribute("aria-expanded", "true");
|
|
overlay.setAttribute("aria-hidden", "false");
|
|
};
|
|
|
|
const closeSidebar = (focusToggle = false) => {
|
|
body.classList.add("sidebar-collapsed");
|
|
body.classList.remove("sidebar-open");
|
|
toggleButton.setAttribute("aria-expanded", "false");
|
|
overlay.setAttribute("aria-hidden", "true");
|
|
if (focusToggle) {
|
|
toggleButton.focus({ preventScroll: true });
|
|
}
|
|
};
|
|
|
|
const toggleSidebar = () => {
|
|
if (body.classList.contains("sidebar-open")) {
|
|
closeSidebar();
|
|
} else {
|
|
openSidebar();
|
|
sidebar.setAttribute("aria-hidden", "false");
|
|
}
|
|
};
|
|
|
|
const applyResponsiveState = (mql) => {
|
|
if (!mql.matches) {
|
|
toggleButton.hidden = true;
|
|
body.classList.remove("sidebar-open", "sidebar-collapsed");
|
|
sidebar.setAttribute("aria-hidden", "true");
|
|
overlay.setAttribute("aria-hidden", "true");
|
|
sidebar.removeAttribute("aria-hidden");
|
|
return;
|
|
}
|
|
|
|
toggleButton.hidden = false;
|
|
if (!body.classList.contains("sidebar-open")) {
|
|
body.classList.add("sidebar-collapsed");
|
|
sidebar.setAttribute("aria-hidden", "true");
|
|
}
|
|
};
|
|
|
|
toggleButton.addEventListener("click", toggleSidebar);
|
|
overlay.addEventListener("click", () => closeSidebar());
|
|
|
|
document.addEventListener("keydown", (event) => {
|
|
if (event.key === "Escape" && body.classList.contains("sidebar-open")) {
|
|
closeSidebar(true);
|
|
}
|
|
});
|
|
|
|
applyResponsiveState(mobileQuery);
|
|
if (typeof mobileQuery.addEventListener === "function") {
|
|
mobileQuery.addEventListener("change", applyResponsiveState);
|
|
} else if (typeof mobileQuery.addListener === "function") {
|
|
mobileQuery.addListener(applyResponsiveState);
|
|
}
|
|
});
|