Add star rating display on agent cards and rating filter

Show inline star icons on agent listing cards so ratings are visible
without opening the detail modal. Add rating filter dropdown (5, 4+,
3+, 2+, 1+, Unrated) alongside existing filters.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
nickviljoen 2026-02-24 10:16:49 +02:00
parent cba9e57db9
commit 3bc757b99c

View file

@ -110,6 +110,17 @@
<option value="Pencil Agents">Pencil Agents</option>
</select>
</div>
<div class="col-md-2 mb-3 mb-md-0">
<select class="form-select" id="ratingFilter">
<option value="">All Ratings</option>
<option value="5">5 Stars</option>
<option value="4">4+ Stars</option>
<option value="3">3+ Stars</option>
<option value="2">2+ Stars</option>
<option value="1">1+ Stars</option>
<option value="unrated">Unrated</option>
</select>
</div>
<div class="col-md-2 mb-3 mb-md-0">
<select class="form-select" id="sortBy">
<option value="created_at">Sort by Created Date</option>
@ -121,8 +132,8 @@
<option value="unique_users">Sort by Unique Users</option>
</select>
</div>
<div class="col-md-2">
<button class="btn btn-outline-primary w-100" id="refreshBtn">
<div class="col-auto">
<button class="btn btn-outline-primary" id="refreshBtn">
<i class="fas fa-sync-alt"></i>
</button>
</div>
@ -453,6 +464,13 @@
</div>
<style>
.card-star-rating {
display: inline-flex;
align-items: center;
gap: 1px;
font-size: 0.9rem;
line-height: 1;
}
.agent-card {
border: 1px solid #e9ecef;
border-radius: 12px;
@ -563,6 +581,7 @@ function setupEventListeners() {
document.getElementById('statusFilter').addEventListener('change', filterAgents);
document.getElementById('auditFilter').addEventListener('change', filterAgents);
document.getElementById('disciplineFilter').addEventListener('change', filterAgents);
document.getElementById('ratingFilter').addEventListener('change', filterAgents);
document.getElementById('sortBy').addEventListener('change', sortAndDisplayAgents);
document.getElementById('refreshBtn').addEventListener('click', function() {
// Reload both datasets to ensure counts are accurate
@ -718,7 +737,7 @@ function displayAgents(agentsToShow) {
</span>
${agent.agent_version ? `<span class="badge bg-light text-dark">v${agent.agent_version}</span>` : ''}
${agent.discipline ? `<span class="badge bg-purple" title="Discipline"><i class="fas fa-layer-group me-1"></i>${agent.discipline}</span>` : ''}
${agent.rating ? `<span class="badge bg-warning text-dark" title="Rating: ${agent.rating}/5"><i class="fas fa-star me-1"></i>${agent.rating.toFixed(1)}${agent.rating_count ? ' (' + agent.rating_count + ')' : ''}</span>` : ''}
${agent.rating ? `<span class="card-star-rating" title="Rating: ${agent.rating}/5">${getCardStars(agent.rating)} <small class="text-muted">${agent.rating.toFixed(1)}${agent.rating_count ? ' (' + agent.rating_count + ')' : ''}</small></span>` : ''}
${agent.quality_audit_status ? '<span class="badge bg-success" title="Quality Audited"><i class="fas fa-certificate"></i></span>' : ''}
${agent.quality_audit_status && agent.risk_factor ? getRiskFactorBadge(agent.risk_factor) : ''}
${agent.total_messages ? `<span class="badge bg-info text-white" title="Total Messages"><i class="fas fa-comments me-1"></i>${agent.total_messages.toLocaleString()}</span>` : ''}
@ -1196,6 +1215,7 @@ function filterAgents() {
const statusFilter = document.getElementById('statusFilter').value;
const auditFilter = document.getElementById('auditFilter').value;
const disciplineFilter = document.getElementById('disciplineFilter').value;
const ratingFilter = document.getElementById('ratingFilter').value;
let filtered = agents.filter(agent => {
const matchesSearch = agent.agent_name.toLowerCase().includes(searchTerm) ||
@ -1206,7 +1226,10 @@ function filterAgents() {
(auditFilter === 'audited' && agent.quality_audit_status) ||
(auditFilter === 'not_audited' && !agent.quality_audit_status);
const matchesDiscipline = !disciplineFilter || agent.discipline === disciplineFilter;
return matchesSearch && matchesStatus && matchesAudit && matchesDiscipline;
const matchesRating = !ratingFilter ||
(ratingFilter === 'unrated' && !agent.rating) ||
(ratingFilter !== 'unrated' && agent.rating && agent.rating >= parseFloat(ratingFilter));
return matchesSearch && matchesStatus && matchesAudit && matchesDiscipline && matchesRating;
});
displayAgents(filtered);
@ -1502,6 +1525,20 @@ function renderUsageChart(chartData) {
}
// Star rating helper functions
function getCardStars(rating) {
let stars = '';
for (let i = 1; i <= 5; i++) {
if (i <= Math.floor(rating)) {
stars += '<span class="text-warning">&#9733;</span>';
} else if (i - 0.5 <= rating) {
stars += '<span class="text-warning">&#9733;</span>';
} else {
stars += '<span class="text-muted">&#9734;</span>';
}
}
return stars;
}
function getStarDisplay(rating, ratingCount) {
if (!rating) return '<span class="text-muted">Not yet rated</span>';
let stars = '';