function createConsumptionPlanDetailView({ createCloseIcon, environment }) { const { document, window, setTimeout, clearTimeout } = environment; let active = null; let sequence = 0; const fields = [ ["pricingModel", "Pricing model"], ["lineStatus", "Line status"], ["startDate", "Start date"], ["endDate", "End date"], ["committedCredits", "Committed credits", true], ["consumed", "Consumed", true], ["consumptionL30Days", "Consumption last 30 days", true], ["monthsRemaining", "Months remaining"], ["projectedConsumption", "Projected consumption", true], ["projectedDelta", "Projected delta", true], ["orderId", "Order number"] ]; function node(tag, className, text) { const result = document.createElement(tag); if (className) result.className = className; if (text !== undefined) result.textContent = text; return result; } function statusBadge(record, tag = "span") { const text = String(record.projectedStatus || "Unknown"); const badge = node(tag, "opportunities-extension-projection-badge", text); badge.dataset.status = text.trim().toLowerCase(); return badge; } function orderLink(record, text) { try { const url = new URL(record.orderTrackerURL); if (url.origin !== "https://accountdashboard.oracle.com" || url.username || url.password) return null; const link = node("a", "", text); link.href = url.href; link.target = "_blank"; link.rel = "noopener noreferrer"; return link; } catch { return null; } } function createLineDetails(record) { const section = node("section", "opportunities-extension-projection-line"); const heading = node("h3"); heading.append(statusBadge(record)); section.append(heading); const details = node("dl", "opportunities-extension-projection-fields"); fields.forEach(([key, label, numeric]) => { const raw = record[key]; let text = raw === null || raw === undefined || raw === "" ? "-" : String(raw); if (numeric && text !== "-" && Number.isFinite(Number(raw))) { text = new Intl.NumberFormat("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(Number(raw)); } const group = node("div"); const value = node("dd"); const link = key === "orderId" && text !== "-" ? orderLink(record, text) : null; if (link) value.append(link); else value.textContent = text; group.append(node("dt", "", label), value); details.append(group); }); section.append(details); if (Array.isArray(record.clines) && record.clines.length) { const children = node("div", "opportunities-extension-projection-children"); record.clines.forEach(child => children.append(createLineDetails(child))); section.append(children); } return section; } function closePlanDetailPopover(restoreFocus = false) { if (!active) return; const previous = active; active = null; clearTimeout(previous.timer); previous.popover.hidden = true; previous.button.setAttribute("aria-expanded", "false"); document.removeEventListener("pointerdown", previous.outside, true); window.removeEventListener("resize", previous.resize); if (restoreFocus && previous.button.isConnected) { previous.suppressFocus = true; previous.button.focus(); previous.suppressFocus = false; } } function positionPopover(entry) { const rect = entry.button.getBoundingClientRect(); const viewportWidth = window.innerWidth; const viewportHeight = window.innerHeight; const width = Math.min(560, viewportWidth - 24); const height = Math.min(520, viewportHeight - 24); entry.popover.style.width = `${width}px`; entry.popover.style.maxHeight = `${height}px`; const below = viewportHeight - rect.bottom - 12; const actualHeight = Math.min(entry.popover.getBoundingClientRect().height, height); entry.popover.style.left = `${Math.max(12, Math.min(rect.left, viewportWidth - width - 12))}px`; entry.popover.style.top = `${Math.max(12, below >= actualHeight ? rect.bottom + 4 : Math.min(rect.top - actualHeight - 4, viewportHeight - actualHeight - 12))}px`; } function createInteractiveBadge(record) { const wrapper = node("div", "opportunities-extension-projection-wrapper"); const button = statusBadge(record, "button"); button.type = "button"; button.setAttribute("aria-label", `Projected status: ${button.textContent}. View plan details`); button.setAttribute("aria-haspopup", "dialog"); button.setAttribute("aria-expanded", "false"); const popover = node("div", "opportunities-extension-projection-popover"); popover.id = `opportunities-extension-projection-${++sequence}`; popover.hidden = true; popover.setAttribute("role", "dialog"); popover.setAttribute("aria-label", "Consumption plan details"); popover.tabIndex = -1; button.setAttribute("aria-controls", popover.id); const dismiss = node("button", "opportunities-extension-projection-dismiss"); dismiss.type = "button"; dismiss.setAttribute("aria-label", "Close plan details"); dismiss.title = "Close plan details"; dismiss.append(createCloseIcon()); dismiss.addEventListener("click", () => closePlanDetailPopover(true)); popover.append(dismiss, createLineDetails(record)); const entry = { button, popover, wrapper, pinned: false, timer: null, suppressFocus: false, outside: event => { if (!wrapper.contains(event.target)) closePlanDetailPopover(); }, resize: () => positionPopover(entry) }; function open() { if (active !== entry) { closePlanDetailPopover(); entry.pinned = false; active = entry; document.addEventListener("pointerdown", entry.outside, true); window.addEventListener("resize", entry.resize); } clearTimeout(entry.timer); popover.hidden = false; button.setAttribute("aria-expanded", "true"); positionPopover(entry); } wrapper.addEventListener("pointerenter", open); wrapper.addEventListener("pointerleave", () => { if (entry.pinned || wrapper.contains(document.activeElement)) return; entry.timer = setTimeout(() => { if (active === entry) closePlanDetailPopover(); }, 180); }); wrapper.addEventListener("focusin", () => { if (!entry.suppressFocus) open(); }); wrapper.addEventListener("focusout", event => { if (!wrapper.contains(event.relatedTarget) && active === entry) closePlanDetailPopover(); }); button.addEventListener("click", () => { if (active === entry && entry.pinned) closePlanDetailPopover(); else { open(); entry.pinned = true; } }); wrapper.addEventListener("keydown", event => { if (event.key === "Escape" && active === entry) { event.preventDefault(); event.stopPropagation(); closePlanDetailPopover(true); } }); wrapper.append(button, popover); return wrapper; } function renderPlanDetailBadges(section, items) { if (active && section.contains(active.button)) closePlanDetailPopover(); section.textContent = ""; if (!items.length) { section.append(node("span", "opportunities-extension-projection-empty", "No projection data")); return; } items.forEach(record => section.append(createInteractiveBadge(record))); } return { renderPlanDetailBadges, createLineDetails, closePlanDetailPopover }; }