From 4279408876268f4960c98492d3814f5475e36e38 Mon Sep 17 00:00:00 2001 From: Tyler Hoang Date: Tue, 12 May 2026 03:15:17 -0700 Subject: Add stats totals, runtime summary, and duplicate detection on add form - Stats page now shows total films watched and total runtime (formatted as Xd Yh) in an overview panel above the world map - /stats/data endpoint includes total_runtime_minutes in payload - New GET /films/find endpoint returns all shelf matches for a tmdb_id - Add film form shows an inline notice when the selected TMDB film is already logged, with shelf name, date, and a link to the entry - Update CLAUDE.md and README to reflect current auth, OMDb, and router/service structure Co-Authored-By: Claude Sonnet 4.6 --- static/app.js | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) (limited to 'static/app.js') diff --git a/static/app.js b/static/app.js index 938cf70..04997a0 100644 --- a/static/app.js +++ b/static/app.js @@ -111,15 +111,17 @@ const applyResult = async (film) => { setValue("#tmdb_id", film.tmdb_id || ""); setPoster(film.poster_url); - // Check if this is the add form (not edit) + const duplicateNotice = document.getElementById("duplicate-notice"); + if (duplicateNotice) duplicateNotice.hidden = true; + 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([ + const [detailResponse, rewatchResponse, findResponse] = await Promise.all([ fetch(`/tmdb/detail/${film.tmdb_id}`), isAddForm ? fetch(`/films/check-rewatch?tmdb_id=${film.tmdb_id}`) : Promise.resolve(null), + isAddForm ? fetch(`/films/find?tmdb_id=${film.tmdb_id}`) : Promise.resolve(null), ]); if (detailResponse.ok) { @@ -137,8 +139,23 @@ const applyResult = async (film) => { } } } + + if (findResponse && findResponse.ok && duplicateNotice) { + const found = await findResponse.json(); + if (found.matches && found.matches.length > 0) { + const shelfLabel = { diary: "Diary", queue: "Queue", abandoned: "Abandoned" }; + const parts = found.matches.map((m) => { + const label = shelfLabel[m.shelf] || m.shelf; + const date = m.date_watched + ? ` · ${new Date(m.date_watched + "T00:00:00").toLocaleDateString(undefined, { month: "short", year: "numeric" })}` + : ""; + return `${label}${date}`; + }); + duplicateNotice.innerHTML = `Already logged — ${parts.join(", ")}`; + duplicateNotice.hidden = false; + } + } } catch (error) { - // Fail silently if detail/rewatch fetch fails console.error("Failed to fetch details", error); } } -- cgit v1.3-2-g0d8e