Files
2026-11-01 11:15:09 +00:00

40 lines
2.7 KiB
JavaScript

// Gemeinsame Bausteine der Ansichten.
import { h, icon, clear, errText } from './dom.js';
import { STATUS_LABELS, safeColor, timeAgo, initials } from './core.js';
export const spinner = () => h('div', { class: 'spinner', role: 'status', 'aria-label': 'Lädt' });
export const statusPill = (status) => h('span', { class: `pill ${status}` }, STATUS_LABELS[status] || status);
export const prioPill = (p) => (p ? h('span', { class: 'pill prio', style: { background: safeColor(p.color) || '#64748b' } }, p.name) : null);
export const avatar = (name, cust = false) => h('span', { class: `avatar${cust ? ' cust' : ''}`, title: name || '' }, initials(name));
/** Eine Ticketzeile. onOpen(ticket) wird beim Klick aufgerufen. */
export function ticketCard(t, onOpen) {
const color = safeColor(t.priority?.color) || 'var(--muted)';
const due = t.isOverdue ? h('span', { class: 'over' }, t.dueLabel || 'überfällig') : (t.dueLabel ? h('span', {}, t.dueLabel) : null);
return h('li', {}, h('button', { class: `tk${t.isOverdue ? ' is-over' : ''}`, onclick: () => onOpen(t), title: `Ticket ${t.number} öffnen` },
h('span', { class: 'bar', style: { background: color } }),
h('div', { class: 'row1' }, h('div', { class: 'subj' }, t.subject)),
h('div', { class: 'row2 meta' }, statusPill(t.status), h('span', {}, `#${t.number}`), h('span', {}, t.requesterName || '—'), due),
h('div', { class: 'row2 meta' }, h('span', {}, t.department?.name || ''), t.assignedStaff ? h('span', {}, `· ${t.assignedStaff.name}`) : h('span', {}, '· nicht zugewiesen'), h('span', {}, `· ${timeAgo(t.updatedAt)}`),
t.following ? icon('eye', 12) : null, t.snoozedUntil ? icon('clock', 12) : null)));
}
export function ticketList(items, onOpen, emptyText = 'Keine Tickets.') {
if (!items.length) return h('div', { class: 'empty' }, emptyText);
return h('ul', { class: 'tickets' }, items.map((t) => ticketCard(t, onOpen)));
}
/** Zeigt einen Fehler im Container; bei abgelaufener Anmeldung zurück zur Anmeldung. */
export function showError(ctx, host, e) {
if (e && e.status === 401) { ctx.logoutExpired(); return; }
clear(host).append(h('div', { class: 'msg err', role: 'alert' }, errText(e)), h('div', { class: 'actions', style: { marginTop: '10px' } }, h('button', { class: 'btn small', onclick: () => ctx.reload() }, icon('refresh', 14), 'Erneut versuchen')));
}
/** Ein Segmentschalter: options = [[wert, beschriftung], …] */
export function segmented(options, current, onPick) {
return h('div', { class: 'seg', role: 'group' }, options.map(([v, label]) => h('button', { type: 'button', class: v === current ? 'on' : '', onclick: () => onPick(v) }, label)));
}
export function field(label, control) { return h('label', {}, label, control); }