Files
boc/landvex-admin-v2/ui/intelligence.html
T
Bernt 6989a98d75 feat: Passwordless cross-device authentication
- Arkitektur: docs/auth/passwordless-architecture.md
- Backend: iom/quixzoom-auth-service/ (FastAPI + Redis)
- Webb: quixzoom-market-pages/se/login/ (QR-kod + polling)
- App: iom/quixzoom-app/src/features/auth/ (push + deep links)

Flöde: QR-kod → app-godkännande → webb-inloggad
2026-07-07 07:11:50 +00:00

722 lines
30 KiB
HTML

<!DOCTYPE html>
<html lang="sv">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LandveX Intelligence — Rapportgenerator</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, system-ui, sans-serif; background: #f5f5f5; }
.header { background: #1a1a2e; color: #fff; padding: 20px; }
.header h1 { font-size: 20px; }
.header p { opacity: 0.7; font-size: 14px; margin-top: 4px; }
.nav { background: #16213e; padding: 0 20px; display: flex; gap: 4px; }
.nav a { color: rgba(255,255,255,0.7); text-decoration: none; padding: 12px 16px; font-size: 14px; }
.nav a.active { color: #fff; background: rgba(255,255,255,0.1); }
.container { max-width: 1200px; margin: 0 auto; padding: 20px; }
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; }
.card { background: #fff; padding: 24px; border-radius: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.card h2 { font-size: 14px; text-transform: uppercase; color: #666; margin-bottom: 12px; }
.card .value { font-size: 28px; font-weight: 600; }
.card .sub { color: #999; font-size: 14px; margin-top: 4px; }
.product-card { cursor: pointer; transition: transform 0.2s, box-shadow 0.2s; }
.product-card:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.15); }
.product-card.selected { border: 2px solid #1a1a2e; }
.price { font-size: 32px; font-weight: 600; color: #1a1a2e; }
.pages { color: #666; font-size: 14px; }
.btn { background: #1a1a2e; color: #fff; border: none; padding: 12px 24px; border-radius: 8px; font-size: 16px; cursor: pointer; margin-top: 16px; }
.btn:hover { background: #2a2a4e; }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
.btn-outline { background: transparent; color: #1a1a2e; border: 2px solid #1a1a2e; }
.btn-outline:hover { background: #1a1a2e; color: #fff; }
.btn-success { background: #28a745; }
.btn-success:hover { background: #218838; }
.btn-danger { background: #dc3545; }
.btn-danger:hover { background: #c82333; }
.input { width: 100%; padding: 12px; border: 1px solid #ddd; border-radius: 8px; font-size: 16px; margin-top: 8px; }
.report-preview { background: #fff; padding: 24px; border-radius: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-top: 20px; }
.section { border-bottom: 1px solid #eee; padding: 16px 0; }
.section:last-child { border-bottom: none; }
.section h3 { font-size: 16px; color: #1a1a2e; margin-bottom: 8px; }
.section p { color: #666; font-size: 14px; line-height: 1.5; }
.confidence-bar { height: 8px; background: #eee; border-radius: 4px; overflow: hidden; margin-top: 8px; }
.confidence-fill { height: 100%; background: linear-gradient(90deg, #dc3545, #ffc107, #28a745); border-radius: 4px; }
.risk-badge { display: inline-block; padding: 4px 12px; border-radius: 12px; font-size: 12px; font-weight: 600; }
.risk-critical { background: #dc3545; color: #fff; }
.risk-high { background: #fd7e14; color: #fff; }
.risk-medium { background: #ffc107; color: #000; }
.risk-low { background: #28a745; color: #fff; }
.recommendation { display: flex; align-items: center; gap: 12px; padding: 12px; background: #f8f9fa; border-radius: 8px; margin-top: 8px; }
.rec-priority { width: 32px; height: 32px; border-radius: 50%; background: #1a1a2e; color: #fff; display: flex; align-items: center; justify-content: center; font-weight: 600; }
.hidden { display: none; }
.loading { text-align: center; padding: 40px; color: #666; }
/* === KPI-RUTOR (NYTT) === */
.kpi-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin: 20px 0; }
.kpi-box {
background: #fff;
border: 1px solid #e0e0e0;
border-radius: 12px;
padding: 20px;
text-align: center;
transition: transform 0.2s, box-shadow 0.2s;
}
.kpi-box:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
.kpi-box.highlight {
background: #1a1a2e;
color: #fff;
border-color: #1a1a2e;
}
.kpi-box.highlight .kpi-value { color: #fff; }
.kpi-box.highlight .kpi-label { color: rgba(255,255,255,0.8); }
.kpi-value { font-size: 32px; font-weight: 700; color: #1a1a2e; line-height: 1.2; }
.kpi-label { font-size: 11px; color: #999; text-transform: uppercase; letter-spacing: 0.5px; margin-top: 8px; }
.kpi-trend { font-size: 12px; margin-top: 4px; font-weight: 600; }
.kpi-trend.up { color: #28a745; }
.kpi-trend.down { color: #dc3545; }
/* === INFOBOXAR === */
.info-box {
background: #f8f9fa;
border-left: 4px solid #0f3460;
padding: 14px 18px;
margin: 12px 0;
border-radius: 0 8px 8px 0;
}
.info-box.warning { border-left-color: #ffc107; background: #fffbeb; }
.info-box.danger { border-left-color: #dc3545; background: #fef2f2; }
.info-box.success { border-left-color: #28a745; background: #f0fdf4; }
.info-box-title {
font-weight: 700;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 6px;
color: #1a1a2e;
}
.info-box p { margin: 0; font-size: 13px; color: #333; }
/* === DOKUMENTKONTROLL === */
.doc-control { background: #fff; padding: 20px; border-radius: 12px; margin-bottom: 20px; }
.doc-control h3 { font-size: 14px; color: #1a1a2e; margin-bottom: 12px; }
.control-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.control-table th {
background: #1a1a2e;
color: #fff;
padding: 8px 12px;
text-align: left;
font-size: 10px;
text-transform: uppercase;
}
.control-table td { padding: 8px 12px; border-bottom: 1px solid #eee; }
.control-table tr:nth-child(even) { background: #f8f9fa; }
/* === PDF-ACTIONS === */
.pdf-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.pdf-actions .btn { margin-top: 0; padding: 10px 18px; font-size: 14px; }
.pdf-status {
padding: 12px 16px;
border-radius: 8px;
margin-top: 12px;
font-size: 13px;
}
.pdf-status.success { background: #f0fdf4; color: #166534; border: 1px solid #bbf7d0; }
.pdf-status.error { background: #fef2f2; color: #991b1b; border: 1px solid #fecaca; }
.pdf-status.info { background: #eff6ff; color: #1e40af; border: 1px solid #bfdbfe; }
/* === DIVIDER === */
.divider { height: 2px; background: linear-gradient(90deg, #1a1a2e, #e94560); margin: 20px 0; border-radius: 1px; }
/* === DATA TABLE === */
.data-table { width: 100%; border-collapse: collapse; margin: 12px 0; font-size: 13px; }
.data-table thead { background: #1a1a2e; color: #fff; }
.data-table th { padding: 10px 12px; text-align: left; font-weight: 600; font-size: 10px; text-transform: uppercase; }
.data-table td { padding: 8px 12px; border-bottom: 1px solid #eee; }
.data-table tbody tr:nth-child(even) { background: #f8f9fa; }
/* === MODAL === */
.modal-overlay {
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.5); display: none;
align-items: center; justify-content: center; z-index: 1000;
}
.modal-overlay.active { display: flex; }
.modal {
background: #fff; border-radius: 12px; padding: 24px;
max-width: 500px; width: 90%; max-height: 80vh; overflow-y: auto;
}
.modal h3 { margin-bottom: 16px; }
.modal .form-group { margin-bottom: 12px; }
.modal label { display: block; font-size: 13px; color: #666; margin-bottom: 4px; }
.modal input, .modal select {
width: 100%; padding: 10px; border: 1px solid #ddd;
border-radius: 6px; font-size: 14px;
}
.modal-actions { display: flex; gap: 8px; margin-top: 16px; }
.modal-actions .btn { margin-top: 0; }
</style>
</head>
<body>
<div class="header">
<h1>LandveX Intelligence</h1>
<p>Rapportgenerator — Från data till beslut</p>
</div>
<div class="nav">
<a href="index.html">Ekonomi</a>
<a href="#" class="active">Intelligence</a>
<a href="#">CRM</a>
<a href="#">Support</a>
</div>
<div class="container">
<!-- Produktväljare -->
<div id="product-selector">
<h2 style="margin-bottom: 16px;">Välj produkt</h2>
<div class="grid" id="products"></div>
</div>
<!-- Rapportgenerator -->
<div id="generator" class="hidden">
<div class="card">
<h2>Generera rapport</h2>
<p style="color: #666; margin-bottom: 12px;">Produkt: <strong id="selected-product-name"></strong></p>
<label style="color: #666; font-size: 14px;">Objekt / Adress</label>
<input type="text" class="input" id="object-name" placeholder="t.ex. Storgatan 1, Stockholm" value="Storgatan 1, Stockholm">
<button class="btn" onclick="generateReport()">Generera rapport</button>
<button class="btn btn-outline" style="margin-left: 8px;" onclick="backToProducts()">Tillbaka</button>
</div>
</div>
<!-- Laddar -->
<div id="loading" class="hidden">
<div class="loading">
<div style="font-size: 24px; margin-bottom: 12px;"></div>
<p>Genererar rapport... Detta tar några sekunder.</p>
</div>
</div>
<!-- Rapportvisning -->
<div id="report-view" class="hidden">
<!-- Dokumentkontroll -->
<div class="doc-control" id="doc-control">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
<h3>📋 Dokumentkontroll</h3>
<button class="btn btn-outline" style="padding: 6px 12px; font-size: 12px; margin-top: 0;" onclick="openDocControlModal()">Redigera</button>
</div>
<table class="control-table" id="doc-control-table">
<tbody></tbody>
</table>
</div>
<!-- KPI-rutor -->
<div class="kpi-grid" id="kpi-grid">
<div class="kpi-box">
<div class="kpi-value" id="kpi-confidence">87%</div>
<div class="kpi-label">Confidence Score</div>
</div>
<div class="kpi-box highlight">
<div class="kpi-value" id="kpi-risks">3</div>
<div class="kpi-label">Kritiska Risker</div>
</div>
<div class="kpi-box">
<div class="kpi-value" id="kpi-observations">47</div>
<div class="kpi-label">Observationer</div>
</div>
<div class="kpi-box">
<div class="kpi-value" id="kpi-budget">$75K</div>
<div class="kpi-label">Total Budget</div>
</div>
</div>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
<h2 id="report-title"></h2>
<div class="pdf-actions">
<button class="btn btn-success" onclick="generatePdf()" id="btn-generate-pdf">
📄 Generera PDF
</button>
<button class="btn" onclick="previewHtml()">
👁️ HTML-preview
</button>
<button class="btn" onclick="exportReport('json')">
JSON
</button>
<button class="btn" onclick="exportReport('csv')">
CSV
</button>
</div>
</div>
<div id="pdf-status" class="pdf-status hidden"></div>
<div class="report-preview" id="report-content"></div>
<div style="margin-top: 16px; display: flex; gap: 8px;">
<button class="btn" onclick="backToProducts()">Ny rapport</button>
<button class="btn btn-outline" onclick="cloneReport()">Klonera rapport</button>
</div>
</div>
</div>
<!-- Modal för dokumentkontroll -->
<div class="modal-overlay" id="doc-control-modal">
<div class="modal">
<h3>Redigera dokumentkontroll</h3>
<div class="form-group">
<label>Referens / Reference</label>
<input type="text" id="dc-reference" placeholder="t.ex. LVX-ABC123">
</div>
<div class="form-group">
<label>Version</label>
<input type="text" id="dc-version" value="1.0">
</div>
<div class="form-group">
<label>Beställare / Client</label>
<input type="text" id="dc-client" placeholder="Kundnamn">
</div>
<div class="form-group">
<label>Analytiker / Analyst</label>
<input type="text" id="dc-analyst" value="Bernt (AI) / LandveX Intelligence">
</div>
<div class="form-group">
<label>Klassificering / Classification</label>
<select id="dc-classification">
<option value="Konfidentiell / Confidential">Konfidentiell</option>
<option value="Strikt konfidentiell / Strictly Confidential">Strikt konfidentiell</option>
<option value="Intern / Internal">Intern</option>
<option value="Offentlig / Public">Offentlig</option>
</select>
</div>
<div class="modal-actions">
<button class="btn" onclick="saveDocControl()">Spara</button>
<button class="btn btn-outline" onclick="closeDocControlModal()">Avbryt</button>
</div>
</div>
</div>
<script>
let selectedProduct = null;
let currentReport = null;
let currentDocControl = null;
async function loadProducts() {
const res = await fetch('/api/intelligence/products');
const data = await res.json();
const container = document.getElementById('products');
container.innerHTML = data.products.map(p => `
<div class="card product-card" onclick="selectProduct('${p.id}')" data-id="${p.id}">
<h2>${p.name}</h2>
<div class="price">$${p.price.toLocaleString()}</div>
<div class="pages">${p.pages} sidor • ${p.deliveryDays} dagar leverans</div>
<p style="color: #666; margin-top: 8px; font-size: 14px;">${p.description}</p>
</div>
`).join('');
}
function selectProduct(id) {
document.querySelectorAll('.product-card').forEach(c => c.classList.remove('selected'));
document.querySelector(`[data-id="${id}"]`).classList.add('selected');
selectedProduct = id;
document.getElementById('product-selector').classList.add('hidden');
document.getElementById('generator').classList.remove('hidden');
fetch('/api/intelligence/products')
.then(r => r.json())
.then(data => {
const product = data.products.find(p => p.id === id);
document.getElementById('selected-product-name').textContent = product.name;
});
}
function backToProducts() {
document.getElementById('product-selector').classList.remove('hidden');
document.getElementById('generator').classList.add('hidden');
document.getElementById('report-view').classList.add('hidden');
selectedProduct = null;
currentReport = null;
currentDocControl = null;
}
async function generateReport() {
const objectName = document.getElementById('object-name').value;
if (!objectName) return alert('Ange objektnamn');
document.getElementById('generator').classList.add('hidden');
document.getElementById('loading').classList.remove('hidden');
const res = await fetch('/api/intelligence/demo/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ productId: selectedProduct, objectName })
});
const data = await res.json();
currentReport = data.report;
await loadReport(data.report.id);
document.getElementById('loading').classList.add('hidden');
document.getElementById('report-view').classList.remove('hidden');
}
async function loadReport(id) {
const res = await fetch(`/api/intelligence/reports/${id}`);
const data = await res.json();
const report = data.report;
currentReport = report;
document.getElementById('report-title').textContent = `${report.productName}${report.objectName}`;
// Uppdatera KPI-rutor
updateKPIs(report);
// Ladda dokumentkontroll
await loadDocControl(id);
// Rendera sektioner
const content = document.getElementById('report-content');
content.innerHTML = report.sections.map(s => renderSection(s)).join('');
}
function updateKPIs(report) {
const confidence = report.confidence?.overall || 0.87;
document.getElementById('kpi-confidence').textContent = Math.round(confidence * 100) + '%';
// Räkna risker
const riskSection = report.sections.find(s => s.id === 'risk_matrix');
const criticalRisks = riskSection?.content?.risks?.filter(r => r.level === 'critical').length || 0;
document.getElementById('kpi-risks').textContent = criticalRisks;
// Observationer
const obsSection = report.sections.find(s => s.id === 'observations');
const obsCount = obsSection?.content?.length || 0;
document.getElementById('kpi-observations').textContent = obsCount;
// Budget
const recSection = report.sections.find(s => s.id === 'recommendations');
let totalBudget = 0;
if (recSection?.content) {
recSection.content.forEach(r => {
const match = r.budget.match(/\$?([\d,]+)/);
if (match) totalBudget += parseInt(match[1].replace(/,/g, ''));
});
}
document.getElementById('kpi-budget').textContent = totalBudget > 0 ? '$' + (totalBudget / 1000).toFixed(0) + 'K' : '—';
}
async function loadDocControl(reportId) {
try {
const res = await fetch(`/api/reports/${reportId}/document-control`);
const data = await res.json();
currentDocControl = data.documentControl;
renderDocControl(data.documentControl);
} catch (e) {
console.log('Doc control not available yet');
}
}
function renderDocControl(dc) {
const tbody = document.querySelector('#doc-control-table tbody');
tbody.innerHTML = `
<tr><td><strong>Referens</strong></td><td>${dc.reference || '—'}</td></tr>
<tr><td><strong>Version</strong></td><td>${dc.version || '1.0'}</td></tr>
<tr><td><strong>Datum</strong></td><td>${dc.date || new Date().toISOString().split('T')[0]}</td></tr>
<tr><td><strong>Beställare</strong></td><td>${dc.client || '—'}</td></tr>
<tr><td><strong>Analytiker</strong></td><td>${dc.analyst || '—'}</td></tr>
<tr><td><strong>Klassificering</strong></td><td><span class="risk-badge risk-critical">${dc.classification || 'Konfidentiell'}</span></td></tr>
`;
}
function openDocControlModal() {
if (!currentDocControl) return;
document.getElementById('dc-reference').value = currentDocControl.reference || '';
document.getElementById('dc-version').value = currentDocControl.version || '1.0';
document.getElementById('dc-client').value = currentDocControl.client || '';
document.getElementById('dc-analyst').value = currentDocControl.analyst || '';
document.getElementById('dc-classification').value = currentDocControl.classification || 'Konfidentiell / Confidential';
document.getElementById('doc-control-modal').classList.add('active');
}
function closeDocControlModal() {
document.getElementById('doc-control-modal').classList.remove('active');
}
async function saveDocControl() {
if (!currentReport) return;
const body = {
reference: document.getElementById('dc-reference').value,
version: document.getElementById('dc-version').value,
client: document.getElementById('dc-client').value,
analyst: document.getElementById('dc-analyst').value,
classification: document.getElementById('dc-classification').value,
changes: 'Uppdaterad via admin-gränssnitt'
};
const res = await fetch(`/api/reports/${currentReport.id}/document-control`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
const data = await res.json();
if (data.ok) {
currentDocControl = data.documentControl;
renderDocControl(data.documentControl);
closeDocControlModal();
showPdfStatus('Dokumentkontroll uppdaterad', 'success');
}
}
async function generatePdf() {
if (!currentReport) return;
const btn = document.getElementById('btn-generate-pdf');
btn.disabled = true;
btn.textContent = '⏳ Genererar PDF...';
showPdfStatus('Genererar professionell PDF med LandveX-branding...', 'info');
try {
const res = await fetch(`/api/reports/${currentReport.id}/pdf`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(currentDocControl || {})
});
const data = await res.json();
if (data.ok) {
showPdfStatus(`✅ PDF genererad! <a href="${data.downloadUrl}" target="_blank" style="color: #166534; font-weight: 600;">Ladda ner PDF</a> | <a href="${data.previewUrl}" target="_blank" style="color: #1e40af;">HTML-preview</a>`, 'success');
} else {
showPdfStatus('❌ PDF-generering misslyckades: ' + (data.error || 'Okänt fel'), 'error');
}
} catch (e) {
showPdfStatus('❌ Nätverksfel: ' + e.message, 'error');
} finally {
btn.disabled = false;
btn.textContent = '📄 Generera PDF';
}
}
function previewHtml() {
if (!currentReport) return;
window.open(`/api/reports/${currentReport.id}/html`, '_blank');
}
function showPdfStatus(message, type) {
const el = document.getElementById('pdf-status');
el.className = 'pdf-status ' + type;
el.innerHTML = message;
el.classList.remove('hidden');
}
async function cloneReport() {
if (!currentReport) return;
const res = await fetch(`/api/reports/${currentReport.id}/clone`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ version: '2.0' })
});
const data = await res.json();
if (data.ok) {
showPdfStatus(`✅ Rapport klonad! Nytt ID: ${data.newId}`, 'success');
}
}
function renderSection(section) {
const c = section.content;
switch(section.id) {
case 'front_page':
return '';
case 'executive_summary':
return `
<div class="section">
<h3>📊 ${section.name}</h3>
<div class="info-box">
<div class="info-box-title">Vad vet vi? / What do we know?</div>
<p>${c.whatWeKnow}</p>
</div>
<div class="info-box success">
<div class="info-box-title">Hur säkert är det? / How confident are we?</div>
<p>${c.confidence}</p>
</div>
<div class="info-box warning">
<div class="info-box-title">Vad bör kunden göra? / What should the client do?</div>
<p>${c.recommendation}</p>
</div>
</div>`;
case 'scope':
return `
<div class="section">
<h3>🎯 ${section.name}</h3>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;">
<div>
<h4 style="color: #28a745; margin-bottom: 8px;">✓ Ingår</h4>
<ul style="margin-left: 20px; color: #666;">
${c.included.map(i => `<li>${i}</li>`).join('')}
</ul>
</div>
<div>
<h4 style="color: #dc3545; margin-bottom: 8px;">✗ Ingår inte</h4>
<ul style="margin-left: 20px; color: #666;">
${c.excluded.map(i => `<li>${i}</li>`).join('')}
</ul>
</div>
</div>
</div>`;
case 'confidence':
return `
<div class="section">
<h3>🎯 ${section.name}${Math.round(c.overall * 100)}%</h3>
<div class="confidence-bar">
<div class="confidence-fill" style="width: ${c.overall * 100}%"></div>
</div>
<div class="info-box" style="margin-top: 12px;">
<div class="info-box-title">Metodologi</div>
<p>${c.methodology}</p>
</div>
<table class="data-table">
<thead>
<tr><th>Källa</th><th>Score</th><th>Vikt</th><th>Noteringar</th></tr>
</thead>
<tbody>
${c.sources.map(s => `
<tr>
<td><strong>${s.name}</strong></td>
<td>${Math.round(s.score * 100)}%</td>
<td>${s.weight}</td>
<td><small style="color: #999;">${s.notes}</small></td>
</tr>
`).join('')}
</tbody>
</table>
</div>`;
case 'data_sources':
return `
<div class="section">
<h3>📚 ${section.name}</h3>
<table class="data-table">
<thead>
<tr><th>Källa</th><th>Typ</th><th>Datum</th><th>Metod</th><th>Begränsningar</th></tr>
</thead>
<tbody>
${c.map(s => `
<tr>
<td><strong>${s.name}</strong></td>
<td>${s.type}</td>
<td>${s.date}</td>
<td>${s.method}</td>
<td><small style="color: #999;">${s.limitations}</small></td>
</tr>
`).join('')}
</tbody>
</table>
</div>`;
case 'analysis':
return `
<div class="section">
<h3>🔍 ${section.name}</h3>
<div class="info-box">
<div class="info-box-title">Metodologi</div>
<p>${c.methodology}</p>
</div>
<table class="data-table">
<thead>
<tr><th>Område</th><th>Fynd</th><th>Allvarlighet</th><th>Bevis</th></tr>
</thead>
<tbody>
${c.findings.map(f => `
<tr>
<td><strong>${f.area}</strong></td>
<td>${f.finding}</td>
<td><span class="risk-badge risk-${f.severity}">${f.severity.toUpperCase()}</span></td>
<td>${f.evidence} observationer</td>
</tr>
`).join('')}
</tbody>
</table>
</div>`;
case 'risk_matrix':
return `
<div class="section">
<h3>⚠️ ${section.name}</h3>
${c.risks.map(r => `
<div class="info-box ${r.level === 'critical' ? 'danger' : r.level === 'high' ? 'warning' : ''}" style="margin: 8px 0;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px;">
<strong>${r.id}: ${r.description}</strong>
<span class="risk-badge risk-${r.level}">${r.level.toUpperCase()}</span>
</div>
<p style="font-size: 13px;">Sannolikhet: ${r.probability} • Konsekvens: ${r.consequence}</p>
<p style="margin-top: 4px; font-size: 13px;"><strong>Åtgärd:</strong> ${r.mitigation}</p>
</div>
`).join('')}
<div class="info-box" style="margin-top: 12px;">
<div class="info-box-title">Metodologi</div>
<p>${c.methodology}</p>
</div>
</div>`;
case 'recommendations':
return `
<div class="section">
<h3>💡 ${section.name}</h3>
${c.map(r => `
<div class="recommendation">
<div class="rec-priority">${r.priority}</div>
<div>
<strong>${r.action}</strong>
<p style="color: #666; font-size: 13px;">📅 ${r.timeline} &nbsp;|&nbsp; 👤 ${r.owner} &nbsp;|&nbsp; 💰 ${r.budget}</p>
</div>
</div>
`).join('')}
</div>`;
case 'appendix':
return `
<div class="section">
<h3>📎 ${section.name}</h3>
${c.dataFiles.map(f => `
<div class="info-box" style="margin: 8px 0;">
<div style="display: flex; justify-content: space-between; align-items: center;">
<strong>${f.name}</strong>
<span style="background: #1a1a2e; color: #fff; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-family: monospace;">${f.type}</span>
</div>
<p style="font-size: 12px; color: #999; margin-top: 4px;">📦 ${f.size}</p>
<p style="font-size: 13px; margin-top: 4px;">${f.description}</p>
</div>
`).join('')}
<div class="info-box" style="margin-top: 12px;">
<div class="info-box-title">Reproducerbarhet</div>
<p>${c.reproducibilityNote}</p>
<p style="margin-top: 4px; font-size: 12px; color: #999;">Protokoll: ${c.methodology} • Version: ${c.version} • Genererad: ${c.generatedAt}</p>
</div>
</div>`;
default:
return `
<div class="section">
<h3>${section.name}</h3>
<pre style="background: #f8f9fa; padding: 12px; border-radius: 4px; overflow-x: auto; font-size: 12px;">${JSON.stringify(c, null, 2)}</pre>
</div>`;
}
}
function exportReport(format) {
if (!currentReport) return;
window.open(`/api/intelligence/reports/${currentReport.id}/export/${format}`, '_blank');
}
loadProducts();
</script>
</body>
</html>