Upload via GUI (69 Dateien)
This commit is contained in:
+3
-3
@@ -275,13 +275,13 @@ function ticketTable(items, opts = {}) {
|
||||
const selected = opts.selected;
|
||||
const allBox = selected ? h('input', { type: 'checkbox', onchange: (e) => { items.forEach((t) => (e.target.checked ? selected.add(t.number) : selected.delete(t.number))); opts.onSelect?.(); for (const c of document.querySelectorAll('.rowcheck')) c.checked = e.target.checked; } }) : null;
|
||||
return h('div', { class: 'panel flush' }, h('table', { class: 'tickets' },
|
||||
h('tr', {}, selected ? h('th', { class: 'chk' }, allBox) : null, h('th', {}, 'Nr.'), h('th', {}, 'Betreff'), h('th', {}, 'Kunde'), h('th', {}, 'Status'), h('th', {}, 'Priorität'), h('th', {}, 'Zugewiesen'), h('th', {}, 'Fällig'), h('th', {}, 'Aktualisiert')),
|
||||
h('tr', {}, selected ? h('th', { class: 'chk' }, allBox) : null, h('th', {}, 'Nr.'), h('th', {}, 'Betreff'), h('th', {}, 'Kunde'), h('th', {}, 'Status'), h('th', {}, 'Priorität'), h('th', {}, 'Zugewiesen'), h('th', {}, 'Eingang'), h('th', {}, 'Fällig'), h('th', {}, 'Aktualisiert')),
|
||||
...items.map((t) => h('tr', { class: 'click' + (t.isOverdue ? ' overdue' : ''), onclick: () => go('ticket', t.number) },
|
||||
selected ? h('td', { class: 'chk', onclick: (e) => e.stopPropagation() }, h('input', { type: 'checkbox', class: 'rowcheck', checked: selected.has(t.number), onchange: (e) => { e.target.checked ? selected.add(t.number) : selected.delete(t.number); e.target.closest('tr').classList.toggle('sel', e.target.checked); opts.onSelect?.(); } })) : null,
|
||||
h('td', { class: 'nowrap' }, t.number), h('td', {}, (t.isSpam ? '🚫 ' : '') + t.subject), h('td', {}, t.requesterName),
|
||||
h('td', { class: 'nowrap' }, h('span', { class: 'pill', color: statusInfo(t.status).color }, statusInfo(t.status).label)),
|
||||
h('td', { class: 'nowrap' }, h('span', { class: 'dot', color: t.priority.color }), t.priority.name),
|
||||
h('td', {}, t.assignedStaff?.name || '—'), h('td', { class: dueClass(t) + ' nowrap' }, t.dueLabel || ''), h('td', { class: 'muted nowrap' }, fmtDate(t.updatedAt))))));
|
||||
h('td', {}, t.assignedStaff?.name || '—'), h('td', { class: 'muted nowrap' }, fmtDate(t.createdAt)), h('td', { class: dueClass(t) + ' nowrap' }, t.dueLabel || ''), h('td', { class: 'muted nowrap' }, fmtDate(t.updatedAt))))));
|
||||
}
|
||||
|
||||
async function vDashboard(main) {
|
||||
@@ -720,7 +720,7 @@ async function vAvailability(main) {
|
||||
const T = 'text', N = 'number', B = 'bool', A = 'area', H = 'heading', ES = 'emailsel', XD = 'extradepts';
|
||||
const ADMIN = [
|
||||
{ key: 'departments', label: 'Abteilungen', perm: 'manage_departments', file: 'departments.php', cols: [['Name', 'name'], ['Status', (x) => x.isActive ? 'Aktiv' : 'Deaktiviert'], ['Mitglieder', (x) => (x.members || []).length], ['Abteilungsleiter', 'managerName'], ['E-Mail', 'email'], ['Aktualisiert', (x) => x.updatedAt ? fmtDate(x.updatedAt) : '']],
|
||||
fields: [['_h1', 'Abteilungsinformationen', H], ['name', 'Name', T], ['isActive', 'Aktiv', B], ['managerId', 'Abteilungsleiter', 'staffOptions?'], ['slaPlanId', 'SLA-Plan (leer = Zeiten der Priorität)', 'slaPlans?'], ['email', 'Abteilungs-E-Mail (optional, sonst Standardadresse)', ES], ['isDefault', 'Standardabteilung', B], ['signature', 'Signatur', A], ['notes', 'Administratornotizen (intern)', A]],
|
||||
fields: [['_h1', 'Abteilungsinformationen', H], ['name', 'Name', T], ['isActive', 'Aktiv', B], ['managerId', 'Abteilungsleiter', 'staffOptions?'], ['autoAssignMode', 'Neue Tickets automatisch zuweisen', [['none', 'Nicht automatisch zuweisen'], ['staff', 'Immer an einen bestimmten Mitarbeiter'], ['round_robin', 'Reihum an die Mitarbeiter der Abteilung']]], ['autoAssignStaffId', 'Mitarbeiter (nur bei „Immer an einen bestimmten Mitarbeiter“)', 'staffOptions?'], ['slaPlanId', 'SLA-Plan (leer = Zeiten der Priorität)', 'slaPlans?'], ['email', 'Abteilungs-E-Mail (optional, sonst Standardadresse)', ES], ['isDefault', 'Standardabteilung', B], ['signature', 'Signatur', A], ['notes', 'Administratornotizen (intern)', A]],
|
||||
info: (x) => x?.members?.length ? `Mitglieder: ${x.members.map((m) => m.name + (m.isPrimary ? '' : ' (zusätzlich)')).join(', ')}` : '' },
|
||||
{ key: 'priorities', label: 'Prioritäten / SLA', perm: 'manage_priorities', file: 'priorities.php', cols: [['Name', 'name'], ['Reaktion (h)', 'responseHours'], ['Lösung (h)', 'resolutionHours']],
|
||||
fields: [['name', 'Name', T], ['color', 'Farbe (#RRGGBB)', T], ['responseHours', 'Reaktionszeit (Std.)', N], ['resolutionHours', 'Lösungszeit (Std.)', N], ['sortOrder', 'Reihenfolge', N]] },
|
||||
|
||||
@@ -19,6 +19,7 @@ const routes = {
|
||||
{ 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: [] }] } }),
|
||||
'ticket_unlock.php': () => { window.__lockedBy = null; return { ok: true, unlocked: true }; },
|
||||
'departments.php': () => ({ staffOptions: [{ id: 1, name: 'Max Muster' }, { id: 2, name: 'Eva Beispiel' }], emailOptions: [], slaPlans: [], items: [{ id: 1, name: 'Technik', email: 'technik@example.de', isDefault: true, signature: '', isActive: true, slaPlanId: null, managerId: 1, managerName: 'Max Muster', autoAssignMode: 'staff', autoAssignStaffId: 2, notes: '', members: [{ id: 1, name: 'Max Muster', isPrimary: true }] }] }),
|
||||
'queues.php': () => ({ items: [{ id: 1, name: 'Meine überfälligen', isShared: false, mine: true, ownerName: 'Max Muster', filters: { status: 'open,pending', assigned: 'me', sla: 'overdue' } }, { id: 2, name: 'Team-Überblick', isShared: true, mine: false, ownerName: 'Eva Beispiel', filters: { status: 'open,pending,answered' } }] }),
|
||||
'report_builder.php': () => ({ config: { dimension: 'department', metrics: ['total', 'open', 'resolved', 'sla_resolve'], from: '2026-09-15', to: '2026-10-15', sort: 'label', desc: false, filters: {} },
|
||||
columns: [{ key: 'label', label: 'Abteilung', unit: '' }, { key: 'total', label: 'Tickets gesamt', unit: '' }, { key: 'open', label: 'Offen', unit: '' }, { key: 'resolved', label: 'Gelöst/Geschlossen', unit: '' }, { key: 'sla_resolve', label: 'SLA Lösung', unit: '%' }],
|
||||
@@ -38,7 +39,7 @@ window.deskhub = {
|
||||
settings: async () => ({ theme: window.__theme || 'dark', autostart: false, closeToTray: true, notifications: true, pollSeconds: 60, version: '1.1.0', packaged: false }),
|
||||
setSettings: async (p) => { if (p.theme) window.__theme = p.theme; return window.deskhub.settings(); },
|
||||
login: async () => ({ staff }), logout: async () => true, notify: async () => {}, badge: async () => {},
|
||||
api: async (req) => { window.__calls.push(req.path); const f = routes[req.path]; return f ? { data: f() } : { error: { message: 'Mock: ' + req.path + ' nicht vorhanden' } }; },
|
||||
api: async (req) => { window.__calls.push(req.path); if (req.body) (window.__bodies = window.__bodies || {})[req.path] = req.body; window.__bodies = window.__bodies || {}; const f = routes[req.path]; return f ? { data: f() } : { error: { message: 'Mock: ' + req.path + ' nicht vorhanden' } }; },
|
||||
onSessionExpired() {}, onNavigate() {},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -78,6 +78,17 @@ app.whenReady().then(async () => {
|
||||
check('Verwaltung „' + key + '“ rendert ohne Fehler', ok, await run(`(document.querySelector('#main .warnbox')||{}).innerText || ''`));
|
||||
}
|
||||
|
||||
// Abteilung: automatische Zuweisung neuer Tickets einstellen
|
||||
await run(`go('admin', 'departments')`);
|
||||
check('Abteilungen: Liste lädt', await waitFor(`document.getElementById('main').innerText.includes('Technik')`));
|
||||
await run(`document.querySelector('#main tr.click').click()`);
|
||||
check('Abteilung bearbeiten: Einstellung „Neue Tickets automatisch zuweisen“ ist da und zeigt den gespeicherten Wert', await waitFor(`(() => { const l = [...document.querySelectorAll('.modal label')].find((x) => x.innerText.includes('Neue Tickets automatisch zuweisen')); return !!l && l.nextElementSibling.value === 'staff'; })()`));
|
||||
check('Abteilung bearbeiten: Mitarbeiter-Auswahl zeigt den gespeicherten Mitarbeiter', await run(`(() => { const l = [...document.querySelectorAll('.modal label')].find((x) => x.innerText.startsWith('Mitarbeiter (nur bei')); return !!l && l.nextElementSibling.value === '2'; })()`));
|
||||
await run(`(() => { const l = [...document.querySelectorAll('.modal label')].find((x) => x.innerText.includes('Neue Tickets automatisch zuweisen')); l.nextElementSibling.value = 'round_robin'; })()`);
|
||||
await run(`[...document.querySelectorAll('.modal button')].find((b) => b.innerText === 'Speichern').click()`);
|
||||
check('Abteilung speichern sendet die Zuweisungs-Einstellung an den Server', await waitFor(`!!(window.__bodies['departments.php'] && window.__bodies['departments.php'].action === 'save')`), JSON.stringify(await run(`window.__bodies['departments.php']`)));
|
||||
check('… mit „Reihum“ und dem Mitarbeiter', await run(`(() => { const b = window.__bodies['departments.php'] || {}; return b.autoAssignMode === 'round_robin' && b.autoAssignStaffId === 2; })()`));
|
||||
|
||||
console.log('Theme');
|
||||
await run(`window.deskhub.setSettings({ theme: 'light' }).then(() => { state.prefs = { ...state.prefs, theme: 'light' }; applyTheme(); })`);
|
||||
check('Hell-Modus setzt data-theme', await waitFor(`document.documentElement.dataset.theme === 'light'`));
|
||||
|
||||
Reference in New Issue
Block a user