feat(dashboard): Week 3 AC 4 (bonus) - music analytics page with Chart.js#17
Merged
Conversation
- getTopArtists: COUNT(DISTINCT title) via $queryRaw (was counting rows, not unique tracks) - getMusicProductivity: GROUP BY artist+title + SUM(listenedMs); endedAt null fallback uses capturedAt+listenedMs duration instead of NOW() - getMusicTotals: totalTracks counts distinct (artist,title) pairs via groupBy (was counting all capture rows)
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Implements the bonus AC 4 for Week 3: a
/dashboard/musicpage thatvisualises the correlation between music listening and coding activity.
Data is sourced from existing
Track+Event+Sessiontables —no new migrations required.
Changes
Code
types/music-stats.ts—MusicStatsresponse type and sub-types (TopArtist,TopTrack,ProductivityRow,HourBucket)server/music.ts— five query functions (getTopArtists,getTopTracks,getMusicProductivity,getHourlyPattern,getMusicTotals); all aggregate correctly across sessionsapp/api/v1/music/stats/route.ts—GET /api/v1/music/stats?range=...; cookie-first auth + Bearer fallback; Zod query validationapp/dashboard/music/page.tsx— Server Component auth guardapp/dashboard/music/music-client.tsx—"use client", TanStack Query, range picker, summary stats, empty state, skeletonapp/dashboard/music/top-artists-chart.tsx— horizontal Bar (Chart.js), cyan,COUNT(DISTINCT title)per artistapp/dashboard/music/productivity-chart.tsx— horizontal Bar (Chart.js), purple, events/min while track was activeapp/dashboard/music/hourly-chart.tsx— grouped vertical Bar (Chart.js), 0–23 hours, listening min + event countserver/range.ts— extractedresolveRangefromserver/reports.ts(shared by both reports and music routes)header-nav.tsx/mobile-nav.tsx— added MUSIC nav itemTooling / config
Docs / plan
plans/week3/AC4-music-analytics.md— full design rationale, architecture, commit map, and verified decisions (updated post-review with logic fix notes)Design decisions
resolveRangeextracted toserver/range.ts— both the reports and music routes need it; moving it prevents duplication and avoids a circular import.getTopArtistsvia$queryRaw— PrismagroupByonly supportsCOUNT(field)(all rows);COUNT(DISTINCT title)requires raw SQL to correctly count unique songs per artist.getMusicProductivityGROUP BY artist, title+SUM— Track is@@unique([sessionId, artist, title]), so the same song across 5 sessions produces 5 rows; without aggregation each session appeared as a separate bar in the chart.endedAt = nullfallback — replacedCOALESCE(endedAt, NOW())withCOALESCE(endedAt, capturedAt + make_interval(secs => listenedMs/1000)); the old fallback stretched the JOIN window to the present moment and inflatedperMinfor tracks whose end was not recorded.getMusicTotals.totalTracks— usesgroupBy [artist, title]to count distinct songs;_count: { id }was counting capture rows (one per session per track), not unique titles.Verification
npx tsc --noEmit(dashboard) — cleannpm run lint(dashboard) — cleannpm run typecheck(extension) — n/a (no extension changes)/dashboard/musicloads with correct data for LAST 7 DAYS/dashboard/musicOut of scope (future PRs)
Closes