added hashed passwords

This commit is contained in:
2025-10-08 18:11:04 +02:00
parent a6ec865571
commit 96a36d68a9
7 changed files with 469 additions and 158 deletions

View File

@@ -1,11 +1,10 @@
// frontend/src/contexts/AuthContext.tsx
import React, { createContext, useContext, useState, useEffect } from 'react';
import { authService, User, LoginRequest, RegisterRequest } from '../services/authService';
import { authService, User, LoginRequest } from '../services/authService';
interface AuthContextType {
user: User | null;
login: (credentials: LoginRequest) => Promise<void>;
register: (userData: RegisterRequest) => Promise<void>;
logout: () => void;
hasRole: (roles: string[]) => boolean;
loading: boolean;
@@ -19,43 +18,26 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
useEffect(() => {
// User aus localStorage laden beim Start
const initAuth = async () => {
const savedUser = authService.getCurrentUser();
if (savedUser) {
setUser(savedUser);
}
setLoading(false);
};
initAuth();
const savedUser = authService.getCurrentUser();
if (savedUser) {
setUser(savedUser);
}
setLoading(false);
}, []);
const login = async (credentials: LoginRequest) => {
try {
const response = await authService.login(credentials);
setUser(response.user); // ← WICHTIG: User State updaten!
console.log('AuthContext: User nach Login gesetzt', response.user);
setUser(response.user);
} catch (error) {
console.error('AuthContext: Login fehlgeschlagen', error);
throw error;
}
};
const register = async (userData: RegisterRequest) => {
try {
const response = await authService.register(userData);
setUser(response.user);
console.log('AuthContext: User nach Registrierung gesetzt', response.user);
} catch (error) {
console.error('AuthContext: Registrierung fehlgeschlagen', error);
throw error;
}
};
const logout = () => {
authService.logout();
setUser(null);
console.log('AuthContext: User nach Logout entfernt');
};
const hasRole = (roles: string[]) => {
@@ -65,7 +47,6 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
const value = {
user,
login,
register,
logout,
hasRole,
loading

View File

@@ -24,9 +24,13 @@ const EmployeeManagement: React.FC = () => {
const loadEmployees = async () => {
try {
setLoading(true);
setError('');
console.log('🔄 Loading employees...');
const data = await employeeService.getEmployees();
console.log('✅ Employees loaded:', data);
setEmployees(data);
} catch (err: any) {
console.error('❌ Error loading employees:', err);
setError(err.message || 'Fehler beim Laden der Mitarbeiter');
} finally {
setLoading(false);
@@ -51,15 +55,19 @@ const EmployeeManagement: React.FC = () => {
const handleBackToList = () => {
setViewMode('list');
setSelectedEmployee(null);
loadEmployees(); // Daten aktualisieren
};
// KORRIGIERT: Explizit Daten neu laden nach Create/Update
const handleEmployeeCreated = () => {
handleBackToList();
console.log('🔄 Reloading employees after creation...');
loadEmployees(); // Daten neu laden
setViewMode('list'); // Zurück zur Liste
};
const handleEmployeeUpdated = () => {
handleBackToList();
console.log('🔄 Reloading employees after update...');
loadEmployees(); // Daten neu laden
setViewMode('list'); // Zurück zur Liste
};
const handleDeleteEmployee = async (employeeId: string) => {
@@ -75,6 +83,13 @@ const EmployeeManagement: React.FC = () => {
}
};
// Debug: Zeige aktuellen State
console.log('📊 Current state:', {
viewMode,
employeesCount: employees.length,
selectedEmployee: selectedEmployee?.name
});
if (loading && viewMode === 'list') {
return (
<div style={{ textAlign: 'center', padding: '40px' }}>
@@ -97,7 +112,7 @@ const EmployeeManagement: React.FC = () => {
<div>
<h1 style={{ margin: 0, color: '#2c3e50' }}>👥 Mitarbeiter Verwaltung</h1>
<p style={{ margin: '5px 0 0 0', color: '#7f8c8d' }}>
Verwalten Sie Mitarbeiterkonten und Verfügbarkeiten
{employees.length} Mitarbeiter gefunden
</p>
</div>
@@ -197,7 +212,7 @@ const EmployeeManagement: React.FC = () => {
{viewMode === 'availability' && selectedEmployee && (
<AvailabilityManager
employee={selectedEmployee}
onSave={handleBackToList}
onSave={handleEmployeeUpdated}
onCancel={handleBackToList}
/>
)}

View File

@@ -152,13 +152,13 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
overflow: 'hidden',
marginBottom: '30px'
}}>
{daysOfWeek.map(day => {
{daysOfWeek.map((day, dayIndex) => {
const dayAvailabilities = getAvailabilitiesForDay(day.id);
const isLastDay = dayIndex === daysOfWeek.length - 1;
return (
<div key={day.id} style={{
borderBottom: '1px solid #f0f0f0',
':last-child': { borderBottom: 'none' }
borderBottom: isLastDay ? 'none' : '1px solid #f0f0f0'
}}>
{/* Tag Header */}
<div style={{
@@ -173,96 +173,99 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
{/* Zeit-Slots */}
<div style={{ padding: '15px 20px' }}>
{dayAvailabilities.map(availability => (
<div
key={availability.id}
style={{
display: 'grid',
gridTemplateColumns: '1fr auto auto auto',
gap: '15px',
alignItems: 'center',
padding: '10px 0',
borderBottom: '1px solid #f8f9fa',
':last-child': { borderBottom: 'none' }
}}
>
{/* Verfügbarkeit Toggle */}
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<input
type="checkbox"
id={`avail-${availability.id}`}
checked={availability.isAvailable}
onChange={(e) => handleAvailabilityChange(availability.id, e.target.checked)}
style={{ width: '18px', height: '18px' }}
/>
<label
htmlFor={`avail-${availability.id}`}
style={{
fontWeight: 'bold',
color: availability.isAvailable ? '#27ae60' : '#95a5a6'
}}
>
{availability.isAvailable ? 'Verfügbar' : 'Nicht verfügbar'}
</label>
</div>
{dayAvailabilities.map((availability, availabilityIndex) => {
const isLastAvailability = availabilityIndex === dayAvailabilities.length - 1;
return (
<div
key={availability.id}
style={{
display: 'grid',
gridTemplateColumns: '1fr auto auto auto',
gap: '15px',
alignItems: 'center',
padding: '10px 0',
borderBottom: isLastAvailability ? 'none' : '1px solid #f8f9fa'
}}
>
{/* Verfügbarkeit Toggle */}
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<input
type="checkbox"
id={`avail-${availability.id}`}
checked={availability.isAvailable}
onChange={(e) => handleAvailabilityChange(availability.id, e.target.checked)}
style={{ width: '18px', height: '18px' }}
/>
<label
htmlFor={`avail-${availability.id}`}
style={{
fontWeight: 'bold',
color: availability.isAvailable ? '#27ae60' : '#95a5a6'
}}
>
{availability.isAvailable ? 'Verfügbar' : 'Nicht verfügbar'}
</label>
</div>
{/* Startzeit */}
<div>
<label style={{ fontSize: '12px', color: '#7f8c8d', display: 'block', marginBottom: '4px' }}>
Von
</label>
<input
type="time"
value={availability.startTime}
onChange={(e) => handleTimeChange(availability.id, 'startTime', e.target.value)}
disabled={!availability.isAvailable}
style={{
padding: '6px 8px',
border: `1px solid ${availability.isAvailable ? '#ddd' : '#f0f0f0'}`,
borderRadius: '4px',
backgroundColor: availability.isAvailable ? 'white' : '#f8f9fa',
color: availability.isAvailable ? '#333' : '#999'
}}
/>
</div>
{/* Startzeit */}
<div>
<label style={{ fontSize: '12px', color: '#7f8c8d', display: 'block', marginBottom: '4px' }}>
Von
</label>
<input
type="time"
value={availability.startTime}
onChange={(e) => handleTimeChange(availability.id, 'startTime', e.target.value)}
disabled={!availability.isAvailable}
style={{
padding: '6px 8px',
border: `1px solid ${availability.isAvailable ? '#ddd' : '#f0f0f0'}`,
borderRadius: '4px',
backgroundColor: availability.isAvailable ? 'white' : '#f8f9fa',
color: availability.isAvailable ? '#333' : '#999'
}}
/>
</div>
{/* Endzeit */}
<div>
<label style={{ fontSize: '12px', color: '#7f8c8d', display: 'block', marginBottom: '4px' }}>
Bis
</label>
<input
type="time"
value={availability.endTime}
onChange={(e) => handleTimeChange(availability.id, 'endTime', e.target.value)}
disabled={!availability.isAvailable}
style={{
padding: '6px 8px',
border: `1px solid ${availability.isAvailable ? '#ddd' : '#f0f0f0'}`,
borderRadius: '4px',
backgroundColor: availability.isAvailable ? 'white' : '#f8f9fa',
color: availability.isavailable ? '#333' : '#999'
}}
/>
</div>
{/* Endzeit */}
<div>
<label style={{ fontSize: '12px', color: '#7f8c8d', display: 'block', marginBottom: '4px' }}>
Bis
</label>
<input
type="time"
value={availability.endTime}
onChange={(e) => handleTimeChange(availability.id, 'endTime', e.target.value)}
disabled={!availability.isAvailable}
style={{
padding: '6px 8px',
border: `1px solid ${availability.isAvailable ? '#ddd' : '#f0f0f0'}`,
borderRadius: '4px',
backgroundColor: availability.isAvailable ? 'white' : '#f8f9fa',
color: availability.isAvailable ? '#333' : '#999'
}}
/>
</div>
{/* Status Badge */}
<div>
<span
style={{
backgroundColor: availability.isAvailable ? '#d5f4e6' : '#fadbd8',
color: availability.isAvailable ? '#27ae60' : '#e74c3c',
padding: '4px 8px',
borderRadius: '12px',
fontSize: '12px',
fontWeight: 'bold'
}}
>
{availability.isAvailable ? 'Aktiv' : 'Inaktiv'}
</span>
{/* Status Badge */}
<div>
<span
style={{
backgroundColor: availability.isAvailable ? '#d5f4e6' : '#fadbd8',
color: availability.isAvailable ? '#27ae60' : '#e74c3c',
padding: '4px 8px',
borderRadius: '12px',
fontSize: '12px',
fontWeight: 'bold'
}}
>
{availability.isAvailable ? 'Aktiv' : 'Inaktiv'}
</span>
</div>
</div>
</div>
))}
);
})}
</div>
</div>
);

View File

@@ -11,6 +11,8 @@ export interface RegisterRequest {
password: string;
name: string;
role?: string;
phone?: string;
department?: string;
}
export interface AuthResponse {
@@ -25,6 +27,10 @@ export interface User {
name: string;
role: 'admin' | 'instandhalter' | 'user';
createdAt: string;
lastLogin?: string;
phone?: string;
department?: string;
isActive?: boolean;
}
class AuthService {
@@ -38,7 +44,8 @@ class AuthService {
});
if (!response.ok) {
throw new Error('Login fehlgeschlagen');
const errorData = await response.json();
throw new Error(errorData.error || 'Login fehlgeschlagen');
}
const data: AuthResponse = await response.json();
@@ -49,23 +56,56 @@ class AuthService {
return data;
}
// Register Methode hinzufügen
async register(userData: RegisterRequest): Promise<AuthResponse> {
const response = await fetch(`${API_BASE}/auth/register`, {
const response = await fetch(`${API_BASE}/employees`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(userData)
});
if (!response.ok) {
throw new Error('Registrierung fehlgeschlagen');
const errorData = await response.json();
throw new Error(errorData.error || 'Registrierung fehlgeschlagen');
}
const data: AuthResponse = await response.json();
this.token = data.token;
localStorage.setItem('token', data.token);
localStorage.setItem('user', JSON.stringify(data.user));
return data;
// Nach der Erstellung automatisch einloggen
return this.login({
email: userData.email,
password: userData.password
});
}
// getCurrentUser als SYNCHRON machen
getCurrentUser(): User | null {
const userStr = localStorage.getItem('user');
return userStr ? JSON.parse(userStr) : null;
}
// Asynchrone Methode für Server-Abfrage
async fetchCurrentUser(): Promise<User | null> {
const token = this.getToken();
if (!token) {
return null;
}
try {
const response = await fetch(`${API_BASE}/auth/me`, {
headers: {
'Authorization': `Bearer ${token}`
}
});
if (response.ok) {
const user = await response.json();
localStorage.setItem('user', JSON.stringify(user));
return user;
}
} catch (error) {
console.error('Error fetching current user:', error);
}
return null;
}
logout(): void {
@@ -81,11 +121,6 @@ class AuthService {
return this.token;
}
getCurrentUser(): User | null {
const userStr = localStorage.getItem('user');
return userStr ? JSON.parse(userStr) : null;
}
isAuthenticated(): boolean {
return this.getToken() !== null;
}

View File

@@ -6,7 +6,7 @@ export interface Employee {
role: 'admin' | 'instandhalter' | 'user';
isActive: boolean;
createdAt: string;
lastLogin?: string;
lastLogin?: string | null;
phone?: string;
department?: string;
}