summaryrefslogtreecommitdiff
path: root/static
diff options
context:
space:
mode:
authorTyler Hoang <tyler@tylerhoang.xyz>2026-05-06 18:05:07 -0700
committerTyler Hoang <tyler@tylerhoang.xyz>2026-05-06 18:05:07 -0700
commitead38fdb13abb406065cef0743d7e411cb27eaf3 (patch)
tree56f5bc31be37833505726ac61cec0cfd19a7be92 /static
parent2d298f982408f222ad344b2aa9c18bbe7dc70f12 (diff)
Add genre tracking and year-in-review improvements
Adds genre field to Film model with TMDB enrichment. Genres populate from TMDB detail fetch during add/edit and bulk enrichment. Genre metadata displays on film cards, detail page (Production section), stats page (top genres panel), and year-in-review (by decade and genre breakdowns). Auto-detects rewatches when adding films via TMDB autocomplete - if a film with the same TMDB ID already exists in diary, pre-fills rewatch checkbox and count. Rewatch count now displays on film cards as "Rewatch #N". Stats page now shows: - Top genres (most watched) - Film decades (sorted chronologically) - Already shows: directors, companions, star distribution, rewatch rate Year-in-review shows decades and genres alongside monthly activity and companions. Bulk enrichment endpoint (/data/enrich-posters) now fetches missing genre metadata along with posters and TMDB IDs. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Diffstat (limited to 'static')
-rw-r--r--static/app.js44
1 files changed, 42 insertions, 2 deletions
diff --git a/static/app.js b/static/app.js
index 7cd501a..fae2dbd 100644
--- a/static/app.js
+++ b/static/app.js
@@ -95,7 +95,7 @@ const renderMessage = (message) => {
tmdbResults.appendChild(node);
};
-const applyResult = (film) => {
+const applyResult = async (film) => {
setValue("#title", film.title || "");
setValue("#original_title", film.original_title || "");
setValue("#director", film.director || "");
@@ -105,6 +105,39 @@ const applyResult = (film) => {
setValue("#runtime", film.runtime || "");
setValue("#tmdb_id", film.tmdb_id || "");
setPoster(film.poster_url);
+
+ // Check if this is the add form (not edit)
+ const isAddForm = window.location.pathname.endsWith("/films/new");
+
+ if (film.tmdb_id) {
+ try {
+ // Fetch full detail for genre and check for rewatches in parallel
+ const [detailResponse, rewatchResponse] = await Promise.all([
+ fetch(`/tmdb/detail/${film.tmdb_id}`),
+ isAddForm ? fetch(`/films/check-rewatch?tmdb_id=${film.tmdb_id}`) : Promise.resolve(null),
+ ]);
+
+ if (detailResponse.ok) {
+ const detail = await detailResponse.json();
+ setValue("#genre", detail.genre || "");
+ }
+
+ if (rewatchResponse && rewatchResponse.ok) {
+ const rw = await rewatchResponse.json();
+ if (rw.count > 0) {
+ const rewatchCheckbox = document.getElementById("rewatch");
+ if (rewatchCheckbox) {
+ rewatchCheckbox.checked = true;
+ setValue("#rewatch_count", String(rw.count));
+ }
+ }
+ }
+ } catch (error) {
+ // Fail silently if detail/rewatch fetch fails
+ console.error("Failed to fetch details", error);
+ }
+ }
+
clearResults();
};
@@ -120,7 +153,14 @@ const renderResults = (films) => {
const button = document.createElement("button");
button.type = "button";
button.className = "tmdb-result";
- button.addEventListener("click", () => applyResult(film));
+ button.addEventListener("click", async () => {
+ button.disabled = true;
+ try {
+ await applyResult(film);
+ } finally {
+ button.disabled = false;
+ }
+ });
if (film.poster_url) {
const image = document.createElement("img");