// 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, };