fix: search issues in webinterfacey
Local Docker Build (Dev) / build-dev (pull_request) Successful in 4s
Local Docker Build (Dev) / build-dev (pull_request) Successful in 4s
This commit is contained in:
+286
-50
@@ -61,6 +61,7 @@ function switchTab(tabName) {
|
||||
function initializeEventListeners() {
|
||||
// Search inputs
|
||||
document.getElementById('movies-search').addEventListener('input', debounce(loadMovies, 500));
|
||||
document.getElementById('movies-imdb-search').addEventListener('input', debounce(loadMovies, 500));
|
||||
document.getElementById('series-search').addEventListener('input', debounce(loadSeries, 500));
|
||||
|
||||
// Filter dropdowns
|
||||
@@ -171,6 +172,7 @@ function getChartColor(index) {
|
||||
// Movies
|
||||
async function loadMovies(page = 1) {
|
||||
const search = document.getElementById('movies-search').value;
|
||||
const imdbSearch = document.getElementById('movies-imdb-search').value;
|
||||
const hasDate = document.getElementById('movies-filter-date').value;
|
||||
const sourceFilter = document.getElementById('movies-filter-source').value;
|
||||
|
||||
@@ -180,6 +182,7 @@ async function loadMovies(page = 1) {
|
||||
});
|
||||
|
||||
if (search) params.append('search', search);
|
||||
if (imdbSearch) params.append('imdb_search', imdbSearch);
|
||||
if (hasDate) params.append('has_date', hasDate);
|
||||
if (sourceFilter) params.append('source_filter', sourceFilter);
|
||||
|
||||
@@ -198,19 +201,37 @@ 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>';
|
||||
tbody.innerHTML = '<tr><td colspan="8" 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>';
|
||||
|
||||
// Determine date type based on source and dates
|
||||
let dateType = 'Unknown';
|
||||
let dateTypeBadge = 'badge-secondary';
|
||||
|
||||
if (movie.source === 'digital_release') {
|
||||
dateType = 'Digital Release';
|
||||
dateTypeBadge = 'badge-success';
|
||||
} else if (movie.source && movie.source.includes('radarr') && movie.source.includes('import')) {
|
||||
dateType = 'Radarr Import';
|
||||
dateTypeBadge = 'badge-warning';
|
||||
} else if (movie.source === 'manual') {
|
||||
dateType = 'Manual';
|
||||
dateTypeBadge = 'badge-info';
|
||||
} else if (movie.source === 'nfo_file_existing') {
|
||||
dateType = 'Existing NFO';
|
||||
dateTypeBadge = 'badge-secondary';
|
||||
} else if (movie.source === 'no_valid_date_source') {
|
||||
dateType = 'No Valid Source';
|
||||
dateTypeBadge = 'badge-danger';
|
||||
}
|
||||
|
||||
return `
|
||||
<tr>
|
||||
<td>${escapeHtml(movie.title)}</td>
|
||||
@@ -218,6 +239,7 @@ function updateMoviesTable(data) {
|
||||
<td>${movie.released || '-'}</td>
|
||||
<td>${dateadded || '-'}</td>
|
||||
<td><span class="badge badge-secondary">${movie.source || 'Unknown'}</span></td>
|
||||
<td><span class="badge ${dateTypeBadge}">${dateType}</span></td>
|
||||
<td>${hasVideoBadge}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-primary" onclick="editMovie('${movie.imdb_id}', '${dateadded}', '${movie.source || ''}')">
|
||||
@@ -460,8 +482,8 @@ function updateReportTables(data) {
|
||||
<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 class="btn btn-sm btn-success" onclick="smartFixMovie('${movie.imdb_id}')">
|
||||
<i class="fas fa-magic"></i> Smart Fix
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -481,8 +503,8 @@ function updateReportTables(data) {
|
||||
<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 class="btn btn-sm btn-success" onclick="smartFixEpisode('${episode.imdb_id}', ${episode.season}, ${episode.episode})">
|
||||
<i class="fas fa-magic"></i> Smart Fix
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -494,33 +516,108 @@ 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);
|
||||
// Smart fix functions
|
||||
async function smartFixMovie(imdbId) {
|
||||
try {
|
||||
const options = await apiCall(`/api/movies/${imdbId}/date-options`);
|
||||
showSmartFixModal('movie', options);
|
||||
} catch (error) {
|
||||
console.error('Failed to load movie options:', error);
|
||||
showToast('Failed to load movie options', 'error');
|
||||
}
|
||||
|
||||
updateMovieDate(imdbId, newDate, newSource);
|
||||
}
|
||||
|
||||
function quickFixEpisode(imdbId, season, episode, aired) {
|
||||
let newSource = 'manual';
|
||||
let newDate = null;
|
||||
async function smartFixEpisode(imdbId, season, episode) {
|
||||
try {
|
||||
const options = await apiCall(`/api/episodes/${imdbId}/${season}/${episode}/date-options`);
|
||||
showSmartFixModal('episode', options);
|
||||
} catch (error) {
|
||||
console.error('Failed to load episode options:', error);
|
||||
showToast('Failed to load episode options', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function showSmartFixModal(mediaType, options) {
|
||||
const modal = document.getElementById('smart-fix-modal');
|
||||
const title = document.getElementById('smart-fix-title');
|
||||
const content = document.getElementById('smart-fix-content');
|
||||
|
||||
if (aired) {
|
||||
newSource = 'airdate';
|
||||
newDate = aired + 'T00:00:00';
|
||||
if (mediaType === 'movie') {
|
||||
title.textContent = `Fix Date for Movie: ${options.imdb_id}`;
|
||||
} else {
|
||||
newDate = new Date().toISOString().slice(0, 16);
|
||||
title.textContent = `Fix Date for Episode: ${options.imdb_id} S${options.season.toString().padStart(2, '0')}E${options.episode.toString().padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
updateEpisodeDate(imdbId, season, episode, newDate, newSource);
|
||||
// Build options HTML
|
||||
let optionsHtml = '<div class="smart-fix-options">';
|
||||
|
||||
options.options.forEach((option, index) => {
|
||||
const isChecked = index === 0 ? 'checked' : '';
|
||||
const dateInput = option.type === 'manual' ?
|
||||
`<input type="datetime-local" id="manual-date-${index}" class="manual-date-input" style="margin-top: 0.5rem;">` : '';
|
||||
|
||||
optionsHtml += `
|
||||
<div class="option-card">
|
||||
<label class="option-label">
|
||||
<input type="radio" name="date-option" value="${index}" ${isChecked}>
|
||||
<div class="option-content">
|
||||
<h4>${option.label}</h4>
|
||||
<p>${option.description}</p>
|
||||
${option.date ? `<small><strong>Date:</strong> ${formatDateTime(option.date)}</small>` : ''}
|
||||
${dateInput}
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
optionsHtml += '</div>';
|
||||
|
||||
optionsHtml += `
|
||||
<div class="form-actions">
|
||||
<button type="button" class="btn btn-secondary" onclick="closeSmartFixModal()">Cancel</button>
|
||||
<button type="button" class="btn btn-success" onclick="applySmartFix('${mediaType}', ${JSON.stringify(options).replace(/'/g, "'")})">
|
||||
<i class="fas fa-magic"></i> Apply Fix
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
content.innerHTML = optionsHtml;
|
||||
modal.classList.add('active');
|
||||
}
|
||||
|
||||
function closeSmartFixModal() {
|
||||
document.getElementById('smart-fix-modal').classList.remove('active');
|
||||
}
|
||||
|
||||
async function applySmartFix(mediaType, options) {
|
||||
const selectedIndex = document.querySelector('input[name="date-option"]:checked').value;
|
||||
const selectedOption = options.options[selectedIndex];
|
||||
|
||||
let dateadded = selectedOption.date;
|
||||
let source = selectedOption.source;
|
||||
|
||||
// Handle manual date entry
|
||||
if (selectedOption.type === 'manual') {
|
||||
const manualDateInput = document.getElementById(`manual-date-${selectedIndex}`);
|
||||
if (manualDateInput && manualDateInput.value) {
|
||||
dateadded = new Date(manualDateInput.value).toISOString();
|
||||
} else {
|
||||
showToast('Please enter a date for manual option', 'warning');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (mediaType === 'movie') {
|
||||
await updateMovieDate(options.imdb_id, dateadded, source);
|
||||
} else {
|
||||
await updateEpisodeDate(options.imdb_id, options.season, options.episode, dateadded, source);
|
||||
}
|
||||
closeSmartFixModal();
|
||||
} catch (error) {
|
||||
console.error('Smart fix failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Tools
|
||||
@@ -601,14 +698,120 @@ async function handleBulkUpdate(event) {
|
||||
}
|
||||
|
||||
// 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 = '';
|
||||
async function editMovie(imdbId, dateadded, source) {
|
||||
try {
|
||||
// Load movie options to populate available dates
|
||||
const options = await apiCall(`/api/movies/${imdbId}/date-options`);
|
||||
showEnhancedEditModal('movie', options, dateadded, source);
|
||||
} catch (error) {
|
||||
console.error('Failed to load movie options for edit:', error);
|
||||
// Fallback to basic edit modal
|
||||
showBasicEditModal('movie', imdbId, dateadded, source);
|
||||
}
|
||||
}
|
||||
|
||||
function showEnhancedEditModal(mediaType, options, currentDateadded, currentSource) {
|
||||
const modal = document.getElementById('edit-modal');
|
||||
const title = document.getElementById('modal-title');
|
||||
const modalBody = document.querySelector('#edit-modal .modal-body');
|
||||
|
||||
if (mediaType === 'movie') {
|
||||
title.textContent = `Edit Movie: ${options.imdb_id}`;
|
||||
} else {
|
||||
title.textContent = `Edit Episode: ${options.imdb_id} S${options.season.toString().padStart(2, '0')}E${options.episode.toString().padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
// Build enhanced edit form with date options
|
||||
let formHtml = `
|
||||
<input type="hidden" id="edit-imdb-id" value="${options.imdb_id}">
|
||||
<input type="hidden" id="edit-media-type" value="${mediaType}">
|
||||
${mediaType === 'episode' ? `
|
||||
<input type="hidden" id="edit-season" value="${options.season}">
|
||||
<input type="hidden" id="edit-episode" value="${options.episode}">
|
||||
` : `
|
||||
<input type="hidden" id="edit-season" value="">
|
||||
<input type="hidden" id="edit-episode" value="">
|
||||
`}
|
||||
|
||||
<div class="form-group">
|
||||
<label>Choose Date Source:</label>
|
||||
<div class="date-options">
|
||||
`;
|
||||
|
||||
// Add available date options
|
||||
options.options.forEach((option, index) => {
|
||||
const isSelected = option.source === currentSource ? 'checked' : '';
|
||||
const optionId = `date-option-${index}`;
|
||||
|
||||
formHtml += `
|
||||
<div class="date-option-card">
|
||||
<label class="date-option-label">
|
||||
<input type="radio" name="edit-date-option" value="${index}" ${isSelected}
|
||||
onchange="updateEditDateFromOption(${index}, ${JSON.stringify(option).replace(/"/g, '"')})">
|
||||
<div class="date-option-content">
|
||||
<h4>${option.label}</h4>
|
||||
<p>${option.description}</p>
|
||||
${option.date ? `<small><strong>Date:</strong> ${formatDateTime(option.date)}</small>` : ''}
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
formHtml += `
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="edit-dateadded">Date Added:</label>
|
||||
<input type="datetime-local" id="edit-dateadded" required>
|
||||
<small>Adjust the date/time as needed</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="edit-source">Source:</label>
|
||||
<select id="edit-source" required>
|
||||
<option value="manual">Manual</option>
|
||||
<option value="airdate">Air Date</option>
|
||||
<option value="digital_release">Digital Release</option>
|
||||
<option value="radarr:db.history.import">Radarr Import</option>
|
||||
<option value="sonarr:history.import">Sonarr Import</option>
|
||||
<option value="no_valid_date_source">No Valid Source</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="button" class="btn btn-secondary" onclick="closeModal()">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary" onclick="handleEnhancedEditSubmit(event)">Save Changes</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
modalBody.innerHTML = formHtml;
|
||||
|
||||
// Set current values
|
||||
if (currentDateadded && currentDateadded !== '-') {
|
||||
try {
|
||||
const date = new Date(currentDateadded);
|
||||
document.getElementById('edit-dateadded').value = date.toISOString().slice(0, 16);
|
||||
} catch (e) {
|
||||
document.getElementById('edit-dateadded').value = '';
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('edit-source').value = currentSource || 'manual';
|
||||
|
||||
// Store options for later use
|
||||
modal.dataset.options = JSON.stringify(options);
|
||||
|
||||
modal.classList.add('active');
|
||||
}
|
||||
|
||||
function showBasicEditModal(mediaType, imdbId, dateadded, source) {
|
||||
// Fallback to original basic edit modal
|
||||
document.getElementById('modal-title').textContent = `Edit ${mediaType}: ${imdbId}`;
|
||||
document.getElementById('edit-imdb-id').value = imdbId;
|
||||
document.getElementById('edit-media-type').value = mediaType;
|
||||
|
||||
// Convert dateadded to datetime-local format if present
|
||||
if (dateadded && dateadded !== '-') {
|
||||
try {
|
||||
const date = new Date(dateadded);
|
||||
@@ -621,32 +824,65 @@ function editMovie(imdbId, dateadded, source) {
|
||||
}
|
||||
|
||||
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;
|
||||
function updateEditDateFromOption(optionIndex, option) {
|
||||
const dateInput = document.getElementById('edit-dateadded');
|
||||
const sourceSelect = document.getElementById('edit-source');
|
||||
|
||||
// Convert dateadded to datetime-local format if present
|
||||
if (dateadded && dateadded !== '-') {
|
||||
if (option.date) {
|
||||
// Convert to datetime-local format
|
||||
try {
|
||||
const date = new Date(dateadded);
|
||||
document.getElementById('edit-dateadded').value = date.toISOString().slice(0, 16);
|
||||
const date = new Date(option.date);
|
||||
dateInput.value = date.toISOString().slice(0, 16);
|
||||
} catch (e) {
|
||||
document.getElementById('edit-dateadded').value = '';
|
||||
dateInput.value = '';
|
||||
}
|
||||
} else {
|
||||
document.getElementById('edit-dateadded').value = '';
|
||||
// Manual option - clear the date for user input
|
||||
dateInput.value = '';
|
||||
}
|
||||
|
||||
document.getElementById('edit-source').value = source || 'manual';
|
||||
sourceSelect.value = option.source;
|
||||
}
|
||||
|
||||
async function handleEnhancedEditSubmit(event) {
|
||||
event.preventDefault();
|
||||
|
||||
document.getElementById('edit-modal').classList.add('active');
|
||||
const modal = document.getElementById('edit-modal');
|
||||
const options = JSON.parse(modal.dataset.options);
|
||||
const imdbId = options.imdb_id;
|
||||
const mediaType = document.getElementById('edit-media-type').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, options.season, options.episode, isoDateadded, source);
|
||||
}
|
||||
|
||||
closeModal();
|
||||
} catch (error) {
|
||||
console.error('Enhanced edit failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function editEpisode(imdbId, season, episode, dateadded, source) {
|
||||
try {
|
||||
// Load episode options to populate available dates
|
||||
const options = await apiCall(`/api/episodes/${imdbId}/${season}/${episode}/date-options`);
|
||||
showEnhancedEditModal('episode', options, dateadded, source);
|
||||
} catch (error) {
|
||||
console.error('Failed to load episode options for edit:', error);
|
||||
// Fallback to basic edit modal
|
||||
showBasicEditModal('episode', imdbId, dateadded, source, season, episode);
|
||||
}
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
|
||||
Reference in New Issue
Block a user