Implemented filters for Service Requests and Actions

This commit is contained in:
2026-09-25 16:37:29 -03:00
parent cc567c8ff1
commit de4da0eec6
26 changed files with 1001 additions and 99 deletions

View File

@@ -55,6 +55,7 @@ function createContentApplication(overrides = {}) {
environment
});
const contentModal = createContentModal({
createActivityFilterControls: (...args) => activityFilters.createControls(...args),
closeCrmSrPopover: () => crmSrPopover.close(),
ensureCurrentUser: () => currentUser.ensure(),
ACCOUNT_PLANS_MODAL_ID: contracts.ACCOUNT_PLANS_MODAL_ID,
@@ -229,6 +230,7 @@ function createContentApplication(overrides = {}) {
isCurrentPeriodRequest: (...args) => opportunitiesController.isCurrentPeriodRequest(...args), environment
});
const opportunitiesTable = createOpportunitiesTable({
renderActivityFilters: () => activityFilters.render(),
closeCrmSrPopover: (...args) => crmSrPopover.close(...args),
createWorkloadActionsBadge: (...args) => opportunityWorkloadActions.createBadge(...args),
ACCOUNT_DETAIL_URL: contracts.ACCOUNT_DETAIL_URL,
@@ -266,6 +268,7 @@ function createContentApplication(overrides = {}) {
environment
});
const opportunitiesFilters = createOpportunitiesFilters({
matchesActivities: item => activityFilters.matches(item),
CUSTOMER_FILTER_BUTTON_ID: contracts.CUSTOMER_FILTER_BUTTON_ID,
CUSTOMER_FILTER_LIST_ID: contracts.CUSTOMER_FILTER_LIST_ID,
CUSTOMER_FILTER_PANEL_ID: contracts.CUSTOMER_FILTER_PANEL_ID,
@@ -352,12 +355,22 @@ function createContentApplication(overrides = {}) {
}
});
const activityProgress = createActivityProgress({ MODAL_ID: contracts.MODAL_ID, environment });
const activityFilters = createActivityFilters({
allowsCrmActivities: () => currentUser.allowsCrmActivities(),
getSrSummary: number => opportunityCrmServiceRequests.getSummary(number),
getActionSummary: number => opportunityWorkloadActions.getSummary(number),
renderTable: () => opportunitiesTable.renderOpportunitiesTable(),
environment
});
const productActivities = createProductActivities({
productsContentState, opportunitiesQueryState,
resetCrm: () => opportunityCrmServiceRequests.reset(),
loadCrm: (...args) => opportunityCrmServiceRequests.load(...args),
loadActions: (...args) => opportunityWorkloadActions.loadProducts(...args),
render: () => productsContentJobs.queueProductsTableRender()
render: () => {
productsContentJobs.renderProductsProgress();
productsContentJobs.queueProductsTableRender();
}
});
const productsContentJobs = createProductsContentJobs({
beginProductActivities: (...args) => productActivities.begin(...args),
@@ -548,6 +561,7 @@ function createContentApplication(overrides = {}) {
startContentApplication({ environment, page: contentFusionPage, styles: contentStyles, products: productsContentJobs });
}
return {
activityFilters,
start, contracts,
opportunityWorkloadActions,
fusionCrmServiceRequests, opportunityCrmServiceRequests,

View File

@@ -33,6 +33,7 @@ function createContentModal({
closePreferencesModal,
createSettingsIcon,
createRefreshAllButton,
createActivityFilterControls,
debugState,
ensureExtensionStyles,
getCustomerOptions,
@@ -380,7 +381,8 @@ function createContentModal({
statusFilter.append(statusFilterLabel, statusFilters);
form.addEventListener("submit", (event) => event.preventDefault());
form.append(field, customerFilter, ownerFilter, tableSearchField, statusFilter, stageFilter);
const badgeFilters = createActivityFilterControls(form, statusFilter, stageFilter);
form.append(field, customerFilter, ownerFilter, tableSearchField, badgeFilters);
body.append(form);
const stageDashboard = document.createElement("section");

View File

@@ -222,7 +222,14 @@ function createDebugContentView({
method.className = "opportunities-extension-debug-method";
method.textContent = entry.method || "GET";
method.setAttribute("data-method", method.textContent.toUpperCase());
summaryMeta.append(startedTime, method, httpStatus, status);
const duration = document.createElement("span");
duration.className = "opportunities-extension-debug-duration";
const hasDuration = typeof entry.durationMs === "number" && Number.isFinite(entry.durationMs) && entry.durationMs >= 0;
duration.textContent = hasDuration
? entry.durationMs < 1000 ? `${Math.round(entry.durationMs)} ms` : `${(entry.durationMs / 1000).toFixed(2)} s`
: entry.status === "pending" ? "In progress" : "N/D";
duration.title = hasDuration ? `Request duration: ${entry.durationMs} ms` : "Request duration not yet available";
summaryMeta.append(startedTime, method, httpStatus, status, duration);
heading.append(name, summaryMeta);

View File

@@ -214,3 +214,10 @@
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-debug-list[role="tabpanel"]:focus-visible {
outline-color: #8cc8d3;
}
.opportunities-extension-debug-duration {
color: #55514d;
font-size: 11px;
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-debug-duration { color: #d6d2ce; }

View File

@@ -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 };

View 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 };
}

View File

@@ -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();

View File

@@ -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 };
}

View File

@@ -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);

View File

@@ -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 };

View File

@@ -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();

View File

@@ -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. */

View File

@@ -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) {

View File

@@ -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 };
}

View File

@@ -123,10 +123,6 @@ function createProductsContentJobs({
});
}
if (Number(message.cachedAt) > productsContentState.productsLastCachedAt) {
productsContentState.productsLastCachedAt = Number(message.cachedAt);
}
productsContentState.productsProgress = {
state: message.state || productsContentState.productsProgress.state,
completed: Number(message.completed) || 0,