feat(org-detail): Slug aus der Oberfläche, Info-Zeile im Kopf entfernt

Der Slug ist eine rein technische Kennung und verschwindet aus Tabelle,
Kopfzeile und Anlege-Formular. Beim Anlegen wird er unsichtbar aus dem
Namen erzeugt, bei Kollision hängt das Portal selbstständig eine Nummer
an. Die Info-Zeile unter dem Org-Namen (Slug, Nutzer, Lizenz,
Kontingent) entfällt komplett, dieselben Werte stehen direkt darunter
in der Übersicht und der Bearbeiten-Karte.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Dieser Commit ist enthalten in:
claude-dev
2026-07-25 17:01:40 +00:00
Ursprung 15cea7cb3b
Commit 68fc1794a9
2 geänderte Dateien mit 18 neuen und 23 gelöschten Zeilen

Datei anzeigen

@@ -118,7 +118,6 @@
<thead>
<tr>
<th>Name</th>
<th>Slug</th>
<th>Nutzer</th>
<th>Lizenz</th>
<th>Kontingent</th>
@@ -523,10 +522,7 @@
<label for="newOrgName">Name</label>
<input type="text" id="newOrgName" required>
</div>
<div class="form-group">
<label for="newOrgSlug">Slug (URL-freundlich)</label>
<input type="text" id="newOrgSlug" required pattern="[a-z0-9-]+" placeholder="z.B. bundespolizei">
</div>
<input type="hidden" id="newOrgSlug">
<div class="form-group">
<label for="newOrgLanguage">Pipeline-Sprache</label>
<select id="newOrgLanguage">
@@ -1109,7 +1105,7 @@
</div>
<script src="/static/js/a11y.js?v=20260725a"></script>
<script src="/static/js/app.js?v=20260725q"></script>
<script src="/static/js/app.js?v=20260725r"></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

@@ -253,13 +253,12 @@ function orgUsageCell(orgId) {
function renderOrgTable(orgs) {
const tbody = document.getElementById("orgTable");
if (orgs.length === 0) {
tbody.innerHTML = '<tr><td colspan="7" class="text-muted">Keine Organisationen</td></tr>';
tbody.innerHTML = '<tr><td colspan="6" class="text-muted">Keine Organisationen</td></tr>';
return;
}
tbody.innerHTML = orgs.map(o => `
<tr class="org-row" onclick="openOrg(${o.id})" title="Organisation öffnen">
<td><a href="#" class="text-accent" style="text-decoration: none;" onclick="openOrg(${o.id}); return false;">${esc(o.name)}</a></td>
<td class="text-secondary">${esc(o.slug)}</td>
<td>${o.user_count}</td>
<td><span class="badge badge-${o.license_type || 'none'}">${o.license_type || "Keine"}</span></td>
<td>${orgUsageCell(o.id)}</td>
@@ -289,8 +288,6 @@ document.addEventListener("DOMContentLoaded", () => {
let currentOrgData = null; // zuletzt geladene Org (fuer Inline-Einstellungen)
function renderOrgHeader(org) {
const kpiAlt = document.getElementById("orgKpiCredits");
const kpiText = kpiAlt ? kpiAlt.textContent : "wird geladen …";
document.getElementById("orgDetailHeader").innerHTML = `
<div style="display:flex; align-items:flex-start; gap:10px; flex-wrap:wrap;">
<div style="flex:1; min-width:260px;">
@@ -306,7 +303,6 @@ function renderOrgHeader(org) {
</button>
</div>
</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">
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/></svg>
@@ -668,9 +664,13 @@ function setupForms() {
e.preventDefault();
const errEl = document.getElementById("newOrgError");
errEl.style.display = "none";
// Slug wird unsichtbar aus dem Namen erzeugt, bei Kollision haengt der
// Submit-Handler unten selbststaendig eine Nummer an.
const baseSlug = (document.getElementById("newOrgSlug").value ||
document.getElementById("newOrgName").value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")) || "organisation";
const body = {
name: document.getElementById("newOrgName").value,
slug: document.getElementById("newOrgSlug").value,
slug: baseSlug,
output_language: document.getElementById("newOrgLanguage").value || "de",
};
// Optional direkt eine Lizenz mit Credits-Kontingent mitgeben
@@ -697,8 +697,17 @@ function setupForms() {
if (warn && warn.value) lic.budget_warning_percent = parseInt(warn.value);
body.license = lic;
}
try {
let angelegt = false;
for (let i = 0; i < 8 && !angelegt; i++) {
body.slug = i === 0 ? baseSlug : baseSlug + "-" + (i + 1);
try {
await API.post("/api/orgs", body);
angelegt = true;
} catch (err) {
if (!(err.message || "").includes("Slug bereits vergeben") || i === 7) throw err;
}
}
closeModal("modalNewOrg");
document.getElementById("newOrgForm").reset();
onNewOrgLicenseToggle();
@@ -1100,16 +1109,6 @@ async function loadOrgTokenUsage(orgId) {
fillBudgetForm(budget);
// Kontingent-Kurzinfo im Org-Kopf
const kpi = document.getElementById("orgKpiCredits");
if (kpi) {
if (unlimited) kpi.textContent = "unbegrenzt";
else if (budget.credits_available) {
const pct = budget.credits_percent_used != null ? ` (${budget.credits_percent_used.toFixed(0)}% verbraucht)` : "";
kpi.textContent = `${Math.round(budget.credits_used || 0).toLocaleString("de-DE")} von ${Math.round(budget.credits_available).toLocaleString("de-DE")} Credits${pct}`;
} else kpi.textContent = "kein Kontingent";
}
const tbody = document.getElementById('tokenMonthlyTable');
if (tbody) {
tbody.innerHTML = monthly.map(function(m) {