function createConsumptionTrendController({ messageTypes, CONSUMPTION_TREND_PERIODS, DEFAULT_CONSUMPTION_TREND_PERIOD, appendBackgroundRequestLog, consumptionTrendState, createPlanDetailSection, renderConsumptionTrendChart, requestConsumerTokenServiceOnce, sendRuntimeMessage, environment }) { const { document } = environment; function createConsumptionTrendSection(plan) { const section = document.createElement("section"); section.className = "opportunities-extension-consumption-trend"; section.dataset.subPlanNum = String(plan.subPlanNum || ""); section.dataset.period = DEFAULT_CONSUMPTION_TREND_PERIOD; const header = document.createElement("header"); header.className = "opportunities-extension-consumption-trend-header"; const periodControls = document.createElement("div"); periodControls.className = "opportunities-extension-consumption-periods"; periodControls.setAttribute("role", "group"); periodControls.setAttribute("aria-label", "Consumption period"); CONSUMPTION_TREND_PERIODS.forEach((periodOption) => { const button = document.createElement("button"); button.type = "button"; button.dataset.period = periodOption.value; button.setAttribute("aria-pressed", String(periodOption.value === DEFAULT_CONSUMPTION_TREND_PERIOD)); button.textContent = periodOption.label; button.addEventListener("click", () => { section.dataset.period = periodOption.value; consumptionTrendState.consumptionTrendWindowOffsets.set( createConsumptionTrendKey(section.dataset.subPlanNum, periodOption.value), 0 ); loadConsumptionTrendForPanel(section.closest("[role='tabpanel']"), plan, periodOption.value); }); periodControls.append(button); }); const chart = document.createElement("div"); chart.className = "opportunities-extension-consumption-chart"; chart.setAttribute("aria-live", "polite"); header.append(createPlanDetailSection(plan), periodControls); section.append(header, chart); return section; } function loadConsumptionTrendForPanel(panel, plan, period) { if (!panel) { return; } const section = panel.querySelector(".opportunities-extension-consumption-trend"); if (!section) { return; } const subPlanNum = String(plan.subPlanNum || "").trim(); const selectedPeriod = CONSUMPTION_TREND_PERIODS.some((item) => item.value === period) ? period : DEFAULT_CONSUMPTION_TREND_PERIOD; const key = createConsumptionTrendKey(subPlanNum, selectedPeriod); const chart = section.querySelector(".opportunities-extension-consumption-chart"); section.dataset.period = selectedPeriod; chart.dataset.requestKey = key; updateConsumptionPeriodButtons(section, selectedPeriod); if (!subPlanNum) { renderConsumptionTrendError(chart, "Sub plan number is unavailable."); return; } const cached = consumptionTrendState.consumptionTrendCache.get(key); renderConsumptionTrendSkeleton(chart); if (cached && cached.status === "loading") { cached.promise .then((items) => renderConsumptionTrendIfCurrent(section, chart, items, selectedPeriod, subPlanNum, key)) .catch((error) => renderConsumptionTrendErrorIfCurrent(chart, error.message, key)); return; } const request = requestConsumerTokenServiceOnce() .then((authorizations) => sendRuntimeMessage({ type: messageTypes.requestConsumptionTrend, subPlanNum, period: selectedPeriod, authorization: authorizations && authorizations.accountPlans })) .then((response) => { appendBackgroundRequestLog(response && response.debugRequests); if (!response || !response.ok) { throw new Error(response && response.error ? response.error : "Unable to load consumption trend."); } return Array.isArray(response.items) ? response.items : []; }) .finally(() => { if (consumptionTrendState.consumptionTrendCache.get(key)?.promise === request) { consumptionTrendState.consumptionTrendCache.delete(key); } }); consumptionTrendState.consumptionTrendCache.set(key, { status: "loading", promise: request }); request .then((items) => renderConsumptionTrendIfCurrent(section, chart, items, selectedPeriod, subPlanNum, key)) .catch((error) => renderConsumptionTrendErrorIfCurrent(chart, error.message, key)); } function createConsumptionTrendKey(subPlanNum, period) { return `${subPlanNum}:${period}`; } function updateConsumptionPeriodButtons(section, period) { section.querySelectorAll(".opportunities-extension-consumption-periods button").forEach((button) => { button.setAttribute("aria-pressed", String(button.dataset.period === period)); }); } function renderConsumptionTrendIfCurrent(section, chart, items, period, subPlanNum, key) { if (chart.dataset.requestKey === key) { renderConsumptionTrendChart(section, chart, items, period, subPlanNum); } } function renderConsumptionTrendErrorIfCurrent(chart, errorMessage, key) { if (chart.dataset.requestKey === key) { renderConsumptionTrendError(chart, errorMessage || "Unable to load consumption trend."); } } function renderConsumptionTrendSkeleton(chart) { chart.textContent = ""; chart.setAttribute("aria-busy", "true"); const skeleton = document.createElement("div"); skeleton.className = "opportunities-extension-consumption-chart-skeleton"; const announcement = document.createElement("span"); announcement.className = "opportunities-extension-visually-hidden"; announcement.textContent = "Loading consumption trend"; const plot = document.createElement("div"); plot.className = "opportunities-extension-consumption-chart-skeleton-plot"; for (let index = 0; index < 13; index += 1) { const bar = document.createElement("span"); bar.style.height = `${28 + ((index * 17) % 58)}%`; plot.append(bar); } skeleton.append(announcement, plot); chart.append(skeleton); } function renderConsumptionTrendError(chart, errorMessage) { chart.textContent = ""; chart.setAttribute("aria-busy", "false"); const error = document.createElement("p"); error.className = "opportunities-extension-consumption-chart-error"; error.setAttribute("role", "alert"); error.textContent = errorMessage; chart.append(error); } return { createConsumptionTrendSection, loadConsumptionTrendForPanel, createConsumptionTrendKey, updateConsumptionPeriodButtons, renderConsumptionTrendIfCurrent, renderConsumptionTrendErrorIfCurrent, renderConsumptionTrendSkeleton, renderConsumptionTrendError }; }