import { showToast } from '../components/toast.js'; import { esc } from '../utils.js'; import { t } from '../i18n.js'; const API = (url) => fetch('/api' + url, { headers: { Authorization: `Bearer ${localStorage.getItem('token')}` }}).then(r => r.json()); export async function render(container) { container.innerHTML = `

${t('common.loading')}

`; let offset = 0; const limit = 50; async function loadActivity(append = false) { try { const items = await API(`/activity?limit=${limit}&offset=${offset}`); const list = document.getElementById('activityList'); if (!append) list.innerHTML = ''; if (items.length === 0 && offset === 0) { list.innerHTML = `

${t('activity.empty_title')}

${t('activity.empty_desc')}

`; return; } const html = items.map(item => { const time = new Date(item.created_at * 1000); const timeStr = time.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }) + ' ' + time.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' }); const icon = getActionIcon(item.action); return `
${icon}
${esc(item.user_name || item.user_email || t('activity.system'))} ${esc(formatAction(item.action))}
${item.details ? `
${esc(item.details)}
` : ''}
${timeStr}
`; }).join(''); if (append) { list.insertAdjacentHTML('beforeend', html); } else { list.innerHTML = html; } document.getElementById('loadMoreBtn').style.display = items.length >= limit ? '' : 'none'; } catch (err) { showToast(err.message, 'error'); } } document.getElementById('loadMoreBtn').onclick = () => { offset += limit; loadActivity(true); }; loadActivity(); } function getActionIcon(action) { if (action.includes('DELETE')) return '🗑'; if (action.includes('POST') && action.includes('content')) return '📤'; if (action.includes('POST') && action.includes('provision')) return '🔗'; if (action.includes('POST') && action.includes('assignment')) return '📋'; if (action.includes('alert')) return '🔔'; if (action.includes('PUT')) return '✎'; if (action.includes('POST')) return '➕'; return '📄'; } // Action verbs are user-visible; translate them through t() so they switch // languages with the rest of the UI. The mapping below preserves the original // verb-then-noun structure of the English version. function formatAction(action) { // Verbs let s = action .replace('POST /api/', t('activity.verb_created') + ' ') .replace('PUT /api/', t('activity.verb_updated') + ' ') .replace('DELETE /api/', t('activity.verb_deleted') + ' '); // Specific endpoints s = s .replace('/provision/pair', t('activity.action_paired_device')) .replace('/content/remote', t('activity.action_added_remote_content')) .replace('/content', t('activity.noun_content')) .replace('/devices/:id', t('activity.noun_device')) .replace('/assignments/device/:deviceId', t('activity.noun_playlist_assignment')) .replace('/assignments/:id', t('activity.noun_assignment')) .replace('/layouts', t('activity.noun_layout')) .replace('/widgets', t('activity.noun_widget')) .replace('/schedules', t('activity.noun_schedule')) .replace('/walls', t('activity.noun_video_wall')) .replace('alert:device_offline', t('activity.alert_device_offline')); return s; } export function cleanup() {}