web: update web interface for tv eps missing date
Local Docker Build (Dev) / build-dev (push) Successful in 4s
Local Docker Build (Dev) / build-dev (push) Successful in 4s
This commit is contained in:
+6
-1
@@ -18,7 +18,7 @@ from api.models import (
|
|||||||
from api.web_routes import (
|
from api.web_routes import (
|
||||||
get_movies_list, get_tv_series_list, get_series_episodes, get_series_sources, get_missing_dates_report,
|
get_movies_list, get_tv_series_list, get_series_episodes, get_series_sources, get_missing_dates_report,
|
||||||
get_dashboard_stats, update_movie_date, update_episode_date, bulk_update_source,
|
get_dashboard_stats, update_movie_date, update_episode_date, bulk_update_source,
|
||||||
get_movie_date_options, get_episode_date_options
|
get_movie_date_options, get_episode_date_options, debug_series_date_distribution
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -1059,6 +1059,11 @@ def register_routes(app, dependencies: dict):
|
|||||||
"""Get list of available episode sources for filtering"""
|
"""Get list of available episode sources for filtering"""
|
||||||
return await get_series_sources(dependencies)
|
return await get_series_sources(dependencies)
|
||||||
|
|
||||||
|
@app.get("/api/debug/series-date-distribution")
|
||||||
|
async def _debug_series_dates():
|
||||||
|
"""Debug endpoint showing TV series date distribution"""
|
||||||
|
return await debug_series_date_distribution(dependencies)
|
||||||
|
|
||||||
@app.get("/api/reports/missing-dates")
|
@app.get("/api/reports/missing-dates")
|
||||||
async def _missing_dates_report():
|
async def _missing_dates_report():
|
||||||
"""Get report of content missing dateadded"""
|
"""Get report of content missing dateadded"""
|
||||||
|
|||||||
@@ -209,6 +209,65 @@ async def get_series_sources(dependencies: dict):
|
|||||||
return {"sources": sources}
|
return {"sources": sources}
|
||||||
|
|
||||||
|
|
||||||
|
async def debug_series_date_distribution(dependencies: dict):
|
||||||
|
"""Debug function to show TV series date distribution"""
|
||||||
|
db = dependencies["db"]
|
||||||
|
|
||||||
|
with db.get_connection() as conn:
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# Get series with episode date statistics
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT
|
||||||
|
s.imdb_id,
|
||||||
|
s.path,
|
||||||
|
COUNT(e.episode) as total_episodes,
|
||||||
|
COUNT(CASE WHEN e.dateadded IS NOT NULL AND e.dateadded != '' THEN 1 END) as episodes_with_dates,
|
||||||
|
COUNT(CASE WHEN e.dateadded IS NULL OR e.dateadded = '' THEN 1 END) as episodes_without_dates
|
||||||
|
FROM series s
|
||||||
|
LEFT JOIN episodes e ON s.imdb_id = e.imdb_id
|
||||||
|
GROUP BY s.imdb_id, s.path
|
||||||
|
HAVING COUNT(e.episode) > 0
|
||||||
|
ORDER BY total_episodes DESC
|
||||||
|
LIMIT 50
|
||||||
|
""")
|
||||||
|
|
||||||
|
series_stats = []
|
||||||
|
complete_count = 0
|
||||||
|
incomplete_count = 0
|
||||||
|
none_count = 0
|
||||||
|
|
||||||
|
for row in cursor.fetchall():
|
||||||
|
stats = dict(row)
|
||||||
|
total = stats['total_episodes']
|
||||||
|
with_dates = stats['episodes_with_dates']
|
||||||
|
without_dates = stats['episodes_without_dates']
|
||||||
|
|
||||||
|
if without_dates == 0:
|
||||||
|
category = "complete"
|
||||||
|
complete_count += 1
|
||||||
|
elif with_dates == 0:
|
||||||
|
category = "none"
|
||||||
|
none_count += 1
|
||||||
|
else:
|
||||||
|
category = "incomplete"
|
||||||
|
incomplete_count += 1
|
||||||
|
|
||||||
|
stats['category'] = category
|
||||||
|
stats['title'] = stats['path'].split('/')[-1] if stats['path'] else stats['imdb_id']
|
||||||
|
series_stats.append(stats)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"series_sample": series_stats[:20], # First 20 for debugging
|
||||||
|
"distribution": {
|
||||||
|
"complete": complete_count,
|
||||||
|
"incomplete": incomplete_count,
|
||||||
|
"none": none_count,
|
||||||
|
"total": complete_count + incomplete_count + none_count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async def get_series_episodes(dependencies: dict, imdb_id: str):
|
async def get_series_episodes(dependencies: dict, imdb_id: str):
|
||||||
"""Get all episodes for a specific TV series"""
|
"""Get all episodes for a specific TV series"""
|
||||||
db = dependencies["db"]
|
db = dependencies["db"]
|
||||||
|
|||||||
+64
-6
@@ -114,13 +114,16 @@ async function loadDashboard() {
|
|||||||
function updateDashboardStats() {
|
function updateDashboardStats() {
|
||||||
if (!dashboardData) return;
|
if (!dashboardData) return;
|
||||||
|
|
||||||
|
// Debug: Log dashboard data to see what fields are available
|
||||||
|
console.log('Dashboard data received:', dashboardData);
|
||||||
|
|
||||||
const moviesTotal = dashboardData.movies_total || 0;
|
const moviesTotal = dashboardData.movies_total || 0;
|
||||||
const moviesWithDates = dashboardData.movies_with_dates || 0;
|
const moviesWithDates = dashboardData.movies_with_dates || 0;
|
||||||
const moviesWithoutDates = dashboardData.movies_without_dates || 0;
|
const moviesWithoutDates = dashboardData.movies_without_dates || (moviesTotal - moviesWithDates);
|
||||||
|
|
||||||
const episodesTotal = dashboardData.episodes_total || 0;
|
const episodesTotal = dashboardData.episodes_total || 0;
|
||||||
const episodesWithDates = dashboardData.episodes_with_dates || 0;
|
const episodesWithDates = dashboardData.episodes_with_dates || 0;
|
||||||
const episodesWithoutDates = dashboardData.episodes_without_dates || 0;
|
const episodesWithoutDates = dashboardData.episodes_without_dates || (episodesTotal - episodesWithDates);
|
||||||
|
|
||||||
document.getElementById('movies-total').textContent = moviesTotal;
|
document.getElementById('movies-total').textContent = moviesTotal;
|
||||||
document.getElementById('movies-with-dates').textContent = `${moviesWithDates} with dates, ${moviesWithoutDates} without`;
|
document.getElementById('movies-with-dates').textContent = `${moviesWithDates} with dates, ${moviesWithoutDates} without`;
|
||||||
@@ -429,14 +432,39 @@ async function viewSeriesEpisodes(imdbId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function showEpisodesModal(data) {
|
function showEpisodesModal(data) {
|
||||||
|
// Calculate statistics
|
||||||
|
const totalEpisodes = data.episodes.length;
|
||||||
|
const episodesWithDates = data.episodes.filter(ep => ep.dateadded && ep.dateadded.trim() !== '').length;
|
||||||
|
const episodesWithoutDates = totalEpisodes - episodesWithDates;
|
||||||
|
const episodesWithVideo = data.episodes.filter(ep => ep.has_video_file).length;
|
||||||
|
|
||||||
const modalHtml = `
|
const modalHtml = `
|
||||||
<div class="modal active" id="episodes-modal">
|
<div class="modal active" id="episodes-modal">
|
||||||
<div class="modal-content" style="max-width: 800px;">
|
<div class="modal-content" style="max-width: 900px;">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h3>${escapeHtml(data.series.title)} - Episodes</h3>
|
<h3>${escapeHtml(data.series.title)} - Episodes</h3>
|
||||||
<button class="modal-close" onclick="closeEpisodesModal()">×</button>
|
<button class="modal-close" onclick="closeEpisodesModal()">×</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
<div class="episode-stats" style="display: flex; gap: 20px; margin-bottom: 20px; padding: 15px; background: #f8f9fa; border-radius: 5px;">
|
||||||
|
<div><strong>Total Episodes:</strong> ${totalEpisodes}</div>
|
||||||
|
<div><strong>With Dates:</strong> ${episodesWithDates}</div>
|
||||||
|
<div style="color: #dc3545;"><strong>Missing Dates:</strong> ${episodesWithoutDates}</div>
|
||||||
|
<div><strong>With Video:</strong> ${episodesWithVideo}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="episode-filters" style="margin-bottom: 15px;">
|
||||||
|
<label style="margin-right: 15px;">
|
||||||
|
<input type="radio" name="episode-filter" value="all" checked onchange="filterEpisodes('all')"> Show All
|
||||||
|
</label>
|
||||||
|
<label style="margin-right: 15px;">
|
||||||
|
<input type="radio" name="episode-filter" value="missing" onchange="filterEpisodes('missing')"> Missing Dates Only
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<input type="radio" name="episode-filter" value="has-dates" onchange="filterEpisodes('has-dates')"> With Dates Only
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="table-container">
|
<div class="table-container">
|
||||||
<table class="data-table">
|
<table class="data-table">
|
||||||
<thead>
|
<thead>
|
||||||
@@ -449,18 +477,24 @@ function showEpisodesModal(data) {
|
|||||||
<th>Actions</th>
|
<th>Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody id="episodes-table-body">
|
||||||
${data.episodes.map(episode => {
|
${data.episodes.map(episode => {
|
||||||
const dateadded = episode.dateadded ? formatDateTime(episode.dateadded) : '';
|
const dateadded = episode.dateadded ? formatDateTime(episode.dateadded) : '';
|
||||||
const hasVideoBadge = episode.has_video_file ?
|
const hasVideoBadge = episode.has_video_file ?
|
||||||
'<span class="badge badge-success">Yes</span>' :
|
'<span class="badge badge-success">Yes</span>' :
|
||||||
'<span class="badge badge-secondary">No</span>';
|
'<span class="badge badge-secondary">No</span>';
|
||||||
|
|
||||||
|
const missingDate = !episode.dateadded || episode.dateadded.trim() === '';
|
||||||
|
const rowClass = missingDate ? 'missing-date-row' : '';
|
||||||
|
const dateCell = missingDate ?
|
||||||
|
'<td style="background-color: #ffebee; color: #c62828;"><strong>MISSING</strong></td>' :
|
||||||
|
`<td>${dateadded}</td>`;
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<tr>
|
<tr class="${rowClass}" data-has-date="${!missingDate}">
|
||||||
<td>S${episode.season.toString().padStart(2, '0')}E${episode.episode.toString().padStart(2, '0')}</td>
|
<td>S${episode.season.toString().padStart(2, '0')}E${episode.episode.toString().padStart(2, '0')}</td>
|
||||||
<td>${episode.aired || '-'}</td>
|
<td>${episode.aired || '-'}</td>
|
||||||
<td>${dateadded || '-'}</td>
|
${dateCell}
|
||||||
<td><span class="badge badge-secondary">${episode.source || 'Unknown'}</span></td>
|
<td><span class="badge badge-secondary">${episode.source || 'Unknown'}</span></td>
|
||||||
<td>${hasVideoBadge}</td>
|
<td>${hasVideoBadge}</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -482,6 +516,30 @@ function showEpisodesModal(data) {
|
|||||||
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function filterEpisodes(filterType) {
|
||||||
|
const rows = document.querySelectorAll('#episodes-table-body tr');
|
||||||
|
|
||||||
|
rows.forEach(row => {
|
||||||
|
const hasDate = row.getAttribute('data-has-date') === 'true';
|
||||||
|
let shouldShow = true;
|
||||||
|
|
||||||
|
switch (filterType) {
|
||||||
|
case 'missing':
|
||||||
|
shouldShow = !hasDate;
|
||||||
|
break;
|
||||||
|
case 'has-dates':
|
||||||
|
shouldShow = hasDate;
|
||||||
|
break;
|
||||||
|
case 'all':
|
||||||
|
default:
|
||||||
|
shouldShow = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
row.style.display = shouldShow ? '' : 'none';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function closeEpisodesModal() {
|
function closeEpisodesModal() {
|
||||||
const modal = document.getElementById('episodes-modal');
|
const modal = document.getElementById('episodes-modal');
|
||||||
if (modal) {
|
if (modal) {
|
||||||
|
|||||||
Reference in New Issue
Block a user