ui: Standard-Profil kompakt in die User-Bar oben statt eigener Karte

- Mini-Select neben dem User-Namen, speichert sofort bei change
- "Meine Einstellungen"-Karte entfernt
- Selector erscheint nur, wenn ≥2 Profile verfügbar (sonst nichts zu wählen)
This commit is contained in:
root 2026-05-14 03:23:36 +00:00
parent 3a9881d36f
commit 482b344787
3 changed files with 50 additions and 52 deletions

View File

@ -34,10 +34,8 @@ const usersBody = document.getElementById("users-body");
const profileLabel = document.getElementById("profile-label"); const profileLabel = document.getElementById("profile-label");
const profileSelect = document.getElementById("profile-select"); const profileSelect = document.getElementById("profile-select");
const defaultProfileName = document.getElementById("default-profile-name"); const defaultProfileName = document.getElementById("default-profile-name");
const settingsCard = document.getElementById("settings-card"); const headerProfileWrap = document.getElementById("header-profile-wrap");
const settingsForm = document.getElementById("settings-form"); const headerProfile = document.getElementById("header-profile");
const settingsProfile = document.getElementById("settings-profile");
const settingsMsg = document.getElementById("settings-msg");
let currentUser = null; // { id, username, is_admin, default_profile } let currentUser = null; // { id, username, is_admin, default_profile }
let mode = "login"; // "login" | "setup" let mode = "login"; // "login" | "setup"
@ -146,12 +144,12 @@ function profileLabelOf(name) {
} }
function applyProfileUI() { function applyProfileUI() {
// Upload-Dropdown nur sichtbar wenn ≥2 Profile zur Auswahl stehen. // Upload-Dropdown und Header-Selector nur sichtbar wenn ≥2 Profile.
const showDropdown = availableProfiles.length > 1; const showDropdown = availableProfiles.length > 1;
profileLabel.classList.toggle("hidden", !showDropdown); profileLabel.classList.toggle("hidden", !showDropdown);
headerProfileWrap.classList.toggle("hidden", !showDropdown);
// Beide Selects befüllen for (const sel of [profileSelect, headerProfile]) {
for (const sel of [profileSelect, settingsProfile]) {
sel.innerHTML = ""; sel.innerHTML = "";
availableProfiles.forEach((p) => { availableProfiles.forEach((p) => {
const o = document.createElement("option"); const o = document.createElement("option");
@ -162,13 +160,32 @@ function applyProfileUI() {
} }
const def = currentUser.default_profile || "meeting"; const def = currentUser.default_profile || "meeting";
profileSelect.value = def; profileSelect.value = def;
settingsProfile.value = def; headerProfile.value = def;
defaultProfileName.textContent = profileLabelOf(def); defaultProfileName.textContent = profileLabelOf(def);
// Settings-Karte nur, wenn es überhaupt was auszuwählen gibt.
settingsCard.classList.toggle("hidden", !showDropdown);
} }
// Sofort-Speichern, sobald der User im Header das Standard-Profil ändert.
headerProfile.addEventListener("change", async () => {
const newDefault = headerProfile.value;
headerProfile.disabled = true;
try {
const r = await api("/api/auth/me", {
method: "PATCH",
body: JSON.stringify({ default_profile: newDefault }),
});
if (!r.ok) throw new Error("save failed");
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";
} finally {
headerProfile.disabled = false;
}
});
authForm.addEventListener("submit", async (ev) => { authForm.addEventListener("submit", async (ev) => {
ev.preventDefault(); ev.preventDefault();
authSubmit.disabled = true; authSubmit.disabled = true;
@ -417,26 +434,6 @@ usersBody.addEventListener("click", async (ev) => {
} }
}); });
settingsForm.addEventListener("submit", async (ev) => {
ev.preventDefault();
settingsMsg.textContent = "";
settingsMsg.className = "msg";
const r = await api("/api/auth/me", {
method: "PATCH",
body: JSON.stringify({ default_profile: settingsProfile.value }),
});
const data = await r.json();
if (!r.ok) {
settingsMsg.textContent = data.detail || "Speichern fehlgeschlagen";
settingsMsg.className = "msg err";
return;
}
currentUser = data;
applyProfileUI();
settingsMsg.textContent = "Gespeichert.";
settingsMsg.className = "msg ok";
});
// ─── Init ────────────────────────────────────────────────────────────────── // ─── Init ──────────────────────────────────────────────────────────────────
bootstrap(); bootstrap();
checkMac(); checkMac();

View File

@ -19,6 +19,11 @@
<span class="muted">Angemeldet als</span> <span class="muted">Angemeldet als</span>
<strong id="user-name"></strong> <strong id="user-name"></strong>
<span id="user-admin-badge" class="badge admin-badge hidden">Admin</span> <span id="user-admin-badge" class="badge admin-badge hidden">Admin</span>
<span id="header-profile-wrap" class="header-profile-wrap hidden">
<span class="muted">·</span>
<span class="muted">Profil:</span>
<select id="header-profile" class="header-profile" title="Standard-Profil — wird sofort gespeichert"></select>
</span>
<button type="button" id="logout-btn" class="btn-link">Abmelden</button> <button type="button" id="logout-btn" class="btn-link">Abmelden</button>
</div> </div>
</div> </div>
@ -71,18 +76,6 @@
<p id="upload-msg" class="msg"></p> <p id="upload-msg" class="msg"></p>
</section> </section>
<section class="card" id="settings-card">
<h2>Meine Einstellungen</h2>
<form id="settings-form" class="settings-form">
<label>
Standard-Profil
<select id="settings-profile"></select>
</label>
<button type="submit">Speichern</button>
</form>
<p id="settings-msg" class="msg"></p>
</section>
<section class="card"> <section class="card">
<h2>Jobs</h2> <h2>Jobs</h2>
<table id="jobs-table"> <table id="jobs-table">

View File

@ -253,15 +253,6 @@ footer { text-align: center; padding: 24px 16px 32px; }
.user-form { grid-template-columns: 1fr; } .user-form { grid-template-columns: 1fr; }
} }
/* Settings-Form */
.settings-form {
display: grid;
grid-template-columns: 1fr auto;
gap: 12px;
align-items: end;
max-width: 480px;
}
/* Select-Felder im selben Stil wie Inputs */ /* Select-Felder im selben Stil wie Inputs */
select { select {
background: #0f1219; background: #0f1219;
@ -273,6 +264,23 @@ select {
font-family: inherit; font-family: inherit;
} }
/* Kompakter Profil-Selector im Header */
.header-profile-wrap {
display: inline-flex;
align-items: center;
gap: 6px;
}
.header-profile {
padding: 3px 6px;
font-size: 12px;
border-radius: 5px;
background: #0f1219;
border: 1px solid var(--border);
color: var(--text);
max-width: 180px;
}
.header-profile:disabled { opacity: .5; }
/* Profile-Tag in der Job-Zeile */ /* Profile-Tag in der Job-Zeile */
.tag-profile { .tag-profile {
display: inline-block; display: inline-block;