diff --git a/dist/chromium/content.js b/dist/chromium/content.js index a137352..2817d11 100644 --- a/dist/chromium/content.js +++ b/dist/chromium/content.js @@ -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; diff --git a/dist/firefox/content.js b/dist/firefox/content.js index a137352..2817d11 100644 --- a/dist/firefox/content.js +++ b/dist/firefox/content.js @@ -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; diff --git a/src/content.js b/src/content.js index a137352..2817d11 100644 --- a/src/content.js +++ b/src/content.js @@ -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;