fixed settings

This commit is contained in:
2025-10-20 02:30:40 +02:00
parent e80bb81b5d
commit ec28c061a0
11 changed files with 280 additions and 102 deletions

View File

@@ -7,9 +7,20 @@ import { AuthRequest } from '../middleware/auth.js';
import { CreateEmployeeRequest } from '../models/Employee.js'; import { CreateEmployeeRequest } from '../models/Employee.js';
function generateEmail(firstname: string, lastname: string): string { function generateEmail(firstname: string, lastname: string): string {
// Remove special characters and convert to lowercase // Convert German umlauts to their expanded forms
const cleanFirstname = firstname.toLowerCase().replace(/[^a-z0-9]/g, ''); const convertUmlauts = (str: string): string => {
const cleanLastname = lastname.toLowerCase().replace(/[^a-z0-9]/g, ''); return str
.toLowerCase()
.replace(/ü/g, 'ue')
.replace(/ö/g, 'oe')
.replace(/ä/g, 'ae')
.replace(/ß/g, 'ss');
};
// Remove any remaining special characters and convert to lowercase
const cleanFirstname = convertUmlauts(firstname).replace(/[^a-z0-9]/g, '');
const cleanLastname = convertUmlauts(lastname).replace(/[^a-z0-9]/g, '');
return `${cleanFirstname}.${cleanLastname}@sp.de`; return `${cleanFirstname}.${cleanLastname}@sp.de`;
} }

View File

@@ -104,11 +104,14 @@ CREATE TABLE IF NOT EXISTS employee_availability (
UNIQUE(employee_id, plan_id, shift_id) UNIQUE(employee_id, plan_id, shift_id)
); );
-- Performance indexes -- Performance indexes (UPDATED - removed role index from employees)
CREATE INDEX IF NOT EXISTS idx_employees_role_active ON employees(role, is_active);
CREATE INDEX IF NOT EXISTS idx_employees_email_active ON employees(email, is_active); CREATE INDEX IF NOT EXISTS idx_employees_email_active ON employees(email, is_active);
CREATE INDEX IF NOT EXISTS idx_employees_type_active ON employees(employee_type, is_active); CREATE INDEX IF NOT EXISTS idx_employees_type_active ON employees(employee_type, is_active);
-- Index for employee_roles table (NEW)
CREATE INDEX IF NOT EXISTS idx_employee_roles_employee ON employee_roles(employee_id);
CREATE INDEX IF NOT EXISTS idx_employee_roles_role ON employee_roles(role);
CREATE INDEX IF NOT EXISTS idx_shift_plans_status_date ON shift_plans(status, start_date, end_date); CREATE INDEX IF NOT EXISTS idx_shift_plans_status_date ON shift_plans(status, start_date, end_date);
CREATE INDEX IF NOT EXISTS idx_shift_plans_created_by ON shift_plans(created_by); CREATE INDEX IF NOT EXISTS idx_shift_plans_created_by ON shift_plans(created_by);
CREATE INDEX IF NOT EXISTS idx_shift_plans_template ON shift_plans(is_template, status); CREATE INDEX IF NOT EXISTS idx_shift_plans_template ON shift_plans(is_template, status);

View File

@@ -14,7 +14,7 @@ export const EMPLOYEE_DEFAULTS = {
export const MANAGER_DEFAULTS = { export const MANAGER_DEFAULTS = {
role: 'admin' as const, role: 'admin' as const,
employeeType: 'manager' as const, employeeType: 'manager' as const,
contractType: 'large' as const, // Not really used but required by DB contractType: 'large' as const,
canWorkAlone: true, canWorkAlone: true,
isActive: true isActive: true
}; };
@@ -64,26 +64,25 @@ export const AVAILABILITY_PREFERENCES = {
} as const; } as const;
// Default availability for new employees (all shifts unavailable as level 3) // Default availability for new employees (all shifts unavailable as level 3)
export function createDefaultAvailabilities(employeeId: string, planId: string, timeSlotIds: string[]): Omit<EmployeeAvailability, 'id'>[] { // UPDATED: Now uses shiftId instead of timeSlotId + dayOfWeek
export function createDefaultAvailabilities(employeeId: string, planId: string, shiftIds: string[]): Omit<EmployeeAvailability, 'id'>[] {
const availabilities: Omit<EmployeeAvailability, 'id'>[] = []; const availabilities: Omit<EmployeeAvailability, 'id'>[] = [];
// Monday to Friday (1-5) // Create one availability entry per shift
for (let day = 1; day <= 5; day++) { for (const shiftId of shiftIds) {
for (const timeSlotId of timeSlotIds) { availabilities.push({
availabilities.push({ employeeId,
employeeId, planId,
planId, shiftId,
dayOfWeek: day, preferenceLevel: 3 // Default to "unavailable" - employees must explicitly set availability
timeSlotId, });
preferenceLevel: 3 // Default to "unavailable" - employees must explicitly set availability
});
}
} }
return availabilities; return availabilities;
} }
// Create complete manager availability for all days (default: only Mon-Tue available) // Create complete manager availability for all days (default: only Mon-Tue available)
// NOTE: This function might need revision based on new schema requirements
export function createManagerDefaultSchedule(managerId: string, planId: string, timeSlotIds: string[]): Omit<ManagerAvailability, 'id'>[] { export function createManagerDefaultSchedule(managerId: string, planId: string, timeSlotIds: string[]): Omit<ManagerAvailability, 'id'>[] {
const assignments: Omit<ManagerAvailability, 'id'>[] = []; const assignments: Omit<ManagerAvailability, 'id'>[] = [];

View File

@@ -1,25 +1,51 @@
// backend/src/models/helpers/employeeHelpers.ts // backend/src/models/helpers/employeeHelpers.ts
import { Employee, CreateEmployeeRequest, EmployeeAvailability } from '../Employee.js'; import { Employee, CreateEmployeeRequest, EmployeeAvailability } from '../Employee.js';
// Simplified validation - use schema validation instead // Email generation function (same as in controllers)
function generateEmail(firstname: string, lastname: string): string {
// Convert German umlauts to their expanded forms
const convertUmlauts = (str: string): string => {
return str
.toLowerCase()
.replace(/ü/g, 'ue')
.replace(/ö/g, 'oe')
.replace(/ä/g, 'ae')
.replace(/ß/g, 'ss');
};
// Remove any remaining special characters and convert to lowercase
const cleanFirstname = convertUmlauts(firstname).replace(/[^a-z0-9]/g, '');
const cleanLastname = convertUmlauts(lastname).replace(/[^a-z0-9]/g, '');
return `${cleanFirstname}.${cleanLastname}@sp.de`;
}
// UPDATED: Validation for new employee model
export function validateEmployeeData(employee: CreateEmployeeRequest): string[] { export function validateEmployeeData(employee: CreateEmployeeRequest): string[] {
const errors: string[] = []; const errors: string[] = [];
if (!employee.email?.includes('@')) { // Email is now auto-generated, so no email validation needed
errors.push('Valid email is required');
}
if (employee.password?.length < 6) { if (employee.password?.length < 6) {
errors.push('Password must be at least 6 characters long'); errors.push('Password must be at least 6 characters long');
} }
if (!employee.name?.trim() || employee.name.trim().length < 2) { if (!employee.firstname?.trim() || employee.firstname.trim().length < 2) {
errors.push('Name is required and must be at least 2 characters long'); errors.push('First name is required and must be at least 2 characters long');
}
if (!employee.lastname?.trim() || employee.lastname.trim().length < 2) {
errors.push('Last name is required and must be at least 2 characters long');
} }
return errors; return errors;
} }
// Generate email for employee (new helper function)
export function generateEmployeeEmail(firstname: string, lastname: string): string {
return generateEmail(firstname, lastname);
}
// Simplified business logic helpers // Simplified business logic helpers
export const isManager = (employee: Employee): boolean => export const isManager = (employee: Employee): boolean =>
employee.employeeType === 'manager'; employee.employeeType === 'manager';
@@ -38,3 +64,26 @@ export const canEmployeeWorkAlone = (employee: Employee): boolean =>
export const getEmployeeWorkHours = (employee: Employee): number => export const getEmployeeWorkHours = (employee: Employee): number =>
isManager(employee) ? 999 : (employee.contractType === 'small' ? 1 : 2); isManager(employee) ? 999 : (employee.contractType === 'small' ? 1 : 2);
// New helper for full name display
export const getFullName = (employee: { firstname: string; lastname: string }): string =>
`${employee.firstname} ${employee.lastname}`;
// Helper for availability validation
export function validateAvailabilityData(availability: Omit<EmployeeAvailability, 'id' | 'employeeId'>): string[] {
const errors: string[] = [];
if (!availability.planId) {
errors.push('Plan ID is required');
}
if (!availability.shiftId) {
errors.push('Shift ID is required');
}
if (![1, 2, 3].includes(availability.preferenceLevel)) {
errors.push('Preference level must be 1, 2, or 3');
}
return errors;
}

View File

@@ -78,7 +78,8 @@ export function calculateTotalRequiredEmployees(plan: ShiftPlan): number {
return plan.shifts.reduce((total, shift) => total + shift.requiredEmployees, 0); return plan.shifts.reduce((total, shift) => total + shift.requiredEmployees, 0);
} }
/*export function getScheduledShiftByDateAndTime( // UPDATED: Get scheduled shift by date and time slot
export function getScheduledShiftByDateAndTime(
plan: ShiftPlan, plan: ShiftPlan,
date: string, date: string,
timeSlotId: string timeSlotId: string
@@ -86,7 +87,7 @@ export function calculateTotalRequiredEmployees(plan: ShiftPlan): number {
return plan.scheduledShifts?.find(shift => return plan.scheduledShifts?.find(shift =>
shift.date === date && shift.timeSlotId === timeSlotId shift.date === date && shift.timeSlotId === timeSlotId
); );
}*/ }
export function canPublishPlan(plan: ShiftPlan): { canPublish: boolean; errors: string[] } { export function canPublishPlan(plan: ShiftPlan): { canPublish: boolean; errors: string[] } {
const errors: string[] = []; const errors: string[] = [];
@@ -116,3 +117,13 @@ export function canPublishPlan(plan: ShiftPlan): { canPublish: boolean; errors:
errors errors
}; };
} }
// NEW: Helper for shift generation
export function generateShiftId(): string {
return `shift_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
// NEW: Helper for time slot generation
export function generateTimeSlotId(): string {
return `timeslot_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}

View File

@@ -16,7 +16,7 @@ interface AuthContextType {
refreshUser: () => void; refreshUser: () => void;
needsSetup: boolean; needsSetup: boolean;
checkSetupStatus: () => Promise<void>; checkSetupStatus: () => Promise<void>;
updateUser: (userData: Employee) => void; // Add this line updateUser: (userData: Employee) => void;
} }
const AuthContext = createContext<AuthContextType | undefined>(undefined); const AuthContext = createContext<AuthContextType | undefined>(undefined);

View File

@@ -14,39 +14,39 @@ export const EMPLOYEE_DEFAULTS = {
export const MANAGER_DEFAULTS = { export const MANAGER_DEFAULTS = {
role: 'admin' as const, role: 'admin' as const,
employeeType: 'manager' as const, employeeType: 'manager' as const,
contractType: 'large' as const, // Not really used but required by DB contractType: 'large' as const,
canWorkAlone: true, canWorkAlone: true,
isActive: true isActive: true
}; };
export const EMPLOYEE_TYPE_CONFIG = [ export const EMPLOYEE_TYPE_CONFIG = {
{ manager: {
value: 'manager' as const, value: 'manager' as const,
label: 'Chef/Administrator', label: 'Chef/Administrator',
color: '#e74c3c', color: '#e74c3c',
independent: true, independent: true,
description: 'Vollzugriff auf alle Funktionen und Mitarbeiterverwaltung' description: 'Vollzugriff auf alle Funktionen und Mitarbeiterverwaltung'
}, },
{ experienced: {
value: 'experienced' as const, value: 'experienced' as const,
label: 'Erfahren', label: 'Erfahren',
color: '#3498db', color: '#3498db',
independent: true, independent: true,
description: 'Langjährige Erfahrung, kann komplexe Aufgaben übernehmen' description: 'Langjährige Erfahrung, kann komplexe Aufgaben übernehmen'
}, },
{ trainee: {
value: 'trainee' as const, value: 'trainee' as const,
label: 'Neuling', label: 'Neuling',
color: '#27ae60', color: '#27ae60',
independent: false, independent: false,
description: 'Benötigt Einarbeitung und Unterstützung' description: 'Benötigt Einarbeitung und Unterstützung'
} }
] as const; } as const;
export const ROLE_CONFIG = [ export const ROLE_CONFIG = [
{ value: 'user', label: 'Mitarbeiter', description: 'Kann eigene Schichten einsehen', color: '#27ae60' }, { value: 'user' as const, label: 'Mitarbeiter', description: 'Kann eigene Schichten einsehen', color: '#27ae60' },
{ value: 'maintenance', label: 'Instandhalter', description: 'Kann Schichtpläne erstellen und Mitarbeiter verwalten', color: '#3498db' }, { value: 'maintenance' as const, label: 'Instandhalter', description: 'Kann Schichtpläne erstellen und Mitarbeiter verwalten', color: '#3498db' },
{ value: 'admin', label: 'Administrator', description: 'Voller Zugriff auf alle Funktionen', color: '#e74c3c' } { value: 'admin' as const, label: 'Administrator', description: 'Voller Zugriff auf alle Funktionen', color: '#e74c3c' }
] as const; ] as const;
// Contract type descriptions // Contract type descriptions
@@ -64,26 +64,25 @@ export const AVAILABILITY_PREFERENCES = {
} as const; } as const;
// Default availability for new employees (all shifts unavailable as level 3) // Default availability for new employees (all shifts unavailable as level 3)
export function createDefaultAvailabilities(employeeId: string, planId: string, timeSlotIds: string[]): Omit<EmployeeAvailability, 'id'>[] { // UPDATED: Now uses shiftId instead of timeSlotId + dayOfWeek
export function createDefaultAvailabilities(employeeId: string, planId: string, shiftIds: string[]): Omit<EmployeeAvailability, 'id'>[] {
const availabilities: Omit<EmployeeAvailability, 'id'>[] = []; const availabilities: Omit<EmployeeAvailability, 'id'>[] = [];
// Monday to Friday (1-5) // Create one availability entry per shift
for (let day = 1; day <= 5; day++) { for (const shiftId of shiftIds) {
for (const timeSlotId of timeSlotIds) { availabilities.push({
availabilities.push({ employeeId,
employeeId, planId,
planId, shiftId,
dayOfWeek: day, preferenceLevel: 3 // Default to "unavailable" - employees must explicitly set availability
timeSlotId, });
preferenceLevel: 3 // Default to "unavailable" - employees must explicitly set availability
});
}
} }
return availabilities; return availabilities;
} }
// Create complete manager availability for all days (default: only Mon-Tue available) // Create complete manager availability for all days (default: only Mon-Tue available)
// NOTE: This function might need revision based on new schema requirements
export function createManagerDefaultSchedule(managerId: string, planId: string, timeSlotIds: string[]): Omit<ManagerAvailability, 'id'>[] { export function createManagerDefaultSchedule(managerId: string, planId: string, timeSlotIds: string[]): Omit<ManagerAvailability, 'id'>[] {
const assignments: Omit<ManagerAvailability, 'id'>[] = []; const assignments: Omit<ManagerAvailability, 'id'>[] = [];

View File

@@ -1,25 +1,51 @@
// backend/src/models/helpers/employeeHelpers.ts // backend/src/models/helpers/employeeHelpers.ts
import { Employee, CreateEmployeeRequest, EmployeeAvailability } from '../Employee.js'; import { Employee, CreateEmployeeRequest, EmployeeAvailability } from '../Employee.js';
// Simplified validation - use schema validation instead // Email generation function (same as in controllers)
function generateEmail(firstname: string, lastname: string): string {
// Convert German umlauts to their expanded forms
const convertUmlauts = (str: string): string => {
return str
.toLowerCase()
.replace(/ü/g, 'ue')
.replace(/ö/g, 'oe')
.replace(/ä/g, 'ae')
.replace(/ß/g, 'ss');
};
// Remove any remaining special characters and convert to lowercase
const cleanFirstname = convertUmlauts(firstname).replace(/[^a-z0-9]/g, '');
const cleanLastname = convertUmlauts(lastname).replace(/[^a-z0-9]/g, '');
return `${cleanFirstname}.${cleanLastname}@sp.de`;
}
// UPDATED: Validation for new employee model
export function validateEmployeeData(employee: CreateEmployeeRequest): string[] { export function validateEmployeeData(employee: CreateEmployeeRequest): string[] {
const errors: string[] = []; const errors: string[] = [];
if (!employee.email?.includes('@')) { // Email is now auto-generated, so no email validation needed
errors.push('Valid email is required');
}
if (employee.password?.length < 6) { if (employee.password?.length < 6) {
errors.push('Password must be at least 6 characters long'); errors.push('Password must be at least 6 characters long');
} }
if (!employee.name?.trim() || employee.name.trim().length < 2) { if (!employee.firstname?.trim() || employee.firstname.trim().length < 2) {
errors.push('Name is required and must be at least 2 characters long'); errors.push('First name is required and must be at least 2 characters long');
}
if (!employee.lastname?.trim() || employee.lastname.trim().length < 2) {
errors.push('Last name is required and must be at least 2 characters long');
} }
return errors; return errors;
} }
// Generate email for employee (new helper function)
export function generateEmployeeEmail(firstname: string, lastname: string): string {
return generateEmail(firstname, lastname);
}
// Simplified business logic helpers // Simplified business logic helpers
export const isManager = (employee: Employee): boolean => export const isManager = (employee: Employee): boolean =>
employee.employeeType === 'manager'; employee.employeeType === 'manager';
@@ -38,3 +64,26 @@ export const canEmployeeWorkAlone = (employee: Employee): boolean =>
export const getEmployeeWorkHours = (employee: Employee): number => export const getEmployeeWorkHours = (employee: Employee): number =>
isManager(employee) ? 999 : (employee.contractType === 'small' ? 1 : 2); isManager(employee) ? 999 : (employee.contractType === 'small' ? 1 : 2);
// New helper for full name display
export const getFullName = (employee: { firstname: string; lastname: string }): string =>
`${employee.firstname} ${employee.lastname}`;
// Helper for availability validation
export function validateAvailabilityData(availability: Omit<EmployeeAvailability, 'id' | 'employeeId'>): string[] {
const errors: string[] = [];
if (!availability.planId) {
errors.push('Plan ID is required');
}
if (!availability.shiftId) {
errors.push('Shift ID is required');
}
if (![1, 2, 3].includes(availability.preferenceLevel)) {
errors.push('Preference level must be 1, 2, or 3');
}
return errors;
}

View File

@@ -1,5 +1,4 @@
// backend/src/models/helpers/shiftPlanHelpers.ts // backend/src/models/helpers/shiftPlanHelpers.ts
import { shiftAssignmentService } from '../../services/shiftAssignmentService.js';
import { ShiftPlan, Shift, ScheduledShift, TimeSlot } from '../ShiftPlan.js'; import { ShiftPlan, Shift, ScheduledShift, TimeSlot } from '../ShiftPlan.js';
// Validation helpers // Validation helpers
@@ -79,16 +78,16 @@ export function calculateTotalRequiredEmployees(plan: ShiftPlan): number {
return plan.shifts.reduce((total, shift) => total + shift.requiredEmployees, 0); return plan.shifts.reduce((total, shift) => total + shift.requiredEmployees, 0);
} }
/*export async function getScheduledShiftByDateAndTime( // UPDATED: Get scheduled shift by date and time slot
export function getScheduledShiftByDateAndTime(
plan: ShiftPlan, plan: ShiftPlan,
date: string, date: string,
timeSlotId: string timeSlotId: string
): Promise<ScheduledShift | undefined> { ): ScheduledShift | undefined {
const scheduledShifts = await shiftAssignmentService.getScheduledShiftsForPlan(plan.id); return plan.scheduledShifts?.find(shift =>
return scheduledShifts.find( shift.date === date && shift.timeSlotId === timeSlotId
shift => shift.date === date && shift.timeSlotId === timeSlotId
); );
}*/ }
export function canPublishPlan(plan: ShiftPlan): { canPublish: boolean; errors: string[] } { export function canPublishPlan(plan: ShiftPlan): { canPublish: boolean; errors: string[] } {
const errors: string[] = []; const errors: string[] = [];
@@ -118,3 +117,13 @@ export function canPublishPlan(plan: ShiftPlan): { canPublish: boolean; errors:
errors errors
}; };
} }
// NEW: Helper for shift generation
export function generateShiftId(): string {
return `shift_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
// NEW: Helper for time slot generation
export function generateTimeSlotId(): string {
return `timeslot_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}

View File

@@ -14,9 +14,10 @@ const Settings: React.FC = () => {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [showAvailabilityManager, setShowAvailabilityManager] = useState(false); const [showAvailabilityManager, setShowAvailabilityManager] = useState(false);
// Profile form state // Profile form state - updated for firstname/lastname
const [profileForm, setProfileForm] = useState({ const [profileForm, setProfileForm] = useState({
name: currentUser?.name || '' firstname: currentUser?.firstname || '',
lastname: currentUser?.lastname || ''
}); });
// Password form state // Password form state
@@ -29,7 +30,8 @@ const Settings: React.FC = () => {
useEffect(() => { useEffect(() => {
if (currentUser) { if (currentUser) {
setProfileForm({ setProfileForm({
name: currentUser.name firstname: currentUser.firstname || '',
lastname: currentUser.lastname || ''
}); });
} }
}, [currentUser]); }, [currentUser]);
@@ -54,10 +56,21 @@ const Settings: React.FC = () => {
e.preventDefault(); e.preventDefault();
if (!currentUser) return; if (!currentUser) return;
// Validation
if (!profileForm.firstname.trim() || !profileForm.lastname.trim()) {
showNotification({
type: 'error',
title: 'Fehler',
message: 'Vorname und Nachname sind erforderlich'
});
return;
}
try { try {
setLoading(true); setLoading(true);
await employeeService.updateEmployee(currentUser.id, { await employeeService.updateEmployee(currentUser.id, {
name: profileForm.name.trim() firstname: profileForm.firstname.trim(),
lastname: profileForm.lastname.trim()
}); });
// Update the auth context with new user data // Update the auth context with new user data
@@ -167,8 +180,10 @@ const Settings: React.FC = () => {
); );
} }
// Style constants for consistency // Get full name for display
const getFullName = () => {
return `${currentUser.firstname || ''} ${currentUser.lastname || ''}`.trim();
};
return ( return (
<div style={styles.container}> <div style={styles.container}>
@@ -294,6 +309,9 @@ const Settings: React.FC = () => {
disabled disabled
style={styles.fieldInputDisabled} style={styles.fieldInputDisabled}
/> />
<div style={styles.fieldHint}>
E-Mail wird automatisch aus Vor- und Nachname generiert
</div>
</div> </div>
<div style={styles.field}> <div style={styles.field}>
<label style={styles.fieldLabel}> <label style={styles.fieldLabel}>
@@ -331,31 +349,61 @@ const Settings: React.FC = () => {
</div> </div>
</div> </div>
<div style={styles.infoCard}> <div style={styles.infoCard}>
{/* Editable name field */} <h4 style={styles.infoCardTitle}>Persönliche Informationen</h4>
<div style={{ ...styles.field, marginTop: '1rem' }}> {/* Editable name fields */}
<label style={styles.fieldLabel}> <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem' }}>
Vollständiger Name * <div style={styles.field}>
</label> <label style={styles.fieldLabel}>
<input Vorname *
type="text" </label>
name="name" <input
value={profileForm.name} type="text"
onChange={handleProfileChange} name="firstname"
required value={profileForm.firstname}
style={{ onChange={handleProfileChange}
...styles.fieldInput, required
width: '95%' style={styles.fieldInput}
}} placeholder="Ihr Vorname"
placeholder="Ihr vollständiger Name" onFocus={(e) => {
onFocus={(e) => { e.target.style.borderColor = '#1a1325';
e.target.style.borderColor = '#1a1325'; e.target.style.boxShadow = '0 0 0 3px rgba(26, 19, 37, 0.1)';
e.target.style.boxShadow = '0 0 0 3px rgba(26, 19, 37, 0.1)'; }}
}} onBlur={(e) => {
onBlur={(e) => { e.target.style.borderColor = '#e8e8e8';
e.target.style.borderColor = '#e8e8e8'; e.target.style.boxShadow = 'none';
e.target.style.boxShadow = 'none'; }}
}} />
/> </div>
<div style={styles.field}>
<label style={styles.fieldLabel}>
Nachname *
</label>
<input
type="text"
name="lastname"
value={profileForm.lastname}
onChange={handleProfileChange}
required
style={styles.fieldInput}
placeholder="Ihr Nachname"
onFocus={(e) => {
e.target.style.borderColor = '#1a1325';
e.target.style.boxShadow = '0 0 0 3px rgba(26, 19, 37, 0.1)';
}}
onBlur={(e) => {
e.target.style.borderColor = '#e8e8e8';
e.target.style.boxShadow = 'none';
}}
/>
</div>
</div>
<div style={{ marginTop: '1rem', padding: '1rem', background: '#f8f9fa', borderRadius: '8px' }}>
<div style={{ fontSize: '0.9rem', color: '#666' }}>
<strong>Vorschau:</strong> {getFullName() || '(Kein Name)'}
</div>
<div style={{ fontSize: '0.8rem', color: '#888', marginTop: '0.5rem' }}>
E-Mail: {currentUser.email}
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -363,21 +411,21 @@ const Settings: React.FC = () => {
<div style={styles.actions}> <div style={styles.actions}>
<button <button
type="submit" type="submit"
disabled={loading || !profileForm.name.trim()} disabled={loading || !profileForm.firstname.trim() || !profileForm.lastname.trim()}
style={{ style={{
...styles.button, ...styles.button,
...styles.buttonPrimary, ...styles.buttonPrimary,
...((loading || !profileForm.name.trim()) ? styles.buttonDisabled : {}) ...((loading || !profileForm.firstname.trim() || !profileForm.lastname.trim()) ? styles.buttonDisabled : {})
}} }}
onMouseEnter={(e) => { onMouseEnter={(e) => {
if (!loading && profileForm.name.trim()) { if (!loading && profileForm.firstname.trim() && profileForm.lastname.trim()) {
e.currentTarget.style.background = styles.buttonPrimaryHover.background; e.currentTarget.style.background = styles.buttonPrimaryHover.background;
e.currentTarget.style.transform = styles.buttonPrimaryHover.transform; e.currentTarget.style.transform = styles.buttonPrimaryHover.transform;
e.currentTarget.style.boxShadow = styles.buttonPrimaryHover.boxShadow; e.currentTarget.style.boxShadow = styles.buttonPrimaryHover.boxShadow;
} }
}} }}
onMouseLeave={(e) => { onMouseLeave={(e) => {
if (!loading && profileForm.name.trim()) { if (!loading && profileForm.firstname.trim() && profileForm.lastname.trim()) {
e.currentTarget.style.background = styles.buttonPrimary.background; e.currentTarget.style.background = styles.buttonPrimary.background;
e.currentTarget.style.transform = 'none'; e.currentTarget.style.transform = 'none';
e.currentTarget.style.boxShadow = styles.buttonPrimary.boxShadow; e.currentTarget.style.boxShadow = styles.buttonPrimary.boxShadow;

View File

@@ -550,7 +550,7 @@ const ShiftPlanView: React.FC = () => {
try { try {
return await employeeService.getAvailabilities(emp.id); return await employeeService.getAvailabilities(emp.id);
} catch (error) { } catch (error) {
console.error(`❌ Failed to load availabilities for ${emp.name}:`, error); console.error(`❌ Failed to load availabilities for ${emp.email}:`, error);
return []; // Return empty array instead of failing entire operation return []; // Return empty array instead of failing entire operation
} }
}); });
@@ -599,7 +599,7 @@ const ShiftPlanView: React.FC = () => {
); );
console.warn('⚠️ Missing availabilities for employees:', console.warn('⚠️ Missing availabilities for employees:',
missingEmployees.map(emp => emp.name)); missingEmployees.map(emp => emp.email));
return false; return false;
} }
@@ -806,7 +806,7 @@ const ShiftPlanView: React.FC = () => {
displayText = assignedEmployees.map(empId => { displayText = assignedEmployees.map(empId => {
const employee = employees.find(emp => emp.id === empId); const employee = employees.find(emp => emp.id === empId);
return employee ? employee.name : 'Unbekannt'; return employee ? employee.email : 'Unbekannt';
}).join(', '); }).join(', ');
} }
} else if (assignmentResult) { } else if (assignmentResult) {
@@ -821,7 +821,7 @@ const ShiftPlanView: React.FC = () => {
assignedEmployees = getAssignmentsForScheduledShift(scheduledShift); assignedEmployees = getAssignmentsForScheduledShift(scheduledShift);
displayText = assignedEmployees.map(empId => { displayText = assignedEmployees.map(empId => {
const employee = employees.find(emp => emp.id === empId); const employee = employees.find(emp => emp.id === empId);
return employee ? employee.name : 'Unbekannt'; return employee ? employee.email : 'Unbekannt';
}).join(', '); }).join(', ');
} }
} }