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,473 @@
|
||||
// -----------------------------
|
||||
// Projektplan — Modals (Meilenstein, TODO, Abwesenheit)
|
||||
// -----------------------------
|
||||
// Dieses Modul stellt die Öffnen-/Schließen-/Speichern-/Löschen-Funktionen
|
||||
// der drei Dialoge der Projektplan-Seite bereit: Meilenstein, TODO und
|
||||
// Abwesenheiten. Der Bearbeitungszustand liegt in window.PlanApp
|
||||
// (plan-state.js): currentMilestoneIndex, currentTodoIndex und
|
||||
// currentAbsencePersonId. Alle Änderungen werden über app.savePlan bzw.
|
||||
// app.saveGlobalPeople gespeichert und das GANTT über renderPlanGantt
|
||||
// (plan-gantt.js) neu gezeichnet. Bei gesperrten Wochen (app.isLocked) sind
|
||||
// alle Bearbeitungsfunktionen deaktiviert.
|
||||
//
|
||||
// Läuft wie alle plan-Module in einem IIFE; Zustand: window.PlanApp
|
||||
// (plan-state.js).
|
||||
|
||||
(function(){
|
||||
const app = window.PlanApp;
|
||||
|
||||
// ---------- Meilenstein-Modal ----------
|
||||
|
||||
// openMilestoneModal: öffnet das Meilenstein-Modal. Parameter: index
|
||||
// (Index des Meilensteins oder null für einen neuen). Befüllt Name, Datum,
|
||||
// Positions-Auswahl und die Vorgänger-Checkboxen (andere Meilensteine und
|
||||
// TODOs) aus dem Plan.
|
||||
window.openMilestoneModal = function(index){
|
||||
if(app.isLocked()) return;
|
||||
const plan = app.ensurePlanLocal();
|
||||
if(!Array.isArray(plan.milestones)) plan.milestones = [];
|
||||
// Migration alter Meilensteine
|
||||
plan.milestones.forEach(m => { if(!Array.isArray(m.dependsOn)) m.dependsOn = []; });
|
||||
app.currentMilestoneIndex = index;
|
||||
|
||||
const isNew = index === null || plan.milestones[index] === undefined;
|
||||
const currentMs = isNew ? null : plan.milestones[index];
|
||||
document.getElementById('milestoneEditName').value = isNew ? '' : (currentMs.name || '');
|
||||
document.getElementById('milestoneEditDate').value = isNew ? '' : (currentMs.date || '');
|
||||
|
||||
const positionSelect = document.getElementById('milestoneEditPosition');
|
||||
if(positionSelect){
|
||||
const currentPosition = currentMs && currentMs.position ? currentMs.position : '';
|
||||
const defaultAfterTodo = isNew && plan.todos && plan.todos.length > 0 ? plan.todos[plan.todos.length - 1].id : null;
|
||||
let opts = '<option value="">Oben in der Projektzeile</option>';
|
||||
if(plan.todos && plan.todos.length > 0){
|
||||
plan.todos.forEach(t => {
|
||||
const beforeVal = 'before:' + t.id;
|
||||
const afterVal = 'after:' + t.id;
|
||||
const beforeSel = currentPosition === beforeVal ? 'selected' : '';
|
||||
let afterSel = currentPosition === afterVal ? 'selected' : '';
|
||||
if(isNew && defaultAfterTodo === t.id && !currentPosition) afterSel = 'selected';
|
||||
opts += `<option value="${esc(beforeVal)}" ${beforeSel}>Vor: ${esc(t.title || 'TODO')}</option>`;
|
||||
opts += `<option value="${esc(afterVal)}" ${afterSel}>Nach: ${esc(t.title || 'TODO')}</option>`;
|
||||
});
|
||||
}
|
||||
positionSelect.innerHTML = opts;
|
||||
}
|
||||
|
||||
const dependsOnList = document.getElementById('milestoneEditDependsOn');
|
||||
if(dependsOnList){
|
||||
const currentDeps = currentMs && Array.isArray(currentMs.dependsOn) ? currentMs.dependsOn : [];
|
||||
let listHtml = '';
|
||||
|
||||
// Andere Meilensteine als Vorgänger anbieten
|
||||
if(plan.milestones.length > 1){
|
||||
listHtml += '<div class="todo-depends-group"><b>Meilensteine</b></div>';
|
||||
plan.milestones.forEach(m => {
|
||||
if(currentMs && m.id === currentMs.id) return;
|
||||
const mId = 'm_' + m.id;
|
||||
const checked = currentDeps.includes(mId) ? 'checked' : '';
|
||||
listHtml += `<label class="todo-depends-item"><input type="checkbox" value="${esc(mId)}" ${checked}> 🚩 ${esc(m.name || 'Meilenstein')} (${formatDateDE(parseDateISO(m.date))})</label>`;
|
||||
});
|
||||
}
|
||||
|
||||
// TODOs als Vorgänger anbieten
|
||||
if(plan.todos && plan.todos.length > 0){
|
||||
listHtml += '<div class="todo-depends-group"><b>Aufgaben</b></div>';
|
||||
(plan.todos || []).forEach(t => {
|
||||
const checked = currentDeps.includes(t.id) ? 'checked' : '';
|
||||
const datesStr = (t.start || t.end) ? ` (${formatDateDE(parseDateISO(t.start))}–${formatDateDE(parseDateISO(t.end))})` : '';
|
||||
listHtml += `<label class="todo-depends-item"><input type="checkbox" value="${esc(t.id)}" ${checked}> ${esc(t.title || 'TODO')}${datesStr}</label>`;
|
||||
});
|
||||
}
|
||||
if(listHtml === '') listHtml = '<span class="hint">Keine anderen Aufgaben oder Meilensteine vorhanden.</span>';
|
||||
dependsOnList.innerHTML = listHtml;
|
||||
}
|
||||
|
||||
document.getElementById('milestoneModal').style.display = 'flex';
|
||||
document.getElementById('milestoneEditName').focus();
|
||||
};
|
||||
|
||||
// closeMilestoneModal: blendet das Meilenstein-Modal aus und setzt den
|
||||
// Bearbeitungsindex zurück.
|
||||
window.closeMilestoneModal = function(){
|
||||
document.getElementById('milestoneModal').style.display = 'none';
|
||||
app.currentMilestoneIndex = null;
|
||||
};
|
||||
|
||||
// saveMilestoneEdit: übernimmt die Modal-Eingaben (Name, Datum, Position,
|
||||
// Vorgänger) in den Plan – als neuer Eintrag oder als Update des
|
||||
// aktuell bearbeiteten Meilensteins. Sortiert danach nach Datum, speichert
|
||||
// und rendert neu.
|
||||
window.saveMilestoneEdit = function(){
|
||||
if(app.isLocked()) return;
|
||||
const name = document.getElementById('milestoneEditName').value.trim();
|
||||
const date = document.getElementById('milestoneEditDate').value;
|
||||
if(!name || !date){ alert('Bitte Name und Datum angeben.'); return; }
|
||||
|
||||
const dependsOnList = document.getElementById('milestoneEditDependsOn');
|
||||
const dependsOn = dependsOnList ? Array.from(dependsOnList.querySelectorAll('input[type="checkbox"]:checked')).map(cb => cb.value).filter(Boolean) : [];
|
||||
|
||||
const positionSelect = document.getElementById('milestoneEditPosition');
|
||||
const position = positionSelect ? positionSelect.value : '';
|
||||
|
||||
const plan = app.ensurePlanLocal();
|
||||
if(!Array.isArray(plan.milestones)) plan.milestones = [];
|
||||
|
||||
if(app.currentMilestoneIndex === null){
|
||||
plan.milestones.push({ id: uid(), name, date, dependsOn, position });
|
||||
} else if(plan.milestones[app.currentMilestoneIndex]){
|
||||
const m = plan.milestones[app.currentMilestoneIndex];
|
||||
m.name = name;
|
||||
m.date = date;
|
||||
m.dependsOn = dependsOn;
|
||||
m.position = position;
|
||||
}
|
||||
plan.milestones.sort((a,b) => parseDateISO(a.date) - parseDateISO(b.date));
|
||||
app.savePlan();
|
||||
closeMilestoneModal();
|
||||
renderPlanGantt();
|
||||
};
|
||||
|
||||
// deleteMilestoneEdit: löscht nach Bestätigung den aktuell bearbeiteten
|
||||
// Meilenstein samt seiner Vorgänger-Verweise (app.removeDependencyRefs),
|
||||
// speichert und rendert neu.
|
||||
window.deleteMilestoneEdit = function(){
|
||||
if(app.isLocked()) return;
|
||||
if(app.currentMilestoneIndex === null){ closeMilestoneModal(); return; }
|
||||
if(!confirm('Meilenstein löschen?')) return;
|
||||
const plan = app.ensurePlanLocal();
|
||||
if(plan.milestones && plan.milestones[app.currentMilestoneIndex]){
|
||||
const deletedId = 'm_' + plan.milestones[app.currentMilestoneIndex].id;
|
||||
plan.milestones.splice(app.currentMilestoneIndex, 1);
|
||||
app.removeDependencyRefs(plan, deletedId);
|
||||
app.savePlan();
|
||||
}
|
||||
closeMilestoneModal();
|
||||
renderPlanGantt();
|
||||
};
|
||||
|
||||
// addPlanMilestone: öffnet das Meilenstein-Modal für einen neuen Eintrag.
|
||||
window.addPlanMilestone = function(){
|
||||
openMilestoneModal(null);
|
||||
};
|
||||
|
||||
// ---------- TODO-Modal ----------
|
||||
|
||||
// openTodoModal: öffnet das TODO-Modal. Parameter: index (Index der Aufgabe
|
||||
// oder null für eine neue). Befüllt Titel, Start/Ende, Erledigt-Status,
|
||||
// Fortschritt, Vorgänger-Checkboxen und die aus dem Projektboard
|
||||
// stammenden Bearbeiter-Auswahl.
|
||||
window.openTodoModal = function(index){
|
||||
if(app.isLocked()) return;
|
||||
const plan = app.ensurePlanLocal();
|
||||
if(!Array.isArray(plan.todos)) plan.todos = [];
|
||||
app.currentTodoIndex = index;
|
||||
|
||||
const isNew = index === null || plan.todos[index] === undefined;
|
||||
const currentTodo = !isNew ? plan.todos[index] : null;
|
||||
|
||||
document.getElementById('todoEditTitle').value = isNew ? '' : (currentTodo.title || '');
|
||||
document.getElementById('todoEditStart').value = isNew ? '' : (currentTodo.start || '');
|
||||
document.getElementById('todoEditEnd').value = isNew ? '' : (currentTodo.end || '');
|
||||
document.getElementById('todoEditDone').checked = isNew ? false : !!currentTodo.done;
|
||||
const progress = isNew ? 0 : (parseInt(currentTodo.progress, 10) || 0);
|
||||
document.getElementById('todoEditProgress').value = progress;
|
||||
document.getElementById('todoEditProgressVal').textContent = progress + '%';
|
||||
|
||||
const dependsOnList = document.getElementById('todoEditDependsOn');
|
||||
if(dependsOnList){
|
||||
const currentDeps = currentTodo && Array.isArray(currentTodo.dependsOn) ? currentTodo.dependsOn : [];
|
||||
let listHtml = '';
|
||||
|
||||
// Meilensteine als Vorgänger anbieten
|
||||
if(Array.isArray(plan.milestones) && plan.milestones.length > 0){
|
||||
listHtml += '<div class="todo-depends-group"><b>Meilensteine</b></div>';
|
||||
plan.milestones.forEach(m => {
|
||||
const mId = 'm_' + m.id;
|
||||
const checked = currentDeps.includes(mId) ? 'checked' : '';
|
||||
listHtml += `<label class="todo-depends-item"><input type="checkbox" value="${esc(mId)}" ${checked}> 🚩 ${esc(m.name || 'Meilenstein')} (${formatDateDE(parseDateISO(m.date))})</label>`;
|
||||
});
|
||||
}
|
||||
|
||||
// Andere TODOs als Vorgänger anbieten
|
||||
if(plan.todos && plan.todos.length > 1){
|
||||
listHtml += '<div class="todo-depends-group"><b>Aufgaben</b></div>';
|
||||
(plan.todos || []).forEach(t => {
|
||||
if(currentTodo && t.id === currentTodo.id) return;
|
||||
const checked = currentDeps.includes(t.id) ? 'checked' : '';
|
||||
const datesStr = (t.start || t.end) ? ` (${formatDateDE(parseDateISO(t.start))}–${formatDateDE(parseDateISO(t.end))})` : '';
|
||||
listHtml += `<label class="todo-depends-item"><input type="checkbox" value="${esc(t.id)}" ${checked}> ${esc(t.title || 'TODO')}${datesStr}</label>`;
|
||||
});
|
||||
}
|
||||
if(listHtml === '') listHtml = '<span class="hint">Keine anderen Aufgaben oder Meilensteine vorhanden.</span>';
|
||||
dependsOnList.innerHTML = listHtml;
|
||||
}
|
||||
|
||||
const assigneesDiv = document.getElementById('todoEditAssignees');
|
||||
if(assigneesDiv){
|
||||
const assignees = projectAssignees(app.project, app.week);
|
||||
const currentAssignees = currentTodo && Array.isArray(currentTodo.assignees) ? currentTodo.assignees : [];
|
||||
const seenPeople = new Set();
|
||||
const uniquePeople = [];
|
||||
assignees.forEach(a => {
|
||||
if(!seenPeople.has(a.person.id)){
|
||||
seenPeople.add(a.person.id);
|
||||
uniquePeople.push(a.person);
|
||||
}
|
||||
});
|
||||
assigneesDiv.innerHTML = uniquePeople.map(person => {
|
||||
const checked = currentAssignees.includes(person.id) ? 'checked' : '';
|
||||
return `<label><input type="checkbox" value="${esc(person.id)}" ${checked}> ${esc(person.name)}</label>`;
|
||||
}).join('') || '<span class="hint">Keine Projektzuordnungen im Board.</span>';
|
||||
}
|
||||
|
||||
document.getElementById('todoModal').style.display = 'flex';
|
||||
document.getElementById('todoEditTitle').focus();
|
||||
};
|
||||
|
||||
// closeTodoModal: blendet das TODO-Modal aus und setzt den Bearbeitungsindex
|
||||
// zurück.
|
||||
window.closeTodoModal = function(){
|
||||
document.getElementById('todoModal').style.display = 'none';
|
||||
app.currentTodoIndex = null;
|
||||
};
|
||||
|
||||
// saveTodoEdit: übernimmt die Modal-Eingaben in den Plan – als neuer TODO
|
||||
// oder Update des aktuell bearbeiteten. Fehlende End-/Startwerte werden
|
||||
// als eintägige Aufgabe ergänzt, „Erledigt“ setzt den Fortschritt auf
|
||||
// 100 %. Speichert und rendert neu.
|
||||
window.saveTodoEdit = function(){
|
||||
if(app.isLocked()) return;
|
||||
const title = document.getElementById('todoEditTitle').value.trim();
|
||||
let start = document.getElementById('todoEditStart').value;
|
||||
let end = document.getElementById('todoEditEnd').value;
|
||||
// Nur ein Datum angegeben: als eintägige Aufgabe behandeln,
|
||||
// sonst würde kein Balken im GANTT erscheinen.
|
||||
if(start && !end) end = start;
|
||||
if(end && !start) start = end;
|
||||
const done = document.getElementById('todoEditDone').checked;
|
||||
let progress = parseInt(document.getElementById('todoEditProgress').value, 10) || 0;
|
||||
// "Erledigt" impliziert 100 % Fortschritt
|
||||
if(done && progress < 100) progress = 100;
|
||||
|
||||
const dependsOnList = document.getElementById('todoEditDependsOn');
|
||||
const dependsOn = dependsOnList ? Array.from(dependsOnList.querySelectorAll('input[type="checkbox"]:checked')).map(cb => cb.value).filter(Boolean) : [];
|
||||
|
||||
const assigneesDiv = document.getElementById('todoEditAssignees');
|
||||
const assignees = assigneesDiv ? Array.from(assigneesDiv.querySelectorAll('input:checked')).map(cb => cb.value) : [];
|
||||
|
||||
if(!title){ alert('Bitte einen Titel eingeben.'); return; }
|
||||
if(start && end && parseDateISO(end) < parseDateISO(start)){ alert('Enddatum liegt vor Startdatum.'); return; }
|
||||
|
||||
const plan = app.ensurePlanLocal();
|
||||
if(!Array.isArray(plan.todos)) plan.todos = [];
|
||||
|
||||
if(app.currentTodoIndex === null){
|
||||
plan.todos.push({ id: uid(), title, start, end, done, progress, dependsOn, assignees });
|
||||
} else if(plan.todos[app.currentTodoIndex]){
|
||||
const t = plan.todos[app.currentTodoIndex];
|
||||
t.title = title;
|
||||
t.start = start;
|
||||
t.end = end;
|
||||
t.done = done;
|
||||
t.progress = progress;
|
||||
t.dependsOn = dependsOn;
|
||||
t.assignees = assignees;
|
||||
}
|
||||
app.savePlan();
|
||||
closeTodoModal();
|
||||
renderPlanGantt();
|
||||
};
|
||||
|
||||
// deleteTodoEdit: löscht nach Bestätigung die aktuell bearbeitete Aufgabe,
|
||||
// entfernt ihre Vorgänger-Verweise und setzt die relative Position
|
||||
// betroffener Meilensteine zurück. Speichert und rendert neu.
|
||||
window.deleteTodoEdit = function(){
|
||||
if(app.isLocked()) return;
|
||||
if(app.currentTodoIndex === null){ closeTodoModal(); return; }
|
||||
if(!confirm('TODO löschen?')) return;
|
||||
const plan = app.ensurePlanLocal();
|
||||
if(plan.todos && plan.todos[app.currentTodoIndex]){
|
||||
const deletedId = plan.todos[app.currentTodoIndex].id;
|
||||
plan.todos.splice(app.currentTodoIndex, 1);
|
||||
app.removeDependencyRefs(plan, deletedId);
|
||||
// Meilensteine, die relativ zu dieser TODO positioniert waren, zurücksetzen
|
||||
(plan.milestones || []).forEach(m => {
|
||||
if(m.position === 'before:' + deletedId || m.position === 'after:' + deletedId) m.position = '';
|
||||
});
|
||||
app.savePlan();
|
||||
}
|
||||
closeTodoModal();
|
||||
renderPlanGantt();
|
||||
};
|
||||
|
||||
// addTodoFromList: legt eine neue Aufgabe aus den Eingabefeldern der
|
||||
// TODO-Liste an (Titel, Start/Ende), leert die Felder und rendert neu.
|
||||
window.addTodoFromList = function(){
|
||||
if(app.isLocked()) return;
|
||||
const title = document.getElementById('newTodoTitle').value.trim();
|
||||
let start = document.getElementById('newTodoStart').value;
|
||||
let end = document.getElementById('newTodoEnd').value;
|
||||
if(start && !end) end = start;
|
||||
if(end && !start) start = end;
|
||||
if(!title){ alert('Bitte einen Titel eingeben.'); return; }
|
||||
if(start && end && parseDateISO(end) < parseDateISO(start)){ alert('Enddatum liegt vor Startdatum.'); return; }
|
||||
const plan = app.ensurePlanLocal();
|
||||
plan.todos.push({ id: uid(), title, start, end, done: false, progress: 0, dependsOn: [], assignees: [] });
|
||||
document.getElementById('newTodoTitle').value = '';
|
||||
document.getElementById('newTodoStart').value = '';
|
||||
document.getElementById('newTodoEnd').value = '';
|
||||
app.savePlan();
|
||||
renderPlanGantt();
|
||||
};
|
||||
|
||||
// addTodo: öffnet das TODO-Modal für eine neue Aufgabe.
|
||||
window.addTodo = function(){
|
||||
openTodoModal(null);
|
||||
};
|
||||
|
||||
// updateTodo: setzt ein einzelnes Feld (z. B. done) einer Aufgabe direkt
|
||||
// und speichert sowie rendert neu. Parameter: idx (Index), field
|
||||
// (Feldname), value (neuer Wert).
|
||||
window.updateTodo = function(idx, field, value){
|
||||
if(app.isLocked()) return;
|
||||
const plan = app.ensurePlanLocal();
|
||||
if(!plan.todos[idx]) return;
|
||||
plan.todos[idx][field] = value;
|
||||
app.savePlan();
|
||||
renderPlanGantt();
|
||||
};
|
||||
|
||||
// toggleTodoDone: schaltet den Erledigt-Status der Aufgabe am Index um,
|
||||
// speichert und rendert neu.
|
||||
window.toggleTodoDone = function(idx){
|
||||
if(app.isLocked()) return;
|
||||
const plan = app.ensurePlanLocal();
|
||||
if(!plan.todos[idx]) return;
|
||||
plan.todos[idx].done = !plan.todos[idx].done;
|
||||
app.savePlan();
|
||||
renderPlanGantt();
|
||||
};
|
||||
|
||||
// deleteTodo: löscht nach Bestätigung die Aufgabe am Index, bereinigt die
|
||||
// Vorgänger-Verweise und die relative Meilenstein-Position. Speichert und
|
||||
// rendert neu.
|
||||
window.deleteTodo = function(idx){
|
||||
if(app.isLocked()) return;
|
||||
if(!confirm('TODO löschen?')) return;
|
||||
const plan = app.ensurePlanLocal();
|
||||
if(!plan.todos[idx]) return;
|
||||
const deletedId = plan.todos[idx].id;
|
||||
plan.todos.splice(idx, 1);
|
||||
app.removeDependencyRefs(plan, deletedId);
|
||||
(plan.milestones || []).forEach(m => {
|
||||
if(m.position === 'before:' + deletedId || m.position === 'after:' + deletedId) m.position = '';
|
||||
});
|
||||
app.savePlan();
|
||||
renderPlanGantt();
|
||||
};
|
||||
|
||||
// ---------- Abwesenheits-Modal (Urlaube der Projektbeteiligten) ----------
|
||||
|
||||
// openAbsenceModal: öffnet das Abwesenheits-Modal für die übergebene
|
||||
// Person-ID, setzt den Titel, leert die Formularfelder und rendert die
|
||||
// Liste der bestehenden Abwesenheiten.
|
||||
window.openAbsenceModal = function(personId){
|
||||
const person = app.findGlobalPerson(personId);
|
||||
if(!person) return;
|
||||
app.currentAbsencePersonId = personId;
|
||||
document.getElementById('absenceModalTitle').textContent = 'Abwesenheiten – ' + (person.fullName || person.name);
|
||||
// Formularfelder zurücksetzen
|
||||
const startInput = document.getElementById('absenceEditStart');
|
||||
const endInput = document.getElementById('absenceEditEnd');
|
||||
const rangeDisplay = document.getElementById('absenceEditRange');
|
||||
if(startInput) startInput.value = '';
|
||||
if(endInput) endInput.value = '';
|
||||
if(rangeDisplay) rangeDisplay.value = '';
|
||||
renderAbsenceModalList();
|
||||
document.getElementById('absenceModal').style.display = 'flex';
|
||||
};
|
||||
|
||||
// closeAbsenceModal: blendet das Abwesenheits-Modal aus und setzt die
|
||||
// aktuelle Person-ID zurück.
|
||||
window.closeAbsenceModal = function(){
|
||||
document.getElementById('absenceModal').style.display = 'none';
|
||||
app.currentAbsencePersonId = null;
|
||||
};
|
||||
|
||||
// renderAbsenceModalList: rendert die Liste der Abwesenheiten der aktuellen
|
||||
// Person im Modal (jeweils Typ, Zeitraum, Notiz und Lösch-Button). Bei
|
||||
// gesperrter Woche wird das Formular ausgeblendet.
|
||||
function renderAbsenceModalList(){
|
||||
const listDiv = document.getElementById('absenceModalList');
|
||||
const person = app.findGlobalPerson(app.currentAbsencePersonId);
|
||||
if(!listDiv || !person) return;
|
||||
const absences = Array.isArray(person.absences) ? person.absences : [];
|
||||
const locked = app.isLocked();
|
||||
if(absences.length === 0){
|
||||
listDiv.innerHTML = '<span class="hint">Keine Abwesenheiten eingetragen.</span>';
|
||||
} else {
|
||||
listDiv.innerHTML = '<div class="absence-list">' + absences.map(a => `
|
||||
<div class="absence-chip">
|
||||
<span>${esc(a.type || 'Abwesenheit')}: ${formatDateDE(parseDateISO(a.start))}–${formatDateDE(parseDateISO(a.end))}</span>
|
||||
${a.note ? `<small>${esc(a.note)}</small>` : ''}
|
||||
${locked ? '' : `<button class="danger small" onclick="deletePlanAbsence('${esc(a.id)}')">×</button>`}
|
||||
</div>`).join('') + '</div>';
|
||||
}
|
||||
const form = document.getElementById('absenceModalForm');
|
||||
if(form) form.style.display = locked ? 'none' : '';
|
||||
}
|
||||
|
||||
// addPlanAbsence: legt aus den Formularfeldern eine neue Abwesenheit für die
|
||||
// aktuelle Person an – in der globalen Personenliste und in der lokalen
|
||||
// Sitzungs-Kopie. Speichert beides und rendert Liste sowie GANTT neu.
|
||||
window.addPlanAbsence = function(){
|
||||
if(app.isLocked()) return;
|
||||
const person = app.findGlobalPerson(app.currentAbsencePersonId);
|
||||
if(!person) return;
|
||||
const start = document.getElementById('absenceEditStart').value;
|
||||
const end = document.getElementById('absenceEditEnd').value;
|
||||
const type = document.getElementById('absenceEditType').value;
|
||||
const note = document.getElementById('absenceEditNote').value.trim();
|
||||
if(!start || !end){ alert('Bitte Start- und Enddatum angeben.'); return; }
|
||||
if(parseDateISO(end) < parseDateISO(start)){ alert('Enddatum liegt vor Startdatum.'); return; }
|
||||
if(!Array.isArray(person.absences)) person.absences = [];
|
||||
const absence = { id: uid(), start, end, type, note };
|
||||
person.absences.push(absence);
|
||||
person.absences.sort((a,b) => parseDateISO(a.start) - parseDateISO(b.start));
|
||||
// Auch die Sitzungs-lokale Kopie aktualisieren, damit die Anzeige konsistent bleibt
|
||||
const localPerson = (app.week.people || []).find(p => p.id === app.currentAbsencePersonId);
|
||||
if(localPerson){
|
||||
if(!Array.isArray(localPerson.absences)) localPerson.absences = [];
|
||||
localPerson.absences.push({ ...absence });
|
||||
localPerson.absences.sort((a,b) => parseDateISO(a.start) - parseDateISO(b.start));
|
||||
}
|
||||
app.saveGlobalPeople();
|
||||
app.savePlan();
|
||||
document.getElementById('absenceEditStart').value = '';
|
||||
document.getElementById('absenceEditEnd').value = '';
|
||||
const rangeDisplay = document.getElementById('absenceEditRange');
|
||||
if(rangeDisplay) rangeDisplay.value = '';
|
||||
document.getElementById('absenceEditNote').value = '';
|
||||
renderAbsenceModalList();
|
||||
renderPlanGantt();
|
||||
};
|
||||
|
||||
// deletePlanAbsence: entfernt die Abwesenheit mit der übergebenen ID aus
|
||||
// der globalen Personenliste und der lokalen Sitzungs-Kopie, speichert
|
||||
// beides und rendert Liste sowie GANTT neu.
|
||||
window.deletePlanAbsence = function(absenceId){
|
||||
if(app.isLocked()) return;
|
||||
const person = app.findGlobalPerson(app.currentAbsencePersonId);
|
||||
if(!person || !Array.isArray(person.absences)) return;
|
||||
person.absences = person.absences.filter(a => a.id !== absenceId);
|
||||
const localPerson = (app.week.people || []).find(p => p.id === app.currentAbsencePersonId);
|
||||
if(localPerson && Array.isArray(localPerson.absences)){
|
||||
localPerson.absences = localPerson.absences.filter(a => a.id !== absenceId);
|
||||
}
|
||||
app.saveGlobalPeople();
|
||||
app.savePlan();
|
||||
renderAbsenceModalList();
|
||||
renderPlanGantt();
|
||||
};
|
||||
})();
|
||||
Reference in new issue
Block a user