Refactor user detail page: split components, unify formatters

Break the 615-line UserDetail.tsx into focused sub-components
(header, stat cards, activity chart, request history, open alerts)
and extract shared utilities to lib/ (format, userChart,
enrichRequests). Promote storage load (GB/hr) to a stat card and
collapse the chart UX to a single metric picker. Add server-wide
average reference line alongside the user's own on every metric,
and link request titles to their Seerr pages.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-04-19 10:00:21 -04:00
parent b2c1642065
commit 74588e50f6
18 changed files with 994 additions and 664 deletions
+46
View File
@@ -0,0 +1,46 @@
import { OverseerrRequest, MediaEntry, EnrichedRequest } from "@/lib/types";
import { bytesToGB } from "@/lib/aggregate";
export function enrichRequests(
userRequests: OverseerrRequest[],
radarrMap: Map<number, MediaEntry>,
sonarrMap: Map<number, MediaEntry>,
seerrBaseUrl?: string
): EnrichedRequest[] {
return userRequests.map((req) => {
let sizeOnDisk = 0;
let title = req.media.title ?? "";
if (req.type === "movie") {
const entry = radarrMap.get(req.media.tmdbId);
sizeOnDisk = entry?.sizeOnDisk ?? 0;
if (entry?.title) title = entry.title;
} else if (req.type === "tv" && req.media.tvdbId) {
const entry = sonarrMap.get(req.media.tvdbId);
sizeOnDisk = entry?.sizeOnDisk ?? 0;
if (entry?.title) title = entry.title;
}
if (!title) {
title = req.type === "movie"
? `Movie #${req.media.tmdbId}`
: `Show #${req.media.tmdbId}`;
}
const seerrUrl = seerrBaseUrl
? `${seerrBaseUrl}/${req.type === "movie" ? "movie" : "tv"}/${req.media.tmdbId}`
: undefined;
return {
id: req.id,
type: req.type,
status: req.status,
createdAt: req.createdAt,
mediaId: req.type === "movie" ? req.media.tmdbId : (req.media.tvdbId ?? 0),
title,
sizeOnDisk,
sizeGB: bytesToGB(sizeOnDisk),
seerrUrl,
};
});
}