120 lines
3.7 KiB
JavaScript
120 lines
3.7 KiB
JavaScript
function createOpportunitiesDashboard({
|
|
STAGE_DASHBOARD_ID,
|
|
STAGE_OPTIONS,
|
|
getNestedValue,
|
|
opportunitiesDashboardState,
|
|
environment
|
|
}) {
|
|
const { document, requestAnimationFrame, performance } = environment;
|
|
function renderStageDashboard(items) {
|
|
const dashboard = document.getElementById(STAGE_DASHBOARD_ID);
|
|
|
|
if (!dashboard) {
|
|
return;
|
|
}
|
|
|
|
dashboard.textContent = "";
|
|
|
|
const totalCard = document.createElement("section");
|
|
const totalLabel = document.createElement("span");
|
|
const totalValue = document.createElement("strong");
|
|
|
|
totalCard.className = "opportunities-extension-stage-dashboard-card opportunities-extension-total-dashboard-card";
|
|
totalCard.setAttribute("data-stage", "TOTAL");
|
|
totalLabel.textContent = "Total Opportunities";
|
|
totalValue.className = "opportunities-extension-stage-dashboard-value";
|
|
totalValue.setAttribute("aria-label", "Total opportunities");
|
|
animateStageDashboardValue(
|
|
totalValue,
|
|
"TOTAL",
|
|
items.length,
|
|
(amount) => Math.round(amount).toLocaleString()
|
|
);
|
|
totalCard.append(totalLabel, totalValue);
|
|
dashboard.append(totalCard);
|
|
|
|
STAGE_OPTIONS.forEach((stage) => {
|
|
const stageItems = items.filter((item) => item.ForecastGroup_c === stage);
|
|
const amount = stageItems.reduce((total, item) => {
|
|
const value = Number(getNestedValue(item, ["PrimaryRevenue", "RevnAmount"]));
|
|
return Number.isFinite(value) ? total + value : total;
|
|
}, 0);
|
|
const card = document.createElement("section");
|
|
const label = document.createElement("span");
|
|
const value = document.createElement("strong");
|
|
|
|
card.className = "opportunities-extension-stage-dashboard-card";
|
|
card.setAttribute("data-stage", stage);
|
|
label.textContent = stage;
|
|
value.className = "opportunities-extension-stage-dashboard-value";
|
|
value.setAttribute("aria-label", `${stage} total amount`);
|
|
|
|
animateStageDashboardValue(
|
|
value,
|
|
stage,
|
|
amount,
|
|
createStageAmountFormatter(stageItems)
|
|
);
|
|
|
|
card.append(label, value);
|
|
dashboard.append(card);
|
|
});
|
|
}
|
|
|
|
function createStageAmountFormatter(items) {
|
|
const currencies = new Set(items
|
|
.map((item) => getNestedValue(item, ["PrimaryRevenue", "RevnAmountCurcyCode"]))
|
|
.filter(Boolean));
|
|
|
|
if (currencies.size === 1) {
|
|
const [currency] = currencies;
|
|
|
|
try {
|
|
const formatter = new Intl.NumberFormat(undefined, {
|
|
style: "currency",
|
|
currency,
|
|
maximumFractionDigits: 0
|
|
});
|
|
return (amount) => formatter.format(amount);
|
|
} catch (error) {
|
|
// Fall through to a neutral numeric total when the currency code is invalid.
|
|
}
|
|
}
|
|
|
|
return (amount) => amount.toLocaleString(undefined, {
|
|
maximumFractionDigits: 0
|
|
});
|
|
}
|
|
|
|
function animateStageDashboardValue(element, stage, target, format) {
|
|
const current = opportunitiesDashboardState.stageDashboardAmounts.get(stage) || 0;
|
|
opportunitiesDashboardState.stageDashboardAmounts.set(stage, target);
|
|
|
|
if (current === target) {
|
|
element.textContent = format(target);
|
|
return;
|
|
}
|
|
|
|
const startTime = performance.now();
|
|
const duration = 480;
|
|
|
|
function tick(now) {
|
|
const progress = Math.min((now - startTime) / duration, 1);
|
|
const easedProgress = 1 - Math.pow(1 - progress, 3);
|
|
const value = current + ((target - current) * easedProgress);
|
|
|
|
element.textContent = format(value);
|
|
|
|
if (progress < 1) {
|
|
requestAnimationFrame(tick);
|
|
} else {
|
|
element.textContent = format(target);
|
|
}
|
|
}
|
|
|
|
requestAnimationFrame(tick);
|
|
}
|
|
|
|
return { renderStageDashboard, createStageAmountFormatter, animateStageDashboardValue };
|
|
}
|