Implemented the Organization Chart feature to create customer organizational
This commit is contained in:
@@ -6,7 +6,10 @@ function createSearchableSelect({ environment }) {
|
||||
if (text !== undefined) node.textContent = text;
|
||||
return node;
|
||||
}
|
||||
function searchableSelect(select, field, onChange, { floating = false, tooltipContent, listWidth = 0, listMaxHeight = 180 } = {}) {
|
||||
function searchableSelect(select, field, onChange, { floating = false, tooltipContent, listWidth = 0, listMaxHeight = 180, search, searchDelay = 350 } = {}) {
|
||||
let searchTimer, searchVersion = 0, disposed = false;
|
||||
const schedule = environment.setTimeout || globalThis.setTimeout, cancel = environment.clearTimeout || globalThis.clearTimeout;
|
||||
function cancelSearch() { searchVersion++; if (searchTimer) cancel(searchTimer); searchTimer = null; input.removeAttribute("aria-busy"); }
|
||||
const box = element("div", "opportunities-extension-action-combobox");
|
||||
const input = element("input");
|
||||
input.type = "text";
|
||||
@@ -67,6 +70,7 @@ function createSearchableSelect({ environment }) {
|
||||
const options = () => Array.from(select.children).filter(item => item.value);
|
||||
const normalize = value => String(value).normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
|
||||
function closeList() {
|
||||
if (search) cancelSearch();
|
||||
hideTooltip();
|
||||
list.hidden = true;
|
||||
input.setAttribute("aria-expanded", "false");
|
||||
@@ -115,10 +119,10 @@ function createSearchableSelect({ environment }) {
|
||||
list.style.transform = opensAbove ? "translateY(-100%)" : "none";
|
||||
}
|
||||
}
|
||||
function render(query = "") {
|
||||
function render(query = "", statusText = "") {
|
||||
if (input.disabled) return;
|
||||
hideTooltip();
|
||||
matches = options().filter(item => normalize(`${item.textContent} ${item.getAttribute?.("data-description") || ""} ${item.value}`).includes(normalize(query)));
|
||||
matches = statusText ? [] : options().filter(item => search || normalize(`${item.textContent} ${item.getAttribute?.("data-description") || ""} ${item.value}`).includes(normalize(query)));
|
||||
index = matches.findIndex(item => item.value === select.value);
|
||||
list.textContent = "";
|
||||
matches.forEach((item, i) => {
|
||||
@@ -137,7 +141,7 @@ function createSearchableSelect({ environment }) {
|
||||
list.append(row);
|
||||
});
|
||||
if (!matches.length) {
|
||||
const empty = element("div", "opportunities-extension-action-option", "No results");
|
||||
const empty = element("div", "opportunities-extension-action-option", statusText || "No results");
|
||||
empty.setAttribute("role", "status");
|
||||
list.append(empty);
|
||||
}
|
||||
@@ -147,7 +151,33 @@ function createSearchableSelect({ environment }) {
|
||||
highlight(false);
|
||||
}
|
||||
input.addEventListener("click", () => { render(); input.select?.(); });
|
||||
input.addEventListener("input", () => render(input.value));
|
||||
function searchInput(event) {
|
||||
if (!search) { render(input.value); return; }
|
||||
cancelSearch(); select.replaceChildren(select.children[0]); select.value = ""; onChange?.();
|
||||
const query = input.value;
|
||||
render("", query.trim() ? "Searching..." : "Type to search.");
|
||||
if (!query.trim() || event?.isComposing) return;
|
||||
const version = searchVersion;
|
||||
const isCurrent = () => !disposed && version === searchVersion && input.isConnected && document.activeElement === input;
|
||||
input.setAttribute("aria-busy", "true");
|
||||
searchTimer = schedule(async () => {
|
||||
if (!isCurrent()) return;
|
||||
try {
|
||||
const results = await search(query, isCurrent);
|
||||
if (!isCurrent()) return;
|
||||
const placeholder = select.children[0]; select.replaceChildren(placeholder);
|
||||
for (const result of results) {
|
||||
const option = element("option", "", result.label); option.value = result.value;
|
||||
if (result.description) option.setAttribute("data-description", result.description);
|
||||
select.append(option);
|
||||
}
|
||||
select.value = ""; render();
|
||||
} catch (error) { if (isCurrent()) render("", (error.message || "Unable to search contacts.") + " Type to retry."); }
|
||||
finally { if (isCurrent()) input.removeAttribute("aria-busy"); }
|
||||
}, searchDelay);
|
||||
}
|
||||
input.addEventListener("input", searchInput);
|
||||
if (search) input.addEventListener("compositionend", searchInput);
|
||||
input.addEventListener("blur", sync);
|
||||
if (tooltip) {
|
||||
input.addEventListener("mouseenter", () => { if (list.hidden) showTooltip(select.value, input); });
|
||||
@@ -169,7 +199,7 @@ function createSearchableSelect({ environment }) {
|
||||
} else if (event.key === "Tab") sync();
|
||||
});
|
||||
sync();
|
||||
return { input, sync };
|
||||
return { input, sync, destroy() { disposed = true; closeList(); } };
|
||||
}
|
||||
return searchableSelect;
|
||||
}
|
||||
|
||||
@@ -408,7 +408,7 @@
|
||||
/* Keep layout rules on the outer table, never on expanded product tables. */
|
||||
.opportunities-extension-table[data-opportunities-layout="inline-actions"] {
|
||||
width: 100%;
|
||||
min-width: 1120px;
|
||||
min-width: 1168px;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
@@ -494,7 +494,7 @@
|
||||
}
|
||||
|
||||
.opportunities-extension-table > tbody > tr > td[data-column="customer"] .opportunities-extension-cell-actions {
|
||||
min-width: 84px;
|
||||
min-width: 114px;
|
||||
}
|
||||
|
||||
.opportunities-extension-cell-actions .opportunities-extension-action-button svg {
|
||||
|
||||
@@ -7,6 +7,7 @@ function createOpportunitiesTable({
|
||||
createCloudConsumptionButton,
|
||||
createInstallBaseButton,
|
||||
createServiceRequestsButton,
|
||||
createOrganizationChartButton,
|
||||
createCopyOpportunityButton,
|
||||
createDetailLink,
|
||||
createProductsCountButton,
|
||||
@@ -122,7 +123,7 @@ function createOpportunitiesTable({
|
||||
if (column.key === "name" || column.key === "customer") {
|
||||
const actions = document.createElement("span");
|
||||
actions.className = "opportunities-extension-cell-actions";
|
||||
for (let action = 0; action < (column.key === "name" ? 4 : 3); action++) {
|
||||
for (let action = 0; action < 4; action++) {
|
||||
const badge = document.createElement("span");
|
||||
badge.className = "opportunities-extension-account-plans-skeleton-bar opportunities-extension-table-skeleton-badge";
|
||||
actions.append(badge);
|
||||
@@ -198,7 +199,7 @@ function createOpportunitiesTable({
|
||||
);
|
||||
const customerActions = document.createElement("span");
|
||||
customerActions.className = "opportunities-extension-cell-actions";
|
||||
customerActions.append(createCloudConsumptionButton(item), createInstallBaseButton(item), createServiceRequestsButton(item));
|
||||
customerActions.append(createCloudConsumptionButton(item), createInstallBaseButton(item), createServiceRequestsButton(item), createOrganizationChartButton(item));
|
||||
customerCellContent.append(customerLink, customerActions);
|
||||
cell.append(customerCellContent);
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user