import { useState, useRef } from 'react'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { formatDate } from '../../utils/dateFormat'; import { Plus, Edit, Trash2, ChevronDown, ChevronUp, FileText, Download, AlertTriangle, Check, Eye } from 'lucide-react'; import Modal from '../ui/Modal'; import Button from '../ui/Button'; import Input from '../ui/Input'; import Select from '../ui/Select'; import Badge from '../ui/Badge'; import { invoiceApi } from '../../services/api'; import type { Invoice, InvoiceType } from '../../types'; const invoiceTypeLabels: Record = { INTERIM: 'Zwischenrechnung', FINAL: 'Schlussrechnung', NOT_AVAILABLE: 'Nicht verfügbar', }; interface InvoicesSectionProps { ecdId?: number; // energyContractDetailsId (optional - für Energie-Verträge) invoices: Invoice[]; contractId: number; canEdit: boolean; showInvoiceWarnings?: boolean; // Warnungen für fehlende Schluss-/Zwischenrechnung (nur Energie) } export default function InvoicesSection({ ecdId, invoices, showInvoiceWarnings = false, contractId, canEdit, }: InvoicesSectionProps) { const [isExpanded, setIsExpanded] = useState(false); const [showAddModal, setShowAddModal] = useState(false); const [editingInvoice, setEditingInvoice] = useState(null); const queryClient = useQueryClient(); const deleteInvoiceMutation = useMutation({ mutationFn: (invoiceId: number) => ecdId ? invoiceApi.deleteInvoice(ecdId, invoiceId) : invoiceApi.deleteInvoice(0, invoiceId), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['contract'] }); }, }); // Sort invoices by date (newest first) const sortedInvoices = [...invoices].sort( (a, b) => new Date(b.invoiceDate).getTime() - new Date(a.invoiceDate).getTime() ); const hasFinalInvoice = invoices.some(i => i.invoiceType === 'FINAL'); const hasNotAvailable = invoices.some(i => i.invoiceType === 'NOT_AVAILABLE'); return (

Rechnungen

{invoices.length} {/* Status-Indicator (nur bei Energie-Verträgen) */} {showInvoiceWarnings && hasFinalInvoice ? ( Schlussrechnung ) : showInvoiceWarnings && hasNotAvailable ? ( Nicht verfügbar ) : showInvoiceWarnings && invoices.length > 0 ? ( Schlussrechnung fehlt ) : null}
{canEdit && ( )} {invoices.length > 0 && ( )}
{/* Collapsed view - show latest invoice */} {!isExpanded && sortedInvoices.length > 0 && (
Letzte: {formatDate(sortedInvoices[0].invoiceDate)} - {invoiceTypeLabels[sortedInvoices[0].invoiceType]}
)} {/* Expanded view */} {isExpanded && sortedInvoices.length > 0 && (
{sortedInvoices.map((invoice) => (
{formatDate(invoice.invoiceDate)}
{invoiceTypeLabels[invoice.invoiceType]}
{invoice.documentPath && ( )} {invoice.notes && ( {invoice.notes} )}
{canEdit && (
)}
))}
)} {isExpanded && sortedInvoices.length === 0 && (

Keine Rechnungen vorhanden.

)} {/* Add/Edit Invoice Modal */} {(showAddModal || editingInvoice) && ( { setShowAddModal(false); setEditingInvoice(null); }} ecdId={ecdId} contractId={contractId} invoice={editingInvoice} /> )}
); } // Invoice Modal Component function InvoiceModal({ isOpen, onClose, ecdId, contractId, invoice, }: { isOpen: boolean; onClose: () => void; ecdId?: number; contractId: number; invoice?: Invoice | null; }) { const queryClient = useQueryClient(); const isEditing = !!invoice; const fileInputRef = useRef(null); const [formData, setFormData] = useState({ invoiceDate: invoice?.invoiceDate ? new Date(invoice.invoiceDate).toISOString().split('T')[0] : new Date().toISOString().split('T')[0], invoiceType: invoice?.invoiceType || 'INTERIM' as InvoiceType, notes: invoice?.notes || '', }); const [selectedFile, setSelectedFile] = useState(null); const [error, setError] = useState(null); const addInvoiceFn = async (data: { invoiceDate: string; invoiceType: string; notes?: string }) => { if (ecdId) { return invoiceApi.addInvoice(ecdId, data as any); } return invoiceApi.addInvoiceByContract(contractId, data as any); }; const createMutation = useMutation({ mutationFn: async (file: File) => { const result = await addInvoiceFn({ invoiceDate: formData.invoiceDate, invoiceType: formData.invoiceType, notes: formData.notes || undefined, }); // 2. Upload file if (result.data?.id) { await invoiceApi.uploadDocument(result.data.id, file); } return result; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['contract'] }); onClose(); }, onError: (err: Error) => { setError(err.message); }, }); const createWithoutFileMutation = useMutation({ mutationFn: async () => { return addInvoiceFn({ invoiceDate: formData.invoiceDate, invoiceType: formData.invoiceType, notes: formData.notes || undefined, }); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['contract'] }); onClose(); }, onError: (err: Error) => { setError(err.message); }, }); const updateMutation = useMutation({ mutationFn: async (file: File | null) => { // 1. Invoice aktualisieren const result = await invoiceApi.updateInvoice(ecdId || 0, invoice!.id, { invoiceDate: formData.invoiceDate, invoiceType: formData.invoiceType, notes: formData.notes || undefined, }); // 2. Upload file if provided if (file) { await invoiceApi.uploadDocument(invoice!.id, file); } return result; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['contract'] }); onClose(); }, onError: (err: Error) => { setError(err.message); }, }); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); setError(null); if (isEditing) { // Edit-Modus: Dokument ist Pflicht, außer bei NOT_AVAILABLE oder wenn schon vorhanden if (formData.invoiceType !== 'NOT_AVAILABLE' && !invoice?.documentPath && !selectedFile) { setError('Bitte laden Sie ein Dokument hoch'); return; } updateMutation.mutate(selectedFile); } else { // Add-Modus: Dokument ist Pflicht, außer bei NOT_AVAILABLE if (formData.invoiceType === 'NOT_AVAILABLE') { createWithoutFileMutation.mutate(); } else if (!selectedFile) { setError('Bitte laden Sie ein Dokument hoch'); return; } else { createMutation.mutate(selectedFile); } } }; const handleFileSelect = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) { if (file.type !== 'application/pdf') { setError('Nur PDF-Dateien sind erlaubt'); return; } if (file.size > 10 * 1024 * 1024) { setError('Datei ist zu groß (max. 10 MB)'); return; } setSelectedFile(file); setError(null); } }; const isPending = createMutation.isPending || createWithoutFileMutation.isPending || updateMutation.isPending; return (
{error && (
{error}
)} setFormData({ ...formData, invoiceDate: e.target.value })} required /> )} {formData.invoiceType === 'NOT_AVAILABLE' && (
Bei diesem Typ wird kein Dokument benötigt. Die Rechnung wird als "nicht mehr zu bekommen" markiert.
)} setFormData({ ...formData, notes: e.target.value })} placeholder="Optionale Anmerkungen..." />
); }