feat(ui-phase-18): issues & warnings view with nav badge

Dedicated view surfacing all templates with blockers (migration will fail)
and warnings (migration with caveats). Each blocker item shows all error
messages; each warning item has a Migrate Anyway button and View Detail link.
Nav badge count driven by state.issueCount (updated when templates load).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Paul Huliganga 2026-04-21 11:38:33 -04:00
parent 587104d520
commit 329edc39d2
1 changed files with 124 additions and 0 deletions

124
web/static/js/issues.js Normal file
View File

@ -0,0 +1,124 @@
// Issues & Warnings view — surfaces all validation problems before migration
import { state } from './state.js';
import { escHtml, formatDate } from './utils.js';
import { navigate } from './router.js';
export function renderIssues() {
const outlet = document.getElementById('router-outlet');
const templates = state.templates || [];
const blocked = templates.filter(t => t.blockers && t.blockers.length > 0);
const warnings = templates.filter(t =>
(!t.blockers || t.blockers.length === 0) && t.warnings && t.warnings.length > 0
);
if (!state.auth.adobe || !state.auth.docusign) {
outlet.innerHTML = `
<div class="page-header"><div><div class="page-title">Issues &amp; Warnings</div></div></div>
<div class="callout info"><span class="callout-icon"></span>Connect both platforms to see validation results.</div>`;
return;
}
if (!blocked.length && !warnings.length) {
outlet.innerHTML = `
<div class="page-header">
<div>
<div class="page-title">Issues &amp; Warnings</div>
<div class="page-subtitle">${templates.length} templates analyzed</div>
</div>
</div>
<div class="callout success" style="font-size:14px">
<span class="callout-icon">🎉</span>
<div>
<strong>All templates are ready!</strong>
<div style="margin-top:4px">No blockers or warnings found across ${templates.length} template${templates.length !== 1 ? 's' : ''}.</div>
</div>
</div>`;
return;
}
outlet.innerHTML = `
<div class="page-header">
<div>
<div class="page-title">Issues &amp; Warnings</div>
<div class="page-subtitle">${templates.length} templates analyzed
${blocked.length ? `<span style="color:var(--error);font-weight:600">${blocked.length} blocked</span>` : ''}
${blocked.length && warnings.length ? ', ' : ''}
${warnings.length ? `<span style="color:var(--warning);font-weight:600">${warnings.length} with warnings</span>` : ''}
</div>
</div>
<div class="page-actions">
<a href="#/templates" class="btn btn-secondary btn-sm"> All Templates</a>
</div>
</div>
${blocked.length ? `
<div style="margin-bottom:24px">
<div style="font-size:14px;font-weight:700;color:var(--error);margin-bottom:10px">
🚫 Blockers ${blocked.length} template${blocked.length > 1 ? 's' : ''} will fail migration
</div>
<div class="attention-list">
${blocked.map(t => _blockerItem(t)).join('')}
</div>
</div>` : ''}
${warnings.length ? `
<div>
<div style="font-size:14px;font-weight:700;color:var(--warning);margin-bottom:10px">
Warnings ${warnings.length} template${warnings.length > 1 ? 's' : ''} will migrate with caveats
</div>
<div class="attention-list">
${warnings.map(t => _warningItem(t)).join('')}
</div>
</div>` : ''}
`;
// Migrate Anyway buttons
document.querySelectorAll('.btn-migrate-anyway').forEach(btn => {
btn.addEventListener('click', () => {
import('./migration.js').then(m => m.showOptionsModal([btn.dataset.id]));
});
});
// View Template links
document.querySelectorAll('.btn-view-template').forEach(btn => {
btn.addEventListener('click', () => navigate(`#/templates/${btn.dataset.id}`));
});
}
function _blockerItem(t) {
const blockers = t.blockers || [];
return `
<div class="attention-item blocker">
<span class="attention-icon">🚫</span>
<div style="flex:1">
<div class="attention-name">${escHtml(t.name)}</div>
${blockers.map(b => `<div class="attention-detail">• ${escHtml(b)}</div>`).join('')}
<div style="margin-top:6px;font-size:11px;color:var(--text-muted)">Modified ${formatDate(t.adobe_modified)}</div>
</div>
<div class="attention-action" style="display:flex;flex-direction:column;gap:6px;align-items:flex-end">
<button class="btn btn-secondary btn-xs btn-view-template" data-id="${escHtml(t.adobe_id)}">View Detail</button>
</div>
</div>
`;
}
function _warningItem(t) {
const warnings = t.warnings || [];
return `
<div class="attention-item warning">
<span class="attention-icon"></span>
<div style="flex:1">
<div class="attention-name">${escHtml(t.name)}</div>
${warnings.slice(0, 3).map(w => `<div class="attention-detail">• ${escHtml(w)}</div>`).join('')}
${warnings.length > 3 ? `<div class="attention-detail" style="color:var(--text-muted)">… +${warnings.length - 3} more</div>` : ''}
<div style="margin-top:6px;font-size:11px;color:var(--text-muted)">Modified ${formatDate(t.adobe_modified)}</div>
</div>
<div class="attention-action" style="display:flex;flex-direction:column;gap:6px;align-items:flex-end">
<button class="btn btn-primary btn-xs btn-migrate-anyway" data-id="${escHtml(t.adobe_id)}">Migrate Anyway</button>
<button class="btn btn-secondary btn-xs btn-view-template" data-id="${escHtml(t.adobe_id)}">View Detail</button>
</div>
</div>
`;
}