// ----------------------------- // 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.js` // (vor `js/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.js` (Personen-Sortierung) genutzt; // `renderBoard()` aus `js/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 += `
${esc(g)}
`; html += `
`; groups[g].forEach(p=>{ html += `
⋮⋮
`; }); html += `
`; if(groups[g].length===0) html+=`
Keine Projekte.
`; }); 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.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(); }