Add loading indicator for main items in admin view to enhance user experience

This commit is contained in:
2026-04-12 20:21:47 +02:00
parent 76dd00831e
commit 05c4c73635
2 changed files with 194 additions and 0 deletions
+97
View File
@@ -730,8 +730,43 @@
let mainItemsLoadingMore = false; let mainItemsLoadingMore = false;
let mainItemsObserver = null; let mainItemsObserver = null;
let mainItemsSentinel = null; let mainItemsSentinel = null;
let mainItemsLoadingIndicator = null;
let mainItemsScrollPrefetchBound = false; let mainItemsScrollPrefetchBound = false;
function ensureMainItemsLoadingIndicator() {
const itemsContainer = document.querySelector('#items-container');
if (!itemsContainer) return null;
if (!mainItemsLoadingIndicator) {
mainItemsLoadingIndicator = document.createElement('div');
mainItemsLoadingIndicator.id = 'main-items-loading-indicator';
mainItemsLoadingIndicator.className = 'items-loading-indicator';
mainItemsLoadingIndicator.innerHTML = `
<div class="loading-track"><div class="loading-fill"></div></div>
<div class="loading-label">Weitere Objekte werden vorbereitet...</div>
`;
}
itemsContainer.appendChild(mainItemsLoadingIndicator);
return itemsContainer;
}
function updateMainItemsLoadingIndicator() {
const itemsContainer = ensureMainItemsLoadingIndicator();
if (!itemsContainer || !mainItemsLoadingIndicator) return;
const shouldShow = mainItemsHasMore || mainItemsLoadingMore;
mainItemsLoadingIndicator.style.display = shouldShow ? 'flex' : 'none';
mainItemsLoadingIndicator.classList.toggle('is-loading', mainItemsLoadingMore);
const label = mainItemsLoadingIndicator.querySelector('.loading-label');
if (label) {
label.textContent = mainItemsLoadingMore
? 'Weitere Objekte werden geladen...'
: 'Weitere Objekte werden vorbereitet...';
}
}
function maybePrefetchMainItems() { function maybePrefetchMainItems() {
const itemsContainer = document.querySelector('#items-container'); const itemsContainer = document.querySelector('#items-container');
if (!itemsContainer || !mainItemsHasMore || mainItemsLoadingMore) { if (!itemsContainer || !mainItemsHasMore || mainItemsLoadingMore) {
@@ -745,8 +780,10 @@
} }
mainItemsLoadingMore = true; mainItemsLoadingMore = true;
updateMainItemsLoadingIndicator();
loadItems(mainItemsNextOffset, true).finally(() => { loadItems(mainItemsNextOffset, true).finally(() => {
mainItemsLoadingMore = false; mainItemsLoadingMore = false;
updateMainItemsLoadingIndicator();
}); });
} }
@@ -1054,11 +1091,15 @@
mainItemsNextOffset = (data.offset || offset) + (data.count || pageItems.length); mainItemsNextOffset = (data.offset || offset) + (data.count || pageItems.length);
mainItemsHasMore = Boolean(data.has_more); mainItemsHasMore = Boolean(data.has_more);
updateMainItemsLoadingIndicator();
setupMainItemsLazyLoading(); setupMainItemsLazyLoading();
maybePrefetchMainItems(); maybePrefetchMainItems();
}) })
.catch(error => { .catch(error => {
console.error('Error fetching items:', error); console.error('Error fetching items:', error);
mainItemsLoadingMore = false;
mainItemsHasMore = false;
updateMainItemsLoadingIndicator();
if (append) { if (append) {
return; return;
} }
@@ -1072,6 +1113,8 @@
const itemsContainer = document.querySelector('#items-container'); const itemsContainer = document.querySelector('#items-container');
if (!itemsContainer) return null; if (!itemsContainer) return null;
ensureMainItemsLoadingIndicator();
if (!mainItemsSentinel) { if (!mainItemsSentinel) {
mainItemsSentinel = document.createElement('div'); mainItemsSentinel = document.createElement('div');
mainItemsSentinel.id = 'main-items-sentinel'; mainItemsSentinel.id = 'main-items-sentinel';
@@ -1117,8 +1160,10 @@
} }
mainItemsLoadingMore = true; mainItemsLoadingMore = true;
updateMainItemsLoadingIndicator();
loadItems(mainItemsNextOffset, true).finally(() => { loadItems(mainItemsNextOffset, true).finally(() => {
mainItemsLoadingMore = false; mainItemsLoadingMore = false;
updateMainItemsLoadingIndicator();
}); });
}, { }, {
root: itemsContainer, root: itemsContainer,
@@ -1127,6 +1172,7 @@
}); });
mainItemsObserver.observe(mainItemsSentinel); mainItemsObserver.observe(mainItemsSentinel);
updateMainItemsLoadingIndicator();
} }
function searchByCode() { function searchByCode() {
@@ -2846,6 +2892,57 @@
scroll-behavior: smooth; scroll-behavior: smooth;
} }
.items-loading-indicator {
display: none;
flex: 0 0 220px;
min-width: 220px;
max-width: 220px;
height: 180px;
align-self: center;
margin-right: 8px;
border: 1px solid #dbe3ee;
border-radius: 10px;
background: #f8fafc;
color: #334155;
justify-content: center;
align-items: center;
flex-direction: column;
gap: 10px;
scroll-snap-align: center;
}
.items-loading-indicator .loading-label {
font-size: 0.84rem;
text-align: center;
line-height: 1.3;
max-width: 170px;
}
.items-loading-indicator .loading-track {
width: 160px;
height: 8px;
border-radius: 999px;
background: #e2e8f0;
overflow: hidden;
}
.items-loading-indicator .loading-fill {
width: 45%;
height: 100%;
border-radius: inherit;
background: linear-gradient(90deg, #60a5fa, #2563eb);
transform: translateX(-100%);
}
.items-loading-indicator.is-loading .loading-fill {
animation: items-loader-slide 1.05s ease-in-out infinite;
}
@keyframes items-loader-slide {
0% { transform: translateX(-100%); }
100% { transform: translateX(260%); }
}
.item-card { .item-card {
background-color: #fff; background-color: #fff;
border: 1px solid #ddd; border: 1px solid #ddd;
+97
View File
@@ -323,6 +323,57 @@
scroll-behavior: smooth; scroll-behavior: smooth;
} }
.items-loading-indicator {
display: none;
flex: 0 0 220px;
min-width: 220px;
max-width: 220px;
height: 180px;
align-self: center;
margin-right: 8px;
border: 1px solid #dbe3ee;
border-radius: 10px;
background: #f8fafc;
color: #334155;
justify-content: center;
align-items: center;
flex-direction: column;
gap: 10px;
scroll-snap-align: center;
}
.items-loading-indicator .loading-label {
font-size: 0.84rem;
text-align: center;
line-height: 1.3;
max-width: 170px;
}
.items-loading-indicator .loading-track {
width: 160px;
height: 8px;
border-radius: 999px;
background: #e2e8f0;
overflow: hidden;
}
.items-loading-indicator .loading-fill {
width: 45%;
height: 100%;
border-radius: inherit;
background: linear-gradient(90deg, #60a5fa, #2563eb);
transform: translateX(-100%);
}
.items-loading-indicator.is-loading .loading-fill {
animation: items-loader-slide 1.05s ease-in-out infinite;
}
@keyframes items-loader-slide {
0% { transform: translateX(-100%); }
100% { transform: translateX(260%); }
}
.item-card { .item-card {
background-color: #fff; background-color: #fff;
border: 1px solid #ddd; border: 1px solid #ddd;
@@ -3261,9 +3312,44 @@ document.addEventListener('DOMContentLoaded', ()=>{
let mainAdminItemsLoadingMore = false; let mainAdminItemsLoadingMore = false;
let mainAdminItemsObserver = null; let mainAdminItemsObserver = null;
let mainAdminItemsSentinel = null; let mainAdminItemsSentinel = null;
let mainAdminItemsLoadingIndicator = null;
let mainAdminItemsScrollPrefetchBound = false; let mainAdminItemsScrollPrefetchBound = false;
let totalItemsInSystem = 0; let totalItemsInSystem = 0;
function ensureMainAdminItemsLoadingIndicator() {
const itemsContainer = document.querySelector('#items-container');
if (!itemsContainer) return null;
if (!mainAdminItemsLoadingIndicator) {
mainAdminItemsLoadingIndicator = document.createElement('div');
mainAdminItemsLoadingIndicator.id = 'main-admin-items-loading-indicator';
mainAdminItemsLoadingIndicator.className = 'items-loading-indicator';
mainAdminItemsLoadingIndicator.innerHTML = `
<div class="loading-track"><div class="loading-fill"></div></div>
<div class="loading-label">Weitere Objekte werden vorbereitet...</div>
`;
}
itemsContainer.appendChild(mainAdminItemsLoadingIndicator);
return itemsContainer;
}
function updateMainAdminItemsLoadingIndicator() {
const itemsContainer = ensureMainAdminItemsLoadingIndicator();
if (!itemsContainer || !mainAdminItemsLoadingIndicator) return;
const shouldShow = mainAdminItemsHasMore || mainAdminItemsLoadingMore;
mainAdminItemsLoadingIndicator.style.display = shouldShow ? 'flex' : 'none';
mainAdminItemsLoadingIndicator.classList.toggle('is-loading', mainAdminItemsLoadingMore);
const label = mainAdminItemsLoadingIndicator.querySelector('.loading-label');
if (label) {
label.textContent = mainAdminItemsLoadingMore
? 'Weitere Objekte werden geladen...'
: 'Weitere Objekte werden vorbereitet...';
}
}
function maybePrefetchMainAdminItems() { function maybePrefetchMainAdminItems() {
const itemsContainer = document.querySelector('#items-container'); const itemsContainer = document.querySelector('#items-container');
if (!itemsContainer || !mainAdminItemsHasMore || mainAdminItemsLoadingMore) { if (!itemsContainer || !mainAdminItemsHasMore || mainAdminItemsLoadingMore) {
@@ -3277,8 +3363,10 @@ document.addEventListener('DOMContentLoaded', ()=>{
} }
mainAdminItemsLoadingMore = true; mainAdminItemsLoadingMore = true;
updateMainAdminItemsLoadingIndicator();
loadItems(mainAdminItemsNextOffset, true).finally(() => { loadItems(mainAdminItemsNextOffset, true).finally(() => {
mainAdminItemsLoadingMore = false; mainAdminItemsLoadingMore = false;
updateMainAdminItemsLoadingIndicator();
}); });
} }
@@ -3582,11 +3670,15 @@ document.addEventListener('DOMContentLoaded', ()=>{
mainAdminItemsNextOffset = (data.offset || offset) + (data.count || pageItems.length); mainAdminItemsNextOffset = (data.offset || offset) + (data.count || pageItems.length);
mainAdminItemsHasMore = Boolean(data.has_more); mainAdminItemsHasMore = Boolean(data.has_more);
updateMainAdminItemsLoadingIndicator();
setupMainAdminItemsLazyLoading(); setupMainAdminItemsLazyLoading();
maybePrefetchMainAdminItems(); maybePrefetchMainAdminItems();
}) })
.catch(error => { .catch(error => {
console.error('Error fetching items:', error); console.error('Error fetching items:', error);
mainAdminItemsLoadingMore = false;
mainAdminItemsHasMore = false;
updateMainAdminItemsLoadingIndicator();
if (append) { if (append) {
return; return;
} }
@@ -3604,6 +3696,8 @@ document.addEventListener('DOMContentLoaded', ()=>{
const itemsContainer = document.querySelector('#items-container'); const itemsContainer = document.querySelector('#items-container');
if (!itemsContainer) return null; if (!itemsContainer) return null;
ensureMainAdminItemsLoadingIndicator();
if (!mainAdminItemsSentinel) { if (!mainAdminItemsSentinel) {
mainAdminItemsSentinel = document.createElement('div'); mainAdminItemsSentinel = document.createElement('div');
mainAdminItemsSentinel.id = 'main-admin-items-sentinel'; mainAdminItemsSentinel.id = 'main-admin-items-sentinel';
@@ -3644,8 +3738,10 @@ document.addEventListener('DOMContentLoaded', ()=>{
} }
mainAdminItemsLoadingMore = true; mainAdminItemsLoadingMore = true;
updateMainAdminItemsLoadingIndicator();
loadItems(mainAdminItemsNextOffset, true).finally(() => { loadItems(mainAdminItemsNextOffset, true).finally(() => {
mainAdminItemsLoadingMore = false; mainAdminItemsLoadingMore = false;
updateMainAdminItemsLoadingIndicator();
}); });
}, { }, {
root: itemsContainer, root: itemsContainer,
@@ -3654,6 +3750,7 @@ document.addEventListener('DOMContentLoaded', ()=>{
}); });
mainAdminItemsObserver.observe(mainAdminItemsSentinel); mainAdminItemsObserver.observe(mainAdminItemsSentinel);
updateMainAdminItemsLoadingIndicator();
} }
function searchByCode() { function searchByCode() {