Files
ksp/pages/escalasi.js
T
2026-09-16 10:38:01 +07:00

196 lines
10 KiB
JavaScript

// ═══════════════════════════════════════════════════════════════════
// ESCALASI PAGE — Escalation pipeline and management
// ═══════════════════════════════════════════════════════════════════
function renderEscalasiPage() {
const escs = APP_STATE.escalations;
const active = escs.filter(e => e.status !== 'resolved');
const resolved = escs.filter(e => e.status === 'resolved');
const levelCounts = { L1:0, L2:0, L3:0, L4:0 };
active.forEach(e => { if (levelCounts[e.level] !== undefined) levelCounts[e.level]++; });
document.getElementById('escalasi-body').innerHTML = `
<div class="scroll-area">
<!-- Header -->
<div class="section-header mb-4">
<div>
<div class="section-title">Pipeline Eskalasi</div>
<div class="section-sub">Manajemen eskalasi berjenjang: Daerah → KSP → Kementerian → Penegakan Hukum</div>
</div>
<button class="btn btn-primary btn-sm" onclick="openNewEscalasiModal()">+ Buat Eskalasi</button>
</div>
<!-- Pipeline Steps -->
<div class="esc-pipeline mb-4">
${['L1','L2','L3','L4'].map((lv, idx) => {
const cfg = ESCALATION_LEVELS[lv];
const desc = {
L1: 'Tim / Dinas Daerah',
L2: 'KSP Internal',
L3: 'Kementerian / Lembaga',
L4: 'Kejaksaan / POLRI / BPK'
}[lv];
return `
<div class="esc-step ${levelCounts[lv] > 0 ? 'active' : ''}">
<div class="esc-step-num">${levelCounts[lv] > 0 ? levelCounts[lv] : idx+1}</div>
<div class="esc-step-label">${cfg.label.replace(lv+' — ','')}</div>
<div style="font-size:10px;color:var(--text-3);margin-top:3px;">${desc}</div>
</div>
`;
}).join('')}
</div>
<!-- Stats -->
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-bottom:20px;">
<div class="card card-p-sm" style="text-align:center;border-color:rgba(239,68,68,0.2);">
<div style="font-size:22px;font-weight:800;color:var(--red);">${active.length}</div>
<div style="font-size:11px;color:var(--text-3);">Eskalasi Aktif</div>
</div>
<div class="card card-p-sm" style="text-align:center;border-color:rgba(245,158,11,0.2);">
<div style="font-size:22px;font-weight:800;color:var(--amber);">${escs.filter(e=>e.status==='pending').length}</div>
<div style="font-size:11px;color:var(--text-3);">Menunggu Persetujuan</div>
</div>
<div class="card card-p-sm" style="text-align:center;border-color:rgba(59,130,246,0.2);">
<div style="font-size:22px;font-weight:800;color:var(--blue);">${escs.filter(e=>e.status==='approved').length}</div>
<div style="font-size:11px;color:var(--text-3);">Disetujui</div>
</div>
<div class="card card-p-sm" style="text-align:center;border-color:rgba(34,197,94,0.2);">
<div style="font-size:22px;font-weight:800;color:var(--green);">${resolved.length}</div>
<div style="font-size:11px;color:var(--text-3);">Selesai</div>
</div>
</div>
<!-- Active Escalations -->
<div style="font-size:13px;font-weight:600;color:var(--text-1);margin-bottom:12px;">⚡ Eskalasi Aktif</div>
${active.length === 0
? `<div class="empty-state card" style="padding:32px;"><div class="empty-icon">✅</div><div class="empty-title">Tidak ada eskalasi aktif</div><div class="empty-desc">Semua eskalasi telah diselesaikan</div></div>`
: active.map(e => renderEscCard(e, false)).join('')
}
${resolved.length > 0 ? `
<div class="divider mt-4 mb-3"></div>
<div style="font-size:13px;font-weight:600;color:var(--text-1);margin-bottom:12px;">✅ Riwayat Eskalasi Selesai</div>
${resolved.map(e => renderEscCard(e, true)).join('')}
` : ''}
</div>
`;
}
function renderEscCard(esc, isResolved) {
const issue = APP_STATE.issues.find(i => i.id === esc.issueId);
const lvCfg = ESCALATION_LEVELS[esc.level];
const statusMap = { pending: { label: 'Menunggu', color: 'var(--amber)', bg: 'var(--amber-soft)' }, approved: { label: 'Disetujui', color: 'var(--blue)', bg: 'var(--blue-soft)' }, rejected: { label: 'Ditolak', color: 'var(--red)', bg: 'var(--red-soft)' }, resolved: { label: 'Selesai', color: 'var(--green)', bg: 'var(--green-soft)' } };
const st = statusMap[esc.status] || statusMap.pending;
return `
<div class="card card-p mb-3 ${isResolved ? '' : ''}" style="${!isResolved ? `border-left:3px solid ${lvCfg?.color || 'var(--border)'};` : 'opacity:0.7;'}">
<div class="flex justify-between items-start mb-3">
<div style="flex:1;">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px;flex-wrap:wrap;">
<span style="font-size:10px;font-family:monospace;color:var(--text-3);font-weight:600;">${esc.id}</span>
<span style="background:${lvCfg?.color || 'var(--border)'}22;color:${lvCfg?.color || 'var(--text-2)'};border:1px solid ${lvCfg?.color || 'var(--border)'}44;padding:2px 8px;border-radius:99px;font-size:11px;font-weight:700;">${esc.level}</span>
<span style="font-size:11px;font-weight:600;color:${st.color};background:${st.bg};padding:2px 8px;border-radius:99px;">${st.label}</span>
${getProgramBadge(esc.programId)}
</div>
<div style="font-size:13px;font-weight:600;color:var(--text-1);margin-bottom:2px;">${esc.levelName}</div>
<div style="font-size:12px;color:var(--text-3);">Ditujukan ke: <strong style="color:var(--text-2);">${esc.assignTo}</strong></div>
</div>
<div style="text-align:right;flex-shrink:0;">
<div style="font-size:11px;color:var(--text-3);">📅 ${formatDate(esc.createdAt)}</div>
${esc.resolvedAt ? `<div style="font-size:11px;color:var(--green);">✅ ${formatDate(esc.resolvedAt)}</div>` : ''}
</div>
</div>
${issue ? `
<div class="card card-p-sm mb-2" style="background:var(--bg);cursor:pointer;" onclick="openIssueDetail('${issue.id}')">
<div style="font-size:10px;color:var(--text-3);margin-bottom:3px;">ISSUE TERKAIT — ${esc.daerah}, ${esc.provinsi}</div>
<div style="font-size:12px;font-weight:500;color:var(--text-1);">${issue.id}: ${issue.judul.slice(0,70)}${issue.judul.length>70?'...':''}</div>
</div>
` : ''}
<div style="font-size:12px;color:var(--text-2);line-height:1.6;margin-bottom:10px;">${esc.notes}</div>
<div class="flex justify-between items-center">
<div style="font-size:11px;color:var(--text-3);">Dibuat oleh: ${esc.createdBy}</div>
${!isResolved ? `
<div style="display:flex;gap:6px;">
${esc.status === 'pending' ? `
<button class="btn btn-success btn-xs" onclick="approveEscalation('${esc.id}')">✓ Setujui</button>
<button class="btn btn-destructive btn-xs" onclick="rejectEscalation('${esc.id}')">✕ Tolak</button>
` : ''}
${esc.status === 'approved' ? `
<button class="btn btn-success btn-xs" onclick="resolveEscalation('${esc.id}')">✅ Selesaikan</button>
` : ''}
</div>
` : ''}
</div>
</div>
`;
}
function approveEscalation(id) {
const e = APP_STATE.escalations.find(e => e.id === id);
if (!e) return;
e.status = 'approved';
showToast('Eskalasi Disetujui', `${id} telah disetujui dan diteruskan`, 'success');
renderEscalasiPage();
}
function rejectEscalation(id) {
const e = APP_STATE.escalations.find(e => e.id === id);
if (!e) return;
e.status = 'rejected';
showToast('Eskalasi Ditolak', `${id} ditolak`, 'warning');
renderEscalasiPage();
}
function resolveEscalation(id) {
const e = APP_STATE.escalations.find(e => e.id === id);
if (!e) return;
e.status = 'resolved';
e.resolvedAt = new Date().toISOString().split('T')[0];
showToast('Eskalasi Diselesaikan', `${id} berhasil diselesaikan`, 'success');
renderEscalasiPage();
updateIssuesBadge();
}
function openNewEscalasiModal() {
// Simple prompt-based creation
const issueList = APP_STATE.issues
.filter(i => i.status !== 'resolved')
.map(i => `${i.id}: ${i.judul.slice(0,50)}`).join('\n');
const issueId = prompt(`Masukkan ID issue untuk dieskalasi:\n\nIssue aktif:\n${issueList}`);
if (!issueId) return;
const issue = APP_STATE.issues.find(i => i.id.toUpperCase() === issueId.toUpperCase().trim());
if (!issue) { showToast('Issue Tidak Ditemukan', `ID ${issueId} tidak ditemukan`, 'error'); return; }
const levelInput = prompt('Pilih level eskalasi:\nL1 - Daerah\nL2 - KSP Internal\nL3 - Kementerian/Lembaga\nL4 - Penegakan Hukum', 'L2');
const level = (levelInput || 'L2').toUpperCase().trim();
if (!ESCALATION_LEVELS[level]) { showToast('Level Tidak Valid', 'Pilih L1, L2, L3, atau L4', 'error'); return; }
const assignTo = prompt('Ditujukan kepada:', 'KSP Div. Pengawasan');
const notes = prompt('Catatan eskalasi:', `Eskalasi dari issue ${issue.id}`);
const id = `ESC-${Date.now().toString().slice(-4)}`;
const now = new Date().toISOString().split('T')[0];
const ts = new Date().toLocaleTimeString('id-ID') + ' WIB';
const newEsc = {
id, issueId: issue.id, programId: issue.programId,
level, levelName: ESCALATION_LEVELS[level].label,
assignTo: assignTo || 'KSP Div. Pengawasan',
notes: notes || `Eskalasi dari issue ${issue.id}`,
status: 'pending', createdBy: 'Admin KSP',
createdAt: now, resolvedAt: null,
daerah: issue.daerah, provinsi: issue.provinsi
};
APP_STATE.escalations.push(newEsc);
issue.escalationIds.push(id);
issue.timeline.push({ time: ts, action: `Eskalasi ${level} dibuat ke ${assignTo}`, actor: 'Admin KSP', type: 'escalated' });
issue.status = 'in_progress';
showToast('Eskalasi Dibuat', `${id} berhasil dibuat`, 'success');
renderEscalasiPage();
}