Implementation of dark mode styling and inclusion of an indicator showing the total number of opportunities

This commit is contained in:
2026-08-26 11:30:47 -03:00
parent 52d59770bd
commit db382b63f2
3 changed files with 1545 additions and 75 deletions

View File

@@ -25,6 +25,7 @@
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;
@@ -53,7 +54,9 @@
let tableSearch = "";
let hasSavedCustomerFilter = false;
let hasSavedOwnerFilter = false;
let stageDashboardAmounts = new Map(STAGE_OPTIONS.map((stage) => [stage, 0]));
let customerFilterInitialized = false;
let ownerFilterInitialized = false;
let stageDashboardAmounts = new Map([["TOTAL", 0], ...STAGE_OPTIONS.map((stage) => [stage, 0])]);
let opportunitiesTableState = {
items: [],
status: "idle",
@@ -72,12 +75,12 @@
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: "winProbability", label: "Win Probability", value: (item) => getNestedValue(item, ["PrimaryRevenue", "WinProb"]), display: formatWinProbability },
{ 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) }
];
@@ -175,6 +178,7 @@
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");
@@ -213,7 +217,20 @@
closeButton.textContent = "X";
closeButton.addEventListener("click", closeOpportunitiesModal);
header.append(titleBlock, closeButton);
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";
@@ -241,6 +258,7 @@
});
select.addEventListener("change", () => {
resetCustomerAndOwnerFiltersForPeriod();
saveFilterPreferences(select.value);
requestOpportunitiesForPeriod(select.value);
});
@@ -702,7 +720,9 @@
tableSearch = "";
hasSavedCustomerFilter = false;
hasSavedOwnerFilter = false;
stageDashboardAmounts = new Map(STAGE_OPTIONS.map((stage) => [stage, 0]));
customerFilterInitialized = false;
ownerFilterInitialized = false;
stageDashboardAmounts = new Map([["TOTAL", 0], ...STAGE_OPTIONS.map((stage) => [stage, 0])]);
opportunitiesTableState = {
items: [],
status: "idle",
@@ -712,6 +732,17 @@
};
}
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);
@@ -739,11 +770,13 @@
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 : "";
@@ -853,11 +886,20 @@
cell.append(createStageBadge(column.display(item)));
} else if (column.key === "status") {
cell.append(createStatusBadge(column.display(item)));
} else if (column.key === "optyNumber") {
cell.append(createDetailLink(
} 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),
@@ -888,6 +930,24 @@
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) => {
@@ -1121,20 +1181,18 @@
function resetCustomerFilter(items) {
const customerOptions = getCustomerOptions(items);
selectedCustomerKeys = hasSavedCustomerFilter
? new Set(customerOptions
.map((customer) => customer.key)
.filter((key) => selectedCustomerKeys.has(key)))
: new Set(customerOptions.map((customer) => customer.key));
if (!customerFilterInitialized) {
selectedCustomerKeys = new Set(customerOptions.map((customer) => customer.key));
customerFilterInitialized = true;
}
}
function resetOwnerFilter(items) {
const ownerOptions = getOwnerOptions(items);
selectedOwnerKeys = hasSavedOwnerFilter
? new Set(ownerOptions
.map((owner) => owner.key)
.filter((key) => selectedOwnerKeys.has(key)))
: new Set(ownerOptions.map((owner) => owner.key));
if (!ownerFilterInitialized) {
selectedOwnerKeys = new Set(ownerOptions.map((owner) => owner.key));
ownerFilterInitialized = true;
}
}
function renderCustomerFilter() {
@@ -1468,6 +1526,76 @@
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() : "";
@@ -1993,6 +2121,53 @@
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;
@@ -2178,6 +2353,18 @@
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;
@@ -2223,7 +2410,7 @@
.opportunities-extension-stage-dashboard {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
grid-template-columns: repeat(6, minmax(0, 1fr));
gap: 12px;
}
@@ -2259,6 +2446,10 @@
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;
}
@@ -2724,23 +2915,23 @@
}
.opportunities-extension-table th:nth-child(3) {
width: 8%;
}
.opportunities-extension-table th:nth-child(4) {
width: 17%;
}
.opportunities-extension-table th:nth-child(5) {
.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: 9%;
width: 8%;
}
.opportunities-extension-table th:nth-child(8) {
@@ -2756,7 +2947,7 @@
}
.opportunities-extension-table td:nth-child(2),
.opportunities-extension-table td:nth-child(3),
.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),
@@ -2784,6 +2975,64 @@
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;
@@ -3079,6 +3328,247 @@
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;

View File

@@ -25,6 +25,7 @@
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;
@@ -53,7 +54,9 @@
let tableSearch = "";
let hasSavedCustomerFilter = false;
let hasSavedOwnerFilter = false;
let stageDashboardAmounts = new Map(STAGE_OPTIONS.map((stage) => [stage, 0]));
let customerFilterInitialized = false;
let ownerFilterInitialized = false;
let stageDashboardAmounts = new Map([["TOTAL", 0], ...STAGE_OPTIONS.map((stage) => [stage, 0])]);
let opportunitiesTableState = {
items: [],
status: "idle",
@@ -72,12 +75,12 @@
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: "winProbability", label: "Win Probability", value: (item) => getNestedValue(item, ["PrimaryRevenue", "WinProb"]), display: formatWinProbability },
{ 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) }
];
@@ -175,6 +178,7 @@
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");
@@ -213,7 +217,20 @@
closeButton.textContent = "X";
closeButton.addEventListener("click", closeOpportunitiesModal);
header.append(titleBlock, closeButton);
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";
@@ -241,6 +258,7 @@
});
select.addEventListener("change", () => {
resetCustomerAndOwnerFiltersForPeriod();
saveFilterPreferences(select.value);
requestOpportunitiesForPeriod(select.value);
});
@@ -702,7 +720,9 @@
tableSearch = "";
hasSavedCustomerFilter = false;
hasSavedOwnerFilter = false;
stageDashboardAmounts = new Map(STAGE_OPTIONS.map((stage) => [stage, 0]));
customerFilterInitialized = false;
ownerFilterInitialized = false;
stageDashboardAmounts = new Map([["TOTAL", 0], ...STAGE_OPTIONS.map((stage) => [stage, 0])]);
opportunitiesTableState = {
items: [],
status: "idle",
@@ -712,6 +732,17 @@
};
}
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);
@@ -739,11 +770,13 @@
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 : "";
@@ -853,11 +886,20 @@
cell.append(createStageBadge(column.display(item)));
} else if (column.key === "status") {
cell.append(createStatusBadge(column.display(item)));
} else if (column.key === "optyNumber") {
cell.append(createDetailLink(
} 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),
@@ -888,6 +930,24 @@
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) => {
@@ -1121,20 +1181,18 @@
function resetCustomerFilter(items) {
const customerOptions = getCustomerOptions(items);
selectedCustomerKeys = hasSavedCustomerFilter
? new Set(customerOptions
.map((customer) => customer.key)
.filter((key) => selectedCustomerKeys.has(key)))
: new Set(customerOptions.map((customer) => customer.key));
if (!customerFilterInitialized) {
selectedCustomerKeys = new Set(customerOptions.map((customer) => customer.key));
customerFilterInitialized = true;
}
}
function resetOwnerFilter(items) {
const ownerOptions = getOwnerOptions(items);
selectedOwnerKeys = hasSavedOwnerFilter
? new Set(ownerOptions
.map((owner) => owner.key)
.filter((key) => selectedOwnerKeys.has(key)))
: new Set(ownerOptions.map((owner) => owner.key));
if (!ownerFilterInitialized) {
selectedOwnerKeys = new Set(ownerOptions.map((owner) => owner.key));
ownerFilterInitialized = true;
}
}
function renderCustomerFilter() {
@@ -1468,6 +1526,76 @@
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() : "";
@@ -1993,6 +2121,53 @@
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;
@@ -2178,6 +2353,18 @@
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;
@@ -2223,7 +2410,7 @@
.opportunities-extension-stage-dashboard {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
grid-template-columns: repeat(6, minmax(0, 1fr));
gap: 12px;
}
@@ -2259,6 +2446,10 @@
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;
}
@@ -2724,23 +2915,23 @@
}
.opportunities-extension-table th:nth-child(3) {
width: 8%;
}
.opportunities-extension-table th:nth-child(4) {
width: 17%;
}
.opportunities-extension-table th:nth-child(5) {
.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: 9%;
width: 8%;
}
.opportunities-extension-table th:nth-child(8) {
@@ -2756,7 +2947,7 @@
}
.opportunities-extension-table td:nth-child(2),
.opportunities-extension-table td:nth-child(3),
.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),
@@ -2784,6 +2975,64 @@
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;
@@ -3079,6 +3328,247 @@
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;

View File

@@ -25,6 +25,7 @@
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;
@@ -53,7 +54,9 @@
let tableSearch = "";
let hasSavedCustomerFilter = false;
let hasSavedOwnerFilter = false;
let stageDashboardAmounts = new Map(STAGE_OPTIONS.map((stage) => [stage, 0]));
let customerFilterInitialized = false;
let ownerFilterInitialized = false;
let stageDashboardAmounts = new Map([["TOTAL", 0], ...STAGE_OPTIONS.map((stage) => [stage, 0])]);
let opportunitiesTableState = {
items: [],
status: "idle",
@@ -72,12 +75,12 @@
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: "winProbability", label: "Win Probability", value: (item) => getNestedValue(item, ["PrimaryRevenue", "WinProb"]), display: formatWinProbability },
{ 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) }
];
@@ -175,6 +178,7 @@
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");
@@ -213,7 +217,20 @@
closeButton.textContent = "X";
closeButton.addEventListener("click", closeOpportunitiesModal);
header.append(titleBlock, closeButton);
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";
@@ -241,6 +258,7 @@
});
select.addEventListener("change", () => {
resetCustomerAndOwnerFiltersForPeriod();
saveFilterPreferences(select.value);
requestOpportunitiesForPeriod(select.value);
});
@@ -702,7 +720,9 @@
tableSearch = "";
hasSavedCustomerFilter = false;
hasSavedOwnerFilter = false;
stageDashboardAmounts = new Map(STAGE_OPTIONS.map((stage) => [stage, 0]));
customerFilterInitialized = false;
ownerFilterInitialized = false;
stageDashboardAmounts = new Map([["TOTAL", 0], ...STAGE_OPTIONS.map((stage) => [stage, 0])]);
opportunitiesTableState = {
items: [],
status: "idle",
@@ -712,6 +732,17 @@
};
}
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);
@@ -739,11 +770,13 @@
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 : "";
@@ -853,11 +886,20 @@
cell.append(createStageBadge(column.display(item)));
} else if (column.key === "status") {
cell.append(createStatusBadge(column.display(item)));
} else if (column.key === "optyNumber") {
cell.append(createDetailLink(
} 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),
@@ -888,6 +930,24 @@
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) => {
@@ -1121,20 +1181,18 @@
function resetCustomerFilter(items) {
const customerOptions = getCustomerOptions(items);
selectedCustomerKeys = hasSavedCustomerFilter
? new Set(customerOptions
.map((customer) => customer.key)
.filter((key) => selectedCustomerKeys.has(key)))
: new Set(customerOptions.map((customer) => customer.key));
if (!customerFilterInitialized) {
selectedCustomerKeys = new Set(customerOptions.map((customer) => customer.key));
customerFilterInitialized = true;
}
}
function resetOwnerFilter(items) {
const ownerOptions = getOwnerOptions(items);
selectedOwnerKeys = hasSavedOwnerFilter
? new Set(ownerOptions
.map((owner) => owner.key)
.filter((key) => selectedOwnerKeys.has(key)))
: new Set(ownerOptions.map((owner) => owner.key));
if (!ownerFilterInitialized) {
selectedOwnerKeys = new Set(ownerOptions.map((owner) => owner.key));
ownerFilterInitialized = true;
}
}
function renderCustomerFilter() {
@@ -1468,6 +1526,76 @@
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() : "";
@@ -1993,6 +2121,53 @@
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;
@@ -2178,6 +2353,18 @@
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;
@@ -2223,7 +2410,7 @@
.opportunities-extension-stage-dashboard {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
grid-template-columns: repeat(6, minmax(0, 1fr));
gap: 12px;
}
@@ -2259,6 +2446,10 @@
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;
}
@@ -2724,23 +2915,23 @@
}
.opportunities-extension-table th:nth-child(3) {
width: 8%;
}
.opportunities-extension-table th:nth-child(4) {
width: 17%;
}
.opportunities-extension-table th:nth-child(5) {
.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: 9%;
width: 8%;
}
.opportunities-extension-table th:nth-child(8) {
@@ -2756,7 +2947,7 @@
}
.opportunities-extension-table td:nth-child(2),
.opportunities-extension-table td:nth-child(3),
.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),
@@ -2784,6 +2975,64 @@
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;
@@ -3079,6 +3328,247 @@
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;