Upload via GUI (74 Dateien)

This commit is contained in:
2026-11-05 21:22:50 +00:00
parent c0b4092498
commit dee5c631b9
10 changed files with 299 additions and 282 deletions
+2 -2
View File
@@ -4,7 +4,7 @@
<body><div id="app"></div><div id="toast" class="toast hidden"></div>
<script>
// Test-Attrappe für die Electron-Brücke (window.deskhub) – nur für lokale UI-Tests ohne Server
const staff = { id: 1, name: 'Max Muster', email: 'max@example.de', role: 'agent', permissions: { view_reports: true, reply_ticket: true, close_ticket: true, post_note: true, assign_ticket: true, create_ticket: true, manage_teams: true, manage_roles: true, manage_business_hours: true } };
const staff = { id: 1, name: 'Max Muster', email: 'max@example.de', role: 'agent', permissions: { view_reports: true, reply_ticket: true, close_ticket: true, post_note: true, assign_ticket: true, create_ticket: true, manage_teams: true, manage_roles: true, manage_business_hours: true, fetch_mail: true, share_views: true, manage_banlist: true } };
const prio = [{ id: 1, name: 'Hoch', color: '#ef4444' }, { id: 2, name: 'Normal', color: '#3b82f6' }];
const statuses = [{ value: 'open', label: 'Offen', color: '#3b82f6' }, { value: 'answered', label: 'Beantwortet', color: '#22c55e' }, { value: 'closed', label: 'Geschlossen', color: '#64748b' }, { value: 'resolved', label: 'Gelöst', color: '#16a34a' }];
const tk = (n, s, st, over) => ({ id: n, number: 'T-' + (1000 + n), subject: s, requesterName: 'Kunde ' + n, requesterEmail: `k${n}@mail.de`, status: st, priority: prio[n % 2], department: { id: 1, name: 'Technik' }, assignedStaff: n % 3 ? { id: 1, name: 'Max Muster' } : null, isOverdue: !!over, dueLabel: over ? 'vor 2 Std (überfällig)' : 'in 5 Std', isSpam: false, createdAt: '2026-10-12 08:00:00', updatedAt: '2026-10-12 09:1' + n + ':00' });
@@ -57,7 +57,7 @@ const routes = {
'canned_responses.php': () => ({ items: [{ id: 1, title: 'Gruß', body: 'Hallo,<br>danke für Ihre Nachricht.', departmentId: null }, { id: 2, title: 'Nur Technik', body: 'Technik-Text', departmentId: 1 }, { id: 3, title: 'Nur Vertrieb', body: 'Vertrieb-Text', departmentId: 2 }] }),
'customer.php': () => ({ customer: { name: 'Kunde 1', email: 'k1@mail.de' }, tickets }),
'teams.php': () => ({ staffOptions: [{ id: 1, name: 'Max Muster' }, { id: 2, name: 'Eva Beispiel' }], items: [{ id: 1, name: 'Support', leadId: 1, leadName: 'Max Muster', notes: '', isActive: true, memberCount: 2, memberIds: [1, 2] }] }),
'roles.php': () => ({ permissionKeys: ['reply_ticket', 'close_ticket', 'manage_teams'], defaultAgentPermissions: { reply_ticket: true }, items: [{ id: 1, name: 'Teamleitung', permissions: { reply_ticket: true }, staffCount: 2 }] }),
'roles.php': () => ({ permissionKeys: ['reply_ticket', 'close_ticket', 'manage_teams', 'merge_split_ticket'], permissionLabels: { reply_ticket: 'Tickets beantworten', close_ticket: 'Tickets schließen/lösen', manage_teams: 'Teams verwalten', merge_split_ticket: 'Tickets zusammenführen und teilen' }, permissionGroups: [{ title: 'Tickets bearbeiten', keys: ['reply_ticket', 'close_ticket', 'merge_split_ticket'] }, { title: 'Team und Sicherheit', keys: ['manage_teams'] }], defaultAgentPermissions: { reply_ticket: true, merge_split_ticket: true }, items: [{ id: 1, name: 'Teamleitung', permissions: { reply_ticket: true }, effectivePermissions: { reply_ticket: true, close_ticket: false, manage_teams: false, merge_split_ticket: true }, staffCount: 2 }] }),
'business_hours.php': () => ({ enabled: true, hours: [1, 2, 3, 4, 5, 6, 0].map((w) => ({ weekday: w, isOpen: w > 0 && w < 6, openTime: '09:00', closeTime: '17:00' })), holidays: [{ id: 1, date: '2026-12-25', name: 'Weihnachten' }] }),
};
window.__calls = [];
+15
View File
@@ -55,6 +55,21 @@ app.whenReady().then(async () => {
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; })()`));
// Neue Rechte (Server 1.0.85): Knöpfe und Bereiche erscheinen nur mit dem Recht
const withPerm = async (perm, on, fn) => { await run(`state.staff.permissions['${perm}'] = ${on}; renderShell(); go('tickets')`); await wait(400); return fn(); };
check('Ohne das Recht „E-Mails jetzt abrufen“ fehlt der Knopf', await withPerm('fetch_mail', false, () => run(`![...document.querySelectorAll('.topbar button')].find(b => b.textContent.includes('E-Mails abrufen'))`)));
check('Mit dem Recht ist der Knopf wieder da', await withPerm('fetch_mail', true, () => run(`!![...document.querySelectorAll('.topbar button')].find(b => b.textContent.includes('E-Mails abrufen'))`)));
await run(`state.staff.permissions.manage_banlist = false; state.staff.permissions.manage_teams = true; go('admin')`);
check('Sperrliste ist ohne Recht nicht in der Verwaltung', await waitFor(`document.getElementById('main').innerText.includes('Teams') && !document.getElementById('main').innerText.includes('Sperrliste')`));
await run(`state.staff.permissions.manage_banlist = true; go('admin')`);
check('Sperrliste erscheint mit dem Recht „Sperrliste“ (ohne Administrator zu sein)', await waitFor(`document.getElementById('main').innerText.includes('Sperrliste')`));
// Rollen-Editor: Gruppen und Beschriftungen vom Server, wirksame Rechte (Standardwerte) angekreuzt
await run(`go('admin', 'roles')`);
check('Rollen: Tabelle zeigt die Rolle', await waitFor(`document.getElementById('main').innerText.includes('Teamleitung')`));
await run(`[...document.querySelectorAll('#main tr.click')][0].click()`);
check('Rollen-Editor zeigt Gruppen und neue Rechte mit Beschriftung vom Server', await waitFor(`(() => { const t = document.body.innerText.toLowerCase(); return t.includes('tickets bearbeiten') && t.includes('team und sicherheit') && t.includes('tickets zusammenführen und teilen'); })()`));
check('Rechte, die für die Rolle als Standard gelten, sind angekreuzt (nicht „aus“)', await run(`(() => { const l = [...document.querySelectorAll('.modal label.check, .modal-box label.check, label.check')].find(x => x.textContent.includes('zusammenführen und teilen')); return !!l && l.querySelector('input').checked; })()`));
await run(`document.querySelector('.modal button.ghost, .modal .btn.ghost, .modal-box .btn.ghost')?.click()`);
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()`);