Workspace: Aufgaben bearbeiten, Personenverwaltung im Browser
Aufgaben: - Bearbeiten-Dialog (Titel, Beschreibung, Prioritaet, Frist, Zuordnung). Als natives <dialog>: Fokusfang, Esc zum Schliessen und Abdunklung ohne eigenen Code. - Loeschen nur fuer Management, mit Rueckfrage und Protokolleintrag. Das Konzept will, dass Erledigtes stehen bleibt -- Loeschen ist der Ausnahmefall fuer Fehleintraege, nicht der normale Abschluss. Personen (/workspace/personen.html, nur Management): - Anlegen, Code erneuern, sperren/entsperren, Protokollansicht - Der Code wird genau einmal in der Antwort zurueckgegeben, nie gespeichert; beim Schliessen auch aus dem Dokument entfernt - Selbstschutz: niemand kann sich selbst sperren, und das letzte aktive Management laesst sich nicht sperren -- sonst kaeme niemand mehr hinein - Code fuer sich selbst tauschen nur mit ausdruecklicher Bestaetigung, weil es die eigene Sitzung sofort beendet Zwei Fehler, die beim Testen aufgefallen sind: 1. Rollenpruefung fehlte beim Ausliefern der Seiten. Ein Creator bekam personen.html mit HTTP 200 -- die Schnittstellen wiesen ihn zwar ab, das Geruest der Seite war aber sichtbar. GESCHUETZT ist jetzt eine Zuordnung Pfad -> erlaubte Rollen statt einer blossen Liste. 2. Das Protokoll nannte den falschen Verursacher. personAnlegen trug die NEU ANGELEGTE Person als person_id ein, der Eintrag las sich also so, als haette sie sich selbst angelegt. Akteur und Betroffener sind jetzt getrennt: Akteur in person_id, Betroffener im Text. Ueber die Kommandozeile angelegte Personen zeigen korrekt keinen Akteur.
This commit is contained in:
@@ -129,3 +129,34 @@
|
||||
|
||||
@media (max-width: 1080px) { .brett { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
||||
@media (max-width: 620px) { .brett { grid-template-columns: 1fr; } }
|
||||
|
||||
/* ---------- Bearbeiten-Dialog ------------------------------------------ */
|
||||
|
||||
.dialog {
|
||||
width: min(560px, calc(100vw - 32px));
|
||||
padding: 0; border: 1px solid var(--rand-hell);
|
||||
border-radius: var(--radius);
|
||||
background: #0b1018;
|
||||
color: var(--text);
|
||||
box-shadow: var(--schatten);
|
||||
}
|
||||
.dialog::backdrop { background: rgba(2, 4, 8, .68); backdrop-filter: blur(3px); }
|
||||
|
||||
.neu--blank { margin: 0; border: 0; background: none; padding: 22px; }
|
||||
|
||||
.dialog__titel { margin: 0 0 18px; font-size: 1.12rem; font-weight: 700; }
|
||||
|
||||
.dialog__knoepfe { display: flex; gap: 10px; align-items: center; margin-top: 16px; flex-wrap: wrap; }
|
||||
.dialog__knoepfe #b-loeschen { margin-left: auto; }
|
||||
|
||||
.abmelden--warnung { color: var(--warn); border-color: rgba(255, 154, 162, .35); }
|
||||
.abmelden--warnung:hover { background: rgba(255, 154, 162, .1); border-color: rgba(255, 154, 162, .6); }
|
||||
|
||||
.karte__stift {
|
||||
margin-left: auto; padding: 3px 8px;
|
||||
background: none; border: 0; border-radius: 7px;
|
||||
color: var(--text-still); font: inherit; font-size: .75rem; cursor: pointer;
|
||||
}
|
||||
.karte__stift:hover { background: rgba(255, 255, 255, .07); color: var(--text-leise); }
|
||||
.karte__kopf { display: flex; align-items: flex-start; gap: 8px; }
|
||||
.karte__kopf .karte__titel { flex: 1 1 auto; }
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/* ===================================================================
|
||||
Personenverwaltung.
|
||||
=================================================================== */
|
||||
|
||||
.person {
|
||||
display: grid; gap: 4px 16px; align-items: center;
|
||||
grid-template-columns: 1fr auto;
|
||||
padding: 14px 16px; margin-bottom: 9px;
|
||||
background: rgba(255, 255, 255, .03);
|
||||
border: 1px solid var(--rand);
|
||||
border-radius: var(--radius-klein);
|
||||
}
|
||||
.person[data-aktiv="nein"] { opacity: .58; }
|
||||
|
||||
.person__name { font-weight: 600; font-size: .98rem; }
|
||||
|
||||
.person__zeile {
|
||||
display: flex; flex-wrap: wrap; gap: 8px;
|
||||
margin-top: 2px; font-size: .79rem; color: var(--text-still);
|
||||
}
|
||||
|
||||
.marke-rolle {
|
||||
padding: 1px 9px; border-radius: 999px;
|
||||
font-size: .7rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
|
||||
}
|
||||
.marke-rolle[data-rolle="admin"] { background: rgba(63, 189, 245, .16); color: #7fd2fb; }
|
||||
.marke-rolle[data-rolle="creator"] { background: rgba(138, 118, 255, .16); color: #b3a6ff; }
|
||||
.marke-rolle[data-rolle="scout"] { background: rgba(77, 171, 119, .16); color: #79d1a2; }
|
||||
|
||||
.person__knoepfe { display: flex; gap: 7px; flex-wrap: wrap; justify-content: flex-end; }
|
||||
|
||||
/* ---------- Code-Anzeige ---------------------------------------------- */
|
||||
|
||||
.code-kasten {
|
||||
margin: 0 0 26px; padding: 20px 22px;
|
||||
background: linear-gradient(100deg, rgba(63, 189, 245, .12), rgba(138, 118, 255, .09));
|
||||
border: 1px solid rgba(63, 189, 245, .5);
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
.code-kasten__schild {
|
||||
margin: 0 0 8px; font-size: .78rem; font-weight: 600;
|
||||
letter-spacing: .06em; text-transform: uppercase; color: var(--text-leise);
|
||||
}
|
||||
.code-kasten__code {
|
||||
margin: 0 0 12px;
|
||||
font-family: "Cascadia Mono", Consolas, "SF Mono", Menlo, monospace;
|
||||
font-size: clamp(1.3rem, 3.4vw, 1.9rem); font-weight: 700;
|
||||
letter-spacing: .12em; color: #eaf4fd;
|
||||
user-select: all; /* ein Klick markiert den ganzen Code */
|
||||
word-break: break-all;
|
||||
}
|
||||
.code-kasten__hinweis { margin: 0 0 14px; font-size: .85rem; color: var(--text-leise); }
|
||||
|
||||
/* ---------- Protokoll --------------------------------------------------- */
|
||||
|
||||
.protokoll-block { margin-top: 44px; }
|
||||
|
||||
.protokoll-zeile {
|
||||
display: grid; gap: 10px;
|
||||
grid-template-columns: 128px 190px 1fr auto;
|
||||
padding: 7px 10px;
|
||||
font-size: .8rem; color: var(--text-leise);
|
||||
border-bottom: 1px solid rgba(150, 186, 220, .08);
|
||||
}
|
||||
.protokoll-zeile:first-child { border-top: 1px solid rgba(150, 186, 220, .08); }
|
||||
.protokoll-zeit { color: var(--text-still); font-variant-numeric: tabular-nums; }
|
||||
.protokoll-ip { color: var(--text-still); font-variant-numeric: tabular-nums; }
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.person { grid-template-columns: 1fr; }
|
||||
.person__knoepfe { justify-content: flex-start; margin-top: 10px; }
|
||||
.protokoll-zeile { grid-template-columns: 1fr; gap: 2px; padding: 10px; }
|
||||
}
|
||||
@@ -48,7 +48,15 @@
|
||||
function karte(a) {
|
||||
const k = el('article', 'karte');
|
||||
k.dataset.prio = a.prioritaet;
|
||||
k.append(el('h3', 'karte__titel', a.titel));
|
||||
|
||||
const kopf = el('div', 'karte__kopf');
|
||||
kopf.append(el('h3', 'karte__titel', a.titel));
|
||||
const stift = el('button', 'karte__stift', 'bearbeiten');
|
||||
stift.type = 'button';
|
||||
stift.setAttribute('aria-label', `${a.titel} bearbeiten`);
|
||||
stift.addEventListener('click', () => dialogOeffnen(a));
|
||||
kopf.append(stift);
|
||||
k.append(kopf);
|
||||
if (a.beschreibung) k.append(el('p', 'karte__text', a.beschreibung));
|
||||
|
||||
const zeile = el('div', 'karte__zeile');
|
||||
@@ -178,6 +186,70 @@
|
||||
} catch { /* bereits umgeleitet */ } finally { knopfEl.disabled = false; }
|
||||
});
|
||||
|
||||
/* ---------- Bearbeiten ------------------------------------------------ */
|
||||
|
||||
const dialog = $('bearbeiten-dialog');
|
||||
let bearbeitet = null;
|
||||
|
||||
function dialogOeffnen(a) {
|
||||
bearbeitet = a;
|
||||
$('b-titel').value = a.titel;
|
||||
$('b-text').value = a.beschreibung || '';
|
||||
$('b-prio').value = a.prioritaet;
|
||||
$('b-frist').value = a.frist || '';
|
||||
$('b-fehler').textContent = '';
|
||||
if (ich.rolle === 'admin') {
|
||||
$('b-creator').value = a.creator_id ? String(a.creator_id) : '';
|
||||
$('b-verant').value = a.verantwortlich_id ? String(a.verantwortlich_id) : '';
|
||||
$('b-loeschen').hidden = false;
|
||||
}
|
||||
dialog.showModal();
|
||||
$('b-titel').focus();
|
||||
}
|
||||
|
||||
$('b-abbrechen').addEventListener('click', () => dialog.close());
|
||||
|
||||
$('b-speichern').addEventListener('click', async () => {
|
||||
const daten = {
|
||||
titel: $('b-titel').value,
|
||||
beschreibung: $('b-text').value,
|
||||
prioritaet: $('b-prio').value,
|
||||
frist: $('b-frist').value,
|
||||
};
|
||||
if (ich.rolle === 'admin') {
|
||||
daten.creator_id = $('b-creator').value || null;
|
||||
daten.verantwortlich_id = $('b-verant').value || null;
|
||||
}
|
||||
$('b-speichern').disabled = true;
|
||||
try {
|
||||
const a = await hole(`/workspace/api/aufgaben/${bearbeitet.id}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(daten),
|
||||
});
|
||||
if (!a.ok) {
|
||||
$('b-fehler').textContent = (await a.json().catch(() => ({}))).fehler || 'Speichern ging nicht.';
|
||||
return;
|
||||
}
|
||||
dialog.close();
|
||||
await laden();
|
||||
} catch { /* umgeleitet */ } finally { $('b-speichern').disabled = false; }
|
||||
});
|
||||
|
||||
$('b-loeschen').addEventListener('click', async () => {
|
||||
if (!confirm(`Aufgabe wirklich loeschen?
|
||||
|
||||
"${bearbeitet.titel}"
|
||||
|
||||
Das laesst sich nicht rueckgaengig machen. Erledigte Aufgaben sollten normalerweise stehen bleiben.`)) return;
|
||||
try {
|
||||
const a = await hole(`/workspace/api/aufgaben/${bearbeitet.id}`, { method: 'DELETE' });
|
||||
if (!a.ok) { $('b-fehler').textContent = 'Loeschen ging nicht.'; return; }
|
||||
dialog.close();
|
||||
await laden();
|
||||
} catch { /* umgeleitet */ }
|
||||
});
|
||||
|
||||
/* ---------- Start ----------------------------------------------------- */
|
||||
|
||||
$('abmelden').addEventListener('click', async () => {
|
||||
@@ -198,10 +270,12 @@
|
||||
if (ich.rolle === 'admin') {
|
||||
$('feld-creator').hidden = false;
|
||||
$('feld-verant').hidden = false;
|
||||
$('b-feld-creator').hidden = false;
|
||||
$('b-feld-verant').hidden = false;
|
||||
try {
|
||||
const { personen } = await (await hole('/workspace/api/personen')).json();
|
||||
for (const p of personen) {
|
||||
for (const ziel of ['f-creator', 'f-verant']) {
|
||||
for (const ziel of ['f-creator', 'f-verant', 'b-creator', 'b-verant']) {
|
||||
const o = el('option', null, `${p.name} (${p.rolle})`);
|
||||
o.value = String(p.id);
|
||||
$(ziel).append(o);
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
/* ===================================================================
|
||||
Personen und Zugangscodes verwalten. Nur für Management -- der Server
|
||||
liefert allen anderen ohnehin 404 auf diese Schnittstellen.
|
||||
=================================================================== */
|
||||
(() => {
|
||||
'use strict';
|
||||
|
||||
const $ = (id) => document.getElementById(id);
|
||||
const ROLLENNAME = { admin: 'Management', creator: 'Creator', scout: 'Scout' };
|
||||
let ich = null;
|
||||
|
||||
const el = (tag, klasse, text) => {
|
||||
const k = document.createElement(tag);
|
||||
if (klasse) k.className = klasse;
|
||||
if (text !== undefined) k.textContent = text;
|
||||
return k;
|
||||
};
|
||||
|
||||
const melde = (t) => { $('fehler').textContent = t || ''; };
|
||||
|
||||
async function hole(pfad, optionen = {}) {
|
||||
const a = await fetch(pfad, { credentials: 'same-origin', ...optionen });
|
||||
if (a.status === 401) { location.assign('/workspace/'); throw new Error('abgemeldet'); }
|
||||
if (a.status === 404 && pfad.includes('/verwaltung/')) {
|
||||
location.assign('/workspace/start.html'); throw new Error('nicht erlaubt');
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
const datum = (iso) => (iso ? iso.slice(0, 16).replace('T', ' ') : '—');
|
||||
|
||||
/* ---------- Code anzeigen -------------------------------------------- */
|
||||
|
||||
function zeigeCode(p) {
|
||||
$('code-name').textContent = `${p.name} · ${ROLLENNAME[p.rolle] || p.rolle}`;
|
||||
$('code-wert').textContent = p.code;
|
||||
$('code-kasten').hidden = false;
|
||||
$('code-kasten').scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
|
||||
$('code-schliessen').addEventListener('click', () => {
|
||||
$('code-kasten').hidden = true;
|
||||
/* Den Code auch aus dem Dokument entfernen, damit er nicht in einer
|
||||
Bildschirmaufnahme oder im Verlauf hängen bleibt. */
|
||||
$('code-wert').textContent = '';
|
||||
});
|
||||
|
||||
$('code-kopieren').addEventListener('click', async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText($('code-wert').textContent);
|
||||
$('code-kopieren').textContent = 'Kopiert ✓';
|
||||
setTimeout(() => { $('code-kopieren').textContent = 'Kopieren'; }, 1800);
|
||||
} catch {
|
||||
melde('Kopieren ging nicht — bitte von Hand markieren.');
|
||||
}
|
||||
});
|
||||
|
||||
/* ---------- Liste ----------------------------------------------------- */
|
||||
|
||||
function zeile(p) {
|
||||
const k = el('div', 'person');
|
||||
k.dataset.aktiv = p.aktiv ? 'ja' : 'nein';
|
||||
|
||||
const links = el('div');
|
||||
links.append(el('div', 'person__name', p.name + (p.id === ich.id ? ' (du)' : '')));
|
||||
|
||||
const z = el('div', 'person__zeile');
|
||||
const rolle = el('span', 'marke-rolle', ROLLENNAME[p.rolle] || p.rolle);
|
||||
rolle.dataset.rolle = p.rolle;
|
||||
z.append(rolle);
|
||||
if (!p.aktiv) z.append(el('span', null, 'gesperrt'));
|
||||
z.append(el('span', null, `letzte Anmeldung: ${datum(p.letzter_login)}`));
|
||||
z.append(el('span', null, `offene Aufgaben: ${p.offene_aufgaben}`));
|
||||
if (p.sitzungen) z.append(el('span', null, `${p.sitzungen} offene Sitzung(en)`));
|
||||
links.append(z);
|
||||
|
||||
const knoepfe = el('div', 'person__knoepfe');
|
||||
knoepfe.append(tuKnopf('Neuer Code', async () => {
|
||||
const eigen = p.id === ich.id;
|
||||
const frage = eigen
|
||||
? `Neuer Code für dich selbst?\n\nDeine aktuelle Sitzung wird sofort beendet und du musst dich mit dem neuen Code neu anmelden.`
|
||||
: `Neuer Code für ${p.name}?\n\nDer alte Code gilt dann nicht mehr, offene Sitzungen werden beendet.`;
|
||||
if (!confirm(frage)) return;
|
||||
const a = await hole(`/workspace/api/verwaltung/personen/${p.id}/code`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(eigen ? { auch_mich: true } : {}),
|
||||
});
|
||||
if (!a.ok) { melde('Neuer Code hat nicht geklappt.'); return; }
|
||||
zeigeCode(await a.json());
|
||||
await laden();
|
||||
}));
|
||||
|
||||
if (p.id !== ich.id) {
|
||||
knoepfe.append(tuKnopf(p.aktiv ? 'Sperren' : 'Entsperren', async () => {
|
||||
if (p.aktiv && !confirm(`${p.name} sperren?\n\nOffene Sitzungen werden sofort beendet.`)) return;
|
||||
const a = await hole(`/workspace/api/verwaltung/personen/${p.id}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ aktiv: !p.aktiv }),
|
||||
});
|
||||
if (!a.ok) { melde((await a.json().catch(() => ({}))).fehler || 'Ging nicht.'); return; }
|
||||
await laden();
|
||||
}));
|
||||
}
|
||||
|
||||
k.append(links, knoepfe);
|
||||
return k;
|
||||
}
|
||||
|
||||
function tuKnopf(text, tu) {
|
||||
const b = el('button', 'schritt', text);
|
||||
b.type = 'button';
|
||||
b.addEventListener('click', async () => {
|
||||
b.disabled = true; melde('');
|
||||
try { await tu(); } catch { /* umgeleitet */ } finally { b.disabled = false; }
|
||||
});
|
||||
return b;
|
||||
}
|
||||
|
||||
async function laden() {
|
||||
const ziel = $('liste');
|
||||
try {
|
||||
const a = await hole('/workspace/api/verwaltung/personen');
|
||||
if (!a.ok) { melde('Personen konnten nicht geladen werden.'); return; }
|
||||
const { personen } = await a.json();
|
||||
ziel.textContent = '';
|
||||
ziel.setAttribute('aria-busy', 'false');
|
||||
for (const p of personen) ziel.append(zeile(p));
|
||||
await protokollLaden();
|
||||
} catch { /* umgeleitet */ }
|
||||
}
|
||||
|
||||
async function protokollLaden() {
|
||||
try {
|
||||
const a = await hole('/workspace/api/verwaltung/protokoll?anzahl=25');
|
||||
if (!a.ok) return;
|
||||
const { eintraege } = await a.json();
|
||||
const ziel = $('protokoll');
|
||||
ziel.textContent = '';
|
||||
for (const e of eintraege) {
|
||||
const z = el('div', 'protokoll-zeile');
|
||||
z.append(el('span', 'protokoll-zeit', datum(e.zeitpunkt)));
|
||||
z.append(el('span', null, e.aktion));
|
||||
z.append(el('span', null, [e.wer, e.detail].filter(Boolean).join(' · ')));
|
||||
z.append(el('span', 'protokoll-ip', e.ip || ''));
|
||||
ziel.append(z);
|
||||
}
|
||||
} catch { /* umgeleitet */ }
|
||||
}
|
||||
|
||||
/* ---------- Anlegen ---------------------------------------------------- */
|
||||
|
||||
const umschalten = (auf) => {
|
||||
$('neu').hidden = !auf; $('neu-oeffnen').hidden = auf;
|
||||
if (auf) $('f-name').focus();
|
||||
};
|
||||
$('neu-oeffnen').addEventListener('click', () => umschalten(true));
|
||||
$('neu-abbrechen').addEventListener('click', () => {
|
||||
$('neu').reset(); $('neu-fehler').textContent = ''; umschalten(false);
|
||||
});
|
||||
|
||||
$('neu').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
$('neu-fehler').textContent = '';
|
||||
const knopf = $('neu-speichern');
|
||||
knopf.disabled = true;
|
||||
try {
|
||||
const a = await hole('/workspace/api/verwaltung/personen', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name: $('f-name').value, rolle: $('f-rolle').value }),
|
||||
});
|
||||
if (!a.ok) {
|
||||
$('neu-fehler').textContent = (await a.json().catch(() => ({}))).fehler || 'Ging nicht.';
|
||||
return;
|
||||
}
|
||||
$('neu').reset(); umschalten(false);
|
||||
zeigeCode(await a.json());
|
||||
await laden();
|
||||
} catch { /* umgeleitet */ } finally { knopf.disabled = false; }
|
||||
});
|
||||
|
||||
/* ---------- Start ------------------------------------------------------- */
|
||||
|
||||
$('abmelden').addEventListener('click', async () => {
|
||||
try { await fetch('/workspace/api/abmelden', { method: 'POST', credentials: 'same-origin' }); }
|
||||
catch { /* egal */ }
|
||||
location.assign('/workspace/');
|
||||
});
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const a = await hole('/workspace/api/ich');
|
||||
if (!a.ok) { location.assign('/workspace/'); return; }
|
||||
ich = await a.json();
|
||||
} catch { return; }
|
||||
if (ich.rolle !== 'admin') { location.assign('/workspace/start.html'); return; }
|
||||
$('wer').textContent = `${ich.name} · ${ich.rolle}`;
|
||||
await laden();
|
||||
})();
|
||||
})();
|
||||
@@ -17,6 +17,7 @@
|
||||
jetzt, damit die Trennung von Anfang an sichtbar ist. */
|
||||
const BEREICHE = [
|
||||
{ name: 'Dashboard', phase: 1, rollen: ['admin', 'creator', 'scout'] },
|
||||
{ name: 'Personen & Zugänge', phase: 1, rollen: ['admin'], ziel: 'personen.html' },
|
||||
{ name: 'Creator-Profile', phase: 1, rollen: ['admin'] },
|
||||
{ name: 'Mein Profil', phase: 1, rollen: ['creator'] },
|
||||
{ name: 'Aufgaben', phase: 1, rollen: ['admin', 'creator', 'scout'], ziel: 'aufgaben.html' },
|
||||
|
||||
@@ -81,6 +81,53 @@
|
||||
|
||||
</main>
|
||||
|
||||
<!-- Bearbeiten. Als natives <dialog>: bringt Fokusfang, Schließen mit Esc
|
||||
und die Abdunklung des Hintergrunds ohne eigenen Code mit. -->
|
||||
<dialog class="dialog" id="bearbeiten-dialog">
|
||||
<form method="dialog" id="bearbeiten" class="neu neu--blank">
|
||||
<h2 class="dialog__titel">Aufgabe bearbeiten</h2>
|
||||
|
||||
<div class="neu__zeile">
|
||||
<label class="feld-schild" for="b-titel">Titel</label>
|
||||
<input id="b-titel" maxlength="160" required />
|
||||
</div>
|
||||
<div class="neu__zeile">
|
||||
<label class="feld-schild" for="b-text">Beschreibung</label>
|
||||
<textarea id="b-text" rows="3" maxlength="4000"></textarea>
|
||||
</div>
|
||||
<div class="neu__raster">
|
||||
<div>
|
||||
<label class="feld-schild" for="b-prio">Priorität</label>
|
||||
<select id="b-prio">
|
||||
<option value="hoch">hoch</option>
|
||||
<option value="mittel">mittel</option>
|
||||
<option value="niedrig">niedrig</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="feld-schild" for="b-frist">Frist</label>
|
||||
<input id="b-frist" type="date" />
|
||||
</div>
|
||||
<div id="b-feld-creator" hidden>
|
||||
<label class="feld-schild" for="b-creator">Bereich von</label>
|
||||
<select id="b-creator"><option value="">—</option></select>
|
||||
</div>
|
||||
<div id="b-feld-verant" hidden>
|
||||
<label class="feld-schild" for="b-verant">Verantwortlich</label>
|
||||
<select id="b-verant"><option value="">—</option></select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="fehler" id="b-fehler" role="alert" aria-live="polite"></p>
|
||||
|
||||
<div class="dialog__knoepfe">
|
||||
<button type="button" class="knopf knopf--klein" id="b-speichern">Speichern</button>
|
||||
<button type="button" class="abmelden" id="b-abbrechen">Abbrechen</button>
|
||||
<button type="button" class="abmelden abmelden--warnung" id="b-loeschen" hidden>Löschen</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
|
||||
<script src="assets/js/aufgaben.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
||||
<title>Personen & Zugänge · Creator Workspace</title>
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
<meta name="theme-color" content="#05070d" />
|
||||
<link rel="icon" href="assets/img/favicon.png" />
|
||||
<link rel="stylesheet" href="assets/css/gate.css" />
|
||||
<link rel="stylesheet" href="assets/css/start.css" />
|
||||
<link rel="stylesheet" href="assets/css/aufgaben.css" />
|
||||
<link rel="stylesheet" href="assets/css/personen.css" />
|
||||
</head>
|
||||
|
||||
<body class="start">
|
||||
|
||||
<header class="kopfleiste">
|
||||
<p class="marke"><a class="zurueck" href="start.html">Creator Workspace</a> · Personen</p>
|
||||
<div class="kopfleiste__rechts">
|
||||
<span class="wer" id="wer">…</span>
|
||||
<button type="button" class="abmelden" id="abmelden">Abmelden</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="inhalt inhalt--breit">
|
||||
|
||||
<section class="kopf-zeile">
|
||||
<div>
|
||||
<p class="marke">Zugänge</p>
|
||||
<h1 class="titel">Personen & Zugangscodes</h1>
|
||||
</div>
|
||||
<button type="button" class="knopf knopf--klein" id="neu-oeffnen">Person anlegen</button>
|
||||
</section>
|
||||
|
||||
<form class="neu" id="neu" hidden>
|
||||
<div class="neu__raster">
|
||||
<div>
|
||||
<label class="feld-schild" for="f-name">Name</label>
|
||||
<input id="f-name" name="name" maxlength="60" placeholder="z. B. Luna" required />
|
||||
</div>
|
||||
<div>
|
||||
<label class="feld-schild" for="f-rolle">Rolle</label>
|
||||
<select id="f-rolle" name="rolle">
|
||||
<option value="creator" selected>Creator</option>
|
||||
<option value="scout">Scout</option>
|
||||
<option value="admin">Management</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<p class="fehler" id="neu-fehler" role="alert" aria-live="polite"></p>
|
||||
<div class="neu__knoepfe">
|
||||
<button type="submit" class="knopf knopf--klein" id="neu-speichern">Anlegen</button>
|
||||
<button type="button" class="abmelden" id="neu-abbrechen">Abbrechen</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Anzeige des frisch erzeugten Codes. Bewusst auffällig und mit klarem
|
||||
Hinweis, dass er nur jetzt zu sehen ist. -->
|
||||
<div class="code-kasten" id="code-kasten" hidden role="status" aria-live="polite">
|
||||
<p class="code-kasten__schild">Zugangscode für <span id="code-name"></span></p>
|
||||
<p class="code-kasten__code" id="code-wert"></p>
|
||||
<p class="code-kasten__hinweis">
|
||||
Dieser Code wird <strong>nur jetzt</strong> angezeigt und ist danach nicht mehr
|
||||
abrufbar. Sicher weitergeben und in Bitwarden ablegen.
|
||||
</p>
|
||||
<div class="neu__knoepfe">
|
||||
<button type="button" class="knopf knopf--klein" id="code-kopieren">Kopieren</button>
|
||||
<button type="button" class="abmelden" id="code-schliessen">Verstanden, schließen</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="fehler" id="fehler" role="alert" aria-live="polite"></p>
|
||||
|
||||
<div id="liste" aria-busy="true"><p class="leise">Personen werden geladen …</p></div>
|
||||
|
||||
<section class="protokoll-block">
|
||||
<p class="feldschild">Letzte Ereignisse</p>
|
||||
<div id="protokoll"><p class="leise">…</p></div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
<script src="assets/js/personen.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user