/** * Moderation Transition History JavaScript * Handles AJAX loading and display of FSM transition history */ let currentPage = 1; let nextPageUrl = null; let previousPageUrl = null; /** * Format timestamp to human-readable format */ function formatTimestamp(timestamp) { const date = new Date(timestamp); return date.toLocaleString('en-US', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }); } /** * Get CSRF token from cookie */ function getCookie(name) { let cookieValue = null; if (document.cookie && document.cookie !== '') { const cookies = document.cookie.split(';'); for (let i = 0; i < cookies.length; i++) { const cookie = cookies[i].trim(); if (cookie.substring(0, name.length + 1) === (name + '=')) { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } } } return cookieValue; } /** * Fetch and display transition history */ function loadHistory(url = null, filters = {}) { const tbody = document.getElementById('history-tbody'); tbody.innerHTML = '
Loading history...'; // Build URL let fetchUrl = url || '/api/moderation/reports/all_history/'; // Add filters to URL if no custom URL provided if (!url && Object.keys(filters).length > 0) { const params = new URLSearchParams(); for (const [key, value] of Object.entries(filters)) { if (value) { params.append(key, value); } } fetchUrl += '?' + params.toString(); } fetch(fetchUrl, { method: 'GET', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, credentials: 'same-origin' }) .then(response => { if (!response.ok) { throw new Error('Network response was not ok'); } return response.json(); }) .then(data => { renderHistoryTable(data.results || data); updatePagination(data); }) .catch(error => { console.error('Error loading history:', error); tbody.innerHTML = 'Error loading history. Please try again.'; }); } /** * Render history table rows */ function renderHistoryTable(logs) { const tbody = document.getElementById('history-tbody'); if (!logs || logs.length === 0) { tbody.innerHTML = 'No transition history found.'; return; } tbody.innerHTML = logs.map(log => ` ${formatTimestamp(log.timestamp)} ${log.model} ${log.object_id} ${log.transition || '-'} ${log.state} ${log.user || 'System'} `).join(''); } /** * Update pagination controls */ function updatePagination(data) { nextPageUrl = data.next || null; previousPageUrl = data.previous || null; const prevBtn = document.getElementById('prev-page'); const nextBtn = document.getElementById('next-page'); const pageInfo = document.getElementById('page-info'); prevBtn.disabled = !previousPageUrl; nextBtn.disabled = !nextPageUrl; // Calculate page number from count if (data.count) { const resultsPerPage = data.results ? data.results.length : 0; const totalPages = Math.ceil(data.count / (resultsPerPage || 1)); pageInfo.textContent = `Page ${currentPage} of ${totalPages}`; } else { pageInfo.textContent = `Page ${currentPage}`; } } /** * View details modal */ function viewDetails(logId) { const modal = document.getElementById('details-modal'); const modalBody = document.getElementById('modal-body'); modalBody.innerHTML = '
Loading details...'; modal.style.display = 'flex'; // Fetch detailed information filtered by id fetch(`/api/moderation/reports/all_history/?id=${logId}`, { headers: { 'X-CSRFToken': getCookie('csrftoken') }, credentials: 'same-origin' }) .then(response => response.json()) .then(data => { // Handle both paginated and non-paginated responses let log = null; if (data.results && data.results.length > 0) { log = data.results[0]; } else if (Array.isArray(data) && data.length > 0) { log = data[0]; } else if (data.id) { // Single object response log = data; } if (log) { modalBody.innerHTML = `
ID: ${log.id}
Timestamp: ${formatTimestamp(log.timestamp)}
Model: ${log.model}
Object ID: ${log.object_id}
Transition: ${log.transition || '-'}
From State: ${log.from_state || '-'}
To State: ${log.to_state || log.state || '-'}
User: ${log.user || 'System'}
${log.reason ? `
Reason:
${log.reason}
` : ''} ${log.description ? `
Description:
${log.description}
` : ''}
`; } else { modalBody.innerHTML = '

No log entry found with this ID.

'; } }) .catch(error => { console.error('Error loading details:', error); modalBody.innerHTML = '

Error loading details.

'; }); } /** * Close modal */ function closeModal() { const modal = document.getElementById('details-modal'); modal.style.display = 'none'; } /** * Get current filters */ function getCurrentFilters() { return { model_type: document.getElementById('model-filter').value, state: document.getElementById('state-filter').value, start_date: document.getElementById('start-date').value, end_date: document.getElementById('end-date').value, user_id: document.getElementById('user-filter').value, }; } /** * Event listeners */ document.addEventListener('DOMContentLoaded', function() { // Apply filters button document.getElementById('apply-filters').addEventListener('click', () => { currentPage = 1; const filters = getCurrentFilters(); loadHistory(null, filters); }); // Clear filters button document.getElementById('clear-filters').addEventListener('click', () => { document.getElementById('model-filter').value = ''; document.getElementById('state-filter').value = ''; document.getElementById('start-date').value = ''; document.getElementById('end-date').value = ''; document.getElementById('user-filter').value = ''; currentPage = 1; loadHistory(); }); // Pagination buttons document.getElementById('prev-page').addEventListener('click', () => { if (previousPageUrl) { currentPage--; loadHistory(previousPageUrl); } }); document.getElementById('next-page').addEventListener('click', () => { if (nextPageUrl) { currentPage++; loadHistory(nextPageUrl); } }); // Close modal on background click document.getElementById('details-modal').addEventListener('click', (e) => { if (e.target.id === 'details-modal') { closeModal(); } }); // Initial load loadHistory(); }); // Additional CSS for badges (inline styles) const style = document.createElement('style'); style.textContent = ` .badge { display: inline-block; padding: 4px 8px; border-radius: 4px; font-size: 0.75rem; font-weight: 600; text-transform: uppercase; } .badge-model { background-color: #e7f3ff; color: #0066cc; } .badge-transition { background-color: #fff3cd; color: #856404; } .badge-state { background-color: #d4edda; color: #155724; } .badge-state-PENDING { background-color: #fff3cd; color: #856404; } .badge-state-APPROVED { background-color: #d4edda; color: #155724; } .badge-state-REJECTED { background-color: #f8d7da; color: #721c24; } .badge-state-IN_PROGRESS { background-color: #d1ecf1; color: #0c5460; } .badge-state-COMPLETED { background-color: #d4edda; color: #155724; } .badge-state-ESCALATED { background-color: #f8d7da; color: #721c24; } .object-link { color: #007bff; text-decoration: none; } .object-link:hover { text-decoration: underline; } .btn-view { background-color: #007bff; color: white; border: none; padding: 4px 12px; border-radius: 4px; cursor: pointer; } .btn-view:hover { background-color: #0056b3; } .detail-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px; } .detail-item { padding: 10px; background-color: #f8f9fa; border-radius: 4px; } .detail-item.full-width { grid-column: 1 / -1; } .detail-item strong { display: block; margin-bottom: 5px; color: #666; font-size: 0.875rem; } `; document.head.appendChild(style);