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
+7 -7
View File
@@ -224,7 +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')),
can('fetch_mail') ? 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')) : null,
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(),
@@ -409,7 +409,7 @@ async function vTickets(main, preset) {
};
const saveView = () => {
const name = h('input', { placeholder: 'z. B. Meine überfälligen' }), shared = h('input', { type: 'checkbox' });
modal('Ansicht speichern', h('div', {}, h('p', { class: 'muted' }, 'Speichert die aktuellen Filter (Reiter, Suche, Abteilung, Priorität).'), h('label', {}, 'Name'), name, h('label', { class: 'check' }, shared, 'Für alle Mitarbeiter sichtbar')),
modal('Ansicht speichern', h('div', {}, h('p', { class: 'muted' }, 'Speichert die aktuellen Filter (Reiter, Suche, Abteilung, Priorität).'), h('label', {}, 'Name'), name, can('share_views') ? h('label', { class: 'check' }, shared, 'Für alle Mitarbeiter sichtbar') : null),
[{ label: 'Speichern', fn: async (close) => {
const { page, ...filters } = filt();
if (await run(() => post('queues.php', { action: 'save', name: name.value, isShared: shared.checked, filters }), 'Ansicht gespeichert')) { close(); paintViews(); }
@@ -843,7 +843,7 @@ async function vReports(main) {
h('div', { class: 'checks wide' }, ...metrics),
h('div', { class: 'row top' }, h('button', { class: 'btn', onclick: () => run_() }, 'Bericht erstellen'),
h('button', { class: 'btn ghost', onclick: () => { const name = h('input', { placeholder: 'z. B. Monatsbericht Support' }), shared = h('input', { type: 'checkbox' });
modal('Bericht speichern', h('div', {}, h('label', {}, 'Name'), name, h('label', { class: 'check' }, shared, 'Für alle Mitarbeiter sichtbar')), [{ label: 'Speichern', fn: async (close) => { if (await run(() => post('report_builder.php', { action: 'save', name: name.value, isShared: shared.checked, config: { ...cfg } }), 'Bericht gespeichert')) { close(); run_(); } } }]); } }, 'Bericht speichern')));
modal('Bericht speichern', h('div', {}, h('label', {}, 'Name'), name, can('share_views') ? h('label', { class: 'check' }, shared, 'Für alle Mitarbeiter sichtbar') : null), [{ label: 'Speichern', fn: async (close) => { if (await run(() => post('report_builder.php', { action: 'save', name: name.value, isShared: shared.checked, config: { ...cfg } }), 'Bericht gespeichert')) { close(); run_(); } } }]); } }, 'Bericht speichern')));
};
// Kundenzufriedenheit (Bewertungen der letzten 30 Tage), nur wenn die Abfrage eingeschaltet ist oder es schon Antworten gibt
const csatBox = h('div');
@@ -871,10 +871,10 @@ async function vAvailability(main) {
const note = h('input', { value: r.myNote, placeholder: 'Notiz (optional)' });
main.append(h('h2', {}, 'Verfügbarkeit'), h('div', { class: 'panel' }, h('label', {}, 'Mein Status'), sel, h('label', {}, 'Notiz'), note,
h('div', { class: 'row' }, h('button', { class: 'btn', onclick: () => run(() => post('availability.php', { status: sel.value, note: note.value }), 'Gespeichert') }, 'Speichern'))),
h('h3', {}, isAdmin() ? 'Team (als Administrator kannst du den Status ändern)' : 'Team'),
h('h3', {}, can('manage_availability') ? 'Team (du kannst den Status ändern)' : 'Team'),
h('div', { class: 'panel' }, h('table', {}, ...r.team.map((m) => {
const cells = [h('td', {}, m.name), h('td', {}, m.isOnVacation ? '🏖 Urlaub' : (labels[m.status] || m.status) + (m.autoOffline ? ' (automatisch, inaktiv)' : '')), h('td', { class: 'muted' }, m.note)];
if (isAdmin()) {
if (can('manage_availability')) {
const ms = h('select', {}, ...Object.entries(labels).map(([v, l]) => h('option', { value: v, selected: v === m.status }, l)));
const mn = h('input', { value: m.note || '', placeholder: 'Notiz', maxlength: '120' });
cells.push(h('td', {}, ms), h('td', {}, mn), h('td', {}, h('button', { class: 'btn sm', onclick: async () => { const ok = await run(() => post('availability.php', { status: ms.value, note: mn.value, staffId: m.id }), 'Status von ' + m.name + ' geändert'); if (ok) go('availability'); } }, 'Setzen')));
@@ -913,7 +913,7 @@ const ADMIN = [
['_h2', 'Zugang', H], ['departmentId', 'Hauptabteilung', 'departments?'], ['extraDepartments', 'Erweiterter Zugriff', XD],
['_h3', 'Berechtigungen', H], ['role', 'Kontotyp', [['agent', 'Mitarbeiter'], ['admin', 'Administrator (alle Rechte)']]], ['roleId', 'Rolle (für Mitarbeiter)', 'roles?'],
['_h4', 'Teams', H], ['teamIds', 'Mitglied in Teams', 'multi:teams']] },
{ key: 'banlist', label: 'Sperrliste', admin: true, file: 'banlist.php', cols: [['Typ', 'matchType'], ['Wert', 'value'], ['Grund', 'reason'], ['Von', 'addedByName']], saveAction: 'create', noEdit: true,
{ key: 'banlist', label: 'Sperrliste', perm: 'manage_banlist', file: 'banlist.php', cols: [['Typ', 'matchType'], ['Wert', 'value'], ['Grund', 'reason'], ['Von', 'addedByName']], saveAction: 'create', noEdit: true,
fields: [['matchType', 'Typ', [['email', 'E-Mail-Adresse'], ['domain', 'Domain']]], ['value', 'Wert', T], ['reason', 'Grund', T]] },
{ key: 'audit', label: 'Admin-Protokoll', perm: 'view_audit_log', file: 'audit_log.php', readonly: true, paged: true, cols: [['Zeit', (x) => fmtDate(x.createdAt)], ['Mitarbeiter', 'staffName'], ['Aktion', 'action']] },
{ key: 'time', label: 'Zeiterfassung', perm: 'view_time_entries', file: 'time_entries.php', readonly: true, list: 'entries', cols: [['Datum', (x) => fmtDate(x.loggedAt)], ['Mitarbeiter', 'staffName'], ['Ticket', 'ticketNumber'], ['Minuten', 'minutes'], ['Notiz', 'note']] },
@@ -937,7 +937,7 @@ async function vAdmin(main, sectionKey) {
if (!sectionKey) {
// Bereiche zu Gruppen ordnen (gleiche Gruppen wie im Adminportal und in der Android-App)
const entries = new Map(sections.map((s) => [s.key, { label: s.label, open: () => go('admin', s.key) }]));
if (isAdmin()) entries.set('status', { label: 'Status der Mitarbeiter', open: () => go('availability') });
if (can('manage_availability')) entries.set('status', { label: 'Status der Mitarbeiter', open: () => go('availability') });
const used = new Set();
const groupsView = ADMIN_GROUPS.map(([title, keys]) => {
const tiles = keys.filter((k) => entries.has(k)).map((k) => { used.add(k); const e = entries.get(k);