ui: Upload-Form & Jobs-Liste aufgeräumt

Upload-Form:
- Title-Label nicht mehr mit "(optional)" suffix — Hinweis wandert in
  den Placeholder. Labels haben jetzt einheitliches Spalten-Layout
  (flex-column + align-items: end), Inputs sind bündig
- "Standard: X" Hinweis unter dem Profil entfernt (Dropdown ist
  selbst-erklärend, das Header-Dropdown zeigt's eh schon)

Jobs-Liste:
- Wenn kein Titel gesetzt ist, wird der Dateiname nur 1× als Titel
  gezeigt (vorher: groß als Titel + nochmal klein darunter)
- Profile/Modell-Tags in eigene flex-Zeile statt margin-left-Hack
- Dateiname-Zeile mit Ellipsis bei zu langen Namen
- Tags 1px kleiner, weniger Padding
- Datumsformat: heute → nur HH:MM, sonst dd.mm HH:MM (statt langem
  dd.mm.yyyy HH:MM:SS) — col-updated von 120px auf 78px geschrumpft
- col-status von 110px auf 170px → Aktions-Buttons stapeln weniger

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
root 2026-05-16 20:34:54 +00:00
parent 79d6cfec74
commit f03d46afb5
3 changed files with 71 additions and 23 deletions

View File

@ -33,7 +33,6 @@ const usersBody = document.getElementById("users-body");
const profileLabel = document.getElementById("profile-label");
const profileSelect = document.getElementById("profile-select");
const defaultProfileName = document.getElementById("default-profile-name");
const headerProfileWrap = document.getElementById("header-profile-wrap");
const headerProfile = document.getElementById("header-profile");
const headerModelWrap = document.getElementById("header-model-wrap");
@ -77,7 +76,12 @@ function escapeHtml(s) {
function fmtDate(s) {
const d = new Date(s);
return d.toLocaleString("de-DE", { dateStyle: "short", timeStyle: "medium" });
const now = new Date();
const sameDay = d.toDateString() === now.toDateString();
if (sameDay) {
return d.toLocaleTimeString("de-DE", { hour: "2-digit", minute: "2-digit" });
}
return d.toLocaleString("de-DE", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" });
}
function fmtSecs(sec) {
@ -228,7 +232,6 @@ function applyProfileUI() {
const def = currentUser.default_profile || "meeting";
profileSelect.value = def;
headerProfile.value = def;
defaultProfileName.textContent = profileLabelOf(def);
}
async function loadModels() {
@ -306,7 +309,6 @@ headerProfile.addEventListener("change", async () => {
currentUser = await r.json();
// Upload-Dropdown synchron mitziehen
profileSelect.value = currentUser.default_profile;
defaultProfileName.textContent = profileLabelOf(currentUser.default_profile);
} catch (e) {
alert("Konnte Standard-Profil nicht speichern.");
headerProfile.value = currentUser.default_profile || "meeting";
@ -376,6 +378,9 @@ async function checkMac() {
// ─── Jobs ──────────────────────────────────────────────────────────────────
function row(job) {
const tr = document.createElement("tr");
// Wenn kein Titel gesetzt ist, zeigen wir den Dateinamen nur EINMAL als Titel —
// sonst doppelt (großer Titel + kleine muted-Zeile) und unnötig hässlich.
const hasRealTitle = !!job.title;
const title = job.title || job.original_name;
const dlCell = document.createElement("td");
@ -395,7 +400,7 @@ function row(job) {
});
const ownerLine = currentUser?.is_admin && job.owner_username
? `<br><small class="muted">von ${escapeHtml(job.owner_username)}</small>`
? `<div class="job-owner muted">von ${escapeHtml(job.owner_username)}</div>`
: "";
// Profile-Tag nur sichtbar wenn ≥2 Profile in der Auswahl ODER es ein anderes als "meeting" ist.
@ -423,9 +428,19 @@ function row(job) {
${runsButtonHtml(job)}
`;
// Dateiname nur dann als zweite Zeile, wenn er sich vom angezeigten Titel unterscheidet.
const fileLine = hasRealTitle
? `<div class="job-filename muted">${escapeHtml(job.original_name)}</div>`
: "";
tr.innerHTML = `
<td class="col-id">#${job.id}</td>
<td class="col-title">${escapeHtml(title)} ${profileTag} ${modelTag}<br><small class="muted">${escapeHtml(job.original_name)}</small>${ownerLine}</td>
<td class="col-title">
<div class="job-title">${escapeHtml(title)}</div>
<div class="job-tags">${profileTag}${modelTag}</div>
${fileLine}
${ownerLine}
</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>

View File

@ -95,11 +95,10 @@
<label id="profile-label" class="hidden">
Verarbeitungs-Profil
<select id="profile-select"></select>
<small class="muted desktop-only">Standard: <span id="default-profile-name"></span></small>
</label>
<label>
Titel <span class="muted">(optional)</span>
<input type="text" name="title" id="title" placeholder="z. B. Monatsbesprechung" />
Titel
<input type="text" name="title" id="title" placeholder="optional, z. B. Monatsbesprechung" />
</label>
</div>

View File

@ -96,15 +96,38 @@ table { width: 100%; border-collapse: collapse; font-size: 14px; table-layout: f
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: 120px; }
.col-updated { width: 120px; }
/* Spaltenbreiten Status bekommt Platz für die Aktions-Buttons,
Updated wird schmaler weil das neue Format kürzer ist (Uhrzeit/dd.mm hh:mm) */
.col-id { width: 52px; }
.col-status { width: 170px; }
.col-progress { width: 110px; }
.col-updated { width: 78px; }
.col-dl { width: 320px; }
/* col-title nimmt den Rest */
.col-title { word-break: break-word; }
.col-title .job-title {
font-weight: 500;
line-height: 1.35;
font-size: 14px;
}
.col-title .job-tags {
margin-top: 3px;
display: flex;
flex-wrap: wrap;
gap: 4px;
}
.col-title .job-tags:empty { display: none; }
.col-title .job-filename {
margin-top: 4px;
font-size: 11.5px;
font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 100%;
}
.col-title .job-owner { margin-top: 2px; font-size: 11.5px; }
.status-pill {
display: inline-block;
@ -368,6 +391,12 @@ select {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
align-items: end; /* Labels unten bündig → Inputs auf gleicher Höhe */
}
.upload-meta > label {
display: flex;
flex-direction: column;
gap: 4px; /* einheitlicher Abstand zwischen Label-Text und Control */
}
.audio-file-details summary {
cursor: pointer;
@ -547,34 +576,39 @@ select {
.btn-retry:hover { background: rgba(241,196,15,.22); text-decoration: none; }
.btn-retry:disabled { opacity: .55; cursor: not-allowed; }
/* Profile-Tag in der Job-Zeile */
/* Profile-Tag in der Job-Zeile kleiner & ohne Außenabstand,
weil sie jetzt in einem eigenen .job-tags-Container mit gap leben */
.tag-profile {
display: inline-block;
padding: 1px 8px;
margin-left: 6px;
padding: 1px 7px;
border-radius: 4px;
background: rgba(46,204,113,.12);
border: 1px solid rgba(46,204,113,.4);
color: var(--ok);
font-size: 11px;
font-size: 10.5px;
text-transform: lowercase;
vertical-align: middle;
line-height: 1.5;
}
/* Modell-Tag — gleicher Stil wie Profile-Tag, andere Farbe (lila) */
.tag-model {
display: inline-block;
padding: 1px 8px;
margin-left: 4px;
padding: 1px 7px;
border-radius: 4px;
background: rgba(155,89,182,.10);
border: 1px solid rgba(155,89,182,.4);
color: #b07cd6;
font-size: 11px;
font-size: 10.5px;
font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
vertical-align: middle;
line-height: 1.5;
}
/* In der mobilen Karte (jc-title) sitzen Tags inline neben dem Titeltext
dort brauchen sie etwas Luft nach links. In der Desktop-Tabelle stehen sie
im eigenen .job-tags-Container mit gap, dort kein margin nötig. */
.jc-title .tag-profile,
.jc-title .tag-model { margin-left: 6px; }
/* Modal für Modell-Auswahl beim Reprocess */
dialog.model-picker {
border: 1px solid rgba(255,255,255,.15);