Adjusted typography, styles, and other visual elements to ensure consistent layouts

This commit is contained in:
2026-10-26 19:14:18 -03:00
parent 3229ce9c41
commit 7606643ff4
20 changed files with 498 additions and 30 deletions

View File

@@ -404,6 +404,7 @@ function createContentModal({
const opportunitiesTable = document.createElement("table");
opportunitiesTable.id = OPPORTUNITIES_TABLE_ID;
opportunitiesTable.className = "opportunities-extension-table";
opportunitiesTable.setAttribute("data-table-layout", "standard");
opportunitiesTable.setAttribute("aria-label", "Opportunities");
tableSurface.append(opportunitiesTable);

View File

@@ -0,0 +1,78 @@
/* Modal titles follow the customer Service Requests heading. Keep section and
calendar headings independent from dialog typography. */
.opportunities-extension-modal :is(
.opportunities-extension-header,
.opportunities-extension-account-plans-header,
.opportunities-extension-preferences-header
) {
padding: 16px;
gap: 16px;
align-items: flex-start;
}
/* These headers live in panels that already supply the outer inset. */
.opportunities-extension-modal :is(.oe-org-dialog.oe-contact-dialog,
.oe-org-confirm-panel, .opportunities-extension-projection-confirm,
.opportunities-extension-debug-panel) { padding: 16px; }
.opportunities-extension-modal :is(.oe-org-editor-header,
.opportunities-extension-debug-panel-header) {
padding: 0;
gap: 16px;
align-items: flex-start;
}
.opportunities-extension-modal :is(
.opportunities-extension-account-plans-header,
.opportunities-extension-preferences-header,
.opportunities-extension-debug-panel-header
) p {
margin: 5px 0 0;
font-family: "Oracle Sans", Arial, Helvetica, sans-serif;
font-size: 13px;
font-weight: 400;
line-height: 1.35;
letter-spacing: normal;
overflow-wrap: anywhere;
}
/* Search controls share the 16px inset of their table frames. */
.opportunities-extension-modal :is(.opportunities-extension-customer-table-toolbar,
.opportunities-extension-ratecard-toolbar,
.opportunities-extension-ratecard-plan-details) { padding-inline: 16px; }
.opportunities-extension-modal .opportunities-extension-customer-table-toolbar {
padding-block: 12px;
}
.opportunities-extension-modal .opportunities-extension-customer-table-search {
width: min(420px, 100%);
height: 38px;
padding: 8px 12px;
font-size: 13px;
line-height: 20px;
}
.opportunities-extension-modal :is(
.opportunities-extension-header,
.opportunities-extension-account-plans-header,
.opportunities-extension-preferences-header,
.opportunities-extension-debug-panel-header,
.oe-org-editor-header
) :is(h1, h2, h3),
.opportunities-extension-modal .opportunities-extension-projection-confirm h2,
.opportunities-extension-modal .oe-org-confirm-panel h3 {
margin: 0;
font-family: "Oracle Sans", Arial, Helvetica, sans-serif;
font-size: 20px;
font-weight: 700;
line-height: 24px;
letter-spacing: normal;
color: #312d2a;
min-width: 0;
overflow-wrap: anywhere;
}
.opportunities-extension-modal[data-theme="dark"] :is(
.opportunities-extension-header,
.opportunities-extension-account-plans-header,
.opportunities-extension-preferences-header,
.opportunities-extension-debug-panel-header,
.oe-org-editor-header
) :is(h1, h2, h3),
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-projection-confirm h2,
.opportunities-extension-modal[data-theme="dark"] .oe-org-confirm-panel h3 {
color: #f6f4f2;
}

View File

@@ -0,0 +1,106 @@
/* Shared data-table presentation. Calendar grids are intentionally not data tables. */
.opportunities-extension-modal :is(
.opportunities-extension-table-surface,
.opportunities-extension-install-base-content,
.opportunities-extension-service-requests-content,
.opportunities-extension-ratecard-viewport,
.opportunities-extension-projection-viewport,
.oe-time-grid,
.oe-contact-opportunities-table
) {
box-sizing: border-box;
min-width: 0;
border: 1px solid var(--oe-table-heading-border);
border-radius: 4px;
overflow: auto;
}
/* Unpadded dialogs need an inset on the scrolling frame itself. */
.opportunities-extension-modal :is(
.opportunities-extension-install-base-content,
.opportunities-extension-service-requests-content,
.opportunities-extension-ratecard-viewport
) { margin: 16px; }
/* Other frames already sit inside a padded body: keep a single gutter. */
.opportunities-extension-modal :is(
.opportunities-extension-body,
.opportunities-extension-projection-body,
.opportunities-extension-products-detail,
.oe-time-body,
.oe-org-dialog.oe-contact-opportunities
) { padding: 16px; }
.opportunities-extension-modal table.opportunities-extension-products-table {
border: 1px solid var(--oe-table-heading-border);
}
.opportunities-extension-modal {
--oe-table-heading: #faf9f8;
--oe-table-text: #312d2a;
--oe-table-border: #ebe8e5;
--oe-table-heading-border: #b8b2ad;
--oe-table-hover: #f5f4f2;
}
.opportunities-extension-modal[data-theme="dark"] {
--oe-table-heading: #333130;
--oe-table-text: #f6f4f2;
--oe-table-border: #3f3c39;
--oe-table-heading-border: #4e4a46;
--oe-table-hover: #35322f;
}
.opportunities-extension-modal table[data-table-layout="standard"]:not(.fc-scrollgrid) {
width: 100%; border-collapse: collapse; border-spacing: 0; table-layout: auto;
color: var(--oe-table-text); font-size: 12px; line-height: 1.35;
}
.opportunities-extension-modal table[data-table-layout="standard"] > thead > tr > th {
padding: 10px 12px; height: 44px; vertical-align: middle; text-align: left;
white-space: nowrap; overflow-wrap: normal; word-break: normal;
font-size: 12px; font-weight: 700; color: var(--oe-table-text);
background: var(--oe-table-heading); border-bottom: 1px solid var(--oe-table-heading-border);
}
.opportunities-extension-modal table[data-table-layout="standard"] > tbody > tr > td {
padding: 10px 12px; vertical-align: middle; text-align: left;
border-bottom: 1px solid var(--oe-table-border);
}
.opportunities-extension-modal table[data-table-layout="standard"] > tbody > tr:hover { background: var(--oe-table-hover); }
.opportunities-extension-modal table[data-table-layout="standard"] > thead > tr > th .opportunities-extension-sort-button,
.opportunities-extension-modal table[data-table-layout="standard"] .opportunities-extension-ratecard-sort {
display: flex; align-items: center; justify-content: flex-start; gap: 8px;
width: 100%; min-width: max-content; min-height: 28px; padding: 4px 18px 4px 0;
border: 0; border-radius: 0; background: transparent; color: inherit;
text-align: left; white-space: nowrap; overflow-wrap: normal; font-weight: 700;
}
.opportunities-extension-modal table[data-table-layout="standard"] .opportunities-extension-sort-label { white-space: nowrap; overflow-wrap: normal; word-break: normal; }
.opportunities-extension-modal table[data-table-layout="standard"] > thead > tr > th .opportunities-extension-sort-button::after { position: absolute; right: 0; left: auto; font-size: 12px; }
.opportunities-extension-modal table[data-table-layout="standard"] .opportunities-extension-ratecard-sort-icon { margin-left: auto; }
/* Currency columns keep their heading and value on the same right edge. */
.opportunities-extension-modal table[data-table-layout="standard"] :is(th, td)[data-align="money"] { text-align: right; font-variant-numeric: tabular-nums; }
.opportunities-extension-modal table[data-table-layout="standard"] > thead > tr > th[data-align="money"] .opportunities-extension-sort-button,
.opportunities-extension-modal table[data-table-layout="standard"] th[data-align="money"] .opportunities-extension-ratecard-sort { justify-content: flex-end; text-align: right; padding: 4px 0 4px 18px; }
.opportunities-extension-modal table[data-table-layout="standard"] > thead > tr > th[data-align="money"] .opportunities-extension-sort-button::after { left: 0; right: auto; }
.opportunities-extension-modal table[data-table-layout="standard"] th[data-align="money"] .opportunities-extension-ratecard-sort-icon { order: -1; margin-left: 0; margin-right: auto; }
.opportunities-extension-modal table[data-table-layout="standard"] td[data-align="money"] > span[class*="skeleton"] { margin-left: auto; }
/* Preserve room for Overview badges and the compact daily entry controls. */
.opportunities-extension-modal table.opportunities-extension-table > tbody > tr > td:is([data-column="name"], [data-column="customer"]) { padding-bottom: 38px; vertical-align: top; }
.opportunities-extension-modal .oe-time-grid table[data-table-layout="standard"] > :is(thead, tbody) > tr > :is(th, td) { padding-left: 5px; padding-right: 5px; }
.opportunities-extension-modal .oe-time-grid table[data-table-layout="standard"] { table-layout: fixed; }
.opportunities-extension-modal table[data-table-layout="standard"] > tbody > tr > th[scope="row"] { padding: 10px 12px; text-align: left; background: var(--oe-table-heading); border-bottom: 1px solid var(--oe-table-border); }
/* Rate Card must fit its drawer; headings may wrap at spaces, never mid-word. */
.opportunities-extension-modal table.opportunities-extension-ratecard-table[data-table-layout="standard"] {
min-width: 0;
table-layout: fixed;
}
.opportunities-extension-modal table.opportunities-extension-ratecard-table[data-table-layout="standard"] > :is(thead, tbody) > tr > :is(th, td) {
padding-inline: 6px;
}
.opportunities-extension-modal table.opportunities-extension-ratecard-table[data-table-layout="standard"] > thead > tr > th,
.opportunities-extension-modal table.opportunities-extension-ratecard-table[data-table-layout="standard"] > thead > tr > th .opportunities-extension-ratecard-sort {
white-space: normal;
overflow-wrap: normal;
word-break: normal;
hyphens: none;
min-width: 0;
}
.opportunities-extension-modal table.opportunities-extension-ratecard-table[data-table-layout="standard"] > thead > tr > th .opportunities-extension-ratecard-sort {
padding: 4px 0;
gap: 4px;
}
.opportunities-extension-modal .opportunities-extension-ratecard-sort > span:first-child { min-width: 0; }
.opportunities-extension-modal .opportunities-extension-ratecard-sort-icon { flex: 0 0 12px; }

View File

@@ -43,7 +43,6 @@
width: 100%; min-width: 1120px; table-layout: fixed; border-collapse: collapse; font-size: 11px;
}
.opportunities-extension-install-base-table col { width: 7%; }
.opportunities-extension-install-base-table .install-base-col-partyName { width: 12%; }
.opportunities-extension-install-base-table .install-base-col-productGroupName { width: 13%; }
.opportunities-extension-install-base-table .install-base-col-productName { width: 13%; }
.opportunities-extension-install-base-table .install-base-col-quantity { width: 5%; }

View File

@@ -2,7 +2,7 @@ function createInstallBaseView({ MODAL_ID, createCloseIcon, formatOracleResponse
const { document } = environment;
let active = null;
const columns = [
["partyName", "Customer"], ["productGroupName", "Product Group"], ["productName", "Product"],
["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"],
@@ -36,6 +36,7 @@ function createInstallBaseView({ MODAL_ID, createCloseIcon, formatOracleResponse
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");

View File

@@ -114,6 +114,7 @@ function createManageTime({ MODAL_ID, environment, tableSorting, searchableSelec
const taskStatus = node("div", "oe-time-status"); taskStatus.setAttribute("role", "status");
const taskRetry = button("Retry Task Types", "clock", loadTaskTypes, true); taskRetry.hidden = true;
const scroll = node("div", "oe-time-grid"), table = node("table");
table.setAttribute("data-table-layout", "standard");
table.setAttribute("aria-label", "Weekly time entries"); scroll.append(table);
const footer = node("footer", "opportunities-extension-action-form-footer oe-time-footer");
const add = button("Add", "add", () => addRow("Service"), true);

View File

@@ -76,6 +76,7 @@ function createOpportunitiesTable({
const isSorted = opportunitiesTableState.opportunitiesTableState.sortKey === column.key;
header.scope = "col";
header.setAttribute("data-column", column.key);
if (column.key === "revenue") header.setAttribute("data-align", "money");
if (column.sortable === false) {
const label = document.createElement("span");
@@ -117,6 +118,7 @@ function createOpportunitiesTable({
OPPORTUNITIES_COLUMNS.forEach(column => {
const cell = document.createElement("td");
cell.setAttribute("data-column", column.key);
if (column.key === "revenue") cell.setAttribute("data-align", "money");
const bar = document.createElement("span");
bar.className = "opportunities-extension-account-plans-skeleton-bar opportunities-extension-table-skeleton-text";
cell.append(bar);
@@ -149,6 +151,7 @@ function createOpportunitiesTable({
OPPORTUNITIES_COLUMNS.forEach((column) => {
const cell = document.createElement("td");
cell.setAttribute("data-column", column.key);
if (column.key === "revenue") cell.setAttribute("data-align", "money");
if (column.key === "stage") {
cell.append(createStageBadge(column.display(item)));

View File

@@ -136,6 +136,7 @@ function createOrgChartView({ environment, tableSorting, model, loadLayout, icon
header.append(title, button("Close opportunities", "close", finish));
const status = el("p", "oe-org-status"), surface = el("div", "oe-contact-opportunities-table"), table = el("table"), head = el("thead"), row = el("tr"), body = el("tbody");
status.setAttribute("role", "status");
table.setAttribute("data-table-layout", "standard");
const columns = [
{ key: "OptyNumber", label: "OptyNumber" }, { key: "WinProb", label: "Win Probability", type: "number" },
{ key: "Name", label: "Name" }, { key: "CurrencyCode", label: "CurrencyCode" },
@@ -143,7 +144,7 @@ function createOrgChartView({ environment, tableSorting, model, loadLayout, icon
];
const sorting = tableSorting.create("contact-opportunities", columns);
let allItems = [], loading = false;
for (const column of columns) { const th = el("th"); th.scope = "col"; sorting.header(th, column.key, column.label, renderRows); row.append(th); }
for (const column of columns) { const th = el("th"); th.scope = "col"; if (column.key === "Revenue") th.setAttribute("data-align", "money"); sorting.header(th, column.key, column.label, renderRows); row.append(th); }
head.append(row); table.append(head, body); surface.append(table);
const footer = el("footer", "oe-org-form-actions");
const retry = button("Retry", "plus", () => load(), true);
@@ -167,6 +168,7 @@ function createOrgChartView({ environment, tableSorting, model, loadLayout, icon
const row = el("tr"); row.setAttribute("aria-hidden", "true");
for (const column of columns) {
const cell = el("td"), bar = el("span", "oe-contact-skeleton");
if (column.key === "Revenue") cell.setAttribute("data-align", "money");
bar.style.width = column.key === "Name" ? "90%" : column.key === "WinProb" || column.key === "StatusCode" ? "45%" : "70%";
cell.append(bar); row.append(cell);
}
@@ -177,7 +179,7 @@ function createOrgChartView({ environment, tableSorting, model, loadLayout, icon
for (const item of sorting.select(allItems)) {
const row = el("tr");
for (const key of ["OptyNumber", "WinProb", "Name", "CurrencyCode", "Revenue", "EffectiveDate", "StatusCode"]) {
const cell = el("td"); let value = item[key] ?? "—";
const cell = el("td"); if (key === "Revenue") cell.setAttribute("data-align", "money"); let value = item[key] ?? "—";
if (key === "Name" && item.OptyNumber) {
const link = el("a", "", String(value)); link.href = "https://eeho.fa.us2.oraclecloud.com/fscmUI/redwood/cx-sales/application/container/opportunities/opportunities-detail?puid=" + encodeURIComponent(item.OptyNumber); link.target = "_blank"; link.rel = "noopener noreferrer"; cell.append(link);
} else {

View File

@@ -101,6 +101,7 @@ function createProductsDetails({
} else {
const table = document.createElement("table");
table.className = "opportunities-extension-products-table";
table.setAttribute("data-table-layout", "standard");
table.setAttribute("aria-label", `Products for opportunity ${optyNumber}`);
const columns = [
@@ -123,6 +124,7 @@ function createProductsDetails({
columns.forEach((column) => {
const header = document.createElement("th");
header.scope = "col";
if (column.label === "Amount") header.setAttribute("data-align", "money");
header.textContent = column.label;
headerRow.append(header);
});
@@ -133,6 +135,7 @@ function createProductsDetails({
const productRow = document.createElement("tr");
columns.forEach((column) => {
const productCell = document.createElement("td");
if (column.label === "Amount") productCell.setAttribute("data-align", "money");
if (column.render) {
productCell.className = "opportunities-extension-projection-cell";
productCell.append(column.render(product));

View File

@@ -94,6 +94,7 @@ function createRampProjectionView({ MODAL_ID, compare, createAlertIcon, createCl
viewport.setAttribute("role", "region");
viewport.setAttribute("aria-label", "Monthly consumption comparison");
const table = node("table", "opportunities-extension-projection-table");
table.setAttribute("data-table-layout", "standard");
const caption = node("caption", "opportunities-extension-visually-hidden", `Monthly consumption comparison (${currency}). Difference is forecast minus calculated.`);
const head = node("thead"), headRow = node("tr"), tbody = node("tbody");
const sorting = tableSorting.create("consumption-projection", [
@@ -103,7 +104,7 @@ function createRampProjectionView({ MODAL_ID, compare, createAlertIcon, createCl
const rowNodes = new Map();
const renderRows = () => tbody.replaceChildren(...sorting.select(result.rows).map(item => rowNodes.get(item)));
for (const [key, label] of [["month", "Month"], ["actual", "Forecast"], ["calculated", "Calculated"], ["difference", "Difference"]]) {
const th = node("th"); th.scope = "col";
const th = node("th"); th.scope = "col"; if (key !== "month") th.setAttribute("data-align", "money");
sorting.header(th, key, label, renderRows); headRow.append(th);
}
head.append(headRow);
@@ -114,7 +115,7 @@ function createRampProjectionView({ MODAL_ID, compare, createAlertIcon, createCl
month.scope = "row";
if (item.prorated) month.append(node("span", "opportunities-extension-projection-prorated", " (Prorated)"));
tr.append(month);
for (const value of [item.actual, item.calculated, item.difference]) tr.append(node("td", "", value === null ? "-" : formatAmount(value, currency)));
for (const value of [item.actual, item.calculated, item.difference]) { const cell = node("td", "", value === null ? "-" : formatAmount(value, currency)); cell.setAttribute("data-align", "money"); tr.append(cell); }
rowNodes.set(item, tr);
}
renderRows();

View File

@@ -139,3 +139,8 @@
.opportunities-extension-ratecard-overlay, .opportunities-extension-ratecard-drawer, .opportunities-extension-ratecard-skeleton,
.opportunities-extension-ratecard-overlay[data-closing="true"], .opportunities-extension-ratecard-overlay[data-closing="true"] .opportunities-extension-ratecard-drawer { animation: none; transition: none; }
}
.opportunities-extension-ratecard-toolbar { flex-wrap: wrap; }
.opportunities-extension-ratecard-order-filter { position: relative; min-width: 0; }
.opportunities-extension-ratecard-order-filter .opportunities-extension-customer-filter-trigger { min-height: 38px; padding-block: 8px; font-size: 13px; }
.opportunities-extension-ratecard-order-filter .opportunities-extension-customer-filter-panel { width: min(320px, calc(100vw - 80px)); max-height: 45vh; overflow: auto; }

View File

@@ -65,12 +65,80 @@ function createRatecardView({ ACCOUNT_PLANS_MODAL_ID, columns, formatValue, sele
search.setAttribute("aria-label", "Search rate card");
const count = element("span", "count", "Loading rate card...");
count.setAttribute("role", "status");
toolbar.append(search, count);
const orderFilter = element("div", "order-filter");
const filterElement = (tag, suffix, text) => {
const node = element(tag, null, text);
node.className = `opportunities-extension-customer-filter-${suffix}`;
return node;
};
const orderButton = filterElement("button", "trigger", "All order numbers");
orderButton.type = "button";
orderButton.disabled = true;
orderButton.setAttribute("aria-label", "Filter by Order Number");
orderButton.setAttribute("aria-expanded", "false");
const orderPanel = filterElement("section", "panel");
orderPanel.id = "opportunities-extension-ratecard-orders";
orderButton.setAttribute("aria-controls", orderPanel.id);
orderPanel.hidden = true;
const orderSearch = filterElement("input", "search");
orderSearch.type = "search";
orderSearch.placeholder = "Search order numbers";
orderSearch.setAttribute("aria-label", "Search order numbers");
const allLabel = filterElement("label", "select-all");
const allOrders = element("input");
allOrders.type = "checkbox";
allLabel.append(allOrders, element("span", null, "Select all"));
const orderList = filterElement("div", "list");
orderPanel.append(orderSearch, allLabel, orderList);
orderFilter.append(orderButton, orderPanel);
toolbar.append(search, orderFilter, count);
let orders = [], selectedOrders = new Set();
const orderKey = item => String(item.orderNumber ?? "").trim();
function closeOrders() {
orderPanel.hidden = true;
orderButton.setAttribute("aria-expanded", "false");
}
function updateOrders() {
const all = orders.length > 0 && selectedOrders.size === orders.length;
allOrders.checked = all;
allOrders.indeterminate = selectedOrders.size > 0 && !all;
orderButton.textContent = all ? "All order numbers" : `${selectedOrders.size} order numbers selected`;
orderList.textContent = "";
const query = orderSearch.value.trim().toLowerCase();
orders.filter(value => (value || "Not provided").toLowerCase().includes(query)).forEach(value => {
const label = filterElement("label", "option");
const checkbox = element("input");
checkbox.type = "checkbox";
checkbox.checked = selectedOrders.has(value);
checkbox.addEventListener("change", () => {
if (checkbox.checked) selectedOrders.add(value); else selectedOrders.delete(value);
allOrders.checked = selectedOrders.size === orders.length;
allOrders.indeterminate = selectedOrders.size > 0 && !allOrders.checked;
orderButton.textContent = allOrders.checked ? "All order numbers" : `${selectedOrders.size} order numbers selected`;
renderRows();
});
label.append(checkbox, element("span", null, value || "Not provided"));
orderList.append(label);
});
}
orderButton.addEventListener("click", () => {
orderPanel.hidden = !orderPanel.hidden;
orderButton.setAttribute("aria-expanded", String(!orderPanel.hidden));
if (!orderPanel.hidden) orderSearch.focus();
});
orderSearch.addEventListener("input", updateOrders);
allOrders.addEventListener("change", () => {
selectedOrders = new Set(allOrders.checked ? orders : []);
updateOrders();
renderRows();
});
root.addEventListener("click", event => { if (!orderFilter.contains(event.target)) closeOrders(); });
const viewport = element("div", "viewport");
viewport.tabIndex = 0;
viewport.setAttribute("role", "region");
viewport.setAttribute("aria-label", "Rate card items");
const table = element("table", "table");
table.setAttribute("data-table-layout", "standard");
const caption = element("caption", null, "Rate card items");
caption.className = "opportunities-extension-visually-hidden";
const colgroup = element("colgroup");
@@ -90,6 +158,7 @@ function createRatecardView({ ACCOUNT_PLANS_MODAL_ID, columns, formatValue, sele
colgroup.append(col);
const th = element("th");
th.scope = "col";
if (column.type === "price") th.setAttribute("data-align", "money");
th.setAttribute("aria-sort", "none");
const sort = element("button", "sort");
sort.type = "button";
@@ -139,7 +208,7 @@ function createRatecardView({ ACCOUNT_PLANS_MODAL_ID, columns, formatValue, sele
function renderRows() {
if (!alive()) return;
body.textContent = "";
const visible = selectItems(current.items, search.value, current.sortKey, current.direction);
const visible = selectItems(current.items.filter(item => selectedOrders.has(orderKey(item))), search.value, current.sortKey, current.direction);
count.textContent = `${visible.length} of ${current.items.length} items`;
if (!visible.length) {
showNotice(current.items.length ? "No matching rate card items." : "No rate card items found for this plan.");
@@ -148,6 +217,7 @@ function createRatecardView({ ACCOUNT_PLANS_MODAL_ID, columns, formatValue, sele
const row = element("tr");
columns.forEach(column => {
const cell = element("td", null, formatValue(item, column));
if (column.type === "price") cell.setAttribute("data-align", "money");
if (["price", "number", "percent"].includes(column.type)) cell.className = "opportunities-extension-ratecard-numeric";
row.append(cell);
});
@@ -172,9 +242,12 @@ function createRatecardView({ ACCOUNT_PLANS_MODAL_ID, columns, formatValue, sele
if (event.key === "Escape") {
event.preventDefault();
event.stopPropagation();
if (!orderPanel.hidden) { closeOrders(); orderButton.focus(); return; }
closeRatecardDrawer();
} else if (event.key === "Tab") {
const controls = [close, search, viewport, ...headRow.querySelectorAll("button")];
const controls = [close, search, ...(!orderButton.disabled ? [orderButton] : []),
...(!orderPanel.hidden ? [orderSearch, allOrders, ...orderList.querySelectorAll("input")] : []),
viewport, ...headRow.querySelectorAll("button")];
const index = controls.indexOf(document.activeElement);
if (index < 0 || (event.shiftKey && index === 0) || (!event.shiftKey && index === controls.length - 1)) {
event.preventDefault();
@@ -196,6 +269,10 @@ function createRatecardView({ ACCOUNT_PLANS_MODAL_ID, columns, formatValue, sele
showItems(items) {
if (!alive()) return;
current.items = items;
orders = [...new Set(items.map(orderKey))].sort((a, b) => a.localeCompare(b, "en", { numeric: true }));
selectedOrders = new Set(orders);
orderButton.disabled = !orders.length;
updateOrders();
current.status = "ready";
panel.setAttribute("aria-busy", "false");
renderRows();

View File

@@ -34,6 +34,7 @@ function createServiceRequestsView({ MODAL_ID, createCloseIcon, formatOracleResp
const sortColumns = columns.map(([key, , value]) => ({ key, value, type: ["created", "lastUpdated"].includes(key) ? "date" : "text" }));
function createTable(items, loading = false, sorting = tableSorting.create("service-requests", sortColumns), onSort) {
const table = element("table", "opportunities-extension-service-requests-table");
table.setAttribute("data-table-layout", "standard");
table.setAttribute("aria-label", "Customer service requests");
const group = element("colgroup");
const head = element("thead");