Add episode progress bar to title cards for in-progress TV shows

Shows a subtle progress bar at the bottom of title cards indicating
watched/total episodes, with a tooltip for exact counts. Uses a single
efficient SQL query with JOINs to batch-fetch progress for all visible
titles on the Explore page.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-05 10:22:05 -05:00
co-authored by Claude Opus 4.6
parent 1b0cad1c83
commit cd63d4f92a
7 changed files with 140 additions and 8 deletions
+43 -1
View File
@@ -1,4 +1,4 @@
import { and, eq, inArray } from "drizzle-orm";
import { and, count, eq, inArray, sql } from "drizzle-orm";
import { db } from "@/lib/db/client";
import {
episodes,
@@ -330,6 +330,48 @@ export function getUserStatusesByTmdbIds(
return result;
}
export function getEpisodeProgressByTmdbIds(
userId: string,
tmdbIds: { tmdbId: number; type: string }[],
): Record<string, { watched: number; total: number }> {
const tvIds = tmdbIds.filter((t) => t.type === "tv").map((t) => t.tmdbId);
if (tvIds.length === 0) return {};
const rows = db
.select({
tmdbId: titles.tmdbId,
totalEpisodes: count(episodes.id),
watchedEpisodes:
sql<number>`sum(case when ${userEpisodeWatches.id} is not null then 1 else 0 end)`.as(
"watchedEpisodes",
),
})
.from(titles)
.innerJoin(seasons, eq(seasons.titleId, titles.id))
.innerJoin(episodes, eq(episodes.seasonId, seasons.id))
.leftJoin(
userEpisodeWatches,
and(
eq(userEpisodeWatches.episodeId, episodes.id),
eq(userEpisodeWatches.userId, userId),
),
)
.where(and(inArray(titles.tmdbId, tvIds), eq(titles.type, "tv")))
.groupBy(titles.tmdbId)
.all();
const result: Record<string, { watched: number; total: number }> = {};
for (const row of rows) {
if (row.watchedEpisodes > 0) {
result[`${row.tmdbId}-tv`] = {
watched: row.watchedEpisodes,
total: row.totalEpisodes,
};
}
}
return result;
}
export function getUserTitleInfo(userId: string, titleId: string) {
const status = db
.select()