code auf einzelne Datein aufgeteilt
This commit is contained in:
1 parent
7451e0bf78
commit
8c94fc91f6
30 files changed
+5765
-27
No files matched your search
@@ -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;
|
||||
}
|
||||
Reference in new issue
Block a user