import React, { useState, useEffect } from 'react'; import { Employee, CreateEmployeeRequest, UpdateEmployeeRequest } from '../../../models/Employee'; import { ROLE_CONFIG, EMPLOYEE_TYPE_CONFIG } from '../../../models/defaults/employeeDefaults'; import { employeeService } from '../../../services/employeeService'; import { useAuth } from '../../../contexts/AuthContext'; import { useBackendValidation } from '../../../hooks/useBackendValidation'; import { useNotification } from '../../../contexts/NotificationContext'; interface EmployeeFormProps { mode: 'create' | 'edit'; employee?: Employee; onSuccess: () => void; onCancel: () => void; } type EmployeeType = 'manager' | 'personell' | 'apprentice' | 'guest'; type ContractType = 'small' | 'large' | 'flexible'; // ===== TYP-DEFINITIONEN ===== interface EmployeeFormData { // Step 1: Grundinformationen firstname: string; lastname: string; email: string; password: string; // Step 2: Mitarbeiterkategorie employeeType: EmployeeType; contractType: ContractType | undefined; isTrainee: boolean; // Step 3: Berechtigungen & Status roles: string[]; canWorkAlone: boolean; isActive: boolean; } interface PasswordFormData { newPassword: string; confirmPassword: string; } // ===== HOOK FÜR FORMULAR-LOGIK ===== const useEmployeeForm = (mode: 'create' | 'edit', employee?: Employee) => { const { validationErrors, getFieldError, hasErrors, executeWithValidation, isSubmitting, clearErrors } = useBackendValidation(); const [currentStep, setCurrentStep] = useState(0); const [formData, setFormData] = useState({ firstname: '', lastname: '', email: '', password: '', employeeType: 'personell', contractType: 'small', isTrainee: false, roles: ['user'], canWorkAlone: false, isActive: true }); const [passwordForm, setPasswordForm] = useState({ newPassword: '', confirmPassword: '' }); const [showPasswordSection, setShowPasswordSection] = useState(false); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); // Steps definition const steps = [ { id: 'basic-info', title: 'Grundinformationen', subtitle: 'Name und Kontaktdaten' }, { id: 'employee-category', title: 'Mitarbeiterkategorie', subtitle: 'Typ und Vertrag' }, { id: 'permissions', title: 'Berechtigungen', subtitle: 'Rollen und Eigenständigkeit' } ]; // Add password step for edit mode if (mode === 'edit') { steps.push({ id: 'security', title: 'Sicherheit', subtitle: 'Passwort und Status' }); } // Generate email preview const generateEmailPreview = (firstname: string, lastname: string): string => { const convertUmlauts = (str: string): string => { return str .toLowerCase() .replace(/ü/g, 'ue') .replace(/ö/g, 'oe') .replace(/ä/g, 'ae') .replace(/ß/g, 'ss'); }; const cleanFirstname = convertUmlauts(firstname).replace(/[^a-z0-9]/g, ''); const cleanLastname = convertUmlauts(lastname).replace(/[^a-z0-9]/g, ''); return `${cleanFirstname}.${cleanLastname}@sp.de`; }; const emailPreview = generateEmailPreview(formData.firstname, formData.lastname); // Initialize form data when employee is provided useEffect(() => { if (mode === 'edit' && employee) { setFormData({ firstname: employee.firstname, lastname: employee.lastname, email: employee.email, password: '', employeeType: employee.employeeType, contractType: employee.contractType, isTrainee: employee.isTrainee || false, roles: employee.roles || ['user'], canWorkAlone: employee.canWorkAlone, isActive: employee.isActive }); } }, [mode, employee]); // ===== SIMPLE FRONTEND VALIDATION (ONLY FOR REQUIRED FIELDS) ===== const validateStep1 = (): boolean => { // Only check for empty required fields - let backend handle everything else if (!formData.firstname.trim()) { setError('Bitte geben Sie einen Vornamen ein.'); return false; } if (!formData.lastname.trim()) { setError('Bitte geben Sie einen Nachnamen ein.'); return false; } return true; }; const validateStep2 = (): boolean => { if (!formData.employeeType) { setError('Bitte wählen Sie eine Mitarbeiterkategorie aus.'); return false; } return true; }; const validateCurrentStep = (stepIndex: number): boolean => { switch (stepIndex) { case 0: return validateStep1(); case 1: return validateStep2(); default: return true; } }; // ===== NAVIGATIONS-FUNKTIONEN ===== const goToNextStep = (): void => { setError(''); clearErrors(); // Clear previous validation errors if (!validateCurrentStep(currentStep)) { return; } if (currentStep < steps.length - 1) { setCurrentStep(prev => prev + 1); } }; const goToPrevStep = (): void => { setError(''); clearErrors(); // Clear validation errors when going back if (currentStep > 0) { setCurrentStep(prev => prev - 1); } }; const handleStepChange = (stepIndex: number): void => { setError(''); clearErrors(); // Clear validation errors when changing steps // Nur erlauben, zu bereits validierten Schritten zu springen if (stepIndex <= currentStep + 1) { // Vor dem Wechsel validieren if (stepIndex > currentStep && !validateCurrentStep(currentStep)) { return; } setCurrentStep(stepIndex); } }; // ===== FORM HANDLER ===== const handleInputChange = (e: React.ChangeEvent) => { const { name, value, type } = e.target; setFormData(prev => ({ ...prev, [name]: type === 'checkbox' ? (e.target as HTMLInputElement).checked : value })); // Clear field-specific error when user starts typing if (validationErrors.length > 0) { clearErrors(); } }; const handlePasswordChange = (e: React.ChangeEvent) => { const { name, value } = e.target; setPasswordForm(prev => ({ ...prev, [name]: value })); // Clear password errors when user starts typing if (validationErrors.length > 0) { clearErrors(); } }; const handleRoleChange = (role: string, checked: boolean) => { setFormData(prev => { if (checked) { return { ...prev, roles: [role] }; } else { const newRoles = prev.roles.filter(r => r !== role); return { ...prev, roles: newRoles.length > 0 ? newRoles : ['user'] }; } }); }; const handleEmployeeTypeChange = (employeeType: EmployeeType) => { // Determine contract type based on employee type let contractType: ContractType | undefined; if (employeeType === 'manager' || employeeType === 'apprentice') { contractType = 'flexible'; } else if (employeeType !== 'guest') { contractType = 'small'; } // Determine if can work alone based on employee type const canWorkAlone = employeeType === 'manager' || (employeeType === 'personell' && !formData.isTrainee); // Reset isTrainee if not personell const isTrainee = employeeType === 'personell' ? formData.isTrainee : false; setFormData(prev => ({ ...prev, employeeType, contractType, canWorkAlone, isTrainee })); }; const handleTraineeChange = (isTrainee: boolean) => { setFormData(prev => ({ ...prev, isTrainee, canWorkAlone: prev.employeeType === 'personell' ? !isTrainee : prev.canWorkAlone })); }; const handleContractTypeChange = (contractType: ContractType) => { setFormData(prev => ({ ...prev, contractType })); }; const handleSubmit = async (): Promise => { setLoading(true); setError(''); clearErrors(); try { if (mode === 'create') { const createData: CreateEmployeeRequest = { firstname: formData.firstname.trim(), lastname: formData.lastname.trim(), password: formData.password, roles: formData.roles, employeeType: formData.employeeType, contractType: formData.employeeType !== 'guest' ? formData.contractType : undefined, canWorkAlone: formData.canWorkAlone, isTrainee: formData.isTrainee }; // Use executeWithValidation ONLY for the API call await executeWithValidation(() => employeeService.createEmployee(createData) ); } else if (employee) { const updateData: UpdateEmployeeRequest = { firstname: formData.firstname.trim(), lastname: formData.lastname.trim(), roles: formData.roles, employeeType: formData.employeeType, contractType: formData.employeeType !== 'guest' ? formData.contractType : undefined, canWorkAlone: formData.canWorkAlone, isActive: formData.isActive, isTrainee: formData.isTrainee }; // Use executeWithValidation for the update call await executeWithValidation(() => employeeService.updateEmployee(employee.id, updateData) ); // Password change logic - backend will validate password requirements if (showPasswordSection && passwordForm.newPassword) { if (passwordForm.newPassword !== passwordForm.confirmPassword) { throw new Error('Die Passwörter stimmen nicht überein'); } // Use executeWithValidation for password change too await executeWithValidation(() => employeeService.changePassword(employee.id, { currentPassword: '', newPassword: passwordForm.newPassword, confirmPassword: passwordForm.confirmPassword }) ); } } return Promise.resolve(); } catch (err: any) { // Only set error if it's not a validation error (validation errors are handled by the hook) if (!err.validationErrors) { setError(err.message || `Fehler beim ${mode === 'create' ? 'Erstellen' : 'Aktualisieren'} des Mitarbeiters`); } return Promise.reject(err); } finally { setLoading(false); } }; const isStepCompleted = (stepIndex: number): boolean => { switch (stepIndex) { case 0: return !!formData.firstname.trim() && !!formData.lastname.trim(); // REMOVE: (mode === 'edit' || formData.password.length >= 6) case 1: return !!formData.employeeType; case 2: return true; // Permissions step is always valid case 3: return true; // Security step is always valid default: return false; } }; return { // State currentStep, formData, passwordForm, loading: loading || isSubmitting, error, steps, emailPreview, showPasswordSection, validationErrors, getFieldError, hasErrors, // Actions goToNextStep, goToPrevStep, handleStepChange, handleInputChange, handlePasswordChange, handleRoleChange, handleEmployeeTypeChange, handleTraineeChange, handleContractTypeChange, handleSubmit, setShowPasswordSection, clearErrors, // Helpers isStepCompleted }; }; // ===== STEP-INHALTS-KOMPONENTEN ===== interface StepContentProps { formData: EmployeeFormData; passwordForm: PasswordFormData; onInputChange: (e: React.ChangeEvent) => void; onPasswordChange: (e: React.ChangeEvent) => void; onRoleChange: (role: string, checked: boolean) => void; onEmployeeTypeChange: (employeeType: EmployeeType) => void; onTraineeChange: (isTrainee: boolean) => void; onContractTypeChange: (contractType: ContractType) => void; emailPreview: string; mode: 'create' | 'edit'; showPasswordSection: boolean; onShowPasswordSection: (show: boolean) => void; hasRole: (roles: string[]) => boolean; getFieldError: (fieldName: string) => string | null; hasErrors: (fieldName?: string) => boolean; } const Step1Content: React.FC = ({ formData, onInputChange, emailPreview, mode }) => (
{/* Email Preview */}
{emailPreview || 'max.mustermann@sp.de'}
Die E-Mail Adresse wird automatisch aus Vorname und Nachname generiert.
{mode === 'create' && (
Das Passwort muss mindestens 8 Zeichen lang sein und Groß-/Kleinbuchstaben, Zahlen und Sonderzeichen enthalten.
)}
); const Step2Content: React.FC = ({ formData, onEmployeeTypeChange, onTraineeChange, onContractTypeChange, hasRole, getFieldError }) => { const contractTypeOptions = [ { value: 'small' as const, label: 'Kleiner Vertrag', description: '1 Schicht pro Woche' }, { value: 'large' as const, label: 'Großer Vertrag', description: '2 Schichten pro Woche' }, { value: 'flexible' as const, label: 'Flexibler Vertrag', description: 'Flexible Arbeitszeiten' } ]; const showContractType = formData.employeeType !== 'guest'; const employeeTypeError = getFieldError('employeeType'); const contractTypeError = getFieldError('contractType'); return (
{/* Mitarbeiter Kategorie */}

👥 Mitarbeiter Kategorie

{employeeTypeError && (
{employeeTypeError}
)}
{Object.values(EMPLOYEE_TYPE_CONFIG).map(type => (
onEmployeeTypeChange(type.value)} > onEmployeeTypeChange(type.value)} style={{ marginRight: '12px', marginTop: '2px', width: '18px', height: '18px' }} />
{type.label}
{type.description}
{type.value.toUpperCase()}
))}
{/* Trainee checkbox for personell type */} {formData.employeeType === 'personell' && (
onTraineeChange(e.target.checked)} style={{ width: '18px', height: '18px' }} />
Neulinge benötigen zusätzliche Betreuung und können nicht eigenständig arbeiten.
)}
{/* Vertragstyp (nur für Admins und interne Mitarbeiter) */} {hasRole(['admin']) && showContractType && (

📝 Vertragstyp

{contractTypeError && (
{contractTypeError}
)}
{contractTypeOptions.map(contract => { const isFlexibleDisabled = contract.value === 'flexible' && formData.employeeType === 'personell'; const isSmallLargeDisabled = (contract.value === 'small' || contract.value === 'large') && (formData.employeeType === 'manager' || formData.employeeType === 'apprentice'); const isDisabled = isFlexibleDisabled || isSmallLargeDisabled; return (
onContractTypeChange(contract.value)} > onContractTypeChange(contract.value)} disabled={isDisabled} style={{ marginRight: '12px', marginTop: '2px', width: '18px', height: '18px' }} />
{contract.label} {isFlexibleDisabled && ( (Nicht verfügbar für Personell) )} {isSmallLargeDisabled && ( (Nicht verfügbar für {formData.employeeType === 'manager' ? 'Manager' : 'Auszubildende'}) )}
{contract.description}
{contract.value.toUpperCase()}
); })}
)}
); }; const Step3Content: React.FC = ({ formData, onInputChange, onRoleChange, hasRole, getFieldError }) => { const rolesError = getFieldError('roles'); const canWorkAloneError = getFieldError('canWorkAlone'); return (
{/* Eigenständigkeit */}

🎯 Eigenständigkeit

{canWorkAloneError && (
{canWorkAloneError}
)}
{formData.employeeType === 'manager' ? 'Chefs sind automatisch als eigenständig markiert.' : formData.employeeType === 'personell' && formData.isTrainee ? 'Auszubildende können nicht als eigenständig markiert werden.' : 'Dieser Mitarbeiter kann komplexe Aufgaben eigenständig lösen und benötigt keine ständige Betreuung.' }
{formData.canWorkAlone ? 'EIGENSTÄNDIG' : 'BETREUUNG'}
{/* Systemrollen (nur für Admins) */} {hasRole(['admin']) && (

⚙️ Systemrollen

{rolesError && (
{rolesError}
)}
{ROLE_CONFIG.map(role => (
onRoleChange(role.value, !formData.roles.includes(role.value))} > onRoleChange(role.value, e.target.checked)} style={{ marginRight: '10px', marginTop: '2px' }} />
{role.label}
{role.description}
))}
Hinweis: Ein Mitarbeiter kann mehrere Rollen haben.
)}
); }; const Step4Content: React.FC = ({ formData, passwordForm, onInputChange, onPasswordChange, showPasswordSection, onShowPasswordSection, mode, getFieldError }) => { const newPasswordError = getFieldError('newPassword'); const confirmPasswordError = getFieldError('confirmPassword'); const isActiveError = getFieldError('isActive'); return (
{/* Passwort ändern */}

🔒 Passwort zurücksetzen

{!showPasswordSection ? ( ) : (
{newPasswordError && (
{newPasswordError}
)}
{confirmPasswordError && (
{confirmPasswordError}
)}
Hinweis: Als Administrator können Sie das Passwort des Benutzers ohne Kenntnis des aktuellen Passworts zurücksetzen.
)}
{/* Aktiv Status */} {mode === 'edit' && (
Inaktive Mitarbeiter können sich nicht anmelden und werden nicht für Schichten eingeplant.
{isActiveError && (
{isActiveError}
)}
)}
); }; // ===== HAUPTKOMPONENTE ===== const EmployeeForm: React.FC = ({ mode, employee, onSuccess, onCancel }) => { const { hasRole } = useAuth(); const { showNotification } = useNotification(); const { currentStep, formData, passwordForm, loading, error, steps, emailPreview, showPasswordSection, validationErrors, getFieldError, hasErrors, goToNextStep, goToPrevStep, handleStepChange, handleInputChange, handlePasswordChange, handleRoleChange, handleEmployeeTypeChange, handleTraineeChange, handleContractTypeChange, handleSubmit, setShowPasswordSection, clearErrors } = useEmployeeForm(mode, employee); // Inline Step Indicator Komponente (wie in Setup.tsx) const StepIndicator: React.FC = () => (
{/* Verbindungslinien */}
{steps.map((step, index) => { const isCompleted = index < currentStep; const isCurrent = index === currentStep; const isClickable = index <= currentStep + 1; return (
{step.title}
{step.subtitle && (
{step.subtitle}
)}
); })}
); const renderStepContent = (): React.ReactNode => { const stepProps = { formData, passwordForm, onInputChange: handleInputChange, onPasswordChange: handlePasswordChange, onRoleChange: handleRoleChange, onEmployeeTypeChange: handleEmployeeTypeChange, onTraineeChange: handleTraineeChange, onContractTypeChange: handleContractTypeChange, emailPreview, mode, showPasswordSection, onShowPasswordSection: setShowPasswordSection, hasRole, getFieldError, hasErrors }; switch (currentStep) { case 0: return ; case 1: return ; case 2: return ; case 3: return ; default: return null; } }; const handleFinalSubmit = async (): Promise => { try { await handleSubmit(); // Show success notification showNotification({ // Changed from addNotification to showNotification type: 'success', title: 'Erfolg', message: mode === 'create' ? 'Mitarbeiter wurde erfolgreich erstellt' : 'Mitarbeiter wurde erfolgreich aktualisiert' }); onSuccess(); } catch (err) { // Errors are already handled by the hook and shown as notifications } }; const getNextButtonText = (): string => { if (loading) return '⏳ Wird gespeichert...'; if (currentStep === steps.length - 1) { return mode === 'create' ? 'Mitarbeiter erstellen' : 'Änderungen speichern'; } return 'Weiter →'; }; const isLastStep = currentStep === steps.length - 1; return (

{mode === 'create' ? '👤 Neuen Mitarbeiter erstellen' : '✏️ Mitarbeiter bearbeiten'}

{/* Inline Step Indicator */} {/* Aktueller Schritt Titel und Beschreibung */}

{steps[currentStep].title}

{steps[currentStep].subtitle && (

{steps[currentStep].subtitle}

)}
{/* Schritt-Inhalt */}
{renderStepContent()}
{/* Navigations-Buttons */}
{/* Zusätzliche Informationen */} {isLastStep && !loading && (
{mode === 'create' ? 'Überprüfen Sie alle Daten, bevor Sie den Mitarbeiter erstellen' : 'Überprüfen Sie alle Änderungen, bevor Sie sie speichern' }
)}
); }; export default EmployeeForm;