131 lines
6.0 KiB
JavaScript
131 lines
6.0 KiB
JavaScript
// -----------------------------
|
||
// Planung — Terminplan-Sandbox
|
||
// -----------------------------
|
||
// Der Tab "Planung" (js/tabs-auth.js, Slug "Planung" in TAB_SLUGS) zeigt eine
|
||
// einfache Liste projektunabhängiger Terminpläne — Kapazitätstests, Angebote
|
||
// vor der Beauftragung, eigene Vorhaben. Jeder Plan ist ein ganz normales
|
||
// Projekt-Objekt (dieselbe Form wie ein echtes Projekt), liegt aber in der
|
||
// reservierten Sitzung SANDBOX_WEEK_LABEL statt in einer echten (siehe
|
||
// db.sandboxWeek, js/store.js, und CLAUDE.md "Planung — projektunabhängige
|
||
// Terminplan-Sandbox"). Das Bearbeiten selbst — Terminplan, Aufgaben,
|
||
// Meilensteine, Honorar-Rechner — läuft über denselben Projektbereich wie bei
|
||
// einem echten Projekt (openSandboxPlan(), js/plan/plan-state.js); dieses
|
||
// Modul stellt nur die Liste und den schlanken Anlege-Dialog bereit.
|
||
|
||
// planungGewaehlteArt: welche Anlege-Art im Dialog gerade markiert ist
|
||
// ('honorar'|'leer'). Zustand nur für die Dauer des offenen Dialogs.
|
||
let planungGewaehlteArt = 'honorar';
|
||
|
||
// renderPlanung: füllt die Terminplan-Liste des Tabs "Planung". Keine
|
||
// Parameter, keine Rückgabe.
|
||
function renderPlanung(){
|
||
const el = document.getElementById('planungList');
|
||
if(!el) return;
|
||
const plaene = (db.sandboxWeek && Array.isArray(db.sandboxWeek.projects)) ? db.sandboxWeek.projects : [];
|
||
|
||
if(plaene.length === 0){
|
||
el.innerHTML = `<div class="empty-hint">
|
||
<span class="big">Noch kein Terminplan angelegt.</span>
|
||
Unabhängig von einem echten Projekt — für Angebote, Kapazitätstests oder eigene Vorhaben.
|
||
</div>`;
|
||
return;
|
||
}
|
||
|
||
// Zuletzt geändert zuerst — genau die Frage, die die Liste beantworten soll.
|
||
const sortiert = [...plaene].sort((a, b) => String(b.updatedAt || '').localeCompare(String(a.updatedAt || '')));
|
||
|
||
el.innerHTML = sortiert.map(p => {
|
||
const honorar = Number(p.honorar) || 0;
|
||
const ausHonorar = honorar > 0;
|
||
const beauftragt = Array.isArray(p.beauftragteLP) ? p.beauftragteLP : [];
|
||
const meta = ausHonorar
|
||
? 'Aus Honorar errechnet · ' + honorar.toLocaleString('de-DE') + ' € · '
|
||
+ (beauftragt.length ? beauftragt.join(', ') : 'keine Leistungsphasen gewählt')
|
||
: 'Frei begonnen · ' + ((p.plan && Array.isArray(p.plan.todos)) ? p.plan.todos.length : 0) + ' Aufgabe(n), '
|
||
+ ((p.plan && Array.isArray(p.plan.milestones)) ? p.plan.milestones.length : 0) + ' Meilenstein(e)';
|
||
const geaendert = p.updatedAt ? formatCommentStamp(p.updatedAt) : '–';
|
||
return `<div class="planung-row" onclick="openSandboxPlan('${esc(p.id)}')">
|
||
<div class="planung-row-icon ${ausHonorar ? 'honorar' : 'leer'}">${ausHonorar ? '€' : '✎'}</div>
|
||
<div class="planung-row-main">
|
||
<p class="planung-row-name">${esc(p.name || 'Ohne Namen')}</p>
|
||
<p class="planung-row-meta">${esc(meta)}</p>
|
||
</div>
|
||
<div class="planung-row-stat">zuletzt geändert<br><b>${esc(geaendert)}</b></div>
|
||
</div>`;
|
||
}).join('');
|
||
}
|
||
|
||
// oeffneNeuerTerminplanDialog: zeigt den Anlege-Dialog mit leerem Namensfeld.
|
||
// Keine Parameter, keine Rückgabe.
|
||
function oeffneNeuerTerminplanDialog(){
|
||
const nameEl = document.getElementById('planungName');
|
||
if(nameEl) nameEl.value = '';
|
||
planungGewaehlteArt = 'honorar';
|
||
aktualisierePlanungArtAuswahl();
|
||
const modal = document.getElementById('neuerTerminplanModal');
|
||
if(modal) modal.style.display = 'flex';
|
||
if(nameEl) nameEl.focus();
|
||
}
|
||
|
||
function schliesseNeuerTerminplanDialog(){
|
||
const modal = document.getElementById('neuerTerminplanModal');
|
||
if(modal) modal.style.display = 'none';
|
||
}
|
||
|
||
// waehlePlanungArt: markiert eine der beiden Anlege-Karten ("Aus Honorar
|
||
// berechnen" / "Leer beginnen"). Parameter: art ('honorar'|'leer').
|
||
function waehlePlanungArt(art){
|
||
planungGewaehlteArt = art;
|
||
aktualisierePlanungArtAuswahl();
|
||
}
|
||
|
||
function aktualisierePlanungArtAuswahl(){
|
||
document.querySelectorAll('#neuerTerminplanModal .choice-card').forEach(card => {
|
||
card.classList.toggle('is-gewaehlt', card.dataset.art === planungGewaehlteArt);
|
||
});
|
||
}
|
||
|
||
// erstellePlanungTerminplan: legt den Sandbox-Plan an (Name-Pflichtfeld,
|
||
// keine doppelten Namen — die einzige Identität eines Sandbox-Plans, siehe
|
||
// CLAUDE.md) und öffnet ihn direkt im Projektbereich. Bei "Aus Honorar
|
||
// berechnen" landet man in den Einstellungen mit fokussiertem Honorar-Feld;
|
||
// den eigentlichen Rechenweg übernimmt der bereits vorhandene "Terminplan
|
||
// aus Honorar"-Knopf dort — kein eigener Rechen-Code. Keine Parameter, keine
|
||
// Rückgabe.
|
||
function erstellePlanungTerminplan(){
|
||
const nameEl = document.getElementById('planungName');
|
||
const name = (nameEl ? nameEl.value : '').trim();
|
||
if(!name){ alert('Bitte einen Namen für den Terminplan eingeben.'); return; }
|
||
const plaene = (db.sandboxWeek && Array.isArray(db.sandboxWeek.projects)) ? db.sandboxWeek.projects : [];
|
||
if(plaene.some(p => (p.name || '').toLowerCase() === name.toLowerCase())){
|
||
alert('Es gibt bereits einen Terminplan mit dem Namen "' + name + '". Bitte einen anderen Namen wählen.');
|
||
return;
|
||
}
|
||
|
||
const neu = {
|
||
id: uid(), projectUid: uid(),
|
||
nr: '', name, group: '', gewerke: [], tags: [],
|
||
status: '', order: plaene.length, lph: {}, assign: {}, plan: null,
|
||
auftragsstatus: 'beauftragt', honorar: 0, groesse: 0,
|
||
beauftragteLP: [], aktuelleLP: [], besprochen: false,
|
||
updatedAt: new Date().toISOString()
|
||
};
|
||
plaene.push(neu);
|
||
markWeekDirty(SANDBOX_WEEK_LABEL);
|
||
scheduleSessionSync();
|
||
|
||
const gewaehlteArt = planungGewaehlteArt;
|
||
schliesseNeuerTerminplanDialog();
|
||
renderPlanung();
|
||
openSandboxPlan(neu.id);
|
||
if(gewaehlteArt === 'honorar'){
|
||
// Kleine UX-Abkürzung: direkt in den Einstellungen landen und das
|
||
// Honorar-Feld fokussieren, statt den Nutzer selbst dorthin navigieren
|
||
// zu lassen. showPlanSection() rendert synchron, das Feld existiert
|
||
// danach im DOM.
|
||
if(typeof showPlanSection === 'function') showPlanSection('einstellungen');
|
||
const honorarEl = document.getElementById('settingsHonorar');
|
||
if(honorarEl) honorarEl.focus();
|
||
}
|
||
}
|