This commit is contained in:
@@ -0,0 +1,791 @@
|
||||
// NFOGuard Web Interface JavaScript
|
||||
|
||||
// Global state
|
||||
let currentTab = 'dashboard';
|
||||
let currentMoviesPage = 1;
|
||||
let currentSeriesPage = 1;
|
||||
let dashboardData = null;
|
||||
|
||||
// Initialize app
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initializeTabs();
|
||||
initializeEventListeners();
|
||||
loadDashboard();
|
||||
});
|
||||
|
||||
// Tab management
|
||||
function initializeTabs() {
|
||||
const tabButtons = document.querySelectorAll('.nav-tab');
|
||||
const tabContents = document.querySelectorAll('.tab-content');
|
||||
|
||||
tabButtons.forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
const tabName = this.dataset.tab;
|
||||
switchTab(tabName);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function switchTab(tabName) {
|
||||
// Update button states
|
||||
document.querySelectorAll('.nav-tab').forEach(btn => btn.classList.remove('active'));
|
||||
document.querySelector(`[data-tab="${tabName}"]`).classList.add('active');
|
||||
|
||||
// Update content
|
||||
document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
|
||||
document.getElementById(tabName).classList.add('active');
|
||||
|
||||
currentTab = tabName;
|
||||
|
||||
// Load tab-specific data
|
||||
switch(tabName) {
|
||||
case 'dashboard':
|
||||
loadDashboard();
|
||||
break;
|
||||
case 'movies':
|
||||
loadMovies();
|
||||
break;
|
||||
case 'tv':
|
||||
loadSeries();
|
||||
break;
|
||||
case 'reports':
|
||||
loadReport();
|
||||
break;
|
||||
case 'tools':
|
||||
loadDetailedStats();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Event listeners
|
||||
function initializeEventListeners() {
|
||||
// Search inputs
|
||||
document.getElementById('movies-search').addEventListener('input', debounce(loadMovies, 500));
|
||||
document.getElementById('series-search').addEventListener('input', debounce(loadSeries, 500));
|
||||
|
||||
// Filter dropdowns
|
||||
document.getElementById('movies-filter-date').addEventListener('change', loadMovies);
|
||||
document.getElementById('movies-filter-source').addEventListener('change', loadMovies);
|
||||
|
||||
// Forms
|
||||
document.getElementById('edit-form').addEventListener('submit', handleEditSubmit);
|
||||
document.getElementById('bulk-update-form').addEventListener('submit', handleBulkUpdate);
|
||||
}
|
||||
|
||||
// API calls
|
||||
async function apiCall(endpoint, options = {}) {
|
||||
try {
|
||||
const response = await fetch(endpoint, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers
|
||||
},
|
||||
...options
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('API call failed:', error);
|
||||
showToast(`API Error: ${error.message}`, 'error');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Dashboard
|
||||
async function loadDashboard() {
|
||||
try {
|
||||
dashboardData = await apiCall('/api/dashboard');
|
||||
updateDashboardStats();
|
||||
updateDashboardCharts();
|
||||
} catch (error) {
|
||||
console.error('Failed to load dashboard:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function updateDashboardStats() {
|
||||
if (!dashboardData) return;
|
||||
|
||||
document.getElementById('movies-total').textContent = dashboardData.movies_total || 0;
|
||||
document.getElementById('movies-with-dates').textContent = `${dashboardData.movies_with_dates || 0} with dates`;
|
||||
|
||||
document.getElementById('series-total').textContent = dashboardData.series_count || 0;
|
||||
document.getElementById('episodes-total').textContent = `${dashboardData.episodes_total || 0} episodes`;
|
||||
|
||||
const missingTotal = (dashboardData.movies_missing_dates || 0) + (dashboardData.episodes_missing_dates || 0);
|
||||
document.getElementById('missing-dates-total').textContent = missingTotal;
|
||||
|
||||
const noValidTotal = (dashboardData.movies_no_valid_source || 0) + (dashboardData.episodes_no_valid_source || 0);
|
||||
document.getElementById('no-valid-source-total').textContent = `${noValidTotal} no valid source`;
|
||||
|
||||
document.getElementById('recent-activity').textContent = dashboardData.recent_activity_count || 0;
|
||||
}
|
||||
|
||||
function updateDashboardCharts() {
|
||||
if (!dashboardData) return;
|
||||
|
||||
// Movie sources chart
|
||||
const movieChart = document.getElementById('movie-sources-chart');
|
||||
if (dashboardData.movie_sources && dashboardData.movie_sources.length > 0) {
|
||||
movieChart.innerHTML = createSimpleChart(dashboardData.movie_sources);
|
||||
} else {
|
||||
movieChart.innerHTML = '<p>No movie source data available</p>';
|
||||
}
|
||||
|
||||
// Episode sources chart
|
||||
const episodeChart = document.getElementById('episode-sources-chart');
|
||||
if (dashboardData.episode_sources && dashboardData.episode_sources.length > 0) {
|
||||
episodeChart.innerHTML = createSimpleChart(dashboardData.episode_sources);
|
||||
} else {
|
||||
episodeChart.innerHTML = '<p>No episode source data available</p>';
|
||||
}
|
||||
}
|
||||
|
||||
function createSimpleChart(data) {
|
||||
const total = data.reduce((sum, item) => sum + item.count, 0);
|
||||
let html = '<div class="simple-chart">';
|
||||
|
||||
data.forEach((item, index) => {
|
||||
const percentage = ((item.count / total) * 100).toFixed(1);
|
||||
const color = getChartColor(index);
|
||||
html += `
|
||||
<div class="chart-item" style="background-color: ${color}20; border-left: 4px solid ${color};">
|
||||
<span class="chart-label">${item.source}</span>
|
||||
<span class="chart-value">${item.count} (${percentage}%)</span>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
html += '</div>';
|
||||
return html;
|
||||
}
|
||||
|
||||
function getChartColor(index) {
|
||||
const colors = ['#007bff', '#28a745', '#ffc107', '#dc3545', '#6c757d', '#17a2b8', '#6f42c1'];
|
||||
return colors[index % colors.length];
|
||||
}
|
||||
|
||||
// Movies
|
||||
async function loadMovies(page = 1) {
|
||||
const search = document.getElementById('movies-search').value;
|
||||
const hasDate = document.getElementById('movies-filter-date').value;
|
||||
const sourceFilter = document.getElementById('movies-filter-source').value;
|
||||
|
||||
const params = new URLSearchParams({
|
||||
skip: (page - 1) * 100,
|
||||
limit: 100
|
||||
});
|
||||
|
||||
if (search) params.append('search', search);
|
||||
if (hasDate) params.append('has_date', hasDate);
|
||||
if (sourceFilter) params.append('source_filter', sourceFilter);
|
||||
|
||||
try {
|
||||
const data = await apiCall(`/api/movies?${params}`);
|
||||
updateMoviesTable(data);
|
||||
updateMoviesPagination(data);
|
||||
updateMoviesSourceFilter(data);
|
||||
currentMoviesPage = page;
|
||||
} catch (error) {
|
||||
console.error('Failed to load movies:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function updateMoviesTable(data) {
|
||||
const tbody = document.getElementById('movies-tbody');
|
||||
|
||||
if (!data.movies || data.movies.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="7" class="text-center">No movies found</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = data.movies.map(movie => {
|
||||
const dateadded = movie.dateadded ? formatDateTime(movie.dateadded) : '';
|
||||
const hasDateBadge = movie.dateadded ?
|
||||
'<span class="badge badge-success">Yes</span>' :
|
||||
'<span class="badge badge-warning">No</span>';
|
||||
const hasVideoBadge = movie.has_video_file ?
|
||||
'<span class="badge badge-success">Yes</span>' :
|
||||
'<span class="badge badge-secondary">No</span>';
|
||||
|
||||
return `
|
||||
<tr>
|
||||
<td>${escapeHtml(movie.title)}</td>
|
||||
<td><code>${movie.imdb_id}</code></td>
|
||||
<td>${movie.released || '-'}</td>
|
||||
<td>${dateadded || '-'}</td>
|
||||
<td><span class="badge badge-secondary">${movie.source || 'Unknown'}</span></td>
|
||||
<td>${hasVideoBadge}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-primary" onclick="editMovie('${movie.imdb_id}', '${dateadded}', '${movie.source || ''}')">
|
||||
<i class="fas fa-edit"></i> Edit
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function updateMoviesPagination(data) {
|
||||
const pagination = document.getElementById('movies-pagination');
|
||||
|
||||
if (data.pages <= 1) {
|
||||
pagination.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
|
||||
if (data.has_prev) {
|
||||
html += `<button class="btn btn-secondary btn-sm" onclick="loadMovies(${data.page - 1})">
|
||||
<i class="fas fa-chevron-left"></i> Previous
|
||||
</button>`;
|
||||
}
|
||||
|
||||
html += `<span class="page-info">Page ${data.page} of ${data.pages}</span>`;
|
||||
|
||||
if (data.has_next) {
|
||||
html += `<button class="btn btn-secondary btn-sm" onclick="loadMovies(${data.page + 1})">
|
||||
Next <i class="fas fa-chevron-right"></i>
|
||||
</button>`;
|
||||
}
|
||||
|
||||
pagination.innerHTML = html;
|
||||
}
|
||||
|
||||
function updateMoviesSourceFilter(data) {
|
||||
// This would be populated from dashboard data
|
||||
if (dashboardData && dashboardData.movie_sources) {
|
||||
const select = document.getElementById('movies-filter-source');
|
||||
const currentValue = select.value;
|
||||
|
||||
select.innerHTML = '<option value="">All Sources</option>';
|
||||
dashboardData.movie_sources.forEach(source => {
|
||||
select.innerHTML += `<option value="${source.source}">${source.source} (${source.count})</option>`;
|
||||
});
|
||||
|
||||
select.value = currentValue;
|
||||
}
|
||||
}
|
||||
|
||||
function refreshMovies() {
|
||||
loadMovies(currentMoviesPage);
|
||||
}
|
||||
|
||||
// TV Series
|
||||
async function loadSeries(page = 1) {
|
||||
const search = document.getElementById('series-search').value;
|
||||
|
||||
const params = new URLSearchParams({
|
||||
skip: (page - 1) * 50,
|
||||
limit: 50
|
||||
});
|
||||
|
||||
if (search) params.append('search', search);
|
||||
|
||||
try {
|
||||
const data = await apiCall(`/api/series?${params}`);
|
||||
updateSeriesTable(data);
|
||||
updateSeriesPagination(data);
|
||||
currentSeriesPage = page;
|
||||
} catch (error) {
|
||||
console.error('Failed to load series:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function updateSeriesTable(data) {
|
||||
const tbody = document.getElementById('series-tbody');
|
||||
|
||||
if (!data.series || data.series.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="6" class="text-center">No series found</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = data.series.map(series => {
|
||||
const progressPercent = series.total_episodes > 0 ?
|
||||
((series.episodes_with_dates / series.total_episodes) * 100).toFixed(1) : 0;
|
||||
|
||||
return `
|
||||
<tr>
|
||||
<td>${escapeHtml(series.title)}</td>
|
||||
<td><code>${series.imdb_id}</code></td>
|
||||
<td>${series.total_episodes}</td>
|
||||
<td>
|
||||
${series.episodes_with_dates}
|
||||
<small class="text-muted">(${progressPercent}%)</small>
|
||||
</td>
|
||||
<td>${series.episodes_with_video}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-primary" onclick="viewSeriesEpisodes('${series.imdb_id}')">
|
||||
<i class="fas fa-list"></i> Episodes
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function updateSeriesPagination(data) {
|
||||
const pagination = document.getElementById('series-pagination');
|
||||
|
||||
if (data.pages <= 1) {
|
||||
pagination.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
|
||||
if (data.has_prev) {
|
||||
html += `<button class="btn btn-secondary btn-sm" onclick="loadSeries(${data.page - 1})">
|
||||
<i class="fas fa-chevron-left"></i> Previous
|
||||
</button>`;
|
||||
}
|
||||
|
||||
html += `<span class="page-info">Page ${data.page} of ${data.pages}</span>`;
|
||||
|
||||
if (data.has_next) {
|
||||
html += `<button class="btn btn-secondary btn-sm" onclick="loadSeries(${data.page + 1})">
|
||||
Next <i class="fas fa-chevron-right"></i>
|
||||
</button>`;
|
||||
}
|
||||
|
||||
pagination.innerHTML = html;
|
||||
}
|
||||
|
||||
function refreshSeries() {
|
||||
loadSeries(currentSeriesPage);
|
||||
}
|
||||
|
||||
async function viewSeriesEpisodes(imdbId) {
|
||||
try {
|
||||
const data = await apiCall(`/api/series/${imdbId}/episodes`);
|
||||
showEpisodesModal(data);
|
||||
} catch (error) {
|
||||
console.error('Failed to load episodes:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function showEpisodesModal(data) {
|
||||
const modalHtml = `
|
||||
<div class="modal active" id="episodes-modal">
|
||||
<div class="modal-content" style="max-width: 800px;">
|
||||
<div class="modal-header">
|
||||
<h3>${escapeHtml(data.series.title)} - Episodes</h3>
|
||||
<button class="modal-close" onclick="closeEpisodesModal()">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="table-container">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Episode</th>
|
||||
<th>Aired</th>
|
||||
<th>Date Added</th>
|
||||
<th>Source</th>
|
||||
<th>Video</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${data.episodes.map(episode => {
|
||||
const dateadded = episode.dateadded ? formatDateTime(episode.dateadded) : '';
|
||||
const hasVideoBadge = episode.has_video_file ?
|
||||
'<span class="badge badge-success">Yes</span>' :
|
||||
'<span class="badge badge-secondary">No</span>';
|
||||
|
||||
return `
|
||||
<tr>
|
||||
<td>S${episode.season.toString().padStart(2, '0')}E${episode.episode.toString().padStart(2, '0')}</td>
|
||||
<td>${episode.aired || '-'}</td>
|
||||
<td>${dateadded || '-'}</td>
|
||||
<td><span class="badge badge-secondary">${episode.source || 'Unknown'}</span></td>
|
||||
<td>${hasVideoBadge}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-primary" onclick="editEpisode('${data.series.imdb_id}', ${episode.season}, ${episode.episode}, '${dateadded}', '${episode.source || ''}')">
|
||||
<i class="fas fa-edit"></i> Edit
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
||||
}
|
||||
|
||||
function closeEpisodesModal() {
|
||||
const modal = document.getElementById('episodes-modal');
|
||||
if (modal) {
|
||||
modal.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// Reports
|
||||
async function loadReport() {
|
||||
try {
|
||||
const data = await apiCall('/api/reports/missing-dates');
|
||||
updateReportSummary(data.summary);
|
||||
updateReportTables(data);
|
||||
} catch (error) {
|
||||
console.error('Failed to load report:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function updateReportSummary(summary) {
|
||||
document.getElementById('report-movies-with').textContent = summary.movies_with_dates;
|
||||
document.getElementById('report-movies-missing').textContent = summary.movies_missing_dates;
|
||||
document.getElementById('report-episodes-with').textContent = summary.episodes_with_dates;
|
||||
document.getElementById('report-episodes-missing').textContent = summary.episodes_missing_dates;
|
||||
}
|
||||
|
||||
function updateReportTables(data) {
|
||||
// Movies missing dates
|
||||
const moviesTbody = document.getElementById('report-movies-tbody');
|
||||
if (data.movies_missing.length === 0) {
|
||||
moviesTbody.innerHTML = '<tr><td colspan="5" class="text-center">No movies missing dates</td></tr>';
|
||||
} else {
|
||||
moviesTbody.innerHTML = data.movies_missing.map(movie => `
|
||||
<tr>
|
||||
<td>${escapeHtml(movie.title)}</td>
|
||||
<td><code>${movie.imdb_id}</code></td>
|
||||
<td>${movie.released || '-'}</td>
|
||||
<td><span class="badge badge-warning">${movie.source || 'Unknown'}</span></td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-success" onclick="quickFixMovie('${movie.imdb_id}', '${movie.released || ''}')">
|
||||
<i class="fas fa-magic"></i> Fix
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// Episodes missing dates
|
||||
const episodesTbody = document.getElementById('report-episodes-tbody');
|
||||
if (data.episodes_missing.length === 0) {
|
||||
episodesTbody.innerHTML = '<tr><td colspan="6" class="text-center">No episodes missing dates</td></tr>';
|
||||
} else {
|
||||
episodesTbody.innerHTML = data.episodes_missing.map(episode => `
|
||||
<tr>
|
||||
<td>${escapeHtml(episode.series_title)}</td>
|
||||
<td>S${episode.season.toString().padStart(2, '0')}E${episode.episode.toString().padStart(2, '0')}</td>
|
||||
<td><code>${episode.imdb_id}</code></td>
|
||||
<td>${episode.aired || '-'}</td>
|
||||
<td><span class="badge badge-warning">${episode.source || 'Unknown'}</span></td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-success" onclick="quickFixEpisode('${episode.imdb_id}', ${episode.season}, ${episode.episode}, '${episode.aired || ''}')">
|
||||
<i class="fas fa-magic"></i> Fix
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
}
|
||||
|
||||
function refreshReport() {
|
||||
loadReport();
|
||||
}
|
||||
|
||||
// Quick fix functions
|
||||
function quickFixMovie(imdbId, released) {
|
||||
let newSource = 'manual';
|
||||
let newDate = null;
|
||||
|
||||
if (released) {
|
||||
newSource = 'digital_release';
|
||||
newDate = released + 'T00:00:00';
|
||||
} else {
|
||||
newDate = new Date().toISOString().slice(0, 16);
|
||||
}
|
||||
|
||||
updateMovieDate(imdbId, newDate, newSource);
|
||||
}
|
||||
|
||||
function quickFixEpisode(imdbId, season, episode, aired) {
|
||||
let newSource = 'manual';
|
||||
let newDate = null;
|
||||
|
||||
if (aired) {
|
||||
newSource = 'airdate';
|
||||
newDate = aired + 'T00:00:00';
|
||||
} else {
|
||||
newDate = new Date().toISOString().slice(0, 16);
|
||||
}
|
||||
|
||||
updateEpisodeDate(imdbId, season, episode, newDate, newSource);
|
||||
}
|
||||
|
||||
// Tools
|
||||
async function loadDetailedStats() {
|
||||
try {
|
||||
const data = await apiCall('/api/dashboard');
|
||||
const statsHtml = `
|
||||
<div class="stats-grid">
|
||||
<div class="stat-row">
|
||||
<strong>Database Size:</strong> ${data.database_size_mb} MB
|
||||
</div>
|
||||
<div class="stat-row">
|
||||
<strong>Total Movies:</strong> ${data.movies_total} (${data.movies_with_video} with video files)
|
||||
</div>
|
||||
<div class="stat-row">
|
||||
<strong>Movies with Dates:</strong> ${data.movies_with_dates} (${((data.movies_with_dates / data.movies_total) * 100).toFixed(1)}%)
|
||||
</div>
|
||||
<div class="stat-row">
|
||||
<strong>Total Series:</strong> ${data.series_count}
|
||||
</div>
|
||||
<div class="stat-row">
|
||||
<strong>Total Episodes:</strong> ${data.episodes_total} (${data.episodes_with_video} with video files)
|
||||
</div>
|
||||
<div class="stat-row">
|
||||
<strong>Episodes with Dates:</strong> ${data.episodes_with_dates} (${((data.episodes_with_dates / data.episodes_total) * 100).toFixed(1)}%)
|
||||
</div>
|
||||
<div class="stat-row">
|
||||
<strong>Processing History:</strong> ${data.processing_history_count} events
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
document.getElementById('detailed-stats').innerHTML = statsHtml;
|
||||
} catch (error) {
|
||||
console.error('Failed to load detailed stats:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleBulkUpdate(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const mediaType = document.getElementById('bulk-media-type').value;
|
||||
const oldSource = document.getElementById('bulk-old-source').value;
|
||||
const newSource = document.getElementById('bulk-new-source').value;
|
||||
|
||||
if (!mediaType || !oldSource || !newSource) {
|
||||
showToast('Please fill in all fields', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm(`This will update all ${mediaType} with source "${oldSource}" to "${newSource}". Continue?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await apiCall('/api/bulk/update-source', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
media_type: mediaType,
|
||||
old_source: oldSource,
|
||||
new_source: newSource
|
||||
})
|
||||
});
|
||||
|
||||
showToast(result.message, 'success');
|
||||
|
||||
// Reset form
|
||||
document.getElementById('bulk-update-form').reset();
|
||||
|
||||
// Refresh current tab
|
||||
if (currentTab === 'movies') loadMovies(currentMoviesPage);
|
||||
if (currentTab === 'tv') loadSeries(currentSeriesPage);
|
||||
if (currentTab === 'reports') loadReport();
|
||||
if (currentTab === 'dashboard') loadDashboard();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Bulk update failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Edit modal functions
|
||||
function editMovie(imdbId, dateadded, source) {
|
||||
document.getElementById('modal-title').textContent = `Edit Movie: ${imdbId}`;
|
||||
document.getElementById('edit-imdb-id').value = imdbId;
|
||||
document.getElementById('edit-media-type').value = 'movie';
|
||||
document.getElementById('edit-season').value = '';
|
||||
document.getElementById('edit-episode').value = '';
|
||||
|
||||
// Convert dateadded to datetime-local format if present
|
||||
if (dateadded && dateadded !== '-') {
|
||||
try {
|
||||
const date = new Date(dateadded);
|
||||
document.getElementById('edit-dateadded').value = date.toISOString().slice(0, 16);
|
||||
} catch (e) {
|
||||
document.getElementById('edit-dateadded').value = '';
|
||||
}
|
||||
} else {
|
||||
document.getElementById('edit-dateadded').value = '';
|
||||
}
|
||||
|
||||
document.getElementById('edit-source').value = source || 'manual';
|
||||
|
||||
document.getElementById('edit-modal').classList.add('active');
|
||||
}
|
||||
|
||||
function editEpisode(imdbId, season, episode, dateadded, source) {
|
||||
document.getElementById('modal-title').textContent = `Edit Episode: ${imdbId} S${season.toString().padStart(2, '0')}E${episode.toString().padStart(2, '0')}`;
|
||||
document.getElementById('edit-imdb-id').value = imdbId;
|
||||
document.getElementById('edit-media-type').value = 'episode';
|
||||
document.getElementById('edit-season').value = season;
|
||||
document.getElementById('edit-episode').value = episode;
|
||||
|
||||
// Convert dateadded to datetime-local format if present
|
||||
if (dateadded && dateadded !== '-') {
|
||||
try {
|
||||
const date = new Date(dateadded);
|
||||
document.getElementById('edit-dateadded').value = date.toISOString().slice(0, 16);
|
||||
} catch (e) {
|
||||
document.getElementById('edit-dateadded').value = '';
|
||||
}
|
||||
} else {
|
||||
document.getElementById('edit-dateadded').value = '';
|
||||
}
|
||||
|
||||
document.getElementById('edit-source').value = source || 'manual';
|
||||
|
||||
document.getElementById('edit-modal').classList.add('active');
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
document.getElementById('edit-modal').classList.remove('active');
|
||||
}
|
||||
|
||||
async function handleEditSubmit(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const imdbId = document.getElementById('edit-imdb-id').value;
|
||||
const mediaType = document.getElementById('edit-media-type').value;
|
||||
const season = document.getElementById('edit-season').value;
|
||||
const episode = document.getElementById('edit-episode').value;
|
||||
const dateadded = document.getElementById('edit-dateadded').value;
|
||||
const source = document.getElementById('edit-source').value;
|
||||
|
||||
// Convert datetime-local to ISO string
|
||||
const isoDateadded = dateadded ? new Date(dateadded).toISOString() : null;
|
||||
|
||||
try {
|
||||
if (mediaType === 'movie') {
|
||||
await updateMovieDate(imdbId, isoDateadded, source);
|
||||
} else {
|
||||
await updateEpisodeDate(imdbId, parseInt(season), parseInt(episode), isoDateadded, source);
|
||||
}
|
||||
|
||||
closeModal();
|
||||
} catch (error) {
|
||||
console.error('Update failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Update functions
|
||||
async function updateMovieDate(imdbId, dateadded, source) {
|
||||
try {
|
||||
const result = await apiCall(`/api/movies/${imdbId}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({
|
||||
imdb_id: imdbId,
|
||||
dateadded: dateadded,
|
||||
source: source
|
||||
})
|
||||
});
|
||||
|
||||
showToast(result.message, 'success');
|
||||
|
||||
// Refresh current view
|
||||
if (currentTab === 'movies') loadMovies(currentMoviesPage);
|
||||
if (currentTab === 'reports') loadReport();
|
||||
if (currentTab === 'dashboard') loadDashboard();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Movie update failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function updateEpisodeDate(imdbId, season, episode, dateadded, source) {
|
||||
try {
|
||||
const result = await apiCall(`/api/episodes/${imdbId}/${season}/${episode}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({
|
||||
imdb_id: imdbId,
|
||||
season: season,
|
||||
episode: episode,
|
||||
dateadded: dateadded,
|
||||
source: source
|
||||
})
|
||||
});
|
||||
|
||||
showToast(result.message, 'success');
|
||||
|
||||
// Refresh current view
|
||||
if (currentTab === 'tv') loadSeries(currentSeriesPage);
|
||||
if (currentTab === 'reports') loadReport();
|
||||
if (currentTab === 'dashboard') loadDashboard();
|
||||
|
||||
// Refresh episodes modal if open
|
||||
const episodesModal = document.getElementById('episodes-modal');
|
||||
if (episodesModal) {
|
||||
closeEpisodesModal();
|
||||
setTimeout(() => viewSeriesEpisodes(imdbId), 100);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Episode update failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
function debounce(func, wait) {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func(...args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
}
|
||||
|
||||
function formatDateTime(dateString) {
|
||||
try {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleString();
|
||||
} catch (e) {
|
||||
return dateString;
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
function showToast(message, type = 'info') {
|
||||
const container = document.getElementById('toast-container');
|
||||
const toast = document.createElement('div');
|
||||
toast.className = `toast ${type}`;
|
||||
toast.innerHTML = `
|
||||
<div class="toast-content">
|
||||
<span>${escapeHtml(message)}</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
container.appendChild(toast);
|
||||
|
||||
// Auto remove after 5 seconds
|
||||
setTimeout(() => {
|
||||
if (toast.parentNode) {
|
||||
toast.parentNode.removeChild(toast);
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
// Remove on click
|
||||
toast.addEventListener('click', () => {
|
||||
if (toast.parentNode) {
|
||||
toast.parentNode.removeChild(toast);
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user