feat(org-detail): Max Nutzer inline editierbar, Org-Status in die Kopfzeile

Zwei Verbesserungen auf Nutzer-Rückmeldung.

- Max Nutzer war nach dem Anlegen einer Lizenz nirgends mehr änderbar.
  In der aktiven Lizenz-Zeile ist es jetzt ein Zahlenfeld mit
  Sofort-Speicherung (der generische Lizenz-Updater akzeptiert max_users
  mit Prüfung 1 bis 1000), die Limit-Anzeige über der Nutzerliste zieht
  mit.
- "Status" (Lizenz) direkt neben "Org-Status" war verwirrend. Der
  Org-Status sitzt jetzt als Umschalter in der Kopfzeile der
  Organisation (an der Stelle des bisherigen Abzeichens), die Tabelle
  behält nur noch Lizenz-Status und Pipeline-Sprache.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Dieser Commit ist enthalten in:
claude-dev
2026-07-25 15:46:51 +00:00
Ursprung 2c3087e389
Commit f301e22c6b
3 geänderte Dateien mit 45 neuen und 14 gelöschten Zeilen

Datei anzeigen

@@ -230,7 +230,6 @@
<th>Gültig ab</th>
<th>Gültig bis</th>
<th>Status</th>
<th>Org-Status</th>
<th>Pipeline-Sprache</th>
<th>Aktionen</th>
</tr>
@@ -1163,7 +1162,7 @@
</div>
<script src="/static/js/a11y.js?v=20260725a"></script>
<script src="/static/js/app.js?v=20260725h"></script>
<script src="/static/js/app.js?v=20260725i"></script>
<script src="/static/js/sources.js?v=20260522x2"></script>
<script src="/static/js/x-scraper.js?v=20260522a"></script>
<script src="/static/js/source-health.js?v=20260509l"></script>

Datei anzeigen

@@ -314,7 +314,13 @@ function renderOrgHeader(org) {
document.getElementById("orgDetailHeader").innerHTML = `
<div style="display:flex; align-items:flex-start; gap:10px; flex-wrap:wrap;">
<div style="flex:1; min-width:260px;">
<h2>${esc(org.name)} <span class="badge badge-${org.is_active ? 'active' : 'inactive'}" style="font-size: 12px; vertical-align: middle;">${org.is_active ? "Aktiv" : "Inaktiv"}</span></h2>
<h2 style="display:flex; align-items:center; gap:10px; flex-wrap:wrap;">${esc(org.name)}
<select onchange="setOrgActive(this.value)" title="Status der Organisation, speichert sofort"
style="width:auto; font-size:13px; padding:4px 8px;">
<option value="true"${org.is_active ? " selected" : ""}>Aktiv</option>
<option value="false"${org.is_active ? "" : " selected"}>Deaktiviert</option>
</select>
</h2>
<div class="text-secondary mt-8">Slug: ${esc(org.slug)} | Nutzer: ${org.user_count} | Lizenz: ${org.license_type || "Keine"} | Kontingent: <span id="orgKpiCredits">${esc(kpiText)}</span></div>
</div>
<button class="btn btn-secondary btn-small" onclick="renameOrg()" title="Organisation umbenennen">
@@ -375,6 +381,25 @@ function setOrgLanguage(wert) {
_saveOrgSetting({ output_language: wert }, "Pipeline-Sprache gespeichert");
}
// Max Nutzer der aktiven Lizenz direkt in der Tabelle ändern
async function setLicenseMaxUsers(licId, wert) {
const n = parseInt(wert, 10);
if (!n || n < 1 || n > 1000) {
showToast("Max Nutzer muss zwischen 1 und 1000 liegen", "error");
loadOrgLicenses(currentOrgId);
return;
}
try {
await API.put(`/api/token-usage/budget/${licId}`, { max_users: n });
showToast("Maximale Nutzerzahl gespeichert", "success");
loadOrgLicenses(currentOrgId);
loadOrgUsers(currentOrgId); // Limit-Anzeige über der Nutzerliste zieht mit
} catch (err) {
showToast(err.message, "error");
loadOrgLicenses(currentOrgId);
}
}
function renameOrg() {
const input = document.getElementById("renameOrgName");
const errEl = document.getElementById("renameOrgError");
@@ -506,17 +531,13 @@ async function loadOrgLicenses(orgId) {
const licenses = await API.get(`/api/licenses?org_id=${orgId}`);
const tbody = document.getElementById("licenseTable");
if (licenses.length === 0) {
tbody.innerHTML = '<tr><td colspan="8" class="text-muted">Keine Lizenzen</td></tr>';
tbody.innerHTML = '<tr><td colspan="7" class="text-muted">Keine Lizenzen</td></tr>';
return;
}
// Org-Status und Pipeline-Sprache sind Org-Einstellungen und stehen nur in
// der aktiven Zeile, direkt umschaltbar (Sofort-Speicherung).
// Pipeline-Sprache ist eine Org-Einstellung und steht nur in der aktiven
// Zeile (Sofort-Speicherung). Max Nutzer ist dort direkt editierbar.
const org = currentOrgData || {};
const orgZellen = `
<td><select style="width:auto; min-width:110px;" onchange="setOrgActive(this.value)">
<option value="true"${org.is_active ? " selected" : ""}>Aktiv</option>
<option value="false"${org.is_active ? "" : " selected"}>Deaktiviert</option>
</select></td>
const spracheZelle = `
<td><select style="width:auto; min-width:100px;" onchange="setOrgLanguage(this.value)" title="Ausgabesprache der KI-Pipeline für alle Nutzer dieser Organisation">
<option value="de"${(org.output_language || "de") === "de" ? " selected" : ""}>Deutsch</option>
<option value="en"${org.output_language === "en" ? " selected" : ""}>English</option>
@@ -524,11 +545,14 @@ async function loadOrgLicenses(orgId) {
tbody.innerHTML = licenses.map(l => `
<tr>
<td><span class="badge badge-${l.license_type}">${l.license_type}</span></td>
<td>${l.max_users}</td>
<td>${l.status === "active"
? `<input type="number" value="${l.max_users}" min="1" max="1000" style="width:70px;"
onchange="setLicenseMaxUsers(${l.id}, this.value)" title="Maximale Nutzerzahl, speichert sofort">`
: l.max_users}</td>
<td>${formatDate(l.valid_from)}</td>
<td>${l.valid_until ? formatDate(l.valid_until) : "Unbegrenzt"}</td>
<td><span class="badge badge-${l.status}">${l.status}</span></td>
${l.status === "active" ? orgZellen : '<td class="text-muted">–</td><td class="text-muted">–</td>'}
${l.status === "active" ? spracheZelle : '<td class="text-muted">–</td>'}
<td>
${l.status === "active" ? `
<button class="btn btn-secondary btn-small" onclick="extendLicense(${l.id})">Verlängern</button>