Badge indicator showing whether an opportunity has an associated Service Request
This commit is contained in:
@@ -13,6 +13,8 @@ function createOpportunitiesController({
|
||||
productsContentState,
|
||||
renderProductsProgress,
|
||||
requestOpportunitiesPage,
|
||||
loadCrmServiceRequests,
|
||||
resetCrmServiceRequests,
|
||||
requestOpptyProductsInBackground,
|
||||
requestTokenRelayOnce,
|
||||
setOpportunitiesTableState
|
||||
@@ -78,6 +80,8 @@ function createOpportunitiesController({
|
||||
}
|
||||
|
||||
try {
|
||||
await loadCrmServiceRequests(token, requestVersion);
|
||||
if (!isCurrentPeriodRequest(requestVersion)) return;
|
||||
const allItems = await requestOpportunitiesPage(token, period, dateRange, 0, [], requestVersion, 1);
|
||||
|
||||
if (allItems && isCurrentPeriodRequest(requestVersion)) {
|
||||
@@ -102,6 +106,7 @@ function createOpportunitiesController({
|
||||
}
|
||||
|
||||
function resetOpportunitiesTable() {
|
||||
resetCrmServiceRequests();
|
||||
opportunitiesFilterState.selectedStages = new Set(STAGE_OPTIONS);
|
||||
opportunitiesFilterState.selectedStatuses = new Set(STATUS_OPTIONS);
|
||||
opportunitiesFilterState.selectedCustomerKeys = new Set();
|
||||
|
||||
55
src/features/opportunities/crm-service-requests.js
Normal file
55
src/features/opportunities/crm-service-requests.js
Normal file
@@ -0,0 +1,55 @@
|
||||
function createOpportunityCrmServiceRequests({ requestCrmServiceRequest, isCurrentPeriodRequest, environment }) {
|
||||
let status = "idle";
|
||||
let byOpportunity = new Map();
|
||||
let pending = null;
|
||||
let generation = 0;
|
||||
function reset() { generation += 1; pending = null; status = "idle"; byOpportunity = new Map(); }
|
||||
function load(token, version) {
|
||||
if (!isCurrentPeriodRequest(version)) return;
|
||||
if (!pending) pending = run(token, version, generation);
|
||||
return pending;
|
||||
}
|
||||
async function run(token, version, currentGeneration) {
|
||||
const isActive = () => currentGeneration === generation;
|
||||
status = "loading";
|
||||
try {
|
||||
const items = await requestCrmServiceRequest(token, version, isActive);
|
||||
if (!items || !isActive()) return;
|
||||
const grouped = new Map();
|
||||
for (const item of items) {
|
||||
const number = item?.OpportunityName_Id_c?.OptyNumber;
|
||||
if (!number) continue;
|
||||
const key = String(number);
|
||||
if (!grouped.has(key)) grouped.set(key, []);
|
||||
grouped.get(key).push(item);
|
||||
}
|
||||
byOpportunity = grouped;
|
||||
status = "ready";
|
||||
} catch {
|
||||
if (isActive()) status = "error";
|
||||
}
|
||||
}
|
||||
function createBadge(optyNumber) {
|
||||
if (!optyNumber || status === "idle" || status === "loading") return null;
|
||||
const items = byOpportunity.get(String(optyNumber)) || [];
|
||||
const badge = environment.document.createElement("span");
|
||||
badge.className = "opportunities-extension-crm-sr-badge";
|
||||
badge.setAttribute("data-state", status === "error" ? "error" : items.length ? "matched" : "missing");
|
||||
badge.textContent = status === "error" ? "SR ?" : items.length ? "\u2705 SR" : "\u26d4 SR";
|
||||
const display = value => value === null || value === undefined || value === "" ? "N/D" : String(value);
|
||||
badge.title = status === "error" ? "Unable to check CRM service requests. See Debug Requests."
|
||||
: !items.length ? "No matching CRM service request with status SVC_PENDING_DELIVERY in your SR team."
|
||||
: items.map(item => [
|
||||
`SR Number: ${display(item.SrNumber)}`, `Title: ${display(item.Title)}`,
|
||||
`Opportunity: ${display(item.OpportunityName_Id_c?.OptyNumber)}`,
|
||||
`Reported Hours: ${display(item.ReportedHours_c)}`,
|
||||
`Owner: ${[item.Owner?.PersonFirstName, item.Owner?.PersonLastName].filter(Boolean).join(" ") || "N/D"}`,
|
||||
`Email: ${display(item.Owner?.PrimaryEmail?.EmailAddress)}`,
|
||||
`Do Not Call: ${display(item.DoNotCallFlag)}`, `Do Not Email: ${display(item.DoNotEmailFlag)}`
|
||||
].join("\n")).join("\n\n");
|
||||
badge.setAttribute("aria-label", badge.title);
|
||||
badge.tabIndex = 0;
|
||||
return badge;
|
||||
}
|
||||
return { reset, load, createBadge };
|
||||
}
|
||||
@@ -1,4 +1,23 @@
|
||||
/* Compact filters use available CSS width, including Windows display scaling. */
|
||||
.opportunities-extension-crm-sr-badge {
|
||||
flex: 0 0 auto;
|
||||
padding: 0 6px;
|
||||
border: 1px solid #408254;
|
||||
border-radius: 8px;
|
||||
font-size: 10px;
|
||||
line-height: 18px;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
color: #185b30;
|
||||
background: #e1f1e5;
|
||||
}
|
||||
.opportunities-extension-crm-sr-badge[data-state="missing"] { color: #9f201b; background: #fde8e7; border-color: #c74640; }
|
||||
.opportunities-extension-crm-sr-badge[data-state="error"] { color: #785000; background: #fff0cd; border-color: #987021; }
|
||||
.opportunities-extension-crm-sr-badge:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }
|
||||
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-crm-sr-badge { color: #b8e4c6; background: #214d34; border-color: #6eac82; }
|
||||
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-crm-sr-badge[data-state="missing"] { color: #ffd9d5; background: #542824; border-color: #ef938b; }
|
||||
.opportunities-extension-modal[data-theme="dark"] .opportunities-extension-crm-sr-badge[data-state="error"] { color: #ffe0a3; background: #584219; border-color: #c99b4b; }
|
||||
|
||||
.opportunities-extension-form {
|
||||
display: grid;
|
||||
grid-template-columns: max-content minmax(110px, 1fr) minmax(110px, 1fr) minmax(145px, 180px) max-content max-content;
|
||||
|
||||
@@ -10,6 +10,7 @@ function createOpportunitiesTable({
|
||||
createDetailLink,
|
||||
createProductsCountButton,
|
||||
createProductsRampBadge,
|
||||
createCrmServiceRequestBadge,
|
||||
createProductsDetailRow,
|
||||
createStageBadge,
|
||||
createStatusBadge,
|
||||
@@ -134,6 +135,8 @@ function createOpportunitiesTable({
|
||||
const rampBadge = createProductsRampBadge(productsResult);
|
||||
if (rampBadge) productActions.append(rampBadge);
|
||||
}
|
||||
const srBadge = createCrmServiceRequestBadge(item.OptyNumber);
|
||||
if (srBadge) productActions.append(srBadge);
|
||||
nameCellContent.append(opportunityLink, productActions);
|
||||
cell.append(nameCellContent);
|
||||
} else if (column.key === "optyNumber" && item.OptyNumber) {
|
||||
|
||||
Reference in New Issue
Block a user