Reestruturação dos arquivos e pastas do código da extensão
This commit is contained in:
714
src/views/dashboard-view.js
Normal file
714
src/views/dashboard-view.js
Normal file
@@ -0,0 +1,714 @@
|
||||
(() => {
|
||||
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">
|
||||
<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,
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user