Files
iptvnator/apps/web/src/app/settings/settings-dashboard-section.component.html
T
4grayandClaude Fable 5 d77a6a28cb feat(dashboard): TMDB trending rail and hero enrichment (backdrop, badges, S/E)
Two dashboard additions, both async after first paint so the page renders
exactly as fast as before:

Trending rail ("Trending this week", dashboardRails.tmdbTrending toggle,
default on, rendered only when TMDB is opted in AND the Electron DB
worker is available):
- TmdbTrendingService fetches /trending/{movie,tv}/week (one request
  each, cached one day per language in tmdb_metadata under
  trending:week), merges by popularity; exposed via the enrichment
  facade (getTrendingWeek)
- DashboardTrendingService matches the titles against imported Xtream
  playlists with ONE batched DB_MATCH_TITLES request, applying the same
  two-tier + year-compatibility rule as actor pages; matched cards show
  the playlist name and navigate straight to the detail view, unmatched
  cards open the global search prefilled (?q=)
- The load fires only after the dashboard's own recent/favorites data
  is in (never competes for the worker at startup) and once per session
- DashboardRailCard gained optional queryParams for the search links

Hero enrichment:
- DashboardHeroTmdbService patches the hero with a TMDB backdrop (only
  when the item has none), a rating badge and up to two genre chips —
  via the enrichment facade, so previously opened items resolve from
  the SQLite cache without network; memoized per title per session,
  staleness-guarded against hero changes in flight
- Series heroes show the tracked "S{n}·E{n}" badge from the playback
  position; the watch-progress bar no longer applies to live heroes

Settings: new dashboardRails.tmdbTrending toggle in Settings > Dashboard.
i18n: 3 new keys translated into all 17 locales via tools/i18n patches.

Tests: dashboard-trending.service.spec.ts (gating, matching, year guard,
single-flight); settings fixtures updated. Docs updated
(tmdb-metadata-enrichment.md Dashboard Integration section, CLAUDE.md).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 11:18:31 +02:00

183 lines
6.4 KiB
HTML

<section
class="settings-group"
[class.settings-group--active]="activeSection() === 'dashboard'"
id="dashboard"
[formGroup]="form()"
>
<header class="settings-group__header">
<div class="settings-group__header-icon">
<mat-icon>dashboard</mat-icon>
</div>
<div class="settings-group__header-text">
<h3>{{ 'SETTINGS.DASHBOARD' | translate }}</h3>
<p>{{ 'SETTINGS.DASHBOARD_SUBTITLE' | translate }}</p>
</div>
</header>
<div class="setting-item">
<div class="setting-item__meta">
<h4>{{ 'SETTINGS.SHOW_DASHBOARD' | translate }}</h4>
<p>
{{ 'SETTINGS.SHOW_DASHBOARD_DESCRIPTION' | translate }}
</p>
</div>
<div class="setting-item__control setting-item__toggle">
<mat-checkbox
formControlName="showDashboard"
data-test-id="toggle-show-dashboard"
></mat-checkbox>
</div>
</div>
<div formGroupName="dashboardRails">
<div class="setting-item">
<div class="setting-item__meta">
<h4>{{ 'SETTINGS.DASHBOARD_HERO' | translate }}</h4>
<p>
{{ 'SETTINGS.DASHBOARD_HERO_DESCRIPTION' | translate }}
</p>
</div>
<div class="setting-item__control setting-item__toggle">
<mat-checkbox
formControlName="hero"
data-test-id="toggle-dashboard-hero"
></mat-checkbox>
</div>
</div>
<div class="setting-item">
<div class="setting-item__meta">
<h4>{{ 'SETTINGS.DASHBOARD_RAIL_CONTINUE' | translate }}</h4>
<p>
{{
'SETTINGS.DASHBOARD_RAIL_CONTINUE_DESCRIPTION'
| translate
}}
</p>
</div>
<div class="setting-item__control setting-item__toggle">
<mat-checkbox
formControlName="continueWatching"
data-test-id="toggle-dashboard-rail-continue-watching"
></mat-checkbox>
</div>
</div>
<div class="setting-item">
<div class="setting-item__meta">
<h4>{{
'SETTINGS.DASHBOARD_RAIL_LIVE_FAVORITES' | translate
}}</h4>
<p>
{{
'SETTINGS.DASHBOARD_RAIL_LIVE_FAVORITES_DESCRIPTION'
| translate
}}
</p>
</div>
<div class="setting-item__control setting-item__toggle">
<mat-checkbox
formControlName="liveFavorites"
data-test-id="toggle-dashboard-rail-live-favorites"
></mat-checkbox>
</div>
</div>
<div class="setting-item">
<div class="setting-item__meta">
<h4>{{
'SETTINGS.DASHBOARD_RAIL_RECENT_LIVE' | translate
}}</h4>
<p>
{{
'SETTINGS.DASHBOARD_RAIL_RECENT_LIVE_DESCRIPTION'
| translate
}}
</p>
</div>
<div class="setting-item__control setting-item__toggle">
<mat-checkbox
formControlName="recentlyWatchedLive"
data-test-id="toggle-dashboard-rail-recently-watched-live"
></mat-checkbox>
</div>
</div>
<div class="setting-item">
<div class="setting-item__meta">
<h4>{{
'SETTINGS.DASHBOARD_RAIL_FAVORITE_VOD' | translate
}}</h4>
<p>
{{
'SETTINGS.DASHBOARD_RAIL_FAVORITE_VOD_DESCRIPTION'
| translate
}}
</p>
</div>
<div class="setting-item__control setting-item__toggle">
<mat-checkbox
formControlName="favoriteMoviesAndSeries"
data-test-id="toggle-dashboard-rail-favorite-movies-and-series"
></mat-checkbox>
</div>
</div>
<div class="setting-item">
<div class="setting-item__meta">
<h4>{{ 'SETTINGS.DASHBOARD_RAIL_SOURCES' | translate }}</h4>
<p>
{{
'SETTINGS.DASHBOARD_RAIL_SOURCES_DESCRIPTION'
| translate
}}
</p>
</div>
<div class="setting-item__control setting-item__toggle">
<mat-checkbox
formControlName="recentSources"
data-test-id="toggle-dashboard-rail-recent-sources"
></mat-checkbox>
</div>
</div>
<div class="setting-item">
<div class="setting-item__meta">
<h4>{{
'SETTINGS.DASHBOARD_RAIL_XTREAM_ADDED' | translate
}}</h4>
<p>
{{
'SETTINGS.DASHBOARD_RAIL_XTREAM_ADDED_DESCRIPTION'
| translate
}}
</p>
</div>
<div class="setting-item__control setting-item__toggle">
<mat-checkbox
formControlName="xtreamRecentlyAdded"
data-test-id="toggle-dashboard-rail-xtream-recently-added"
></mat-checkbox>
</div>
</div>
<div class="setting-item">
<div class="setting-item__meta">
<h4>{{ 'SETTINGS.DASHBOARD_RAIL_TMDB_TRENDING' | translate }}</h4>
<p>
{{
'SETTINGS.DASHBOARD_RAIL_TMDB_TRENDING_DESCRIPTION'
| translate
}}
</p>
</div>
<div class="setting-item__control setting-item__toggle">
<mat-checkbox
formControlName="tmdbTrending"
data-test-id="toggle-dashboard-rail-tmdb-trending"
></mat-checkbox>
</div>
</div>
</div>
</section>