Files
opportunities-extension/dist/chromium/content.js

3709 lines
118 KiB
JavaScript

(function () {
"use strict";
const TILE_ID = "c_5c719a88b5624b1eaf81defe2c2ex4x5";
const TILE_LABEL_ID = `${TILE_ID}_0`;
const SALES_GROUP_SELECTOR = "#yourapps_groupNode_sales";
const ADD_ICON_SELECTOR = ".flat-grid-cell.flat-grid-cell-addicon";
const MODAL_ID = "opportunities-extension-modal";
const STYLE_ID = "opportunities-extension-redwood-styles";
const ESCAPE_LISTENER_KEY = "opportunitiesExtensionEscapeListener";
const DEBUG_PANEL_ID = "opportunities-extension-debug-panel";
const OPPORTUNITIES_TABLE_ID = "opportunities-extension-table";
const STAGE_FILTERS_ID = "opportunities-extension-stage-filters";
const STATUS_FILTERS_ID = "opportunities-extension-status-filters";
const CUSTOMER_FILTER_BUTTON_ID = "opportunities-extension-customer-filter-button";
const CUSTOMER_FILTER_PANEL_ID = "opportunities-extension-customer-filter-panel";
const CUSTOMER_FILTER_SEARCH_ID = "opportunities-extension-customer-filter-search";
const CUSTOMER_FILTER_SELECT_ALL_ID = "opportunities-extension-customer-filter-select-all";
const CUSTOMER_FILTER_LIST_ID = "opportunities-extension-customer-filter-list";
const OWNER_FILTER_BUTTON_ID = "opportunities-extension-owner-filter-button";
const OWNER_FILTER_PANEL_ID = "opportunities-extension-owner-filter-panel";
const OWNER_FILTER_SEARCH_ID = "opportunities-extension-owner-filter-search";
const OWNER_FILTER_SELECT_ALL_ID = "opportunities-extension-owner-filter-select-all";
const OWNER_FILTER_LIST_ID = "opportunities-extension-owner-filter-list";
const TABLE_SEARCH_ID = "opportunities-extension-table-search";
const STAGE_DASHBOARD_ID = "opportunities-extension-stage-dashboard";
const FILTER_PREFERENCES_STORAGE_KEY = "opportunities-extension-filter-preferences";
const THEME_STORAGE_KEY = "opportunities-extension-theme";
const OPPORTUNITY_DETAIL_URL = "https://eeho.fa.us2.oraclecloud.com/fscmUI/redwood/cx-sales/application/container/opportunities/opportunities-detail?puid=";
const ACCOUNT_DETAIL_URL = "https://eeho.fa.us2.oraclecloud.com/fscmUI/redwood/cx-sales/application/container/accounts/accounts-detail?id=";
const DEBUG_BODY_LIMIT = 12000;
const OPPORTUNITIES_PAGE_LIMIT = 15;
const TOKEN_RELAY_URL = "https://eeho.fa.us2.oraclecloud.com/fscmRestApi/tokenrelay";
const OPPORTUNITIES_QUERY_URL = "https://eeho.fa.us2.oraclecloud.com/crmRestApi/searchResources/11.13.18.05/custom-actions/queries";
const AUTH_STATUS = {
idle: "idle",
pending: "pending",
authenticated: "authenticated",
unauthenticated: "unauthenticated"
};
const STAGE_OPTIONS = ["SQL", "PIPELINE", "UPSIDE", "FORECAST", "WON"];
const STATUS_OPTIONS = ["OPEN", "CLOSED", "LOST", "WON"];
let tokenRelayRequest = null;
let accessToken = "";
let periodRequestVersion = 0;
let authStatus = AUTH_STATUS.idle;
let requestLog = [];
let selectedStages = new Set(STAGE_OPTIONS);
let selectedStatuses = new Set(STATUS_OPTIONS);
let selectedCustomerKeys = new Set();
let customerSearch = "";
let selectedOwnerKeys = new Set();
let ownerSearch = "";
let tableSearch = "";
let hasSavedCustomerFilter = false;
let hasSavedOwnerFilter = false;
let customerFilterInitialized = false;
let ownerFilterInitialized = false;
let stageDashboardAmounts = new Map([["TOTAL", 0], ...STAGE_OPTIONS.map((stage) => [stage, 0])]);
let opportunitiesTableState = {
items: [],
status: "idle",
message: "",
sortKey: "",
sortDirection: "ascending"
};
const PERIOD_OPTIONS = [
"Current Quarter",
"Next Quarter",
"Previous Quarter",
"Current Fiscal Year",
"4 Rolling Quarters (CQ + 3)",
"RENEWALS (Current + Past Due)"
];
const OPPORTUNITIES_COLUMNS = [
{ key: "name", label: "Name", value: (item) => item.Name, display: (item) => item.Name },
{ key: "optyNumber", label: "Opty Number", value: (item) => item.OptyNumber, display: (item) => item.OptyNumber },
{ key: "customer", label: "Customer", value: (item) => getNestedValue(item, ["CustomerAccount", "PartyUniqueName"]), display: (item) => getNestedValue(item, ["CustomerAccount", "PartyUniqueName"]) },
{ key: "owner", label: "Owner", value: getOwnerName, display: getOwnerName },
{ key: "revenue", label: "Amount", value: (item) => getNestedValue(item, ["PrimaryRevenue", "RevnAmount"]), display: formatRevenue },
{ key: "closeDate", label: "Close Date", value: (item) => item.EffectiveDate, display: (item) => formatOracleResponseDate(item.EffectiveDate) },
{ key: "stage", label: "Stage", value: (item) => item.ForecastGroup_c, display: (item) => item.ForecastGroup_c },
{ key: "winProbability", label: "Win Probability", value: (item) => getNestedValue(item, ["PrimaryRevenue", "WinProb"]), display: formatWinProbability },
{ key: "status", label: "Status", value: (item) => item.StatusCode, display: (item) => item.StatusCode },
{ key: "lastUpdateDate", label: "Last Update Date", value: (item) => item.LastUpdateDate, display: (item) => formatOracleResponseDate(item.LastUpdateDate, true) }
];
const ALLOWED_PATHS = [
"/hcmUI/faces/FuseWelcome",
"/fscmUI/faces/FuseWelcome"
];
if (!isAllowedPage()) {
return;
}
ensureExtensionStyles();
function createOpportunitiesTile() {
const wrapper = document.createElement("div");
wrapper.className = "flat-grid-cell";
const item = document.createElement("div");
item.id = TILE_ID;
item.className = "app-nav-item opportunities-extension-tile";
item.setAttribute("filmstrip", "Opportunities Extension");
item.setAttribute("page", "undefined");
item.setAttribute("index", "0");
item.setAttribute("type", "subcluster");
item.setAttribute("title", "Opportunities Extension");
item.setAttribute("group", "groupNode_tools");
item.setAttribute("destinationurl", "https://gxpap.oracle.com/ords/pgxpap/f?p=138");
item.setAttribute("targetframe", "_blank");
item.setAttribute("isdesturlexist", "true");
item.setAttribute("role", "presentation");
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("viewBox", "0 0 48 48");
svg.setAttribute("style", "fill:currentColor");
svg.setAttribute("class", "svg-nav suiicon svg-bkgd09");
svg.setAttribute("data-icon", "navi_reportsearch");
svg.setAttribute("role", "presentation");
svg.setAttribute("focusable", "false");
appendPath(svg, "svg-shortcut", "M28 42.5l-3 2.7v-1.7c-.4 0-1.4 0-2.5.6-1.3 1-1.5 1.6-1.5 1.6s-.4-1.2.8-2.7c1.2-1.6 2.6-1.7 3.2-1.6v-1.6l3 2.7z");
appendPath(svg, "svg-cluster", "M28.5 41.3c.6 0 1.2.5 1.2 1.2s-.6 1.2-1.2 1.2-1.2-.5-1.2-1.2.5-1.2 1.2-1.2zm-4 0c.6 0 1.2.5 1.2 1.2s-.6 1.2-1.2 1.2c-.7 0-1.2-.5-1.2-1.2s.5-1.2 1.2-1.2zm-4 0c.7 0 1.2.5 1.2 1.2s-.5 1.2-1.2 1.2-1.2-.5-1.2-1.2.5-1.2 1.2-1.2z");
appendPath(svg, "svg-icon15", "M16 31l-1.6-1-3.4 6.5s0 1 .5 1.4c.5.2 1.4-.4 1.4-.4l3-6.7z");
appendPath(svg, "svg-icon03", "M36 10H12c-.8 0-2 1.2-2 2v20c0 .4.2.8.5 1l2-3.6c-1-1.4-1.6-3-1.6-5 0-4.2 3.3-7.6 7.4-7.6H20V16h2v1.7c.7.4 1.3 1 1.8 1.5.6.5 1 1 1.3 1.8h4v7h-4c-.3.7-.8 1.4-1.4 2H35v2H20.3l-2 .2H18L17 34h19c.8 0 2-1.2 2-2V12c0-.8-1.2-2-2-2zm-23 4v-2h22v2H13zm22 14h-5V17h5v11z");
appendPath(svg, "svg-icon12", "M18.5 19c-3 0-5.5 2.5-5.5 5.5s2.5 5.5 5.5 5.5 5.5-2.5 5.5-5.5-2.5-5.5-5.5-5.5zm0 9c-2 0-3.5-1.6-3.5-3.5 0-2 1.6-3.5 3.5-3.5s3.5 1.6 3.5 3.5c0 2-1.6 3.5-3.5 3.5z");
appendPath(svg, "svg-outline", "M35 34.56H13a2.7 2.7 0 0 1-3-3V14a2.76 2.76 0 0 1 3-3h22a2.74 2.74 0 0 1 3 3v17.56a2.68 2.68 0 0 1-3 3zM16.98 22.32a4.72 4.72 0 1 0 4.73 4.73 4.72 4.72 0 0 0-4.73-4.73zM24 25h3.47v4.72H24V25zm5.78-4.66h4.69v9.38h-4.69v-9.38zM13.5 14.5h20.9v2.4H13.5v-2.4zm6.9 17.59l-.01-1.94zm-2.04-12.75l-5.35-.02zm2.13 12.67H35.7zm-.09-8.05L20.39 19z");
appendPath(svg, "svg-outline", "M16.98 31.72a4.68 4.68 0 1 0-4.75-4.67 4.74 4.74 0 0 0 4.75 4.67zm-1.44-.5l-3.6 6.83zM8 40");
const link = document.createElement("a");
link.id = TILE_LABEL_ID;
link.className = "app-nav-label flat-grid-nav-label";
link.href = "#";
link.textContent = "Opportunities Extension";
item.append(svg, link);
wrapper.append(item);
wrapper.addEventListener("click", handleTileClick);
return wrapper;
}
function appendPath(svg, className, d) {
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.setAttribute("class", className);
path.setAttribute("d", d);
svg.append(path);
}
function handleTileClick(event) {
event.preventDefault();
event.stopPropagation();
openOpportunitiesModal();
}
function openOpportunitiesModal() {
ensureExtensionStyles();
const existingModal = document.getElementById(MODAL_ID);
if (existingModal) {
existingModal.hidden = false;
existingModal.querySelector("select").focus();
document.documentElement.classList.add("opportunities-extension-scroll-lock");
ensureEscapeKeyHandler();
requestTokenRelayOnce().catch(() => {});
return;
}
const overlay = document.createElement("div");
requestLog = [];
tokenRelayRequest = null;
accessToken = "";
periodRequestVersion = 0;
authStatus = AUTH_STATUS.idle;
resetOpportunitiesTable();
overlay.id = MODAL_ID;
overlay.className = "opportunities-extension-modal";
overlay.setAttribute("data-theme", getSavedTheme());
overlay.setAttribute("role", "dialog");
overlay.setAttribute("aria-modal", "true");
overlay.setAttribute("aria-labelledby", "opportunities-extension-title");
overlay.setAttribute("aria-describedby", "opportunities-extension-subtitle");
const shell = document.createElement("section");
shell.className = "opportunities-extension-shell";
const header = document.createElement("header");
header.className = "opportunities-extension-header";
const title = document.createElement("h1");
title.id = "opportunities-extension-title";
title.textContent = "Opportunities Extension";
const subtitle = document.createElement("p");
subtitle.id = "opportunities-extension-subtitle";
subtitle.textContent = "Lista de oportunidades do HCM Opportunities List";
const authBadge = document.createElement("span");
authBadge.className = "opportunities-extension-auth-badge opportunities-extension-auth-badge-pending";
authBadge.setAttribute("data-auth-status", AUTH_STATUS.pending);
authBadge.textContent = "Authenticating";
const subtitleRow = document.createElement("div");
subtitleRow.className = "opportunities-extension-subtitle-row";
subtitleRow.append(subtitle, authBadge);
const titleBlock = document.createElement("div");
titleBlock.append(title, subtitleRow);
const closeButton = document.createElement("button");
closeButton.type = "button";
closeButton.className = "opportunities-extension-icon-button";
closeButton.setAttribute("aria-label", "Fechar");
closeButton.textContent = "X";
closeButton.addEventListener("click", closeOpportunitiesModal);
const themeButton = document.createElement("button");
themeButton.type = "button";
themeButton.className = "opportunities-extension-icon-button opportunities-extension-theme-toggle";
themeButton.addEventListener("click", () => {
const nextTheme = overlay.getAttribute("data-theme") === "dark" ? "light" : "dark";
applyTheme(overlay, themeButton, nextTheme);
});
applyTheme(overlay, themeButton, overlay.getAttribute("data-theme"));
const headerActions = document.createElement("div");
headerActions.className = "opportunities-extension-header-actions";
headerActions.append(themeButton, closeButton);
header.append(titleBlock, headerActions);
const body = document.createElement("main");
body.className = "opportunities-extension-body";
const form = document.createElement("form");
form.className = "opportunities-extension-form";
const field = document.createElement("label");
field.className = "opportunities-extension-field";
const labelText = document.createElement("span");
labelText.textContent = "Periodo";
const selectWrap = document.createElement("span");
selectWrap.className = "opportunities-extension-select-wrap";
const select = document.createElement("select");
select.name = "opportunityPeriod";
PERIOD_OPTIONS.forEach((optionLabel) => {
const option = document.createElement("option");
option.value = optionLabel;
option.textContent = optionLabel;
select.append(option);
});
select.addEventListener("change", () => {
resetCustomerAndOwnerFiltersForPeriod();
saveFilterPreferences(select.value);
requestOpportunitiesForPeriod(select.value);
});
selectWrap.append(select);
field.append(labelText, selectWrap);
restoreFilterPreferences(select);
const stageFilter = document.createElement("section");
stageFilter.className = "opportunities-extension-stage-filter";
const stageFilterLabel = document.createElement("span");
stageFilterLabel.className = "opportunities-extension-stage-filter-label";
stageFilterLabel.textContent = "Stage";
const stageFilters = document.createElement("div");
stageFilters.id = STAGE_FILTERS_ID;
stageFilters.className = "opportunities-extension-stage-filter-controls";
stageFilters.setAttribute("role", "group");
stageFilters.setAttribute("aria-label", "Filter by stage");
stageFilter.append(stageFilterLabel, stageFilters);
const customerFilter = document.createElement("section");
customerFilter.className = "opportunities-extension-customer-filter";
const customerFilterLabel = document.createElement("span");
customerFilterLabel.className = "opportunities-extension-customer-filter-label";
customerFilterLabel.textContent = "Customer";
const customerFilterButton = document.createElement("button");
customerFilterButton.id = CUSTOMER_FILTER_BUTTON_ID;
customerFilterButton.type = "button";
customerFilterButton.className = "opportunities-extension-customer-filter-trigger";
customerFilterButton.setAttribute("aria-expanded", "false");
customerFilterButton.setAttribute("aria-controls", CUSTOMER_FILTER_PANEL_ID);
customerFilterButton.addEventListener("click", toggleCustomerFilterPanel);
const customerFilterPanel = document.createElement("section");
customerFilterPanel.id = CUSTOMER_FILTER_PANEL_ID;
customerFilterPanel.className = "opportunities-extension-customer-filter-panel";
customerFilterPanel.hidden = true;
const customerSearchInput = document.createElement("input");
customerSearchInput.id = CUSTOMER_FILTER_SEARCH_ID;
customerSearchInput.className = "opportunities-extension-customer-filter-search";
customerSearchInput.type = "search";
customerSearchInput.placeholder = "Search customers";
customerSearchInput.setAttribute("aria-label", "Search customers");
customerSearchInput.addEventListener("input", () => {
customerSearch = customerSearchInput.value;
saveFilterPreferences();
renderCustomerFilterList();
});
const selectAllLabel = document.createElement("label");
selectAllLabel.className = "opportunities-extension-customer-filter-select-all";
const selectAllCheckbox = document.createElement("input");
selectAllCheckbox.id = CUSTOMER_FILTER_SELECT_ALL_ID;
selectAllCheckbox.type = "checkbox";
selectAllCheckbox.addEventListener("change", () => {
const customerOptions = getCustomerOptions();
selectedCustomerKeys = selectAllCheckbox.checked
? new Set(customerOptions.map((customer) => customer.key))
: new Set();
hasSavedCustomerFilter = true;
saveFilterPreferences();
renderCustomerFilter();
renderOpportunitiesTable();
});
const selectAllText = document.createElement("span");
selectAllText.textContent = "Select all";
selectAllLabel.append(selectAllCheckbox, selectAllText);
const customerFilterList = document.createElement("div");
customerFilterList.id = CUSTOMER_FILTER_LIST_ID;
customerFilterList.className = "opportunities-extension-customer-filter-list";
customerFilterPanel.append(customerSearchInput, selectAllLabel, customerFilterList);
customerFilter.append(customerFilterLabel, customerFilterButton, customerFilterPanel);
const ownerFilter = document.createElement("section");
ownerFilter.className = "opportunities-extension-customer-filter";
const ownerFilterLabel = document.createElement("span");
ownerFilterLabel.className = "opportunities-extension-customer-filter-label";
ownerFilterLabel.textContent = "Owner";
const ownerFilterButton = document.createElement("button");
ownerFilterButton.id = OWNER_FILTER_BUTTON_ID;
ownerFilterButton.type = "button";
ownerFilterButton.className = "opportunities-extension-customer-filter-trigger";
ownerFilterButton.setAttribute("aria-expanded", "false");
ownerFilterButton.setAttribute("aria-controls", OWNER_FILTER_PANEL_ID);
ownerFilterButton.addEventListener("click", toggleOwnerFilterPanel);
const ownerFilterPanel = document.createElement("section");
ownerFilterPanel.id = OWNER_FILTER_PANEL_ID;
ownerFilterPanel.className = "opportunities-extension-customer-filter-panel";
ownerFilterPanel.hidden = true;
const ownerSearchInput = document.createElement("input");
ownerSearchInput.id = OWNER_FILTER_SEARCH_ID;
ownerSearchInput.className = "opportunities-extension-customer-filter-search";
ownerSearchInput.type = "search";
ownerSearchInput.placeholder = "Search owners";
ownerSearchInput.setAttribute("aria-label", "Search owners");
ownerSearchInput.addEventListener("input", () => {
ownerSearch = ownerSearchInput.value;
saveFilterPreferences();
renderOwnerFilterList();
});
const ownerSelectAllLabel = document.createElement("label");
ownerSelectAllLabel.className = "opportunities-extension-customer-filter-select-all";
const ownerSelectAllCheckbox = document.createElement("input");
ownerSelectAllCheckbox.id = OWNER_FILTER_SELECT_ALL_ID;
ownerSelectAllCheckbox.type = "checkbox";
ownerSelectAllCheckbox.addEventListener("change", () => {
const ownerOptions = getOwnerOptions();
selectedOwnerKeys = ownerSelectAllCheckbox.checked
? new Set(ownerOptions.map((owner) => owner.key))
: new Set();
hasSavedOwnerFilter = true;
saveFilterPreferences();
renderOwnerFilter();
renderOpportunitiesTable();
});
const ownerSelectAllText = document.createElement("span");
ownerSelectAllText.textContent = "Select all";
ownerSelectAllLabel.append(ownerSelectAllCheckbox, ownerSelectAllText);
const ownerFilterList = document.createElement("div");
ownerFilterList.id = OWNER_FILTER_LIST_ID;
ownerFilterList.className = "opportunities-extension-customer-filter-list";
ownerFilterPanel.append(ownerSearchInput, ownerSelectAllLabel, ownerFilterList);
ownerFilter.append(ownerFilterLabel, ownerFilterButton, ownerFilterPanel);
const tableSearchField = document.createElement("label");
tableSearchField.className = "opportunities-extension-table-search-field";
const tableSearchLabel = document.createElement("span");
tableSearchLabel.textContent = "Search";
const tableSearchInput = document.createElement("input");
tableSearchInput.id = TABLE_SEARCH_ID;
tableSearchInput.type = "search";
tableSearchInput.placeholder = "Search opportunities";
tableSearchInput.setAttribute("aria-label", "Search opportunities");
tableSearchInput.addEventListener("input", () => {
tableSearch = tableSearchInput.value;
saveFilterPreferences();
renderOpportunitiesTable();
});
tableSearchField.append(tableSearchLabel, tableSearchInput);
const statusFilter = document.createElement("section");
statusFilter.className = "opportunities-extension-status-filter";
const statusFilterLabel = document.createElement("span");
statusFilterLabel.className = "opportunities-extension-status-filter-label";
statusFilterLabel.textContent = "Status";
const statusFilters = document.createElement("div");
statusFilters.id = STATUS_FILTERS_ID;
statusFilters.className = "opportunities-extension-status-filter-controls";
statusFilters.setAttribute("role", "group");
statusFilters.setAttribute("aria-label", "Filter by status");
statusFilter.append(statusFilterLabel, statusFilters);
form.addEventListener("submit", (event) => event.preventDefault());
form.append(field, customerFilter, ownerFilter, tableSearchField, statusFilter, stageFilter);
body.append(form);
const stageDashboard = document.createElement("section");
stageDashboard.id = STAGE_DASHBOARD_ID;
stageDashboard.className = "opportunities-extension-stage-dashboard";
stageDashboard.setAttribute("aria-label", "Opportunity amount by stage");
body.append(stageDashboard);
const tableSurface = document.createElement("section");
tableSurface.className = "opportunities-extension-table-surface";
const opportunitiesTable = document.createElement("table");
opportunitiesTable.id = OPPORTUNITIES_TABLE_ID;
opportunitiesTable.className = "opportunities-extension-table";
opportunitiesTable.setAttribute("aria-label", "Opportunities");
tableSurface.append(opportunitiesTable);
body.append(tableSurface);
const debugPanel = document.createElement("aside");
debugPanel.id = DEBUG_PANEL_ID;
debugPanel.className = "opportunities-extension-debug-panel";
debugPanel.hidden = true;
body.append(debugPanel);
shell.append(header, body);
overlay.append(shell);
document.body.append(overlay);
document.documentElement.classList.add("opportunities-extension-scroll-lock");
ensureEscapeKeyHandler();
renderStageFilterButtons();
renderStatusFilterButtons();
renderCustomerFilter();
renderOwnerFilter();
renderOpportunitiesTable();
requestOpportunitiesForPeriod(select.value);
select.focus();
}
function requestTokenRelayOnce() {
if (!tokenRelayRequest) {
authStatus = AUTH_STATUS.pending;
updateAuthBadge(authStatus);
tokenRelayRequest = requestTokenRelay()
.then((token) => {
accessToken = token;
authStatus = AUTH_STATUS.authenticated;
return token;
})
.catch((error) => {
accessToken = "";
authStatus = AUTH_STATUS.unauthenticated;
throw error;
})
.finally(() => {
updateAuthBadge(authStatus);
});
} else {
updateAuthBadge(authStatus);
}
return tokenRelayRequest;
}
async function requestTokenRelay() {
const xsrfTokenSource = await getXsrfToken();
const requestEntry = logRequestStart({
name: "requestTokenRelay",
method: "GET",
url: TOKEN_RELAY_URL,
headers: {
"x-xsrf-token": xsrfTokenSource.token ? maskToken(xsrfTokenSource.token) : "(missing)"
},
metadata: {
xsrfCookieName: xsrfTokenSource.cookieName || "(missing)",
xsrfTokenSource: xsrfTokenSource.source,
xsrfMatchedCookies: xsrfTokenSource.matchedCookieNames && xsrfTokenSource.matchedCookieNames.length
? xsrfTokenSource.matchedCookieNames.join(", ")
: "(none)",
xsrfLookupDetails: xsrfTokenSource.lookupDetails && xsrfTokenSource.lookupDetails.length
? xsrfTokenSource.lookupDetails.join(" | ")
: "(none)",
xsrfTokenError: xsrfTokenSource.error || ""
}
});
if (!xsrfTokenSource.token) {
logRequestFailure(requestEntry, "XSRF token cookie not found.");
throw new Error("XSRF token cookie not found.");
}
try {
const response = await fetch(TOKEN_RELAY_URL, {
method: "GET",
credentials: "include",
headers: {
"x-xsrf-token": xsrfTokenSource.token
}
});
const responseBody = await readResponseBodyForDebug(response);
if (!response.ok) {
logRequestSuccess(requestEntry, response, responseBody);
throw new Error(`Token relay failed with status ${response.status}.`);
}
let responseData;
try {
responseData = await response.json();
} catch (error) {
logRequestSuccess(requestEntry, response, responseBody);
throw error;
}
if (!responseData || typeof responseData.access_token !== "string" || !responseData.access_token) {
logRequestSuccess(requestEntry, response, responseBody);
throw new Error("Token relay response does not contain access_token.");
}
logRequestSuccess(requestEntry, response, createDebugBody(JSON.stringify({
...responseData,
access_token: maskToken(responseData.access_token)
}, null, 2)));
return responseData.access_token;
} catch (error) {
logRequestFailure(requestEntry, error.message || "Request failed.");
throw error;
}
}
async function requestOpportunitiesForPeriod(period) {
const dateRange = getFiscalPeriodRange(period, new Date());
const requestVersion = ++periodRequestVersion;
if (!dateRange) {
setOpportunitiesTableState({
items: [],
status: "empty",
message: "No opportunities to display for this period."
});
return;
}
try {
const token = accessToken || await requestTokenRelayOnce();
if (requestVersion !== periodRequestVersion) {
return;
}
await requestOpportunities(token, period, dateRange, requestVersion);
} catch (error) {
// Authentication and request errors are recorded by their respective request handlers.
}
}
async function requestOpportunities(token, period, dateRange, requestVersion) {
if (isCurrentPeriodRequest(requestVersion)) {
setOpportunitiesTableState({
items: [],
status: "loading",
message: "Loading opportunities..."
});
}
try {
const allItems = await requestOpportunitiesPage(token, period, dateRange, 0, [], requestVersion, 1);
if (allItems && isCurrentPeriodRequest(requestVersion)) {
setOpportunitiesTableState({
items: allItems,
status: "ready",
message: ""
});
}
} catch (error) {
if (isCurrentPeriodRequest(requestVersion)) {
setOpportunitiesTableState({
items: [],
status: "error",
message: "Unable to load opportunities."
});
}
throw error;
}
}
async function requestOpportunitiesPage(token, period, dateRange, offset, accumulatedItems, requestVersion, page) {
if (!isCurrentPeriodRequest(requestVersion)) {
return null;
}
const payload = createOpportunitiesQueryPayload(dateRange, offset);
const requestBody = JSON.stringify(payload);
const requestEntry = logRequestStart({
name: "requestOpportunities",
method: "POST",
url: OPPORTUNITIES_QUERY_URL,
headers: {
Accept: "application/json",
Authorization: `Bearer ${maskToken(token)}`,
"Content-Type": "application/json",
Origin: "https://eeho.fa.us2.oraclecloud.com",
Preference: "transient"
},
body: JSON.stringify(payload, null, 2),
metadata: {
period,
page,
offset,
startCloseDate: dateRange.startCloseDate,
endCloseDate: dateRange.endCloseDate
}
});
try {
const response = await fetch(OPPORTUNITIES_QUERY_URL, {
method: "POST",
credentials: "include",
headers: {
Accept: "application/json",
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
Origin: "https://eeho.fa.us2.oraclecloud.com",
Preference: "transient"
},
body: requestBody
});
const responseBody = await readResponseBodyForDebug(response);
logRequestSuccess(requestEntry, response, responseBody);
if (!response.ok) {
throw new Error(`Opportunities query failed with status ${response.status}.`);
}
const responseData = await response.json();
const pageItems = Array.isArray(responseData.items) ? responseData.items : [];
const allItems = accumulatedItems.concat(pageItems);
if (!responseData.hasMore) {
return allItems;
}
const responseOffset = Number(responseData.offset);
const responseCount = Number(responseData.count);
const receivedCount = Number.isFinite(responseCount) ? responseCount : pageItems.length;
const currentOffset = Number.isFinite(responseOffset) ? responseOffset : offset;
if (receivedCount <= 0) {
throw new Error("Opportunities query returned hasMore without additional results.");
}
return requestOpportunitiesPage(
token,
period,
dateRange,
currentOffset + receivedCount,
allItems,
requestVersion,
page + 1
);
} catch (error) {
logRequestFailure(requestEntry, error.message || "Request failed.");
throw error;
}
}
function resetOpportunitiesTable() {
selectedStages = new Set(STAGE_OPTIONS);
selectedStatuses = new Set(STATUS_OPTIONS);
selectedCustomerKeys = new Set();
customerSearch = "";
selectedOwnerKeys = new Set();
ownerSearch = "";
tableSearch = "";
hasSavedCustomerFilter = false;
hasSavedOwnerFilter = false;
customerFilterInitialized = false;
ownerFilterInitialized = false;
stageDashboardAmounts = new Map([["TOTAL", 0], ...STAGE_OPTIONS.map((stage) => [stage, 0])]);
opportunitiesTableState = {
items: [],
status: "idle",
message: "",
sortKey: "",
sortDirection: "ascending"
};
}
function resetCustomerAndOwnerFiltersForPeriod() {
selectedCustomerKeys = new Set();
selectedOwnerKeys = new Set();
customerSearch = "";
ownerSearch = "";
hasSavedCustomerFilter = false;
hasSavedOwnerFilter = false;
customerFilterInitialized = false;
ownerFilterInitialized = false;
}
function restoreFilterPreferences(select) {
try {
const rawPreferences = localStorage.getItem(FILTER_PREFERENCES_STORAGE_KEY);
if (!rawPreferences) {
return;
}
const preferences = JSON.parse(rawPreferences);
const savedStages = Array.isArray(preferences.stages)
? preferences.stages.filter((stage) => STAGE_OPTIONS.includes(stage))
: null;
const savedStatuses = Array.isArray(preferences.statuses)
? preferences.statuses.filter((status) => STATUS_OPTIONS.includes(status))
: null;
if (savedStages) {
selectedStages = new Set(savedStages);
}
if (savedStatuses) {
selectedStatuses = new Set(savedStatuses);
}
if (Array.isArray(preferences.customerKeys)) {
selectedCustomerKeys = new Set(preferences.customerKeys.map(String));
hasSavedCustomerFilter = true;
customerFilterInitialized = true;
}
if (Array.isArray(preferences.ownerKeys)) {
selectedOwnerKeys = new Set(preferences.ownerKeys.map(String));
hasSavedOwnerFilter = true;
ownerFilterInitialized = true;
}
customerSearch = typeof preferences.customerSearch === "string" ? preferences.customerSearch : "";
ownerSearch = typeof preferences.ownerSearch === "string" ? preferences.ownerSearch : "";
tableSearch = typeof preferences.tableSearch === "string" ? preferences.tableSearch : "";
if (PERIOD_OPTIONS.includes(preferences.period)) {
select.value = preferences.period;
}
} catch (error) {
// Ignore malformed or unavailable browser storage and use default filters.
}
}
function saveFilterPreferences(period) {
try {
const currentPeriod = period || document.querySelector(`#${MODAL_ID} select`)?.value || PERIOD_OPTIONS[0];
localStorage.setItem(FILTER_PREFERENCES_STORAGE_KEY, JSON.stringify({
period: currentPeriod,
stages: Array.from(selectedStages),
statuses: Array.from(selectedStatuses),
customerKeys: hasSavedCustomerFilter ? Array.from(selectedCustomerKeys) : null,
ownerKeys: hasSavedOwnerFilter ? Array.from(selectedOwnerKeys) : null,
customerSearch,
ownerSearch,
tableSearch
}));
} catch (error) {
// Ignore unavailable browser storage; filters still work for the current modal.
}
}
function setOpportunitiesTableState(nextState) {
if (nextState.status === "ready" && Array.isArray(nextState.items)) {
resetCustomerFilter(nextState.items);
resetOwnerFilter(nextState.items);
}
opportunitiesTableState = {
...opportunitiesTableState,
...nextState,
sortKey: nextState.items ? "" : opportunitiesTableState.sortKey,
sortDirection: nextState.items ? "ascending" : opportunitiesTableState.sortDirection
};
renderOpportunitiesTable();
renderCustomerFilter();
renderOwnerFilter();
}
function renderOpportunitiesTable() {
const table = document.getElementById(OPPORTUNITIES_TABLE_ID);
if (!table) {
return;
}
table.textContent = "";
table.setAttribute("aria-busy", opportunitiesTableState.status === "loading" ? "true" : "false");
const caption = document.createElement("caption");
caption.className = "opportunities-extension-visually-hidden";
caption.textContent = "Opportunities";
const tableHead = document.createElement("thead");
const headerRow = document.createElement("tr");
OPPORTUNITIES_COLUMNS.forEach((column) => {
const header = document.createElement("th");
const isSorted = opportunitiesTableState.sortKey === column.key;
header.scope = "col";
header.setAttribute("aria-sort", isSorted ? opportunitiesTableState.sortDirection : "none");
const sortButton = document.createElement("button");
sortButton.type = "button";
sortButton.className = "opportunities-extension-sort-button";
sortButton.setAttribute("data-sort-direction", isSorted ? opportunitiesTableState.sortDirection : "none");
sortButton.setAttribute("aria-label", `Sort by ${column.label}${isSorted ? `, ${opportunitiesTableState.sortDirection}` : ""}`);
sortButton.textContent = column.label;
sortButton.addEventListener("click", () => sortOpportunitiesBy(column.key));
header.append(sortButton);
headerRow.append(header);
});
tableHead.append(headerRow);
const tableBody = document.createElement("tbody");
const items = getSortedOpportunities();
renderStageDashboard(items);
if (opportunitiesTableState.status === "loading" || opportunitiesTableState.status === "error" || opportunitiesTableState.status === "empty" || (opportunitiesTableState.status === "ready" && items.length === 0)) {
const row = document.createElement("tr");
const cell = document.createElement("td");
cell.className = "opportunities-extension-table-status";
cell.colSpan = OPPORTUNITIES_COLUMNS.length;
cell.textContent = opportunitiesTableState.message || "No opportunities found.";
row.append(cell);
tableBody.append(row);
} else {
items.forEach((item) => {
const row = document.createElement("tr");
OPPORTUNITIES_COLUMNS.forEach((column) => {
const cell = document.createElement("td");
if (column.key === "stage") {
cell.append(createStageBadge(column.display(item)));
} else if (column.key === "status") {
cell.append(createStatusBadge(column.display(item)));
} else if (column.key === "name" || column.key === "optyNumber") {
const opportunityLink = createDetailLink(
column.display(item),
item.OptyNumber ? `${OPPORTUNITY_DETAIL_URL}${encodeURIComponent(item.OptyNumber)}` : ""
);
if (column.key === "optyNumber" && item.OptyNumber) {
const opportunityCellContent = document.createElement("span");
opportunityCellContent.className = "opportunities-extension-opty-number-content";
opportunityCellContent.append(opportunityLink, createCopyOpportunityButton(item.OptyNumber));
cell.append(opportunityCellContent);
} else {
cell.append(opportunityLink);
}
} else if (column.key === "customer") {
cell.append(createDetailLink(
column.display(item),
getNestedValue(item, ["CustomerAccount", "PartyId"])
? `${ACCOUNT_DETAIL_URL}${encodeURIComponent(getNestedValue(item, ["CustomerAccount", "PartyId"]))}`
: ""
));
} else {
cell.textContent = displayOpportunityValue(column, item);
}
row.append(cell);
});
tableBody.append(row);
});
}
table.append(caption, tableHead, tableBody);
}
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 = stageDashboardAmounts.get(stage) || 0;
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);
}
function sortOpportunitiesBy(key) {
const isSameColumn = opportunitiesTableState.sortKey === key;
opportunitiesTableState.sortKey = key;
opportunitiesTableState.sortDirection = isSameColumn && opportunitiesTableState.sortDirection === "ascending"
? "descending"
: "ascending";
renderOpportunitiesTable();
}
function renderStageFilterButtons() {
const controls = document.getElementById(STAGE_FILTERS_ID);
if (!controls) {
return;
}
controls.textContent = "";
STAGE_OPTIONS.forEach((stage) => {
const button = document.createElement("button");
const isPressed = selectedStages.has(stage);
button.type = "button";
button.className = "opportunities-extension-stage-filter-button";
button.setAttribute("data-stage", stage);
button.setAttribute("aria-pressed", isPressed ? "true" : "false");
button.textContent = stage;
button.addEventListener("click", () => toggleStageFilter(stage));
controls.append(button);
});
}
function toggleStageFilter(stage) {
if (selectedStages.has(stage)) {
selectedStages.delete(stage);
} else {
selectedStages.add(stage);
}
saveFilterPreferences();
renderStageFilterButtons();
renderOpportunitiesTable();
}
function renderStatusFilterButtons() {
const controls = document.getElementById(STATUS_FILTERS_ID);
if (!controls) {
return;
}
controls.textContent = "";
STATUS_OPTIONS.forEach((status) => {
const button = document.createElement("button");
const isPressed = selectedStatuses.has(status);
button.type = "button";
button.className = "opportunities-extension-status-filter-button";
button.setAttribute("data-status", status);
button.setAttribute("aria-pressed", isPressed ? "true" : "false");
button.textContent = status;
button.addEventListener("click", () => toggleStatusFilter(status));
controls.append(button);
});
}
function toggleStatusFilter(status) {
if (selectedStatuses.has(status)) {
selectedStatuses.delete(status);
} else {
selectedStatuses.add(status);
}
saveFilterPreferences();
renderStatusFilterButtons();
renderOpportunitiesTable();
}
function toggleCustomerFilterPanel() {
const panel = document.getElementById(CUSTOMER_FILTER_PANEL_ID);
const button = document.getElementById(CUSTOMER_FILTER_BUTTON_ID);
if (!panel || !button || button.disabled) {
return;
}
panel.hidden = !panel.hidden;
button.setAttribute("aria-expanded", panel.hidden ? "false" : "true");
if (!panel.hidden) {
const searchInput = document.getElementById(CUSTOMER_FILTER_SEARCH_ID);
searchInput.focus();
}
}
function closeCustomerFilterPanel(restoreFocus = true) {
const panel = document.getElementById(CUSTOMER_FILTER_PANEL_ID);
const button = document.getElementById(CUSTOMER_FILTER_BUTTON_ID);
if (!panel || panel.hidden) {
return false;
}
panel.hidden = true;
button.setAttribute("aria-expanded", "false");
if (restoreFocus) {
button.focus();
}
return true;
}
function toggleOwnerFilterPanel() {
const panel = document.getElementById(OWNER_FILTER_PANEL_ID);
const button = document.getElementById(OWNER_FILTER_BUTTON_ID);
if (!panel || !button || button.disabled) {
return;
}
panel.hidden = !panel.hidden;
button.setAttribute("aria-expanded", panel.hidden ? "false" : "true");
if (!panel.hidden) {
const searchInput = document.getElementById(OWNER_FILTER_SEARCH_ID);
searchInput.focus();
}
}
function closeOwnerFilterPanel(restoreFocus = true) {
const panel = document.getElementById(OWNER_FILTER_PANEL_ID);
const button = document.getElementById(OWNER_FILTER_BUTTON_ID);
if (!panel || panel.hidden) {
return false;
}
panel.hidden = true;
button.setAttribute("aria-expanded", "false");
if (restoreFocus) {
button.focus();
}
return true;
}
function resetCustomerFilter(items) {
const customerOptions = getCustomerOptions(items);
if (!customerFilterInitialized) {
selectedCustomerKeys = new Set(customerOptions.map((customer) => customer.key));
customerFilterInitialized = true;
}
}
function resetOwnerFilter(items) {
const ownerOptions = getOwnerOptions(items);
if (!ownerFilterInitialized) {
selectedOwnerKeys = new Set(ownerOptions.map((owner) => owner.key));
ownerFilterInitialized = true;
}
}
function renderCustomerFilter() {
const trigger = document.getElementById(CUSTOMER_FILTER_BUTTON_ID);
const searchInput = document.getElementById(CUSTOMER_FILTER_SEARCH_ID);
const selectAllCheckbox = document.getElementById(CUSTOMER_FILTER_SELECT_ALL_ID);
const customerOptions = getCustomerOptions();
if (!trigger || !searchInput || !selectAllCheckbox) {
return;
}
const selectedCount = customerOptions.filter((customer) => selectedCustomerKeys.has(customer.key)).length;
trigger.disabled = customerOptions.length === 0;
trigger.textContent = getCustomerFilterSummary(customerOptions.length, selectedCount);
searchInput.value = customerSearch;
selectAllCheckbox.disabled = customerOptions.length === 0;
selectAllCheckbox.checked = customerOptions.length > 0 && selectedCount === customerOptions.length;
selectAllCheckbox.indeterminate = selectedCount > 0 && selectedCount < customerOptions.length;
renderCustomerFilterList();
}
function renderCustomerFilterList() {
const list = document.getElementById(CUSTOMER_FILTER_LIST_ID);
if (!list) {
return;
}
const normalizedSearch = customerSearch.trim().toLocaleLowerCase();
const customerOptions = getCustomerOptions().filter((customer) => {
return customer.label.toLocaleLowerCase().includes(normalizedSearch);
});
list.textContent = "";
if (customerOptions.length === 0) {
const empty = document.createElement("p");
empty.className = "opportunities-extension-customer-filter-empty";
empty.textContent = customerSearch ? "No matching customers." : "No customers available.";
list.append(empty);
return;
}
customerOptions.forEach((customer) => {
const option = document.createElement("label");
option.className = "opportunities-extension-customer-filter-option";
const checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.checked = selectedCustomerKeys.has(customer.key);
checkbox.addEventListener("change", () => {
if (checkbox.checked) {
selectedCustomerKeys.add(customer.key);
} else {
selectedCustomerKeys.delete(customer.key);
}
hasSavedCustomerFilter = true;
saveFilterPreferences();
renderCustomerFilter();
renderOpportunitiesTable();
});
const label = document.createElement("span");
label.textContent = customer.label;
option.append(checkbox, label);
list.append(option);
});
}
function getCustomerOptions(items) {
const optionsByKey = new Map();
(items || opportunitiesTableState.items).forEach((item) => {
const key = getCustomerKey(item);
const label = getNestedValue(item, ["CustomerAccount", "PartyUniqueName"]);
if (key && label && !optionsByKey.has(key)) {
optionsByKey.set(key, { key, label: String(label) });
}
});
return Array.from(optionsByKey.values()).sort((first, second) => {
return first.label.localeCompare(second.label, undefined, {
sensitivity: "base"
});
});
}
function getCustomerKey(item) {
const partyId = getNestedValue(item, ["CustomerAccount", "PartyId"]);
const partyName = getNestedValue(item, ["CustomerAccount", "PartyUniqueName"]);
if (partyId !== null && partyId !== undefined && partyId !== "") {
return `party:${partyId}`;
}
return partyName ? `name:${partyName}` : "";
}
function getOwnerName(item) {
const owner = item.Owner;
if (typeof owner === "string" || typeof owner === "number") {
return owner;
}
if (owner && typeof owner === "object") {
return owner.PartyName
|| owner.PartyUniqueName
|| owner.Name
|| owner.DisplayName
|| owner.ResourceName
|| owner.OwnerName
|| "";
}
return "";
}
function getCustomerFilterSummary(total, selected) {
if (total === 0) {
return "No customers";
}
if (selected === total) {
return "All customers";
}
if (selected === 0) {
return "No customers";
}
return `${selected} customer${selected === 1 ? "" : "s"}`;
}
function renderOwnerFilter() {
const trigger = document.getElementById(OWNER_FILTER_BUTTON_ID);
const searchInput = document.getElementById(OWNER_FILTER_SEARCH_ID);
const selectAllCheckbox = document.getElementById(OWNER_FILTER_SELECT_ALL_ID);
const ownerOptions = getOwnerOptions();
if (!trigger || !searchInput || !selectAllCheckbox) {
return;
}
const selectedCount = ownerOptions.filter((owner) => selectedOwnerKeys.has(owner.key)).length;
trigger.disabled = ownerOptions.length === 0;
trigger.textContent = getCustomerFilterSummary(ownerOptions.length, selectedCount)
.replace("customer", "owner");
searchInput.value = ownerSearch;
selectAllCheckbox.disabled = ownerOptions.length === 0;
selectAllCheckbox.checked = ownerOptions.length > 0 && selectedCount === ownerOptions.length;
selectAllCheckbox.indeterminate = selectedCount > 0 && selectedCount < ownerOptions.length;
renderOwnerFilterList();
}
function renderOwnerFilterList() {
const list = document.getElementById(OWNER_FILTER_LIST_ID);
if (!list) {
return;
}
const normalizedSearch = ownerSearch.trim().toLocaleLowerCase();
const ownerOptions = getOwnerOptions().filter((owner) => {
return owner.label.toLocaleLowerCase().includes(normalizedSearch);
});
list.textContent = "";
if (ownerOptions.length === 0) {
const empty = document.createElement("p");
empty.className = "opportunities-extension-customer-filter-empty";
empty.textContent = ownerSearch ? "No matching owners." : "No owners available.";
list.append(empty);
return;
}
ownerOptions.forEach((owner) => {
const option = document.createElement("label");
option.className = "opportunities-extension-customer-filter-option";
const checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.checked = selectedOwnerKeys.has(owner.key);
checkbox.addEventListener("change", () => {
if (checkbox.checked) {
selectedOwnerKeys.add(owner.key);
} else {
selectedOwnerKeys.delete(owner.key);
}
hasSavedOwnerFilter = true;
saveFilterPreferences();
renderOwnerFilter();
renderOpportunitiesTable();
});
const label = document.createElement("span");
label.textContent = owner.label;
option.append(checkbox, label);
list.append(option);
});
}
function getOwnerOptions(items) {
const optionsByKey = new Map();
(items || opportunitiesTableState.items).forEach((item) => {
const key = getOwnerKey(item);
const label = getOwnerName(item);
if (key && label && !optionsByKey.has(key)) {
optionsByKey.set(key, { key, label: String(label) });
}
});
return Array.from(optionsByKey.values()).sort((first, second) => {
return first.label.localeCompare(second.label, undefined, {
sensitivity: "base"
});
});
}
function getOwnerKey(item) {
const owner = item.Owner;
const partyId = owner && typeof owner === "object" ? owner.PartyId : "";
const name = getOwnerName(item);
if (partyId !== null && partyId !== undefined && partyId !== "") {
return `party:${partyId}`;
}
return name ? `name:${name}` : "";
}
function getSortedOpportunities() {
const normalizedSearch = tableSearch.trim().toLocaleLowerCase();
const items = opportunitiesTableState.items.filter((item) => {
return selectedStages.has(item.ForecastGroup_c)
&& selectedStatuses.has(item.StatusCode)
&& selectedCustomerKeys.has(getCustomerKey(item))
&& selectedOwnerKeys.has(getOwnerKey(item))
&& matchesTableSearch(item, normalizedSearch);
});
const column = OPPORTUNITIES_COLUMNS.find((candidate) => candidate.key === opportunitiesTableState.sortKey);
if (!column) {
return items;
}
const direction = opportunitiesTableState.sortDirection === "ascending" ? 1 : -1;
return items.sort((first, second) => {
const firstValue = column.value(first);
const secondValue = column.value(second);
const firstIsEmpty = firstValue === null || firstValue === undefined || firstValue === "";
const secondIsEmpty = secondValue === null || secondValue === undefined || secondValue === "";
if (firstIsEmpty || secondIsEmpty) {
if (firstIsEmpty && secondIsEmpty) {
return 0;
}
return firstIsEmpty ? 1 : -1;
}
const firstNumber = Number(firstValue);
const secondNumber = Number(secondValue);
if (Number.isFinite(firstNumber) && Number.isFinite(secondNumber)) {
return (firstNumber - secondNumber) * direction;
}
return String(firstValue).localeCompare(String(secondValue), undefined, {
numeric: true,
sensitivity: "base"
}) * direction;
});
}
function matchesTableSearch(item, normalizedSearch) {
if (!normalizedSearch) {
return true;
}
return OPPORTUNITIES_COLUMNS.some((column) => {
const rawValue = stringifySearchValue(column.value(item));
const displayValue = stringifySearchValue(column.display(item));
return `${rawValue} ${displayValue}`.toLocaleLowerCase().includes(normalizedSearch);
});
}
function stringifySearchValue(value) {
if (value === null || value === undefined) {
return "";
}
if (typeof value === "object") {
return JSON.stringify(value);
}
return String(value);
}
function displayOpportunityValue(column, item) {
const value = column.display(item);
return value === null || value === undefined || value === "" ? "-" : String(value);
}
function createDetailLink(label, href) {
if (label === null || label === undefined || label === "") {
const placeholder = document.createElement("span");
placeholder.textContent = "-";
return placeholder;
}
if (!href) {
const text = document.createElement("span");
text.textContent = String(label);
return text;
}
const link = document.createElement("a");
link.href = href;
link.target = "_blank";
link.rel = "noopener noreferrer";
link.textContent = String(label);
return link;
}
function createCopyOpportunityButton(optyNumber) {
const button = document.createElement("button");
const icon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
button.type = "button";
button.className = "opportunities-extension-copy-button";
button.setAttribute("aria-label", `Copy opportunity number ${optyNumber}`);
button.title = "Copy opportunity number";
button.addEventListener("click", (event) => {
event.preventDefault();
event.stopPropagation();
copyOpportunityNumber(String(optyNumber));
});
icon.setAttribute("viewBox", "0 0 24 24");
icon.setAttribute("aria-hidden", "true");
path.setAttribute("d", "M8 8V5.5A2.5 2.5 0 0 1 10.5 3h8A2.5 2.5 0 0 1 21 5.5v8a2.5 2.5 0 0 1-2.5 2.5H16v2.5a2.5 2.5 0 0 1-2.5 2.5h-8A2.5 2.5 0 0 1 3 18.5v-8A2.5 2.5 0 0 1 5.5 8H8zm2 0h3.5A2.5 2.5 0 0 1 16 10.5v3.5h2.5a.5.5 0 0 0 .5-.5v-8a.5.5 0 0 0-.5-.5h-8a.5.5 0 0 0-.5.5V8zm3.5 2h-8a.5.5 0 0 0-.5.5v8a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5v-8a.5.5 0 0 0-.5-.5z");
icon.append(path);
button.append(icon);
return button;
}
async function copyOpportunityNumber(optyNumber) {
try {
if (navigator.clipboard && navigator.clipboard.writeText) {
await navigator.clipboard.writeText(optyNumber);
} else {
const input = document.createElement("textarea");
input.value = optyNumber;
input.setAttribute("readonly", "true");
input.style.position = "fixed";
input.style.opacity = "0";
document.body.append(input);
input.select();
document.execCommand("copy");
input.remove();
}
showCopyConfirmation(`Opty Number ${optyNumber} copied.`);
} catch (error) {
showCopyConfirmation("Unable to copy opportunity number.", true);
}
}
function showCopyConfirmation(message, isError = false) {
const modal = document.getElementById(MODAL_ID);
if (!modal) {
return;
}
let toast = modal.querySelector(".opportunities-extension-copy-toast");
if (!toast) {
toast = document.createElement("div");
toast.className = "opportunities-extension-copy-toast";
toast.setAttribute("role", "status");
modal.append(toast);
}
toast.classList.toggle("opportunities-extension-copy-toast-error", isError);
toast.textContent = message;
toast.hidden = false;
window.clearTimeout(toast.copyTimeout);
toast.copyTimeout = window.setTimeout(() => {
toast.hidden = true;
}, 2400);
}
function createStageBadge(stage) {
const badge = document.createElement("span");
const normalizedStage = typeof stage === "string" ? stage.toUpperCase() : "";
badge.className = "opportunities-extension-stage-badge";
badge.setAttribute("data-stage", normalizedStage);
badge.textContent = normalizedStage || "-";
return badge;
}
function createStatusBadge(status) {
const badge = document.createElement("span");
const normalizedStatus = typeof status === "string" ? status.toUpperCase() : "";
badge.className = "opportunities-extension-status-badge";
badge.setAttribute("data-status", normalizedStatus);
badge.textContent = normalizedStatus || "-";
return badge;
}
function getNestedValue(value, path) {
return path.reduce((result, key) => result && result[key], value);
}
function formatWinProbability(item) {
const value = getNestedValue(item, ["PrimaryRevenue", "WinProb"]);
const number = Number(value);
return Number.isFinite(number) ? `${number}%` : value;
}
function formatRevenue(item) {
const amount = getNestedValue(item, ["PrimaryRevenue", "RevnAmount"]);
const currency = getNestedValue(item, ["PrimaryRevenue", "RevnAmountCurcyCode"]);
const number = Number(amount);
if (!Number.isFinite(number)) {
return amount;
}
if (!currency) {
return number.toLocaleString();
}
try {
return new Intl.NumberFormat(undefined, {
style: "currency",
currency
}).format(number);
} catch (error) {
return `${currency} ${number.toLocaleString()}`;
}
}
function formatOracleResponseDate(value, includeTime) {
if (typeof value !== "string") {
return value;
}
const match = value.match(/^(\d{4})-(\d{2})-(\d{2})(?:T(\d{2}):(\d{2}))?/);
if (!match) {
return value;
}
const date = `${match[3]}/${match[2]}/${match[1]}`;
return includeTime && match[4] ? `${date} ${match[4]}:${match[5]}` : date;
}
function isCurrentPeriodRequest(requestVersion) {
return requestVersion === periodRequestVersion;
}
function createOpportunitiesQueryPayload(dateRange, offset) {
return {
aggregationResults: false,
applications: ["ORACLE-ISS-APP"],
onlyData: true,
entity: "Opportunity",
q: {
op: "$and",
criteria: [
{
op: "$or",
criteria: [
{ op: "$eq", attribute: "RecordSet", value: "ORA_MYSALESTEAMOPTIES" },
{ op: "$eq", attribute: "RecordSet", value: "ORA_MYASSGTERROPTIES" }
]
},
{
op: "$wi",
attribute: "EffectiveDate",
value1: dateRange.startCloseDate,
value2: dateRange.endCloseDate,
dynamicDate: false
}
]
},
keywords: null,
keywordsFields: [
"Name",
"OptyNumber",
"PrimaryRevenue.WinProb",
"CustomerAccount",
"PrimaryRevenue.RevnAmount",
"EffectiveDate",
"StatusCode",
"DealRisk_c",
"Owner",
"ForecastGroup_c",
"LastUpdateDate",
"PrimaryRevenue.RevnAmountCurcyCode",
"OptyId"
],
fields: [
"Name",
"OptyNumber",
"PrimaryRevenue.WinProb",
"CustomerAccount",
"PrimaryRevenue.RevnAmount",
"EffectiveDate",
"StatusCode",
"DealRisk_c",
"Owner",
"ForecastGroup_c",
"LastUpdateDate",
"PrimaryRevenue.RevnAmountCurcyCode",
"OptyId",
"CustomerAccount.PartyUniqueName",
"CustomerAccount.PartyId",
"CustomerAccount.PartyNumber"
],
sort: [],
language: "en",
skipInValidFields: true,
skipHiddenFromUIFields: true,
copiedFrom: "queries/d5dbcd01-9ff3-40be-9b5b-64744cbf7162",
limit: OPPORTUNITIES_PAGE_LIMIT,
offset
};
}
function getFiscalPeriodRange(period, currentDate) {
const currentYear = currentDate.getFullYear();
const currentMonth = currentDate.getMonth();
const fiscalStartYear = currentMonth >= 5 ? currentYear : currentYear - 1;
const currentQuarterIndex = Math.floor(((currentMonth - 5 + 12) % 12) / 3);
if (period === "Current Fiscal Year") {
const isBeforeFiscalYearStart = currentMonth < 5;
const startYear = isBeforeFiscalYearStart ? currentYear - 1 : currentYear;
const endYear = isBeforeFiscalYearStart ? currentYear : currentYear + 1;
return createDateRange(startYear, 5, endYear, 4);
}
let quarterOffset;
let numberOfQuarters = 1;
if (period === "Current Quarter") {
quarterOffset = currentQuarterIndex;
} else if (period === "Next Quarter") {
quarterOffset = currentQuarterIndex + 1;
} else if (period === "Previous Quarter") {
quarterOffset = currentQuarterIndex - 1;
} else if (period === "4 Rolling Quarters (CQ + 3)") {
quarterOffset = currentQuarterIndex;
numberOfQuarters = 4;
} else {
return null;
}
const start = new Date(fiscalStartYear, 5 + (quarterOffset * 3), 1);
const end = new Date(start.getFullYear(), start.getMonth() + (numberOfQuarters * 3), 0);
return {
startCloseDate: formatOracleDate(start),
endCloseDate: formatOracleDate(end)
};
}
function createDateRange(startYear, startMonth, endYear, endMonth) {
return {
startCloseDate: formatOracleDate(new Date(startYear, startMonth, 1)),
endCloseDate: formatOracleDate(new Date(endYear, endMonth + 1, 0))
};
}
function formatOracleDate(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `${year}-${month}-${day}T00:00:00.000Z`;
}
function logRequestStart(details) {
const entry = {
id: requestLog.length + 1,
name: details.name,
method: details.method,
url: details.url,
headers: details.headers,
status: "pending",
startedAt: new Date(),
completedAt: null,
durationMs: null,
httpStatus: null,
statusText: "",
requestHeaders: details.headers,
requestBody: details.body || "",
metadata: details.metadata || {},
responseHeaders: {},
responseBody: "",
responseBodyTruncated: false,
error: ""
};
requestLog.push(entry);
renderDebugPanel();
return entry;
}
function logRequestSuccess(entry, response, responseBody) {
entry.status = response.ok ? "success" : "failed";
entry.completedAt = new Date();
entry.durationMs = entry.completedAt.getTime() - entry.startedAt.getTime();
entry.httpStatus = response.status;
entry.statusText = response.statusText || "";
entry.responseHeaders = headersToObject(response.headers);
entry.responseBody = responseBody.value;
entry.responseBodyTruncated = responseBody.truncated;
renderDebugPanel();
}
function logRequestFailure(entry, errorMessage) {
entry.status = "failed";
entry.completedAt = new Date();
entry.durationMs = entry.completedAt.getTime() - entry.startedAt.getTime();
entry.error = errorMessage;
renderDebugPanel();
}
async function readResponseBodyForDebug(response) {
try {
const text = await response.clone().text();
return createDebugBody(text);
} catch (error) {
return {
value: `Unable to read response body: ${error.message || "unknown error"}`,
truncated: false
};
}
}
function createDebugBody(text) {
if (text.length > DEBUG_BODY_LIMIT) {
return {
value: `${text.slice(0, DEBUG_BODY_LIMIT)}\n... truncated ${text.length - DEBUG_BODY_LIMIT} characters`,
truncated: true
};
}
return {
value: text || "(empty response body)",
truncated: false
};
}
function headersToObject(headers) {
const headerMap = {};
headers.forEach((value, key) => {
headerMap[key] = value;
});
return headerMap;
}
function formatHeaderBlock(headers) {
const entries = Object.entries(headers);
if (entries.length === 0) {
return "(none)";
}
return entries
.map(([key, value]) => `${key}: ${value}`)
.join("\n");
}
function formatMetadataBlock(metadata) {
const entries = Object.entries(metadata);
if (entries.length === 0) {
return "(none)";
}
return entries
.map(([key, value]) => `${key}: ${value || "(none)"}`)
.join("\n");
}
function maskToken(token) {
if (token.length <= 10) {
return "(present)";
}
return `${token.slice(0, 4)}...${token.slice(-4)}`;
}
function toggleDebugPanel() {
const modal = document.getElementById(MODAL_ID);
if (!modal || modal.hidden) {
return;
}
const panel = document.getElementById(DEBUG_PANEL_ID);
if (!panel) {
return;
}
panel.hidden = !panel.hidden;
renderDebugPanel();
}
function renderDebugPanel() {
const panel = document.getElementById(DEBUG_PANEL_ID);
if (!panel || panel.hidden) {
return;
}
panel.textContent = "";
const title = document.createElement("h2");
title.textContent = "Debug Requests";
const summary = document.createElement("p");
summary.textContent = `${requestLog.length} request${requestLog.length === 1 ? "" : "s"} since modal opened.`;
const list = document.createElement("div");
list.className = "opportunities-extension-debug-list";
if (requestLog.length === 0) {
const empty = document.createElement("div");
empty.className = "opportunities-extension-debug-empty";
empty.textContent = "No requests recorded yet.";
list.append(empty);
}
requestLog.forEach((entry) => {
const item = document.createElement("details");
item.className = `opportunities-extension-debug-item opportunities-extension-debug-item-${entry.status}`;
const heading = document.createElement("summary");
heading.className = "opportunities-extension-debug-heading";
const name = document.createElement("strong");
name.textContent = `${entry.id}. ${entry.name}`;
const httpStatus = document.createElement("span");
httpStatus.className = "opportunities-extension-debug-http-status";
httpStatus.textContent = entry.httpStatus ? String(entry.httpStatus) : "pending";
const status = document.createElement("span");
status.className = "opportunities-extension-debug-status";
status.textContent = entry.status;
const summaryMeta = document.createElement("span");
summaryMeta.className = "opportunities-extension-debug-summary-meta";
summaryMeta.append(httpStatus, status);
heading.append(name, summaryMeta);
const lines = [
`${entry.method} ${entry.url}`,
`Started: ${formatDebugTime(entry.startedAt)}`,
entry.completedAt ? `Completed: ${formatDebugTime(entry.completedAt)} (${entry.durationMs}ms)` : "Completed: pending",
entry.httpStatus ? `HTTP: ${entry.httpStatus} ${entry.statusText}`.trim() : "",
entry.error ? `Error: ${entry.error}` : "",
`Response body truncated: ${entry.responseBodyTruncated ? "yes" : "no"}`
].filter(Boolean);
const details = document.createElement("div");
details.className = "opportunities-extension-debug-details";
appendDebugBlock(details, "Request", lines.join("\n"));
appendDebugBlock(details, "Request Metadata", formatMetadataBlock(entry.metadata));
appendDebugBlock(details, "Request Headers", formatHeaderBlock(entry.requestHeaders));
appendDebugBlock(details, "Request Body", entry.requestBody || "(none)");
appendDebugBlock(details, "Response Headers", formatHeaderBlock(entry.responseHeaders));
appendDebugBlock(details, "Response Body", entry.responseBody || "(none)");
item.append(heading, details);
list.append(item);
});
panel.append(title, summary, list);
}
function appendDebugBlock(container, label, value) {
const block = document.createElement("section");
const heading = document.createElement("h3");
const content = document.createElement("pre");
heading.textContent = label;
content.textContent = value;
block.append(heading, content);
container.append(block);
}
function formatDebugTime(date) {
return date.toLocaleTimeString(undefined, {
hour: "2-digit",
minute: "2-digit",
second: "2-digit"
});
}
async function getXsrfToken() {
const extensionCookie = await getXsrfTokenFromExtensionCookies();
if (extensionCookie.token) {
return extensionCookie;
}
const xsrfCookie = document.cookie
.split(";")
.map((cookie) => cookie.trim())
.find((cookie) => cookie.startsWith("XSRF-TOKEN-"));
if (!xsrfCookie) {
return {
token: "",
cookieName: "",
source: extensionCookie.error ? "document.cookie fallback after cookies API error" : "document.cookie fallback",
matchedCookieNames: extensionCookie.matchedCookieNames || [],
lookupDetails: extensionCookie.lookupDetails || [],
error: extensionCookie.error || ""
};
}
const cookieName = xsrfCookie.slice(0, xsrfCookie.indexOf("="));
const tokenValue = xsrfCookie.slice(xsrfCookie.indexOf("=") + 1);
return {
token: decodeURIComponent(tokenValue),
cookieName,
source: "document.cookie",
matchedCookieNames: extensionCookie.matchedCookieNames || [],
lookupDetails: extensionCookie.lookupDetails || [],
error: ""
};
}
function getXsrfTokenFromExtensionCookies() {
const runtimeApi = typeof browser !== "undefined" ? browser : chrome;
if (!runtimeApi || !runtimeApi.runtime || !runtimeApi.runtime.sendMessage) {
return Promise.resolve({
token: "",
cookieName: "",
source: "unavailable cookies API",
error: "Runtime messaging API unavailable."
});
}
return new Promise((resolve) => {
runtimeApi.runtime.sendMessage({
type: "opportunitiesExtension.getXsrfToken"
}, (response) => {
const lastError = runtimeApi.runtime.lastError;
if (lastError) {
resolve({
token: "",
cookieName: "",
source: "cookies API",
matchedCookieNames: [],
lookupDetails: [],
error: lastError.message
});
return;
}
resolve({
token: response && response.ok ? response.token : "",
cookieName: response && response.ok ? response.cookieName : "",
source: "cookies API",
matchedCookieNames: response && response.matchedCookieNames ? response.matchedCookieNames : [],
lookupDetails: response && response.lookupDetails ? response.lookupDetails : [],
error: response && response.error ? response.error : ""
});
});
});
}
function updateAuthBadge(status) {
const badge = document.querySelector(`#${MODAL_ID} .opportunities-extension-auth-badge`);
if (!badge) {
return;
}
const badgeStatus = status === AUTH_STATUS.authenticated || status === AUTH_STATUS.unauthenticated
? status
: AUTH_STATUS.pending;
badge.setAttribute("data-auth-status", badgeStatus);
badge.className = `opportunities-extension-auth-badge opportunities-extension-auth-badge-${badgeStatus}`;
badge.textContent = badgeStatus === AUTH_STATUS.authenticated
? "Authenticated"
: badgeStatus === AUTH_STATUS.unauthenticated
? "Unauthenticated"
: "Authenticating";
}
function closeOpportunitiesModal() {
const modal = document.getElementById(MODAL_ID);
if (modal) {
modal.hidden = true;
}
document.documentElement.classList.remove("opportunities-extension-scroll-lock");
}
function getSavedTheme() {
try {
return localStorage.getItem(THEME_STORAGE_KEY) === "dark" ? "dark" : "light";
} catch (error) {
return "light";
}
}
function applyTheme(modal, button, theme) {
const isDarkTheme = theme === "dark";
modal.setAttribute("data-theme", isDarkTheme ? "dark" : "light");
button.textContent = "";
button.setAttribute("aria-label", isDarkTheme ? "Ativar modo claro" : "Ativar modo escuro");
button.title = isDarkTheme ? "Light mode" : "Dark mode";
button.append(createThemeIcon(isDarkTheme ? "sun" : "moon"));
try {
localStorage.setItem(THEME_STORAGE_KEY, isDarkTheme ? "dark" : "light");
} catch (error) {
// Continue with the selected theme when browser storage is unavailable.
}
}
function createThemeIcon(iconName) {
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
svg.setAttribute("viewBox", "0 0 24 24");
svg.setAttribute("aria-hidden", "true");
svg.setAttribute("focusable", "false");
if (iconName === "sun") {
path.setAttribute("d", "M12 3V1m0 22v-2m9-9h2M1 12h2m15.36 6.36 1.42 1.42M4.22 4.22l1.42 1.42m12.72 0 1.42-1.42M4.22 19.78l1.42-1.42M16.5 12a4.5 4.5 0 1 1-9 0 4.5 4.5 0 0 1 9 0Z");
} else {
path.setAttribute("d", "M20.5 14.2A8.5 8.5 0 0 1 9.8 3.5a8.5 8.5 0 1 0 10.7 10.7Z");
}
path.setAttribute("fill", "none");
path.setAttribute("stroke", "currentColor");
path.setAttribute("stroke-linecap", "round");
path.setAttribute("stroke-linejoin", "round");
path.setAttribute("stroke-width", "1.8");
svg.append(path);
return svg;
}
function ensureEscapeKeyHandler() {
if (window[ESCAPE_LISTENER_KEY]) {
return;
}
window[ESCAPE_LISTENER_KEY] = true;
document.addEventListener("keydown", (event) => {
const modal = document.getElementById(MODAL_ID);
if (!modal || modal.hidden) {
return;
}
if (event.ctrlKey && event.key.toLowerCase() === "d") {
event.preventDefault();
event.stopPropagation();
toggleDebugPanel();
return;
}
if (event.key === "Escape") {
if (closeCustomerFilterPanel()) {
event.preventDefault();
return;
}
if (closeOwnerFilterPanel()) {
event.preventDefault();
return;
}
closeOpportunitiesModal();
}
});
document.addEventListener("pointerdown", (event) => {
const modal = document.getElementById(MODAL_ID);
const panel = document.getElementById(CUSTOMER_FILTER_PANEL_ID);
const ownerPanel = document.getElementById(OWNER_FILTER_PANEL_ID);
if (!modal || modal.hidden) {
return;
}
if (panel && !panel.hidden) {
const customerFilter = panel.closest(".opportunities-extension-customer-filter");
if (customerFilter && !customerFilter.contains(event.target)) {
closeCustomerFilterPanel(false);
}
}
if (ownerPanel && !ownerPanel.hidden) {
const ownerFilter = ownerPanel.closest(".opportunities-extension-customer-filter");
if (ownerFilter && !ownerFilter.contains(event.target)) {
closeOwnerFilterPanel(false);
}
}
});
}
function ensureExtensionStyles() {
if (document.getElementById(STYLE_ID)) {
return;
}
const style = document.createElement("style");
style.id = STYLE_ID;
style.textContent = `
.opportunities-extension-scroll-lock {
overflow: hidden !important;
}
.opportunities-extension-modal,
.opportunities-extension-modal * {
box-sizing: border-box;
font-family: "Oracle Sans", Arial, Helvetica, sans-serif;
}
.opportunities-extension-modal {
position: fixed;
inset: 0;
z-index: 2147483647;
background: #f5f4f2;
color: #000000;
}
.opportunities-extension-modal[hidden] {
display: none !important;
}
.opportunities-extension-shell {
height: 100vh;
min-height: 0;
display: grid;
grid-template-rows: auto minmax(0, 1fr);
background: #f5f4f2;
}
.opportunities-extension-header {
position: relative;
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 24px;
padding: 28px 40px 22px;
border-bottom: 1px solid #dedbd7;
background:
linear-gradient(90deg, #8f624a 0 11%, #c74634 11% 23%, #6f5a7f 23% 36%, #00758f 36% 51%, #d4b06a 51% 63%, transparent 63% 100%) top left / 100% 6px no-repeat,
#ffffff;
}
.opportunities-extension-header::before {
position: absolute;
left: 40px;
bottom: -1px;
width: 64px;
height: 3px;
background: #00758f;
content: "";
}
.opportunities-extension-header h1 {
margin: 0;
color: #000000;
font-size: 24px;
font-weight: 700;
line-height: 1.2;
letter-spacing: 0;
}
.opportunities-extension-header p {
margin: 0;
color: #5f5a55;
font-size: 14px;
line-height: 1.4;
}
.opportunities-extension-subtitle-row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
margin-top: 6px;
}
.opportunities-extension-auth-badge {
display: inline-flex;
align-items: center;
min-height: 22px;
padding: 2px 8px;
border-radius: 999px;
color: #ffffff;
font-size: 12px;
font-weight: 700;
line-height: 1.2;
}
.opportunities-extension-auth-badge-authenticated {
background: #3f6f17;
}
.opportunities-extension-auth-badge-unauthenticated {
background: #c5331f;
}
.opportunities-extension-auth-badge-pending {
background: #6f5a7f;
}
.opportunities-extension-icon-button {
width: 36px;
height: 36px;
flex: 0 0 36px;
border: 1px solid transparent;
border-radius: 4px;
background: transparent;
color: #312d2a;
font-size: 18px;
font-weight: 600;
line-height: 1;
cursor: pointer;
}
.opportunities-extension-header-actions {
display: inline-flex;
align-items: center;
gap: 6px;
}
.opportunities-extension-icon-button svg {
width: 18px;
height: 18px;
vertical-align: middle;
}
.opportunities-extension-icon-button:hover,
.opportunities-extension-icon-button:focus {
border-color: #b8b2ad;
background: #f5f4f2;
outline: none;
}
.opportunities-extension-body {
display: grid;
min-height: 0;
grid-template-rows: auto auto minmax(0, 1fr);
gap: 16px;
padding: 22px 40px 36px;
background: #f5f4f2;
overflow: hidden;
}
.opportunities-extension-form {
display: flex;
align-items: end;
flex-wrap: wrap;
gap: 24px;
min-height: 82px;
max-width: none;
margin: 0;
padding: 14px 16px;
border: 1px solid #dedbd7;
border-radius: 4px;
background: #ffffff;
}
.opportunities-extension-field {
display: grid;
max-width: max-content;
min-width: 270px;
width: max-content;
gap: 6px;
color: #312d2a;
font-size: 13px;
font-weight: 600;
line-height: 1.3;
}
.opportunities-extension-stage-dashboard {
display: grid;
grid-template-columns: repeat(6, minmax(0, 1fr));
gap: 12px;
}
.opportunities-extension-stage-dashboard-card {
display: grid;
gap: 6px;
min-width: 0;
padding: 12px 16px;
border: 1px solid #dedbd7;
border-top: 3px solid #5f5a55;
border-radius: 4px;
background: #ffffff;
}
.opportunities-extension-stage-dashboard-card span {
color: #5f5a55;
font-size: 11px;
font-weight: 700;
line-height: 1.2;
}
.opportunities-extension-stage-dashboard-value {
overflow: hidden;
color: #312d2a;
font-size: 22px;
font-weight: 700;
line-height: 1.2;
text-overflow: ellipsis;
white-space: nowrap;
}
.opportunities-extension-stage-dashboard-card[data-stage="SQL"] {
border-top-color: #008aa6;
}
.opportunities-extension-total-dashboard-card {
border-top-color: #00758f;
}
.opportunities-extension-stage-dashboard-card[data-stage="PIPELINE"] {
border-top-color: #6f6863;
}
.opportunities-extension-stage-dashboard-card[data-stage="UPSIDE"] {
border-top-color: #c87b00;
}
.opportunities-extension-stage-dashboard-card[data-stage="FORECAST"] {
border-top-color: #755b8b;
}
.opportunities-extension-stage-dashboard-card[data-stage="WON"] {
border-top-color: #4b7f22;
}
.opportunities-extension-stage-filter {
display: grid;
grid-template-rows: auto 44px;
gap: 6px;
min-width: 0;
}
.opportunities-extension-stage-filter-label {
color: #312d2a;
font-size: 13px;
font-weight: 600;
line-height: 1.3;
}
.opportunities-extension-stage-filter-controls {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
}
.opportunities-extension-stage-filter-button {
min-height: 36px;
padding: 6px 12px;
border: 1px solid currentColor;
border-radius: 18px;
background: #ffffff;
color: #5f5a55;
font-size: 12px;
font-weight: 700;
letter-spacing: 0;
line-height: 1.2;
cursor: pointer;
}
.opportunities-extension-stage-filter-button[data-stage="SQL"] {
color: #006b84;
}
.opportunities-extension-stage-filter-button[data-stage="PIPELINE"] {
color: #5f5a55;
}
.opportunities-extension-stage-filter-button[data-stage="UPSIDE"] {
color: #945400;
}
.opportunities-extension-stage-filter-button[data-stage="FORECAST"] {
color: #624e74;
}
.opportunities-extension-stage-filter-button[data-stage="WON"] {
color: #3f6f17;
}
.opportunities-extension-stage-filter-button[aria-pressed="true"][data-stage="SQL"] {
border-color: #006b84;
background: #006b84;
color: #ffffff;
}
.opportunities-extension-stage-filter-button[aria-pressed="true"][data-stage="PIPELINE"] {
border-color: #5f5a55;
background: #5f5a55;
color: #ffffff;
}
.opportunities-extension-stage-filter-button[aria-pressed="true"][data-stage="UPSIDE"] {
border-color: #945400;
background: #945400;
color: #ffffff;
}
.opportunities-extension-stage-filter-button[aria-pressed="true"][data-stage="FORECAST"] {
border-color: #624e74;
background: #624e74;
color: #ffffff;
}
.opportunities-extension-stage-filter-button[aria-pressed="true"][data-stage="WON"] {
border-color: #3f6f17;
background: #3f6f17;
color: #ffffff;
}
.opportunities-extension-stage-filter-button:focus-visible {
outline: 2px solid #00758f;
outline-offset: 2px;
}
.opportunities-extension-status-filter {
display: grid;
grid-template-rows: auto 44px;
gap: 6px;
min-width: 0;
}
.opportunities-extension-status-filter-label {
color: #312d2a;
font-size: 13px;
font-weight: 600;
line-height: 1.3;
}
.opportunities-extension-status-filter-controls {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
}
.opportunities-extension-status-filter-button {
min-height: 36px;
padding: 6px 12px;
border: 1px solid currentColor;
border-radius: 18px;
background: #ffffff;
font-size: 12px;
font-weight: 700;
letter-spacing: 0;
line-height: 1.2;
cursor: pointer;
}
.opportunities-extension-status-filter-button[data-status="OPEN"] {
color: #197a3d;
}
.opportunities-extension-status-filter-button[data-status="CLOSED"] {
color: #4b5563;
}
.opportunities-extension-status-filter-button[data-status="LOST"] {
color: #b13b34;
}
.opportunities-extension-status-filter-button[data-status="WON"] {
color: #1f5f99;
}
.opportunities-extension-status-filter-button[aria-pressed="true"][data-status="OPEN"] {
border-color: #197a3d;
background: #197a3d;
color: #ffffff;
}
.opportunities-extension-status-filter-button[aria-pressed="true"][data-status="CLOSED"] {
border-color: #4b5563;
background: #4b5563;
color: #ffffff;
}
.opportunities-extension-status-filter-button[aria-pressed="true"][data-status="LOST"] {
border-color: #b13b34;
background: #b13b34;
color: #ffffff;
}
.opportunities-extension-status-filter-button[aria-pressed="true"][data-status="WON"] {
border-color: #1f5f99;
background: #1f5f99;
color: #ffffff;
}
.opportunities-extension-status-filter-button:focus-visible {
outline: 2px solid #00758f;
outline-offset: 2px;
}
.opportunities-extension-customer-filter {
position: relative;
display: grid;
grid-template-rows: auto 44px;
gap: 6px;
min-width: 220px;
}
.opportunities-extension-customer-filter-label {
color: #312d2a;
font-size: 13px;
font-weight: 600;
line-height: 1.3;
}
.opportunities-extension-customer-filter-trigger {
position: relative;
min-width: 220px;
min-height: 44px;
padding: 10px 38px 10px 12px;
border: 1px solid #b8b2ad;
border-radius: 3px;
background: #ffffff;
color: #312d2a;
font-size: 14px;
line-height: 1.3;
text-align: left;
cursor: pointer;
}
.opportunities-extension-customer-filter-trigger::after {
position: absolute;
top: 50%;
right: 16px;
width: 0;
height: 0;
border-top: 6px solid #312d2a;
border-right: 5px solid transparent;
border-left: 5px solid transparent;
content: "";
pointer-events: none;
transform: translateY(-35%);
}
.opportunities-extension-customer-filter-trigger:disabled {
cursor: not-allowed;
background: #f5f4f2;
color: #7d7772;
}
.opportunities-extension-customer-filter-trigger:focus-visible {
border-color: #312d2a;
box-shadow: 0 0 0 1px #312d2a;
outline: none;
}
.opportunities-extension-customer-filter-panel {
position: absolute;
top: calc(100% + 8px);
left: 0;
z-index: 4;
display: grid;
width: min(360px, calc(100vw - 80px));
gap: 10px;
padding: 12px;
border: 1px solid #8f8a85;
border-radius: 4px;
background: #ffffff;
box-shadow: 0 4px 12px rgba(0, 0, 0, .18);
}
.opportunities-extension-customer-filter-panel[hidden] {
display: none !important;
}
.opportunities-extension-customer-filter-search {
width: 100%;
min-height: 40px;
padding: 8px 10px;
border: 1px solid #b8b2ad;
border-radius: 3px;
background: #ffffff;
color: #312d2a;
font-size: 14px;
}
.opportunities-extension-customer-filter-search:focus {
border-color: #312d2a;
box-shadow: 0 0 0 1px #312d2a;
outline: none;
}
.opportunities-extension-customer-filter-select-all,
.opportunities-extension-customer-filter-option {
display: flex;
align-items: center;
gap: 8px;
color: #312d2a;
font-size: 13px;
line-height: 1.35;
}
.opportunities-extension-customer-filter-select-all {
min-height: 32px;
padding-bottom: 8px;
border-bottom: 1px solid #dedbd7;
font-weight: 700;
}
.opportunities-extension-customer-filter-panel input[type="checkbox"] {
width: 16px;
height: 16px;
flex: 0 0 16px;
accent-color: #00758f;
}
.opportunities-extension-customer-filter-list {
display: block;
max-height: 250px;
overflow: auto;
}
.opportunities-extension-customer-filter-option {
display: grid;
grid-template-columns: 16px minmax(0, 1fr);
align-items: start;
min-height: 0;
height: auto !important;
padding: 8px 4px;
cursor: pointer;
}
.opportunities-extension-customer-filter-option input[type="checkbox"] {
margin-top: 1px;
}
.opportunities-extension-customer-filter-option span {
display: block;
min-width: 0;
line-height: 18px;
overflow-wrap: anywhere;
white-space: normal;
}
.opportunities-extension-customer-filter-option:hover {
background: #f5f4f2;
}
.opportunities-extension-customer-filter-empty {
margin: 4px 0;
color: #5f5a55;
font-size: 13px;
}
.opportunities-extension-table-search-field {
display: grid;
grid-template-rows: auto 44px;
gap: 6px;
width: 260px;
color: #312d2a;
font-size: 13px;
font-weight: 600;
line-height: 1.3;
}
.opportunities-extension-table-search-field input {
width: 100%;
min-height: 44px;
padding: 10px 12px;
border: 1px solid #b8b2ad;
border-radius: 3px;
background: #ffffff;
color: #312d2a;
font-size: 14px;
line-height: 1.3;
}
.opportunities-extension-table-search-field input:focus {
border-color: #312d2a;
box-shadow: 0 0 0 1px #312d2a;
outline: none;
}
.opportunities-extension-select-wrap {
position: relative;
display: block;
width: 270px;
max-width: 100%;
}
.opportunities-extension-select-wrap::after {
position: absolute;
top: 50%;
right: 16px;
width: 0;
height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-top: 6px solid #000000;
content: "";
pointer-events: none;
transform: translateY(-35%);
}
.opportunities-extension-field select {
width: 100%;
min-width: 0;
max-width: 100%;
min-height: 44px;
box-sizing: border-box;
appearance: none;
border: 1px solid #b8b2ad;
border-radius: 3px;
background: #ffffff;
color: #000000;
font-size: 14px;
line-height: 1.3;
padding: 11px 44px 11px 12px;
}
.opportunities-extension-field select:focus {
border-color: #312d2a;
box-shadow: 0 0 0 1px #312d2a;
outline: none;
}
.opportunities-extension-table-surface {
min-height: 0;
height: 100%;
border: 1px solid #dedbd7;
border-radius: 4px;
background: #ffffff;
overflow: auto;
}
.opportunities-extension-table {
width: 100%;
min-width: 1440px;
border-collapse: collapse;
table-layout: fixed;
color: #312d2a;
font-size: 13px;
line-height: 1.35;
}
.opportunities-extension-table thead {
background: #faf9f8;
}
.opportunities-extension-table th {
position: sticky;
top: 0;
z-index: 1;
height: 44px;
border-bottom: 1px solid #b8b2ad;
background: #faf9f8;
color: #312d2a;
font-size: 12px;
font-weight: 700;
text-align: left;
white-space: nowrap;
}
.opportunities-extension-table td {
min-height: 48px;
padding: 12px 16px;
border-bottom: 1px solid #ebe8e5;
vertical-align: middle;
overflow-wrap: anywhere;
}
.opportunities-extension-table th:nth-child(1) {
width: 14%;
}
.opportunities-extension-table th:nth-child(2) {
width: 8%;
}
.opportunities-extension-table th:nth-child(3) {
width: 17%;
}
.opportunities-extension-table th:nth-child(4) {
width: 12%;
}
.opportunities-extension-table th:nth-child(5) {
width: 9%;
}
.opportunities-extension-table th:nth-child(6) {
width: 9%;
}
.opportunities-extension-table th:nth-child(7) {
width: 8%;
}
.opportunities-extension-table th:nth-child(8) {
width: 8%;
}
.opportunities-extension-table th:nth-child(9) {
width: 7%;
}
.opportunities-extension-table th:nth-child(10) {
width: 8%;
}
.opportunities-extension-table td:nth-child(2),
.opportunities-extension-table td:nth-child(5),
.opportunities-extension-table td:nth-child(6),
.opportunities-extension-table td:nth-child(7),
.opportunities-extension-table td:nth-child(8),
.opportunities-extension-table td:nth-child(9),
.opportunities-extension-table td:nth-child(10) {
white-space: nowrap;
}
.opportunities-extension-table tbody tr:hover {
background: #f7f6f4;
}
.opportunities-extension-table tbody tr:last-child td {
border-bottom: 0;
}
.opportunities-extension-table a {
color: #006b84;
text-decoration: underline;
text-decoration-thickness: 1px;
text-underline-offset: 2px;
}
.opportunities-extension-table a:hover {
color: #004f63;
}
.opportunities-extension-opty-number-content {
display: inline-flex;
align-items: center;
gap: 6px;
max-width: 100%;
}
.opportunities-extension-copy-button {
display: inline-flex;
align-items: center;
justify-content: center;
flex: 0 0 auto;
width: 24px;
height: 24px;
padding: 3px;
border: 0;
border-radius: 3px;
background: transparent;
color: #00758f;
cursor: pointer;
}
.opportunities-extension-copy-button svg {
width: 16px;
height: 16px;
fill: currentColor;
}
.opportunities-extension-copy-button:hover,
.opportunities-extension-copy-button:focus-visible {
background: #e8f4f6;
color: #004f63;
outline: none;
}
.opportunities-extension-copy-toast {
position: fixed;
right: 28px;
bottom: 28px;
z-index: 3;
max-width: min(360px, calc(100vw - 56px));
padding: 12px 16px;
border-radius: 4px;
background: #2f6f18;
box-shadow: 0 4px 12px rgba(0, 0, 0, .18);
color: #ffffff;
font-size: 13px;
font-weight: 700;
}
.opportunities-extension-copy-toast-error {
background: #a52b1c;
}
.opportunities-extension-copy-toast[hidden] {
display: none !important;
}
.opportunities-extension-stage-badge {
display: inline-flex;
align-items: center;
min-height: 24px;
padding: 3px 8px;
border-radius: 12px;
background: #ebe8e5;
color: #312d2a;
font-size: 11px;
font-weight: 700;
line-height: 1.2;
white-space: nowrap;
}
.opportunities-extension-stage-badge[data-stage="SQL"] {
background: #d9f0f5;
color: #006b84;
}
.opportunities-extension-stage-badge[data-stage="PIPELINE"] {
background: #ebe8e5;
color: #5f5a55;
}
.opportunities-extension-stage-badge[data-stage="UPSIDE"] {
background: #fff0d8;
color: #945400;
}
.opportunities-extension-stage-badge[data-stage="FORECAST"] {
background: #eee8f5;
color: #624e74;
}
.opportunities-extension-stage-badge[data-stage="WON"] {
background: #e5f1d9;
color: #3f6f17;
}
.opportunities-extension-status-badge {
display: inline-flex;
align-items: center;
min-height: 24px;
padding: 3px 8px;
border-radius: 12px;
background: #ebe8e5;
color: #312d2a;
font-size: 11px;
font-weight: 700;
line-height: 1.2;
white-space: nowrap;
}
.opportunities-extension-status-badge[data-status="OPEN"] {
background: #e2f2e5;
color: #197a3d;
}
.opportunities-extension-status-badge[data-status="CLOSED"] {
background: #e5e7eb;
color: #4b5563;
}
.opportunities-extension-status-badge[data-status="LOST"] {
background: #f9e3e1;
color: #b13b34;
}
.opportunities-extension-status-badge[data-status="WON"] {
background: #e3edf8;
color: #1f5f99;
}
.opportunities-extension-sort-button {
position: relative;
display: inline-flex;
align-items: center;
width: 100%;
min-height: 44px;
padding: 10px 30px 10px 16px;
border: 0;
background: transparent;
color: inherit;
font: inherit;
font-weight: inherit;
letter-spacing: 0;
text-align: left;
cursor: pointer;
}
.opportunities-extension-sort-button::after {
position: absolute;
right: 15px;
color: #5f5a55;
content: "↕";
font-size: 16px;
font-weight: 400;
}
.opportunities-extension-sort-button[data-sort-direction="ascending"]::after {
color: #00758f;
content: "↑";
}
.opportunities-extension-sort-button[data-sort-direction="descending"]::after {
color: #00758f;
content: "↓";
}
.opportunities-extension-sort-button:hover {
background: #f0eeeb;
}
.opportunities-extension-sort-button:focus-visible {
outline: 2px solid #00758f;
outline-offset: -2px;
}
.opportunities-extension-table-status {
height: 152px;
color: #5f5a55;
font-size: 14px;
text-align: center;
}
.opportunities-extension-visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.opportunities-extension-debug-panel {
position: fixed;
right: 24px;
bottom: 24px;
z-index: 1;
width: min(640px, calc(100vw - 48px));
max-height: min(560px, calc(100vh - 120px));
overflow: auto;
border: 1px solid #8f8a85;
border-radius: 4px;
background: #ffffff;
box-shadow: 0 2px 8px rgba(0, 0, 0, .16);
padding: 16px;
color: #000000;
}
.opportunities-extension-debug-panel[hidden] {
display: none !important;
}
.opportunities-extension-debug-panel h2 {
margin: 0 0 4px;
font-size: 18px;
font-weight: 700;
line-height: 1.25;
}
.opportunities-extension-debug-panel p {
margin: 0 0 12px;
color: #5f5a55;
font-size: 13px;
}
.opportunities-extension-debug-list {
display: grid;
gap: 10px;
}
.opportunities-extension-debug-item {
border: 1px solid #dedbd7;
border-left-width: 4px;
border-radius: 4px;
background: #faf9f8;
}
.opportunities-extension-debug-item[open] {
background: #ffffff;
}
.opportunities-extension-debug-item-success {
border-left-color: #3f6f17;
}
.opportunities-extension-debug-item-success .opportunities-extension-debug-status {
background: #e2f2e5;
color: #356d19;
}
.opportunities-extension-debug-item-failed {
border-left-color: #c5331f;
}
.opportunities-extension-debug-item-failed .opportunities-extension-debug-status {
background: #f9e3e1;
color: #a52b1c;
}
.opportunities-extension-debug-item-pending {
border-left-color: #6f5a7f;
}
.opportunities-extension-debug-item-pending .opportunities-extension-debug-status {
background: #eee8f2;
color: #6f5a7f;
}
.opportunities-extension-debug-heading {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
min-height: 42px;
padding: 9px 12px 9px 10px;
cursor: pointer;
list-style: none;
}
.opportunities-extension-debug-heading::-webkit-details-marker {
display: none;
}
.opportunities-extension-debug-heading::before {
content: "⌄";
width: 14px;
color: #5f5a55;
font-size: 16px;
line-height: 1;
transform: rotate(-90deg);
transition: transform .15s ease;
}
.opportunities-extension-debug-item[open] .opportunities-extension-debug-heading::before {
transform: rotate(0deg);
}
.opportunities-extension-debug-summary-meta {
display: inline-flex;
align-items: center;
gap: 8px;
margin-left: auto;
}
.opportunities-extension-debug-http-status,
.opportunities-extension-debug-status {
display: inline-flex;
align-items: center;
min-height: 22px;
padding: 0 8px;
border-radius: 999px;
font-size: 11px;
font-weight: 700;
line-height: 1;
text-transform: uppercase;
}
.opportunities-extension-debug-http-status {
background: #e8f4f6;
color: #006d7a;
}
.opportunities-extension-debug-status {
background: #ebe8e5;
color: #5f5a55;
}
.opportunities-extension-debug-heading strong {
font-size: 13px;
}
.opportunities-extension-debug-item pre {
margin: 0;
padding: 8px 12px 12px;
color: #312d2a;
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
line-height: 1.45;
white-space: pre-wrap;
word-break: break-word;
}
.opportunities-extension-debug-empty {
padding: 14px;
border: 1px dashed #b8b2ad;
border-radius: 4px;
color: #5f5a55;
font-size: 13px;
}
.opportunities-extension-modal[data-theme="dark"] {
background: #1f1f1f;
color: #f6f4f2;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-shell,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-body {
background: #1f1f1f;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-header {
border-bottom-color: #4e4a46;
background:
linear-gradient(90deg, #a8795d 0 11%, #dd5a47 11% 23%, #876e9b 23% 36%, #00a6be 36% 51%, #dfbd76 51% 63%, transparent 63% 100%) top left / 100% 6px no-repeat,
#292827;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-header h1,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-stage-dashboard-value,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-table,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-field,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-stage-filter-label,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-status-filter-label,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-customer-filter-label,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-table-search-field {
color: #f6f4f2;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-header p,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-stage-dashboard-card span,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-customer-filter-empty,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-debug-panel p,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-debug-empty {
color: #c9c5c1;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-form,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-stage-dashboard-card,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-table-surface,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-customer-filter-panel,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-debug-panel,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-debug-item[open] {
border-color: #4e4a46;
background: #292827;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-customer-filter-trigger,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-customer-filter-search,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-table-search-field input,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-field select,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-stage-filter-button,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-status-filter-button {
border-color: #6a6560;
background: #333130;
color: #f6f4f2;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-customer-filter-trigger::after {
border-top-color: #f6f4f2;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-select-wrap::after {
border-top-color: #f6f4f2;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-customer-filter-trigger:disabled {
background: #292827;
color: #918b86;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-customer-filter-search::placeholder,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-table-search-field input::placeholder {
color: #b5b0ab;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-customer-filter-select-all,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-customer-filter-option,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-debug-heading,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-debug-item pre,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-debug-panel h2 {
color: #f6f4f2;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-customer-filter-select-all,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-table th {
border-bottom-color: #4e4a46;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-customer-filter-option:hover,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-table tbody tr:hover,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-sort-button:hover,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-icon-button:hover,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-icon-button:focus {
background: #3b3937;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-table thead,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-table th {
background: #333130;
color: #f6f4f2;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-table td {
border-bottom-color: #3f3c39;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-table a,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-copy-button {
color: #43c4d5;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-table a:hover,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-copy-button:hover,
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-copy-button:focus-visible {
background: #1d4e55;
color: #91e4ed;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-stage-filter-button[data-stage="SQL"] {
color: #43c4d5;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-stage-filter-button[data-stage="PIPELINE"] {
color: #d1cbc6;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-stage-filter-button[data-stage="UPSIDE"] {
color: #ffbf62;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-stage-filter-button[data-stage="FORECAST"] {
color: #d2b7e9;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-stage-filter-button[data-stage="WON"] {
color: #9bd46a;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-stage-dashboard-card[data-stage="SQL"] {
border-top-color: #43c4d5;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-total-dashboard-card {
border-top-color: #43c4d5;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-stage-dashboard-card[data-stage="PIPELINE"] {
border-top-color: #c0b9b3;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-stage-dashboard-card[data-stage="UPSIDE"] {
border-top-color: #ffb84d;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-stage-dashboard-card[data-stage="FORECAST"] {
border-top-color: #cba7e7;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-stage-dashboard-card[data-stage="WON"] {
border-top-color: #92cf5b;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-stage-filter-button[aria-pressed="true"][data-stage="SQL"] {
border-color: #008aa6;
background: #008aa6;
color: #ffffff;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-stage-filter-button[aria-pressed="true"][data-stage="PIPELINE"] {
border-color: #6f6863;
background: #6f6863;
color: #ffffff;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-stage-filter-button[aria-pressed="true"][data-stage="UPSIDE"] {
border-color: #b96e00;
background: #b96e00;
color: #ffffff;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-stage-filter-button[aria-pressed="true"][data-stage="FORECAST"] {
border-color: #755b8b;
background: #755b8b;
color: #ffffff;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-stage-filter-button[aria-pressed="true"][data-stage="WON"] {
border-color: #4b7f22;
background: #4b7f22;
color: #ffffff;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-status-filter-button[data-status="OPEN"] {
color: #69c985;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-status-filter-button[data-status="CLOSED"] {
color: #c3cad3;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-status-filter-button[data-status="LOST"] {
color: #ff9d94;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-status-filter-button[data-status="WON"] {
color: #83bfff;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-status-filter-button[aria-pressed="true"][data-status="OPEN"] {
border-color: #197a3d;
background: #197a3d;
color: #ffffff;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-status-filter-button[aria-pressed="true"][data-status="CLOSED"] {
border-color: #4b5563;
background: #4b5563;
color: #ffffff;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-status-filter-button[aria-pressed="true"][data-status="LOST"] {
border-color: #b13b34;
background: #b13b34;
color: #ffffff;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-status-filter-button[aria-pressed="true"][data-status="WON"] {
border-color: #1f5f99;
background: #1f5f99;
color: #ffffff;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-debug-item {
border-color: #4e4a46;
background: #252423;
}
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-icon-button {
color: #f6f4f2;
}
@media (max-width: 700px) {
.opportunities-extension-header {
padding: 24px 20px 18px;
}
.opportunities-extension-header::before {
left: 20px;
}
.opportunities-extension-header h1 {
font-size: 22px;
}
.opportunities-extension-header p {
font-size: 14px;
}
.opportunities-extension-body {
padding: 16px 20px 24px;
}
.opportunities-extension-form {
align-items: stretch;
flex-wrap: wrap;
gap: 14px;
min-height: auto;
padding: 12px;
}
.opportunities-extension-field {
max-width: none;
width: 100%;
}
.opportunities-extension-customer-filter {
width: 100%;
}
.opportunities-extension-customer-filter-trigger {
width: 100%;
}
.opportunities-extension-customer-filter-panel {
width: min(360px, calc(100vw - 64px));
}
.opportunities-extension-table-search-field {
width: 100%;
}
.opportunities-extension-stage-dashboard {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.opportunities-extension-stage-dashboard-value {
font-size: 18px;
}
.opportunities-extension-stage-filter-controls {
gap: 6px;
}
.opportunities-extension-status-filter-controls {
gap: 6px;
}
.opportunities-extension-table-surface {
min-width: 0;
min-height: calc(100vh - 230px);
}
.opportunities-extension-debug-panel {
right: 12px;
bottom: 12px;
width: calc(100vw - 24px);
max-height: calc(100vh - 96px);
}
}
`;
document.head.append(style);
}
function isAllowedPage() {
if (window.location.hostname !== "eeho.fa.us2.oraclecloud.com") {
return false;
}
return ALLOWED_PATHS.some((path) => {
return window.location.pathname === path || window.location.pathname.startsWith(`${path}/`);
});
}
function findInsertionPoint(salesGroup) {
const addIconInsideGroup = salesGroup.querySelector(ADD_ICON_SELECTOR);
if (addIconInsideGroup) {
return addIconInsideGroup;
}
const container = salesGroup.closest(".flat-grid, .flat-grid-container, [id*='yourapps'], [id*='groupNode']") || salesGroup.parentElement || document.body;
const addIconCells = Array.from(container.querySelectorAll(ADD_ICON_SELECTOR));
return addIconCells.find((cell) => {
return Boolean(salesGroup.compareDocumentPosition(cell) & Node.DOCUMENT_POSITION_FOLLOWING);
}) || null;
}
function insertTile() {
if (document.getElementById(TILE_ID)) {
return true;
}
const salesGroup = document.querySelector(SALES_GROUP_SELECTOR);
const addIconCell = salesGroup ? findInsertionPoint(salesGroup) : null;
if (!salesGroup || !addIconCell) {
return false;
}
addIconCell.before(createOpportunitiesTile());
return true;
}
if (insertTile()) {
return;
}
const observer = new MutationObserver(() => {
insertTile();
});
observer.observe(document.documentElement, {
childList: true,
subtree: true
});
})();