Initial commit - Stand 26.04.2026

This commit is contained in:
OpenClaw
2026-04-26 07:51:39 +02:00
commit b29c467187
186 changed files with 39281 additions and 0 deletions
+314
View File
@@ -0,0 +1,314 @@
// API Service für alle Backend-Calls
// Docker: Frontend (nginx:80) -> Backend (3001) via nginx proxy
const API_URL = '/api';
// Helper für API Calls
async function apiCall(endpoint, options = {}) {
const url = `${API_URL}${endpoint}`;
const config = {
headers: {
'Content-Type': 'application/json',
...options.headers,
},
...options,
};
if (options.body && typeof options.body === 'object') {
config.body = JSON.stringify(options.body);
}
try {
const response = await fetch(url, config);
if (!response.ok) {
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
throw new Error(error.error || `HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
console.error(`API Error (${endpoint}):`, error);
throw error;
}
}
// Kredite API
export const krediteAPI = {
getAll: (params = {}) => {
const queryParams = new URLSearchParams(params).toString();
return apiCall(`/kredite${queryParams ? '?' + queryParams : ''}`);
},
create: (data) => apiCall('/kredite', {
method: 'POST',
body: data,
}),
update: (id, data) => apiCall(`/kredite/${id}`, {
method: 'PUT',
body: data,
}),
// PATCH für partielle Updates (z.B. nur Restschuld)
patch: (id, data) => apiCall(`/kredite/${id}`, {
method: 'PATCH',
body: data,
}),
delete: (id) => apiCall(`/kredite/${id}`, {
method: 'DELETE',
}),
getBuchungen: (id) => apiCall(`/kredite/${id}/buchungen`),
addBuchung: (id, data) => apiCall(`/kredite/${id}/buchungen`, {
method: 'POST',
body: data,
}),
deleteBuchung: (id, buchungId) => apiCall(`/kredite/${id}/buchungen/${buchungId}`, {
method: 'DELETE',
}),
// Zahlungen API (Alias für Buchungen)
getZahlungen: (id) => apiCall(`/kredite/${id}/zahlungen`),
addZahlung: (id, data) => apiCall(`/kredite/${id}/zahlungen`, {
method: 'POST',
body: data,
}),
deleteZahlung: (id, zahlungId) => apiCall(`/kredite/${id}/zahlungen/${zahlungId}`, {
method: 'DELETE',
}),
};
// Stunden API
export const stundenAPI = {
getAll: (params = {}) => {
const queryParams = new URLSearchParams(params).toString();
return apiCall(`/stunden${queryParams ? '?' + queryParams : ''}`);
},
create: (data) => apiCall('/stunden', {
method: 'POST',
body: data,
}),
update: (id, data) => apiCall(`/stunden/${id}`, {
method: 'PUT',
body: data,
}),
delete: (id) => apiCall(`/stunden/${id}`, {
method: 'DELETE',
}),
};
// Nebenkosten API
export const nebenkostenAPI = {
getAll: (params = {}) => {
const queryParams = new URLSearchParams(params).toString();
return apiCall(`/nebenkosten${queryParams ? '?' + queryParams : ''}`);
},
create: (data) => apiCall('/nebenkosten', {
method: 'POST',
body: data,
}),
update: (id, data) => apiCall(`/nebenkosten/${id}`, {
method: 'PUT',
body: data,
}),
delete: (id) => apiCall(`/nebenkosten/${id}`, {
method: 'DELETE',
}),
};
// Kostenplanung API
export const kostenplanungAPI = {
getAll: (params = {}) => {
const queryParams = new URLSearchParams(params).toString();
return apiCall(`/kostenplanung${queryParams ? '?' + queryParams : ''}`);
},
create: (data) => apiCall('/kostenplanung', {
method: 'POST',
body: data,
}),
update: (id, data) => apiCall(`/kostenplanung/${id}`, {
method: 'PUT',
body: data,
}),
delete: (id) => apiCall(`/kostenplanung/${id}`, {
method: 'DELETE',
}),
};
// Geschäftsplanung API (neu)
export const geschaeftsplanungAPI = {
// Monatliche Übersicht mit Ergebnis und Cashflow
getMonatlich: (jahr) => apiCall(`/geschaeftsplanung/monatlich?jahr=${jahr}`),
// Kategorie-Übersicht
getKategorien: (jahr) => apiCall(`/geschaeftsplanung/kategorien?jahr=${jahr}`),
// Miete vs Nebenkosten pro Objekt
getObjektVergleich: (objekt, jahr) => apiCall(`/geschaeftsplanung/objektvergleich/${objekt}?jahr=${jahr}`),
// Umsatzvorschau (nur Einnahmen)
getUmsatzVorschau: (jahr) => apiCall(`/geschaeftsplanung/umsatzvorschau?jahr=${jahr}`),
};
// Kunden API (für Auftragsnachweise)
export const kundenAPI = {
getAll: () => apiCall('/kunden'),
create: (data) => apiCall('/kunden', {
method: 'POST',
body: data,
}),
update: (id, data) => apiCall(`/kunden/${id}`, {
method: 'PUT',
body: data,
}),
delete: (id) => apiCall(`/kunden/${id}`, {
method: 'DELETE',
}),
};
// Auftragsnachweise API
export const auftragsnachweisAPI = {
getAll: (params = {}) => {
const queryParams = new URLSearchParams(params).toString();
return apiCall(`/auftragsnachweise${queryParams ? '?' + queryParams : ''}`);
},
getById: (id) => apiCall(`/auftragsnachweise/${id}`),
create: (data) => {
// Stelle sicher, dass art_der_arbeit ein Array ist
const sanitizedData = {
...data,
art_der_arbeit: Array.isArray(data.art_der_arbeit)
? data.art_der_arbeit
: (data.art_der_arbeit ? [data.art_der_arbeit] : []),
stunden_ids: Array.isArray(data.stunden_ids)
? data.stunden_ids
: (data.stunden_ids ? [data.stunden_ids] : []),
};
return apiCall('/auftragsnachweise', {
method: 'POST',
body: sanitizedData,
});
},
delete: (id) => apiCall(`/auftragsnachweise/${id}`, {
method: 'DELETE',
}),
// PDF generieren und herunterladen
generatePDF: async (id, firmenname) => {
const response = await fetch(`${API_URL}/auftragsnachweise/${id}/pdf`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ firmenname }),
});
if (!response.ok) {
// Bei Fehler erst als Text versuchen, dann als JSON
const errorText = await response.text().catch(() => 'Unknown error');
let errorMessage = errorText;
try {
const errorJson = JSON.parse(errorText);
errorMessage = errorJson.error || errorText;
} catch (e) {
// Nicht JSON, Text verwenden
}
throw new Error(errorMessage || `HTTP ${response.status}`);
}
// Blob für Download
const blob = await response.blob();
if (blob.size === 0) {
throw new Error('PDF ist leer');
}
// Download-Dateiname aus Header extrahieren oder Default verwenden
const disposition = response.headers.get('Content-Disposition');
let filename = `auftragsnachweis-${id}.pdf`;
if (disposition && disposition.includes('filename=')) {
const match = disposition.match(/filename="([^"]+)"/);
if (match) filename = match[1];
}
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
return true;
},
// Vorhandenes PDF herunterladen
downloadPDF: async (id) => {
const response = await fetch(`${API_URL}/auftragsnachweise/${id}/pdf`);
if (!response.ok) {
const errorText = await response.text().catch(() => 'Unknown error');
let errorMessage = errorText;
try {
const errorJson = JSON.parse(errorText);
errorMessage = errorJson.error || errorText;
} catch (e) {
// Nicht JSON, Text verwenden
}
throw new Error(errorMessage || `HTTP ${response.status}`);
}
const blob = await response.blob();
if (blob.size === 0) {
throw new Error('PDF ist leer');
}
const disposition = response.headers.get('Content-Disposition');
let filename = `auftragsnachweis-${id}.pdf`;
if (disposition && disposition.includes('filename=')) {
const match = disposition.match(/filename="([^"]+)"/);
if (match) filename = match[1];
}
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
return true;
},
};
// Health Check
export const healthCheck = () => apiCall('/health');
export default {
kredite: krediteAPI,
stunden: stundenAPI,
nebenkosten: nebenkostenAPI,
kostenplanung: kostenplanungAPI,
geschaeftsplanung: geschaeftsplanungAPI,
health: healthCheck,
};