diff --git a/CLAUDE.md b/CLAUDE.md
index a3d3b06..50693cf 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -4247,6 +4247,13 @@ mit Link direkt auf die Aufgabe. Kein Kanal.
Projekt, Fällig/Zeitraum, Aufwand, Zugewiesen von, bei mehreren auch
Zuständig, Knopf „Aufgabe öffnen“). Ohne Konto/E-Mail keine Nachricht
(nur ein Log-Eintrag). Testnachricht geht an die E-Mail des Admins.
+- **Admin-Karte** (`loadAdminTeams()`): Statuszeile (eingerichtet + Host
+ bzw. „noch nicht eingerichtet“), breites mehrzeiliges Adressfeld
+ (`teamsFeldPruefen()`: Speichern erst bei https ohne Leerzeichen; bei
+ hinterlegter Adresse erst nach „Adresse ändern“ sichtbar), Schalter für
+ die Benachrichtigungen (gesperrt ohne Adresse), „Testnachricht an mich
+ senden“, „Anbindung entfernen“. Stile: Block „Admin → Teams“ am Ende von
+ `styles.css`.
- **Speicher:** Tabelle `integrationen` (`teams_webhook`,
`teams_neue_aufgaben`), bewusst NICHT `app_settings` (das darf jedes
Konto lesen) und nicht in `IMPORT_TABLES`; die Adresse geht nie an den
diff --git a/public/App.html b/public/App.html
index b82c942..fa62b98 100644
--- a/public/App.html
+++ b/public/App.html
@@ -38,7 +38,7 @@
-
+
-
+
diff --git a/public/js/admin/admin.js b/public/js/admin/admin.js
index 5d24e25..7f4d320 100644
--- a/public/js/admin/admin.js
+++ b/public/js/admin/admin.js
@@ -1113,22 +1113,66 @@ async function loadAdminTeams(){
try { d = await fetch(baseUrl() + '/admin/teams').then(r => r.json()); }
catch(e){ el.innerHTML = 'Konnte nicht geladen werden. '; return; }
if(!d || !d.ok){ el.innerHTML = 'Keine Berechtigung. '; return; }
+ // Ist schon eine Adresse hinterlegt, bleibt das Eingabefeld zu, bis man
+ // „Ändern“ wählt — die Adresse selbst kommt ohnehin nie zurück.
+ const feldOffen = !d.konfiguriert;
el.innerHTML = `
-
Status
- ${d.konfiguriert ? 'Eingerichtet ' + esc(d.host) + ' '
- : 'Nicht eingerichtet '}
- ${d.konfiguriert ? 'Neue Adresse (ersetzt die bisherige)' : 'Adresse aus dem Teams-Workflow'}
-
- Zuständige über neue Aufgaben benachrichtigen
-
-
Adresse speichern
-
Testnachricht senden
- ${d.konfiguriert ? '
Entfernen ' : ''}
+
+
+ ${d.konfiguriert
+ ? 'Eingerichtet Workflow bei ' + esc(d.host) + ' '
+ : 'Noch nicht eingerichtet Adresse aus dem Teams-Workflow unten einfügen '}
+ ${d.konfiguriert ? 'Adresse ändern ' : ''}
+
+
+
+
${d.konfiguriert ? 'Neue Workflow-Adresse' : 'Workflow-Adresse'}
+
+
+ Bleibt geheim: Nach dem Speichern wird die Adresse nicht mehr angezeigt.
+ ${d.konfiguriert ? 'Abbrechen ' : ''}
+ Speichern
+
+
+
+
+
+
+ Zuständige über neue Aufgaben benachrichtigen
+ Persönliche Nachricht mit Link zur Aufgabe
+
+
+
+ Testnachricht an mich senden
+ ${d.konfiguriert ? 'Anbindung entfernen ' : ''}
`;
}
+// teamsFeldZeigen: blendet das Eingabefeld für eine neue Adresse ein.
+function teamsFeldZeigen(){
+ const f = document.getElementById('teamsFeld');
+ if(f){ f.hidden = false; document.getElementById('teamsWebhook').focus(); }
+}
+
+// teamsFeldPruefen: Speichern erst bei einer https-Adresse; Hinweis passt sich an.
+function teamsFeldPruefen(){
+ const wert = (document.getElementById('teamsWebhook').value || '').trim();
+ const ok = /^https:\/\/\S+$/.test(wert);
+ const knopf = document.getElementById('teamsSpeichernKnopf');
+ const hinweis = document.getElementById('teamsFeldHinweis');
+ if(knopf) knopf.disabled = !ok;
+ if(hinweis){
+ hinweis.classList.toggle('ist-fehler', !!wert && !ok);
+ hinweis.textContent = !wert ? 'Bleibt geheim: Nach dem Speichern wird die Adresse nicht mehr angezeigt.'
+ : ok ? 'Sieht gut aus — jetzt speichern und danach eine Testnachricht senden.'
+ : 'Die Adresse muss mit https:// beginnen und darf keine Leerzeichen enthalten.';
+ }
+}
+
async function teamsSpeichern(patch){
if('webhook' in patch && !String(patch.webhook || '').trim()){ alert('Bitte die Adresse aus dem Teams-Workflow einfügen.'); return; }
const r = await fetch(baseUrl() + '/admin/teams', {
diff --git a/public/styles.css b/public/styles.css
index 8c592f0..547c13d 100644
--- a/public/styles.css
+++ b/public/styles.css
@@ -6232,3 +6232,60 @@ button.verlauf-weitere { white-space: nowrap; }
.kalender-anleitung summary { cursor: pointer; color: var(--accent); }
.kalender-anleitung ol { margin: 8px 0; padding-left: 20px; display: grid; gap: 6px; }
.kalender-aktionen { display: flex; gap: 8px; }
+
+/* ---------- Admin → Teams (loadAdminTeams(), js/admin/admin.js) ----------
+ Eigene Klassen statt .settings-field: das Adressfeld ist lang (≈ 300
+ Zeichen) und braucht eine breite, mehrzeilige Fläche. */
+#adminTeamsInhalt { display: grid; gap: 14px; margin: 12px 0 6px; }
+.teams-status {
+ display: flex; align-items: center; gap: 12px; padding: 12px 14px;
+ border: 1px solid var(--line); border-radius: var(--r-lg); background: var(--surface-sunk);
+}
+.teams-punkt { width: 10px; height: 10px; border-radius: 50%; background: var(--text-subtle); flex: none; }
+.teams-status.ist-an .teams-punkt { background: var(--green); box-shadow: 0 0 0 4px color-mix(in srgb, var(--green) 20%, transparent); }
+.teams-status-text { display: grid; gap: 2px; flex: 1; min-width: 0; }
+.teams-status-text b { font-size: 13.5px; }
+.teams-status-text span { font-size: 12px; color: var(--text-muted); overflow-wrap: anywhere; }
+button.teams-text-knopf, button.teams-text-knopf:hover, button.teams-text-knopf:active {
+ all: unset; cursor: pointer; font-size: 12.5px; color: var(--accent); white-space: nowrap; padding: 4px 2px;
+}
+button.teams-text-knopf:hover { text-decoration: underline; }
+button.teams-text-knopf:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
+button.teams-text-knopf.ist-gefahr { color: var(--red); }
+
+.teams-feld[hidden] { display: none; }
+.teams-feld { display: grid; gap: 6px; }
+.teams-feld label { font-size: 12px; font-weight: 600; color: var(--text-muted); }
+.teams-feld textarea {
+ width: 100%; box-sizing: border-box; resize: vertical; min-height: 64px;
+ padding: 10px 12px; border: 1px solid var(--line) !important; border-radius: var(--r-md) !important;
+ background: var(--surface) !important; color: var(--text); box-shadow: none !important;
+ font: 12px/1.45 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; word-break: break-all;
+}
+.teams-feld textarea:focus { outline: none; border-color: var(--accent) !important; box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent) !important; }
+.teams-feld-fuss { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
+.teams-feld-hinweis { flex: 1; min-width: 200px; font-size: 12px; color: var(--text-subtle); }
+.teams-feld-hinweis.ist-fehler { color: var(--red); }
+
+/* Schalter statt nackter Checkbox */
+.teams-schalter { display: flex; align-items: center; gap: 12px; cursor: pointer; }
+.teams-schalter input { position: absolute; opacity: 0; width: 1px; height: 1px; }
+.teams-schalter-bahn {
+ flex: none; width: 38px; height: 22px; border-radius: 11px; background: var(--line); position: relative;
+ transition: background var(--dur, 150ms);
+}
+.teams-schalter-bahn::after {
+ content: ''; position: absolute; top: 3px; left: 3px; width: 16px; height: 16px; border-radius: 50%;
+ background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.3); transition: transform var(--dur, 150ms) var(--ease-out, ease);
+}
+.teams-schalter input:checked + .teams-schalter-bahn { background: var(--accent); }
+.teams-schalter input:checked + .teams-schalter-bahn::after { transform: translateX(16px); }
+.teams-schalter input:focus-visible + .teams-schalter-bahn { outline: 2px solid var(--accent); outline-offset: 2px; }
+.teams-schalter input:disabled + .teams-schalter-bahn { opacity: .45; }
+.teams-schalter:has(input:disabled) { cursor: default; }
+.teams-schalter-text { display: grid; gap: 2px; }
+.teams-schalter-text b { font-size: 13px; font-weight: 600; }
+.teams-schalter-text span { font-size: 12px; color: var(--text-muted); }
+.teams-schalter:has(input:disabled) .teams-schalter-text { opacity: .55; }
+
+.teams-aktionen { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }