Add filmography sorting and role-count UI

Introduce filmography sorting controls and role-count indicators across cast list and detail views. In CastDetailView: add sort state (sortBy/sortOrder), compute sortedFilmography, add UI for choosing sort key and toggling order, and show a filmography count badge; import new icons and useState. In CastView: add a new 'roleCount' sort option (default sort is now roleCount desc), persist/restore it from localStorage, adjust reset/default filter logic and hasActiveFilters check, render role-count badges for each person, and tweak layout (flex-1) for better truncation. These changes make it easier to surface prolific cast members and sort filmography entries by year, title, or role.
This commit is contained in:
Lars Behrends
2026-04-11 00:47:04 +02:00
parent b36b72b8e0
commit 52d272c701
2 changed files with 65 additions and 14 deletions

View File

@@ -22,11 +22,11 @@ export default function CastView({ onPersonClick, enabledCategories, itemsPerPag
const [searchQuery, setSearchQuery] = useState(() => {
return localStorage.getItem('castSearchQuery') || '';
});
const [sortBy, setSortBy] = useState<'name' | 'role' | 'birthDate' | 'height'>(() => {
return (localStorage.getItem('castSortBy') as 'name' | 'role' | 'birthDate' | 'height') || 'name';
const [sortBy, setSortBy] = useState<'name' | 'role' | 'birthDate' | 'height' | 'roleCount'>(() => {
return (localStorage.getItem('castSortBy') as 'name' | 'role' | 'birthDate' | 'height' | 'roleCount') || 'roleCount';
});
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>(() => {
return (localStorage.getItem('castSortOrder') as 'asc' | 'desc') || 'asc';
return (localStorage.getItem('castSortOrder') as 'asc' | 'desc') || 'desc';
});
const [filterOccupation, setFilterOccupation] = useState<string>(() => {
return localStorage.getItem('castFilterOccupation') || '';
@@ -68,13 +68,13 @@ export default function CastView({ onPersonClick, enabledCategories, itemsPerPag
const handleResetFilters = () => {
setSearchQuery('');
setSortBy('name');
setSortOrder('asc');
setSortBy('roleCount');
setSortOrder('desc');
setFilterOccupation('');
setFilterMediaType('');
};
const hasActiveFilters = searchQuery || filterOccupation || filterMediaType || sortBy !== 'name' || sortOrder !== 'asc';
const hasActiveFilters = searchQuery || filterOccupation || filterMediaType || sortBy !== 'roleCount' || sortOrder !== 'desc';
useEffect(() => {
const loadCast = async () => {
@@ -137,6 +137,10 @@ export default function CastView({ onPersonClick, enabledCategories, itemsPerPag
const heightA = a.height || 0;
const heightB = b.height || 0;
comparison = heightA - heightB;
} else if (sortBy === 'roleCount') {
const roleCountA = a.filmography?.length || 0;
const roleCountB = b.filmography?.length || 0;
comparison = roleCountA - roleCountB;
}
return sortOrder === 'desc' ? -comparison : comparison;
@@ -247,6 +251,7 @@ export default function CastView({ onPersonClick, enabledCategories, itemsPerPag
<option value="role">Role</option>
<option value="birthDate">Birth Date</option>
<option value="height">Height</option>
<option value="roleCount">Role Count</option>
</select>
</div>
<div>
@@ -345,7 +350,7 @@ export default function CastView({ onPersonClick, enabledCategories, itemsPerPag
referrerPolicy="no-referrer"
/>
</div>
<div className="min-w-0">
<div className="min-w-0 flex-1">
<h3 className="font-black text-foreground truncate group-hover:text-[#6d28d9] transition-colors">
{person.name}
</h3>
@@ -353,6 +358,11 @@ export default function CastView({ onPersonClick, enabledCategories, itemsPerPag
{person.role}
</p>
</div>
{person.filmography && person.filmography.length > 0 && (
<Badge variant="outline" className="border-[#6d28d9]/30 text-[#6d28d9] font-bold text-[10px] px-2 py-0.5 shrink-0">
{person.filmography.length}
</Badge>
)}
</div>
{person.filmography && person.filmography.length > 0 && (