Files
opportunities-extension/src/features/install-base/view.js

154 lines
7.2 KiB
JavaScript

function createInstallBaseView({ MODAL_ID, createCloseIcon, formatOracleResponseDate, createSearch, tableSorting, environment }) {
const { document } = environment;
let active = null;
const columns = [
["productGroupName", "Product Group"], ["productName", "Product"],
["currentSupport", "Current Support", "amount"], ["orderNumber", "Order Number"],
["supportStatus", "Support Status", "status"], ["quantityType", "Quantity Type"],
["quantity", "Quantity", "quantity"], ["supportStartDate", "Support Start Date", "date"],
["supportEndDate", "Support End Date", "date"], ["licenseValue", "License Value", "amount"],
["shipDate", "Ship Date", "date"]
];
function element(tag, className, text) {
const node = document.createElement(tag);
if (className) node.className = className;
if (text !== undefined) node.textContent = text;
return node;
}
function closeInstallBaseModal() {
if (!active) return false;
const previous = active;
active = null;
previous.modal.remove();
if (previous.trigger?.isConnected) previous.trigger.focus();
return true;
}
function formatValue(value, type) {
if (value === null || value === undefined || value === "") return "-";
if (type === "date") return formatOracleResponseDate(value);
if ((type === "amount" || type === "quantity") && Number.isFinite(Number(value))) {
return new Intl.NumberFormat("en-US", {
minimumFractionDigits: type === "amount" ? 2 : 0, maximumFractionDigits: type === "amount" ? 2 : 4
}).format(Number(value));
}
return String(value).trim();
}
const sortColumns = columns.map(([key, , type]) => ({ key, type: ["amount", "quantity"].includes(type) ? "number" : type }));
function createTable(items, loading = false, sorting = tableSorting.create("installed-base", sortColumns), onSort) {
const table = element("table", "opportunities-extension-install-base-table");
table.setAttribute("data-table-layout", "standard");
table.setAttribute("aria-label", "Installed base items");
const head = element("thead");
const header = element("tr");
const group = element("colgroup");
columns.forEach(([key, label]) => {
const col = element("col");
col.className = `install-base-col-${key}`;
group.append(col);
const th = element("th");
th.scope = "col";
sorting.header(th, key, label, onSort, loading);
header.append(th);
});
head.append(header);
const body = element("tbody");
(loading ? Array.from({ length: 8 }, () => ({})) : sorting.select(items)).forEach(item => {
const row = element("tr");
columns.forEach(([key, , type]) => {
const cell = element("td");
if (loading) {
const bar = element("span", "opportunities-extension-account-plans-skeleton-bar");
bar.setAttribute("aria-hidden", "true");
cell.append(bar);
} else {
const value = item[key];
const text = formatValue(value, type);
if (type === "status") {
const badge = element("span", "opportunities-extension-install-base-status", text);
badge.dataset.status = text.toUpperCase();
cell.append(badge);
} else cell.textContent = text;
if (type) cell.dataset.format = type;
}
row.append(cell);
});
body.append(row);
});
table.append(group, head, body);
return table;
}
function openInstallBaseModal(opportunity, trigger) {
closeInstallBaseModal();
const overlay = document.getElementById(MODAL_ID);
if (!overlay || overlay.hidden) return { render() {} };
const modal = element("section", "opportunities-extension-account-plans-modal");
modal.setAttribute("role", "dialog");
modal.setAttribute("aria-modal", "true");
modal.setAttribute("aria-busy", "true");
modal.setAttribute("aria-labelledby", "opportunities-extension-install-base-title");
const dialog = element("div", "opportunities-extension-account-plans-dialog opportunities-extension-install-base-dialog");
const header = element("header", "opportunities-extension-account-plans-header");
const heading = element("div");
const title = element("h2", "", "Installed Base");
title.id = "opportunities-extension-install-base-title";
heading.append(title, element("p", "", opportunity.CustomerAccount?.PartyUniqueName || "Customer"));
const close = element("button", "opportunities-extension-icon-button");
close.type = "button";
close.title = "Close";
close.setAttribute("aria-label", "Close installed base");
close.append(createCloseIcon());
close.addEventListener("click", closeInstallBaseModal);
header.append(heading, close);
const content = element("div", "opportunities-extension-install-base-content");
content.tabIndex = 0;
content.setAttribute("role", "region");
content.setAttribute("aria-label", "Installed base results");
const status = element("p", "opportunities-extension-install-base-message", "Loading installed base...");
status.setAttribute("role", "status");
let loadedItems = [], loaded = false, failure = "";
const sorting = tableSorting.create("installed-base", sortColumns);
const search = createSearch({ label: "Search installed base", onSearch: renderResults,
getValues: item => columns.flatMap(([key, , type]) => [item[key], formatValue(item[key], type)]) });
const toolbar = element("div", "opportunities-extension-customer-table-toolbar");
toolbar.append(search.input);
content.append(status, createTable([], true));
dialog.append(header, toolbar, content);
modal.append(dialog);
overlay.append(modal);
active = { modal, trigger };
modal.addEventListener("keydown", event => {
if (event.key === "Tab") {
event.preventDefault();
const controls = [close, search.input, content, ...content.querySelectorAll("button")].filter(control => !control.disabled);
const index = controls.indexOf(document.activeElement);
controls[(index + (event.shiftKey ? -1 : 1) + controls.length) % controls.length].focus();
}
});
close.focus();
function renderResults() {
if (!loaded || active?.modal !== modal || overlay.hidden) return;
const items = failure ? [] : search.filter(loadedItems);
content.textContent = "";
status.textContent = failure || (loadedItems.length
? `${items.length} of ${loadedItems.length} installed base items${items.length ? "" : ". No matching records."}`
: "No installed base items found for this customer.");
status.setAttribute("role", failure ? "alert" : "status");
content.append(status);
if (!failure && items.length) content.append(createTable(items, false, sorting, key => {
renderResults();
content.querySelector(`[data-sort-key="${key}"]`)?.focus();
}));
content.scrollTop = 0;
}
return { render(items, error = "") {
// A closed or replaced dialog must never receive a late response.
if (active?.modal !== modal || overlay.hidden) return;
modal.setAttribute("aria-busy", "false");
loadedItems = items; failure = error; loaded = true;
search.reset();
renderResults();
} };
}
return { openInstallBaseModal, closeInstallBaseModal, createTable };
}