mirror of
https://github.com/donpat1to/Schichtenplaner.git
synced 2025-11-30 22:45:46 +01:00
added hashed passwords
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ export interface Employee {
|
||||
role: 'admin' | 'instandhalter' | 'user';
|
||||
isActive: boolean;
|
||||
createdAt: string;
|
||||
lastLogin?: string;
|
||||
lastLogin?: string | null;
|
||||
phone?: string;
|
||||
department?: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user