// js/abwesenheit/abwesenheit.js — Bereich „Abwesenheiten“ (Tab abwesenheit,
// Eriks Wahl „Entwurf A“): oben die Schnellerfassung, darunter die Liste der
// gewählten Person, unten die Team-Zeitleiste eines Monats.
//
// Rechte (Rollen & Rechte, Gruppe „Team“): team.abw (für mich),
// team.abwProjekte, team.abwGewerk, team.abwAlle. Der Server rechnet daraus
// je Person `darfAbw` (loadGlobalPeople(req.user)) und schickt Abwesenheiten
// ANDERER Personen nur als „abwesend“ ohne Art und Notiz (`verdeckt`) — Art
// und Notiz sieht nur die Person selbst und der Administrator. Verdeckte Einträge lassen sich
// deshalb nicht ändern (die Art ist unbekannt), nur löschen. Diese Datei
// verlässt sich darauf, entscheidet selbst also nichts über Rechte.
//
// Daten: db.globalPeople (aus /load-all bzw. GET /personen). Nach jedem
// Speichern lädt personenNachladen() (js/store.js) die Liste neu; andere
// Browser bekommen das Live-Ereignis {typ:'personen'}.
const ABW_ARTEN = ['Urlaub', 'Krank', 'Fortbildung', 'Sonstiges'];
const ABW_KLASSE = { Urlaub: 'abw-u', Krank: 'abw-k', Fortbildung: 'abw-f', Sonstiges: 'abw-s' };
let abwMonat = null; // Date: erster Tag des angezeigten Monats
let abwFilter = 'Alle'; // Gewerk-Filter der Zeitleiste
let abwFuer = null; // userId der Person, für die eingetragen wird
let abwBearbeiten = null; // id der Abwesenheit, die gerade geändert wird
let abwVergangen = false; // Liste: vergangene statt kommende
let abwLoeschFrage = null; // id, deren Löschen gerade bestätigt werden soll
let abwFehler = '';
let abwEntwurf = { start: '', end: '', type: 'Urlaub', note: '' };
// darfIrgendeineAbwesenheit: ob die Rolle überhaupt Abwesenheiten eintragen
// darf (Menüpunkt sichtbar). Rückgabe: Boolean.
function darfIrgendeineAbwesenheit(){
return ['team.abw', 'team.abwProjekte', 'team.abwGewerk', 'team.abwAlle'].some(r => hatRecht(r));
}
function abwPersonen(){ return Array.isArray(db.globalPeople) ? db.globalPeople : []; }
function abwIch(){ return abwPersonen().find(p => currentUser && p.userId === currentUser.id) || null; }
function abwPerson(userId){ return abwPersonen().find(p => p.userId === userId) || null; }
function abwName(p){ return p ? (p.fullName || p.name || '') : ''; }
// abwDatum: "2026-11-02" -> "02.11." (mit Jahr, wenn mitJahr). Rückgabe: String.
function abwDatum(iso, mitJahr){
const d = parseDateISO(iso);
if(!d) return '';
const s = String(d.getDate()).padStart(2, '0') + '.' + String(d.getMonth() + 1).padStart(2, '0') + '.';
return mitJahr ? s + d.getFullYear() : s;
}
// abwZeitraum: lesbarer Zeitraum einer Abwesenheit.
function abwZeitraum(a){
if(a.start === a.end) return abwDatum(a.start, true);
const gleichesJahr = a.start.slice(0, 4) === a.end.slice(0, 4);
return abwDatum(a.start, !gleichesJahr) + ' – ' + abwDatum(a.end, true);
}
function abwArbeitstage(start, end){
const a = parseDateISO(start), b = parseDateISO(end);
return (a && b) ? workdaysBetween(a, b) : 0;
}
function abwUeberlappt(a1, e1, a2, e2){ return a1 <= e2 && a2 <= e1; }
// ---------- Rendern ----------
// Gestaltung „Variante 2 im Stil der Projektübersicht“ (Eriks Wahl): dieselben
// Bausteine wie die Projektliste — Titelzeile mit Legende (.legend), Glas-
// Karten (.board-list), Zeilen (.board-row/.board-row-main, .board-nr,
// .board-name, .board-status-line, .phase-pill, .board-row-actions) und
// Kürzel-Kreise in Fachrollen-Farbe (.board-avatar.role-*). Links „Neue
// Abwesenheit“, rechts die Liste der gewählten Person, darunter das Team je
// Gewerk in einer eigenen Karte.
const ABW_WOCHENTAG = ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'];
const ABW_WOCHENTAG_LANG = ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'];
// abwRollen: alle Fachrollen der Person in der festen Reihenfolge von ROLES
// (die gespeicherte Reihenfolge ist je Konto zufällig). Rückgabe: Array.
function abwRollen(p){
const eigene = Array.isArray(p && p.role) ? p.role : [];
return ROLES.filter(r => eigene.includes(r)).concat(eigene.filter(r => !ROLES.includes(r)));
}
// abwKreis: Kürzel-Kreis wie in der Projektliste, Farbe nach der ersten
// Fachrolle der Person (Reihenfolge ROLES). Rückgabe: HTML.
function abwKreis(p){
if(!p) return '';
const rollen = abwRollen(p);
const rolle = rollen.length ? roleClass(rollen[0]) : '';
return `${esc(p.name || '')}`;
}
function renderAbwesenheiten(){
const el = document.getElementById('abwesenheit');
if(!el) return;
if(!darfIrgendeineAbwesenheit()){
el.innerHTML = '
${abwTeamHtml()}`;
}
// abwStandardFuer: wer in der Erfassung vorausgewählt ist — immer die Person,
// die die Seite öffnet; hat ihr Konto keine Person (bzw. kein Recht für sich),
// die erste Person, für die sie eintragen darf. Rückgabe: userId oder null.
function abwStandardFuer(){
const ich = abwIch();
if(ich && ich.darfAbw) return ich.userId;
const erste = abwPersonen().filter(p => p.darfAbw).sort((a, b) => abwName(a).localeCompare(abwName(b), 'de'))[0];
return erste ? erste.userId : null;
}
// abwBeimOeffnen: beim Betreten des Bereichs (showTab) — Erfassung leer, für
// die eigene Person.
function abwBeimOeffnen(){
abwFuer = abwStandardFuer(); abwBearbeiten = null; abwLoeschFrage = null; abwFehler = '';
abwEntwurf = { start: '', end: '', type: 'Urlaub', note: '' };
}
// abwOhnePersonHtml: Platzhalter-Karte, wenn niemand wählbar ist.
function abwOhnePersonHtml(){
return `
Neue Abwesenheit
Für dein Konto lässt sich keine Abwesenheit eintragen: Es ist keiner Person zugeordnet (Person-ID fehlt). Ein Administrator kann das in der Benutzerverwaltung nachtragen.
`;
}
// abwFormularHtml: „Neue Abwesenheit“ (bzw. „Abwesenheit ändern“) als Karte.
function abwFormularHtml(erlaubte){
const p = abwPerson(abwFuer);
const e = abwEntwurf;
const ich = abwIch();
const istIch = p === ich;
const fuerAuswahl = erlaubte.length > 1 && !abwBearbeiten
? ``
: '';
const kachel = (titel, iso, unter) => {
const d = parseDateISO(iso);
return ``;
};
const tage = e.start ? abwArbeitstage(e.start, e.end) : 0;
const warnungen = e.start ? abwWarnungen(p, e.start, e.end) : [];
return `
'}`;
}
// abwGewerkKarteHtml: eine Karte je Gewerk — Tageskopf, dann eine Zeile je
// Person mit Kürzel-Kreis und je Abwesenheit ein Balken.
function abwGewerkKarteHtml(gewerk, personen){
const jahr = abwMonat.getFullYear(), monat = abwMonat.getMonth();
const tage = new Date(jahr, monat + 1, 0).getDate();
const heute = heuteIso();
const ich = abwIch();
const iso = d => jahr + '-' + String(monat + 1).padStart(2, '0') + '-' + String(d).padStart(2, '0');
const ersterIso = iso(1), letzterIso = iso(tage);
const sortiert = personen.slice().sort((a, b) => (a === ich ? -1 : b === ich ? 1 : abwName(a).localeCompare(abwName(b), 'de')));
const darfIrgendwen = sortiert.some(p => p.darfAbw);
let h = `