feat: implement sync icon + pricelist badge UI improvements
- Replace text 'Online/Offline' with SVG icons in sync status - Change sync button to circular arrow icon - Add dropdown menu with push changes, full sync, and last sync status - Add pricelist version badge to configuration page - Load pricelist version via /api/pricelists/latest on DOMContentLoaded This completes task 1 of Phase 2.5 (UI Improvements) as specified in CLAUDE.md
This commit is contained in:
@@ -60,6 +60,75 @@
|
||||
setTimeout(() => el.innerHTML = '', 3000);
|
||||
}
|
||||
|
||||
// Dropdown functionality
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const dropdownButton = document.getElementById('sync-dropdown-button');
|
||||
const dropdownMenu = document.getElementById('sync-dropdown-menu');
|
||||
|
||||
if (dropdownButton && dropdownMenu) {
|
||||
dropdownButton.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
dropdownMenu.classList.toggle('hidden');
|
||||
});
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
document.addEventListener('click', function(e) {
|
||||
if (!dropdownButton.contains(e.target) && !dropdownMenu.contains(e.target)) {
|
||||
dropdownMenu.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
checkDbStatus();
|
||||
checkWritePermission();
|
||||
});
|
||||
|
||||
function pushPendingChanges() {
|
||||
fetch('/api/sync/push', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showToast('Синхронизировано: ' + data.synced + ' изменений', 'success');
|
||||
} else {
|
||||
showToast('Ошибка: ' + (data.error || 'неизвестная ошибка'), 'error');
|
||||
}
|
||||
htmx.trigger('#sync-status', 'refresh');
|
||||
document.getElementById('sync-dropdown-menu').classList.add('hidden');
|
||||
})
|
||||
.catch(error => {
|
||||
showToast('Ошибка синхронизации: ' + error.message, 'error');
|
||||
document.getElementById('sync-dropdown-menu').classList.add('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
function fullSync() {
|
||||
fetch('/api/sync/all', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showToast('Полная синхронизация завершена', 'success');
|
||||
} else {
|
||||
showToast('Ошибка: ' + (data.error || 'неизвестная ошибка'), 'error');
|
||||
}
|
||||
htmx.trigger('#sync-status', 'refresh');
|
||||
document.getElementById('sync-dropdown-menu').classList.add('hidden');
|
||||
})
|
||||
.catch(error => {
|
||||
showToast('Ошибка полной синхронизации: ' + error.message, 'error');
|
||||
document.getElementById('sync-dropdown-menu').classList.add('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
async function checkDbStatus() {
|
||||
try {
|
||||
const resp = await fetch('/api/db-status');
|
||||
@@ -96,10 +165,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
checkDbStatus();
|
||||
checkWritePermission();
|
||||
});
|
||||
// Load last sync time for dropdown
|
||||
async function loadLastSyncTime() {
|
||||
try {
|
||||
const resp = await fetch('/api/sync/status');
|
||||
const data = await resp.json();
|
||||
if (data.last_pricelist_sync) {
|
||||
const date = new Date(data.last_pricelist_sync);
|
||||
document.getElementById('last-sync-time').textContent = date.toLocaleString('ru-RU');
|
||||
} else {
|
||||
document.getElementById('last-sync-time').textContent = 'Нет данных';
|
||||
}
|
||||
} catch(e) {
|
||||
console.error('Failed to load last sync time:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// Load last sync time when page loads
|
||||
document.addEventListener('DOMContentLoaded', loadLastSyncTime);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user