feat: Add manual refresh for flagged items triggered by the alarm bell, including UI feedback and toast notifications.

This commit is contained in:
Kantine Wrapper
2026-03-10 15:49:38 +01:00
parent a4dff30bb5
commit d05812dbb2
13 changed files with 309 additions and 243 deletions

View File

@@ -61,6 +61,7 @@ Das System umfasst die Darstellung von Menüplänen in einer Wochenübersicht, d
| 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 aber kein Menü verfügbar, Grün=Mindestens ein 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.6.11 (Update v1.5.0) |
| FR-092 | Solange Menüdaten für die Nächste Woche verfügbar sind, aber noch keine Bestellungen getätigt wurden, muss der entsprechende Navigation-Button animiert und farblich (Gelb) hervorgehoben werden. Nach der ersten Bestellung muss die Hervorhebung automatisch erlöschen. Zusätzlich muss beim erstmaligen Erscheinen der Daten ein einmaliger Toast-Hinweis angezeigt werden. | Mittel | v1.6.0 (Update v1.4.21) |
| FR-093 | Das System muss dem Benutzer ermöglichen, durch Klicken auf das Alarm-Icon im Header eine manuelle Prüfung der geflaggten Menüs auszulösen. Während der Prüfung muss das Icon visuell animiert sein (Rotation). Nach Abschluss der Prüfung muss eine Toast-Nachricht mit der Anzahl der geprüften Menüs angezeigt werden. | Mittel | v1.6.13 |
| **Sprachfilter** | | | |
| FR-120 | Das System muss zweisprachige Menübeschreibungen (Deutsch/Englisch) erkennen und dem Benutzer erlauben, via UI-Toggle zwischen DE, EN und ALL (beide Sprachen) zu wechseln. Die Sprachpräferenz muss persistent gespeichert werden. Allergen-Codes müssen in allen Modi angezeigt werden. | Mittel | v1.6.0 |
| FR-121 | Das System muss bei fehlenden Übersetzungen in zweisprachigen Menüs robust reagieren. Wenn ein Gang nur in einer Sprache vorliegt, muss dieser Teil für beide Sprachansichten herangezogen werden, um die Konsistenz der Ganganzahl zu gewährleisten. | Mittel | v1.6.10 |

View File

@@ -1,3 +1,11 @@
## v1.6.14 (2026-03-10)
- 🐛 **Bugfix**: Die globale "Aktualisiert am"-Zeit im Header wird bei einer manuellen Prüfung der geflaggten Menüs nicht mehr zurückgesetzt.
## v1.6.13 (2026-03-10)
-**Feature**: Manueller Refresh der geflaggten Menüs durch Klick auf das Alarm-Icon im Header ([FR-093](REQUIREMENTS.md#FR-093)).
- 🔄 **UI**: Visuelle Rückmeldung während der Prüfung durch Rotation des Icons.
- 🔔 **Notification**: Toast-Benachrichtigung zeigt die Anzahl der geprüften Menüs an.
## v1.6.12 (2026-03-10)
- 🔄 **Refactor**: Modularisierung von `kantine.js` in ES6-Module (`api.js`, `state.js`, `utils.js`, `ui.js`, etc.).
- 📦 **Build**: Integration von Webpack in den Build-Prozess zur Unterstützung der modularen Struktur.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

36
dist/install.html vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -6,6 +6,7 @@
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ A0: () => (/* binding */ refreshFlaggedItems),
/* harmony export */ Aq: () => (/* binding */ fetchFullOrderHistory),
/* harmony export */ BM: () => (/* binding */ checkHighlight),
/* harmony export */ Et: () => (/* binding */ stopPolling),
@@ -23,7 +24,7 @@
/* harmony export */ oL: () => (/* binding */ addHighlightTag),
/* harmony export */ wH: () => (/* binding */ placeOrder)
/* harmony export */ });
/* unused harmony exports renderHistory, saveFlags, refreshFlaggedItems, pollFlaggedItems, saveHighlightTags, removeHighlightTag, saveMenuCache, updateLastUpdatedTime */
/* unused harmony exports renderHistory, saveFlags, pollFlaggedItems, saveHighlightTags, removeHighlightTag, saveMenuCache, updateLastUpdatedTime */
/* harmony import */ var _state_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(901);
/* harmony import */ var _utils_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(413);
/* harmony import */ var _constants_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(521);
@@ -470,6 +471,10 @@ async function refreshFlaggedItems() {
}
let updated = false;
const bellBtn = document.getElementById('alarm-bell');
if (bellBtn) bellBtn.classList.add('refreshing');
try {
for (const dateStr of datesToFetch) {
try {
const resp = await fetch(`${_constants_js__WEBPACK_IMPORTED_MODULE_2__/* .API_BASE */ .tE}/venues/${_constants_js__WEBPACK_IMPORTED_MODULE_2__/* .VENUE_ID */ .eW}/menu/${_constants_js__WEBPACK_IMPORTED_MODULE_2__/* .MENU_ID */ .YU}/${dateStr}/`, {
@@ -513,11 +518,15 @@ async function refreshFlaggedItems() {
if (updated) {
saveMenuCache();
updateLastUpdatedTime(new Date().toISOString());
localStorage.setItem('kantine_flagged_items_last_checked', new Date().toISOString());
(0,_ui_helpers_js__WEBPACK_IMPORTED_MODULE_4__/* .updateAlarmBell */ .Mb)();
(0,_ui_helpers_js__WEBPACK_IMPORTED_MODULE_4__/* .renderVisibleWeeks */ .OR)();
}
showToast(`${_state_js__WEBPACK_IMPORTED_MODULE_0__/* .userFlags */ .BY.size} ${_state_js__WEBPACK_IMPORTED_MODULE_0__/* .userFlags */ .BY.size === 1 ? 'Menü' : 'Menüs'} geprüft`, 'info');
} finally {
if (bellBtn) bellBtn.classList.remove('refreshing');
}
}
@@ -1351,7 +1360,7 @@ function createDayCard(day) {
header.className = 'card-header';
const dateStr = cardDate.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit' });
const badgesHtml = menuBadges.map(code => `<span class="menu-code-badge">${code}</span>`).join('');
const badgesHtml = menuBadges.reduce((acc, code) => acc + `<span class="menu-code-badge">${code}</span>`, '');
let headerClass = '';
const hasAnyOrder = day.items && day.items.some(item => {
@@ -1467,10 +1476,7 @@ function createDayCard(day) {
let tagsHtml = '';
if (matchedTags.length > 0) {
let badges = '';
for (const t of matchedTags) {
badges += `<span class="tag-badge-small"><span class="material-icons-round" style="font-size:10px;margin-right:2px">star</span>${(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__/* .escapeHtml */ .ZD)(t)}</span>`;
}
const badges = matchedTags.reduce((acc, t) => acc + `<span class="tag-badge-small"><span class="material-icons-round" style="font-size:10px;margin-right:2px">star</span>${(0,_utils_js__WEBPACK_IMPORTED_MODULE_1__/* .escapeHtml */ .ZD)(t)}</span>`, '');
tagsHtml = `<div class="matched-tags">${badges}</div>`;
}
@@ -2578,6 +2584,13 @@ function bindEvents() {
(0,actions/* loadMenuDataFromAPI */.m9)();
});
const bellBtn = document.getElementById('alarm-bell');
if (bellBtn) {
bellBtn.addEventListener('click', () => {
(0,actions/* refreshFlaggedItems */.A0)();
});
}
btnLoginOpen.addEventListener('click', () => {
loginModal.classList.remove('hidden');
document.getElementById('login-error').classList.add('hidden');

2
package-lock.json generated
View File

@@ -1,5 +1,5 @@
{
"name": "app",
"name": "kantine-wrapper",
"lockfileVersion": 3,
"requires": true,
"packages": {

View File

@@ -439,6 +439,10 @@ export async function refreshFlaggedItems() {
}
let updated = false;
const bellBtn = document.getElementById('alarm-bell');
if (bellBtn) bellBtn.classList.add('refreshing');
try {
for (const dateStr of datesToFetch) {
try {
const resp = await fetch(`${API_BASE}/venues/${VENUE_ID}/menu/${MENU_ID}/${dateStr}/`, {
@@ -482,11 +486,15 @@ export async function refreshFlaggedItems() {
if (updated) {
saveMenuCache();
updateLastUpdatedTime(new Date().toISOString());
localStorage.setItem('kantine_flagged_items_last_checked', new Date().toISOString());
updateAlarmBell();
renderVisibleWeeks();
}
showToast(`${userFlags.size} ${userFlags.size === 1 ? 'Menü' : 'Menüs'} geprüft`, 'info');
} finally {
if (bellBtn) bellBtn.classList.remove('refreshing');
}
}

View File

@@ -1,5 +1,5 @@
import { displayMode, langMode, authToken, currentUser, orderMap, userFlags, pollIntervalId, setLangMode, setDisplayMode, setAuthToken, setCurrentUser, setOrderMap } from './state.js';
import { updateAuthUI, loadMenuDataFromAPI, fetchOrders, startPolling, stopPolling, fetchFullOrderHistory, addHighlightTag, renderTagsList } from './actions.js';
import { updateAuthUI, loadMenuDataFromAPI, fetchOrders, startPolling, stopPolling, fetchFullOrderHistory, addHighlightTag, renderTagsList, refreshFlaggedItems } from './actions.js';
import { renderVisibleWeeks, openVersionMenu } from './ui_helpers.js';
import { API_BASE, GUEST_TOKEN } from './constants.js';
import { apiHeaders } from './api.js';
@@ -162,6 +162,13 @@ export function bindEvents() {
loadMenuDataFromAPI();
});
const bellBtn = document.getElementById('alarm-bell');
if (bellBtn) {
bellBtn.addEventListener('click', () => {
refreshFlaggedItems();
});
}
btnLoginOpen.addEventListener('click', () => {
loginModal.classList.remove('hidden');
document.getElementById('login-error').classList.add('hidden');

View File

@@ -352,7 +352,8 @@ body {
}
/* Refresh button animation */
#btn-refresh.refreshing .material-icons-round {
#btn-refresh.refreshing .material-icons-round,
#alarm-bell.refreshing .material-icons-round {
animation: rotate 1s linear infinite;
}

View File

@@ -112,7 +112,9 @@ const testCode = `
alarmBtn.classList.add('hidden');
if (!document.getElementById('alarm-bell').className.includes('hidden')) throw new Error("Bell should be hidden");
console.log("✅ Alarm Bell Test Passed");
// Test Click Refresh
alarmBtn.click();
console.log("✅ Alarm Bell Test (Click) Passed");
console.log("--- Testing Highlights Modal ---");
// First, verify initial state

View File

@@ -1 +1 @@
v1.6.12
v1.6.14