updates
All checks were successful
Deploy / deploy-staging (push) Successful in 25s
Deploy / deploy-production (push) Has been skipped

This commit is contained in:
2026-06-22 23:36:01 +02:00
parent dc66f75eef
commit a5cf110de2
7 changed files with 452 additions and 157 deletions

View File

@@ -1405,6 +1405,14 @@ h1 {
background: rgba(255, 255, 255, 0.12);
}
.tray-menu-copy {
margin: 0;
padding: 6px 4px 2px;
color: #cbd5e1;
font-size: 12px;
line-height: 1.45;
}
.window-content p:last-child {
margin-bottom: 0;
}

View File

@@ -17,6 +17,8 @@ if (payloadNode) {
const startMenuUserAvatar = document.getElementById('start-menu-user-avatar');
const startMenuSessionAction = document.getElementById('start-menu-session-action');
const accountMenu = document.getElementById('tray-account-menu');
const fxTrayMenu = document.getElementById('tray-fx-menu');
const fxTrayStatusNode = document.getElementById('tray-fx-status');
const clockNode = document.getElementById('clock');
const clockTopNode = document.getElementById('clock-top');
const debugToggleNode = document.getElementById('debug-toggle');
@@ -389,6 +391,88 @@ if (payloadNode) {
accountButton.setAttribute('aria-expanded', String(isOpen));
};
const setFxTrayMenuOpen = (isOpen) => {
const fxButton = document.querySelector('.tray-pill[data-tray-app-id="fx-rates"]');
if (!(fxTrayMenu instanceof HTMLElement) || !(fxButton instanceof HTMLElement)) {
return;
}
fxTrayMenu.hidden = !isOpen;
fxButton.setAttribute('aria-expanded', String(isOpen));
};
const loadFxTrayStatus = async () => {
const fxButton = document.querySelector('.tray-pill[data-tray-app-id="fx-rates"]');
if (!(fxTrayStatusNode instanceof HTMLElement) || !(fxButton instanceof HTMLElement)) {
return null;
}
fxTrayStatusNode.textContent = 'Kursstatus wird geladen ...';
fxButton.setAttribute('title', 'Kursstatus wird geladen ...');
try {
const response = await fetch('/api/fx-rates/index.php?path=v1/status', {
credentials: 'same-origin',
headers: { Accept: 'application/json' },
});
const payload = await response.json().catch(() => ({}));
const rows = Array.isArray(payload?.data) ? payload.data : [];
const latest = rows.slice().sort((left, right) => {
const leftTs = new Date(left?.fetched_at || 0).getTime();
const rightTs = new Date(right?.fetched_at || 0).getTime();
return rightTs - leftTs;
})[0] || null;
const statusText = latest
? `Letzter Abruf: ${latest.fetched_at_display || latest.fetched_at} · ${latest.base_currency} · ${latest.provider}`
: 'Noch keine gespeicherten Kurse vorhanden.';
fxTrayStatusNode.textContent = statusText;
fxButton.setAttribute('title', statusText);
return latest;
} catch (_error) {
const errorText = 'Kursstatus konnte nicht geladen werden.';
fxTrayStatusNode.textContent = errorText;
fxButton.setAttribute('title', errorText);
return null;
}
};
const runFxTrayRefresh = async (force) => {
if (!(fxTrayStatusNode instanceof HTMLElement)) {
return;
}
fxTrayStatusNode.textContent = 'Aktualisierung laeuft ...';
try {
const response = await fetch('/api/fx-rates/index.php?path=v1/refresh', {
method: 'POST',
credentials: 'same-origin',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
force: force === true,
trigger_source: 'tray',
}),
});
const payload = await response.json().catch(() => ({}));
if (!response.ok) {
throw new Error(payload?.context?.message || payload?.error || `HTTP ${response.status}`);
}
const result = payload?.data || {};
fxTrayStatusNode.textContent = result.reused
? 'Kein neuer API-Abruf. Snapshot ist noch jung genug.'
: `Kurse aktualisiert. ${result.updated_count || 0} Werte gespeichert.`;
await loadFxTrayStatus();
} catch (error) {
fxTrayStatusNode.textContent = error instanceof Error ? error.message : 'Aktualisierung fehlgeschlagen.';
}
};
const findAppById = (appId) => payload.apps.find((app) => app.app_id === appId) || null;
const dispatchUserSettingsUpdate = (detail) => {
@@ -1711,12 +1795,17 @@ if (payloadNode) {
if (!(event.target instanceof Element) || !event.target.closest('.tray-pill[data-tray-id="account"], #tray-account-menu')) {
setAccountMenuOpen(false);
}
if (!(event.target instanceof Element) || !event.target.closest('.tray-pill[data-tray-app-id="fx-rates"], #tray-fx-menu')) {
setFxTrayMenuOpen(false);
}
});
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape') {
closeDesktopContextMenu();
setAccountMenuOpen(false);
setFxTrayMenuOpen(false);
}
});
@@ -1729,6 +1818,7 @@ if (payloadNode) {
const isClosed = startMenu.hasAttribute('hidden');
setStartMenuOpen(isClosed);
setAccountMenuOpen(false);
setFxTrayMenuOpen(false);
});
document.querySelector('.tray-pill[data-tray-id="account"]')?.addEventListener('click', () => {
@@ -1738,20 +1828,50 @@ if (payloadNode) {
setStartMenuOpen(false);
setAccountMenuOpen(accountMenu.hidden);
setFxTrayMenuOpen(false);
});
document.querySelectorAll('.tray-pill[data-tray-app-id]').forEach((button) => {
button.addEventListener('click', () => {
const appId = button.getAttribute('data-tray-app-id') || '';
if (appId === 'fx-rates') {
setStartMenuOpen(false);
setAccountMenuOpen(false);
setFxTrayMenuOpen(fxTrayMenu instanceof HTMLElement ? fxTrayMenu.hidden : false);
void loadFxTrayStatus();
return;
}
const app = findAppById(appId);
if (app) {
openApp(app);
setStartMenuOpen(false);
setAccountMenuOpen(false);
setFxTrayMenuOpen(false);
}
});
});
fxTrayMenu?.addEventListener('click', (event) => {
const target = event.target instanceof Element ? event.target.closest('[data-fx-action]') : null;
if (!target) {
return;
}
const action = target.getAttribute('data-fx-action');
if (action === 'refresh') {
void runFxTrayRefresh(false);
return;
}
if (action === 'force-refresh') {
void runFxTrayRefresh(true);
}
});
void loadFxTrayStatus();
debugToggleNode?.addEventListener('click', () => {
toggleDebugWindow();
});