Prima importazione
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* Dashboard View
|
||||
* Shows stats overview, distribution charts, and recent tickets.
|
||||
*/
|
||||
const DashboardView = {
|
||||
async render() {
|
||||
const container = document.getElementById('view-container');
|
||||
container.innerHTML = '<div class="loading-screen"><div class="spinner"></div><p>Caricamento dashboard...</p></div>';
|
||||
|
||||
try {
|
||||
const stats = await App.api('/api/dashboard/stats');
|
||||
|
||||
const maxByState = Math.max(...(stats.by_state || []).map(s => parseInt(s.count)), 1);
|
||||
const maxByPriority = Math.max(...(stats.by_priority || []).map(s => parseInt(s.count)), 1);
|
||||
const maxByQueue = Math.max(...(stats.by_queue || []).map(s => parseInt(s.count)), 1);
|
||||
|
||||
container.innerHTML = `
|
||||
<!-- Stats Cards -->
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card accent">
|
||||
<div class="stat-label">Ticket Aperti</div>
|
||||
<div class="stat-value">${stats.total_open}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Creati Oggi</div>
|
||||
<div class="stat-value">${stats.created_today}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Creati Settimana</div>
|
||||
<div class="stat-value">${stats.created_this_week}</div>
|
||||
</div>
|
||||
<div class="stat-card ${stats.escalated > 0 ? 'danger' : ''}">
|
||||
<div class="stat-label">Escalated</div>
|
||||
<div class="stat-value">${stats.escalated}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Distributions -->
|
||||
<div class="distribution-section">
|
||||
<div class="card">
|
||||
<div class="card-title">Per Stato</div>
|
||||
${(stats.by_state || []).map(s => `
|
||||
<div class="dist-bar-container">
|
||||
<div class="dist-bar-header">
|
||||
<span class="dist-bar-label">${s.state}</span>
|
||||
<span class="dist-bar-value">${s.count}</span>
|
||||
</div>
|
||||
<div class="dist-bar-track">
|
||||
<div class="dist-bar-fill" style="width: ${(parseInt(s.count) / maxByState * 100).toFixed(1)}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
${(stats.by_state || []).length === 0 ? '<p style="color:var(--text-tertiary);font-size:0.85rem;">Nessun dato</p>' : ''}
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">Per Priorità</div>
|
||||
${(stats.by_priority || []).map((p, idx) => `
|
||||
<div class="dist-bar-container">
|
||||
<div class="dist-bar-header">
|
||||
<span class="dist-bar-label">${p.priority}</span>
|
||||
<span class="dist-bar-value">${p.count}</span>
|
||||
</div>
|
||||
<div class="dist-bar-track">
|
||||
<div class="dist-bar-fill" style="width: ${(parseInt(p.count) / maxByPriority * 100).toFixed(1)}%; background: linear-gradient(90deg, var(--priority-${idx + 1}-text, var(--accent-primary)), var(--accent-secondary));"></div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
${(stats.by_priority || []).length === 0 ? '<p style="color:var(--text-tertiary);font-size:0.85rem;">Nessun dato</p>' : ''}
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">Per Coda (Top 10)</div>
|
||||
${(stats.by_queue || []).map(q => `
|
||||
<div class="dist-bar-container">
|
||||
<div class="dist-bar-header">
|
||||
<span class="dist-bar-label">${q.queue}</span>
|
||||
<span class="dist-bar-value">${q.count}</span>
|
||||
</div>
|
||||
<div class="dist-bar-track">
|
||||
<div class="dist-bar-fill" style="width: ${(parseInt(q.count) / maxByQueue * 100).toFixed(1)}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
${(stats.by_queue || []).length === 0 ? '<p style="color:var(--text-tertiary);font-size:0.85rem;">Nessun dato</p>' : ''}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Tickets -->
|
||||
<div class="card">
|
||||
<div class="card-title">Ticket Recenti</div>
|
||||
${(stats.recent_tickets || []).length > 0 ? `
|
||||
<table class="recent-tickets-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Numero</th>
|
||||
<th>Titolo</th>
|
||||
<th>Stato</th>
|
||||
<th>Priorità</th>
|
||||
<th>Coda</th>
|
||||
<th>Data</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${stats.recent_tickets.map(t => `
|
||||
<tr onclick="window.location.hash='#/tickets/${t.id}'">
|
||||
<td><span class="ticket-tn">${t.tn}</span></td>
|
||||
<td class="ticket-title-cell">${App.escapeHtml(t.title || '')}</td>
|
||||
<td><span class="badge badge-state" data-state-type="${(t.state_name || '').toLowerCase()}">${t.state_name}</span></td>
|
||||
<td><span class="badge badge-priority" data-priority="${t.priority_name ? App.priorityIndex(t.priority_name) : 3}">${t.priority_name}</span></td>
|
||||
<td><span class="badge badge-queue">${t.queue_name}</span></td>
|
||||
<td style="color:var(--text-tertiary);font-size:0.78rem;">${App.formatDate(t.create_time)}</td>
|
||||
</tr>
|
||||
`).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
` : `
|
||||
<div class="empty-state">
|
||||
<div class="empty-state-icon">📭</div>
|
||||
<div class="empty-state-text">Nessun ticket recente</div>
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Animate bars after render
|
||||
requestAnimationFrame(() => {
|
||||
document.querySelectorAll('.dist-bar-fill').forEach(bar => {
|
||||
const w = bar.style.width;
|
||||
bar.style.width = '0%';
|
||||
requestAnimationFrame(() => { bar.style.width = w; });
|
||||
});
|
||||
});
|
||||
|
||||
// Update open ticket count in sidebar badge
|
||||
const badge = document.getElementById('open-ticket-count');
|
||||
if (badge && stats.total_open > 0) {
|
||||
badge.textContent = stats.total_open;
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
container.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<div class="empty-state-icon">⚠️</div>
|
||||
<div class="empty-state-text">Errore caricamento dashboard</div>
|
||||
<div class="empty-state-sub">${App.escapeHtml(err.message)}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user