Wunsch: "ich will auch immer bei jeder seite auch immer einen knopf
haben damit ich zurueck gehen kann auf die seite vorher."
Neues gemeinsames Modul assets/js/kopf.js. Blindes history.back() reicht
dafuer nicht: Wer die Adresse direkt eingibt oder gerade von der
Anmeldung kommt, landet damit ausserhalb des Workspace oder wieder im
Anmeldeformular. Deshalb wird zuerst geprueft, woher der Aufruf kam:
* vorherige Seite im Workspace -> "Zurueck", history.back()
(fuehrt wirklich dorthin zurueck, samt Bildlaufposition)
* kein Verlauf, aber Unterseite -> "Uebersicht", geht zur Startseite
* kein Verlauf auf der Startseite -> Knopf bleibt verborgen
Ein Knopf, der nichts tut, ist schlimmer als keiner.
Der Knopf sitzt links in der Kopfleiste, an derselben Stelle wie im
Browser.
Nebenbei aufgeraeumt: Das Abmelden stand bisher in jeder der fuenf
Seitendateien noch einmal -- fuenfmal derselbe Block, fuenfmal eine
Stelle zum Vergessen. Liegt jetzt ebenfalls in kopf.js.
Geprueft: Start nach Login -> verborgen; Aufgaben von Start ->
"Zurueck", fuehrt zurueck; Kalender direkt aufgerufen -> "Uebersicht".
Keine Konsolenfehler, Abmelden funktioniert weiterhin.
Nur statische Dateien, kein Neustart noetig.
262 lines
9.1 KiB
JavaScript
262 lines
9.1 KiB
JavaScript
/* ===================================================================
|
|
Kalender: eigene Termine und Fristen aus den Aufgaben in einer Liste.
|
|
Fristen sind nur eingeblendet -- geändert werden sie im Aufgabenbrett.
|
|
=================================================================== */
|
|
(() => {
|
|
'use strict';
|
|
|
|
const $ = (id) => document.getElementById(id);
|
|
const WOCHENTAGE = ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch',
|
|
'Donnerstag', 'Freitag', 'Samstag'];
|
|
const ARTNAME = { call: 'Call', termin: 'Termin', review: 'Review', frist: 'Frist' };
|
|
|
|
let ich = null;
|
|
let tage = 30;
|
|
|
|
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 || ''; };
|
|
const heute = new Date().toISOString().slice(0, 10);
|
|
|
|
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'); }
|
|
return a;
|
|
}
|
|
|
|
function datumHuebsch(iso) {
|
|
const [j, m, t] = iso.slice(0, 10).split('-');
|
|
return `${t}.${m}.${j}`;
|
|
}
|
|
|
|
function wochentag(iso) {
|
|
return WOCHENTAGE[new Date(iso.slice(0, 10) + 'T12:00:00').getDay()];
|
|
}
|
|
|
|
/* ---------- Zeichnen --------------------------------------------------- */
|
|
|
|
function eintragTermin(t) {
|
|
const k = el('div', 'eintrag');
|
|
k.dataset.art = t.art;
|
|
k.dataset.erledigt = t.erledigt ? 'ja' : 'nein';
|
|
|
|
k.append(el('span', 'eintrag__zeit', t.beginn.slice(11, 16) + ' Uhr'));
|
|
k.append(el('span', 'eintrag__titel', t.titel));
|
|
|
|
const knoepfe = el('div', 'eintrag__knoepfe');
|
|
knoepfe.append(el('span', 'eintrag__art', ARTNAME[t.art] || t.art));
|
|
knoepfe.append(tuKnopf(t.erledigt ? 'wieder offen' : 'erledigt', async () => {
|
|
const a = await hole('/workspace/api/termine/' + t.id, {
|
|
method: 'PATCH',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ erledigt: !t.erledigt }),
|
|
});
|
|
if (!a.ok) { melde('Ändern ging nicht.'); return; }
|
|
await laden();
|
|
}));
|
|
if (ich.rolle === 'admin' || t.erstellt_von === ich.id) {
|
|
knoepfe.append(tuKnopf('löschen', async () => {
|
|
if (!confirm('Termin "' + t.titel + '" löschen?')) return;
|
|
const a = await hole('/workspace/api/termine/' + t.id, { method: 'DELETE' });
|
|
if (!a.ok) { melde('Löschen ging nicht.'); return; }
|
|
await laden();
|
|
}));
|
|
}
|
|
k.append(knoepfe);
|
|
|
|
const zusatz = el('div', 'eintrag__zusatz');
|
|
if (t.dauer_min) zusatz.append(el('span', null, t.dauer_min + ' Min'));
|
|
if (t.teilnehmer_name) zusatz.append(el('span', null, '· mit ' + t.teilnehmer_name));
|
|
if (ich.rolle === 'admin' && t.creator_name && t.creator_name !== t.teilnehmer_name) {
|
|
zusatz.append(el('span', null, '· Bereich ' + t.creator_name));
|
|
}
|
|
if (t.ort) {
|
|
/* Links sollen klickbar sein, aber nur echte http(s)-Adressen --
|
|
sonst liesse sich hier javascript: einschleusen. */
|
|
if (/^https?:\/\//i.test(t.ort)) {
|
|
const a = el('a', null, t.ort.replace(/^https?:\/\//i, '').slice(0, 60));
|
|
a.href = t.ort; a.target = '_blank'; a.rel = 'noopener noreferrer';
|
|
zusatz.append(a);
|
|
} else {
|
|
zusatz.append(el('span', null, '· ' + t.ort));
|
|
}
|
|
}
|
|
if (t.beschreibung) zusatz.append(el('span', null, '· ' + t.beschreibung));
|
|
if (zusatz.childElementCount) k.append(zusatz);
|
|
return k;
|
|
}
|
|
|
|
function eintragFrist(f) {
|
|
const k = el('div', 'eintrag');
|
|
k.dataset.art = 'frist';
|
|
k.append(el('span', 'eintrag__zeit', '—'));
|
|
k.append(el('span', 'eintrag__titel', f.titel));
|
|
const knoepfe = el('div', 'eintrag__knoepfe');
|
|
knoepfe.append(el('span', 'eintrag__art', 'Frist'));
|
|
const link = el('a', 'schritt', 'zur Aufgabe');
|
|
link.href = 'aufgaben.html';
|
|
knoepfe.append(link);
|
|
k.append(knoepfe);
|
|
const zusatz = el('div', 'eintrag__zusatz');
|
|
zusatz.append(el('span', null, 'Priorität ' + f.prioritaet));
|
|
if (f.verantwortlich_name) zusatz.append(el('span', null, '· ' + f.verantwortlich_name));
|
|
k.append(zusatz);
|
|
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;
|
|
}
|
|
|
|
function zeichne(daten) {
|
|
const ziel = $('liste');
|
|
ziel.textContent = '';
|
|
ziel.setAttribute('aria-busy', 'false');
|
|
|
|
/* Termine und Fristen nach Tag zusammenlegen. */
|
|
const nachTag = new Map();
|
|
for (const t of daten.termine) {
|
|
const tag = t.beginn.slice(0, 10);
|
|
if (!nachTag.has(tag)) nachTag.set(tag, { termine: [], fristen: [] });
|
|
nachTag.get(tag).termine.push(t);
|
|
}
|
|
for (const f of daten.fristen) {
|
|
if (!nachTag.has(f.frist)) nachTag.set(f.frist, { termine: [], fristen: [] });
|
|
nachTag.get(f.frist).fristen.push(f);
|
|
}
|
|
|
|
if (!nachTag.size) {
|
|
ziel.append(el('p', 'leer-hinweis',
|
|
'Für diesen Zeitraum ist nichts eingetragen.'));
|
|
return;
|
|
}
|
|
|
|
for (const tag of [...nachTag.keys()].sort()) {
|
|
const block = el('section', 'tag');
|
|
if (tag < heute) block.dataset.vergangen = 'ja';
|
|
|
|
const kopf = el('div', 'tag__kopf');
|
|
kopf.append(el('span', 'tag__datum', datumHuebsch(tag)));
|
|
kopf.append(el('span', 'tag__wochentag', wochentag(tag)));
|
|
if (tag === heute) kopf.append(el('span', 'tag__marke', 'Heute'));
|
|
block.append(kopf);
|
|
|
|
const { termine, fristen } = nachTag.get(tag);
|
|
for (const t of termine) block.append(eintragTermin(t));
|
|
for (const f of fristen) block.append(eintragFrist(f));
|
|
ziel.append(block);
|
|
}
|
|
}
|
|
|
|
async function laden() {
|
|
melde('');
|
|
try {
|
|
const a = await hole('/workspace/api/termine?tage=' + tage);
|
|
if (!a.ok) { melde('Kalender konnte nicht geladen werden.'); return; }
|
|
zeichne(await a.json());
|
|
} catch { /* umgeleitet */ }
|
|
}
|
|
|
|
/* ---------- Zeitraum ---------------------------------------------------- */
|
|
|
|
for (const b of document.querySelectorAll('.zeitraum .schritt')) {
|
|
b.setAttribute('aria-pressed', String(Number(b.dataset.tage) === tage));
|
|
b.addEventListener('click', async () => {
|
|
tage = Number(b.dataset.tage);
|
|
for (const x of document.querySelectorAll('.zeitraum .schritt')) {
|
|
x.setAttribute('aria-pressed', String(x === b));
|
|
}
|
|
await laden();
|
|
});
|
|
}
|
|
|
|
/* ---------- Neuer Termin ------------------------------------------------ */
|
|
|
|
const umschalten = (auf) => {
|
|
$('neu').hidden = !auf; $('neu-oeffnen').hidden = auf;
|
|
if (auf) $('f-titel').focus();
|
|
};
|
|
$('neu-oeffnen').addEventListener('click', () => {
|
|
/* Vorbelegung: morgen 18:00 -- ein realistischer Zeitpunkt für einen
|
|
Call, spart in der Regel zwei Klicks. */
|
|
const m = new Date(Date.now() + 86400_000);
|
|
m.setHours(18, 0, 0, 0);
|
|
$('f-beginn').value = new Date(m.getTime() - m.getTimezoneOffset() * 60000)
|
|
.toISOString().slice(0, 16);
|
|
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');
|
|
const daten = {
|
|
titel: $('f-titel').value,
|
|
art: $('f-art').value,
|
|
beginn: $('f-beginn').value,
|
|
dauer_min: Number($('f-dauer').value) || 30,
|
|
ort: $('f-ort').value,
|
|
beschreibung: $('f-text').value,
|
|
};
|
|
if (ich.rolle === 'admin') {
|
|
const wahl = $('f-teilnehmer').value;
|
|
daten.teilnehmer_id = wahl || null;
|
|
daten.creator_id = wahl || null;
|
|
}
|
|
knopf.disabled = true;
|
|
try {
|
|
const a = await hole('/workspace/api/termine', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(daten),
|
|
});
|
|
if (!a.ok) {
|
|
$('neu-fehler').textContent = (await a.json().catch(() => ({}))).fehler || 'Ging nicht.';
|
|
return;
|
|
}
|
|
$('neu').reset(); umschalten(false);
|
|
await laden();
|
|
} catch { /* umgeleitet */ } finally { knopf.disabled = false; }
|
|
});
|
|
|
|
/* ---------- Start -------------------------------------------------------- */
|
|
|
|
|
|
(async () => {
|
|
try {
|
|
const a = await hole('/workspace/api/ich');
|
|
if (!a.ok) { location.assign('/workspace/'); return; }
|
|
ich = await a.json();
|
|
} catch { return; }
|
|
$('wer').textContent = ich.name + ' · ' + ich.rolle;
|
|
|
|
if (ich.rolle === 'admin') {
|
|
$('feld-teilnehmer').hidden = false;
|
|
try {
|
|
const { personen } = await (await hole('/workspace/api/personen')).json();
|
|
for (const p of personen) {
|
|
const o = document.createElement('option');
|
|
o.value = String(p.id);
|
|
o.textContent = p.name + ' (' + p.rolle + ')';
|
|
$('f-teilnehmer').append(o);
|
|
}
|
|
} catch { /* Zuordnung bleibt leer */ }
|
|
}
|
|
await laden();
|
|
})();
|
|
})();
|