// Copyright (c) 2026 Gordon Bolton. MIT License. import { state } from '../state.js'; import { escapeHtml } from '../utils/format.js'; import { applyRequestFilters, getRequestStatus } from '../utils/ombiFilters.js'; /** * Helper function to extract the username from an Ombi request object. * The Ombi API returns requestedUser as an OmbiStore.Entities.OmbiUser object, * not a string, so we need to extract the username from the object. * * Must stay in sync with server/utils/ombiHelpers.js * * @param {Object} request - The Ombi request object * @returns {string} The extracted username, or empty string if not found */ function extractRequestedUser(request) { if (!request) return ''; // Handle object format: OmbiStore.Entities.OmbiUser if (request.requestedUser && typeof request.requestedUser === 'object') { // Priority: alias > userAlias > userName > normalizedUserName > requestedByAlias return request.requestedUser.alias || request.requestedUser.userAlias || request.requestedUser.userName || request.requestedUser.normalizedUserName || request.requestedByAlias || ''; } // Handle string format (fallback for compatibility) return request.requestedUser || request.requestedByAlias || ''; } export function renderRequests() { const requestsList = document.getElementById('requests-list'); const noRequests = document.getElementById('no-requests'); if (!requestsList) return; const ombiRequests = state.ombiRequests || { movie: [], tv: [] }; const allRequests = [ ...ombiRequests.movie.map(r => ({ ...r, mediaType: 'movie' })), ...ombiRequests.tv.map(r => ({ ...r, mediaType: 'tv' })) ]; // Apply client-side filters, sorting, and search const filtered = applyRequestFilters(allRequests, { types: state.selectedRequestTypes, statuses: state.selectedRequestStatuses, sort: state.requestSortMode, search: state.requestSearchQuery }); requestsList.innerHTML = ''; if (filtered.length === 0) { if (noRequests) { noRequests.style.display = 'block'; const p = noRequests.querySelector('p'); if (p) { // Differentiate between no data from Ombi vs filters excluded everything const hasAnyData = allRequests.length > 0; p.textContent = hasAnyData ? 'No requests match your filters.' : 'No requests found.'; } } return; } if (noRequests) noRequests.style.display = 'none'; filtered.forEach(request => { const card = createRequestCard(request); requestsList.appendChild(card); }); } function createRequestCard(request) { if (!request) { const card = document.createElement('div'); card.className = 'request-card'; card.textContent = 'Invalid request data'; return card; } const card = document.createElement('div'); card.className = 'request-card'; const typeIcon = document.createElement('span'); typeIcon.className = `request-type-icon ${request.mediaType || ''}`; typeIcon.textContent = request.mediaType === 'movie' ? '🎬' : '📺'; const content = document.createElement('div'); content.className = 'request-content'; const title = document.createElement('div'); title.className = 'request-title'; title.textContent = request.title || 'Unknown Title'; const meta = document.createElement('div'); meta.className = 'request-meta'; const statusBadge = createStatusBadge(request); meta.appendChild(statusBadge); if (request.year) { const year = document.createElement('span'); year.className = 'request-year'; year.textContent = request.year; meta.appendChild(year); } const username = extractRequestedUser(request); if (username) { const user = document.createElement('span'); user.className = 'request-user'; user.textContent = `Requested by: ${username}`; meta.appendChild(user); } if (request.quality) { const quality = document.createElement('span'); quality.className = 'request-quality'; quality.textContent = request.quality; meta.appendChild(quality); } content.appendChild(title); content.appendChild(meta); const actions = document.createElement('div'); actions.className = 'request-actions'; if (state.ombiBaseUrl && request.theMovieDbId) { const ombiLink = document.createElement('a'); ombiLink.className = 'request-link ombi-link'; ombiLink.href = `${state.ombiBaseUrl}/details/${request.mediaType || 'movie'}/${request.theMovieDbId}`; ombiLink.target = '_blank'; ombiLink.title = 'View in Ombi'; const ombiIcon = document.createElement('img'); ombiIcon.src = '/images/ombi.svg'; ombiIcon.alt = 'Ombi'; ombiIcon.className = 'request-icon'; ombiLink.appendChild(ombiIcon); actions.appendChild(ombiLink); } card.appendChild(typeIcon); card.appendChild(content); card.appendChild(actions); return card; } function createStatusBadge(request) { const badge = document.createElement('span'); badge.className = 'request-status-badge'; const status = getRequestStatus(request); const statusTexts = { available: 'Available', denied: `Denied: ${request.deniedReason || 'No reason'}`, approved: 'Approved', pending: 'Pending', unknown: 'Unknown' }; badge.classList.add(status); badge.textContent = statusTexts[status] || 'Unknown'; return badge; }