Files
otrs-turbo/public/js/views/dashboard.js
T

188 lines
8.4 KiB
JavaScript

/**
* 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 style="display:flex; justify-content:space-between; align-items:center; margin-bottom:var(--space-md); flex-wrap:wrap; gap:var(--space-xs);">
<div class="card-title" style="margin-bottom:0;">Ticket Recenti</div>
<div style="display:flex; align-items:center; gap:var(--space-xs); font-size:0.85rem; color:var(--text-secondary);">
<span>Mostra:</span>
<select id="dashboard-preview-limit" class="form-select" style="padding: 4px 28px 4px 8px; font-size: 0.8rem; height: 28px; min-width: 70px; margin: 0; background-position: right 8px center; border-color: var(--border-light);">
<option value="5" ${stats.preview_limit === 5 ? 'selected' : ''}>5</option>
<option value="10" ${stats.preview_limit === 10 ? 'selected' : ''}>10</option>
<option value="20" ${stats.preview_limit === 20 ? 'selected' : ''}>20</option>
<option value="50" ${stats.preview_limit === 50 ? 'selected' : ''}>50</option>
<option value="100" ${stats.preview_limit === 100 ? 'selected' : ''}>100</option>
</select>
</div>
</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; });
});
});
// Bind preview limit change event
const limitSelect = document.getElementById('dashboard-preview-limit');
if (limitSelect) {
limitSelect.addEventListener('change', async () => {
const newLimit = parseInt(limitSelect.value, 10);
try {
await App.api('/api/dashboard/settings', {
method: 'POST',
body: JSON.stringify({ preview_limit: newLimit }),
});
Toast.success(`Limite anteprima aggiornato a ${newLimit} ticket!`);
this.render();
} catch (err) {
Toast.error('Errore durante il salvataggio dell\'impostazione: ' + err.message);
}
});
}
// Update open ticket count in sidebar badge
const badge = document.getElementById('open-ticket-count');
if (badge) {
badge.textContent = stats.total_open > 0 ? stats.total_open : '';
}
// Update my ticket count in sidebar badge
const myBadge = document.getElementById('my-ticket-count');
if (myBadge) {
myBadge.textContent = stats.total_my_open > 0 ? stats.total_my_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>
`;
}
},
};