173 lines
7.7 KiB
JavaScript
173 lines
7.7 KiB
JavaScript
// -----------------------------
|
|
// Projektübersicht — Projekte (Listen-Ansicht)
|
|
// -----------------------------
|
|
// Diese Datei rendert den Projekte-Tab (Tab "Projekte" in `Test.html`) als
|
|
// sortierbare Listenansicht: Projekte werden nach Gruppe ("Aktive Projekte",
|
|
// "Kleine Projekte" usw.) gruppiert, inline bearbeitbar und per
|
|
// Drag-and-Drop umsortiert. Sie wird von `Test.html` nach `js/people/people.js`
|
|
// (vor `js/load/load.js`) geladen und stellt globale Funktionen wie
|
|
// `renderProjects()`, `editProjectField()`, `editProjectActiveLP()` sowie die
|
|
// Drag-and-Drop-Handler und die Sortier-Hilfe `getDragAfterElement()` bereit.
|
|
// Letztere wird auch von `js/people/people.js` (Personen-Sortierung) genutzt;
|
|
// `renderBoard()` aus `js/board/board.js` wird nach Änderungen aktualisiert.
|
|
|
|
// delProjectFromList: löscht ein Projekt (per Bestätigungsdialog) in der
|
|
// Listen-Ansicht. Parameter `id`: Projekt-ID. Keine Rückgabe; bei gesperrter
|
|
// Sitzung abgebrochen.
|
|
function delProjectFromList(id){
|
|
if(guardWeekLocked()) return;
|
|
if(!confirm("Projekt aus dieser Sitzung löschen?")) return;
|
|
const w = currentWeek();
|
|
w.projects = w.projects.filter(p=>p.id!==id);
|
|
save(); renderProjects(); renderBoard();
|
|
}
|
|
|
|
// editProjectField: ändert ein Feld eines Projekts (z.B. Name oder Gruppe).
|
|
// Parameter: `id` (Projekt-ID), `field` (Feldname), `val` (neuer Wert).
|
|
// Keine Rückgabe.
|
|
function editProjectField(id,field,val){
|
|
if(guardWeekLocked()) return;
|
|
const p = currentWeek().projects.find(x=>x.id===id);
|
|
if(p){ p[field]=val; save(); renderProjects(); renderBoard(); }
|
|
}
|
|
|
|
// editProjectActiveLP: setzt bzw. hebt die amtierende Leistungsphase eines
|
|
// Projekts auf (`p.aktuelleLP`). Parameter: `id` (Projekt-ID), `value`
|
|
// (LPH-Name oder leer). Aktualisiert Projekte, Board und Auslastung.
|
|
function editProjectActiveLP(id, value){
|
|
if(guardWeekLocked()) return;
|
|
const p = currentWeek().projects.find(x=>x.id===id);
|
|
if(!p) return;
|
|
if(value) p.aktuelleLP = [value];
|
|
else delete p.aktuelleLP;
|
|
save(); renderProjects(); renderBoard(); renderLoad();
|
|
}
|
|
|
|
// renderProjects: baut die HTML-Liste der Projekte auf, gruppiert nach `group`
|
|
// und sortiert je Gruppe nach `order`. Jedes Projekt erhält Felder für Nr.,
|
|
// Name, Gruppe, aktive LP sowie Drag-and-Drop-Handler. Keine Parameter, keine
|
|
// Rückgabe.
|
|
function renderProjects(){
|
|
const w = currentWeek();
|
|
if(!w) return;
|
|
let html="";
|
|
const groups = {};
|
|
w.projects.forEach(p=>{ (groups[p.group]||(groups[p.group]=[])).push(p); });
|
|
Object.keys(groups).forEach(g=>{
|
|
groups[g].sort((a,b)=>(a.order||0)-(b.order||0));
|
|
});
|
|
|
|
sortGroups(Object.keys(groups)).forEach(g=>{
|
|
html += `<div class="section-title">${esc(g)}</div>`;
|
|
html += `<div class="projects-sortable" data-group="${g}">`;
|
|
groups[g].forEach(p=>{
|
|
html += `<div class="person-list-item" draggable="true" data-project-id="${p.id}" ondragstart="handleDragStart(event)" ondragover="handleDragOver(event)" ondrop="handleDrop(event)" ondragend="handleDragEnd(event)">
|
|
<span class="drag-handle" style="cursor:grab;color:#999;margin-right:8px">⋮⋮</span>
|
|
<input type="text" value="${esc(p.nr)}" onchange="editProjectField('${p.id}','nr',this.value)" style="width:100px" placeholder="Proj-Nr">
|
|
<input type="text" value="${esc(p.name)}" onchange="editProjectField('${p.id}','name',this.value)" style="width:200px" placeholder="Bezeichnung">
|
|
<select onchange="editProjectField('${p.id}','group',this.value)">
|
|
<option ${p.group==='Aktive Projekte'?'selected':''}>Aktive Projekte</option>
|
|
<option ${p.group==='Kleine Projekte'?'selected':''}>Kleine Projekte</option>
|
|
<option ${p.group==='In Anbahnung'?'selected':''}>In Anbahnung</option>
|
|
<option ${p.group==='Allgemein'?'selected':''}>Allgemein</option>
|
|
</select>
|
|
<select onchange="editProjectActiveLP('${p.id}',this.value)" title="Aktive Leistungsphase wählen">
|
|
<option value="" ${lpActiveList(p).length===0?'selected':''}>Aktive LP: keine</option>
|
|
${AUFWAND_LPS.map(l => `<option value="${l}" ${lpActiveList(p)[0]===l?'selected':''}>${l}</option>`).join('')}
|
|
</select>
|
|
<button class="secondary small" onclick="openLpModal('${p.id}')">✎ LP-Aufwand</button>
|
|
<button class="danger small" onclick="delProjectFromList('${p.id}')">Löschen</button>
|
|
</div>`;
|
|
});
|
|
html += `</div>`;
|
|
if(groups[g].length===0) html+=`<div class="hint">Keine Projekte.</div>`;
|
|
});
|
|
document.getElementById('projectsList').innerHTML=html;
|
|
}
|
|
|
|
// Drag-and-Drop Handling für Projekte
|
|
|
|
// draggedProject: die während eines Drag-and-Drop-Vorgangs gezogene
|
|
// Projekt-Zeile (DOM-Element) bzw. null nach dem Loslassen.
|
|
let draggedProject = null;
|
|
|
|
// handleDragStart: startet das Ziehen einer Projekt-Zeile, merkt sich das
|
|
// Element in `draggedProject` und verringert seine Deckkraft.
|
|
function handleDragStart(event){
|
|
draggedProject = event.currentTarget;
|
|
event.dataTransfer.effectAllowed = 'move';
|
|
event.currentTarget.style.opacity = '0.5';
|
|
}
|
|
|
|
// handleDragOver: erlaubt das Ablegen während des Überfahrens eines Ziels
|
|
// (verhindert Standardverhalten, setzt `dropEffect='move'`).
|
|
function handleDragOver(event){
|
|
event.preventDefault();
|
|
event.dataTransfer.dropEffect = 'move';
|
|
}
|
|
|
|
// handleDrop: legt die gezogene Projekt-Zeile an der Position ab, die
|
|
// `getDragAfterElement` relativ zur Mausposition ermittelt, und speichert
|
|
// die neue Reihenfolge über `reorderProjects()`.
|
|
function handleDrop(event){
|
|
event.preventDefault();
|
|
if(!draggedProject || draggedProject === event.currentTarget) return;
|
|
const sortable = event.currentTarget.parentElement;
|
|
const afterElement = getDragAfterElement(sortable, event.clientY);
|
|
if(afterElement == null){
|
|
sortable.appendChild(draggedProject);
|
|
} else {
|
|
sortable.insertBefore(draggedProject, afterElement);
|
|
}
|
|
reorderProjects();
|
|
}
|
|
|
|
// handleDragEnd: stellt die Deckkraft der gezogenen Zeile wieder her und
|
|
// setzt `draggedProject` zurück (Ende des Drag-Vorgangs).
|
|
function handleDragEnd(event){
|
|
event.currentTarget.style.opacity = '1';
|
|
draggedProject = null;
|
|
}
|
|
|
|
// getDragAfterElement: ermittelt das Element in `container`, vor dem die
|
|
// gezogene Zeile bei Mausposition `y` abgelegt werden soll (Suche nach dem
|
|
// Element mit der kleinsten negativen Distanz zur Mitte). Rückgabe:
|
|
// Ziel-Element oder undefined (→ ans Ende anfügen). Wird auch von
|
|
// `js/people/people.js` für die Personen-Sortierung verwendet.
|
|
function getDragAfterElement(container, y){
|
|
const draggableElements = [...container.querySelectorAll('.person-list-item:not([style*="opacity"])')];
|
|
return draggableElements.reduce((closest, child) => {
|
|
const box = child.getBoundingClientRect();
|
|
const offset = y - box.top - box.height / 2;
|
|
if(offset < 0 && offset > closest.offset){
|
|
return { offset: offset, element: child };
|
|
} else {
|
|
return closest;
|
|
}
|
|
}, { offset: Number.NEGATIVE_INFINITY }).element;
|
|
}
|
|
|
|
// reorderProjects: übernimmt die im DOM vorgefundene Reihenfolge der
|
|
// Projekt-Zeilen in die Daten (je Gruppe `order`-Werte) und aktualisiert
|
|
// Gruppe/Zuordnung pro Projekt. Keine Parameter, keine Rückgabe; bei
|
|
// gesperrter Sitzung abgebrochen.
|
|
function reorderProjects(){
|
|
if(guardWeekLocked()) return;
|
|
const w = currentWeek();
|
|
const sortables = document.querySelectorAll('.projects-sortable');
|
|
sortables.forEach(sortable => {
|
|
const group = sortable.dataset.group;
|
|
const items = sortable.querySelectorAll('[data-project-id]');
|
|
items.forEach((item, index) => {
|
|
const projectId = item.dataset.projectId;
|
|
const project = w.projects.find(p => p.id === projectId);
|
|
if(project){
|
|
project.order = index;
|
|
project.group = group;
|
|
}
|
|
});
|
|
});
|
|
save();
|
|
renderProjects();
|
|
renderBoard();
|
|
} |