Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce7d8a3de5 | ||
|
|
0309f488bd | ||
|
|
d82762430f | ||
|
|
54e5ada03d |
@@ -54,10 +54,13 @@ Das System umfasst die Darstellung von Menüplänen in einer Wochenübersicht, d
|
|||||||
| FR-071 | Die Hervorhebung muss anhand von Menüname und Beschreibung erfolgen (Substring-Matching, case-insensitive). | Niedrig | v1.1.0 |
|
| FR-071 | Die Hervorhebung muss anhand von Menüname und Beschreibung erfolgen (Substring-Matching, case-insensitive). | Niedrig | v1.1.0 |
|
||||||
| FR-072 | Hervorgehobene Menüs müssen ein Tag-Badge mit dem matchenden Schlagwort anzeigen. | Niedrig | v1.2.4 |
|
| FR-072 | Hervorgehobene Menüs müssen ein Tag-Badge mit dem matchenden Schlagwort anzeigen. | Niedrig | v1.2.4 |
|
||||||
| FR-073 | Die Nächste-Woche-Navigation muss die Anzahl der dort gefundenen Highlights als Badge anzeigen. | Niedrig | v1.2.5 |
|
| FR-073 | Die Nächste-Woche-Navigation muss die Anzahl der dort gefundenen Highlights als Badge anzeigen. | Niedrig | v1.2.5 |
|
||||||
| **Darstellung & Theming** | | | |
|
|
||||||
| FR-080 | Das System muss einen hellen und einen dunklen Darstellungsmodus (Light/Dark Theme) unterstützen. | Niedrig | v1.0.1 |
|
| FR-080 | Das System muss einen hellen und einen dunklen Darstellungsmodus (Light/Dark Theme) unterstützen. | Niedrig | v1.0.1 |
|
||||||
| FR-081 | Die Theme-Präferenz des Benutzers muss persistent gespeichert werden. | Niedrig | v1.0.1 |
|
| FR-081 | Die Theme-Präferenz des Benutzers muss persistent gespeichert werden. | Niedrig | v1.0.1 |
|
||||||
| FR-082 | Das System muss beim erstmaligen Laden die Betriebssystem-Präferenz für das Farbschema berücksichtigen. | Niedrig | v1.0.1 |
|
| FR-082 | Das System muss beim erstmaligen Laden die Betriebssystem-Präferenz für das Farbschema berücksichtigen. | Niedrig | v1.0.1 |
|
||||||
|
| **Header UI & Navigation** | | | |
|
||||||
|
| FR-090 | Die Hauptnavigation (Wochen-Toggles) muss linksbündig neben dem App-Titel positioniert sein. | Niedrig | v1.5.0 |
|
||||||
|
| FR-091 | Ein dynamisches Alarm-Icon im Header muss den Überwachungsstatus geflaggter Menüs anzeigen (Gelb=Überwachung aktiv, Grün=Menü verfügbar, Versteckt=keine Flags). Der Tooltip muss den Zeitpunkt der letzten Prüfung als relativen String (z.B. "vor 4 Min.") enthalten. | Mittel | v1.5.0 |
|
||||||
|
| FR-092 | Sobald über den Daten-Refresh erstmals Menüdaten für die Nächste Woche geladen werden, muss der entsprechende Navigation-Button animiert und farblich (Gelb) hervorgehoben werden. Zusätzlich muss einmalig ein Hinweis eingeblendet werden. Bei Klick auf den Button muss die Hervorhebung erlöschen. | Mittel | v1.6.0 |
|
||||||
| **Benutzer-Feedback** | | | |
|
| **Benutzer-Feedback** | | | |
|
||||||
| FR-090 | Alle benutzerrelevanten Aktionen (Bestellung, Stornierung, Fehler) müssen durch nicht-blockierende Benachrichtigungen (Toasts) bestätigt werden. | Mittel | v1.0.1 |
|
| FR-090 | Alle benutzerrelevanten Aktionen (Bestellung, Stornierung, Fehler) müssen durch nicht-blockierende Benachrichtigungen (Toasts) bestätigt werden. | Mittel | v1.0.1 |
|
||||||
| FR-091 | Bei einem Verbindungsfehler muss ein Fehlerdialog mit Fallback-Link zur Originalseite angezeigt werden. | Mittel | v1.0.1 |
|
| FR-091 | Bei einem Verbindungsfehler muss ein Fehlerdialog mit Fallback-Link zur Originalseite angezeigt werden. | Mittel | v1.0.1 |
|
||||||
@@ -69,6 +72,7 @@ Das System umfasst die Darstellung von Menüplänen in einer Wochenübersicht, d
|
|||||||
| FR-112 | Benutzer müssen eine Versionsliste mit Installationslinks einsehen können (Versionsmenü). | Niedrig | v1.3.0 |
|
| FR-112 | Benutzer müssen eine Versionsliste mit Installationslinks einsehen können (Versionsmenü). | Niedrig | v1.3.0 |
|
||||||
| FR-113 | Es muss möglich sein, zu einer älteren Version zurückzukehren (Downgrade). | Niedrig | v1.3.0 |
|
| FR-113 | Es muss möglich sein, zu einer älteren Version zurückzukehren (Downgrade). | Niedrig | v1.3.0 |
|
||||||
| FR-114 | Ein Dev-Mode muss es ermöglichen, zwischen stabilen Releases und Entwicklungs-Tags umzuschalten. | Niedrig | v1.3.0 |
|
| FR-114 | Ein Dev-Mode muss es ermöglichen, zwischen stabilen Releases und Entwicklungs-Tags umzuschalten. | Niedrig | v1.3.0 |
|
||||||
|
| FR-115 | Das Versionsmenü muss Links zur Erstellung von Feature-Requests und Bug-Reports auf GitHub enthalten. | Niedrig | v1.4.4 |
|
||||||
|
|
||||||
## 3. Nicht-funktionale Anforderungen
|
## 3. Nicht-funktionale Anforderungen
|
||||||
|
|
||||||
|
|||||||
BIN
__pycache__/test_build.cpython-312-pytest-9.0.2.pyc
Executable file
BIN
__pycache__/test_build.cpython-312-pytest-9.0.2.pyc
Executable file
Binary file not shown.
@@ -1,3 +1,9 @@
|
|||||||
|
## v1.4.4 (2026-02-24)
|
||||||
|
- **Feature**: Das Versionsmenü enthält nun direkte Links zu GitHub, um Fehler zu melden oder neue Features vorzuschlagen.
|
||||||
|
|
||||||
|
## v1.4.3 (2026-02-24)
|
||||||
|
- **Fix**: Der Rahmen des "Heute Bestellt" Menüs ist nun konsequent violett (passend zum Glow-Effekt).
|
||||||
|
|
||||||
## v1.4.2 (2026-02-23)
|
## v1.4.2 (2026-02-23)
|
||||||
- **Fix**: Das "Heute Bestellt" Menü leuchtet nun stimmig im Design-Violett statt Blau.
|
- **Fix**: Das "Heute Bestellt" Menü leuchtet nun stimmig im Design-Violett statt Blau.
|
||||||
- **Fix**: Abfangen des GitHub API Rate Limit (403) im Versionsdialog mit einer freundlicheren Fehlermeldung, da der User-Agent im Browser nicht manuell gesetzt werden darf.
|
- **Fix**: Abfangen des GitHub API Rate Limit (403) im Versionsdialog mit einer freundlicheren Fehlermeldung, da der User-Agent im Browser nicht manuell gesetzt werden darf.
|
||||||
|
|||||||
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
18
dist/install.html
vendored
18
dist/install.html
vendored
File diff suppressed because one or more lines are too long
132
dist/kantine-standalone.html
vendored
132
dist/kantine-standalone.html
vendored
@@ -197,6 +197,29 @@ body {
|
|||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Notification state for Next Week */
|
||||||
|
.nav-btn.new-week-available {
|
||||||
|
animation: goldPulse 2s infinite;
|
||||||
|
border-color: #f59e0b;
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
.nav-btn.new-week-available.active {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes goldPulse {
|
||||||
|
0% {
|
||||||
|
box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.7);
|
||||||
|
}
|
||||||
|
70% {
|
||||||
|
box-shadow: 0 0 0 10px rgba(245, 158, 11, 0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
box-shadow: 0 0 0 0 rgba(245, 158, 11, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Badge for nav buttons (day count indicator) */
|
/* Badge for nav buttons (day count indicator) */
|
||||||
.nav-badge {
|
.nav-badge {
|
||||||
background-color: var(--error-color);
|
background-color: var(--error-color);
|
||||||
@@ -795,7 +818,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.menu-item.today-ordered {
|
.menu-item.today-ordered {
|
||||||
border: 2px solid var(--accent-color);
|
border: 2px solid #8b5cf6;
|
||||||
box-shadow: 0 0 20px rgba(139, 92, 246, 0.4);
|
box-shadow: 0 0 20px rgba(139, 92, 246, 0.4);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
@@ -1997,9 +2020,16 @@ 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.2</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.4</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;">
|
||||||
|
<button id="btn-this-week" class="nav-btn active">Diese Woche</button>
|
||||||
|
<button id="btn-next-week" class="nav-btn">Nächste Woche</button>
|
||||||
|
</div>
|
||||||
|
<button id="alarm-bell" class="icon-btn hidden" aria-label="Benachrichtigungen" title="Keine beobachteten Menüs" style="margin-left: -0.5rem;">
|
||||||
|
<span class="material-icons-round" id="alarm-bell-icon" style="color:var(--text-secondary); transition: color 0.3s;">notifications</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-center-wrapper">
|
<div class="header-center-wrapper">
|
||||||
<div id="header-week-info" class="header-week-info"></div>
|
<div id="header-week-info" class="header-week-info"></div>
|
||||||
@@ -2015,10 +2045,6 @@ body {
|
|||||||
<button id="btn-highlights" class="icon-btn" aria-label="Persönliche Highlights verwalten" title="Persönliche Highlights verwalten">
|
<button id="btn-highlights" class="icon-btn" aria-label="Persönliche Highlights verwalten" title="Persönliche Highlights verwalten">
|
||||||
<span class="material-icons-round">label</span>
|
<span class="material-icons-round">label</span>
|
||||||
</button>
|
</button>
|
||||||
<div class="nav-group">
|
|
||||||
<button id="btn-this-week" class="nav-btn active">Diese Woche</button>
|
|
||||||
<button id="btn-next-week" class="nav-btn">Nächste Woche</button>
|
|
||||||
</div>
|
|
||||||
<button id="theme-toggle" class="icon-btn" aria-label="Toggle Theme">
|
<button id="theme-toggle" class="icon-btn" aria-label="Toggle Theme">
|
||||||
<span class="material-icons-round theme-icon">light_mode</span>
|
<span class="material-icons-round theme-icon">light_mode</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -2136,7 +2162,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.2</span>
|
<strong>Aktuell:</strong> <span id="version-current">v1.4.4</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;">
|
||||||
@@ -2144,9 +2170,17 @@ body {
|
|||||||
<span>Dev-Mode (alle Tags anzeigen)</span>
|
<span>Dev-Mode (alle Tags anzeigen)</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div id="version-list-container" style="margin-top:1rem;">
|
<div id="version-list-container" style="margin-top:1rem; max-height: 250px; overflow-y: auto;">
|
||||||
<p style="color:var(--text-secondary);">Lade Versionen...</p>
|
<p style="color:var(--text-secondary);">Lade Versionen...</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div style="margin-top: 1.5rem; padding-top: 1rem; border-top: 1px solid var(--border-color); display: flex; flex-direction: column; gap: 0.75rem; font-size: 0.9em;">
|
||||||
|
<a href="https://github.com/TauNeutrino/kantine-overview/issues" target="_blank" rel="noopener noreferrer" style="color: var(--primary-color); text-decoration: none; display: flex; align-items: center; gap: 0.5rem;" title="Melde einen Fehler auf GitHub">
|
||||||
|
<span class="material-icons-round" style="font-size: 1.2em;">bug_report</span> Fehler melden
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/TauNeutrino/kantine-overview/discussions/categories/ideas" target="_blank" rel="noopener noreferrer" style="color: var(--primary-color); text-decoration: none; display: flex; align-items: center; gap: 0.5rem;" title="Schlage ein neues Feature auf GitHub vor">
|
||||||
|
<span class="material-icons-round" style="font-size: 1.2em;">lightbulb</span> Feature vorschlagen
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -2280,6 +2314,7 @@ body {
|
|||||||
});
|
});
|
||||||
|
|
||||||
btnNextWeek.addEventListener('click', () => {
|
btnNextWeek.addEventListener('click', () => {
|
||||||
|
btnNextWeek.classList.remove('new-week-available');
|
||||||
if (displayMode !== 'next-week') {
|
if (displayMode !== 'next-week') {
|
||||||
displayMode = 'next-week';
|
displayMode = 'next-week';
|
||||||
btnNextWeek.classList.add('active');
|
btnNextWeek.classList.add('active');
|
||||||
@@ -2484,11 +2519,24 @@ 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
|
||||||
if (fullOrderHistoryCache) {
|
if (fullOrderHistoryCache) {
|
||||||
renderHistory(fullOrderHistoryCache);
|
renderHistory(fullOrderHistoryCache);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check local storage cache
|
||||||
|
const localCache = localStorage.getItem('kantine_history_cache');
|
||||||
|
if (localCache) {
|
||||||
|
try {
|
||||||
|
fullOrderHistoryCache = JSON.parse(localCache);
|
||||||
|
renderHistory(fullOrderHistoryCache);
|
||||||
|
return;
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('History cache parse error', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!authToken) return;
|
if (!authToken) return;
|
||||||
|
|
||||||
historyContent.innerHTML = '';
|
historyContent.innerHTML = '';
|
||||||
@@ -2526,6 +2574,9 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fullOrderHistoryCache = allOrders;
|
fullOrderHistoryCache = allOrders;
|
||||||
|
try {
|
||||||
|
localStorage.setItem('kantine_history_cache', JSON.stringify(allOrders));
|
||||||
|
} catch (e) { console.warn('History cache write error', e); }
|
||||||
renderHistory(fullOrderHistoryCache);
|
renderHistory(fullOrderHistoryCache);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -2732,6 +2783,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');
|
||||||
await fetchOrders();
|
await fetchOrders();
|
||||||
} else {
|
} else {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
@@ -2761,6 +2813,7 @@ body {
|
|||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
showToast(`Storniert: ${name}`, 'success');
|
showToast(`Storniert: ${name}`, 'success');
|
||||||
|
localStorage.removeItem('kantine_history_cache');
|
||||||
await fetchOrders();
|
await fetchOrders();
|
||||||
} else {
|
} else {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
@@ -2777,6 +2830,55 @@ body {
|
|||||||
localStorage.setItem('kantine_flags', JSON.stringify([...userFlags]));
|
localStorage.setItem('kantine_flags', JSON.stringify([...userFlags]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateAlarmBell() {
|
||||||
|
const bellBtn = document.getElementById('alarm-bell');
|
||||||
|
const bellIcon = document.getElementById('alarm-bell-icon');
|
||||||
|
if (!bellBtn || !bellIcon) return;
|
||||||
|
|
||||||
|
if (userFlags.size === 0) {
|
||||||
|
bellBtn.classList.add('hidden');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bellBtn.classList.remove('hidden');
|
||||||
|
|
||||||
|
// Check if any flagged item is available
|
||||||
|
let anyAvailable = false;
|
||||||
|
for (const wk of allWeeks) {
|
||||||
|
if (!wk.days) continue;
|
||||||
|
for (const d of wk.days) {
|
||||||
|
if (!d.items) continue;
|
||||||
|
for (const item of d.items) {
|
||||||
|
if (item.available && userFlags.has(item.id)) {
|
||||||
|
anyAvailable = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (anyAvailable) break;
|
||||||
|
}
|
||||||
|
if (anyAvailable) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastUpdatedStr = localStorage.getItem('kantine_last_updated');
|
||||||
|
let timeStr = 'Unbekannt';
|
||||||
|
if (lastUpdatedStr) {
|
||||||
|
const lastUpdated = new Date(lastUpdatedStr);
|
||||||
|
const diffMs = Date.now() - lastUpdated.getTime();
|
||||||
|
const diffMins = Math.floor(diffMs / 60000);
|
||||||
|
if (diffMins < 60) timeStr = `vor ${diffMins} Min.`;
|
||||||
|
else timeStr = `vor ${Math.floor(diffMins / 60)} Std.`;
|
||||||
|
}
|
||||||
|
bellBtn.title = `Zuletzt geprüft: ${timeStr}`;
|
||||||
|
|
||||||
|
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.textShadow = '0 0 10px rgba(245, 158, 11, 0.4)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function toggleFlag(date, articleId, name, cutoff) {
|
function toggleFlag(date, articleId, name, cutoff) {
|
||||||
const id = `${date}_${articleId}`;
|
const id = `${date}_${articleId}`;
|
||||||
if (userFlags.has(id)) {
|
if (userFlags.has(id)) {
|
||||||
@@ -2790,6 +2892,7 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
saveFlags();
|
saveFlags();
|
||||||
|
updateAlarmBell();
|
||||||
renderVisibleWeeks();
|
renderVisibleWeeks();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3158,6 +3261,7 @@ body {
|
|||||||
updateAuthUI(); // This will trigger fetchOrders if logged in
|
updateAuthUI(); // This will trigger fetchOrders if logged in
|
||||||
renderVisibleWeeks();
|
renderVisibleWeeks();
|
||||||
updateNextWeekBadge();
|
updateNextWeekBadge();
|
||||||
|
updateAlarmBell();
|
||||||
|
|
||||||
progressMessage.textContent = 'Fertig!';
|
progressMessage.textContent = 'Fertig!';
|
||||||
setTimeout(() => progressModal.classList.add('hidden'), 500);
|
setTimeout(() => progressModal.classList.add('hidden'), 500);
|
||||||
@@ -3315,6 +3419,14 @@ body {
|
|||||||
badge.classList.add('has-highlights');
|
badge.classList.add('has-highlights');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FR-092: Highlight Next Week Button when new data arrives
|
||||||
|
const storageKey = `kantine_notified_nextweek_${nextYear}_${nextWeek}`;
|
||||||
|
if (!localStorage.getItem(storageKey)) {
|
||||||
|
localStorage.setItem(storageKey, 'true');
|
||||||
|
btnNextWeek.classList.add('new-week-available');
|
||||||
|
showToast('Neue Menüdaten für nächste Woche verfügbar!', 'info');
|
||||||
|
}
|
||||||
|
|
||||||
} else if (badge) {
|
} else if (badge) {
|
||||||
badge.remove();
|
badge.remove();
|
||||||
}
|
}
|
||||||
@@ -3721,7 +3833,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.2';
|
const currentVersion = 'v1.4.4';
|
||||||
const devMode = localStorage.getItem('kantine_dev_mode') === 'true';
|
const devMode = localStorage.getItem('kantine_dev_mode') === 'true';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -3762,7 +3874,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.2';
|
const currentVersion = 'v1.4.4';
|
||||||
|
|
||||||
if (!modal) return;
|
if (!modal) return;
|
||||||
modal.classList.remove('hidden');
|
modal.classList.remove('hidden');
|
||||||
|
|||||||
99
kantine.js
99
kantine.js
@@ -74,6 +74,13 @@
|
|||||||
<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ü">{{VERSION}}</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ü">{{VERSION}}</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;">
|
||||||
|
<button id="btn-this-week" class="nav-btn active">Diese Woche</button>
|
||||||
|
<button id="btn-next-week" class="nav-btn">Nächste Woche</button>
|
||||||
|
</div>
|
||||||
|
<button id="alarm-bell" class="icon-btn hidden" aria-label="Benachrichtigungen" title="Keine beobachteten Menüs" style="margin-left: -0.5rem;">
|
||||||
|
<span class="material-icons-round" id="alarm-bell-icon" style="color:var(--text-secondary); transition: color 0.3s;">notifications</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-center-wrapper">
|
<div class="header-center-wrapper">
|
||||||
<div id="header-week-info" class="header-week-info"></div>
|
<div id="header-week-info" class="header-week-info"></div>
|
||||||
@@ -89,10 +96,6 @@
|
|||||||
<button id="btn-highlights" class="icon-btn" aria-label="Persönliche Highlights verwalten" title="Persönliche Highlights verwalten">
|
<button id="btn-highlights" class="icon-btn" aria-label="Persönliche Highlights verwalten" title="Persönliche Highlights verwalten">
|
||||||
<span class="material-icons-round">label</span>
|
<span class="material-icons-round">label</span>
|
||||||
</button>
|
</button>
|
||||||
<div class="nav-group">
|
|
||||||
<button id="btn-this-week" class="nav-btn active">Diese Woche</button>
|
|
||||||
<button id="btn-next-week" class="nav-btn">Nächste Woche</button>
|
|
||||||
</div>
|
|
||||||
<button id="theme-toggle" class="icon-btn" aria-label="Toggle Theme">
|
<button id="theme-toggle" class="icon-btn" aria-label="Toggle Theme">
|
||||||
<span class="material-icons-round theme-icon">light_mode</span>
|
<span class="material-icons-round theme-icon">light_mode</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -218,9 +221,17 @@
|
|||||||
<span>Dev-Mode (alle Tags anzeigen)</span>
|
<span>Dev-Mode (alle Tags anzeigen)</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div id="version-list-container" style="margin-top:1rem;">
|
<div id="version-list-container" style="margin-top:1rem; max-height: 250px; overflow-y: auto;">
|
||||||
<p style="color:var(--text-secondary);">Lade Versionen...</p>
|
<p style="color:var(--text-secondary);">Lade Versionen...</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div style="margin-top: 1.5rem; padding-top: 1rem; border-top: 1px solid var(--border-color); display: flex; flex-direction: column; gap: 0.75rem; font-size: 0.9em;">
|
||||||
|
<a href="https://github.com/TauNeutrino/kantine-overview/issues" target="_blank" rel="noopener noreferrer" style="color: var(--primary-color); text-decoration: none; display: flex; align-items: center; gap: 0.5rem;" title="Melde einen Fehler auf GitHub">
|
||||||
|
<span class="material-icons-round" style="font-size: 1.2em;">bug_report</span> Fehler melden
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/TauNeutrino/kantine-overview/discussions/categories/ideas" target="_blank" rel="noopener noreferrer" style="color: var(--primary-color); text-decoration: none; display: flex; align-items: center; gap: 0.5rem;" title="Schlage ein neues Feature auf GitHub vor">
|
||||||
|
<span class="material-icons-round" style="font-size: 1.2em;">lightbulb</span> Feature vorschlagen
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -354,6 +365,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
btnNextWeek.addEventListener('click', () => {
|
btnNextWeek.addEventListener('click', () => {
|
||||||
|
btnNextWeek.classList.remove('new-week-available');
|
||||||
if (displayMode !== 'next-week') {
|
if (displayMode !== 'next-week') {
|
||||||
displayMode = 'next-week';
|
displayMode = 'next-week';
|
||||||
btnNextWeek.classList.add('active');
|
btnNextWeek.classList.add('active');
|
||||||
@@ -558,11 +570,24 @@
|
|||||||
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
|
||||||
if (fullOrderHistoryCache) {
|
if (fullOrderHistoryCache) {
|
||||||
renderHistory(fullOrderHistoryCache);
|
renderHistory(fullOrderHistoryCache);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check local storage cache
|
||||||
|
const localCache = localStorage.getItem('kantine_history_cache');
|
||||||
|
if (localCache) {
|
||||||
|
try {
|
||||||
|
fullOrderHistoryCache = JSON.parse(localCache);
|
||||||
|
renderHistory(fullOrderHistoryCache);
|
||||||
|
return;
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('History cache parse error', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!authToken) return;
|
if (!authToken) return;
|
||||||
|
|
||||||
historyContent.innerHTML = '';
|
historyContent.innerHTML = '';
|
||||||
@@ -600,6 +625,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
fullOrderHistoryCache = allOrders;
|
fullOrderHistoryCache = allOrders;
|
||||||
|
try {
|
||||||
|
localStorage.setItem('kantine_history_cache', JSON.stringify(allOrders));
|
||||||
|
} catch (e) { console.warn('History cache write error', e); }
|
||||||
renderHistory(fullOrderHistoryCache);
|
renderHistory(fullOrderHistoryCache);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -806,6 +834,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');
|
||||||
await fetchOrders();
|
await fetchOrders();
|
||||||
} else {
|
} else {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
@@ -835,6 +864,7 @@
|
|||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
showToast(`Storniert: ${name}`, 'success');
|
showToast(`Storniert: ${name}`, 'success');
|
||||||
|
localStorage.removeItem('kantine_history_cache');
|
||||||
await fetchOrders();
|
await fetchOrders();
|
||||||
} else {
|
} else {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
@@ -851,6 +881,55 @@
|
|||||||
localStorage.setItem('kantine_flags', JSON.stringify([...userFlags]));
|
localStorage.setItem('kantine_flags', JSON.stringify([...userFlags]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateAlarmBell() {
|
||||||
|
const bellBtn = document.getElementById('alarm-bell');
|
||||||
|
const bellIcon = document.getElementById('alarm-bell-icon');
|
||||||
|
if (!bellBtn || !bellIcon) return;
|
||||||
|
|
||||||
|
if (userFlags.size === 0) {
|
||||||
|
bellBtn.classList.add('hidden');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bellBtn.classList.remove('hidden');
|
||||||
|
|
||||||
|
// Check if any flagged item is available
|
||||||
|
let anyAvailable = false;
|
||||||
|
for (const wk of allWeeks) {
|
||||||
|
if (!wk.days) continue;
|
||||||
|
for (const d of wk.days) {
|
||||||
|
if (!d.items) continue;
|
||||||
|
for (const item of d.items) {
|
||||||
|
if (item.available && userFlags.has(item.id)) {
|
||||||
|
anyAvailable = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (anyAvailable) break;
|
||||||
|
}
|
||||||
|
if (anyAvailable) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastUpdatedStr = localStorage.getItem('kantine_last_updated');
|
||||||
|
let timeStr = 'Unbekannt';
|
||||||
|
if (lastUpdatedStr) {
|
||||||
|
const lastUpdated = new Date(lastUpdatedStr);
|
||||||
|
const diffMs = Date.now() - lastUpdated.getTime();
|
||||||
|
const diffMins = Math.floor(diffMs / 60000);
|
||||||
|
if (diffMins < 60) timeStr = `vor ${diffMins} Min.`;
|
||||||
|
else timeStr = `vor ${Math.floor(diffMins / 60)} Std.`;
|
||||||
|
}
|
||||||
|
bellBtn.title = `Zuletzt geprüft: ${timeStr}`;
|
||||||
|
|
||||||
|
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.textShadow = '0 0 10px rgba(245, 158, 11, 0.4)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function toggleFlag(date, articleId, name, cutoff) {
|
function toggleFlag(date, articleId, name, cutoff) {
|
||||||
const id = `${date}_${articleId}`;
|
const id = `${date}_${articleId}`;
|
||||||
if (userFlags.has(id)) {
|
if (userFlags.has(id)) {
|
||||||
@@ -864,6 +943,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
saveFlags();
|
saveFlags();
|
||||||
|
updateAlarmBell();
|
||||||
renderVisibleWeeks();
|
renderVisibleWeeks();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1232,6 +1312,7 @@
|
|||||||
updateAuthUI(); // This will trigger fetchOrders if logged in
|
updateAuthUI(); // This will trigger fetchOrders if logged in
|
||||||
renderVisibleWeeks();
|
renderVisibleWeeks();
|
||||||
updateNextWeekBadge();
|
updateNextWeekBadge();
|
||||||
|
updateAlarmBell();
|
||||||
|
|
||||||
progressMessage.textContent = 'Fertig!';
|
progressMessage.textContent = 'Fertig!';
|
||||||
setTimeout(() => progressModal.classList.add('hidden'), 500);
|
setTimeout(() => progressModal.classList.add('hidden'), 500);
|
||||||
@@ -1389,6 +1470,14 @@
|
|||||||
badge.classList.add('has-highlights');
|
badge.classList.add('has-highlights');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FR-092: Highlight Next Week Button when new data arrives
|
||||||
|
const storageKey = `kantine_notified_nextweek_${nextYear}_${nextWeek}`;
|
||||||
|
if (!localStorage.getItem(storageKey)) {
|
||||||
|
localStorage.setItem(storageKey, 'true');
|
||||||
|
btnNextWeek.classList.add('new-week-available');
|
||||||
|
showToast('Neue Menüdaten für nächste Woche verfügbar!', 'info');
|
||||||
|
}
|
||||||
|
|
||||||
} else if (badge) {
|
} else if (badge) {
|
||||||
badge.remove();
|
badge.remove();
|
||||||
}
|
}
|
||||||
|
|||||||
25
style.css
25
style.css
@@ -186,6 +186,29 @@ body {
|
|||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Notification state for Next Week */
|
||||||
|
.nav-btn.new-week-available {
|
||||||
|
animation: goldPulse 2s infinite;
|
||||||
|
border-color: #f59e0b;
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
.nav-btn.new-week-available.active {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes goldPulse {
|
||||||
|
0% {
|
||||||
|
box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.7);
|
||||||
|
}
|
||||||
|
70% {
|
||||||
|
box-shadow: 0 0 0 10px rgba(245, 158, 11, 0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
box-shadow: 0 0 0 0 rgba(245, 158, 11, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Badge for nav buttons (day count indicator) */
|
/* Badge for nav buttons (day count indicator) */
|
||||||
.nav-badge {
|
.nav-badge {
|
||||||
background-color: var(--error-color);
|
background-color: var(--error-color);
|
||||||
@@ -784,7 +807,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.menu-item.today-ordered {
|
.menu-item.today-ordered {
|
||||||
border: 2px solid var(--accent-color);
|
border: 2px solid #8b5cf6;
|
||||||
box-shadow: 0 0 20px rgba(139, 92, 246, 0.4);
|
box-shadow: 0 0 20px rgba(139, 92, 246, 0.4);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
v1.4.2
|
v1.4.4
|
||||||
|
|||||||
Reference in New Issue
Block a user