Table sorting preferences will be saved and reused based on the user’s selections
This commit is contained in:
@@ -60,7 +60,7 @@ function createOpportunityCalendar({ environment, loadLibrary, getState, getItem
|
||||
const count = dayCounts.get(key) || 0;
|
||||
badge.hidden = count === 0;
|
||||
badge.textContent = String(count);
|
||||
badge.title = `${count} oportunidade${count === 1 ? "" : "s"}`;
|
||||
badge.title = `${count} ${count === 1 ? "opportunity" : "opportunities"}`;
|
||||
badge.setAttribute("aria-label", badge.title);
|
||||
badge.setAttribute("data-empty", String(count === 0));
|
||||
}
|
||||
@@ -137,7 +137,7 @@ function createOpportunityCalendar({ environment, loadLibrary, getState, getItem
|
||||
if (copy) identity.append(copy);
|
||||
const summary = node("dl", "opportunities-extension-action-form-context");
|
||||
for (const [label, value] of [["Customer", item.CustomerAccount?.PartyUniqueName], ["Owner", getOwnerName(item)],
|
||||
["Close Date", closeDate(item.EffectiveDate) ? formatDate(item.EffectiveDate) : "N/D"], ["Amount", formatRevenue(item)]]) {
|
||||
["Close Date", closeDate(item.EffectiveDate) ? formatDate(item.EffectiveDate) : "N/A"], ["Amount", formatRevenue(item)]]) {
|
||||
summary.append(node("dt", "", label), node("dd", "", String(value || "-")));
|
||||
}
|
||||
const badges = node("div", "opportunities-calendar-detail-badges");
|
||||
@@ -154,13 +154,13 @@ function createOpportunityCalendar({ environment, loadLibrary, getState, getItem
|
||||
for (const event of events.values()) {
|
||||
const item = event.extendedProps.item;
|
||||
const button = node("button", "opportunities-calendar-day-item"); button.type = "button";
|
||||
const summary = `${item.OptyNumber || "-"} | ${item.ForecastGroup_c || "N/D"} | ${formatRevenue(item)}`;
|
||||
const summary = `${item.OptyNumber || "-"} | ${item.ForecastGroup_c || "N/A"} | ${formatRevenue(item)}`;
|
||||
button.append(node("strong", "", event.title), node("span", "", summary));
|
||||
button.title = `${event.title}\n${summary}`;
|
||||
button.addEventListener("click", () => showDetails(item, trigger));
|
||||
controls.push(button); content.append(button);
|
||||
}
|
||||
showDialog(`${formatDate(date.toISOString().slice(0, 10))} | ${events.size} oportunidades`, content, controls, trigger);
|
||||
showDialog(`${formatDate(date.toISOString().slice(0, 10))} | ${events.size} ${events.size === 1 ? "opportunity" : "opportunities"}`, content, controls, trigger);
|
||||
// A truthy non-string result prevents FullCalendar from opening its measured popover.
|
||||
return true;
|
||||
}
|
||||
@@ -173,14 +173,14 @@ function createOpportunityCalendar({ environment, loadLibrary, getState, getItem
|
||||
closeDetails(false);
|
||||
const workspace = node("section", "opportunities-extension-views");
|
||||
const tablist = node("div", "opportunities-extension-view-tabs");
|
||||
tablist.setAttribute("role", "tablist"); tablist.setAttribute("aria-label", "Visualiza\u00e7\u00e3o das oportunidades");
|
||||
tablist.setAttribute("role", "tablist"); tablist.setAttribute("aria-label", "Opportunity views");
|
||||
const tabs = {};
|
||||
const surface = node("section", "opportunities-extension-calendar");
|
||||
const message = node("p", "opportunities-calendar-message"); message.setAttribute("role", "status");
|
||||
const calendarHost = node("div", "opportunities-calendar-grid");
|
||||
const undated = node("details", "opportunities-calendar-undated");
|
||||
surface.append(message, calendarHost, undated);
|
||||
for (const [key, label, panel] of [["overview", "Vis\u00e3o geral", tableSurface], ["calendar", "Calend\u00e1rio", surface]]) {
|
||||
for (const [key, label, panel] of [["overview", "Overview", tableSurface], ["calendar", "Calendar", surface]]) {
|
||||
const tab = node("button", ""); tab.type = "button"; tab.id = `opportunities-view-${key}-tab`;
|
||||
tab.append(createViewIcon(key), node("span", "", label));
|
||||
panel.id = `opportunities-view-${key}`; panel.setAttribute("role", "tabpanel"); panel.setAttribute("aria-labelledby", tab.id);
|
||||
@@ -234,12 +234,12 @@ function createOpportunityCalendar({ environment, loadLibrary, getState, getItem
|
||||
calendar = new library.Calendar(calendarHost, {
|
||||
initialView: "dayGridMonth", initialDate: anchor, height: "100%", expandRows: true, fixedWeekCount: false,
|
||||
datesSet: scheduleLayout,
|
||||
locale: "pt-br", firstDay: 0, editable: false, selectable: false, eventInteractive: true,
|
||||
locale: "en", firstDay: 0, editable: false, selectable: false, eventInteractive: true,
|
||||
headerToolbar: { left: "prev,next today", center: "title", right: "dayGridMonth,dayGridWeek" },
|
||||
buttonText: { today: "Hoje", month: "M\u00eas", week: "Semana" },
|
||||
buttonHints: { prev: "Anterior", next: "Pr\u00f3ximo", today: "Hoje", dayGridMonth: "M\u00eas", dayGridWeek: "Semana" },
|
||||
buttonText: { today: "Today", month: "Month", week: "Week" },
|
||||
buttonHints: { prev: "Previous", next: "Next", today: "Today", dayGridMonth: "Month", dayGridWeek: "Week" },
|
||||
dayMaxEvents: 2, moreLinkText: count => `... +${count}`, displayEventTime: false,
|
||||
moreLinkHint: count => `Ver mais ${count} oportunidades`,
|
||||
moreLinkHint: count => `View ${count} more ${count === 1 ? "opportunity" : "opportunities"}`,
|
||||
moreLinkClick: showDayOpportunities,
|
||||
weekNumbers: true,
|
||||
weekNumberContent: ({ date }) => ({ domNodes: [node("span", "opportunities-calendar-fiscal-week", fiscalWeekLabel(date))] }),
|
||||
@@ -255,7 +255,7 @@ function createOpportunityCalendar({ environment, loadLibrary, getState, getItem
|
||||
eventContent: ({ event }) => {
|
||||
const item = event.extendedProps.item;
|
||||
const card = node("div", "opportunities-calendar-event-content");
|
||||
const summary = node("span", "", `${item.OptyNumber || "-"} | ${item.ForecastGroup_c || "N/D"} | ${formatRevenue(item)}`);
|
||||
const summary = node("span", "", `${item.OptyNumber || "-"} | ${item.ForecastGroup_c || "N/A"} | ${formatRevenue(item)}`);
|
||||
summary.title = summary.textContent;
|
||||
card.append(node("strong", "", event.title), summary);
|
||||
return { domNodes: [card] };
|
||||
@@ -280,7 +280,7 @@ function createOpportunityCalendar({ environment, loadLibrary, getState, getItem
|
||||
const missing = items.filter(item => !closeDate(item.EffectiveDate));
|
||||
undated.hidden = !missing.length; undated.textContent = "";
|
||||
if (missing.length) {
|
||||
undated.append(node("summary", "", `Sem Close Date v\u00e1lido (${missing.length})`));
|
||||
undated.append(node("summary", "", `No valid Close Date (${missing.length})`));
|
||||
for (const item of missing) {
|
||||
const button = node("button", "", `${item.OptyNumber || "-"} | ${item.Name || "-"}`); button.type = "button";
|
||||
button.addEventListener("click", () => showDetails(item, button)); undated.append(button);
|
||||
|
||||
@@ -72,15 +72,15 @@ function createOpportunityCrmServiceRequests({ requestCrmServiceRequest, isCurre
|
||||
const summary = getSummary(optyNumber);
|
||||
const reportedHours = summary.reportedHours !== null
|
||||
? new Intl.NumberFormat("en-US", { useGrouping: false, maximumFractionDigits: 10 }).format(summary.reportedHours)
|
||||
: "N/D";
|
||||
setBadgeContent(badge, "sr", status === "error" ? "?" : items.length ? `${reportedHours}${reportedHours === "N/D" ? "" : "h"}` : "0h");
|
||||
const display = value => value === null || value === undefined || value === "" ? "N/D" : String(value);
|
||||
: "N/A";
|
||||
setBadgeContent(badge, "sr", status === "error" ? "?" : items.length ? `${reportedHours}${reportedHours === "N/A" ? "" : "h"}` : "0h");
|
||||
const display = value => value === null || value === undefined || value === "" ? "N/A" : String(value);
|
||||
const descriptions = items.map(item => [
|
||||
`Title: ${display(item.Title)}`,
|
||||
`Opportunity: ${display(item.OpportunityName_Id_c?.OptyNumber)}`,
|
||||
`Reported Hours: ${display(item.ReportedHours_c)}`,
|
||||
`Customer Deadline: ${display(formatDate(item.CustomerDeadline_c))}`,
|
||||
`Owner: ${[item.Owner?.PersonFirstName, item.Owner?.PersonLastName].filter(Boolean).join(" ") || "N/D"}`,
|
||||
`Owner: ${[item.Owner?.PersonFirstName, item.Owner?.PersonLastName].filter(Boolean).join(" ") || "N/A"}`,
|
||||
`Email: ${display(item.Owner?.PrimaryEmail?.EmailAddress)}`,
|
||||
`Do Not Call: ${display(item.DoNotCallFlag)}`, `Do Not Email: ${display(item.DoNotEmailFlag)}`
|
||||
].join("\n"));
|
||||
|
||||
@@ -81,7 +81,7 @@ function createCrmSrPopover({ MODAL_ID, createCopyButton, createCloseIcon, reque
|
||||
heading.className = "opportunities-extension-sr-number";
|
||||
const number = item.SrNumber;
|
||||
const link = document.createElement(number ? "a" : "span");
|
||||
link.textContent = number ? String(number) : "N/D";
|
||||
link.textContent = number ? String(number) : "N/A";
|
||||
if (number) {
|
||||
link.href = `https://eeho.fa.us2.oraclecloud.com/fscmUI/redwood/service/ec/container/sr/edit?srNumber=${encodeURIComponent(String(number))}`;
|
||||
link.target = "_blank";
|
||||
@@ -137,7 +137,7 @@ function createCrmSrPopover({ MODAL_ID, createCopyButton, createCloseIcon, reque
|
||||
const label = document.createElement("span");
|
||||
label.textContent = "Internal SR Number:";
|
||||
const link = document.createElement(record.SrNumber ? "a" : "span");
|
||||
link.textContent = record.SrNumber ? String(record.SrNumber) : "N/D";
|
||||
link.textContent = record.SrNumber ? String(record.SrNumber) : "N/A";
|
||||
if (record.SrNumber) {
|
||||
link.href = `https://eeho.fa.us2.oraclecloud.com/fscmUI/redwood/service/ec/container/sr/edit?srNumber=${encodeURIComponent(String(record.SrNumber))}&srId=${encodeURIComponent(String(record.SrId ?? ""))}`;
|
||||
link.target = "_blank";
|
||||
@@ -149,7 +149,7 @@ function createCrmSrPopover({ MODAL_ID, createCopyButton, createCloseIcon, reque
|
||||
body.className = "opportunities-extension-sr-details";
|
||||
body.textContent = ["Title", "OpportunityName_c", "StatusCd", "ServiceNew_c", "AccountPartyName",
|
||||
"LastUpdateDate", "SvcHealth_c", "AssigneePersonName", "OpenDate"].map(key =>
|
||||
`${key === "ServiceNew_c" ? "Service Queue" : key}: ${record[key] == null || record[key] === "" ? "N/D" : String(key === "LastUpdateDate" || key === "OpenDate" ? formatDate(record[key]) : record[key])}`).join("\n");
|
||||
`${key === "ServiceNew_c" ? "Service Queue" : key}: ${record[key] == null || record[key] === "" ? "N/A" : String(key === "LastUpdateDate" || key === "OpenDate" ? formatDate(record[key]) : record[key])}`).join("\n");
|
||||
child.append(row, body);
|
||||
children.append(child);
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ function createOpportunitiesDashboard({
|
||||
totalValue,
|
||||
"TOTAL",
|
||||
items.length,
|
||||
(amount) => `${Math.round(amount).toLocaleString()}/${totalCount.toLocaleString()}`
|
||||
(amount) => `${Math.round(amount).toLocaleString("en-US")}/${totalCount.toLocaleString("en-US")}`
|
||||
);
|
||||
totalCard.append(totalLabel, totalValue);
|
||||
dashboard.append(totalCard);
|
||||
@@ -81,7 +81,7 @@ function createOpportunitiesDashboard({
|
||||
}
|
||||
}
|
||||
|
||||
return (amount) => amount.toLocaleString(undefined, {
|
||||
return (amount) => amount.toLocaleString("en-US", {
|
||||
maximumFractionDigits: 0
|
||||
});
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ function createOpportunitiesFormatters({
|
||||
}
|
||||
|
||||
if (!currency) {
|
||||
return number.toLocaleString();
|
||||
return number.toLocaleString("en-US");
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -153,7 +153,7 @@ function createOpportunitiesFormatters({
|
||||
currency
|
||||
}).format(number);
|
||||
} catch (error) {
|
||||
return `${currency} ${number.toLocaleString()}`;
|
||||
return `${currency} ${number.toLocaleString("en-US")}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ function createInternalSrForm({ MODAL_ID, searchableSelect, formatDate, getOppor
|
||||
const context = node("dl", "opportunities-extension-action-form-context");
|
||||
const opportunity = getOpportunity(parent.OpportunityName_Id_c?.OptyNumber);
|
||||
for (const [label, value] of [["SR Number", parent.SrNumber], ["Opportunity", opportunity?.Name], ["Title", parent.Title]]) {
|
||||
context.append(node("dt", "", label), node("dd", "", value == null || value === "" ? "N/D" : String(value)));
|
||||
context.append(node("dt", "", label), node("dd", "", value == null || value === "" ? "N/A" : String(value)));
|
||||
}
|
||||
heading.append(title, context);
|
||||
header.append(heading);
|
||||
@@ -60,7 +60,7 @@ function createInternalSrForm({ MODAL_ID, searchableSelect, formatDate, getOppor
|
||||
const titleInput = field("title", "Title", "text", true).input;
|
||||
const deadline = field("customerDeadline", "Customer Deadline", "text").input;
|
||||
deadline.readOnly = true;
|
||||
deadline.value = formatDate(parent.CustomerDeadline_c) || "N/D";
|
||||
deadline.value = formatDate(parent.CustomerDeadline_c) || "N/A";
|
||||
field("internalDeadline", "Internal Deadline", "date");
|
||||
const platform = field("platform", "Platform", "select", true);
|
||||
field("description", "Description", "textarea", true);
|
||||
|
||||
@@ -412,6 +412,14 @@
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
.opportunities-extension-table-skeleton-row { height: 76px; }
|
||||
.opportunities-extension-table-skeleton-text { width: 85%; max-width: 100%; }
|
||||
.opportunities-extension-table-skeleton-row:nth-of-type(even) .opportunities-extension-table-skeleton-text { width: 70%; }
|
||||
.opportunities-extension-table-skeleton-badge { flex: 0 1 30px; width: 30px; min-width: 0; height: 16px; }
|
||||
.opportunities-extension-table-skeleton-row > td[data-column="customer"] .opportunities-extension-table-skeleton-badge { flex-basis: 18px; width: 18px; }
|
||||
.opportunities-extension-table-skeleton-row > td[data-column="status"] .opportunities-extension-table-skeleton-text,
|
||||
.opportunities-extension-table-skeleton-row > td[data-column="stage"] .opportunities-extension-table-skeleton-text { height: 20px; max-width: 66px; border-radius: 8px; }
|
||||
|
||||
/* Fixed table layout shares leftover width only between the two auto columns. */
|
||||
.opportunities-extension-table > thead > tr > th[data-column="name"] { width: auto; }
|
||||
.opportunities-extension-table > thead > tr > th[data-column="customer"] { width: auto; }
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
function createOpportunitiesTable({
|
||||
tableSorting,
|
||||
ACCOUNT_DETAIL_URL,
|
||||
OPPORTUNITIES_COLUMNS,
|
||||
OPPORTUNITIES_TABLE_ID,
|
||||
@@ -41,8 +42,8 @@ function createOpportunitiesTable({
|
||||
opportunitiesTableState.opportunitiesTableState = {
|
||||
...opportunitiesTableState.opportunitiesTableState,
|
||||
...nextState,
|
||||
sortKey: nextState.items ? "" : opportunitiesTableState.opportunitiesTableState.sortKey,
|
||||
sortDirection: nextState.items ? "ascending" : opportunitiesTableState.opportunitiesTableState.sortDirection
|
||||
sortKey: tableSorting.read("overview", OPPORTUNITIES_COLUMNS).key,
|
||||
sortDirection: tableSorting.read("overview", OPPORTUNITIES_COLUMNS).direction
|
||||
};
|
||||
renderOpportunitiesTable();
|
||||
renderCustomerFilter();
|
||||
@@ -64,7 +65,7 @@ function createOpportunitiesTable({
|
||||
|
||||
const caption = document.createElement("caption");
|
||||
caption.className = "opportunities-extension-visually-hidden";
|
||||
caption.textContent = "Opportunities";
|
||||
caption.textContent = opportunitiesTableState.opportunitiesTableState.status === "loading" ? "Loading opportunities..." : "Opportunities";
|
||||
|
||||
const tableHead = document.createElement("thead");
|
||||
const headerRow = document.createElement("tr");
|
||||
@@ -85,6 +86,7 @@ function createOpportunitiesTable({
|
||||
|
||||
const sortButton = document.createElement("button");
|
||||
sortButton.type = "button";
|
||||
sortButton.disabled = opportunitiesTableState.opportunitiesTableState.status === "loading";
|
||||
sortButton.className = "opportunities-extension-sort-button";
|
||||
sortButton.setAttribute("data-sort-direction", isSorted ? opportunitiesTableState.opportunitiesTableState.sortDirection : "none");
|
||||
sortButton.setAttribute("aria-label", `Sort by ${column.label}${isSorted ? `, ${opportunitiesTableState.opportunitiesTableState.sortDirection}` : ""}`);
|
||||
@@ -106,7 +108,32 @@ function createOpportunitiesTable({
|
||||
renderStageDashboard(items, opportunitiesTableState.opportunitiesTableState.items.length);
|
||||
renderCalendar?.(items, opportunitiesTableState.opportunitiesTableState);
|
||||
|
||||
if (opportunitiesTableState.opportunitiesTableState.status === "loading" || opportunitiesTableState.opportunitiesTableState.status === "error" || opportunitiesTableState.opportunitiesTableState.status === "empty" || (opportunitiesTableState.opportunitiesTableState.status === "ready" && items.length === 0)) {
|
||||
if (opportunitiesTableState.opportunitiesTableState.status === "loading") {
|
||||
for (let index = 0; index < 8; index++) {
|
||||
const row = document.createElement("tr");
|
||||
row.className = "opportunities-extension-table-skeleton-row";
|
||||
row.setAttribute("aria-hidden", "true");
|
||||
OPPORTUNITIES_COLUMNS.forEach(column => {
|
||||
const cell = document.createElement("td");
|
||||
cell.setAttribute("data-column", column.key);
|
||||
const bar = document.createElement("span");
|
||||
bar.className = "opportunities-extension-account-plans-skeleton-bar opportunities-extension-table-skeleton-text";
|
||||
cell.append(bar);
|
||||
if (column.key === "name" || column.key === "customer") {
|
||||
const actions = document.createElement("span");
|
||||
actions.className = "opportunities-extension-cell-actions";
|
||||
for (let action = 0; action < (column.key === "name" ? 4 : 3); action++) {
|
||||
const badge = document.createElement("span");
|
||||
badge.className = "opportunities-extension-account-plans-skeleton-bar opportunities-extension-table-skeleton-badge";
|
||||
actions.append(badge);
|
||||
}
|
||||
cell.append(actions);
|
||||
}
|
||||
row.append(cell);
|
||||
});
|
||||
tableBody.append(row);
|
||||
}
|
||||
} else if (opportunitiesTableState.opportunitiesTableState.status === "error" || opportunitiesTableState.opportunitiesTableState.status === "empty" || (opportunitiesTableState.opportunitiesTableState.status === "ready" && items.length === 0)) {
|
||||
const row = document.createElement("tr");
|
||||
const cell = document.createElement("td");
|
||||
cell.className = "opportunities-extension-table-status";
|
||||
@@ -193,11 +220,13 @@ function createOpportunitiesTable({
|
||||
}
|
||||
|
||||
function sortOpportunitiesBy(key) {
|
||||
if (!OPPORTUNITIES_COLUMNS.some(column => column.key === key && column.sortable !== false)) return;
|
||||
const isSameColumn = opportunitiesTableState.opportunitiesTableState.sortKey === key;
|
||||
opportunitiesTableState.opportunitiesTableState.sortKey = key;
|
||||
opportunitiesTableState.opportunitiesTableState.sortDirection = isSameColumn && opportunitiesTableState.opportunitiesTableState.sortDirection === "ascending"
|
||||
? "descending"
|
||||
: "ascending";
|
||||
tableSorting.save("overview", { key, direction: opportunitiesTableState.opportunitiesTableState.sortDirection });
|
||||
renderOpportunitiesTable();
|
||||
}
|
||||
|
||||
|
||||
@@ -155,9 +155,9 @@ function createOpportunityWorkloadActions({ authSessionState, allowsCrmActivitie
|
||||
badge.setAttribute("data-state", state);
|
||||
setBadgeContent(badge, "actions", state === "matched" || state === "missing" ? actions.length : state === "loading" ? "..." : "?");
|
||||
badge.title = actions.length ? actions.map(action => [
|
||||
`Action: ${action.name ?? "N/D"}`, `ID: ${action.id ?? "N/D"}`, `Status: ${action.status ?? "N/D"}`,
|
||||
`Owner: ${action.owner || "N/D"}`, `Email: ${action.ownerEmail || "N/D"}`, `Workload: ${action.workloadName || "N/D"}`,
|
||||
`Description: ${action.workloadDescription || "N/D"}`, `Start: ${action.startDate || "N/D"}`, `End: ${action.endDate || "N/D"}`
|
||||
`Action: ${action.name ?? "N/A"}`, `ID: ${action.id ?? "N/A"}`, `Status: ${action.status ?? "N/A"}`,
|
||||
`Owner: ${action.owner || "N/A"}`, `Email: ${action.ownerEmail || "N/A"}`, `Workload: ${action.workloadName || "N/A"}`,
|
||||
`Description: ${action.workloadDescription || "N/A"}`, `Start: ${action.startDate || "N/A"}`, `End: ${action.endDate || "N/A"}`
|
||||
].join("\n")).join("\n\n") : state === "missing" ? "No actions found for this opportunity's products."
|
||||
: state === "loading" ? "Loading workload actions..." : "Unable to confirm workload actions. Check product RevnId and Debug Requests.";
|
||||
if (records.some(record => record?.hasNext)) badge.title += "\nAdditional actions exist; pagination is not enabled.";
|
||||
|
||||
Reference in New Issue
Block a user