732 lines
27 KiB
JavaScript
732 lines
27 KiB
JavaScript
(() => {
|
||
function create(dependencies) {
|
||
const {
|
||
appState,
|
||
buildWorkloadStartCalendar,
|
||
hasRampForecastMismatch,
|
||
renderOpportunityLink,
|
||
renderWorkloadLink,
|
||
escapeHtml,
|
||
cleanString,
|
||
formatWholeNumber,
|
||
formatCurrency,
|
||
formatPercent,
|
||
formatProgressPercent,
|
||
formatDate,
|
||
} = dependencies;
|
||
|
||
function renderMainContent() {
|
||
if (appState.status === "loading") {
|
||
return `
|
||
<main class="arch-panel-extension-main-shell">
|
||
<section class="arch-panel-extension-state-card">
|
||
<p class="arch-panel-extension-state-eyebrow">Loading data</p>
|
||
<h2 class="arch-panel-extension-state-title">Building the workload dataset</h2>
|
||
<p class="arch-panel-extension-state-copy">${escapeHtml(appState.loadingMessage)}</p>
|
||
<div
|
||
class="arch-panel-extension-loading-progress"
|
||
role="progressbar"
|
||
aria-valuemin="0"
|
||
aria-valuemax="100"
|
||
aria-valuenow="${Math.round(appState.loadingProgress || 0)}"
|
||
>
|
||
<div class="arch-panel-extension-loading-progress-meta">
|
||
<span>Overall progress</span>
|
||
</div>
|
||
<div class="arch-panel-extension-loading-progress-track">
|
||
<div
|
||
class="arch-panel-extension-loading-progress-fill"
|
||
style="width: 0%;"
|
||
></div>
|
||
<span class="arch-panel-extension-loading-progress-value">
|
||
${formatProgressPercent(appState.loadingProgress || 0)}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</main>
|
||
`;
|
||
}
|
||
|
||
if (appState.status === "error") {
|
||
return `
|
||
<main class="arch-panel-extension-main-shell">
|
||
<section class="arch-panel-extension-state-card is-error">
|
||
<p class="arch-panel-extension-state-eyebrow">Request failed</p>
|
||
<h2 class="arch-panel-extension-state-title">Unable to assemble the dashboard</h2>
|
||
<p class="arch-panel-extension-state-copy">${escapeHtml(appState.errorMessage)}</p>
|
||
<div class="arch-panel-extension-state-actions">
|
||
<button type="button" class="arch-panel-extension-utility-button" data-action="refresh">
|
||
Retry
|
||
</button>
|
||
</div>
|
||
</section>
|
||
</main>
|
||
`;
|
||
}
|
||
|
||
if (!appState.dataset) {
|
||
return `
|
||
<main class="arch-panel-extension-main-shell">
|
||
<section class="arch-panel-extension-state-card">
|
||
<p class="arch-panel-extension-state-eyebrow">Ready</p>
|
||
<h2 class="arch-panel-extension-state-title">Start the live data extraction</h2>
|
||
<p class="arch-panel-extension-state-copy">
|
||
No cached snapshot is available yet. Use Refresh data to fetch the current user, customers, workloads, actions and service requests.
|
||
</p>
|
||
<div class="arch-panel-extension-state-actions">
|
||
<button type="button" class="arch-panel-extension-utility-button" data-action="refresh">
|
||
Refresh data
|
||
</button>
|
||
</div>
|
||
</section>
|
||
</main>
|
||
`;
|
||
}
|
||
|
||
const { user, summary } = appState.dataset;
|
||
|
||
return `
|
||
<aside class="arch-panel-extension-sidebar${appState.sidebarCollapsed ? " is-collapsed" : ""}" aria-label="Dataset summary">
|
||
<button
|
||
type="button"
|
||
class="arch-panel-extension-sidebar-toggle"
|
||
data-action="toggle-sidebar"
|
||
aria-label="${appState.sidebarCollapsed ? "Show sidebar" : "Hide sidebar"}"
|
||
aria-expanded="${appState.sidebarCollapsed ? "false" : "true"}"
|
||
title="${appState.sidebarCollapsed ? "Show sidebar" : "Hide sidebar"}"
|
||
>
|
||
<svg viewBox="0 0 20 20" aria-hidden="true" focusable="false">
|
||
${
|
||
appState.sidebarCollapsed
|
||
? `<path d="M7.22 15.78a.75.75 0 0 1 0-1.06L11.94 10 7.22 5.28a.75.75 0 0 1 1.06-1.06l5.25 5.25a.75.75 0 0 1 0 1.06l-5.25 5.25a.75.75 0 0 1-1.06 0Z"></path>`
|
||
: `<path d="M12.78 4.22a.75.75 0 0 1 0 1.06L8.06 10l4.72 4.72a.75.75 0 1 1-1.06 1.06l-5.25-5.25a.75.75 0 0 1 0-1.06l5.25-5.25a.75.75 0 0 1 1.06 0Z"></path>`
|
||
}
|
||
</svg>
|
||
</button>
|
||
|
||
<div class="arch-panel-extension-sidebar-group">
|
||
<p class="arch-panel-extension-sidebar-label">Current user</p>
|
||
${renderCurrentUserSidebar(user)}
|
||
</div>
|
||
|
||
<div class="arch-panel-extension-sidebar-group">
|
||
<p class="arch-panel-extension-sidebar-label">Exports</p>
|
||
<button type="button" class="arch-panel-extension-sidebar-button" data-action="download-json">
|
||
Download JSON
|
||
</button>
|
||
<button type="button" class="arch-panel-extension-sidebar-button" data-action="download-html">
|
||
Download HTML
|
||
</button>
|
||
<button type="button" class="arch-panel-extension-sidebar-button" data-action="refresh">
|
||
Refresh data
|
||
</button>
|
||
</div>
|
||
|
||
<div class="arch-panel-extension-sidebar-group">
|
||
<p class="arch-panel-extension-sidebar-label">Coverage</p>
|
||
<div class="arch-panel-extension-sidebar-data">
|
||
<span>Customers: ${formatWholeNumber(summary.totalCustomers)}</span>
|
||
<span>Workloads: ${formatWholeNumber(summary.totalWorkloads)}</span>
|
||
<span>Pending SRs: ${formatWholeNumber(summary.pendingServiceRequests.length)}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="arch-panel-extension-sidebar-group">
|
||
<p class="arch-panel-extension-sidebar-label">Last refresh</p>
|
||
<div class="arch-panel-extension-sidebar-data">
|
||
<strong>${escapeHtml(summary.generatedAtLabel || "-")}</strong>
|
||
<span>Updated by Refresh data</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="arch-panel-extension-sidebar-group">
|
||
<p class="arch-panel-extension-sidebar-label">Extension version</p>
|
||
<div class="arch-panel-extension-sidebar-data">
|
||
<span>v${escapeHtml(getExtensionVersion())}</span>
|
||
</div>
|
||
</div>
|
||
</aside>
|
||
|
||
<main class="arch-panel-extension-main-shell">
|
||
<section class="arch-panel-extension-summary-row" aria-label="Overview metrics">
|
||
${renderMetricCard("Total clients", formatWholeNumber(summary.totalCustomers), "Loaded from listSummary")}
|
||
${renderMetricCard("Total workloads", formatWholeNumber(summary.totalWorkloads), "Excluding Won workloads")}
|
||
${renderMetricCard("Total (ACR)", formatCurrency(summary.totalEligibleAcr), "Across eligible workloads")}
|
||
${renderMetricCard("Pending SRs", formatWholeNumber(summary.pendingServiceRequests.length), "SVC_PENDING_DELIVERY")}
|
||
</section>
|
||
|
||
<section class="arch-panel-extension-indicator-grid" aria-label="Opportunity indicators">
|
||
${summary.stageIndicators.map((indicator) => renderStageIndicator(indicator)).join("")}
|
||
</section>
|
||
|
||
<section class="arch-panel-extension-status-grid" aria-label="SR and action status indicators">
|
||
${renderBooleanIndicator(
|
||
"Status de SRs",
|
||
"Workloads com SR",
|
||
summary.srStatus.trueCount,
|
||
"Workloads sem SR",
|
||
summary.srStatus.falseCount,
|
||
"sr"
|
||
)}
|
||
${renderBooleanIndicator(
|
||
"Status de Consumption Plan/action",
|
||
"Workloads com action",
|
||
summary.actionStatus.trueCount,
|
||
"Workloads sem action",
|
||
summary.actionStatus.falseCount,
|
||
"action"
|
||
)}
|
||
</section>
|
||
|
||
${renderSrHoursAndCalendarSection(summary)}
|
||
|
||
</main>
|
||
`;
|
||
}
|
||
|
||
function renderCurrentUserSidebar(user) {
|
||
const displayName = cleanString(user?.name || user?.userEmail);
|
||
const userEmail = cleanString(user?.userEmail);
|
||
const avatar = cleanString(user?.avatar);
|
||
const initials = getUserInitials(displayName || userEmail);
|
||
|
||
return `
|
||
<div class="arch-panel-extension-current-user-card">
|
||
${
|
||
avatar
|
||
? `<img
|
||
class="arch-panel-extension-current-user-avatar"
|
||
src="${escapeHtml(avatar)}"
|
||
alt="${escapeHtml(displayName ? `${displayName} avatar` : "User avatar")}"
|
||
loading="lazy"
|
||
referrerpolicy="no-referrer"
|
||
>`
|
||
: `<span class="arch-panel-extension-current-user-avatar is-fallback" aria-hidden="true">${escapeHtml(initials)}</span>`
|
||
}
|
||
<div class="arch-panel-extension-sidebar-data">
|
||
<strong>${escapeHtml(displayName || userEmail)}</strong>
|
||
<span>${escapeHtml(userEmail)}</span>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
function getUserInitials(value) {
|
||
const parts = cleanString(value)
|
||
.split(/\s+/)
|
||
.filter(Boolean)
|
||
.slice(0, 2);
|
||
|
||
if (!parts.length) {
|
||
return "U";
|
||
}
|
||
|
||
return parts.map((part) => part.charAt(0).toUpperCase()).join("");
|
||
}
|
||
|
||
function getExtensionVersion() {
|
||
try {
|
||
return chrome.runtime.getManifest()?.version || "0.0.0";
|
||
} catch {
|
||
return "0.0.0";
|
||
}
|
||
}
|
||
|
||
function renderMetricCard(label, value, detail) {
|
||
return `
|
||
<article class="arch-panel-extension-summary-card">
|
||
<p class="arch-panel-extension-summary-label">${escapeHtml(label)}</p>
|
||
<p class="arch-panel-extension-summary-value">${escapeHtml(value)}</p>
|
||
<p class="arch-panel-extension-summary-detail">${escapeHtml(detail)}</p>
|
||
</article>
|
||
`;
|
||
}
|
||
|
||
function renderStageIndicator(indicator) {
|
||
return `
|
||
<button
|
||
type="button"
|
||
class="arch-panel-extension-summary-card arch-panel-extension-summary-card-button"
|
||
data-action="open-detail"
|
||
data-detail-scope="stage"
|
||
data-detail-value="${escapeHtml(indicator.label)}"
|
||
>
|
||
<p class="arch-panel-extension-summary-label">${escapeHtml(indicator.label)}</p>
|
||
<p class="arch-panel-extension-summary-value">${formatWholeNumber(indicator.count)}</p>
|
||
<p class="arch-panel-extension-summary-detail">Total (ACR): ${formatCurrency(indicator.totalAcr)}</p>
|
||
</button>
|
||
`;
|
||
}
|
||
|
||
function renderBooleanIndicator(
|
||
title,
|
||
trueLabel,
|
||
trueCount,
|
||
falseLabel,
|
||
falseCount,
|
||
scope
|
||
) {
|
||
const total = trueCount + falseCount;
|
||
const truePercent = total > 0 ? (trueCount / total) * 100 : 0;
|
||
const falsePercent = total > 0 ? (falseCount / total) * 100 : 0;
|
||
|
||
return `
|
||
<article class="arch-panel-extension-panel">
|
||
<header class="arch-panel-extension-panel-head">
|
||
<div>
|
||
<p class="arch-panel-extension-panel-label">${escapeHtml(title)}</p>
|
||
</div>
|
||
</header>
|
||
<div class="arch-panel-extension-progress">
|
||
<div class="arch-panel-extension-progress-track">
|
||
<span
|
||
class="arch-panel-extension-progress-segment is-success"
|
||
style="width: ${truePercent}%;"
|
||
></span>
|
||
<span
|
||
class="arch-panel-extension-progress-segment is-danger"
|
||
style="width: ${falsePercent}%;"
|
||
></span>
|
||
</div>
|
||
<div class="arch-panel-extension-progress-labels">
|
||
<span>${escapeHtml(trueLabel)}: ${formatPercent(truePercent)}</span>
|
||
<span>${escapeHtml(falseLabel)}: ${formatPercent(falsePercent)}</span>
|
||
</div>
|
||
</div>
|
||
<div class="arch-panel-extension-boolean-grid">
|
||
<button
|
||
type="button"
|
||
class="arch-panel-extension-summary-card arch-panel-extension-summary-card-button"
|
||
data-action="open-detail"
|
||
data-detail-scope="${escapeHtml(scope)}"
|
||
data-detail-value="true"
|
||
>
|
||
<p class="arch-panel-extension-summary-label">${escapeHtml(trueLabel)}</p>
|
||
<p class="arch-panel-extension-summary-value">${formatWholeNumber(trueCount)}</p>
|
||
<p class="arch-panel-extension-summary-detail">${formatPercent(truePercent)}</p>
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="arch-panel-extension-summary-card arch-panel-extension-summary-card-button"
|
||
data-action="open-detail"
|
||
data-detail-scope="${escapeHtml(scope)}"
|
||
data-detail-value="false"
|
||
>
|
||
<p class="arch-panel-extension-summary-label">${escapeHtml(falseLabel)}</p>
|
||
<p class="arch-panel-extension-summary-value">${formatWholeNumber(falseCount)}</p>
|
||
<p class="arch-panel-extension-summary-detail">${formatPercent(falsePercent)}</p>
|
||
</button>
|
||
</div>
|
||
</article>
|
||
`;
|
||
}
|
||
|
||
function renderSrHoursAndCalendarSection(summary) {
|
||
const calendar = buildWorkloadStartCalendar(
|
||
summary.allWorkloads,
|
||
appState.calendarYear,
|
||
appState.calendarMonth,
|
||
appState.calendarView,
|
||
appState.calendarAnchorDateKey
|
||
);
|
||
|
||
return `
|
||
<section class="arch-panel-extension-operations-grid" aria-label="Pending SRs and workload calendar">
|
||
${renderSrHoursIndicatorCard(summary)}
|
||
${renderWorkloadStartCalendar(calendar)}
|
||
</section>
|
||
`;
|
||
}
|
||
|
||
function renderSrHoursIndicatorCard(summary) {
|
||
const zeroToFourCount = summary.srHours.betweenZeroAndFour.length;
|
||
const aboveFourCount = summary.srHours.aboveFour.length;
|
||
const total = zeroToFourCount + aboveFourCount;
|
||
const zeroToFourPercent = total > 0 ? (zeroToFourCount / total) * 100 : 0;
|
||
const aboveFourPercent = total > 0 ? (aboveFourCount / total) * 100 : 0;
|
||
|
||
return `
|
||
<article class="arch-panel-extension-panel">
|
||
<header class="arch-panel-extension-panel-head">
|
||
<div>
|
||
<p class="arch-panel-extension-panel-label">Horas apontadas</p>
|
||
</div>
|
||
</header>
|
||
<div class="arch-panel-extension-progress">
|
||
<div class="arch-panel-extension-progress-track">
|
||
<span
|
||
class="arch-panel-extension-progress-segment is-success"
|
||
style="width: ${zeroToFourPercent}%;"
|
||
></span>
|
||
<span
|
||
class="arch-panel-extension-progress-segment is-danger"
|
||
style="width: ${aboveFourPercent}%;"
|
||
></span>
|
||
</div>
|
||
<div class="arch-panel-extension-progress-labels">
|
||
<span>SRs 0-4h: ${formatPercent(zeroToFourPercent)}</span>
|
||
<span>SRs >4h: ${formatPercent(aboveFourPercent)}</span>
|
||
</div>
|
||
</div>
|
||
<div class="arch-panel-extension-boolean-grid">
|
||
${renderSrHoursIndicator(
|
||
"SRs 0-4h",
|
||
formatPercent(zeroToFourPercent),
|
||
zeroToFourCount,
|
||
"0-4"
|
||
)}
|
||
${renderSrHoursIndicator(
|
||
"SRs >4h",
|
||
formatPercent(aboveFourPercent),
|
||
aboveFourCount,
|
||
"above-4"
|
||
)}
|
||
</div>
|
||
</article>
|
||
`;
|
||
}
|
||
|
||
function renderSrHoursIndicator(label, detail, count, value) {
|
||
return `
|
||
<button
|
||
type="button"
|
||
class="arch-panel-extension-summary-card arch-panel-extension-summary-card-button"
|
||
data-action="open-detail"
|
||
data-detail-scope="sr-hours"
|
||
data-detail-value="${escapeHtml(value)}"
|
||
aria-label="Open ${escapeHtml(label)} service requests"
|
||
>
|
||
<p class="arch-panel-extension-summary-label">${escapeHtml(label)}</p>
|
||
<p class="arch-panel-extension-summary-value">${formatWholeNumber(count)}</p>
|
||
<p class="arch-panel-extension-summary-detail">${escapeHtml(detail)}</p>
|
||
</button>
|
||
`;
|
||
}
|
||
|
||
function renderWorkloadStartCalendar(calendar) {
|
||
return `
|
||
<article class="arch-panel-extension-panel arch-panel-extension-calendar-card">
|
||
<header class="arch-panel-extension-panel-head">
|
||
<div>
|
||
<p class="arch-panel-extension-panel-label">Workload start calendar</p>
|
||
<h3 class="arch-panel-extension-panel-title">${escapeHtml(calendar.periodLabel)}</h3>
|
||
</div>
|
||
<div class="arch-panel-extension-calendar-actions">
|
||
<div class="arch-panel-extension-calendar-view-toggle" aria-label="Calendar view">
|
||
${["month", "week"]
|
||
.map(
|
||
(view) => `
|
||
<button
|
||
type="button"
|
||
class="arch-panel-extension-calendar-view-button${
|
||
calendar.view === view ? " is-active" : ""
|
||
}"
|
||
data-action="calendar-view"
|
||
data-calendar-view="${view}"
|
||
aria-pressed="${calendar.view === view ? "true" : "false"}"
|
||
>
|
||
${view === "month" ? "Month" : "Week"}
|
||
</button>
|
||
`
|
||
)
|
||
.join("")}
|
||
</div>
|
||
<button
|
||
type="button"
|
||
class="arch-panel-extension-calendar-nav"
|
||
data-action="calendar-prev"
|
||
aria-label="${calendar.view === "week" ? "Previous week" : "Previous month"}"
|
||
>
|
||
‹
|
||
</button>
|
||
<span>${formatWholeNumber(calendar.totalVisibleWorkloads)} starts</span>
|
||
<button
|
||
type="button"
|
||
class="arch-panel-extension-calendar-today"
|
||
data-action="calendar-today"
|
||
aria-label="${calendar.view === "week" ? "Go to current week" : "Go to current month"}"
|
||
>
|
||
Today
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="arch-panel-extension-calendar-nav"
|
||
data-action="calendar-next"
|
||
aria-label="${calendar.view === "week" ? "Next week" : "Next month"}"
|
||
>
|
||
›
|
||
</button>
|
||
</div>
|
||
</header>
|
||
<div class="arch-panel-extension-calendar-weekdays${
|
||
calendar.view === "week" ? " is-week" : ""
|
||
}">
|
||
${["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
|
||
.map((day) => `<span>${day}</span>`)
|
||
.join("")}
|
||
</div>
|
||
<div class="arch-panel-extension-calendar-grid${
|
||
calendar.view === "week" ? " is-week" : ""
|
||
}">
|
||
${calendar.days
|
||
.map((day) => renderCalendarDay(day, calendar.view))
|
||
.join("")}
|
||
</div>
|
||
</article>
|
||
`;
|
||
}
|
||
|
||
function renderCalendarDay(day, view = "month") {
|
||
if (view === "week") {
|
||
return renderCalendarWeekDay(day);
|
||
}
|
||
|
||
const firstWorkload = day.workloads[0];
|
||
const calendarWorkloadLabel =
|
||
firstWorkload?.description || firstWorkload?.name || "Workload";
|
||
const className = [
|
||
"arch-panel-extension-calendar-day",
|
||
day.isCurrentMonth ? "" : "is-muted",
|
||
day.isToday ? "is-today" : "",
|
||
day.workloads.length ? "has-workload" : "",
|
||
]
|
||
.filter(Boolean)
|
||
.join(" ");
|
||
|
||
return `
|
||
<button
|
||
type="button"
|
||
class="${className}"
|
||
title="${escapeHtml(
|
||
day.workloads
|
||
.map((workload) => workload.description || workload.name)
|
||
.filter(Boolean)
|
||
.join(" | ")
|
||
)}"
|
||
data-action="open-detail"
|
||
data-detail-scope="calendar-day"
|
||
data-detail-value="${escapeHtml(day.dateKey)}"
|
||
${day.workloads.length ? "" : "disabled"}
|
||
>
|
||
<span class="arch-panel-extension-calendar-date">${day.dayNumber}</span>
|
||
${
|
||
day.isWeekStart
|
||
? `<span class="arch-panel-extension-calendar-week-number">${escapeHtml(
|
||
day.operationalWeekLabel
|
||
)}</span>`
|
||
: ""
|
||
}
|
||
${
|
||
day.workloads.length
|
||
? `<span class="arch-panel-extension-calendar-workload">${escapeHtml(
|
||
calendarWorkloadLabel
|
||
)}</span><span class="arch-panel-extension-calendar-count">${formatWholeNumber(
|
||
day.workloads.length
|
||
)}</span>`
|
||
: ""
|
||
}
|
||
</button>
|
||
`;
|
||
}
|
||
|
||
function renderCalendarWeekDay(day) {
|
||
const className = [
|
||
"arch-panel-extension-calendar-day",
|
||
"is-week-day",
|
||
day.isToday ? "is-today" : "",
|
||
day.workloads.length ? "has-workload" : "",
|
||
]
|
||
.filter(Boolean)
|
||
.join(" ");
|
||
const title = day.workloads
|
||
.map((workload) => workload.description || workload.name)
|
||
.filter(Boolean)
|
||
.join(" | ");
|
||
|
||
return `
|
||
<button
|
||
type="button"
|
||
class="${className}"
|
||
title="${escapeHtml(title)}"
|
||
data-action="open-detail"
|
||
data-detail-scope="calendar-day"
|
||
data-detail-value="${escapeHtml(day.dateKey)}"
|
||
${day.workloads.length ? "" : "disabled"}
|
||
>
|
||
<span class="arch-panel-extension-calendar-week-day-head">
|
||
<span class="arch-panel-extension-calendar-date">${day.dayNumber}</span>
|
||
${
|
||
day.isWeekStart
|
||
? `<span class="arch-panel-extension-calendar-week-number">${escapeHtml(
|
||
day.operationalWeekLabel
|
||
)}</span>`
|
||
: ""
|
||
}
|
||
</span>
|
||
<span class="arch-panel-extension-calendar-week-workloads">
|
||
${
|
||
day.workloads.length
|
||
? day.workloads.map((workload) => renderCalendarWeekWorkload(workload)).join("")
|
||
: `<span class="arch-panel-extension-calendar-week-empty">No starts</span>`
|
||
}
|
||
</span>
|
||
</button>
|
||
`;
|
||
}
|
||
|
||
function renderCalendarWeekWorkload(workload) {
|
||
const label = workload.description || workload.name || "Workload";
|
||
const customer = workload.customerName || "";
|
||
|
||
return `
|
||
<span class="arch-panel-extension-calendar-week-workload">
|
||
<span class="arch-panel-extension-calendar-week-workload-title">${escapeHtml(label)}</span>
|
||
${
|
||
customer
|
||
? `<span class="arch-panel-extension-calendar-week-workload-meta">${escapeHtml(
|
||
customer
|
||
)}</span>`
|
||
: ""
|
||
}
|
||
</span>
|
||
`;
|
||
}
|
||
|
||
function renderWorkloadTable(workloads) {
|
||
return `
|
||
<div class="arch-panel-extension-table-wrap is-main">
|
||
<table class="arch-panel-extension-table">
|
||
<colgroup>
|
||
<col class="arch-panel-extension-col-opportunity">
|
||
<col class="arch-panel-extension-col-customer">
|
||
<col class="arch-panel-extension-col-workload">
|
||
<col class="arch-panel-extension-col-acr">
|
||
<col class="arch-panel-extension-col-type">
|
||
<col class="arch-panel-extension-col-flag">
|
||
<col class="arch-panel-extension-col-flag">
|
||
</colgroup>
|
||
<thead>
|
||
<tr>
|
||
<th>Opportunity</th>
|
||
<th>Customer</th>
|
||
<th>Workload</th>
|
||
<th class="is-numeric">ACR</th>
|
||
<th>Type</th>
|
||
<th>Tem SR?</th>
|
||
<th>Tem Action?</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
${workloads.map((workload) => renderWorkloadTableRow(workload)).join("")}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
function renderWorkloadTableRow(
|
||
workload,
|
||
includeConsumptionStartDate = false,
|
||
includeRampMonths = false
|
||
) {
|
||
const hasRampMismatch =
|
||
includeRampMonths && hasRampForecastMismatch(workload);
|
||
|
||
return `
|
||
<tr>
|
||
<td>${renderOpportunityLink(workload)}</td>
|
||
<td>${escapeHtml(workload.customerName || "")}</td>
|
||
<td>${renderWorkloadLink(workload, hasRampMismatch)}</td>
|
||
${
|
||
includeConsumptionStartDate
|
||
? `<td>${escapeHtml(formatDate(workload.consumptionStartDate) || "-")}</td>`
|
||
: ""
|
||
}
|
||
${
|
||
includeRampMonths
|
||
? `<td class="is-numeric">${renderRampMonthsButton(workload)}</td>`
|
||
: ""
|
||
}
|
||
<td class="is-numeric">${formatCurrency(workload.adjustedACR)}</td>
|
||
<td>${escapeHtml(workload.opportunityForecastTypeGroup)}</td>
|
||
<td>${renderBooleanBadge(workload.hasSR)}</td>
|
||
<td>${renderHasActionCell(workload)}</td>
|
||
</tr>
|
||
`;
|
||
}
|
||
|
||
function renderRampMonthsButton(workload) {
|
||
return `
|
||
<button
|
||
type="button"
|
||
class="arch-panel-extension-row-link arch-panel-extension-inline-link-button"
|
||
data-action="open-ramp-comparison"
|
||
data-workload-id="${escapeHtml(workload.workloadId || "")}"
|
||
aria-label="Abrir comparação de rampa para ${escapeHtml(
|
||
workload.name || "workload"
|
||
)}"
|
||
>
|
||
${formatWholeNumber(workload.rampMonths || 0)}
|
||
</button>
|
||
`;
|
||
}
|
||
|
||
function renderBooleanBadge(value) {
|
||
const isTrue = Boolean(value);
|
||
const variantClass = isTrue ? "is-success" : "is-danger";
|
||
const label = isTrue ? "Sim" : "Não";
|
||
|
||
return `
|
||
<span class="arch-panel-extension-badge ${variantClass}">
|
||
${label}
|
||
</span>
|
||
`;
|
||
}
|
||
|
||
function renderHasActionCell(workload) {
|
||
if (workload.hasAction) {
|
||
return renderBooleanBadge(true);
|
||
}
|
||
|
||
return `
|
||
<div class="arch-panel-extension-action-cell">
|
||
${renderBooleanBadge(false)}
|
||
<button
|
||
type="button"
|
||
class="arch-panel-extension-add-action-button"
|
||
data-action="open-action-form"
|
||
data-workload-id="${escapeHtml(workload.workloadId || "")}"
|
||
aria-label="Criar action para ${escapeHtml(workload.name || "workload")}"
|
||
title="Criar action"
|
||
>
|
||
➕
|
||
</button>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
function renderInsightRow(label, value, detail) {
|
||
return `
|
||
<div class="arch-panel-extension-insight">
|
||
<div>
|
||
<p class="arch-panel-extension-insight-label">${escapeHtml(label)}</p>
|
||
<p class="arch-panel-extension-insight-detail">${escapeHtml(detail)}</p>
|
||
</div>
|
||
<p class="arch-panel-extension-insight-value">${escapeHtml(value)}</p>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
|
||
return {
|
||
renderMainContent,
|
||
renderWorkloadTableRow,
|
||
};
|
||
}
|
||
|
||
globalThis.ArchPanelDashboardView = Object.freeze({
|
||
create,
|
||
});
|
||
})();
|