All new
alle new
This commit is contained in:
1 parent
da64472251
commit
5b20adee48
1605 files changed
+396517
-1674
No files matched your search
@@ -0,0 +1,130 @@
|
||||
// -----------------------------
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user