code auf einzelne Datein aufgeteilt

This commit is contained in:
erik committed 2026-08-13 16:07:29 +02:00
1 parent 7451e0bf78
commit 8c94fc91f6
30 files changed
+5765 -27

No files matched your search

+221
View File
@@ -0,0 +1,221 @@
// -----------------------------
// Projektübersicht — Datumsbereich-Picker
// Ein Kalender-Popup für Von+Bis: erster Klick setzt das Startdatum,
// zweiter Klick das Enddatum (bei Klick vor dem Start wird getauscht).
// Schreibt ISO-Werte in zwei (versteckte) Inputs und aktualisiert optional
// ein Anzeige-Element. Verwendung: openRangePicker({ anchor, startInputId, endInputId, displayId, onApply })
// -----------------------------
// Diese Datei wird nach js/utils.js von beiden HTML-Einstiegspunkten geladen
// und stellt den Kalender-Picker (Einzeldatum "openDatePicker" und Bereich
// "openRangePicker") bereit. Der Zustand des offenen Popups liegt in der
// Modulvariablen rangePickerState. Genutzt wird der Picker v. a. von
// js/load.js (LP-Bereiche), js/people.js (Abwesenheiten) und den
// Plan-Modulen unter js/plan/* (z. B. GANTT-Datumsangaben in plan-modals.js).
// rangePickerState: Zustand des aktuell geöffneten Picker-Popups (Optionen,
// Start-/Enddatum, Ansichtsmonat); null solange kein Popup offen ist.
let rangePickerState = null;
// openDatePicker: öffnet den Kalender zur Auswahl eines einzelnen Datums
// Parameter: opts mit inputId, optional anchor, displayId, onApply. Rückgabe: keine.
function openDatePicker(opts){
closeRangePicker();
const input = document.getElementById(opts.inputId);
if(!input) return;
const preValue = parseDateISO(input.value);
const today = new Date();
rangePickerState = {
opts: Object.assign({}, opts, { mode: 'single' }),
start: preValue,
end: preValue,
mode: 'single',
// Ansichtsmonat: vorhandener Wert, sonst heutiger Monat
viewYear: (preValue || today).getFullYear(),
viewMonth: (preValue || today).getMonth()
};
const pop = document.createElement('div');
pop.id = 'rangePickerPopup';
pop.className = 'range-picker';
document.body.appendChild(pop);
const anchorRect = opts.anchor ? opts.anchor.getBoundingClientRect() : { left: window.innerWidth/2 - 140, bottom: window.innerHeight/2 - 160 };
pop.style.left = Math.max(8, Math.min(anchorRect.left, window.innerWidth - 296)) + 'px';
pop.style.top = Math.min(anchorRect.bottom + 6, window.innerHeight - 340) + 'px';
renderRangePicker();
setTimeout(() => {
document.addEventListener('mousedown', rangePickerOutsideClick);
}, 0);
}
// openRangePicker: öffnet den Kalender zur Auswahl eines Von-Bis-Datumsbereichs
// (oder eines Einzeldatums bei mode:'single'). Parameter: opts mit startInputId,
// endInputId, optional anchor, displayId, mode, onApply. Rückgabe: keine.
function openRangePicker(opts){
closeRangePicker();
const isSingle = (opts.mode || 'range') === 'single';
const startInput = document.getElementById(isSingle ? (opts.inputId || opts.startInputId) : opts.startInputId);
const endInput = isSingle ? null : document.getElementById(opts.endInputId);
if(!startInput || (!isSingle && !endInput)) return;
const preStart = parseDateISO(startInput.value);
const preEnd = isSingle ? preStart : parseDateISO(endInput.value);
const today = new Date();
rangePickerState = {
opts: Object.assign({}, opts, { mode: isSingle ? 'single' : 'range' }),
start: preStart,
end: preEnd,
mode: isSingle ? 'single' : 'range',
// Ansichtsmonat: vorhandener Start, sonst heutiger Monat
viewYear: (preStart || today).getFullYear(),
viewMonth: (preStart || today).getMonth()
};
const pop = document.createElement('div');
pop.id = 'rangePickerPopup';
pop.className = 'range-picker';
document.body.appendChild(pop);
// Position: unterhalb des Ankers, innerhalb des Viewports halten
const anchorRect = opts.anchor ? opts.anchor.getBoundingClientRect() : { left: window.innerWidth/2 - 140, bottom: window.innerHeight/2 - 160 };
pop.style.left = Math.max(8, Math.min(anchorRect.left, window.innerWidth - 296)) + 'px';
pop.style.top = Math.min(anchorRect.bottom + 6, window.innerHeight - 340) + 'px';
renderRangePicker();
// Klick außerhalb schließt das Popup (ohne Übernahme)
setTimeout(() => {
document.addEventListener('mousedown', rangePickerOutsideClick);
}, 0);
}
// rangePickerOutsideClick: schließt das Popup, wenn außerhalb davon geklickt wird
// Parameter: e (Mousedown-Event). Rückgabe: keine.
function rangePickerOutsideClick(e){
const pop = document.getElementById('rangePickerPopup');
if(pop && !pop.contains(e.target)) closeRangePicker();
}
// closeRangePicker: entfernt das Popup wieder und setzt rangePickerState zurück
// Parameter: keine. Rückgabe: keine.
function closeRangePicker(){
const pop = document.getElementById('rangePickerPopup');
if(pop) pop.remove();
document.removeEventListener('mousedown', rangePickerOutsideClick);
rangePickerState = null;
}
// rangePickerNav: blättert im Kalender um einen Monat vor/zurück und rendert neu
// Parameter: delta (Number, -1 oder 1). Rückgabe: keine.
function rangePickerNav(delta){
if(!rangePickerState) return;
let m = rangePickerState.viewMonth + delta;
let y = rangePickerState.viewYear;
if(m < 0){ m = 11; y--; }
if(m > 11){ m = 0; y++; }
rangePickerState.viewMonth = m;
rangePickerState.viewYear = y;
renderRangePicker();
}
// rangePickerSelect: übernimmt einen Klick auf einen Kalendertag; im Bereichsmodus
// wird beim ersten Klick der Start und beim zweiten das Ende (ggf. getauscht) gesetzt
// Parameter: iso (String, ausgewähltes Datum). Rückgabe: keine.
function rangePickerSelect(iso){
if(!rangePickerState) return;
const d = parseDateISO(iso);
if(!d) return;
const st = rangePickerState;
if(st.mode === 'single'){
st.start = d;
st.end = d;
applyRangePicker();
return;
}
if(!st.start || (st.start && st.end)){
// Neuen Bereich beginnen
st.start = d;
st.end = null;
} else {
// Zweiter Klick: Ende setzen, bei Bedarf tauschen
if(d < st.start){ st.end = st.start; st.start = d; }
else st.end = d;
applyRangePicker();
return;
}
renderRangePicker();
}
// applyRangePicker: schreibt die gewählten Daten in die (versteckten) Inputs bzw.
// das Anzeige-Element, schließt das Popup und ruft den onApply-Callback auf
// Parameter: keine. Rückgabe: keine.
function applyRangePicker(){
if(!rangePickerState) return;
const { opts, start, end } = rangePickerState;
const isSingle = (opts.mode || 'range') === 'single';
const startInput = document.getElementById(isSingle ? (opts.inputId || opts.startInputId) : opts.startInputId);
const endInput = isSingle ? null : document.getElementById(opts.endInputId);
const selected = start || end;
if(startInput) startInput.value = isSingle ? (selected ? formatDateISO(selected) : '') : (start ? formatDateISO(start) : '');
if(endInput) endInput.value = end ? formatDateISO(end) : (start ? formatDateISO(start) : '');
if(opts.displayId){
const disp = document.getElementById(opts.displayId);
if(disp){
const from = start ? formatDateDE(start) : '';
const to = end ? formatDateDE(end) : from;
disp.value = isSingle ? (from || '') : (from ? (from + ' – ' + to) : '');
}
}
const cb = opts.onApply;
closeRangePicker();
if(typeof cb === 'function') cb();
}
// renderRangePicker: baut die Kalender-HTML für den aktuellen Ansichtsmonat und
// zeigt Start/Ende-Auswahl markiert an. Parameter: keine. Rückgabe: keine.
function renderRangePicker(){
const pop = document.getElementById('rangePickerPopup');
if(!pop || !rangePickerState) return;
const st = rangePickerState;
const monthNames = ['Januar','Februar','März','April','Mai','Juni','Juli','August','September','Oktober','November','Dezember'];
const first = new Date(st.viewYear, st.viewMonth, 1);
// Wochentag des Monatsersten, Montag = 0
const firstWeekday = (first.getDay() + 6) % 7;
const daysInMonth = new Date(st.viewYear, st.viewMonth + 1, 0).getDate();
const today = new Date();
const todayMid = new Date(today.getFullYear(), today.getMonth(), today.getDate());
let hint;
if(st.mode === 'single') hint = 'Datum wählen';
else if(!st.start || (st.start && st.end)) hint = 'Startdatum wählen';
else hint = 'Enddatum wählen (Start: ' + formatDateDE(st.start) + ')';
let html = `<div class="range-picker-head">
<button type="button" class="secondary small" onclick="rangePickerNav(-1)">‹</button>
<span class="range-picker-title">${monthNames[st.viewMonth]} ${st.viewYear}</span>
<button type="button" class="secondary small" onclick="rangePickerNav(1)">›</button>
</div>
<div class="range-picker-hint">${hint}</div>
<div class="range-picker-grid">`;
['Mo','Di','Mi','Do','Fr','Sa','So'].forEach(w => html += `<div class="range-picker-weekday">${w}</div>`);
for(let i = 0; i < firstWeekday; i++) html += '<div></div>';
for(let day = 1; day <= daysInMonth; day++){
const d = new Date(st.viewYear, st.viewMonth, day);
const iso = formatDateISO(d);
const classes = ['range-picker-day'];
if(d.getDay() === 0 || d.getDay() === 6) classes.push('weekend');
if(d.getTime() === todayMid.getTime()) classes.push('today');
if(st.start && d.getTime() === st.start.getTime()) classes.push('sel-start');
if(st.end && d.getTime() === st.end.getTime()) classes.push('sel-end');
if(st.start && st.end && d > st.start && d < st.end) classes.push('in-range');
html += `<div class="${classes.join(' ')}" onclick="rangePickerSelect('${iso}')">${day}</div>`;
}
html += '</div>';
html += `<div class="range-picker-footer">
<button type="button" class="secondary small" onclick="closeRangePicker()">Abbrechen</button>
${st.mode !== 'single' && st.start && !st.end ? '<button type="button" class="small" onclick="rangePickerState.end = rangePickerState.start; applyRangePicker()">Nur dieser Tag</button>' : ''}
</div>`;
pop.innerHTML = html;
}