Upload via GUI (74 Dateien)
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "deskhub",
|
||||
"productName": "DeskHub",
|
||||
"version": "2.11.0",
|
||||
"version": "2.12.2",
|
||||
"description": "DeskHub Desktop-Client für Mitarbeiter",
|
||||
"main": "src/main.js",
|
||||
"author": "M_Viper",
|
||||
|
||||
+49
-7
@@ -40,6 +40,8 @@ async function api(path, opts = {}) {
|
||||
e.code = r.error.code; e.status = r.error.status; e.offline = down; throw e;
|
||||
}
|
||||
if (state.offline) setOffline(false);
|
||||
// Änderungen an Tickets (antworten, Status, Zuweisung): Zähler bald neu holen, damit er sofort stimmt
|
||||
if (/^(ticket_message|ticket_update|ticket_spam|ticket_collaborators)\.php/.test(path) && opts.method === 'POST' && typeof pollSoon === 'function') pollSoon();
|
||||
return r.data;
|
||||
}
|
||||
|
||||
@@ -96,6 +98,16 @@ async function installUpdate() {
|
||||
if (r.error) { state.update = { ...state.update, busy: false }; paintBanners(); toast(r.error.message, true); }
|
||||
}
|
||||
window.deskhub.onUpdateProgress((pct) => { if (state.update?.busy) { state.update.pct = pct; paintBanners(); } });
|
||||
/** E-Mail-Abruf von Hand (wie der Knopf in der Ticketliste der Weboberfläche). */
|
||||
async function fetchMailNow(btn) {
|
||||
const old = btn.textContent;
|
||||
btn.disabled = true; btn.textContent = tr('⏳ Abruf läuft …');
|
||||
try {
|
||||
const r = await post('mail_fetch.php', {});
|
||||
toast(r.message, r.level === 'error');
|
||||
} catch (e) { toast(e.message, true); }
|
||||
finally { btn.disabled = false; btn.textContent = old; }
|
||||
}
|
||||
async function run(fn, okMsg) {
|
||||
try { const r = await fn(); if (okMsg) toast(okMsg); return r; }
|
||||
catch (e) { toast(e.message, true); return undefined; }
|
||||
@@ -172,7 +184,7 @@ const metaKey = (url) => 'meta:' + url;
|
||||
async function startApp(staff, cachedMeta, serverUrl) {
|
||||
state.staff = staff;
|
||||
checkUpdate(); clearInterval(state.updateTimer); state.updateTimer = setInterval(checkUpdate, 6 * 3600 * 1000);
|
||||
state.since = ''; state.badge = 0;
|
||||
state.since = ''; state.badge = 0; state.knownIds = null;
|
||||
if (!state.prefs) state.prefs = await window.deskhub.settings();
|
||||
applyTheme();
|
||||
const fresh = (async () => {
|
||||
@@ -212,6 +224,7 @@ function renderShell() {
|
||||
const top = h('header', { class: 'topbar' },
|
||||
h('div', { class: 'brand' }, h('span', { class: 'logo' }, 'D'), 'DeskHub'),
|
||||
can('create_ticket') ? h('button', { class: 'btn ok', onclick: () => go('newticket') }, 'Neues Ticket') : null,
|
||||
h('button', { class: 'btn ghost sm', title: tr('Ruft neue E-Mails aus den Postfächern ab'), onclick: (e) => fetchMailNow(e.currentTarget) }, tr('📥 E-Mails abrufen')),
|
||||
h('button', { class: 'searchbtn', onclick: openPalette }, h('span', { class: 'nl' }, icon('search'), 'Ticket, Kunde oder Bereich suchen…'), h('kbd', {}, 'Strg K')),
|
||||
h('div', { class: 'spacer' }),
|
||||
window.I18N.switcher(),
|
||||
@@ -244,15 +257,16 @@ function go(page, arg) {
|
||||
main.scrollTop = 0;
|
||||
const view = VIEWS_BY_PAGE()[page];
|
||||
if (!view) return;
|
||||
Promise.resolve(view(main, arg)).catch((e) => main.append(h('div', { class: 'warnbox' }, e.message)));
|
||||
Promise.resolve(view(main, arg)).then(() => { if (page === 'ticket') pollSoon(); }).catch((e) => main.append(h('div', { class: 'warnbox' }, e.message)));
|
||||
}
|
||||
window.addEventListener('focus', () => { if (state.staff) pollActivity(); }); // beim Zurückkehren zum Fenster sofort den aktuellen Stand holen
|
||||
function every(ms, fn) { const id = setInterval(fn, ms); state.timers.push({ id, page: true }); }
|
||||
|
||||
async function doLogout() {
|
||||
clearTimeout(state.pollTimer);
|
||||
await window.deskhub.logout();
|
||||
state.timers.forEach((t) => clearInterval(t.id ?? t)); state.timers = []; state.staff = null;
|
||||
window.deskhub.badge(0, null);
|
||||
window.deskhub.badge(0, null); state.knownIds = null;
|
||||
showLogin();
|
||||
}
|
||||
|
||||
@@ -270,7 +284,8 @@ function paintBadge() {
|
||||
if (b) { b.textContent = state.badge; b.classList.toggle('hidden', !state.badge); }
|
||||
window.deskhub.badge(state.badge, state.badge ? badgeImage(state.badge) : null);
|
||||
}
|
||||
function resetBadge() { state.badge = 0; paintBadge(); }
|
||||
// Der Zähler folgt dem Stand auf dem Server (siehe pollActivity) und wird nicht mehr beim Öffnen der Ticketliste geleert; nach einer Aktion fragen wir bald neu ab
|
||||
function pollSoon() { if (state.staff) schedulePoll(1200); }
|
||||
|
||||
function schedulePoll(delay) {
|
||||
clearTimeout(state.pollTimer);
|
||||
@@ -278,10 +293,28 @@ function schedulePoll(delay) {
|
||||
state.pollTimer = setTimeout(async () => { await pollActivity(); if (state.staff) schedulePoll(seconds * 1000); }, delay ?? seconds * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Der Zähler zeigt, wie viele Tickets jetzt Aufmerksamkeit brauchen (offen, dir zugewiesen oder noch niemandem, seit der letzten
|
||||
* Kundennachricht nicht geöffnet): Er sinkt von selbst, wenn ein Ticket geöffnet, beantwortet oder übernommen wird, auch anderswo
|
||||
* (Weboberfläche, Chrome-Erweiterung). Benachrichtigt wird nur für Tickets, die neu dazukommen. Ältere Server ohne „attention“ liefern nur
|
||||
* „seit dem letzten Mal“: Dafür bleibt die frühere, aufsummierende Zählung.
|
||||
*/
|
||||
async function pollActivity() {
|
||||
try {
|
||||
const a = await api('activity.php', { query: { since: state.since } });
|
||||
if (state.since && (a.newReplies || a.newUnassigned)) {
|
||||
if (a.attention && Array.isArray(a.attention.unassigned) && Array.isArray(a.attention.mine)) {
|
||||
const known = state.knownIds;
|
||||
const freshU = known ? a.attention.unassigned.filter((id) => !known.has(id)).length : 0;
|
||||
const freshM = known ? a.attention.mine.filter((id) => !known.has(id)).length : 0;
|
||||
state.knownIds = new Set([...a.attention.unassigned, ...a.attention.mine]);
|
||||
if (state.badge !== state.knownIds.size) { state.badge = state.knownIds.size; paintBadge(); }
|
||||
if ((freshU || freshM) && state.prefs?.notifications !== false) {
|
||||
const parts = [];
|
||||
if (freshM) parts.push(`${freshM} Aktualisierung(en) an deinen Tickets`);
|
||||
if (freshU) parts.push(`${freshU} neue(s) nicht zugewiesene(s) Ticket(s)`);
|
||||
window.deskhub.notify('DeskHub', parts.map((p) => tr(p)).join(', '));
|
||||
}
|
||||
} else if (state.since && (a.newReplies || a.newUnassigned)) {
|
||||
state.badge += a.newReplies + a.newUnassigned;
|
||||
if (state.prefs?.notifications !== false) {
|
||||
const parts = [];
|
||||
@@ -346,7 +379,7 @@ const VIEWS = {
|
||||
overdue: ['Überfällig', { status: 'open,pending,answered,on_hold', assigned: '', sla: 'overdue' }],
|
||||
atrisk: ['Bald fällig', { status: 'open,pending,answered,on_hold', assigned: '', sla: 'at_risk' }],
|
||||
paused: ['Pausiert', { status: 'on_hold', assigned: '', sla: '' }],
|
||||
snoozed: ['💤 Wiedervorlage', { status: 'open,pending,answered,on_hold,resolved,closed', assigned: '', sla: '', snoozed: '1' }],
|
||||
snoozed: ['Wiedervorlage', { status: 'open,pending,answered,on_hold,resolved,closed', assigned: '', sla: '', snoozed: '1' }],
|
||||
following: ['👁 Beobachtet', { status: 'open,pending,answered,on_hold', assigned: '', sla: '', following: '1' }],
|
||||
closed: ['Erledigt', { status: 'resolved,closed', assigned: '', sla: '' }],
|
||||
all: ['Alle', { status: 'open,pending,answered,on_hold,resolved,closed', assigned: '', sla: '' }],
|
||||
@@ -430,7 +463,7 @@ async function vTickets(main, preset) {
|
||||
tabs, viewsBar, h('div', { class: 'toolbar' }, search, h('button', { class: 'btn', onclick: () => { f.q = search.value; f.page = 1; load(); } }, 'Suchen'),
|
||||
sel('departmentId', [['', 'Alle Abteilungen'], ...state.meta.departments.map((d) => [d.id, d.name])]), sel('priorityId', [['', 'Alle Prioritäten'], ...state.meta.priorities.map((p) => [p.id, p.name])])),
|
||||
bulk, list);
|
||||
paintTabs(); paintViews(); resetBadge();
|
||||
paintTabs(); paintViews();
|
||||
await load();
|
||||
every(30000, () => { if (!selected.size && !document.querySelector('.modal-bg')) load(true); });
|
||||
}
|
||||
@@ -569,6 +602,14 @@ async function vTicket(main, number) {
|
||||
}));
|
||||
|
||||
const spamBtn = can('close_ticket') ? h('button', { class: 'btn ghost sm', onclick: () => run(async () => { await post('ticket_spam.php', { number, action: t.isSpam ? 'unspam' : 'spam' }); reload(); }, t.isSpam ? 'Kein Spam' : 'Als Spam markiert') }, t.isSpam ? 'Kein Spam' : 'Spam') : null;
|
||||
// Anhänge, Bilder und Formatierung der Original-E-Mail aus dem Postfach nachladen (nur bei E-Mail-Tickets)
|
||||
const reloadMailBtn = t.source === 'email' && can('reply_ticket') ? h('button', { class: 'btn ghost sm', title: tr('Holt Anhänge, Bilder und die Formatierung der E-Mail aus dem Postfach, soweit sie beim Abruf nicht übernommen wurden'), onclick: () => run(async () => {
|
||||
const r = await post('ticket_mail_reload.php', { number });
|
||||
const n = (r.added || 0) + (r.inlineImages || 0);
|
||||
toast(n > 0 || r.formatting ? tr('Anhänge und Bilder aus der E-Mail nachgeladen.') : tr('Es gab nichts Neues zu laden.'));
|
||||
if (r.failed?.length) toast(r.failed.join(' | '), true);
|
||||
reload();
|
||||
}) }, tr('📎 Anhänge laden')) : null;
|
||||
const others = h('div', { class: 'muted' }, 'Lade…');
|
||||
if (t.requesterEmail) api('customer.php', { query: { email: t.requesterEmail } }).then((r) => {
|
||||
const rest = r.tickets.filter((x) => x.number !== number).slice(0, 6);
|
||||
@@ -586,6 +627,7 @@ async function vTicket(main, number) {
|
||||
h('button', { class: 'btn ghost sm', onclick: () => update({ following: !t.following }, t.following ? 'Du beobachtest das Ticket nicht mehr' : 'Du beobachtest das Ticket jetzt') }, t.following ? 'Nicht mehr beobachten' : '👁 Beobachten'),
|
||||
h('button', { class: 'btn ghost sm', title: 'Im Browser öffnen: zusammenführen, aufteilen, drucken', onclick: () => openWebPage('/staff/ticket_view.php?n=' + encodeURIComponent(t.number)) }, '🌐 Im Browser'),
|
||||
spamBtn,
|
||||
reloadMailBtn,
|
||||
h('button', { class: 'btn ghost sm', title: 'Ticketnummer kopieren', onclick: () => { navigator.clipboard?.writeText(t.number); toast('Nummer kopiert'); } }, 'Nummer kopieren'),
|
||||
h('button', { class: 'btn ghost sm', onclick: reload }, 'Aktualisieren'));
|
||||
const st = statusInfo(t.status);
|
||||
|
||||
File diff suppressed because one or more lines are too long
+1
-1
@@ -158,7 +158,7 @@ ipcMain.handle('notify', (_e, { title, body }) => {
|
||||
});
|
||||
|
||||
// ─── Einstellungen (settings.json im Benutzerordner) ────────────────────────
|
||||
const DEFAULTS = { theme: 'system', autostart: false, closeToTray: true, notifications: true, pollSeconds: 60, startMinimized: false, bounds: null, language: null };
|
||||
const DEFAULTS = { theme: 'system', autostart: false, closeToTray: true, notifications: true, pollSeconds: 30, startMinimized: false, bounds: null, language: null };
|
||||
let settings = { ...DEFAULTS };
|
||||
// Sprache für Tray-Menü und Meldungen (Auswahl im Programm, sonst die von Windows)
|
||||
const lang = () => (settings.language === 'de' || settings.language === 'en' ? settings.language : ((app.getLocale() || 'de').toLowerCase().startsWith('de') ? 'de' : 'en'));
|
||||
|
||||
@@ -11,7 +11,7 @@ const tk = (n, s, st, over) => ({ id: n, number: 'T-' + (1000 + n), subject: s,
|
||||
const tickets = [tk(1, 'Drucker druckt nicht', 'open', true), tk(2, 'Passwort zurücksetzen', 'answered'), tk(3, 'Rechnung fehlt', 'open'), tk(4, 'VPN Verbindung bricht ab', 'open', true)];
|
||||
const routes = {
|
||||
'me.php': () => ({ staff }), 'metadata.php': () => ({ staff, departments: [{ id: 1, name: 'Technik' }], priorities: prio, agents: [{ id: 1, name: 'Max Muster' }, { id: 2, name: 'Eva Beispiel' }], statuses, tags: [], liveChatEnabled: true }),
|
||||
'activity.php': () => ({ newReplies: 0, newUnassigned: 0, serverTime: '2026-10-12 10:00:00', health: window.__health || [] }),
|
||||
'activity.php': () => ({ newReplies: 0, newUnassigned: 0, ...(window.__attention ? { attention: window.__attention } : {}), serverTime: '2026-10-12 10:00:00', health: window.__health || [] }),
|
||||
'dashboard.php': () => ({ myOpen: 3, overdue: 2, unassigned: 1, unanswered: 4, resolvedToday: 5, slaPercent: 92.4, recent: tickets, departments: [{ name: 'Technik', open: 8, overdue: 2 }, { name: 'Vertrieb', open: 3, overdue: 0 }] }),
|
||||
'web_pages.php': () => ({ pages: [{ key: 'settings', label: 'Einstellungen', path: '/staff/settings.php', group: 'Verwaltung' }, { key: 'backup', label: 'Backup', path: '/staff/backup.php', group: 'System' }] }),
|
||||
'assistant.php': (req) => {
|
||||
@@ -22,10 +22,12 @@ const routes = {
|
||||
return { dbReady: true, settings: { enabled: true, mode: 'offline_only', name: '', greeting: '', noAnswer: '', departmentId: 0, priorityId: 0, logDays: 90, synonyms: 'Zugang, Einladung, beitreten' }, defaults: { name: 'Digitaler Assistent', greeting: 'Hallo!', noAnswer: 'Keine Antwort.' }, state: { activeNow: true, liveChatEnabled: false, kbEnabled: true, helpdeskOffline: false }, departments: [{ id: 1, name: 'Technik' }], priorities: [{ id: 1, name: 'Normal' }] };
|
||||
},
|
||||
'tickets.php': () => ({ items: tickets, page: 1, perPage: 30, total: 4, hasMore: false }),
|
||||
'ticket.php': () => ({ ticket: { ...tickets[0], collaborators: window.__collabs || [{ id: 1, email: 'cc@firma.de', name: 'Cc Person' }], followers: [{ id: 2, name: 'Eva Beispiel' }], csat: { rating: 4, comment: 'Schnell und freundlich', sentAt: '2026-10-12 09:00:00', answeredAt: '2026-10-12 09:30:00' }, source: 'web', firstResponseDueAt: null, resolutionDueAt: '2026-10-12 12:00:00', similar: { articles: [{ title: 'Drucker einrichten', url: '/kb_article.php?slug=drucker' }], tickets: [{ number: 'T-77', subject: 'Alter Druckerfall' }] }, lockedByOtherName: null, lockedBy: window.__lockedBy || null, canUnlock: !!window.__lockedBy, customFields: [{ label: 'Titel der Geschichte', type: 'text', value: 'Der kleine Bär' }, { label: 'Nutzerbedingungen', type: 'checkbox', value: 'Ja' }], replySignatures: { default: 'own', own: { available: true, text: 'Viele Grüße\nMax persönlich', html: '<p>Viele Grüße</p><div>Max persönlich</div>', hasImage: false }, department: { available: true, name: 'Technik', text: 'Technik-Team Support', html: '<p>Technik-Team Support</p><img src="https://example.org/logo.png" width="120" onerror="alert(1)" style="width:999px"><a href="javascript:alert(1)">Link</a><scr' + 'ipt>alert(2)</scr' + 'ipt>', hasImage: true } }, threads: [
|
||||
'ticket.php': () => ({ ticket: { ...tickets[0], collaborators: window.__collabs || [{ id: 1, email: 'cc@firma.de', name: 'Cc Person' }], followers: [{ id: 2, name: 'Eva Beispiel' }], csat: { rating: 4, comment: 'Schnell und freundlich', sentAt: '2026-10-12 09:00:00', answeredAt: '2026-10-12 09:30:00' }, source: window.__source || 'web', firstResponseDueAt: null, resolutionDueAt: '2026-10-12 12:00:00', similar: { articles: [{ title: 'Drucker einrichten', url: '/kb_article.php?slug=drucker' }], tickets: [{ number: 'T-77', subject: 'Alter Druckerfall' }] }, lockedByOtherName: null, lockedBy: window.__lockedBy || null, canUnlock: !!window.__lockedBy, customFields: [{ label: 'Titel der Geschichte', type: 'text', value: 'Der kleine Bär' }, { label: 'Nutzerbedingungen', type: 'checkbox', value: 'Ja' }], replySignatures: { default: 'own', own: { available: true, text: 'Viele Grüße\nMax persönlich', html: '<p>Viele Grüße</p><div>Max persönlich</div>', hasImage: false }, department: { available: true, name: 'Technik', text: 'Technik-Team Support', html: '<p>Technik-Team Support</p><img src="https://example.org/logo.png" width="120" onerror="alert(1)" style="width:999px"><a href="javascript:alert(1)">Link</a><scr' + 'ipt>alert(2)</scr' + 'ipt>', hasImage: true } }, threads: [
|
||||
{ id: 1, authorType: 'user', authorName: 'Kunde 1', message: 'Mein Drucker druckt nichts mehr.\nBitte helft.', isInternalNote: false, createdAt: '2026-10-12 08:00:00', attachments: [{ id: 1, name: 'foto.png', size: 20480 }], comments: [] },
|
||||
{ id: 2, authorType: 'staff', authorName: 'Max Muster', message: 'Wir schauen uns das an.', isInternalNote: false, createdAt: '2026-10-12 08:30:00', attachments: [], comments: [] },
|
||||
{ id: 3, authorType: 'staff', authorName: 'Max Muster', message: 'Treiber prüfen', isInternalNote: true, createdAt: '2026-10-12 08:35:00', attachments: [], comments: [] }] } }),
|
||||
'mail_fetch.php': (req) => { (window.__mailFetchPosts = window.__mailFetchPosts || []).push(req.body); return { ok: true, level: 'success', message: 'Abruf fertig: 2 E-Mail(s) verarbeitet.', processed: 2, newTickets: 2, replies: 0, errors: [], available: true }; },
|
||||
'ticket_mail_reload.php': (req) => { (window.__mailReloadPosts = window.__mailReloadPosts || []).push(req.body); return { added: 1, inlineImages: 1, formatting: true, skipped: 0, alreadyThere: 0, failed: [] }; },
|
||||
'ticket_collaborators.php': (req) => {
|
||||
window.__collabs = window.__collabs || [{ id: 1, email: 'cc@firma.de', name: 'Cc Person' }];
|
||||
const b = req.body || {};
|
||||
|
||||
@@ -50,6 +50,17 @@ app.whenReady().then(async () => {
|
||||
check('Reiter „Wiedervorlage“, „Pausiert“ und „Beobachtet“ sind da', await run(`(() => { const t = [...document.querySelectorAll('#main .tabs button')].map((b) => b.innerText).join('|'); return t.includes('Wiedervorlage') && t.includes('Pausiert') && t.includes('Beobachtet'); })()`));
|
||||
check('Gespeicherte Ansicht erscheint als Chip', await run(`document.getElementById('main').innerText.includes('Meine überfälligen')`));
|
||||
|
||||
// E-Mail-Abruf von Hand und Nachladen der Mail-Anhänge
|
||||
check('Kopfleiste hat den Knopf „E-Mails abrufen“', await run(`!![...document.querySelectorAll('.topbar button')].find(b => b.textContent.includes('E-Mails abrufen'))`));
|
||||
await run(`[...document.querySelectorAll('.topbar button')].find(b => b.textContent.includes('E-Mails abrufen')).click()`);
|
||||
check('Abruf von Hand sendet die Anfrage und zeigt das Ergebnis', await waitFor(`(window.__mailFetchPosts || []).length === 1 && document.body.innerText.includes('Abruf fertig: 2 E-Mail(s) verarbeitet.')`));
|
||||
check('Knopf ist nach dem Abruf wieder bedienbar', await waitFor(`(() => { const b = [...document.querySelectorAll('.topbar button')].find(b => b.textContent.includes('E-Mails abrufen')); return !!b && !b.disabled; })()`));
|
||||
await run(`window.__source = 'email'; go('ticket', 1)`);
|
||||
check('E-Mail-Ticket: Knopf „Anhänge laden“ ist da', await waitFor(`!![...document.querySelectorAll('#main button')].find(b => b.textContent.includes('Anhänge laden'))`));
|
||||
await run(`[...document.querySelectorAll('#main button')].find(b => b.textContent.includes('Anhänge laden')).click()`);
|
||||
check('Nachladen sendet die Ticketnummer', await waitFor(`(window.__mailReloadPosts || []).length === 1 && !!window.__mailReloadPosts[0].number`));
|
||||
await run(`window.__source = 'web'; go('ticket', 1)`);
|
||||
check('Web-Ticket: kein Knopf „Anhänge laden“', await waitFor(`document.getElementById('main').innerText.includes('Drucker druckt nicht') && ![...document.querySelectorAll('#main button')].find(b => b.textContent.includes('Anhänge laden'))`));
|
||||
await run(`go('ticket', 1)`);
|
||||
check('Ticketdetail zeigt Betreff und Verlauf', await waitFor(`['Drucker druckt nicht', 'Mein Drucker druckt nichts mehr', 'Treiber prüfen'].every(x => document.getElementById('main').innerText.includes(x))`));
|
||||
check('Ticketdetail zeigt Eigenschaften und Aktionen', await run(`['eigenschaften', 'schließen'].every(x => document.getElementById('main').innerText.toLowerCase().includes(x))`));
|
||||
@@ -194,6 +205,24 @@ app.whenReady().then(async () => {
|
||||
check('Klick auf die deutsche Flagge speichert die Sprache', await run(`localStorage.getItem('lang') === 'de'`));
|
||||
await run(`localStorage.removeItem('lang')`);
|
||||
|
||||
console.log('Zähler nach echtem Stand');
|
||||
await run(`go('dashboard')`);
|
||||
await run(`window.__notified = []; const on = window.deskhub.notify; window.deskhub.notify = (t, m) => { window.__notified.push(m); return on && on(t, m); }; window.__badgeCalls = []; const ob = window.deskhub.badge; window.deskhub.badge = (c, d) => { window.__badgeCalls.push(c); return ob && ob(c, d); }; window.__attention = { unassigned: [5], mine: [7] }; state.knownIds = null; state.badge = 0;`);
|
||||
await run(`pollActivity()`);
|
||||
check('Zähler zeigt die Zahl der Tickets, die Aufmerksamkeit brauchen', await waitFor(`state.badge === 2 && document.getElementById('badge') && document.getElementById('badge').textContent === '2'`));
|
||||
check('Erster Abruf meldet nichts (nur Bezugspunkt)', await run(`window.__notified.length === 0`));
|
||||
await run(`window.__attention = { unassigned: [], mine: [] }; pollActivity()`);
|
||||
check('Zähler sinkt von selbst auf 0, wenn nichts mehr offen ist', await waitFor(`state.badge === 0 && document.getElementById('badge').classList.contains('hidden')`));
|
||||
await run(`window.__attention = { unassigned: [9], mine: [] }; pollActivity()`);
|
||||
check('Neues Ticket: Zähler steigt und es kommt eine Meldung', await waitFor(`state.badge === 1 && window.__notified.length === 1 && window.__notified[0].includes('nicht zugewiesen')`));
|
||||
await run(`pollActivity()`);
|
||||
await wait(200);
|
||||
check('Gleicher Stand: keine zweite Meldung', await run(`window.__notified.length === 1`));
|
||||
await run(`go('tickets')`);
|
||||
await wait(300);
|
||||
check('Öffnen der Ticketliste leert den Zähler nicht mehr', await run(`state.badge === 1`));
|
||||
await run(`delete window.__attention`);
|
||||
|
||||
check('Keine JavaScript-Fehler in der Konsole', consoleErrors.length === 0, consoleErrors.slice(0, 3).join(' | '));
|
||||
} catch (e) {
|
||||
failures.push('Testlauf abgebrochen: ' + e.message);
|
||||
|
||||
Reference in New Issue
Block a user