/* =====================================================================
aether-medical / Ansicht: Nachrichten und Aushänge
Postfach und Schwarzes Brett. Abgeschickt ist abgeschickt — es gibt
hier bewusst kein Bearbeiten und kein Löschen von Nachrichten.
Ein Aushang wird archiviert, nie entfernt. Und eine Kenntnisnahme
setzt jeder nur für sich selbst.
===================================================================== */
"use strict";
(() => {
const { sicher, zeit, minuten, aufruf, meldung, fenster, bestaetigen } = App;
const z = {
ordner: "posteingang", // posteingang | gesendet | aushang | archiv
daten: null,
};
// Marker-Art je Empfängerart
const typArt = { rundmeldung: "kritisch", station: "warn", person: "info" };
const typName = { rundmeldung: "Rundmeldung", station: "Station", person: "Persönlich" };
/* ---- Kleine Helfer ---------------------------------------------------- */
/** Anzeigename einer Aushangkategorie */
function kategorieName(kennung) {
const treffer = (z.daten.kategorien || []).find((k) => k.id === kennung);
return treffer ? treffer.name : kennung;
}
/** Farbe einer Aushangkategorie */
function kategorieFarbe(kennung) {
const treffer = (z.daten.kategorien || []).find((k) => k.id === kennung);
return treffer ? treffer.farbe : "#8697a4";
}
/** Beschreibt eine Frist in Worten */
function fristText(a) {
if (a.restMinuten === null || a.restMinuten === undefined) return null;
if (a.restMinuten <= 0) return `Frist seit ${minuten(-a.restMinuten)} abgelaufen`;
return `noch ${minuten(a.restMinuten)}`;
}
/* ---- Gerüst ------------------------------------------------------------ */
function reiterleiste(d) {
const eintraege = [
{ id: "posteingang", text: "Posteingang", zahl: d.zaehler.ungelesen },
{ id: "gesendet", text: "Gesendet", zahl: d.zaehler.gesendet },
{ id: "aushang", text: "Schwarzes Brett", zahl: d.zaehler.aushaenge },
{ id: "archiv", text: "Archiv", zahl: 0 },
];
return `
${eintraege.map((e) => `
${sicher(e.text)}${e.zahl > 0 ? `${e.zahl} ` : ""}
`).join("")}
`;
}
function kennzahlen(d) {
const zk = d.zaehler;
return `
Ungelesen
${zk.ungelesen}
im Posteingang
Kenntnisnahme offen
${zk.offeneKenntnis}
von dir noch nicht bestätigt
Aushänge
${zk.aushaenge}
hängen gerade
Von dir gesendet
${zk.gesendet}
bleiben unverändert stehen
`;
}
/* ---- Übersicht ---------------------------------------------------------- */
function uebersicht(behaelter) {
const d = z.daten;
const amBrett = z.ordner === "aushang" || z.ordner === "archiv";
behaelter.innerHTML = `
Nachrichten und Aushänge
Was hier steht, hat jemand geschrieben und unterschrieben. Eine gesendete
Nachricht lässt sich nicht mehr ändern, ein Aushang wird archiviert statt
gelöscht.
${d.rechte.senden
? `✉ Nachricht schreiben ` : ""}
${d.rechte.schreiben
? `+ Aushang veröffentlichen ` : ""}
${kennzahlen(d)}
${reiterleiste(d)}
${amBrett ? brettAnsicht(d) : postAnsicht(d)}
`;
verdrahteUebersicht(behaelter);
}
/* ---- Postfach ------------------------------------------------------------- */
function postAnsicht(d) {
const gesendet = z.ordner === "gesendet";
return `
${gesendet ? "Von dir gesendet" : "Posteingang"}
${gesendet
? "Gelesen heißt: jemand hat sie geöffnet"
: "Als gelesen gilt, was du geöffnet hast"}
${d.liste.length ? d.liste.map(gesendet ? gesendetZeile : postZeile).join("")
: `
${gesendet
? "Du hast noch nichts geschrieben." : "Keine Nachrichten."}
`}
`;
}
function postZeile(n) {
return `
${sicher(n.betreff || "(ohne Betreff)")}
${sicher(n.absenderName)} · ${sicher(n.vorschau)}
${sicher(typName[n.empf_typ] || n.empf_typ)}
${zeit(n.gesendet)}
`;
}
function gesendetZeile(n) {
return `
${sicher(n.betreff || "(ohne Betreff)")}
an ${sicher(n.empfaengerName)} · ${sicher(n.vorschau)}
${sicher(typName[n.empf_typ] || n.empf_typ)}
${n.gelesenVon} gelesen
${zeit(n.gesendet)}
`;
}
/* ---- Schwarzes Brett ------------------------------------------------------- */
function brettAnsicht(d) {
const archiv = z.ordner === "archiv";
if (!d.liste.length) {
return `${archiv
? "Nichts archiviert." : "Am Brett hängt nichts."}
`;
}
return `${d.liste.map((a) => `
${a.angeheftet ? `📌 ` : ""}
${sicher(a.titel)}
${sicher(kategorieName(a.kategorie))}
${sicher(a.vorschau)}
${sicher(a.autor)} · ${zeit(a.erstellt_am)}
${a.station ? `${sicher(a.stationName)} ` : ""}
${a.kenntnispflicht
? (a.bestaetigt
? `gelesen `
: `Kenntnis offen `)
: ""}
${a.kenntnispflicht
? `${a.kenntnisse} bestätigt ` : ""}
${fristText(a)
? `
${sicher(fristText(a))} ` : ""}
${archiv && a.archiviertVon
? `
Abgenommen von ${sicher(a.archiviertVon)} ·
${zeit(a.archiviert_am)}
` : ""}
`).join("")}
`;
}
/* ---- Verdrahtung ------------------------------------------------------------ */
function verdrahteUebersicht(behaelter) {
behaelter.querySelectorAll("[data-reiter]").forEach((el) => {
el.addEventListener("click", () => {
z.ordner = el.dataset.reiter;
rendern(behaelter);
});
});
behaelter.querySelectorAll("[data-nachricht]").forEach((el) => {
el.addEventListener("click", () => nachrichtFenster(behaelter, +el.dataset.nachricht));
});
behaelter.querySelectorAll("[data-aushang]").forEach((el) => {
el.addEventListener("click", () => aushangFenster(behaelter, +el.dataset.aushang));
});
const schreiben = behaelter.querySelector("[data-schreiben]");
if (schreiben) schreiben.addEventListener("click", () => schreibenFenster(behaelter, null));
const neuerAushang = behaelter.querySelector("[data-neueraushang]");
if (neuerAushang) {
neuerAushang.addEventListener("click", () => aushangSchreibenFenster(behaelter, null));
}
}
/* ---- Nachricht lesen ---------------------------------------------------------- */
async function nachrichtFenster(behaelter, nachrichtId) {
let d;
try {
d = await aufruf("kommunikation", "nachricht", { id: nachrichtId });
} catch (f) { return meldung(f.message, "fehler"); }
const n = d.nachricht;
const dialog = fenster({
titel: n.betreff || "(ohne Betreff)",
inhalt: `
Von ${sicher(n.absenderName)}
An ${sicher(n.empfaengerName)}
Gesendet ${zeit(n.gesendet)}
${sicher(n.text)}
${n.vonMir ? `
Gelesen von
${d.leser.length} Person(en)
${d.leser.length ? d.leser.map((l) => `
`).join("")
: `
Noch niemand hat sie geöffnet.
`}
` : ""}
${!n.vonMir && d.rechte.senden ? `
Antworten
` : ""}
`,
nachOeffnen: (hg) => {
const antworten = hg.querySelector("[data-antworten]");
if (antworten) {
antworten.addEventListener("click", () => {
dialog.schliessen();
schreibenFenster(behaelter, {
typ: "person",
ziel: n.absender,
zielName: n.absenderName,
betreff: n.betreff && !/^Re:/i.test(n.betreff)
? "Re: " + n.betreff : (n.betreff || ""),
});
});
}
},
});
// Der Posteingang zeigt sie danach als gelesen
rendernStill(behaelter);
}
/* ---- Nachricht schreiben -------------------------------------------------------- */
async function schreibenFenster(behaelter, vorgabe) {
let e;
try {
e = await aufruf("kommunikation", "empfaenger", {});
} catch (f) { return meldung(f.message, "fehler"); }
let typ = (vorgabe && vorgabe.typ) || "person";
let ziel = (vorgabe && vorgabe.ziel) || null;
bestaetigen({
titel: "Nachricht schreiben",
inhalt: `
ℹ
Eine abgeschickte Nachricht lässt sich weder ändern noch löschen.
Sie bleibt mit deinem Namen stehen.
Empfänger
Person
Station
${e.rechte.rundmeldung
? `Alle im Haus ` : ""}
${e.personal.map((p) => `
${sicher(p.name)}
${p.selbst ? `du ` : ""}
${sicher(p.rolleName)} ·
${sicher(p.personalnummer || "")} · ${sicher(p.stationName)}
${p.imDienst ? `
im Dienst `
: `
außer Dienst `}
`).join("")}
Wer nicht im Dienst ist, bekommt die Nachricht
beim nächsten Öffnen des Systems.
Station
${e.stationen.map((s) =>
`${sicher(s.name)} `).join("")}
📣
Diese Nachricht geht an jeden im Haus und wird allen im Dienst
sofort gemeldet.
Betreff
Text
`,
bestaetigenText: "Abschicken",
bestaetigt: async (el, zu) => {
const text = el.querySelector("[name=text]").value.trim();
if (text.length < 2) return meldung("Bitte einen Text schreiben.", "fehler");
if (typ === "person" && !ziel) {
return meldung("Bitte einen Empfänger wählen.", "fehler");
}
const zielWert = typ === "station"
? el.querySelector("[name=station]").value
: (typ === "person" ? ziel : null);
try {
const antwort = await aufruf("kommunikation", "senden", {
typ, ziel: zielWert,
betreff: el.querySelector("[name=betreff]").value.trim(),
text,
});
zu();
meldung(`Nachricht an ${antwort.empfaenger} abgeschickt.`, "gut");
z.ordner = "gesendet";
rendern(behaelter);
} catch (f) { meldung(f.message, "fehler"); }
},
nachOeffnen: (hg) => {
// Empfängerart umschalten
hg.querySelectorAll("[data-typ]").forEach((el) => {
el.addEventListener("click", () => {
typ = el.dataset.typ;
hg.querySelectorAll("[data-typ]").forEach((k) => k.classList.remove("aktiv"));
el.classList.add("aktiv");
hg.querySelectorAll("[data-wahl]").forEach((block) => {
block.style.display = block.dataset.wahl === typ ? "" : "none";
});
});
});
// Person wählen
hg.querySelectorAll("[data-person]").forEach((el) => {
el.addEventListener("click", () => {
ziel = +el.dataset.person;
hg.querySelectorAll("[data-person]")
.forEach((zeile) => zeile.classList.remove("gewaehlt"));
el.classList.add("gewaehlt");
});
});
// Personenliste filtern
const suche = hg.querySelector("[name=suche]");
suche.addEventListener("input", () => {
const begriff = suche.value.trim().toLowerCase();
hg.querySelectorAll("[data-person]").forEach((el) => {
el.style.display = !begriff || el.dataset.suchtext.includes(begriff) ? "" : "none";
});
});
},
});
}
/* ---- Aushang lesen -------------------------------------------------------------- */
async function aushangFenster(behaelter, aushangId) {
let d;
try {
d = await aufruf("kommunikation", "aushang", { id: aushangId });
} catch (f) { return meldung(f.message, "fehler"); }
const a = d.aushang;
const dialog = fenster({
titel: a.titel,
inhalt: `
${a.aktiv ? "" : `
📁
Archiviert — abgenommen von ${sicher(a.archiviertVon || "unbekannt")}
am ${zeit(a.archiviertAm)}. Der Wortlaut bleibt nachlesbar.
`}
${a.kenntnispflicht && !a.bestaetigt && a.aktiv ? `
✋
Kenntnisnahme steht aus. Bestätigen kannst nur du selbst —
niemand kann das für dich tun.
` : ""}
${fristText(a) ? `
🕓
${sicher(fristText(a))}. Eine abgelaufene Frist erinnert —
sie hat keine weitere Folge.
` : ""}
Kategorie ${sicher(kategorieName(a.kategorie))}
Gilt für ${sicher(a.stationName)}
Veröffentlicht ${zeit(a.erstelltAm)}
Von ${sicher(a.autor)}
${sicher(a.text)}
${a.aktiv && a.kenntnispflicht && !a.bestaetigt
? `Zur Kenntnis genommen ` : ""}
${a.aktiv && d.rechte.schreiben
? `Bearbeiten
${a.angeheftet ? "Lösen" : "Anheften"} ` : ""}
${a.aktiv && d.rechte.archivieren
? `Abnehmen ` : ""}
${a.textGesperrt && a.aktiv ? `
${d.kenntnis.length} Person(en) haben diesen Wortlaut gelesen — Titel und Text
lassen sich deshalb nicht mehr ändern. Für eine Änderung: abnehmen und neu
veröffentlichen.
` : ""}
${a.kenntnispflicht ? `
Zur Kenntnis genommen
${d.kenntnis.length}
${d.kenntnis.length ? d.kenntnis.map((k) => `
${sicher(k.personalnummer || "—")}
${zeit(k.bestaetigt_am)}
`).join("")
: `
Noch niemand.
`}
Steht noch aus
${d.offen.length}
${d.offen.length ? d.offen.map((o) => `
${sicher(o.personalnummer || "—")}
`).join("")
: `
Alle haben bestätigt.
`}
` : ""}
`,
nachOeffnen: (hg) => {
const kenntnis = hg.querySelector("[data-kenntnis]");
if (kenntnis) {
kenntnis.addEventListener("click", async () => {
try {
await aufruf("kommunikation", "kenntnis", { id: a.id });
dialog.schliessen();
meldung("Kenntnisnahme bestätigt.", "gut");
rendern(behaelter);
} catch (f) { meldung(f.message, "fehler"); }
});
}
const anheften = hg.querySelector("[data-anheften]");
if (anheften) {
anheften.addEventListener("click", async () => {
try {
await aufruf("kommunikation", "anheften", {
id: a.id, angeheftet: !a.angeheftet,
});
dialog.schliessen();
meldung(a.angeheftet ? "Aushang gelöst." : "Aushang angeheftet.", "gut");
rendern(behaelter);
} catch (f) { meldung(f.message, "fehler"); }
});
}
const bearbeiten = hg.querySelector("[data-bearbeiten]");
if (bearbeiten) {
bearbeiten.addEventListener("click", () => {
dialog.schliessen();
aushangSchreibenFenster(behaelter, a);
});
}
const archivieren = hg.querySelector("[data-archivieren]");
if (archivieren) {
archivieren.addEventListener("click", () => {
dialog.schliessen();
archivierenFenster(behaelter, a);
});
}
},
});
}
/* ---- Aushang veröffentlichen ------------------------------------------------------ */
function aushangSchreibenFenster(behaelter, a) {
const d = z.daten;
const neu = !a;
bestaetigen({
titel: neu ? "Aushang veröffentlichen" : `„${a.titel}" bearbeiten`,
inhalt: `
${!neu && a.textGesperrt ? `
✋
Titel und Text sind gesperrt. Es haben bereits Personen diesen
Wortlaut zur Kenntnis genommen. Für eine inhaltliche Änderung den Aushang
abnehmen und neu veröffentlichen.
` : ""}
Titel
Kategorie
${d.kategorien.map((k) =>
`
${sicher(k.name)} `).join("")}
Gilt für
gesamte Klinik
${d.stationen.map((s) =>
`
${sicher(s.name)} `).join("")}
Text
Oben anheften
`,
bestaetigenText: neu ? "Veröffentlichen" : "Speichern",
bestaetigt: async (el, zu) => {
const gesperrt = !neu && a.textGesperrt;
const titel = gesperrt ? a.titel : el.querySelector("[name=titel]").value.trim();
const text = gesperrt ? a.text : el.querySelector("[name=text]").value.trim();
if (!titel) return meldung("Bitte einen Titel angeben.", "fehler");
if (text.length < 5) return meldung("Bitte einen Text schreiben.", "fehler");
try {
await aufruf("kommunikation", "aushangSchreiben", {
id: neu ? null : a.id,
titel, text,
kategorie: el.querySelector("[name=kategorie]").value,
station: el.querySelector("[name=station]").value,
angeheftet: el.querySelector("[name=angeheftet]").checked,
kenntnispflicht: el.querySelector("[name=kenntnispflicht]").checked,
frist: el.querySelector("[name=frist]").value,
});
zu();
meldung(neu ? "Aushang hängt." : "Aushang gespeichert.", "gut");
z.ordner = "aushang";
rendern(behaelter);
} catch (f) { meldung(f.message, "fehler"); }
},
});
}
function archivierenFenster(behaelter, a) {
bestaetigen({
titel: "Aushang abnehmen",
gefahr: true,
inhalt: `
„${sicher(a.titel)} " wird vom Brett genommen und wandert ins Archiv.
Gelöscht wird nichts — der Wortlaut und alle Kenntnisnahmen bleiben.
${a.kenntnispflicht ? `
ℹ
Offene Kenntnisnahmen halten das Abnehmen nicht auf. Wie viele noch
ausstanden, wird im Protokoll festgehalten.
` : ""}
Grund (Pflicht)
`,
bestaetigenText: "Abnehmen",
bestaetigt: async (el, zu) => {
const grund = el.querySelector("[name=grund]").value.trim();
if (!grund) return meldung("Bitte einen Grund angeben.", "fehler");
try {
const antwort = await aufruf("kommunikation", "aushangArchivieren",
{ id: a.id, grund });
zu();
meldung(antwort.offen > 0
? `Abgenommen — ${antwort.offen} Kenntnisnahme(n) standen noch aus.`
: "Aushang abgenommen.", "gut");
rendern(behaelter);
} catch (f) { meldung(f.message, "fehler"); }
},
});
}
/* ---- Laden ------------------------------------------------------------------------ */
/** Frischt die Liste im Hintergrund auf, ohne ein offenes Fenster zu stören */
async function rendernStill(behaelter) {
try {
z.daten = await aufruf("kommunikation", "uebersicht", { ordner: z.ordner });
if (!document.querySelector(".fenster-hg")) uebersicht(behaelter);
} catch (f) { /* eine stille Auffrischung darf folgenlos scheitern */ }
}
async function rendern(behaelter) {
try {
z.daten = await aufruf("kommunikation", "uebersicht", { ordner: z.ordner });
uebersicht(behaelter);
// Sprungziel aus einem Hinweis
const ziel = App.zustand.modulZiel;
if (ziel && ziel.nachricht) {
App.zustand.modulZiel = null;
return nachrichtFenster(behaelter, ziel.nachricht);
}
if (ziel && ziel.aushang) {
App.zustand.modulZiel = null;
return aushangFenster(behaelter, ziel.aushang);
}
} catch (f) {
behaelter.innerHTML = `Nachrichten und Aushänge
Konnte nicht geladen werden: ${sicher(f.message)}
`;
}
}
App.registriereAnsicht("kommunikation", {
rendern,
aktualisieren() {
if (App.zustand.aktivesModul === "kommunikation"
&& !document.querySelector(".fenster-hg")) {
rendern(App.$("inhalt"));
}
},
});
})();