Replaces the three fixed star chips with the Stars component as a min-average selector, combinable with the verdict chips
This commit is contained in:
@@ -23,9 +23,6 @@ const FEEDBACK_FILTERS = [
|
||||
{ key: 'accept', label: '👍' },
|
||||
{ key: 'reject', label: '👎' },
|
||||
{ key: 'undecided', label: 'Undecided' },
|
||||
{ key: 'star3', label: '★ 3+' },
|
||||
{ key: 'star4', label: '★ 4+' },
|
||||
{ key: 'star5', label: '★ 5' },
|
||||
]
|
||||
|
||||
// Expiry convention, in one place for the create form and the row editor:
|
||||
@@ -432,28 +429,27 @@ export default function AlbumPage() {
|
||||
return ratings.reduce((sum, r) => sum + r.rating, 0) / ratings.length
|
||||
}
|
||||
|
||||
// Filter over client feedback: verdicts, or minimum average star rating.
|
||||
// Two combinable filter dimensions over client feedback: verdict chips
|
||||
// plus a minimum-average-stars threshold (0 = off).
|
||||
const [filter, setFilter] = useState('all')
|
||||
const matchesFilter = (p, key) => {
|
||||
const [minStars, setMinStars] = useState(0)
|
||||
const matchesVerdict = (p, key) => {
|
||||
if (key === 'all') return true
|
||||
const verdicts = feedback[p.id]?.verdicts || []
|
||||
if (key === 'accept') return verdicts.some((v) => v.verdict === 'accept')
|
||||
if (key === 'reject') return verdicts.some((v) => v.verdict === 'reject')
|
||||
if (key === 'undecided') return verdicts.length === 0
|
||||
return (avgRating(p.id) ?? 0) >= Number(key.slice(4))
|
||||
return verdicts.length === 0
|
||||
}
|
||||
const shown = ready.filter((p) => matchesFilter(p, filter))
|
||||
const shown = ready.filter(
|
||||
(p) => matchesVerdict(p, filter) && (minStars === 0 || (avgRating(p.id) ?? 0) >= minStars),
|
||||
)
|
||||
const filterCounts = useMemo(() => {
|
||||
const counts = { all: ready.length, accept: 0, reject: 0, undecided: 0, star3: 0, star4: 0, star5: 0 }
|
||||
const counts = { all: ready.length, accept: 0, reject: 0, undecided: 0 }
|
||||
for (const p of ready) {
|
||||
const verdicts = feedback[p.id]?.verdicts || []
|
||||
if (verdicts.some((v) => v.verdict === 'accept')) counts.accept += 1
|
||||
if (verdicts.some((v) => v.verdict === 'reject')) counts.reject += 1
|
||||
if (verdicts.length === 0) counts.undecided += 1
|
||||
const avg = avgRating(p.id) ?? 0
|
||||
if (avg >= 3) counts.star3 += 1
|
||||
if (avg >= 4) counts.star4 += 1
|
||||
if (avg >= 5) counts.star5 += 1
|
||||
}
|
||||
return counts
|
||||
}, [detail])
|
||||
@@ -566,6 +562,13 @@ export default function AlbumPage() {
|
||||
{f.label} {filterCounts[f.key]}
|
||||
</button>
|
||||
))}
|
||||
<span
|
||||
className={`filter-stars${minStars > 0 ? ' active' : ''}`}
|
||||
title="Minimum average rating — click a star to filter, click it again to clear"
|
||||
>
|
||||
<Stars value={minStars} onChange={(n) => setMinStars(n)} small />
|
||||
{minStars > 0 && <span className="muted">≥ {minStars}</span>}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -675,6 +675,22 @@ progress {
|
||||
justify-content: flex-start;
|
||||
margin: 0 0 0.75rem;
|
||||
}
|
||||
.filter-stars {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--panel-2);
|
||||
border-radius: 999px;
|
||||
padding: 0.1rem 0.7rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.filter-stars.active {
|
||||
border-color: var(--accent);
|
||||
}
|
||||
.filter-stars .star {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* transient action feedback (keyboard votes that navigate away) */
|
||||
.action-flash {
|
||||
|
||||
Reference in New Issue
Block a user