ui: Job-Tabelle aufräumen — feste Spaltenbreiten, Download-Buttons, Fehler unter Status
- table-layout: fixed mit definierten Spaltenbreiten (Status 110px, Downloads 280px) - Status-Pill bleibt kompakt (white-space: nowrap, nur Status-Name) - Fehlertext jetzt als eigene Zeile unter dem Pill, max. 3 Zeilen, mit Tooltip - Downloads als 2x2-Grid aus Pill-Buttons (DOCX / Protokoll / Summary / Transkript) - Disabled-Buttons grau und ohne href - HTML-Escape für Titel/Filename/Fehler
This commit is contained in:
parent
955075e3fd
commit
b57f44cc73
@ -32,27 +32,50 @@ function fmtDate(s) {
|
||||
return d.toLocaleString("de-DE", { dateStyle: "short", timeStyle: "medium" });
|
||||
}
|
||||
|
||||
const DOWNLOADS = [
|
||||
{ kind: "docx", label: "DOCX", title: "Sitzungsprotokoll als Word-Dokument" },
|
||||
{ kind: "protocol", label: "Protokoll", title: "Strukturiertes Protokoll (JSON)" },
|
||||
{ kind: "summary", label: "Summary", title: "Zusammenfassung (JSON)" },
|
||||
{ kind: "transcript", label: "Transkript", title: "Rohes Transkript (JSON)" },
|
||||
];
|
||||
|
||||
function escapeHtml(s) {
|
||||
return String(s ?? "").replace(/[&<>"']/g, (c) => ({
|
||||
"&": "&", "<": "<", ">": ">", '"': """, "'": "'",
|
||||
}[c]));
|
||||
}
|
||||
|
||||
function row(job) {
|
||||
const tr = document.createElement("tr");
|
||||
const title = job.title || job.original_name;
|
||||
const dl = (kind, label) => {
|
||||
const a = document.createElement("a");
|
||||
a.textContent = label;
|
||||
a.href = `/api/jobs/${job.id}/download/${kind}`;
|
||||
if (!job.has[kind]) a.classList.add("disabled");
|
||||
return a;
|
||||
};
|
||||
|
||||
const dlCell = document.createElement("td");
|
||||
dlCell.className = "dl";
|
||||
["docx", "protocol", "summary", "transcript"].forEach((k) =>
|
||||
dlCell.appendChild(dl(k, k === "docx" ? "DOCX" : k.charAt(0).toUpperCase() + k.slice(1) + " (JSON)"))
|
||||
);
|
||||
DOWNLOADS.forEach(({ kind, label, title: tip }) => {
|
||||
const a = document.createElement("a");
|
||||
a.className = "btn-dl";
|
||||
a.textContent = label;
|
||||
a.title = tip;
|
||||
a.href = `/api/jobs/${job.id}/download/${kind}`;
|
||||
if (!job.has[kind]) {
|
||||
a.classList.add("disabled");
|
||||
a.removeAttribute("href");
|
||||
a.title = `${tip} — noch nicht verfügbar`;
|
||||
}
|
||||
dlCell.appendChild(a);
|
||||
});
|
||||
|
||||
const statusCell = `
|
||||
<span class="status-pill status-${job.status}">${escapeHtml(job.status)}</span>
|
||||
${job.error ? `<div class="status-error" title="${escapeHtml(job.error)}">${escapeHtml(job.error)}</div>` : ""}
|
||||
`;
|
||||
|
||||
tr.innerHTML = `
|
||||
<td>#${job.id}</td>
|
||||
<td>${title}<br><small class="muted">${job.original_name}</small></td>
|
||||
<td><span class="status-pill status-${job.status}">${job.status}${job.error ? " — " + job.error : ""}</span></td>
|
||||
<td><div class="bar"><div class="bar-fill" style="width:${job.progress}%"></div></div><small class="muted">${job.progress}%</small></td>
|
||||
<td><small class="muted">${fmtDate(job.updated_at)}</small></td>
|
||||
<td class="col-id">#${job.id}</td>
|
||||
<td class="col-title">${escapeHtml(title)}<br><small class="muted">${escapeHtml(job.original_name)}</small></td>
|
||||
<td class="col-status">${statusCell}</td>
|
||||
<td class="col-progress"><div class="bar"><div class="bar-fill" style="width:${job.progress}%"></div></div><small class="muted">${job.progress}%</small></td>
|
||||
<td class="col-updated"><small class="muted">${fmtDate(job.updated_at)}</small></td>
|
||||
`;
|
||||
tr.appendChild(dlCell);
|
||||
return tr;
|
||||
|
||||
@ -39,7 +39,12 @@
|
||||
<table id="jobs-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th><th>Titel / Datei</th><th>Status</th><th>Fortschritt</th><th>Aktualisiert</th><th>Downloads</th>
|
||||
<th class="col-id">#</th>
|
||||
<th class="col-title">Titel / Datei</th>
|
||||
<th class="col-status">Status</th>
|
||||
<th class="col-progress">Fortschritt</th>
|
||||
<th class="col-updated">Aktualisiert</th>
|
||||
<th class="col-dl">Downloads</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="jobs-body">
|
||||
|
||||
@ -92,26 +92,73 @@ button:disabled { opacity: .6; cursor: not-allowed; }
|
||||
background: var(--accent);
|
||||
transition: width .25s ease;
|
||||
}
|
||||
table { width: 100%; border-collapse: collapse; font-size: 14px; }
|
||||
th, td { padding: 10px 8px; text-align: left; border-bottom: 1px solid var(--border); }
|
||||
table { width: 100%; border-collapse: collapse; font-size: 14px; table-layout: fixed; }
|
||||
th, td { padding: 10px 8px; text-align: left; border-bottom: 1px solid var(--border); vertical-align: top; }
|
||||
th { color: var(--muted); font-weight: 600; font-size: 12px; text-transform: uppercase; letter-spacing: .5px; }
|
||||
|
||||
/* Spaltenbreiten — Status bleibt schmal, Downloads bekommen Platz */
|
||||
.col-id { width: 56px; }
|
||||
.col-status { width: 110px; }
|
||||
.col-progress { width: 130px; }
|
||||
.col-updated { width: 130px; }
|
||||
.col-dl { width: 280px; }
|
||||
/* col-title nimmt den Rest */
|
||||
|
||||
.col-title { word-break: break-word; }
|
||||
|
||||
.status-pill {
|
||||
display: inline-block;
|
||||
padding: 2px 10px;
|
||||
border-radius: 999px;
|
||||
font-size: 12px;
|
||||
border: 1px solid var(--border);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.status-queued { color: var(--muted); }
|
||||
.status-transcribing, .status-summarizing, .status-protocolling, .status-exporting { color: var(--warn); border-color: rgba(241,196,15,.4); }
|
||||
.status-done { color: var(--ok); border-color: rgba(46,204,113,.4); }
|
||||
.status-failed { color: var(--err); border-color: rgba(231,76,60,.4); }
|
||||
.dl a {
|
||||
|
||||
/* Fehlertext unter dem Pill — bricht um, max. 3 Zeilen */
|
||||
.status-error {
|
||||
margin-top: 6px;
|
||||
font-size: 11px;
|
||||
color: var(--err);
|
||||
line-height: 1.35;
|
||||
word-break: break-word;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Download-Buttons — sauberes 2×2-Grid, alle gleich breit */
|
||||
.dl {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 6px;
|
||||
}
|
||||
.btn-dl {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 5px 10px;
|
||||
border-radius: 6px;
|
||||
background: rgba(79,140,255,.12);
|
||||
border: 1px solid rgba(79,140,255,.4);
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
margin-right: 10px;
|
||||
font-size: 13px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
transition: background .15s ease, border-color .15s ease;
|
||||
}
|
||||
.btn-dl:hover { background: rgba(79,140,255,.22); border-color: var(--accent); }
|
||||
.btn-dl.disabled {
|
||||
color: var(--muted);
|
||||
background: transparent;
|
||||
border-color: var(--border);
|
||||
pointer-events: none;
|
||||
opacity: .55;
|
||||
}
|
||||
.dl a:hover { text-decoration: underline; }
|
||||
.dl .disabled { color: var(--muted); pointer-events: none; }
|
||||
footer { text-align: center; padding: 24px 16px 32px; }
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user