152 lines
7.0 KiB
JavaScript
152 lines
7.0 KiB
JavaScript
// ═══════════════════════════════════════════════════════════════════
|
|
// ISSUES PAGE — Kanban + List view for issue management
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
|
|
let _issueView = 'kanban'; // 'kanban' | 'list'
|
|
|
|
function renderIssuesPage() {
|
|
const filtered = getFilteredIssues();
|
|
if (_issueView === 'kanban') renderKanbanView(filtered);
|
|
else renderListView(filtered);
|
|
}
|
|
|
|
function applyIssueFilters() {
|
|
renderIssuesPage();
|
|
}
|
|
|
|
function switchIssueView(view) {
|
|
_issueView = view;
|
|
document.getElementById('kanban-view-btn').classList.toggle('active', view === 'kanban');
|
|
document.getElementById('list-view-btn').classList.toggle('active', view === 'list');
|
|
renderIssuesPage();
|
|
}
|
|
|
|
function getFilteredIssues() {
|
|
const search = (document.getElementById('issues-search')?.value || '').toLowerCase().trim();
|
|
const program = document.getElementById('issues-filter-program')?.value || 'all';
|
|
const severity= document.getElementById('issues-filter-severity')?.value || 'all';
|
|
|
|
return APP_STATE.issues.filter(i => {
|
|
const matchSearch = !search || i.judul.toLowerCase().includes(search) || i.daerah.toLowerCase().includes(search) || i.provinsi.toLowerCase().includes(search) || i.id.toLowerCase().includes(search);
|
|
const matchProg = program === 'all' || i.programId === program;
|
|
const matchSev = severity === 'all' || i.severity === severity;
|
|
return matchSearch && matchProg && matchSev;
|
|
});
|
|
}
|
|
|
|
// ─── KANBAN VIEW ─────────────────────────────────────────────────
|
|
function renderKanbanView(issues) {
|
|
const cols = [
|
|
{ key: 'open', label: 'Terbuka', icon: '🔴', color: 'var(--red)' },
|
|
{ key: 'in_progress', label: 'Diproses', icon: '🔵', color: 'var(--blue)' },
|
|
{ key: 'resolved', label: 'Selesai', icon: '✅', color: 'var(--green)' }
|
|
];
|
|
document.getElementById('issues-body').innerHTML = `
|
|
<div class="kanban-view">
|
|
${cols.map(col => {
|
|
const colIssues = issues.filter(i => i.status === col.key);
|
|
return `
|
|
<div class="kanban-col">
|
|
<div class="kanban-col-header">
|
|
<div class="kanban-col-title">
|
|
<span>${col.icon}</span>
|
|
<span>${col.label}</span>
|
|
</div>
|
|
<span class="col-count">${colIssues.length}</span>
|
|
</div>
|
|
<div class="kanban-cards">
|
|
${colIssues.length === 0
|
|
? `<div class="empty-state" style="padding:32px 16px;"><div class="empty-icon" style="font-size:24px;opacity:0.3;">📭</div><div class="empty-desc" style="font-size:11px;">Tidak ada issue</div></div>`
|
|
: colIssues.map(i => renderIssueCard(i)).join('')
|
|
}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}).join('')}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
function renderIssueCard(issue) {
|
|
const prog = getProgramById(issue.programId);
|
|
const survCount = issue.surveyIds.length;
|
|
const escCount = issue.escalationIds.length;
|
|
return `
|
|
<div class="issue-card ${issue.severity}-card fade-in" onclick="openIssueDetail('${issue.id}')">
|
|
<div class="ic-header">
|
|
<span class="ic-id">${issue.id}</span>
|
|
${getSevBadge(issue.severity)}
|
|
</div>
|
|
<div class="flex gap-2 items-center mb-2">
|
|
${getProgramBadge(issue.programId)}
|
|
<span style="font-size:10px;color:var(--text-3);">📍 ${issue.daerah}</span>
|
|
</div>
|
|
<div class="ic-title">${issue.judul}</div>
|
|
<div class="ic-desc">${issue.deskripsi}</div>
|
|
<div class="ic-footer">
|
|
<div style="display:flex;gap:8px;align-items:center;">
|
|
${survCount > 0 ? `<span style="font-size:10px;color:var(--purple);">📋 ${survCount} survey</span>` : ''}
|
|
${escCount > 0 ? `<span style="font-size:10px;color:var(--orange);">🔺 ${escCount} eskalasi</span>` : ''}
|
|
</div>
|
|
<div class="ic-meta">${formatDate(issue.createdAt)}</div>
|
|
</div>
|
|
<div style="margin-top:8px;padding-top:8px;border-top:1px solid var(--border);font-size:10px;color:var(--text-3);" class="truncate">
|
|
👤 ${issue.assignee}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// ─── LIST / TABLE VIEW ────────────────────────────────────────────
|
|
function renderListView(issues) {
|
|
document.getElementById('issues-body').innerHTML = `
|
|
<div class="list-view">
|
|
${issues.length === 0
|
|
? `<div class="empty-state"><div class="empty-icon">🔍</div><div class="empty-title">Tidak ada issue</div><div class="empty-desc">Tidak ada issue yang cocok dengan filter yang dipilih</div></div>`
|
|
: `<div class="table-wrap">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>Judul Issue</th>
|
|
<th>Program</th>
|
|
<th>Daerah</th>
|
|
<th>Keparahan</th>
|
|
<th>Status</th>
|
|
<th>Ditugaskan ke</th>
|
|
<th>Tanggal</th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
${issues.map(i => `
|
|
<tr style="cursor:pointer;" onclick="openIssueDetail('${i.id}')">
|
|
<td><span style="font-family:monospace;font-size:11px;color:var(--text-3);">${i.id}</span></td>
|
|
<td style="max-width:240px;">
|
|
<div style="font-size:12px;font-weight:500;color:var(--text-1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:220px;" title="${i.judul}">${i.judul}</div>
|
|
</td>
|
|
<td>${getProgramBadge(i.programId)}</td>
|
|
<td>
|
|
<div style="font-size:12px;font-weight:500;">${i.daerah}</div>
|
|
<div class="td-muted">${i.provinsi}</div>
|
|
</td>
|
|
<td>${getSevBadge(i.severity)}</td>
|
|
<td>${getStatBadge(i.status)}</td>
|
|
<td class="td-muted truncate" style="max-width:140px;">${i.assignee}</td>
|
|
<td class="td-muted">${formatDate(i.createdAt)}</td>
|
|
<td>
|
|
<div style="display:flex;gap:4px;">
|
|
${i.surveyIds.length > 0 ? `<span title="${i.surveyIds.length} survey" style="font-size:12px;">📋</span>` : ''}
|
|
${i.escalationIds.length > 0 ? `<span title="${i.escalationIds.length} eskalasi" style="font-size:12px;">🔺</span>` : ''}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
`).join('')}
|
|
</tbody>
|
|
</table>
|
|
</div>`
|
|
}
|
|
</div>
|
|
`;
|
|
}
|