Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
320c4066f3 | ||
|
|
cda74e65db | ||
|
|
d1a19b043d | ||
|
|
8c4de96432 |
@@ -171,7 +171,7 @@ cat > "$DIST_DIR/install.html" << INSTALLEOF
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="text-align: center; margin-top: 40px; color: #5c6b7f; font-size: 0.8rem;">
|
<div style="text-align: center; margin-top: 40px; color: #5c6b7f; font-size: 0.8rem;">
|
||||||
<p>Powered by <strong>Kaufi-Kitchen</strong> 👨🍳</p>
|
<p>Powered by <strong>Kaufis-Kitchen</strong> 👨🍳</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,12 @@
|
|||||||
|
## v1.4.7 (2026-02-24)
|
||||||
|
- **Performance**: Die Bestellhistorie nutzt nun einen inkrementellen Delta-Cache anstatt immer alle Seiten von der API herunterzuladen, was die Ladezeiten für Vielbesteller enorm reduziert.
|
||||||
|
|
||||||
|
## v1.4.6 (2026-02-24)
|
||||||
|
- **Fix**: Die Umrandung für bereits bestellte Menüs der vergangenen Tage ist nun ebenfalls einheitlich violett statt blau.
|
||||||
|
|
||||||
|
## v1.4.5 (2026-02-24)
|
||||||
|
- **Fix**: Doppelten Scrollbalken in der Versionen-Liste entfernt.
|
||||||
|
|
||||||
## v1.4.4 (2026-02-24)
|
## v1.4.4 (2026-02-24)
|
||||||
- **Feature**: Das Versionsmenü enthält nun direkte Links zu GitHub, um Fehler zu melden oder neue Features vorzuschlagen.
|
- **Feature**: Das Versionsmenü enthält nun direkte Links zu GitHub, um Fehler zu melden oder neue Features vorzuschlagen.
|
||||||
|
|
||||||
|
|||||||
4
dist/bookmarklet-payload.js
vendored
4
dist/bookmarklet-payload.js
vendored
File diff suppressed because one or more lines are too long
2
dist/bookmarklet.txt
vendored
2
dist/bookmarklet.txt
vendored
File diff suppressed because one or more lines are too long
26
dist/install.html
vendored
26
dist/install.html
vendored
File diff suppressed because one or more lines are too long
133
dist/kantine-standalone.html
vendored
133
dist/kantine-standalone.html
vendored
@@ -203,6 +203,7 @@ body {
|
|||||||
border-color: #f59e0b;
|
border-color: #f59e0b;
|
||||||
color: var(--accent-color);
|
color: var(--accent-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-btn.new-week-available.active {
|
.nav-btn.new-week-available.active {
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
@@ -211,9 +212,11 @@ body {
|
|||||||
0% {
|
0% {
|
||||||
box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.7);
|
box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.7);
|
||||||
}
|
}
|
||||||
|
|
||||||
70% {
|
70% {
|
||||||
box-shadow: 0 0 0 10px rgba(245, 158, 11, 0);
|
box-shadow: 0 0 0 10px rgba(245, 158, 11, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
box-shadow: 0 0 0 0 rgba(245, 158, 11, 0);
|
box-shadow: 0 0 0 0 rgba(245, 158, 11, 0);
|
||||||
}
|
}
|
||||||
@@ -809,7 +812,7 @@ body {
|
|||||||
/* No opacity/filter here - fully visible */
|
/* No opacity/filter here - fully visible */
|
||||||
background: var(--bg-card);
|
background: var(--bg-card);
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
border: 1px solid var(--accent-color);
|
border: 1px solid #8b5cf6;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
margin: 0 -1rem 1.5rem -1rem;
|
margin: 0 -1rem 1.5rem -1rem;
|
||||||
@@ -1629,8 +1632,6 @@ body {
|
|||||||
.version-list {
|
.version-list {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
max-height: 350px;
|
|
||||||
overflow-y: auto;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2020,7 +2021,7 @@ body {
|
|||||||
<div class="brand">
|
<div class="brand">
|
||||||
<span class="material-icons-round logo-icon">restaurant_menu</span>
|
<span class="material-icons-round logo-icon">restaurant_menu</span>
|
||||||
<div class="header-left">
|
<div class="header-left">
|
||||||
<h1>Kantinen Übersicht <small class="version-tag" style="font-size: 0.6em; opacity: 0.7; font-weight: 400; cursor: pointer;" title="Klick für Versionsmenü">v1.4.4</small></h1>
|
<h1>Kantinen Übersicht <small class="version-tag" style="font-size: 0.6em; opacity: 0.7; font-weight: 400; cursor: pointer;" title="Klick für Versionsmenü">v1.4.7</small></h1>
|
||||||
<div id="last-updated-subtitle" class="subtitle"></div>
|
<div id="last-updated-subtitle" class="subtitle"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-group" style="margin-left: 1rem;">
|
<div class="nav-group" style="margin-left: 1rem;">
|
||||||
@@ -2162,7 +2163,7 @@ body {
|
|||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div style="margin-bottom: 1rem;">
|
<div style="margin-bottom: 1rem;">
|
||||||
<strong>Aktuell:</strong> <span id="version-current">v1.4.4</span>
|
<strong>Aktuell:</strong> <span id="version-current">v1.4.7</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="dev-toggle">
|
<div class="dev-toggle">
|
||||||
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;">
|
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;">
|
||||||
@@ -2519,37 +2520,49 @@ body {
|
|||||||
const progressFill = document.getElementById('history-progress-fill');
|
const progressFill = document.getElementById('history-progress-fill');
|
||||||
const progressText = document.getElementById('history-progress-text');
|
const progressText = document.getElementById('history-progress-text');
|
||||||
|
|
||||||
// Check memory cache first
|
// Check local storage cache (we still use memory cache if available)
|
||||||
|
let localCache = [];
|
||||||
if (fullOrderHistoryCache) {
|
if (fullOrderHistoryCache) {
|
||||||
renderHistory(fullOrderHistoryCache);
|
localCache = fullOrderHistoryCache;
|
||||||
return;
|
} else {
|
||||||
}
|
const ls = localStorage.getItem('kantine_history_cache');
|
||||||
|
if (ls) {
|
||||||
// Check local storage cache
|
|
||||||
const localCache = localStorage.getItem('kantine_history_cache');
|
|
||||||
if (localCache) {
|
|
||||||
try {
|
try {
|
||||||
fullOrderHistoryCache = JSON.parse(localCache);
|
localCache = JSON.parse(ls);
|
||||||
renderHistory(fullOrderHistoryCache);
|
fullOrderHistoryCache = localCache;
|
||||||
return;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('History cache parse error', e);
|
console.warn('History cache parse error', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show cached version immediately if we have one
|
||||||
|
if (localCache.length > 0) {
|
||||||
|
renderHistory(localCache);
|
||||||
|
}
|
||||||
|
|
||||||
if (!authToken) return;
|
if (!authToken) return;
|
||||||
|
|
||||||
|
// Start background delta sync
|
||||||
|
if (localCache.length === 0) {
|
||||||
historyContent.innerHTML = '';
|
historyContent.innerHTML = '';
|
||||||
historyLoading.classList.remove('hidden');
|
historyLoading.classList.remove('hidden');
|
||||||
progressFill.style.width = '0%';
|
}
|
||||||
progressText.textContent = 'Lade Bestellhistorie...';
|
|
||||||
|
|
||||||
let nextUrl = `${API_BASE}/user/orders/?venue=${VENUE_ID}&ordering=-created&limit=50`;
|
progressFill.style.width = '0%';
|
||||||
let allOrders = [];
|
progressText.textContent = localCache.length > 0 ? 'Suche nach neuen Bestellungen...' : 'Lade Bestellhistorie...';
|
||||||
|
if (localCache.length > 0) historyLoading.classList.remove('hidden');
|
||||||
|
|
||||||
|
let nextUrl = localCache.length > 0
|
||||||
|
? `${API_BASE}/user/orders/?venue=${VENUE_ID}&ordering=-created&limit=5`
|
||||||
|
: `${API_BASE}/user/orders/?venue=${VENUE_ID}&ordering=-created&limit=50`;
|
||||||
|
let fetchedOrders = [];
|
||||||
let totalCount = 0;
|
let totalCount = 0;
|
||||||
|
let requiresFullFetch = localCache.length === 0;
|
||||||
|
let deltaComplete = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
while (nextUrl) {
|
while (nextUrl && !deltaComplete) {
|
||||||
const response = await fetch(nextUrl, { headers: apiHeaders(authToken) });
|
const response = await fetch(nextUrl, { headers: apiHeaders(authToken) });
|
||||||
if (!response.ok) throw new Error(`Fetch failed: ${response.status}`);
|
if (!response.ok) throw new Error(`Fetch failed: ${response.status}`);
|
||||||
|
|
||||||
@@ -2559,29 +2572,75 @@ body {
|
|||||||
totalCount = data.count;
|
totalCount = data.count;
|
||||||
}
|
}
|
||||||
|
|
||||||
allOrders = allOrders.concat(data.results || []);
|
const results = data.results || [];
|
||||||
|
|
||||||
|
for (const order of results) {
|
||||||
|
// Check if we hit an order that is already in our cache AND has the exact same state/update time
|
||||||
|
// Bessa returns 'updated' timestamp, we can use it to determine if anything changed
|
||||||
|
const existingOrderIndex = localCache.findIndex(cached => cached.id === order.id);
|
||||||
|
|
||||||
|
if (!requiresFullFetch && existingOrderIndex !== -1) {
|
||||||
|
const existingOrder = localCache[existingOrderIndex];
|
||||||
|
// If order exists and wasn't updated since our cache, we've reached the point
|
||||||
|
// where everything older is already correctly cached.
|
||||||
|
// order.updated is an ISO string like "2025-02-18T10:30:15.123456Z"
|
||||||
|
if (existingOrder.updated === order.updated && existingOrder.order_state === order.order_state) {
|
||||||
|
deltaComplete = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fetchedOrders.push(order);
|
||||||
|
}
|
||||||
|
|
||||||
// Update progress
|
// Update progress
|
||||||
|
if (!deltaComplete && requiresFullFetch) {
|
||||||
if (totalCount > 0) {
|
if (totalCount > 0) {
|
||||||
const pct = Math.round((allOrders.length / totalCount) * 100);
|
const pct = Math.round((fetchedOrders.length / totalCount) * 100);
|
||||||
progressFill.style.width = `${pct}%`;
|
progressFill.style.width = `${pct}%`;
|
||||||
progressText.textContent = `Lade Bestellung ${allOrders.length} von ${totalCount}...`;
|
progressText.textContent = `Lade Bestellung ${fetchedOrders.length} von ${totalCount}...`;
|
||||||
} else {
|
} else {
|
||||||
progressText.textContent = `Lade Bestellung ${allOrders.length}...`;
|
progressText.textContent = `Lade Bestellung ${fetchedOrders.length}...`;
|
||||||
|
}
|
||||||
|
} else if (!deltaComplete) {
|
||||||
|
progressText.textContent = `${fetchedOrders.length} neue/geänderte Bestellungen gefunden...`;
|
||||||
}
|
}
|
||||||
|
|
||||||
nextUrl = data.next;
|
nextUrl = deltaComplete ? null : data.next;
|
||||||
}
|
}
|
||||||
|
|
||||||
fullOrderHistoryCache = allOrders;
|
// Merge fetched orders with cache
|
||||||
|
if (fetchedOrders.length > 0) {
|
||||||
|
// We have new/updated orders. We need to merge them into the cache.
|
||||||
|
// 1. Create a map of the existing cache for quick ID lookup
|
||||||
|
const cacheMap = new Map(localCache.map(o => [o.id, o]));
|
||||||
|
|
||||||
|
// 2. Update/Insert the newly fetched orders
|
||||||
|
for (const order of fetchedOrders) {
|
||||||
|
cacheMap.set(order.id, order); // Overwrites existing, or adds new
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Convert back to array and sort by created date (descending)
|
||||||
|
const mergedOrders = Array.from(cacheMap.values());
|
||||||
|
mergedOrders.sort((a, b) => new Date(b.created) - new Date(a.created));
|
||||||
|
|
||||||
|
fullOrderHistoryCache = mergedOrders;
|
||||||
try {
|
try {
|
||||||
localStorage.setItem('kantine_history_cache', JSON.stringify(allOrders));
|
localStorage.setItem('kantine_history_cache', JSON.stringify(mergedOrders));
|
||||||
} catch (e) { console.warn('History cache write error', e); }
|
} catch (e) {
|
||||||
|
console.warn('History cache write error', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render the updated history
|
||||||
renderHistory(fullOrderHistoryCache);
|
renderHistory(fullOrderHistoryCache);
|
||||||
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching full history:', error);
|
console.error('Error in history sync:', error);
|
||||||
|
if (localCache.length === 0) {
|
||||||
historyContent.innerHTML = `<p style="color:var(--error-color);text-align:center;">Fehler beim Laden der Historie.</p>`;
|
historyContent.innerHTML = `<p style="color:var(--error-color);text-align:center;">Fehler beim Laden der Historie.</p>`;
|
||||||
|
} else {
|
||||||
|
showToast('Hintergrund-Synchronisation fehlgeschlagen', 'error');
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
historyLoading.classList.add('hidden');
|
historyLoading.classList.add('hidden');
|
||||||
}
|
}
|
||||||
@@ -2783,7 +2842,7 @@ body {
|
|||||||
|
|
||||||
if (response.ok || response.status === 201) {
|
if (response.ok || response.status === 201) {
|
||||||
showToast(`Bestellt: ${name}`, 'success');
|
showToast(`Bestellt: ${name}`, 'success');
|
||||||
localStorage.removeItem('kantine_history_cache');
|
fullOrderHistoryCache = null; // Clear memory cache so next history open triggers delta sync
|
||||||
await fetchOrders();
|
await fetchOrders();
|
||||||
} else {
|
} else {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
@@ -2813,7 +2872,7 @@ body {
|
|||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
showToast(`Storniert: ${name}`, 'success');
|
showToast(`Storniert: ${name}`, 'success');
|
||||||
localStorage.removeItem('kantine_history_cache');
|
fullOrderHistoryCache = null; // Clear memory cache so next history open triggers delta sync
|
||||||
await fetchOrders();
|
await fetchOrders();
|
||||||
} else {
|
} else {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
@@ -2871,11 +2930,11 @@ body {
|
|||||||
bellBtn.title = `Zuletzt geprüft: ${timeStr}`;
|
bellBtn.title = `Zuletzt geprüft: ${timeStr}`;
|
||||||
|
|
||||||
if (anyAvailable) {
|
if (anyAvailable) {
|
||||||
bellIcon.style.color = 'var(--success-color)';
|
|
||||||
bellIcon.style.textShadow = '0 0 10px rgba(16, 185, 129, 0.4)';
|
|
||||||
} else {
|
|
||||||
bellIcon.style.color = 'var(--warning-color)';
|
bellIcon.style.color = 'var(--warning-color)';
|
||||||
bellIcon.style.textShadow = '0 0 10px rgba(245, 158, 11, 0.4)';
|
bellIcon.style.textShadow = '0 0 10px rgba(245, 158, 11, 0.4)';
|
||||||
|
} else {
|
||||||
|
bellIcon.style.color = 'var(--text-secondary)';
|
||||||
|
bellIcon.style.textShadow = 'none';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3833,7 +3892,7 @@ body {
|
|||||||
|
|
||||||
// Periodic update check (runs on init + every hour)
|
// Periodic update check (runs on init + every hour)
|
||||||
async function checkForUpdates() {
|
async function checkForUpdates() {
|
||||||
const currentVersion = 'v1.4.4';
|
const currentVersion = 'v1.4.7';
|
||||||
const devMode = localStorage.getItem('kantine_dev_mode') === 'true';
|
const devMode = localStorage.getItem('kantine_dev_mode') === 'true';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -3874,7 +3933,7 @@ body {
|
|||||||
const modal = document.getElementById('version-modal');
|
const modal = document.getElementById('version-modal');
|
||||||
const container = document.getElementById('version-list-container');
|
const container = document.getElementById('version-list-container');
|
||||||
const devToggle = document.getElementById('dev-mode-toggle');
|
const devToggle = document.getElementById('dev-mode-toggle');
|
||||||
const currentVersion = 'v1.4.4';
|
const currentVersion = 'v1.4.7';
|
||||||
|
|
||||||
if (!modal) return;
|
if (!modal) return;
|
||||||
modal.classList.remove('hidden');
|
modal.classList.remove('hidden');
|
||||||
|
|||||||
118
kantine.js
118
kantine.js
@@ -570,37 +570,49 @@
|
|||||||
const progressFill = document.getElementById('history-progress-fill');
|
const progressFill = document.getElementById('history-progress-fill');
|
||||||
const progressText = document.getElementById('history-progress-text');
|
const progressText = document.getElementById('history-progress-text');
|
||||||
|
|
||||||
// Check memory cache first
|
// Check local storage cache (we still use memory cache if available)
|
||||||
|
let localCache = [];
|
||||||
if (fullOrderHistoryCache) {
|
if (fullOrderHistoryCache) {
|
||||||
renderHistory(fullOrderHistoryCache);
|
localCache = fullOrderHistoryCache;
|
||||||
return;
|
} else {
|
||||||
}
|
const ls = localStorage.getItem('kantine_history_cache');
|
||||||
|
if (ls) {
|
||||||
// Check local storage cache
|
|
||||||
const localCache = localStorage.getItem('kantine_history_cache');
|
|
||||||
if (localCache) {
|
|
||||||
try {
|
try {
|
||||||
fullOrderHistoryCache = JSON.parse(localCache);
|
localCache = JSON.parse(ls);
|
||||||
renderHistory(fullOrderHistoryCache);
|
fullOrderHistoryCache = localCache;
|
||||||
return;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('History cache parse error', e);
|
console.warn('History cache parse error', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show cached version immediately if we have one
|
||||||
|
if (localCache.length > 0) {
|
||||||
|
renderHistory(localCache);
|
||||||
|
}
|
||||||
|
|
||||||
if (!authToken) return;
|
if (!authToken) return;
|
||||||
|
|
||||||
|
// Start background delta sync
|
||||||
|
if (localCache.length === 0) {
|
||||||
historyContent.innerHTML = '';
|
historyContent.innerHTML = '';
|
||||||
historyLoading.classList.remove('hidden');
|
historyLoading.classList.remove('hidden');
|
||||||
progressFill.style.width = '0%';
|
}
|
||||||
progressText.textContent = 'Lade Bestellhistorie...';
|
|
||||||
|
|
||||||
let nextUrl = `${API_BASE}/user/orders/?venue=${VENUE_ID}&ordering=-created&limit=50`;
|
progressFill.style.width = '0%';
|
||||||
let allOrders = [];
|
progressText.textContent = localCache.length > 0 ? 'Suche nach neuen Bestellungen...' : 'Lade Bestellhistorie...';
|
||||||
|
if (localCache.length > 0) historyLoading.classList.remove('hidden');
|
||||||
|
|
||||||
|
let nextUrl = localCache.length > 0
|
||||||
|
? `${API_BASE}/user/orders/?venue=${VENUE_ID}&ordering=-created&limit=5`
|
||||||
|
: `${API_BASE}/user/orders/?venue=${VENUE_ID}&ordering=-created&limit=50`;
|
||||||
|
let fetchedOrders = [];
|
||||||
let totalCount = 0;
|
let totalCount = 0;
|
||||||
|
let requiresFullFetch = localCache.length === 0;
|
||||||
|
let deltaComplete = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
while (nextUrl) {
|
while (nextUrl && !deltaComplete) {
|
||||||
const response = await fetch(nextUrl, { headers: apiHeaders(authToken) });
|
const response = await fetch(nextUrl, { headers: apiHeaders(authToken) });
|
||||||
if (!response.ok) throw new Error(`Fetch failed: ${response.status}`);
|
if (!response.ok) throw new Error(`Fetch failed: ${response.status}`);
|
||||||
|
|
||||||
@@ -610,29 +622,75 @@
|
|||||||
totalCount = data.count;
|
totalCount = data.count;
|
||||||
}
|
}
|
||||||
|
|
||||||
allOrders = allOrders.concat(data.results || []);
|
const results = data.results || [];
|
||||||
|
|
||||||
|
for (const order of results) {
|
||||||
|
// Check if we hit an order that is already in our cache AND has the exact same state/update time
|
||||||
|
// Bessa returns 'updated' timestamp, we can use it to determine if anything changed
|
||||||
|
const existingOrderIndex = localCache.findIndex(cached => cached.id === order.id);
|
||||||
|
|
||||||
|
if (!requiresFullFetch && existingOrderIndex !== -1) {
|
||||||
|
const existingOrder = localCache[existingOrderIndex];
|
||||||
|
// If order exists and wasn't updated since our cache, we've reached the point
|
||||||
|
// where everything older is already correctly cached.
|
||||||
|
// order.updated is an ISO string like "2025-02-18T10:30:15.123456Z"
|
||||||
|
if (existingOrder.updated === order.updated && existingOrder.order_state === order.order_state) {
|
||||||
|
deltaComplete = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fetchedOrders.push(order);
|
||||||
|
}
|
||||||
|
|
||||||
// Update progress
|
// Update progress
|
||||||
|
if (!deltaComplete && requiresFullFetch) {
|
||||||
if (totalCount > 0) {
|
if (totalCount > 0) {
|
||||||
const pct = Math.round((allOrders.length / totalCount) * 100);
|
const pct = Math.round((fetchedOrders.length / totalCount) * 100);
|
||||||
progressFill.style.width = `${pct}%`;
|
progressFill.style.width = `${pct}%`;
|
||||||
progressText.textContent = `Lade Bestellung ${allOrders.length} von ${totalCount}...`;
|
progressText.textContent = `Lade Bestellung ${fetchedOrders.length} von ${totalCount}...`;
|
||||||
} else {
|
} else {
|
||||||
progressText.textContent = `Lade Bestellung ${allOrders.length}...`;
|
progressText.textContent = `Lade Bestellung ${fetchedOrders.length}...`;
|
||||||
|
}
|
||||||
|
} else if (!deltaComplete) {
|
||||||
|
progressText.textContent = `${fetchedOrders.length} neue/geänderte Bestellungen gefunden...`;
|
||||||
}
|
}
|
||||||
|
|
||||||
nextUrl = data.next;
|
nextUrl = deltaComplete ? null : data.next;
|
||||||
}
|
}
|
||||||
|
|
||||||
fullOrderHistoryCache = allOrders;
|
// Merge fetched orders with cache
|
||||||
|
if (fetchedOrders.length > 0) {
|
||||||
|
// We have new/updated orders. We need to merge them into the cache.
|
||||||
|
// 1. Create a map of the existing cache for quick ID lookup
|
||||||
|
const cacheMap = new Map(localCache.map(o => [o.id, o]));
|
||||||
|
|
||||||
|
// 2. Update/Insert the newly fetched orders
|
||||||
|
for (const order of fetchedOrders) {
|
||||||
|
cacheMap.set(order.id, order); // Overwrites existing, or adds new
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Convert back to array and sort by created date (descending)
|
||||||
|
const mergedOrders = Array.from(cacheMap.values());
|
||||||
|
mergedOrders.sort((a, b) => new Date(b.created) - new Date(a.created));
|
||||||
|
|
||||||
|
fullOrderHistoryCache = mergedOrders;
|
||||||
try {
|
try {
|
||||||
localStorage.setItem('kantine_history_cache', JSON.stringify(allOrders));
|
localStorage.setItem('kantine_history_cache', JSON.stringify(mergedOrders));
|
||||||
} catch (e) { console.warn('History cache write error', e); }
|
} catch (e) {
|
||||||
|
console.warn('History cache write error', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render the updated history
|
||||||
renderHistory(fullOrderHistoryCache);
|
renderHistory(fullOrderHistoryCache);
|
||||||
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching full history:', error);
|
console.error('Error in history sync:', error);
|
||||||
|
if (localCache.length === 0) {
|
||||||
historyContent.innerHTML = `<p style="color:var(--error-color);text-align:center;">Fehler beim Laden der Historie.</p>`;
|
historyContent.innerHTML = `<p style="color:var(--error-color);text-align:center;">Fehler beim Laden der Historie.</p>`;
|
||||||
|
} else {
|
||||||
|
showToast('Hintergrund-Synchronisation fehlgeschlagen', 'error');
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
historyLoading.classList.add('hidden');
|
historyLoading.classList.add('hidden');
|
||||||
}
|
}
|
||||||
@@ -834,7 +892,7 @@
|
|||||||
|
|
||||||
if (response.ok || response.status === 201) {
|
if (response.ok || response.status === 201) {
|
||||||
showToast(`Bestellt: ${name}`, 'success');
|
showToast(`Bestellt: ${name}`, 'success');
|
||||||
localStorage.removeItem('kantine_history_cache');
|
fullOrderHistoryCache = null; // Clear memory cache so next history open triggers delta sync
|
||||||
await fetchOrders();
|
await fetchOrders();
|
||||||
} else {
|
} else {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
@@ -864,7 +922,7 @@
|
|||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
showToast(`Storniert: ${name}`, 'success');
|
showToast(`Storniert: ${name}`, 'success');
|
||||||
localStorage.removeItem('kantine_history_cache');
|
fullOrderHistoryCache = null; // Clear memory cache so next history open triggers delta sync
|
||||||
await fetchOrders();
|
await fetchOrders();
|
||||||
} else {
|
} else {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
@@ -922,11 +980,11 @@
|
|||||||
bellBtn.title = `Zuletzt geprüft: ${timeStr}`;
|
bellBtn.title = `Zuletzt geprüft: ${timeStr}`;
|
||||||
|
|
||||||
if (anyAvailable) {
|
if (anyAvailable) {
|
||||||
bellIcon.style.color = 'var(--success-color)';
|
|
||||||
bellIcon.style.textShadow = '0 0 10px rgba(16, 185, 129, 0.4)';
|
|
||||||
} else {
|
|
||||||
bellIcon.style.color = 'var(--warning-color)';
|
bellIcon.style.color = 'var(--warning-color)';
|
||||||
bellIcon.style.textShadow = '0 0 10px rgba(245, 158, 11, 0.4)';
|
bellIcon.style.textShadow = '0 0 10px rgba(245, 158, 11, 0.4)';
|
||||||
|
} else {
|
||||||
|
bellIcon.style.color = 'var(--text-secondary)';
|
||||||
|
bellIcon.style.textShadow = 'none';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -192,6 +192,7 @@ body {
|
|||||||
border-color: #f59e0b;
|
border-color: #f59e0b;
|
||||||
color: var(--accent-color);
|
color: var(--accent-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-btn.new-week-available.active {
|
.nav-btn.new-week-available.active {
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
@@ -200,9 +201,11 @@ body {
|
|||||||
0% {
|
0% {
|
||||||
box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.7);
|
box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.7);
|
||||||
}
|
}
|
||||||
|
|
||||||
70% {
|
70% {
|
||||||
box-shadow: 0 0 0 10px rgba(245, 158, 11, 0);
|
box-shadow: 0 0 0 10px rgba(245, 158, 11, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
box-shadow: 0 0 0 0 rgba(245, 158, 11, 0);
|
box-shadow: 0 0 0 0 rgba(245, 158, 11, 0);
|
||||||
}
|
}
|
||||||
@@ -798,7 +801,7 @@ body {
|
|||||||
/* No opacity/filter here - fully visible */
|
/* No opacity/filter here - fully visible */
|
||||||
background: var(--bg-card);
|
background: var(--bg-card);
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
border: 1px solid var(--accent-color);
|
border: 1px solid #8b5cf6;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
margin: 0 -1rem 1.5rem -1rem;
|
margin: 0 -1rem 1.5rem -1rem;
|
||||||
@@ -1618,8 +1621,6 @@ body {
|
|||||||
.version-list {
|
.version-list {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
max-height: 350px;
|
|
||||||
overflow-y: auto;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -108,7 +108,8 @@ try {
|
|||||||
[/function\s+isNewer/, 'isNewer function'],
|
[/function\s+isNewer/, 'isNewer function'],
|
||||||
[/function\s+openVersionMenu/, 'openVersionMenu function'],
|
[/function\s+openVersionMenu/, 'openVersionMenu function'],
|
||||||
[/kantine_dev_mode/, 'dev-mode localStorage key'],
|
[/kantine_dev_mode/, 'dev-mode localStorage key'],
|
||||||
[/function\s+isCacheFresh/, 'isCacheFresh function']
|
[/function\s+isCacheFresh/, 'isCacheFresh function'],
|
||||||
|
[/limit=5/, 'Delta fetch limit parameter']
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const [regex, label] of checks) {
|
for (const [regex, label] of checks) {
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
v1.4.4
|
v1.4.7
|
||||||
|
|||||||
Reference in New Issue
Block a user