Updated the calendar to improve opportunity display and navigation

This commit is contained in:
2026-10-27 03:22:10 -03:00
parent 7606643ff4
commit 1d5b28e95b
51 changed files with 1486 additions and 1237 deletions

View File

@@ -37,13 +37,11 @@ function setup(items = [synthetic()], localStorage) {
getElementById: id => id === app.contracts.OPPORTUNITIES_TABLE_ID ? table : null };
const app = loadApplication("content", { document, Intl, Date, localStorage });
app.currentUser.allowsCrmActivities = () => true;
const summaries = [];
app.aiSummaryContent.requestGenerateAiSummary = number => summaries.push(number);
app.opportunitiesTable.setOpportunitiesTableState({ status: "ready", items });
const rows = () => table.children.find(child => child.tag === "tbody").children;
const cell = (key, index = 0) => rows()[index].children.find(child => child.attributes["data-column"] === key);
const headers = () => table.children.find(child => child.tag === "thead").children[0].children;
return { app, table, rows, cell, headers, summaries };
return { app, table, rows, cell, headers };
}
test("Overview restores independent column and direction preferences across reloads and new data", () => {
@@ -104,7 +102,7 @@ test("ramp months preserve zero and flag missing values beside the product count
[[0], "0"], [[0, "3", null, " 6 ", 3], "0 | 3 | N/A | 6 | 3"]
]) {
h.app.productsContentState.opptyProductsByNumber.set("TEST-1", {
fromCache: true, items: values.map(RampMonths_c => ({ RampMonths_c }))
fromCache: true, items: values.map(RampMonths_c => ({ RampMonths_c, WorkloadName_c: "Synthetic workload" }))
});
h.app.opportunitiesTable.renderOpportunitiesTable();
if (expected === null) assert.equal(badge(), undefined);
@@ -121,12 +119,11 @@ test("ramp months preserve zero and flag missing values beside the product count
}
});
test("products remain expandable with AI summary and a ten-column detail row after sorting", () => {
test("products remain expandable with a ten-column detail row after sorting", () => {
const h = setup([synthetic("B", "Zulu"), synthetic("A", "Alpha")]);
h.app.productsContentState.opptyProductsByNumber.set("B", { items: [{ ProdGroupName: "Cloud", RevnAmount: "10" }] });
h.app.opportunitiesTable.renderOpportunitiesTable();
findClass(h.cell("name"), "opportunities-extension-products-count").click();
assert.deepEqual(h.summaries, ["B"]);
assert.equal(h.rows()[1].className, "opportunities-extension-products-detail-row");
assert.equal(h.rows()[1].children[0].colSpan, 10);
assert.ok(findClass(h.rows()[1], "opportunities-extension-products-table"));
@@ -136,7 +133,6 @@ test("products remain expandable with AI summary and a ten-column detail row aft
assert.equal(h.rows()[2].children[0].colSpan, 10);
findClass(h.cell("name", 1), "opportunities-extension-products-count").click();
assert.equal(h.rows().length, 2);
assert.deepEqual(h.summaries, ["B"], "collapsing does not generate another summary");
});
test("product labels support multiple digits without changing their tooltip", () => {
@@ -149,7 +145,7 @@ test("product labels support multiple digits without changing their tooltip", ()
assert.equal(count.attributes["aria-label"], "12 products for opportunity TEST-1");
});
test("opportunity projection alert is last and aggregates only divergent products", () => {
test("opportunity projection alert follows the ramp and aggregates only divergent products", () => {
const h = setup();
const alert = () => findClass(h.cell("name"), "opportunities-extension-opportunity-projection-alert");
const product = amount => ({ RampMonths_c: 0, workload: { adjustedACR: 1200, consumptionStartDate: "2026-10-01",
@@ -159,17 +155,15 @@ test("opportunity projection alert is last and aggregates only divergent product
h.app.opportunitiesTable.renderOpportunitiesTable();
assert.equal(alert().title, "Consumption forecast differs in 2 products. View products.");
const actions = findClass(h.cell("name"), "opportunities-extension-cell-actions");
assert.equal(actions.children.at(-1), alert());
assert.equal(actions.children[2], alert());
assert.equal(alert().tag, "button");
assert.equal(alert().attributes["aria-expanded"], "false");
alert().click();
assert.equal(h.rows()[1].className, "opportunities-extension-products-detail-row");
assert.equal(alert().attributes["aria-expanded"], "true");
assert.equal(walk(h.rows()[1]).filter(node => node.attributes["aria-haspopup"] === "dialog").length, 2);
assert.deepEqual(h.summaries, ["TEST-1"]);
alert().click();
assert.equal(h.rows()[1].className, "opportunities-extension-products-detail-row", "clicking again never collapses the list");
assert.deepEqual(h.summaries, ["TEST-1"], "already expanded list is not reloaded");
h.app.productsContentState.opptyProductsByNumber.set("TEST-1", { items: [product(100), {}] });
h.app.opportunitiesTable.renderOpportunitiesTable();
assert.equal(alert(), undefined);
@@ -200,7 +194,7 @@ test("CRM SR badge follows Ramp Months and remains visible while product data is
await h.app.opportunityCrmServiceRequests.load("synthetic", h.app.opportunitiesQueryState.periodRequestVersion);
h.app.opportunitiesTable.renderOpportunitiesTable();
assert.equal(findClass(h.cell("name"), "opportunities-extension-crm-sr-badge").textContent, "0h");
h.app.productsContentState.opptyProductsByNumber.set("TEST-1", { items: [{ RampMonths_c: 3 }] });
h.app.productsContentState.opptyProductsByNumber.set("TEST-1", { items: [{ RampMonths_c: 3, WorkloadName_c: "Synthetic workload" }] });
h.app.opportunitiesTable.renderOpportunitiesTable();
const actions = h.cell("name").children[0].children[1];
assert.deepEqual(actions.children.map(child => child.textContent), ["1", "3", "0h", "?"]);
@@ -319,6 +313,35 @@ test("table styles reserve compact widths, prioritize Name/Customer and keep nes
assert.ok(remaining > 0);
assert.ok(remaining / 2 >= 12 + 114, "four customer controls fit after the left inset");
}
const allCss = ["src/content/styles/redwood.css", "src/features/debug/styles.css", "src/features/opportunities/styles.css", "src/features/install-base/styles.css", "src/features/service-requests/styles.css", "src/features/consumption/plan-detail.css", "src/features/ratecard/styles.css", "src/features/opportunities/customer-table-search.css", "src/features/opportunities/calendar.css", "src/features/manage-time/styles.css", "src/features/products/ramp-projection.css", "src/features/org-chart/styles.css", "src/content/styles/tables.css"].map(read).join("\n");
const allCss = ["src/content/styles/redwood.css", "src/features/debug/styles.css", "src/features/opportunities/styles.css", "src/features/install-base/styles.css", "src/features/service-requests/styles.css", "src/features/consumption/plan-detail.css", "src/features/ratecard/styles.css", "src/features/opportunities/customer-table-search.css", "src/features/opportunities/calendar.css", "src/features/manage-time/styles.css", "src/features/products/ramp-projection.css", "src/features/org-chart/styles.css", "src/content/styles/tables.css", "src/content/styles/dialogs.css"].map(read).join("\n");
assert.ok(composeScript("content").includes(`const redwoodStyles = ${JSON.stringify(allCss)};`));
});
test("Booking replaces ramp only when every product has no Workload name", () => {
const h = setup();
for (const value of [undefined, null, "", " ", "-"]) {
const badge = h.app.productsDetails.createProductsRampBadge({items:[{WorkloadName_c:value},{WorkloadName_c:" - "}]}, {});
assert.equal(badge.textContent,"Booking");
assert.equal(badge.tag,"span");
assert.equal(badge.attributes["aria-haspopup"],undefined);
}
const mixed = h.app.productsDetails.createProductsRampBadge({items:[{WorkloadName_c:"-",RampMonths_c:2},{WorkloadName_c:"Migration",RampMonths_c:3}]},{});
assert.equal(mixed.textContent,"2 | 3");
assert.equal(mixed.tag,"button");
});
test("Booking and Workload filters use product names and compose with shared opportunity filtering", () => {
const h = setup();
const selected = h.app.opportunitiesFilterState.selectedProductTypes;
const result = { items: [{ WorkloadName_c: "-" }, { WorkloadName_c: "" }] };
h.app.productsContentState.opptyProductsByNumber.set("TEST-1", result);
selected.delete("Workload");
assert.equal(h.app.opportunitiesFilters.getSortedOpportunities().length, 1);
selected.clear(); selected.add("Workload");
assert.equal(h.app.opportunitiesFilters.getSortedOpportunities().length, 0);
result.items[1].WorkloadName_c = "Migration";
assert.equal(h.app.opportunitiesFilters.getSortedOpportunities().length, 1);
selected.clear();
assert.equal(h.app.opportunitiesFilters.getSortedOpportunities().length, 0);
selected.add("Booking"); selected.add("Workload");
assert.equal(h.app.opportunitiesFilters.getSortedOpportunities().length, 1);
});