Protokoll-Import: KI-Erkennung (Gruppe, Teilnehmer, Status, Mehrfachpunkte), Ladeanzeige, Erstellungsdatum je Punkt
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6f18848f86
commit
11eabe42ae
275 files changed
+71952
-3029
No files matched your search
+108
-23
@@ -1,15 +1,27 @@
|
||||
// -----------------------------
|
||||
// Projektübersicht — Projekte (Listen-Ansicht)
|
||||
// Projektübersicht — Projektliste (Ablage-Logik)
|
||||
// -----------------------------
|
||||
// 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.
|
||||
// Der eigene Tab "Projekte" ist entfallen. Er listete dieselben Projekte ein
|
||||
// zweites Mal auf und ließ dieselben Angaben — Nummer, Bezeichnung, Gruppe,
|
||||
// amtierende Leistungsphase, Schlagwörter, Aufwand — ein zweites Mal
|
||||
// bearbeiten. Diese Angaben haben ihren einen Ort jetzt im Projektbereich
|
||||
// (js/plan/plan-settings.js, js/plan/plan-info.js); die Liste selbst ist im
|
||||
// Projektboard aufgegangen (renderBoard(), js/board/board.js).
|
||||
//
|
||||
// Was hier bleibt, betrifft die LISTE, nicht die einzelnen Angaben:
|
||||
//
|
||||
// * delProjectFromList() — Projekt aus der Sitzung löschen
|
||||
// * die Drag-and-Drop-Handler + reorderProjects() — Reihenfolge innerhalb
|
||||
// einer Gruppe und Wechsel zwischen Gruppen. Das Board spannt dieselbe
|
||||
// Ablagefläche auf (.projects-sortable mit data-group), damit diese
|
||||
// Handler unverändert weiterarbeiten.
|
||||
// * getDragAfterElement() — Sortier-Hilfe, auch von js/people/people.js
|
||||
// genutzt
|
||||
//
|
||||
// editProjectField()/editProjectActiveLP()/addProjectTag()/removeProjectTag()
|
||||
// stehen weiterhin bereit: Der Beauftragungs-Dialog und die Auslastungs-Maske
|
||||
// rufen sie auf. renderProjects() rendert nur noch, wenn ein Element
|
||||
// #projectsList vorhanden ist — sonst übernimmt renderBoard().
|
||||
|
||||
// delProjectFromList: löscht ein Projekt (per Bestätigungsdialog) in der
|
||||
// Listen-Ansicht. Parameter `id`: Projekt-ID. Keine Rückgabe; bei gesperrter
|
||||
@@ -55,16 +67,67 @@ function editProjectActiveLP(id, value){
|
||||
save(); renderProjects(); renderBoard(); renderLoad();
|
||||
}
|
||||
|
||||
// addProjectTag: fügt dem Projekt einen neuen Tag hinzu (getrimmt, nicht
|
||||
// leer, keine exakten Duplikate). Initialisiert `p.tags`, falls es fehlt
|
||||
// oder kein Array ist. Parameter: `projectId`, `tag` (roher Eingabetext).
|
||||
// Keine Rückgabe; bei gesperrter Sitzung abgebrochen.
|
||||
function addProjectTag(projectId, tag){
|
||||
if(guardWeekLocked()) return;
|
||||
const val = String(tag || '').trim();
|
||||
if(!val) return;
|
||||
const w = currentWeek();
|
||||
const p = w.projects.find(x=>x.id===projectId);
|
||||
if(!p) return;
|
||||
if(!Array.isArray(p.tags)) p.tags = [];
|
||||
if(p.tags.includes(val)) return;
|
||||
p.tags.push(val);
|
||||
save(); renderProjects(); renderBoard();
|
||||
}
|
||||
|
||||
// removeProjectTag: entfernt einen Tag aus dem Projekt (exakter Textvergleich).
|
||||
// Parameter: `projectId`, `tag`. Keine Rückgabe; bei gesperrter Sitzung
|
||||
// abgebrochen.
|
||||
function removeProjectTag(projectId, tag){
|
||||
if(guardWeekLocked()) return;
|
||||
const w = currentWeek();
|
||||
const p = w.projects.find(x=>x.id===projectId);
|
||||
if(!p) return;
|
||||
p.tags = (Array.isArray(p.tags) ? p.tags : []).filter(t => t !== tag);
|
||||
save(); renderProjects(); renderBoard();
|
||||
}
|
||||
|
||||
// handleProjectTagKeydown: fügt bei Enter oder Komma im Tag-Eingabefeld den
|
||||
// aktuellen Feldinhalt als neuen Tag hinzu (das Eingabefeld wird ohnehin
|
||||
// beim anschließenden renderProjects() frisch/leer neu aufgebaut). Parameter:
|
||||
// `event` (KeyboardEvent), `projectId`. Keine Rückgabe.
|
||||
function handleProjectTagKeydown(event, projectId){
|
||||
if(event.key !== 'Enter' && event.key !== ','){
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
addProjectTag(projectId, event.target.value);
|
||||
}
|
||||
|
||||
// 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(){
|
||||
// Ohne #projectsList gibt es den eigenen Projekte-Bereich nicht mehr — die
|
||||
// Liste steht im Projektboard. Der Aufruf bleibt dennoch überall stehen
|
||||
// (renderAll(), editProjectField(), …), damit kein Pfad ins Leere greift.
|
||||
const listEl = document.getElementById('projectsList');
|
||||
if(!listEl) return;
|
||||
const w = currentWeek();
|
||||
if(!w) return;
|
||||
const locked = isWeekLocked(w);
|
||||
let html="";
|
||||
const groups = {};
|
||||
// Alle Standardgruppen vorbelegen: Ohne das entsteht für eine leer geräumte
|
||||
// Gruppe weder Überschrift noch Ablagefläche — ein Projekt ließ sich dann
|
||||
// nur noch über das Auswahlfeld zurückverschieben, und der Hinweis
|
||||
// "Keine Projekte." unten war toter Code.
|
||||
['Aktive Projekte','Kleine Projekte','In Anbahnung','Allgemein'].forEach(g => { groups[g] = []; });
|
||||
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));
|
||||
@@ -74,28 +137,37 @@ function renderProjects(){
|
||||
html += `<div class="section-title">${esc(g)}</div>`;
|
||||
html += `<div class="projects-sortable" data-group="${g}" ${locked ? '' : 'ondragover="handleDragOver(event)" ondrop="handleDrop(event)"'}>`;
|
||||
groups[g].forEach(p=>{
|
||||
html += `<div class="person-list-item" ${locked ? '' : 'draggable="true" ondragstart="handleDragStart(event)" ondragend="handleDragEnd(event)"'} data-project-id="${p.id}">
|
||||
${locked ? '' : `<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)">
|
||||
const tags = Array.isArray(p.tags) ? p.tags : [];
|
||||
html += `<div class="person-list-item project-item" ${locked ? '' : 'draggable="true" ondragstart="handleDragStart(event)" ondragend="handleDragEnd(event)"'} data-project-id="${p.id}">
|
||||
${locked ? '' : `<span class="drag-handle" style="cursor:grab;color:#999">${icon('grip')}</span>`}
|
||||
<input type="text" value="${esc(p.nr)}" onchange="editProjectField('${p.id}','nr',this.value)" style="width:100px" placeholder="Proj-Nr" ${locked ? 'disabled' : ''}>
|
||||
<input type="text" value="${esc(p.name)}" onchange="editProjectField('${p.id}','name',this.value)" style="width:200px" placeholder="Bezeichnung" ${locked ? 'disabled' : ''}>
|
||||
<select onchange="editProjectField('${p.id}','group',this.value)" ${locked ? 'disabled' : ''}>
|
||||
<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">
|
||||
<select onchange="editProjectActiveLP('${p.id}',this.value)" title="Aktive Leistungsphase wählen" ${locked ? 'disabled' : ''}>
|
||||
<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 class="project-tags-row">
|
||||
<div class="tag-chip-list">
|
||||
${tags.map(tag => `<span class="tag-chip" data-tag="${esc(tag)}">${esc(tag)}${locked ? '' : ` <span class="tag-remove" title="Tag entfernen" onclick="removeProjectTag('${p.id}', this.closest('.tag-chip').dataset.tag)">×</span>`}</span>`).join('')}
|
||||
</div>
|
||||
${locked ? '' : `<input type="text" class="tag-input" placeholder="+ Tag" onkeydown="handleProjectTagKeydown(event,'${p.id}')">`}
|
||||
</div>
|
||||
<div class="project-card-footer">
|
||||
<button class="secondary small" onclick="openLpModal('${p.id}')" ${locked ? 'disabled' : ''}>LP-Aufwand</button>
|
||||
<button class="danger small" onclick="delProjectFromList('${p.id}')" ${locked ? 'disabled' : ''}>Löschen</button>
|
||||
</div>
|
||||
</div>`;
|
||||
});
|
||||
if(groups[g].length===0) html+=`<div class="hint" style="padding:4px 2px 10px">Keine Projekte — Projekt hierher ziehen.</div>`;
|
||||
html += `</div>`;
|
||||
if(groups[g].length===0) html+=`<div class="hint">Keine Projekte.</div>`;
|
||||
});
|
||||
document.getElementById('projectsList').innerHTML=html;
|
||||
listEl.innerHTML = html;
|
||||
}
|
||||
|
||||
// Drag-and-Drop Handling für Projekte
|
||||
@@ -135,7 +207,12 @@ function handleDrop(event){
|
||||
sourceSortable.dataset && sortable.dataset &&
|
||||
sourceSortable.dataset.group === sortable.dataset.group;
|
||||
if(isSameGroup){
|
||||
const afterElement = getDragAfterElement(sortable, event.clientY);
|
||||
// Die Ablagefläche der Projektliste enthält .board-row-Zeilen, der
|
||||
// Personen-/Projekte-Aufbau .person-list-item. getDragAfterElement()
|
||||
// braucht den passenden Selektor, sonst findet es keine Nachbarn und
|
||||
// hängt jedes Projekt ans Gruppenende.
|
||||
const sel = sortable.querySelector('.board-row') ? '.board-row:not([style*="opacity"])' : undefined;
|
||||
const afterElement = getDragAfterElement(sortable, event.clientY, sel);
|
||||
if(afterElement == null){
|
||||
sortable.appendChild(draggedProject);
|
||||
} else {
|
||||
@@ -151,7 +228,13 @@ function handleDrop(event){
|
||||
// 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';
|
||||
// Attribut entfernen statt auf 1 setzen: getDragAfterElement() schließt jedes
|
||||
// Element mit style="…opacity…" von der Positionsberechnung aus. Nach einem
|
||||
// abgebrochenen Ziehen (Loslassen außerhalb der Liste) blieb das Attribut
|
||||
// stehen, und die Zeile wurde beim nächsten Ablegen ignoriert — das Projekt
|
||||
// landete dann am Gruppenende statt an der gewünschten Stelle.
|
||||
event.currentTarget.style.removeProperty('opacity');
|
||||
if(event.currentTarget.getAttribute('style') === '') event.currentTarget.removeAttribute('style');
|
||||
draggedProject = null;
|
||||
}
|
||||
|
||||
@@ -197,6 +280,8 @@ function reorderProjects(){
|
||||
});
|
||||
});
|
||||
save();
|
||||
renderProjects();
|
||||
// renderProjects() nur, solange der eigene Projekte-Bereich existiert; die
|
||||
// Projektliste im Board rendert renderBoard().
|
||||
if(typeof renderProjects === 'function' && document.getElementById('projectsList')) renderProjects();
|
||||
renderBoard();
|
||||
}
|
||||
Reference in new issue
Block a user