import { useEffect, useState } from 'react';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useForm } from 'react-hook-form';
import { contractApi, customerApi, platformApi, cancellationPeriodApi, contractDurationApi, providerApi, contractCategoryApi } from '../../services/api';
import Card from '../../components/ui/Card';
import Button from '../../components/ui/Button';
import Input from '../../components/ui/Input';
import Select from '../../components/ui/Select';
import type { ContractType } from '../../types';
import { Plus, Trash2, Eye, EyeOff, Info, X } from 'lucide-react';
// Contract types are now loaded dynamically from the database
const statusOptions = [
{ value: 'DRAFT', label: 'Entwurf' },
{ value: 'PENDING', label: 'Ausstehend' },
{ value: 'ACTIVE', label: 'Aktiv' },
{ value: 'CANCELLED', label: 'Gekündigt' },
{ value: 'EXPIRED', label: 'Abgelaufen' },
{ value: 'DEACTIVATED', label: 'Deaktiviert' },
];
// Status-Erklärungen für Info-Modal
const statusDescriptions = [
{ status: 'DRAFT', label: 'Entwurf', description: 'Vertrag wird noch vorbereitet', color: 'text-gray-600' },
{ status: 'PENDING', label: 'Ausstehend', description: 'Wartet auf Aktivierung', color: 'text-yellow-600' },
{ status: 'ACTIVE', label: 'Aktiv', description: 'Vertrag läuft normal', color: 'text-green-600' },
{ status: 'EXPIRED', label: 'Abgelaufen', description: 'Laufzeit vorbei, läuft aber ohne Kündigung weiter', color: 'text-orange-600' },
{ status: 'CANCELLED', label: 'Gekündigt', description: 'Aktive Kündigung eingereicht, Vertrag endet', color: 'text-red-600' },
{ status: 'DEACTIVATED', label: 'Deaktiviert', description: 'Manuell beendet/archiviert', color: 'text-gray-500' },
];
function StatusInfoModal({ isOpen, onClose }: { isOpen: boolean; onClose: () => void }) {
if (!isOpen) return null;
return (
Vertragsstatus-Übersicht
{statusDescriptions.map(({ status, label, description, color }) => (
{label}
{description}
))}
);
}
export default function ContractForm() {
const { id } = useParams();
const [searchParams] = useSearchParams();
const navigate = useNavigate();
const queryClient = useQueryClient();
const isEdit = !!id;
const preselectedCustomerId = searchParams.get('customerId');
const { register, handleSubmit, reset, watch, setValue, formState: { errors } } = useForm({
defaultValues: {
customerId: preselectedCustomerId || '',
type: 'ELECTRICITY',
status: 'DRAFT',
previousContractId: '',
},
});
const contractType = watch('type') as ContractType;
const customerId = watch('customerId');
// Fetch existing contract for edit
const { data: contract } = useQuery({
queryKey: ['contract', id],
queryFn: () => contractApi.getById(parseInt(id!)),
enabled: isEdit,
});
// Fetch customers for dropdown
const { data: customersData } = useQuery({
queryKey: ['customers-all'],
queryFn: () => customerApi.getAll({ limit: 1000 }),
});
// Fetch customer details when selected
const { data: customerDetail } = useQuery({
queryKey: ['customer', customerId],
queryFn: () => customerApi.getById(parseInt(customerId)),
enabled: !!customerId,
});
// Fetch contracts for same customer (for predecessor selection)
const { data: customerContractsData } = useQuery({
queryKey: ['customer-contracts-for-predecessor', customerId],
queryFn: () => contractApi.getAll({ customerId: parseInt(customerId), limit: 1000 }),
enabled: !!customerId,
});
// Fetch platforms
const { data: platformsData } = useQuery({
queryKey: ['platforms'],
queryFn: () => platformApi.getAll(),
});
// Fetch cancellation periods
const { data: cancellationPeriodsData } = useQuery({
queryKey: ['cancellation-periods'],
queryFn: () => cancellationPeriodApi.getAll(),
});
// Fetch contract durations
const { data: contractDurationsData } = useQuery({
queryKey: ['contract-durations'],
queryFn: () => contractDurationApi.getAll(),
});
// Fetch providers
const { data: providersData } = useQuery({
queryKey: ['providers'],
queryFn: () => providerApi.getAll(),
});
// Fetch contract categories (types)
const { data: contractCategoriesData } = useQuery({
queryKey: ['contract-categories'],
queryFn: () => contractCategoryApi.getAll(),
});
// Watch providerId for tariff filtering
const selectedProviderId = watch('providerId');
// Reset tariffId when providerId changes (but not on initial load)
const [previousProviderId, setPreviousProviderId] = useState(null);
// SIM-Karten State für Mobilfunk
interface SimCardInput {
id?: number;
phoneNumber: string;
simCardNumber: string;
pin: string;
puk: string;
hasExistingPin?: boolean; // Zeigt an ob PIN bereits in DB vorhanden
hasExistingPuk?: boolean; // Zeigt an ob PUK bereits in DB vorhanden
isMultisim: boolean;
isMain: boolean;
}
const [simCards, setSimCards] = useState([]);
// Rufnummern State für Internet-Verträge
interface PhoneNumberInput {
id?: number;
phoneNumber: string;
sipUsername: string;
sipPassword: string;
hasExistingSipPassword?: boolean;
sipServer: string;
isMain: boolean;
}
const [phoneNumbers, setPhoneNumbers] = useState([]);
const [isContractLoaded, setIsContractLoaded] = useState(false);
// Portal-Benutzername: Manuell oder Stressfrei-Adresse
const [usernameType, setUsernameType] = useState<'manual' | 'stressfrei'>('manual');
const [selectedStressfreiEmailId, setSelectedStressfreiEmailId] = useState('');
// Passwort-Sichtbarkeit
const [showPortalPassword, setShowPortalPassword] = useState(false);
const [showInternetPassword, setShowInternetPassword] = useState(false);
const [showSipPasswords, setShowSipPasswords] = useState>({});
const [showSimPins, setShowSimPins] = useState>({});
const [showSimPuks, setShowSimPuks] = useState>({});
// Status-Info Modal
const [showStatusInfo, setShowStatusInfo] = useState(false);
// For new contracts, mark as "loaded" immediately so provider change detection works
useEffect(() => {
if (!isEdit) {
setIsContractLoaded(true);
}
}, [isEdit]);
// Set preselected customer from URL params (for new contracts)
useEffect(() => {
if (!isEdit && preselectedCustomerId && customersData?.data) {
// Only set if the customer exists in the list
const customerExists = customersData.data.some(c => c.id.toString() === preselectedCustomerId);
if (customerExists) {
setValue('customerId', preselectedCustomerId);
}
}
}, [isEdit, preselectedCustomerId, customersData, setValue]);
// Reset tariffId when providerId changes (but only after initial contract load)
useEffect(() => {
// Only reset tariff if:
// 1. Contract has been loaded (or we're creating new)
// 2. Provider actually changed (not empty/null -> value which happens on load)
// Note: previousProviderId can be '' (empty string) initially, so we need to check for truthy
if (isContractLoaded && previousProviderId && selectedProviderId !== previousProviderId) {
setValue('tariffId', '');
}
setPreviousProviderId(selectedProviderId);
}, [selectedProviderId, previousProviderId, setValue, isContractLoaded]);
// Set default type when categories are loaded (only for new contracts)
useEffect(() => {
if (!isEdit && contractCategoriesData?.data && contractCategoriesData.data.length > 0) {
const currentType = watch('type');
// Only set if current type is empty or doesn't exist in categories
const categories = contractCategoriesData.data.filter(c => c.isActive);
const typeExists = categories.some(c => c.code === currentType);
if (!currentType || !typeExists) {
const firstCategory = categories.sort((a, b) => a.sortOrder - b.sortOrder)[0];
if (firstCategory) {
setValue('type', firstCategory.code);
}
}
}
}, [contractCategoriesData, isEdit, setValue, watch]);
// Bootstrap: Set customerId first so customerDetail can load (needed for dropdowns)
useEffect(() => {
if (isEdit && contract?.data && !customerId) {
setValue('customerId', contract.data.customerId.toString());
}
}, [isEdit, contract, customerId, setValue]);
// Reset form when contract data AND ALL dropdown data are loaded
// This ensures dropdowns can properly display the selected values
// We need customerDetail for addresses/bankCards/documents and providersData for tariffs
useEffect(() => {
if (contract?.data && platformsData?.data && contractCategoriesData?.data && providersData?.data && customerDetail?.data) {
const c = contract.data;
reset({
customerId: c.customerId.toString(),
type: c.type,
status: c.status,
addressId: c.addressId?.toString() || '',
billingAddressId: c.billingAddressId?.toString() || '',
bankCardId: c.bankCardId?.toString() || '',
identityDocumentId: c.identityDocumentId?.toString() || '',
salesPlatformId: c.salesPlatformId?.toString() || '',
providerId: c.providerId?.toString() || '',
tariffId: c.tariffId?.toString() || '',
providerName: c.providerName || '',
tariffName: c.tariffName || '',
customerNumberAtProvider: c.customerNumberAtProvider || '',
priceFirst12Months: c.priceFirst12Months || '',
priceFrom13Months: c.priceFrom13Months || '',
priceAfter24Months: c.priceAfter24Months || '',
startDate: c.startDate ? c.startDate.split('T')[0] : '',
endDate: c.endDate ? c.endDate.split('T')[0] : '',
cancellationPeriodId: c.cancellationPeriodId?.toString() || '',
contractDurationId: c.contractDurationId?.toString() || '',
commission: c.commission || '',
portalUsername: c.portalUsername || '',
notes: c.notes || '',
// Energy details
meterId: c.energyDetails?.meterId?.toString() || '',
maloId: c.energyDetails?.maloId || '',
annualConsumption: c.energyDetails?.annualConsumption || '',
annualConsumptionKwh: c.energyDetails?.annualConsumptionKwh || '',
basePrice: c.energyDetails?.basePrice || '',
unitPrice: c.energyDetails?.unitPrice || '',
bonus: c.energyDetails?.bonus || '',
previousProviderName: c.energyDetails?.previousProviderName || '',
previousCustomerNumber: c.energyDetails?.previousCustomerNumber || '',
// Internet details
downloadSpeed: c.internetDetails?.downloadSpeed || '',
uploadSpeed: c.internetDetails?.uploadSpeed || '',
routerModel: c.internetDetails?.routerModel || '',
routerSerialNumber: c.internetDetails?.routerSerialNumber || '',
installationDate: c.internetDetails?.installationDate ? c.internetDetails.installationDate.split('T')[0] : '',
internetUsername: c.internetDetails?.internetUsername || '',
homeId: c.internetDetails?.homeId || '',
activationCode: c.internetDetails?.activationCode || '',
// Mobile details
requiresMultisim: c.mobileDetails?.requiresMultisim || false,
dataVolume: c.mobileDetails?.dataVolume || '',
includedMinutes: c.mobileDetails?.includedMinutes || '',
includedSMS: c.mobileDetails?.includedSMS || '',
deviceModel: c.mobileDetails?.deviceModel || '',
deviceImei: c.mobileDetails?.deviceImei || '',
// Legacy
phoneNumber: c.mobileDetails?.phoneNumber || '',
simCardNumber: c.mobileDetails?.simCardNumber || '',
// TV details
receiverModel: c.tvDetails?.receiverModel || '',
smartcardNumber: c.tvDetails?.smartcardNumber || '',
tvPackage: c.tvDetails?.package || '',
// Car insurance details
licensePlate: c.carInsuranceDetails?.licensePlate || '',
hsn: c.carInsuranceDetails?.hsn || '',
tsn: c.carInsuranceDetails?.tsn || '',
vin: c.carInsuranceDetails?.vin || '',
vehicleType: c.carInsuranceDetails?.vehicleType || '',
firstRegistration: c.carInsuranceDetails?.firstRegistration ? c.carInsuranceDetails.firstRegistration.split('T')[0] : '',
noClaimsClass: c.carInsuranceDetails?.noClaimsClass || '',
insuranceType: c.carInsuranceDetails?.insuranceType || 'LIABILITY',
deductiblePartial: c.carInsuranceDetails?.deductiblePartial || '',
deductibleFull: c.carInsuranceDetails?.deductibleFull || '',
policyNumber: c.carInsuranceDetails?.policyNumber || '',
previousInsurer: c.carInsuranceDetails?.previousInsurer || '',
// Kündigungsdaten
cancellationConfirmationDate: c.cancellationConfirmationDate ? c.cancellationConfirmationDate.split('T')[0] : '',
cancellationConfirmationOptionsDate: c.cancellationConfirmationOptionsDate ? c.cancellationConfirmationOptionsDate.split('T')[0] : '',
wasSpecialCancellation: c.wasSpecialCancellation || false,
// Vorgänger-Vertrag
previousContractId: c.previousContractId?.toString() || '',
});
// Load simCards if available
if (c.mobileDetails?.simCards && c.mobileDetails.simCards.length > 0) {
setSimCards(c.mobileDetails.simCards.map(sc => ({
id: sc.id,
phoneNumber: sc.phoneNumber || '',
simCardNumber: sc.simCardNumber || '',
pin: '', // PIN/PUK werden nicht im Klartext geladen
puk: '',
hasExistingPin: !!sc.pin, // true wenn verschlüsselter Wert vorhanden
hasExistingPuk: !!sc.puk, // true wenn verschlüsselter Wert vorhanden
isMultisim: sc.isMultisim,
isMain: sc.isMain,
})));
} else {
setSimCards([]);
}
// Load phoneNumbers if available (Internet contracts)
if (c.internetDetails?.phoneNumbers && c.internetDetails.phoneNumbers.length > 0) {
setPhoneNumbers(c.internetDetails.phoneNumbers.map(pn => ({
id: pn.id,
phoneNumber: pn.phoneNumber || '',
sipUsername: pn.sipUsername || '',
sipPassword: '', // SIP-Passwort wird nicht im Klartext geladen
hasExistingSipPassword: !!pn.sipPasswordEncrypted,
sipServer: pn.sipServer || '',
isMain: pn.isMain,
})));
} else {
setPhoneNumbers([]);
}
// Portal-Benutzername-Typ initialisieren
if (c.stressfreiEmailId) {
setUsernameType('stressfrei');
setSelectedStressfreiEmailId(c.stressfreiEmailId.toString());
} else {
setUsernameType('manual');
setSelectedStressfreiEmailId('');
}
// Mark contract as loaded so provider change detection works correctly
setIsContractLoaded(true);
}
}, [contract, reset, platformsData, contractCategoriesData, providersData, customerDetail]);
// Watch für Enddatum-Berechnung
const startDate = watch('startDate');
const contractDurationId = watch('contractDurationId');
// Berechne Vertragsende aus Beginn + Laufzeit
useEffect(() => {
if (startDate && contractDurationId && contractDurationsData?.data) {
const duration = contractDurationsData.data.find(d => d.id === parseInt(contractDurationId));
if (duration) {
const start = new Date(startDate);
const code = duration.code;
// Parse duration code (z.B. "12M", "24M", "1J", "2J")
const match = code.match(/^(\d+)([MTJ])$/);
if (match) {
const value = parseInt(match[1]);
const unit = match[2];
let endDate = new Date(start);
if (unit === 'T') {
endDate.setDate(endDate.getDate() + value);
} else if (unit === 'M') {
endDate.setMonth(endDate.getMonth() + value);
} else if (unit === 'J') {
endDate.setFullYear(endDate.getFullYear() + value);
}
// Setze das berechnete Enddatum
setValue('endDate', endDate.toISOString().split('T')[0]);
}
}
}
}, [startDate, contractDurationId, contractDurationsData, setValue]);
const createMutation = useMutation({
mutationFn: contractApi.create,
onSuccess: (_, variables) => {
queryClient.invalidateQueries({ queryKey: ['contracts'] });
if (variables.customerId) {
queryClient.invalidateQueries({ queryKey: ['customer', variables.customerId.toString()] });
}
queryClient.invalidateQueries({ queryKey: ['customers'] });
// Navigate back to customer's contracts tab if coming from customer
if (preselectedCustomerId) {
navigate(`/customers/${preselectedCustomerId}?tab=contracts`);
} else {
navigate('/contracts');
}
},
});
const updateMutation = useMutation({
mutationFn: (data: any) => contractApi.update(parseInt(id!), data),
onSuccess: (_, variables) => {
queryClient.invalidateQueries({ queryKey: ['contracts'] });
queryClient.invalidateQueries({ queryKey: ['contract', id] });
if (variables.customerId) {
queryClient.invalidateQueries({ queryKey: ['customer', variables.customerId.toString()] });
}
queryClient.invalidateQueries({ queryKey: ['customers'] });
navigate(`/contracts/${id}`);
},
});
const onSubmit = (data: any) => {
// Helper to safely parse integers - returns undefined for empty/invalid values
const safeParseInt = (val: string | number | undefined | null): number | undefined => {
if (val === undefined || val === null || val === '') return undefined;
const parsed = parseInt(String(val));
return isNaN(parsed) ? undefined : parsed;
};
// Find the selected category to get its ID
const selectedCategory = contractCategories.find(c => c.code === data.type);
// Validate required fields
const customerIdParsed = safeParseInt(data.customerId);
if (!customerIdParsed) {
alert('Bitte wählen Sie einen Kunden aus');
return;
}
if (!data.type || !selectedCategory) {
alert('Bitte wählen Sie einen Vertragstyp aus');
return;
}
// Helper: Leere Strings werden zu null, damit Prisma das Feld auf NULL setzt
const emptyToNull = (val: string | undefined | null): string | null => {
if (val === undefined || val === null || val === '') return null;
return val;
};
const contractData: any = {
customerId: customerIdParsed,
type: data.type,
contractCategoryId: selectedCategory.id,
status: data.status,
addressId: safeParseInt(data.addressId) ?? null,
billingAddressId: safeParseInt(data.billingAddressId) ?? null,
bankCardId: safeParseInt(data.bankCardId) ?? null,
identityDocumentId: safeParseInt(data.identityDocumentId) ?? null,
salesPlatformId: safeParseInt(data.salesPlatformId) ?? null,
providerId: safeParseInt(data.providerId) ?? null,
tariffId: safeParseInt(data.tariffId) ?? null,
providerName: emptyToNull(data.providerName),
tariffName: emptyToNull(data.tariffName),
customerNumberAtProvider: emptyToNull(data.customerNumberAtProvider),
priceFirst12Months: emptyToNull(data.priceFirst12Months),
priceFrom13Months: emptyToNull(data.priceFrom13Months),
priceAfter24Months: emptyToNull(data.priceAfter24Months),
startDate: data.startDate ? new Date(data.startDate) : null,
endDate: data.endDate ? new Date(data.endDate) : null,
cancellationPeriodId: safeParseInt(data.cancellationPeriodId) ?? null,
contractDurationId: safeParseInt(data.contractDurationId) ?? null,
commission: data.commission ? parseFloat(data.commission) : null,
// Portal-Benutzername: entweder manuell oder Stressfrei-Adresse
portalUsername: usernameType === 'manual' ? emptyToNull(data.portalUsername) : null,
stressfreiEmailId: usernameType === 'stressfrei' && selectedStressfreiEmailId ? parseInt(selectedStressfreiEmailId) : null,
portalPassword: data.portalPassword || undefined, // Passwort: undefined = nicht ändern
notes: emptyToNull(data.notes),
cancellationConfirmationDate: data.cancellationConfirmationDate ? new Date(data.cancellationConfirmationDate) : null,
cancellationConfirmationOptionsDate: data.cancellationConfirmationOptionsDate ? new Date(data.cancellationConfirmationOptionsDate) : null,
wasSpecialCancellation: data.wasSpecialCancellation || false,
previousContractId: safeParseInt(data.previousContractId) ?? null,
};
// Add type-specific details
if (['ELECTRICITY', 'GAS'].includes(data.type)) {
contractData.energyDetails = {
meterId: safeParseInt(data.meterId) ?? null,
maloId: emptyToNull(data.maloId),
annualConsumption: data.annualConsumption ? parseFloat(data.annualConsumption) : null,
annualConsumptionKwh: data.annualConsumptionKwh ? parseFloat(data.annualConsumptionKwh) : null,
basePrice: data.basePrice ? parseFloat(data.basePrice) : null,
unitPrice: data.unitPrice ? parseFloat(data.unitPrice) : null,
bonus: data.bonus ? parseFloat(data.bonus) : null,
previousProviderName: emptyToNull(data.previousProviderName),
previousCustomerNumber: emptyToNull(data.previousCustomerNumber),
};
}
if (['DSL', 'CABLE', 'FIBER'].includes(data.type)) {
contractData.internetDetails = {
downloadSpeed: safeParseInt(data.downloadSpeed) ?? null,
uploadSpeed: safeParseInt(data.uploadSpeed) ?? null,
routerModel: emptyToNull(data.routerModel),
routerSerialNumber: emptyToNull(data.routerSerialNumber),
installationDate: data.installationDate ? new Date(data.installationDate) : null,
// Internet-Zugangsdaten
internetUsername: emptyToNull(data.internetUsername),
internetPassword: data.internetPassword || undefined, // Passwort: undefined = nicht ändern
// Glasfaser-spezifisch
homeId: emptyToNull(data.homeId),
// Vodafone DSL/Kabel spezifisch
activationCode: emptyToNull(data.activationCode),
// Rufnummern mit SIP-Daten
phoneNumbers: phoneNumbers.length > 0 ? phoneNumbers.map(pn => ({
id: pn.id,
phoneNumber: pn.phoneNumber || '',
isMain: pn.isMain ?? false,
sipUsername: emptyToNull(pn.sipUsername),
sipPassword: pn.sipPassword || undefined, // Passwort: undefined = nicht ändern
sipServer: emptyToNull(pn.sipServer),
})) : undefined,
};
}
if (data.type === 'MOBILE') {
contractData.mobileDetails = {
requiresMultisim: data.requiresMultisim || false,
dataVolume: data.dataVolume ? parseFloat(data.dataVolume) : null,
includedMinutes: safeParseInt(data.includedMinutes) ?? null,
includedSMS: safeParseInt(data.includedSMS) ?? null,
deviceModel: emptyToNull(data.deviceModel),
deviceImei: emptyToNull(data.deviceImei),
// Legacy
phoneNumber: emptyToNull(data.phoneNumber),
simCardNumber: emptyToNull(data.simCardNumber),
// SIM-Karten
simCards: simCards.length > 0 ? simCards.map(sc => ({
id: sc.id,
phoneNumber: emptyToNull(sc.phoneNumber),
simCardNumber: emptyToNull(sc.simCardNumber),
pin: sc.pin || undefined, // Passwort: undefined = nicht ändern
puk: sc.puk || undefined, // Passwort: undefined = nicht ändern
isMultisim: sc.isMultisim,
isMain: sc.isMain,
})) : undefined,
};
}
if (data.type === 'TV') {
contractData.tvDetails = {
receiverModel: emptyToNull(data.receiverModel),
smartcardNumber: emptyToNull(data.smartcardNumber),
package: emptyToNull(data.tvPackage),
};
}
if (data.type === 'CAR_INSURANCE') {
contractData.carInsuranceDetails = {
licensePlate: emptyToNull(data.licensePlate),
hsn: emptyToNull(data.hsn),
tsn: emptyToNull(data.tsn),
vin: emptyToNull(data.vin),
vehicleType: emptyToNull(data.vehicleType),
firstRegistration: data.firstRegistration ? new Date(data.firstRegistration) : null,
noClaimsClass: emptyToNull(data.noClaimsClass),
insuranceType: data.insuranceType,
deductiblePartial: data.deductiblePartial ? parseFloat(data.deductiblePartial) : null,
deductibleFull: data.deductibleFull ? parseFloat(data.deductibleFull) : null,
policyNumber: emptyToNull(data.policyNumber),
previousInsurer: emptyToNull(data.previousInsurer),
};
}
if (isEdit) {
updateMutation.mutate(contractData);
} else {
createMutation.mutate(contractData);
}
};
const isLoading = createMutation.isPending || updateMutation.isPending;
const error = createMutation.error || updateMutation.error;
const customer = customerDetail?.data;
const addresses = customer?.addresses || [];
const bankCards = customer?.bankCards?.filter((c) => c.isActive) || [];
const documents = customer?.identityDocuments?.filter((d) => d.isActive) || [];
const meters = customer?.meters?.filter((m) => m.isActive) || [];
const stressfreiEmails = customer?.stressfreiEmails?.filter((e: { isActive: boolean }) => e.isActive) || [];
const platforms = platformsData?.data || [];
const cancellationPeriods = cancellationPeriodsData?.data || [];
const contractDurations = contractDurationsData?.data || [];
const providers = providersData?.data?.filter(p => p.isActive) || [];
const contractCategories = contractCategoriesData?.data?.filter(c => c.isActive).sort((a, b) => a.sortOrder - b.sortOrder) || [];
const typeOptions = contractCategories.map(c => ({ value: c.code, label: c.name }));
// Available predecessor contracts (same customer, excluding current contract if editing)
const predecessorContracts = (customerContractsData?.data || [])
.filter(c => !isEdit || c.id !== parseInt(id!))
.sort((a, b) => new Date(b.startDate || 0).getTime() - new Date(a.startDate || 0).getTime());
// Get tariffs for selected provider
const selectedProvider = providers.find(p => p.id === parseInt(selectedProviderId || '0'));
const availableTariffs = selectedProvider?.tariffs?.filter(t => t.isActive) || [];
// Helper to format customer label with optional birth date
const formatCustomerLabel = (c: { customerNumber: string; companyName?: string; firstName: string; lastName: string; birthDate?: string }) => {
const name = c.companyName || `${c.firstName} ${c.lastName}`;
const birthDateStr = c.birthDate
? ` (geb. ${new Date(c.birthDate).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' })})`
: '';
return `${c.customerNumber} - ${name}${birthDateStr}`;
};
// Build customer options, ensuring current customer from contract is always included
const customerOptions = (() => {
const customers = customersData?.data || [];
const options = customers.map((c) => ({
value: c.id.toString(),
label: formatCustomerLabel(c),
}));
// If editing and contract's customer isn't in the list yet, add it
if (isEdit && contract?.data?.customer) {
const contractCustomer = contract.data.customer;
const exists = options.some((o) => o.value === contractCustomer.id.toString());
if (!exists) {
options.unshift({
value: contractCustomer.id.toString(),
label: formatCustomerLabel(contractCustomer),
});
}
}
return options;
})();
return (
{isEdit ? 'Vertrag bearbeiten' : 'Neuer Vertrag'}
{error && (
{error instanceof Error ? error.message : 'Ein Fehler ist aufgetreten'}
)}
{/* Status-Info Modal */}
setShowStatusInfo(false)} />
);
}