Add UI sync status indicator with pending badge

- Create htmx-powered partial template for sync status display
- Show Online/Offline indicator with color coding (green/red)
- Display pending changes count badge when there are unsynced items
- Add Sync button to push pending changes (appears only when needed)
- Auto-refresh every 30 seconds via htmx polling
- Replace JavaScript-based sync indicator with server-rendered partial
- Integrate SyncStatusPartial handler with template rendering

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-02 06:38:23 +03:00
parent 1f739a3ab2
commit ec3c16f3fc
4 changed files with 81 additions and 74 deletions

View File

@@ -26,8 +26,11 @@
</div>
</div>
<div class="flex items-center space-x-4">
<!-- Sync Status Indicator -->
<div id="sync-indicator" class="flex items-center space-x-2">
<!-- Sync Status Indicator (htmx-powered) -->
<div id="sync-status"
hx-get="/partials/sync-status"
hx-trigger="load, refresh from:body, every 30s"
hx-swap="innerHTML">
<span class="animate-pulse text-gray-400 text-xs">Загрузка...</span>
</div>
<span id="db-user" class="text-sm text-gray-600"></span>
@@ -57,72 +60,6 @@
setTimeout(() => el.innerHTML = '', 3000);
}
async function checkSyncStatus() {
try {
const resp = await fetch('/api/sync/status');
const data = await resp.json();
updateSyncIndicator(data);
} catch(e) {
console.error('Failed to check sync status:', e);
const indicator = document.getElementById('sync-indicator');
if (indicator) {
indicator.innerHTML = '<span class="w-2 h-2 rounded-full bg-red-500"></span><span class="text-xs text-red-600">Offline</span>';
}
}
}
function updateSyncIndicator(data) {
const indicator = document.getElementById('sync-indicator');
if (!indicator) return;
const statusColor = data.is_online ? 'bg-green-500' : 'bg-red-500';
const statusText = data.is_online ? 'Online' : 'Offline';
const textColor = data.is_online ? 'text-green-700' : 'text-red-700';
const needSync = data.need_component_sync || data.need_pricelist_sync;
const syncWarning = needSync ? '<span class="text-yellow-600 ml-1" title="Требуется синхронизация">⚠</span>' : '';
let html = `
<div class="flex items-center space-x-2">
<span class="w-2 h-2 rounded-full ${statusColor}" title="${statusText}"></span>
<span class="text-xs ${textColor}">${statusText}</span>
${syncWarning}
${data.is_online ? `
<button onclick="syncAll()"
class="text-xs px-2 py-1 bg-blue-500 text-white rounded hover:bg-blue-600 transition"
title="Синхронизировать все">
Sync
</button>
` : ''}
</div>
`;
indicator.innerHTML = html;
}
async function syncAll() {
const btn = event.target;
btn.disabled = true;
btn.textContent = '...';
try {
const resp = await fetch('/api/sync/all', { method: 'POST' });
const data = await resp.json();
if (data.success) {
showToast(`Синхронизация завершена: компоненты ${data.components_synced}, прайслисты ${data.pricelists_synced}`, 'success');
checkSyncStatus();
} else {
showToast('Ошибка синхронизации: ' + (data.error || 'неизвестная ошибка'), 'error');
}
} catch(e) {
showToast('Ошибка синхронизации: ' + e.message, 'error');
} finally {
btn.disabled = false;
btn.textContent = 'Sync';
}
}
async function checkDbStatus() {
try {
const resp = await fetch('/api/db-status');
@@ -162,9 +99,6 @@
document.addEventListener('DOMContentLoaded', function() {
checkDbStatus();
checkWritePermission();
checkSyncStatus();
// Auto-refresh sync status every 30 seconds
setInterval(checkSyncStatus, 30000);
});
</script>
</body>

View File

@@ -0,0 +1,37 @@
{{define "sync_status"}}
<div class="flex items-center gap-2">
{{if .IsOffline}}
<span class="flex items-center gap-1 text-red-600" title="Offline">
<span class="w-2 h-2 bg-red-500 rounded-full"></span>
<span class="text-xs">Offline</span>
</span>
{{else}}
<span class="flex items-center gap-1 text-green-600" title="Online">
<span class="w-2 h-2 bg-green-500 rounded-full"></span>
<span class="text-xs">Online</span>
</span>
{{end}}
{{if gt .PendingCount 0}}
<span class="bg-yellow-100 text-yellow-800 px-2 py-0.5 rounded-full text-xs font-medium">
{{.PendingCount}} pending
</span>
<button hx-post="/api/sync/push"
hx-swap="none"
hx-on::after-request="
if(event.detail.successful) {
const resp = JSON.parse(event.detail.xhr.response);
if(resp.success) {
showToast('Синхронизировано: ' + resp.synced + ' изменений', 'success');
} else {
showToast('Ошибка: ' + (resp.error || 'неизвестная ошибка'), 'error');
}
htmx.trigger('#sync-status', 'refresh');
}
"
class="text-blue-600 hover:text-blue-800 text-xs underline cursor-pointer">
Sync
</button>
{{end}}
</div>
{{end}}