function formatDate(dateStr) { const d = new Date(dateStr); return d.toLocaleDateString('fr-FR', { year: 'numeric', month: 'long', day: 'numeric' }); } function showError(message) { document.getElementById('error').innerHTML = `
${escapeHtml(message)}
`; } function statTile(value, label) { return `
${value}
${escapeHtml(label)}
`; } function adminUserRowTemplate(u) { return `
${avatarHtml(u, 'avatar-sm')}
${escapeHtml(u.name)}${u.isAdmin ? ' admin' : ''}
Membre depuis ${formatDate(u.createdAt)} · Dernière connexion ${formatDate(u.lastSeenAt)}
🎵 ${u.songsAdded} 💡 ${u.suggestionsProposed} ✔ ${u.votesCast}
`; } (async function init() { const me = await initNav('admin'); if (!me.isAdmin) { window.location.href = '/index.html'; return; } try { const stats = await api.get('/api/admin/stats'); const container = document.getElementById('content'); container.innerHTML = `

Administration

Vue d'ensemble de l'activité et des données de l'application.

Vue d'ensemble

${statTile(stats.userCount, 'Utilisateurs')} ${statTile(stats.songs.totalSongs, 'Morceaux au répertoire')} ${statTile(stats.songs.distinctArtists, 'Artistes différents')} ${statTile(stats.songs.tutorialsAdded, 'Tutoriels ajoutés')} ${statTile(stats.suggestions.pending, 'Suggestions en attente')} ${statTile(stats.suggestions.approved, 'Suggestions approuvées')} ${statTile(stats.suggestions.totalVotes, 'Votes exprimés')} ${statTile(stats.concerts.pastConcerts, 'Concerts passés')} ${statTile(stats.concerts.upcomingConcerts, 'Concerts à venir')} ${statTile(stats.concerts.avgSongsPerConcert, 'Morceaux / concert (moy.)')}

Activité des utilisateurs

La date de dernière connexion est approximative (dernière synchronisation du profil, pas un journal de connexion précis).

${stats.users.map(adminUserRowTemplate).join('')}
`; } catch (err) { showError(err.message); } })();