Files
projektuebersicht/public/js/server.js
T
erikandClaude Opus 5.5 b7e0bd0d92 Termin aus Mail, Rollen & Rechte, Abwesenheiten, Glas-Darstellung und Pi-Betrieb
- Termin aus Mail: Mailtext oder .eml per Drag & Drop/Strg+V auf Terminplan
  oder Aufgaben, KI schlägt Aufgaben vor, Prüfung im Aufgaben-Panel; neues
  Notizfeld je Aufgabe (plan_todos.notiz)
- Terminplan: Meilensteine ohne Aufgabenbezug je in eigener Zeile,
  "+ Neu"-Menü klappt neben schmaler Seitenleiste nach rechts, Leistungsphasen
- Rollen & Rechte, Abwesenheiten-Seite, Benachrichtigungen, Rundgang
- Glas-Darstellung, Logo als Favicon
- Pi-Betrieb: PDF-Arbeit im Kindprozess, Wächter, Einrichtungs- und
  Prüfskript inkl. WLAN-Wächter

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 14:16:52 +01:00

110 lines
5.0 KiB
JavaScript

// -----------------------------
// Projektübersicht — Server-Kommunikation (JSON-API)
// -----------------------------
// Diese Datei kapselt die HTTP-Kommunikation mit dem Express-Server (siehe
// server.js im Projekt-Root) und wird nach js/date-picker.js von beiden
// HTML-Einstiegspunkten geladen. Sie stellt baseUrl,
// sendWeekToServer und deleteWeekFromServer bereit. Genutzt wird sie von
// js/store.js (syncAllToServer, Speichern aller Wochen), js/weeks.js
// (Wochen-Löschung) und js/plan/plan-state.js (savePlan schreibt direkt die
// aktuelle Woche über sendWeekToServer). Die Daten werden als strukturierte
// JSON-Objekte an die SQLite-API des Servers übergeben (keine CSV-Strings
// mehr). Die globale Personenliste hat seit der Ablösung von
// people/person_roles/person_absences keinen eigenen Schreibpfad mehr
// (Projektbeteiligte werden über die Benutzerverwaltung gepflegt, siehe
// /admin/users bzw. /users/:id/absences).
// baseUrl: dynamische Server-URL, damit die App auch von anderen Geraeten im Netzwerk erreichbar ist.
// Fallback auf localhost:3003, wenn die Seite als lokale Datei geladen wurde.
// Parameter: keine. Rückgabe: String mit Basis-URL.
function baseUrl(){
const origin = window.location.origin;
if(typeof origin === 'string' && /^https?:\/\//.test(origin)){
return origin;
}
return 'http://localhost:3003';
}
// sendWeekToServer: POSTet ein komplettes Wochen-Objekt (JSON) an
// <baseUrl>/save-week. Parameter: week (Objekt). Rückgabe: Promise mit
// Server-Antwort. `baseUpdatedAt` übermittelt die Revision, auf der die
// lokalen Daten beruhen; meldet der Server 409 (conflict), wird ein Error
// mit `err.conflict = true` und `err.updatedAt` geworfen, damit der Aufrufer
// den Konflikt anzeigen kann.
// CLIENT_ID: zufällige Kennung dieses Browser-Tabs. Geht bei jedem Speichern
// als X-Client-Id mit und bei GET /ereignisse als ?client= — so bekommt der
// Tab seine eigenen Änderungen nicht als "fremdes" Live-Ereignis zurück.
const CLIENT_ID = Math.random().toString(36).slice(2) + Date.now().toString(36);
function sendWeekToServer(week){
if(!window.fetch) return Promise.reject(new Error('fetch nicht verfügbar'));
return fetch(baseUrl() + '/save-week',{
method:'POST',
headers:{'Content-Type':'application/json; charset=utf-8', 'X-Client-Id': CLIENT_ID},
body:JSON.stringify({
week,
baseUpdatedAt:(week && week.updatedAt) ? week.updatedAt : undefined
})
}).then(r=>{
return r.json().then(json => {
if(r.ok && json) return json;
if(json && json.conflict){
const err = new Error(json.error || 'Konflikt: Sitzung wurde zwischenzeitlich geändert');
err.conflict = true;
err.label = (week && week.label) || '';
err.updatedAt = json.updatedAt;
throw err;
}
const err = new Error((json && json.error) || ('Server HTTP '+r.status));
err.status = r.status;
err.label = (week && week.label) || '';
throw err;
});
});
}
// sendProjectsToServer: speichert nur einzelne Projekte einer Sitzung
// (POST /save-projects, siehe js/store.js syncAllToServer()). Parameter:
// label, projekte (Array von Projekt-Objekten; ihr updatedAt ist die Basis
// für die Konfliktprüfung je Projekt). Rückgabe: Promise mit
// {ok, updatedAt, projekte:{id: updatedAt}}; bei 409 ein Error mit
// conflict=true, wie sendWeekToServer().
function sendProjectsToServer(label, projekte){
if(!window.fetch) return Promise.reject(new Error('fetch nicht verfügbar'));
return fetch(baseUrl() + '/save-projects', {
method: 'POST',
headers: {'Content-Type':'application/json; charset=utf-8', 'X-Client-Id': CLIENT_ID},
body: JSON.stringify({ label, projects: projekte.map(p => ({ project: p, baseUpdatedAt: p.updatedAt || '' })) })
}).then(r => r.json().catch(() => null).then(json => {
if(r.ok && json) return json;
const err = new Error((json && json.error) || ('Server HTTP ' + r.status));
if(json && json.conflict){ err.conflict = true; err.projekte = json.projekte || []; }
err.status = r.status;
err.label = label;
throw err;
}));
}
// deleteWeekFromServer: fordert das Löschen einer Woche über POST /delete-week an
// Parameter: label (String, das Wochen-Label). Rückgabe: Promise mit Server-Antwort.
// Bei einer Fehler-Antwort (z. B. 403 „Keine Berechtigung“) trägt der geworfene
// Error zusätzlich `status` (HTTP-Statuscode), damit Aufrufer (js/weeks.js)
// gezielt darauf reagieren können.
function deleteWeekFromServer(label){
if(!window.fetch) return Promise.reject(new Error('fetch nicht verfügbar'));
return fetch(baseUrl() + '/delete-week', {
method: 'POST',
headers: {'Content-Type':'application/json; charset=utf-8'},
body: JSON.stringify({ label })
}).then(r=>{
if(!r.ok){
return r.json().catch(()=>({})).then(json => {
const err = new Error((json && json.error) || ('Server HTTP '+r.status));
err.status = r.status;
throw err;
});
}
return r.json();
});
}