Implemented filters for Service Requests and Actions
This commit is contained in:
@@ -24,7 +24,11 @@ function createActivityCache({ authSessionState, preferencesState, environment }
|
||||
const hours = Number(preferencesState.opptyProductsCacheHours);
|
||||
const ttl = Number.isFinite(hours) && hours > 0 ? Math.min(hours, 720) * 3600000 : 86400000;
|
||||
if (!principal || !Number.isFinite(fetchedAt) || fetchedAt <= 0 || fetchedAt > Date.now() || Date.now() - fetchedAt >= ttl) return null;
|
||||
return { key: `opportunitiesExtension.activities.v1:${JSON.stringify([principal, kind, String(id)])}`, fetchedAt };
|
||||
const identity = [principal, kind, String(id)];
|
||||
const legacyKey = `opportunitiesExtension.activities.v1:${JSON.stringify(identity)}`;
|
||||
// SR snapshots from different product updates must not overwrite each other.
|
||||
if (kind === "sr") identity.push(fetchedAt);
|
||||
return { key: `opportunitiesExtension.activities.v1:${JSON.stringify(identity)}`, legacyKey, fetchedAt, ttl };
|
||||
}
|
||||
async function read(kind, id, timestamp) {
|
||||
const scope = context(kind, id, timestamp);
|
||||
@@ -33,7 +37,11 @@ function createActivityCache({ authSessionState, preferencesState, environment }
|
||||
const invalidated = await storage("get", invalidatedKey);
|
||||
if (scope.fetchedAt <= Number(invalidated?.[invalidatedKey] || 0)) return null;
|
||||
const result = await storage("get", scope.key);
|
||||
const entry = result?.[scope.key];
|
||||
let entry = result?.[scope.key];
|
||||
if (!entry && scope.key !== scope.legacyKey) {
|
||||
const legacy = await storage("get", scope.legacyKey);
|
||||
entry = legacy?.[scope.legacyKey];
|
||||
}
|
||||
return entry?.fetchedAt === scope.fetchedAt && Array.isArray(entry.items)
|
||||
&& entry.items.every(item => item && typeof item === "object" && !Array.isArray(item))
|
||||
&& typeof entry.hasNext === "boolean" ? entry : null;
|
||||
@@ -46,6 +54,13 @@ function createActivityCache({ authSessionState, preferencesState, environment }
|
||||
const invalidated = await storage("get", invalidatedKey);
|
||||
if (scope.fetchedAt <= Number(invalidated?.[invalidatedKey] || 0)) return;
|
||||
await storage("set", { [scope.key]: { fetchedAt: scope.fetchedAt, items, hasNext: Boolean(hasNext) } });
|
||||
if (kind === "sr") {
|
||||
const entries = await storage("get", null);
|
||||
const prefix = `${scope.legacyKey.slice(0, -1)},`;
|
||||
const expired = Object.keys(entries || {}).filter(key => key.startsWith(prefix)
|
||||
&& Number(entries[key]?.fetchedAt) <= Math.max(Date.now() - scope.ttl, Number(invalidated?.[invalidatedKey] || 0)));
|
||||
if (expired.length) await storage("remove", expired);
|
||||
}
|
||||
} catch { /* Storage failure must not discard successfully fetched data. */ }
|
||||
}
|
||||
return { invalidate, read, write };
|
||||
|
||||
83
src/features/opportunities/activity-filters.js
Normal file
83
src/features/opportunities/activity-filters.js
Normal file
@@ -0,0 +1,83 @@
|
||||
function createActivityFilters({ allowsCrmActivities, getSrSummary, getActionSummary, renderTable, environment }) {
|
||||
const selections = { sr: new Set(["matched", "missing"]), actions: new Set(["matched", "missing"]) };
|
||||
let container = null;
|
||||
let form = null;
|
||||
const groups = [];
|
||||
const buttons = [];
|
||||
|
||||
function createControls(parentForm, statusFilter, stageFilter) {
|
||||
form = parentForm;
|
||||
groups.length = 0;
|
||||
buttons.length = 0;
|
||||
container = environment.document.createElement("div");
|
||||
container.className = "opportunities-extension-badge-filters";
|
||||
for (const [kind, title, options] of [
|
||||
["sr", "Service Request", [["matched", "\u2705 SR"], ["under4", "\u2705 SR < 4h"], ["missing", "\u26d4 SR"]]],
|
||||
["actions", "Actions", [["matched", "\u2705 Action"], ["missing", "\u26d4 Action"]]]
|
||||
]) {
|
||||
const section = environment.document.createElement("section");
|
||||
section.className = "opportunities-extension-activity-filter";
|
||||
const label = environment.document.createElement("span");
|
||||
label.textContent = title;
|
||||
const controls = environment.document.createElement("div");
|
||||
controls.className = "opportunities-extension-activity-filter-controls";
|
||||
controls.setAttribute("role", "group");
|
||||
controls.setAttribute("aria-label", `Filter by ${title}`);
|
||||
for (const [value, text] of options) {
|
||||
const button = environment.document.createElement("button");
|
||||
button.type = "button";
|
||||
button.className = "opportunities-extension-activity-filter-button";
|
||||
button.setAttribute("data-value", value);
|
||||
button.textContent = text;
|
||||
if (value === "under4") button.title = "At least one SR with reported hours below 4; replaces other SR filters.";
|
||||
button.addEventListener("click", () => toggle(kind, value));
|
||||
buttons.push({ button, kind, value });
|
||||
controls.append(button);
|
||||
}
|
||||
section.append(label, controls);
|
||||
groups.push(section);
|
||||
container.append(section);
|
||||
}
|
||||
container.append(statusFilter, stageFilter);
|
||||
render();
|
||||
return container;
|
||||
}
|
||||
|
||||
function render() {
|
||||
if (!container) return;
|
||||
const allowed = allowsCrmActivities();
|
||||
form.setAttribute("data-activity-filters", String(allowed));
|
||||
for (const group of groups) group.hidden = !allowed;
|
||||
for (const { button, kind, value } of buttons) {
|
||||
button.setAttribute("aria-pressed", String(selections[kind].has(value)));
|
||||
}
|
||||
}
|
||||
|
||||
function toggle(kind, value) {
|
||||
const selected = selections[kind];
|
||||
if (!selected || !allowsCrmActivities()) return;
|
||||
if (selected.has(value)) selected.delete(value);
|
||||
else {
|
||||
if (value === "under4") selected.clear();
|
||||
else selected.delete("under4");
|
||||
selected.add(value);
|
||||
}
|
||||
render();
|
||||
renderTable();
|
||||
}
|
||||
|
||||
function matchesSelection(selected, summary) {
|
||||
// Unrestricted filters retain rows whose activities are not known yet.
|
||||
if (selected.has("matched") && selected.has("missing")) return true;
|
||||
if (!summary) return false;
|
||||
if (selected.has("under4")) return summary.state === "matched" && summary.hasUnderFourHours;
|
||||
return selected.has(summary.state);
|
||||
}
|
||||
|
||||
function matches(item) {
|
||||
if (!allowsCrmActivities()) return true;
|
||||
return matchesSelection(selections.sr, getSrSummary(item.OptyNumber))
|
||||
&& matchesSelection(selections.actions, getActionSummary(item.OptyNumber));
|
||||
}
|
||||
return { createControls, render, toggle, matches };
|
||||
}
|
||||
@@ -69,7 +69,6 @@ function createOpportunitiesController({
|
||||
failed: 0,
|
||||
cacheHits: 0
|
||||
};
|
||||
productsContentState.productsLastCachedAt = 0;
|
||||
consumptionPlansState.accountPlansRequestState = new Map();
|
||||
consumptionTrendState.consumptionTrendCache = new Map();
|
||||
consumptionTrendState.consumptionTrendWindowOffsets = new Map();
|
||||
|
||||
@@ -40,17 +40,27 @@ function createOpportunityCrmServiceRequests({ requestCrmServiceRequest, isCurre
|
||||
if (isActive()) { status = "error"; reportProgress("error"); }
|
||||
}
|
||||
}
|
||||
function getSummary(optyNumber) {
|
||||
if (!allowsCrmActivities() || !optyNumber) return null;
|
||||
const items = byOpportunity.get(String(optyNumber)) || [];
|
||||
const hours = items.map(item => item.ReportedHours_c).filter(value =>
|
||||
(typeof value === "number" || (typeof value === "string" && value.trim() !== "")) && Number.isFinite(Number(value))
|
||||
).map(Number);
|
||||
return {
|
||||
state: status === "ready" ? items.length ? "matched" : "missing" : status,
|
||||
reportedHours: hours.length === items.length ? hours.reduce((total, value) => total + value, 0) : null,
|
||||
hasUnderFourHours: status === "ready" && hours.some(value => value < 4)
|
||||
};
|
||||
}
|
||||
function createBadge(optyNumber) {
|
||||
if (!allowsCrmActivities() || !optyNumber || status === "idle" || status === "loading") return null;
|
||||
const items = byOpportunity.get(String(optyNumber)) || [];
|
||||
const badge = environment.document.createElement(items.length && status === "ready" ? "button" : "span");
|
||||
badge.className = "opportunities-extension-crm-sr-badge";
|
||||
badge.setAttribute("data-state", status === "error" ? "error" : items.length ? "matched" : "missing");
|
||||
const hours = items.map(item => item.ReportedHours_c).filter(value =>
|
||||
(typeof value === "number" || (typeof value === "string" && value.trim() !== "")) && Number.isFinite(Number(value))
|
||||
).map(Number);
|
||||
const reportedHours = hours.length === items.length
|
||||
? new Intl.NumberFormat("en-US", { useGrouping: false, maximumFractionDigits: 10 }).format(hours.reduce((total, value) => total + value, 0))
|
||||
const summary = getSummary(optyNumber);
|
||||
const reportedHours = summary.reportedHours !== null
|
||||
? new Intl.NumberFormat("en-US", { useGrouping: false, maximumFractionDigits: 10 }).format(summary.reportedHours)
|
||||
: "N/D";
|
||||
badge.textContent = status === "error" ? "SR ?" : items.length ? `\u2705 SR (${reportedHours}${reportedHours === "N/D" ? "" : "h"})` : "\u26d4 SR";
|
||||
const display = value => value === null || value === undefined || value === "" ? "N/D" : String(value);
|
||||
@@ -71,5 +81,5 @@ function createOpportunityCrmServiceRequests({ requestCrmServiceRequest, isCurre
|
||||
badge.tabIndex = 0;
|
||||
return badge;
|
||||
}
|
||||
return { reset, load, createBadge };
|
||||
return { reset, load, createBadge, getSummary };
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ function createOpportunitiesFilters({
|
||||
renderOpportunitiesTable,
|
||||
saveFilterPreferences,
|
||||
stringifySearchValue,
|
||||
matchesActivities,
|
||||
environment
|
||||
}) {
|
||||
const { document } = environment;
|
||||
@@ -340,7 +341,8 @@ function createOpportunitiesFilters({
|
||||
&& matchesSelectedStatus(item.StatusCode)
|
||||
&& opportunitiesFilterState.selectedCustomerKeys.has(getCustomerKey(item))
|
||||
&& opportunitiesFilterState.selectedOwnerKeys.has(getOwnerKey(item))
|
||||
&& matchesTableSearch(item, normalizedSearch);
|
||||
&& matchesTableSearch(item, normalizedSearch)
|
||||
&& matchesActivities(item);
|
||||
});
|
||||
const column = OPPORTUNITIES_COLUMNS.find((candidate) => candidate.key === opportunitiesTableState.opportunitiesTableState.sortKey);
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ function createProductActivities({ productsContentState, opportunitiesQueryState
|
||||
const products = [...productsContentState.opptyProductsByNumber.values()].filter(result => !result.error);
|
||||
if (!products.length) return;
|
||||
resetCrm();
|
||||
const pending = [];
|
||||
const seen = new Set();
|
||||
for (const result of products) {
|
||||
const items = result.items.filter(item => {
|
||||
@@ -20,11 +21,15 @@ function createProductActivities({ productsContentState, opportunitiesQueryState
|
||||
seen.add(id);
|
||||
return true;
|
||||
});
|
||||
loadActions({ ...result, items }, true, { cachedAt: result.cachedAt, cacheOnly: result.fromCache === true });
|
||||
pending.push(loadActions({ ...result, items }, true, { cachedAt: result.cachedAt, cacheOnly: result.fromCache === true }));
|
||||
}
|
||||
const cachedAt = Math.max(...products.map(result => Number(result.cachedAt) || 0));
|
||||
Promise.resolve(loadCrm(current.token, current.version, { cachedAt, cacheOnly: products.every(result => result.fromCache) })).finally(() => {
|
||||
if (cycle === current && productsContentState.currentProductsRequestId === requestId) render();
|
||||
pending.push(loadCrm(current.token, current.version, { cachedAt, cacheOnly: products.every(result => result.fromCache) }));
|
||||
return Promise.allSettled(pending).then(() => {
|
||||
if (cycle !== current || productsContentState.currentProductsRequestId !== requestId
|
||||
|| opportunitiesQueryState.periodRequestVersion !== current.version) return;
|
||||
if (cachedAt > 0) productsContentState.productsLastCachedAt = cachedAt;
|
||||
render();
|
||||
}).catch(() => {});
|
||||
}
|
||||
return { begin, complete };
|
||||
|
||||
@@ -8,7 +8,6 @@ function createRefreshAll({ messageTypes, sendRuntimeMessage, activityCache, res
|
||||
productsContentState.currentProductsRequestId = "";
|
||||
productsContentState.opptyProductsByNumber = new Map();
|
||||
productsContentState.expandedProductOptyNumbers = new Set();
|
||||
productsContentState.productsLastCachedAt = 0;
|
||||
productsContentState.productsProgress = { state: "idle", completed: 0, total: 0, failed: 0, cacheHits: 0 };
|
||||
consumptionPlansState.accountPlansRequestState = new Map();
|
||||
consumptionTrendState.consumptionTrendCache = new Map();
|
||||
|
||||
@@ -31,14 +31,15 @@
|
||||
|
||||
.opportunities-extension-form {
|
||||
display: grid;
|
||||
grid-template-columns: max-content minmax(110px, 1fr) minmax(110px, 1fr) minmax(145px, 180px) max-content max-content;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr)) max-content max-content;
|
||||
align-items: end;
|
||||
gap: 12px;
|
||||
min-height: 0;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.opportunities-extension-form > * {
|
||||
.opportunities-extension-form > *,
|
||||
.opportunities-extension-badge-filters > section {
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
@@ -47,7 +48,8 @@
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.opportunities-extension-form > * > span:first-child {
|
||||
.opportunities-extension-form > * > span:first-child,
|
||||
.opportunities-extension-badge-filters > section > span:first-child {
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
}
|
||||
@@ -97,22 +99,12 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Native select intrinsic sizing includes its longest option, not just the selection. */
|
||||
@media (min-width: 1151px) {
|
||||
.opportunities-extension-form > .opportunities-extension-field,
|
||||
.opportunities-extension-form .opportunities-extension-select-wrap,
|
||||
.opportunities-extension-form .opportunities-extension-field select {
|
||||
width: max-content;
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1150px) {
|
||||
.opportunities-extension-form {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
}
|
||||
.opportunities-extension-form > .opportunities-extension-status-filter,
|
||||
.opportunities-extension-form > .opportunities-extension-stage-filter {
|
||||
.opportunities-extension-badge-filters > .opportunities-extension-status-filter,
|
||||
.opportunities-extension-badge-filters > .opportunities-extension-stage-filter {
|
||||
grid-column: span 2;
|
||||
}
|
||||
}
|
||||
@@ -125,16 +117,73 @@
|
||||
.opportunities-extension-form .opportunities-extension-status-filter-controls {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.opportunities-extension-form > .opportunities-extension-status-filter,
|
||||
.opportunities-extension-form > .opportunities-extension-stage-filter {
|
||||
.opportunities-extension-badge-filters > .opportunities-extension-status-filter,
|
||||
.opportunities-extension-badge-filters > .opportunities-extension-stage-filter {
|
||||
grid-template-rows: auto auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 420px) {
|
||||
.opportunities-extension-form { grid-template-columns: minmax(0, 1fr); }
|
||||
.opportunities-extension-form > .opportunities-extension-status-filter,
|
||||
.opportunities-extension-form > .opportunities-extension-stage-filter { grid-column: auto; }
|
||||
.opportunities-extension-badge-filters > .opportunities-extension-status-filter,
|
||||
.opportunities-extension-badge-filters > .opportunities-extension-stage-filter { grid-column: auto; }
|
||||
}
|
||||
|
||||
.opportunities-extension-badge-filters { display: contents; }
|
||||
.opportunities-extension-activity-filter { display: grid; }
|
||||
.opportunities-extension-activity-filter[hidden] { display: none; }
|
||||
.opportunities-extension-activity-filter-controls { display: flex; align-items: center; flex-wrap: nowrap; gap: 4px; }
|
||||
/* Flexible fields share the space left by the four compact badge groups. */
|
||||
.opportunities-extension-form[data-activity-filters="true"] {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr)) repeat(4, max-content);
|
||||
column-gap: 8px;
|
||||
}
|
||||
.opportunities-extension-activity-filter-button {
|
||||
border: 1px solid currentColor;
|
||||
border-radius: 18px;
|
||||
min-height: 28px;
|
||||
padding: 4px 7px;
|
||||
font: inherit;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
line-height: 18px;
|
||||
white-space: nowrap;
|
||||
color: #185b30;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
.opportunities-extension-activity-filter-button[data-value="missing"] { color: #9f201b; }
|
||||
.opportunities-extension-activity-filter-button[data-value="under4"] { color: #005f7d; }
|
||||
.opportunities-extension-activity-filter-button[aria-pressed="true"] { color: #fff; background: #25653a; border-color: #25653a; }
|
||||
.opportunities-extension-activity-filter-button[aria-pressed="true"][data-value="missing"] { background: #a12d27; border-color: #a12d27; }
|
||||
.opportunities-extension-activity-filter-button[aria-pressed="true"][data-value="under4"] { background: #006b86; border-color: #006b86; }
|
||||
.opportunities-extension-activity-filter-button:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }
|
||||
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-activity-filter { color: #eeeae6; }
|
||||
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-activity-filter-button { color: #b8e4c6; }
|
||||
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-activity-filter-button[data-value="missing"] { color: #ffd9d5; }
|
||||
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-activity-filter-button[data-value="under4"] { color: #b5e9fc; }
|
||||
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-activity-filter-button[aria-pressed="true"] { border-color: currentColor; }
|
||||
@media (max-width: 1350px) {
|
||||
.opportunities-extension-form[data-activity-filters="true"] { grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
||||
.opportunities-extension-form[data-activity-filters="true"] .opportunities-extension-badge-filters {
|
||||
display: flex;
|
||||
grid-column: 1 / -1;
|
||||
flex-wrap: wrap;
|
||||
align-items: start;
|
||||
gap: 12px 20px;
|
||||
}
|
||||
.opportunities-extension-form[data-activity-filters="true"] .opportunities-extension-badge-filters > section {
|
||||
width: auto;
|
||||
max-width: 100%;
|
||||
grid-template-rows: auto auto;
|
||||
}
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
.opportunities-extension-form[data-activity-filters="true"] { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
.opportunities-extension-activity-filter-controls { flex-wrap: wrap; }
|
||||
}
|
||||
@media (max-width: 420px) {
|
||||
.opportunities-extension-form[data-activity-filters="true"] { grid-template-columns: minmax(0, 1fr); }
|
||||
}
|
||||
|
||||
/* Keep layout rules on the outer table, never on expanded product tables. */
|
||||
|
||||
@@ -23,6 +23,7 @@ function createOpportunitiesTable({
|
||||
productsContentState,
|
||||
renderCustomerFilter,
|
||||
renderOwnerFilter,
|
||||
renderActivityFilters,
|
||||
renderStageDashboard,
|
||||
resetCustomerFilter,
|
||||
resetOwnerFilter,
|
||||
@@ -47,6 +48,7 @@ function createOpportunitiesTable({
|
||||
}
|
||||
|
||||
function renderOpportunitiesTable() {
|
||||
renderActivityFilters();
|
||||
const table = document.getElementById(OPPORTUNITIES_TABLE_ID);
|
||||
|
||||
if (!table) {
|
||||
|
||||
@@ -11,6 +11,7 @@ function createOpportunityWorkloadActions({ authSessionState, allowsCrmActivitie
|
||||
}
|
||||
function reset(enabled = true) {
|
||||
session.enabled = false;
|
||||
for (const job of session.queue) job.record.resolveCompletion();
|
||||
session.queue.length = 0;
|
||||
session = newSession();
|
||||
session.enabled = enabled;
|
||||
@@ -21,17 +22,24 @@ function createOpportunityWorkloadActions({ authSessionState, allowsCrmActivitie
|
||||
}
|
||||
function loadProducts(products, refresh = false, cacheOptions = null) {
|
||||
if (!session.enabled || !allowsCrmActivities()) return;
|
||||
const pending = [];
|
||||
if ([...session.progress].every(record => record.status !== "loading")) session.progress.clear();
|
||||
for (const revnId of idsFor(products)) {
|
||||
if (!refresh && session.records.has(revnId)) continue;
|
||||
if (!refresh && session.records.has(revnId)) {
|
||||
pending.push(session.records.get(revnId).completion);
|
||||
continue;
|
||||
}
|
||||
session.progress.delete(session.records.get(revnId));
|
||||
const record = { status: "loading", actions: [], hasNext: false };
|
||||
record.completion = new Promise(resolve => { record.resolveCompletion = resolve; });
|
||||
pending.push(record.completion);
|
||||
session.records.set(revnId, record);
|
||||
if (!cacheOptions) session.progress.add(record);
|
||||
session.queue.push({ revnId, record, cacheOptions });
|
||||
}
|
||||
updateProgress(session);
|
||||
pump(session);
|
||||
return Promise.all(pending);
|
||||
}
|
||||
function reopen() {
|
||||
reset();
|
||||
@@ -73,10 +81,11 @@ function createOpportunityWorkloadActions({ authSessionState, allowsCrmActivitie
|
||||
record.status = "error";
|
||||
record.error = error.message || "Unable to load workload actions.";
|
||||
} finally {
|
||||
record.resolveCompletion();
|
||||
if (current.enabled && current === session) { updateProgress(current); queueProductsTableRender(); }
|
||||
}
|
||||
}
|
||||
function createBadge(optyNumber) {
|
||||
function getSummary(optyNumber) {
|
||||
if (!session.enabled || !allowsCrmActivities() || !optyNumber) return null;
|
||||
const products = productsContentState.opptyProductsByNumber.get(String(optyNumber));
|
||||
if (!products) return null;
|
||||
@@ -89,6 +98,12 @@ function createOpportunityWorkloadActions({ authSessionState, allowsCrmActivitie
|
||||
const principal = authSessionState.principal;
|
||||
const owned = Boolean(principal) && actions.some(action => typeof action.owner === "string" && action.owner.trim() === principal);
|
||||
const state = owned ? "matched" : pending ? "loading" : uncertain || !principal ? "error" : "missing";
|
||||
return { state, actions, records, principal, pending, uncertain };
|
||||
}
|
||||
function createBadge(optyNumber) {
|
||||
const summary = getSummary(optyNumber);
|
||||
if (!summary) return null;
|
||||
const { state, actions, records, principal, pending, uncertain } = summary;
|
||||
const badge = environment.document.createElement("span");
|
||||
badge.className = "opportunities-extension-crm-sr-badge opportunities-extension-wl-actions-badge";
|
||||
badge.setAttribute("data-state", state);
|
||||
@@ -107,5 +122,5 @@ function createOpportunityWorkloadActions({ authSessionState, allowsCrmActivitie
|
||||
badge.setAttribute("aria-label", badge.title);
|
||||
return badge;
|
||||
}
|
||||
return { reset, reopen, loadProducts, createBadge };
|
||||
return { reset, reopen, loadProducts, createBadge, getSummary };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user