284 lines
9.0 KiB
JavaScript
284 lines
9.0 KiB
JavaScript
import { apiGet, apiPost, apiPostForm, apiPut, isAuthRedirectError } from '../api.js';
|
|
import { guard } from '../core/auth-guard.js';
|
|
import {
|
|
ticketStatusLabel,
|
|
ticketPriorityLabel,
|
|
eventTypeLabel,
|
|
eventTypeBadgeClass,
|
|
statusBadgeClass,
|
|
priorityBadgeClass,
|
|
} from '../core/constants.js';
|
|
import { esc, formatDateTime, extrasName } from '../core/utils.js';
|
|
import { bindAttachmentPreview } from '../core/attachment-preview.js';
|
|
import {
|
|
eventInhaltHtml,
|
|
syncEventFormFieldGroups,
|
|
buildEventPostBody,
|
|
loadTeamViewerConnectionsIntoSelect,
|
|
sortEventsChronologicalWithAttachmentsLast,
|
|
} from '../core/ticket-events.js';
|
|
|
|
const UUID =
|
|
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
|
|
const loadingEl = document.getElementById('page-loading');
|
|
const badIdEl = document.getElementById('ticket-bad-id');
|
|
const errEl = document.getElementById('page-error');
|
|
const mainEl = document.getElementById('page-main');
|
|
const panelView = document.getElementById('panel-ticket-view');
|
|
const panelEdit = document.getElementById('panel-ticket-edit');
|
|
|
|
function showError(msg) {
|
|
loadingEl.hidden = true;
|
|
badIdEl.hidden = true;
|
|
mainEl.hidden = true;
|
|
errEl.hidden = false;
|
|
errEl.textContent = msg;
|
|
}
|
|
|
|
function fillTicketView(ticket) {
|
|
document.getElementById('ticket-title').textContent = ticket.title;
|
|
const stBadge = document.getElementById('t-status-badge');
|
|
stBadge.textContent = ticketStatusLabel[ticket.status];
|
|
stBadge.className = `badge ${statusBadgeClass[ticket.status] || ''}`;
|
|
document.getElementById('t-priority-label').textContent =
|
|
ticketPriorityLabel[ticket.priority];
|
|
const slaSel = document.getElementById('t-sla-days');
|
|
if (slaSel) {
|
|
slaSel.value =
|
|
ticket.slaDays != null && ticket.slaDays !== ''
|
|
? String(ticket.slaDays)
|
|
: '';
|
|
}
|
|
document.getElementById('t-description').textContent = ticket.description;
|
|
|
|
const mrow = document.getElementById('t-machine-row');
|
|
if (ticket.machine) {
|
|
mrow.hidden = false;
|
|
const mn = extrasName(ticket.machine);
|
|
const link = document.getElementById('t-machine-link');
|
|
link.href = `/machine.html?id=${encodeURIComponent(ticket.machine.id)}`;
|
|
link.textContent = ticket.machine.seriennummer;
|
|
document.getElementById('t-machine-suffix').textContent = mn
|
|
? ` · ${mn}`
|
|
: '';
|
|
} else {
|
|
mrow.hidden = true;
|
|
}
|
|
}
|
|
|
|
function fillEditForm(ticket) {
|
|
document.getElementById('tu-title').value = ticket.title;
|
|
document.getElementById('tu-desc').value = ticket.description;
|
|
document.getElementById('tu-status').value = ticket.status;
|
|
document.getElementById('tu-priority').value = ticket.priority;
|
|
}
|
|
|
|
function renderEvents(events) {
|
|
const tbody = document.getElementById('events-table-body');
|
|
if (events.length === 0) {
|
|
tbody.innerHTML =
|
|
'<tr><td colspan="3" class="muted">Noch keine Ereignisse.</td></tr>';
|
|
return;
|
|
}
|
|
tbody.innerHTML = events
|
|
.map(
|
|
(ev) => `
|
|
<tr>
|
|
<td class="events-table-time">${esc(formatDateTime(ev.createdAt))}</td>
|
|
<td><span class="badge event-type-badge ${eventTypeBadgeClass[ev.type] || ''}">${esc(eventTypeLabel[ev.type] || ev.type)}</span></td>
|
|
<td class="events-table-desc">${eventInhaltHtml(ev)}</td>
|
|
</tr>`,
|
|
)
|
|
.join('');
|
|
}
|
|
|
|
function showViewMode() {
|
|
panelView.hidden = false;
|
|
panelEdit.hidden = true;
|
|
}
|
|
|
|
function showEditMode() {
|
|
panelView.hidden = true;
|
|
panelEdit.hidden = false;
|
|
}
|
|
|
|
async function viewTicketDetail(id) {
|
|
const [ticket, events] = await Promise.all([
|
|
apiGet(`/tickets/${id}`),
|
|
apiGet(`/tickets/${id}/events`),
|
|
]);
|
|
|
|
let currentTicket = ticket;
|
|
|
|
fillTicketView(currentTicket);
|
|
fillEditForm(currentTicket);
|
|
showViewMode();
|
|
renderEvents(sortEventsChronologicalWithAttachmentsLast(events));
|
|
|
|
const sect2 = document.getElementById('sect-second-ticket');
|
|
if (currentTicket.machineId) {
|
|
sect2.hidden = false;
|
|
} else {
|
|
sect2.hidden = true;
|
|
}
|
|
|
|
const slaSel = document.getElementById('t-sla-days');
|
|
if (slaSel) {
|
|
slaSel.onchange = async () => {
|
|
const v = slaSel.value;
|
|
const slaDays = v === '' ? null : Number(v);
|
|
try {
|
|
const updated = await apiPut(`/tickets/${id}`, { slaDays });
|
|
const evs = await apiGet(`/tickets/${id}/events`);
|
|
currentTicket = updated;
|
|
fillTicketView(updated);
|
|
fillEditForm(updated);
|
|
renderEvents(sortEventsChronologicalWithAttachmentsLast(evs));
|
|
} catch (err) {
|
|
slaSel.value =
|
|
currentTicket.slaDays != null
|
|
? String(currentTicket.slaDays)
|
|
: '';
|
|
if (isAuthRedirectError(err)) return;
|
|
window.alert(err.message || 'Fehler beim Speichern der Fälligkeit.');
|
|
}
|
|
};
|
|
}
|
|
|
|
document.getElementById('btn-t-edit').onclick = () => {
|
|
fillEditForm(currentTicket);
|
|
showEditMode();
|
|
};
|
|
|
|
panelEdit.onsubmit = async (e) => {
|
|
e.preventDefault();
|
|
const fd = new FormData(panelEdit);
|
|
await apiPut(`/tickets/${id}`, Object.fromEntries(fd.entries()));
|
|
location.reload();
|
|
};
|
|
|
|
document.getElementById('tu-cancel').onclick = () => {
|
|
fillTicketView(currentTicket);
|
|
showViewMode();
|
|
};
|
|
|
|
const formEv = document.getElementById('form-ev');
|
|
syncEventFormFieldGroups(formEv);
|
|
formEv.querySelector('#ev-type-sel').onchange = () =>
|
|
syncEventFormFieldGroups(formEv);
|
|
document.getElementById('btn-tv-reload').onclick = () =>
|
|
loadTeamViewerConnectionsIntoSelect();
|
|
formEv.onsubmit = async (e) => {
|
|
e.preventDefault();
|
|
const fd = new FormData(e.target);
|
|
const evType = fd.get('type');
|
|
if (evType === 'ATTACHMENT') {
|
|
const fileInput = document.getElementById('ev-attachment-files');
|
|
const files = fileInput?.files;
|
|
if (!files || files.length === 0) {
|
|
formEv.insertAdjacentHTML(
|
|
'afterbegin',
|
|
'<p class="error tv-form-err">Mindestens eine Datei auswählen.</p>',
|
|
);
|
|
setTimeout(() => {
|
|
document.querySelector('.tv-form-err')?.remove();
|
|
}, 4000);
|
|
return;
|
|
}
|
|
const formData = new FormData();
|
|
formData.append(
|
|
'description',
|
|
String(fd.get('description_attachment') ?? '').trim(),
|
|
);
|
|
for (let i = 0; i < files.length; i += 1) {
|
|
formData.append('files', files[i]);
|
|
}
|
|
await apiPostForm(`/tickets/${id}/events/attachments`, formData);
|
|
e.target.reset();
|
|
syncEventFormFieldGroups(formEv);
|
|
location.reload();
|
|
return;
|
|
}
|
|
let body = buildEventPostBody(id, fd);
|
|
if (body.type === 'REMOTE') {
|
|
const sel = document.getElementById('tv-conn-select');
|
|
const opt = sel?.selectedOptions?.[0];
|
|
if (opt?.value) {
|
|
body.teamviewerId = opt.value;
|
|
const sd = opt.getAttribute('data-start-date');
|
|
const ed = opt.getAttribute('data-end-date');
|
|
if (sd) body.teamviewerStartDate = sd;
|
|
if (ed) body.teamviewerEndDate = ed;
|
|
const n = opt.getAttribute('data-notes');
|
|
if (n && String(n).trim()) body.teamviewerNotes = String(n).trim();
|
|
const dn = opt.getAttribute('data-devicename') || '';
|
|
const u = String(fd.get('description_remote') ?? '').trim();
|
|
if (dn) {
|
|
body.description = u
|
|
? `${u}\n\nTeamViewer-Gerät: ${dn}`
|
|
: `TeamViewer-Gerät: ${dn}`;
|
|
} else if (u) {
|
|
body.description = u;
|
|
} else {
|
|
body.description = 'Remote-Session (TeamViewer)';
|
|
}
|
|
} else {
|
|
body.description = String(body.description ?? '').trim();
|
|
if (!body.description) {
|
|
formEv.insertAdjacentHTML(
|
|
'afterbegin',
|
|
'<p class="error tv-form-err">Beschreibung oder Gerät auswählen.</p>',
|
|
);
|
|
setTimeout(() => {
|
|
document.querySelector('.tv-form-err')?.remove();
|
|
}, 4000);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
await apiPost('/events', body);
|
|
e.target.reset();
|
|
syncEventFormFieldGroups(formEv);
|
|
location.reload();
|
|
};
|
|
|
|
document.getElementById('form-t2').onsubmit = async (e) => {
|
|
e.preventDefault();
|
|
const fd = new FormData(e.target);
|
|
await apiPost('/tickets', {
|
|
machineId: currentTicket.machineId,
|
|
title: fd.get('title'),
|
|
description: fd.get('description'),
|
|
});
|
|
e.target.reset();
|
|
location.reload();
|
|
};
|
|
}
|
|
|
|
async function init() {
|
|
const st = await guard({ activeNav: 'tickets' });
|
|
if (!st) return;
|
|
|
|
const id = new URLSearchParams(location.search).get('id');
|
|
if (!id || !UUID.test(id)) {
|
|
loadingEl.hidden = true;
|
|
badIdEl.hidden = false;
|
|
return;
|
|
}
|
|
|
|
loadingEl.hidden = true;
|
|
mainEl.hidden = false;
|
|
|
|
bindAttachmentPreview(document.body);
|
|
|
|
try {
|
|
await viewTicketDetail(id);
|
|
} catch (e) {
|
|
if (isAuthRedirectError(e)) return;
|
|
showError(e.message || 'Fehler');
|
|
}
|
|
}
|
|
|
|
init();
|