Skip to content

Commit aba4e5a

Browse files
committed
add statistics endpoint for today's top songs and implement pagination and sorting in song and user stats
- Added new API endpoint `/api/statistics-today` to fetch top songs for the current day. - Introduced `Pagination` component for paginating song and user statistics. - Implemented `SortableTh` component for sortable table headers in song and user stats. - Enhanced `SongStats` and `UserStats` pages to support searching, sorting, and exporting to CSV. - Updated styles for pagination, toolbar, and responsive design adjustments. - Refactored data fetching logic to accommodate new statistics endpoint and improved data handling.
1 parent ace2788 commit aba4e5a

13 files changed

Lines changed: 970 additions & 214 deletions

File tree

.claude/settings.local.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@
33
"allow": [
44
"WebFetch(domain:github.com)",
55
"WebSearch",
6-
"WebFetch(domain:support.discord.com)"
6+
"WebFetch(domain:support.discord.com)",
7+
"Skill(dataviz)",
8+
"Bash(node scripts/validate_palette.js \"#ff6b6b,#4ecdc4,#45b7d1,#f9ca24,#f0932b,#eb4d4b,#6ab04c,#9c88ff\" --mode dark)",
9+
"PowerShell(node \"C:\\\\Users\\\\stand\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\bundled-skills\\\\2.1.206\\\\086a8e9e37ae6507cf6e474311e79744\\\\dataviz\\\\scripts\\\\validate_palette.js\" \"#ff6b6b,#4ecdc4,#45b7d1,#f9ca24,#f0932b,#eb4d4b,#6ab04c,#9c88ff\" --mode dark)",
10+
"PowerShell(node \"C:\\\\Users\\\\stand\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\bundled-skills\\\\2.1.206\\\\086a8e9e37ae6507cf6e474311e79744\\\\dataviz\\\\scripts\\\\validate_palette.js\" \"#3987e5,#199e70,#c98500,#008300,#9085e9,#e66767,#d55181,#d95926\" --mode dark --surface \"#366472\")",
11+
"PowerShell(node \"C:\\\\Users\\\\stand\\\\AppData\\\\Local\\\\Temp\\\\claude\\\\G--personal-Discord-Music-Bot\\\\42ca7a0b-f5a7-4a2e-a708-2f026c81c580\\\\scratchpad\\\\mock-api.js\")"
712
]
813
}
914
}

src/UI/Api/ControllerExtensions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ public void AddApiController()
1717
await statisticsService.GetAllSongsAsync())
1818
.WithName("GetStatisticsAll");
1919

20+
app.MapGet("/api/statistics-today",
21+
async (IStatisticsService statisticsService, int? limit) =>
22+
await statisticsService.GetTopSongsAsync(true, limit ?? 100))
23+
.WithName("GetStatisticsToday");
24+
2025
app.MapGet("/api/users",
2126
async (IUserService userService) => await userService.GetAllUsersAsync())
2227
.WithName("GetAllUsers");
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
interface PaginationProps {
2+
page: number;
3+
pageCount: number;
4+
totalItems: number;
5+
pageSize: number;
6+
onPageChange: (page: number) => void;
7+
}
8+
9+
export function Pagination({
10+
page,
11+
pageCount,
12+
totalItems,
13+
pageSize,
14+
onPageChange,
15+
}: PaginationProps) {
16+
if (pageCount <= 1) return null;
17+
18+
const start = (page - 1) * pageSize + 1;
19+
const end = Math.min(page * pageSize, totalItems);
20+
21+
return (
22+
<div className="pagination">
23+
<span className="pagination-info">
24+
Showing {start}-{end} of {totalItems}
25+
</span>
26+
<div className="pagination-controls">
27+
<button
28+
className="glass-button pagination-button"
29+
disabled={page <= 1}
30+
onClick={() => onPageChange(page - 1)}
31+
>
32+
Prev
33+
</button>
34+
<span className="pagination-page">
35+
{page} / {pageCount}
36+
</span>
37+
<button
38+
className="glass-button pagination-button"
39+
disabled={page >= pageCount}
40+
onClick={() => onPageChange(page + 1)}
41+
>
42+
Next
43+
</button>
44+
</div>
45+
</div>
46+
);
47+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
interface SortableThProps {
2+
label: string;
3+
active: boolean;
4+
direction: 'asc' | 'desc';
5+
onSort: () => void;
6+
className?: string;
7+
}
8+
9+
export function SortableTh({
10+
label,
11+
active,
12+
direction,
13+
onSort,
14+
className,
15+
}: SortableThProps) {
16+
return (
17+
<th
18+
className={`sortable ${className ?? ''}`}
19+
onClick={onSort}
20+
title={`Sort by ${label}`}
21+
aria-sort={
22+
active ? (direction === 'asc' ? 'ascending' : 'descending') : 'none'
23+
}
24+
>
25+
<span className="sort-header">
26+
{label}
27+
<span
28+
className={`sort-arrow ${active ? `active ${direction}` : ''}`}
29+
aria-hidden="true"
30+
/>
31+
</span>
32+
</th>
33+
);
34+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { useSyncExternalStore } from 'react';
2+
3+
const MOBILE_QUERY = '(max-width: 768px)';
4+
5+
function subscribe(callback: () => void) {
6+
const mql = window.matchMedia(MOBILE_QUERY);
7+
mql.addEventListener('change', callback);
8+
return () => mql.removeEventListener('change', callback);
9+
}
10+
11+
function getSnapshot() {
12+
return window.matchMedia(MOBILE_QUERY).matches;
13+
}
14+
15+
export function useIsMobile(): boolean {
16+
return useSyncExternalStore(subscribe, getSnapshot, () => false);
17+
}

0 commit comments

Comments
 (0)