221 lines
8.4 KiB
JavaScript
221 lines
8.4 KiB
JavaScript
import { apiGet, apiUrl } from '../api.js';
|
|
import { esc, formatDateTime, formatRemoteDurationDe, telHref } from './utils.js';
|
|
|
|
function formatFileSizeDe(n) {
|
|
if (n == null || typeof n !== 'number' || n < 0) return '';
|
|
if (n < 1024) return `${n} B`;
|
|
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
|
|
return `${(n / (1024 * 1024)).toFixed(1)} MB`;
|
|
}
|
|
|
|
/** Zwischenspeicher für GET /integrations/teamviewer/connections */
|
|
let tvSessionsCache = null;
|
|
|
|
/** HTML für die Inhaltsspalte (nur server-/formularbekannte Typen) */
|
|
export function eventInhaltHtml(ev) {
|
|
const t = ev.type;
|
|
if (t === 'CALL') {
|
|
let h = `<div class="event-inhalt-block"><p class="event-inhalt-label">Beschreibung</p><div class="event-inhalt-text">${esc(ev.description)}</div>`;
|
|
if (ev.callbackNumber) {
|
|
const th = telHref(ev.callbackNumber);
|
|
const numHtml = th
|
|
? `<a href="${esc(th)}">${esc(ev.callbackNumber)}</a>`
|
|
: esc(ev.callbackNumber);
|
|
h += `<p class="event-inhalt-meta"><strong>Rückrufnummer:</strong> ${numHtml}</p>`;
|
|
}
|
|
return `${h}</div>`;
|
|
}
|
|
if (t === 'REMOTE') {
|
|
let h = `<div class="event-inhalt-block"><p class="event-inhalt-label">Beschreibung</p><div class="event-inhalt-text">${esc(ev.description)}</div>`;
|
|
if (ev.teamviewerId) {
|
|
h += `<p class="event-inhalt-meta"><strong>Gerät-ID (TeamViewer):</strong> <code>${esc(ev.teamviewerId)}</code></p>`;
|
|
}
|
|
if (ev.remoteDurationSeconds != null) {
|
|
h += `<p class="event-inhalt-meta"><strong>Remote-Dauer:</strong> ${esc(formatRemoteDurationDe(ev.remoteDurationSeconds))}</p>`;
|
|
}
|
|
if (ev.teamviewerNotes && String(ev.teamviewerNotes).trim()) {
|
|
h += `<p class="event-inhalt-meta"><strong>Notizen:</strong> <span class="event-tv-notes" style="white-space:pre-wrap">${esc(String(ev.teamviewerNotes).trim())}</span></p>`;
|
|
}
|
|
return `${h}</div>`;
|
|
}
|
|
if (t === 'PART') {
|
|
let h = `<div class="event-inhalt-block"><p class="event-inhalt-meta"><strong>Artikelnummer:</strong> <code class="event-artnr">${esc(ev.articleNumber || '')}</code></p>`;
|
|
if (ev.description && String(ev.description).trim()) {
|
|
h += `<p class="event-inhalt-label">Bemerkung</p><div class="event-inhalt-text">${esc(ev.description)}</div>`;
|
|
}
|
|
return `${h}</div>`;
|
|
}
|
|
if (t === 'ATTACHMENT') {
|
|
let h = '';
|
|
if (ev.description && String(ev.description).trim()) {
|
|
h += `<div class="event-inhalt-block"><p class="event-inhalt-label">Beschreibung</p><div class="event-inhalt-text">${esc(ev.description)}</div></div>`;
|
|
}
|
|
const atts = ev.attachments || [];
|
|
if (atts.length === 0) {
|
|
h += '<p class="muted">Keine Dateien.</p>';
|
|
} else {
|
|
h += '<ul class="event-attachment-list">';
|
|
for (const a of atts) {
|
|
const href = apiUrl(a.url);
|
|
const timeParen =
|
|
a.createdAt != null
|
|
? ` <span class="muted">(${esc(formatDateTime(a.createdAt))})</span>`
|
|
: '';
|
|
h += `<li><a href="${esc(href)}" class="js-attachment-preview" data-mime="${esc(a.mimeType || '')}" data-name="${esc(a.originalName)}">${esc(a.originalName)}</a>${timeParen}`;
|
|
if (a.sizeBytes != null) {
|
|
h += ` <span class="muted">· ${esc(formatFileSizeDe(a.sizeBytes))}</span>`;
|
|
}
|
|
h += '</li>';
|
|
}
|
|
h += '</ul>';
|
|
}
|
|
return h;
|
|
}
|
|
return `<div class="event-inhalt-text">${esc(ev.description)}</div>`;
|
|
}
|
|
|
|
export function fillTvDeviceSelect() {
|
|
const userSel = document.getElementById('tv-user-select');
|
|
const devSel = document.getElementById('tv-conn-select');
|
|
if (!devSel) return;
|
|
const ukey = userSel?.value ?? '';
|
|
devSel.innerHTML =
|
|
'<option value="">— Gerät / Session wählen —</option>';
|
|
if (!ukey || !tvSessionsCache) {
|
|
devSel.disabled = true;
|
|
return;
|
|
}
|
|
const u = (tvSessionsCache.users || []).find((x) => x.userKey === ukey);
|
|
const devices = u?.devices || [];
|
|
if (devices.length === 0) {
|
|
devSel.disabled = true;
|
|
return;
|
|
}
|
|
devSel.disabled = false;
|
|
devSel.innerHTML +=
|
|
devices
|
|
.map(
|
|
(d) =>
|
|
`<option value="${esc(d.deviceid)}" data-devicename="${esc(d.label)}" data-start-date="${esc(d.startDate || '')}" data-end-date="${esc(d.endDate || '')}" data-notes="${esc(d.notes || '')}">${esc(d.label)}</option>`,
|
|
)
|
|
.join('');
|
|
}
|
|
|
|
export async function loadTeamViewerConnectionsIntoSelect() {
|
|
const userSel = document.getElementById('tv-user-select');
|
|
const devSel = document.getElementById('tv-conn-select');
|
|
const hint = document.getElementById('tv-conn-hint');
|
|
if (!userSel || !devSel) return;
|
|
userSel.innerHTML = '<option value="">… lädt …</option>';
|
|
devSel.innerHTML = '<option value="">…</option>';
|
|
devSel.disabled = true;
|
|
if (hint) hint.textContent = '';
|
|
try {
|
|
const data = await apiGet('/integrations/teamviewer/connections');
|
|
tvSessionsCache = data;
|
|
const users = data.users || [];
|
|
userSel.innerHTML =
|
|
'<option value="">— Benutzer wählen (letzte 7 Tage) —</option>' +
|
|
users
|
|
.map((u) => {
|
|
const label =
|
|
u.username && u.username !== '_unbekannt'
|
|
? u.username
|
|
: u.userid
|
|
? `Benutzer ${u.userid}`
|
|
: 'Unbekannt (Benutzer)';
|
|
return `<option value="${esc(u.userKey)}">${esc(label)}</option>`;
|
|
})
|
|
.join('');
|
|
devSel.innerHTML =
|
|
'<option value="">— zuerst Benutzer wählen —</option>';
|
|
devSel.disabled = true;
|
|
userSel.onchange = () => fillTvDeviceSelect();
|
|
if (hint) {
|
|
const ndev = users.reduce((n, u) => n + (u.devices?.length || 0), 0);
|
|
if (users.length) {
|
|
hint.textContent = `${users.length} Benutzer, ${ndev} Gerät(e)/Session(s).`;
|
|
} else {
|
|
hint.textContent =
|
|
data.meta?.recordCount === 0
|
|
? 'Keine Verbindungen in den letzten 7 Tagen.'
|
|
: 'Keine gruppierten Einträge (TeamViewer-Antwort prüfen).';
|
|
}
|
|
}
|
|
} catch (e) {
|
|
tvSessionsCache = null;
|
|
userSel.innerHTML = '<option value="">— Laden fehlgeschlagen —</option>';
|
|
devSel.innerHTML = '<option value="">—</option>';
|
|
devSel.disabled = true;
|
|
if (hint) hint.textContent = e.message || 'Fehler';
|
|
}
|
|
}
|
|
|
|
export function syncEventFormFieldGroups(form) {
|
|
const sel = form.querySelector('#ev-type-sel');
|
|
if (!sel) return;
|
|
const v = sel.value;
|
|
form.querySelectorAll('.ev-field-group').forEach((el) => {
|
|
const show = el.getAttribute('data-ev-type') === v;
|
|
el.hidden = !show;
|
|
el.querySelectorAll('input, textarea').forEach((inp) => {
|
|
const name = inp.getAttribute('name');
|
|
let req = false;
|
|
if (show) {
|
|
if (v === 'NOTE' && name === 'description_note') req = true;
|
|
if (v === 'CALL' && name === 'description_call') req = true;
|
|
if (v === 'REMOTE' && name === 'description_remote') req = false;
|
|
if (v === 'PART' && name === 'articleNumber') req = true;
|
|
if (v === 'ATTACHMENT') req = false;
|
|
}
|
|
inp.required = req;
|
|
});
|
|
});
|
|
if (v === 'REMOTE') {
|
|
loadTeamViewerConnectionsIntoSelect();
|
|
}
|
|
}
|
|
|
|
/** Neueste zuerst; ATTACHMENT-Blöcke bleiben unten (innerhalb ebenfalls neuer über älter). */
|
|
export function sortEventsChronologicalWithAttachmentsLast(events) {
|
|
const non = events
|
|
.filter((e) => e.type !== 'ATTACHMENT')
|
|
.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
|
|
const att = events
|
|
.filter((e) => e.type === 'ATTACHMENT')
|
|
.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
|
|
return [...non, ...att];
|
|
}
|
|
|
|
export function buildEventPostBody(ticketId, fd) {
|
|
const type = fd.get('type');
|
|
const base = { ticketId, type };
|
|
if (type === 'NOTE') {
|
|
return { ...base, description: fd.get('description_note') };
|
|
}
|
|
if (type === 'CALL') {
|
|
return {
|
|
...base,
|
|
description: fd.get('description_call'),
|
|
callbackNumber: fd.get('callbackNumber'),
|
|
};
|
|
}
|
|
if (type === 'REMOTE') {
|
|
const body = {
|
|
...base,
|
|
description: fd.get('description_remote'),
|
|
};
|
|
const tv = fd.get('teamviewerDevice');
|
|
if (tv && String(tv).trim()) body.teamviewerId = String(tv).trim();
|
|
return body;
|
|
}
|
|
if (type === 'PART') {
|
|
return {
|
|
...base,
|
|
articleNumber: fd.get('articleNumber'),
|
|
description: fd.get('description_part') || '',
|
|
};
|
|
}
|
|
return base;
|
|
}
|