first commit
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// 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();
|
||||
}
|
||||
Reference in New Issue
Block a user