From dec92daf7cbc8c6db5946f1547da0afa08cbc617 Mon Sep 17 00:00:00 2001 From: donpat1to Date: Mon, 13 Oct 2025 11:57:27 +0200 Subject: [PATCH] added changing password frontend backend --- backend/src/controllers/employeeController.ts | 32 ++ .../src/controllers/shiftPlanController.ts | 4 +- backend/src/routes/employees.ts | 4 +- backend/src/routes/shiftPlans.ts | 4 +- frontend/src/contexts/AuthContext.tsx | 16 +- frontend/src/pages/Settings/Settings.tsx | 543 +++++++++++++++++- frontend/src/services/employeeService.ts | 13 + frontend/src/services/shiftPlanService.ts | 4 +- 8 files changed, 595 insertions(+), 25 deletions(-) diff --git a/backend/src/controllers/employeeController.ts b/backend/src/controllers/employeeController.ts index cb2d9bd..c29a736 100644 --- a/backend/src/controllers/employeeController.ts +++ b/backend/src/controllers/employeeController.ts @@ -365,4 +365,36 @@ export const updateAvailabilities = async (req: AuthRequest, res: Response): Pro console.error('Error updating availabilities:', error); res.status(500).json({ error: 'Internal server error' }); } +}; + +export const changePassword = async (req: AuthRequest, res: Response): Promise => { + try { + const { id } = req.params; + const { currentPassword, newPassword } = req.body; + + // Check if employee exists and get password + const employee = await db.get<{ password: string }>('SELECT password FROM employees WHERE id = ?', [id]); + if (!employee) { + res.status(404).json({ error: 'Employee not found' }); + return; + } + + // Verify current password + const isValidPassword = await bcrypt.compare(currentPassword, employee.password); + if (!isValidPassword) { + res.status(400).json({ error: 'Current password is incorrect' }); + return; + } + + // Hash new password + const hashedPassword = await bcrypt.hash(newPassword, 10); + + // Update password + await db.run('UPDATE employees SET password = ? WHERE id = ?', [hashedPassword, id]); + + res.json({ message: 'Password updated successfully' }); + } catch (error) { + console.error('Error changing password:', error); + res.status(500).json({ error: 'Internal server error' }); + } }; \ No newline at end of file diff --git a/backend/src/controllers/shiftPlanController.ts b/backend/src/controllers/shiftPlanController.ts index ac081cc..2315433 100644 --- a/backend/src/controllers/shiftPlanController.ts +++ b/backend/src/controllers/shiftPlanController.ts @@ -710,7 +710,7 @@ export const getTemplates = async (req: Request, res: Response): Promise = }; // Neue Funktion: Create from Template -export const createFromTemplate = async (req: Request, res: Response): Promise => { +/*export const createFromTemplate = async (req: Request, res: Response): Promise => { try { const { templatePlanId, name, startDate, endDate, description } = req.body; const userId = (req as AuthRequest).user?.userId; @@ -800,4 +800,4 @@ export const createFromTemplate = async (req: Request, res: Response): Promise void; needsSetup: boolean; checkSetupStatus: () => Promise; + updateUser: (userData: Employee) => void; // Add this line } const AuthContext = createContext(undefined); @@ -26,7 +28,7 @@ interface AuthProviderProps { export const AuthProvider: React.FC = ({ children }) => { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); - const [needsSetup, setNeedsSetup] = useState(null); // ← Start mit null + const [needsSetup, setNeedsSetup] = useState(null); // Token aus localStorage laden const getStoredToken = (): string | null => { @@ -55,7 +57,7 @@ export const AuthProvider: React.FC = ({ children }) => { setNeedsSetup(data.needsSetup === true); } catch (error) { console.error('❌ Error checking setup status:', error); - setNeedsSetup(true); // Bei Fehler Setup annehmen + setNeedsSetup(true); } }; @@ -92,6 +94,12 @@ export const AuthProvider: React.FC = ({ children }) => { } }; + // Add the updateUser function + const updateUser = (userData: Employee) => { + console.log('🔄 Updating user in auth context:', userData); + setUser(userData); + }; + const login = async (credentials: LoginRequest): Promise => { try { console.log('🔐 Attempting login for:', credentials.email); @@ -112,7 +120,6 @@ export const AuthProvider: React.FC = ({ children }) => { const data = await response.json(); console.log('✅ Login successful, storing token'); - // Token persistent speichern setStoredToken(data.token); setUser(data.user); } catch (error) { @@ -156,8 +163,9 @@ export const AuthProvider: React.FC = ({ children }) => { hasRole, loading, refreshUser, - needsSetup: needsSetup === null ? true : needsSetup, // Falls null, true annehmen + needsSetup: needsSetup === null ? true : needsSetup, checkSetupStatus, + updateUser, // Add this to the context value }; return ( diff --git a/frontend/src/pages/Settings/Settings.tsx b/frontend/src/pages/Settings/Settings.tsx index a68403e..d37bd07 100644 --- a/frontend/src/pages/Settings/Settings.tsx +++ b/frontend/src/pages/Settings/Settings.tsx @@ -1,26 +1,541 @@ // frontend/src/pages/Settings/Settings.tsx -import React from 'react'; +import React, { useState, useEffect } from 'react'; +import { useAuth } from '../../contexts/AuthContext'; +import { employeeService } from '../../services/employeeService'; +import { useNotification } from '../../contexts/NotificationContext'; +import AvailabilityManager from '../Employees/components/AvailabilityManager'; +import { Employee } from '../../models/Employee'; const Settings: React.FC = () => { + const { user: currentUser, updateUser } = useAuth(); + const { showNotification } = useNotification(); + const [activeTab, setActiveTab] = useState<'profile' | 'password' | 'availability'>('profile'); + const [loading, setLoading] = useState(false); + const [showAvailabilityManager, setShowAvailabilityManager] = useState(false); + + // Profile form state + const [profileForm, setProfileForm] = useState({ + name: currentUser?.name || '' + }); + + // Password form state + const [passwordForm, setPasswordForm] = useState({ + currentPassword: '', + newPassword: '', + confirmPassword: '' + }); + + useEffect(() => { + if (currentUser) { + setProfileForm({ + name: currentUser.name + }); + } + }, [currentUser]); + + const handleProfileChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + setProfileForm(prev => ({ + ...prev, + [name]: value + })); + }; + + const handlePasswordChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + setPasswordForm(prev => ({ + ...prev, + [name]: value + })); + }; + + const handleProfileUpdate = async (e: React.FormEvent) => { + e.preventDefault(); + if (!currentUser) return; + + try { + setLoading(true); + await employeeService.updateEmployee(currentUser.id, { + name: profileForm.name.trim() + }); + + // Update the auth context with new user data + const updatedUser = await employeeService.getEmployee(currentUser.id); + updateUser(updatedUser); + + showNotification({ + type: 'success', + title: 'Erfolg', + message: 'Profil erfolgreich aktualisiert' + }); + } catch (error: any) { + showNotification({ + type: 'error', + title: 'Fehler', + message: error.message || 'Profil konnte nicht aktualisiert werden' + }); + } finally { + setLoading(false); + } + }; + + const handlePasswordUpdate = async (e: React.FormEvent) => { + e.preventDefault(); + if (!currentUser) return; + + // Validation + if (passwordForm.newPassword.length < 6) { + showNotification({ + type: 'error', + title: 'Fehler', + message: 'Das neue Passwort muss mindestens 6 Zeichen lang sein' + }); + return; + } + + if (passwordForm.newPassword !== passwordForm.confirmPassword) { + showNotification({ + type: 'error', + title: 'Fehler', + message: 'Die Passwörter stimmen nicht überein' + }); + return; + } + + try { + setLoading(true); + + // Use the actual password change endpoint + await employeeService.changePassword(currentUser.id, { + currentPassword: passwordForm.currentPassword, + newPassword: passwordForm.newPassword + }); + + showNotification({ + type: 'success', + title: 'Erfolg', + message: 'Passwort erfolgreich geändert' + }); + + // Clear password form + setPasswordForm({ + currentPassword: '', + newPassword: '', + confirmPassword: '' + }); + } catch (error: any) { + showNotification({ + type: 'error', + title: 'Fehler', + message: error.message || 'Passwort konnte nicht geändert werden' + }); + } finally { + setLoading(false); + } + }; + + const handleAvailabilitySave = () => { + setShowAvailabilityManager(false); + showNotification({ + type: 'success', + title: 'Erfolg', + message: 'Verfügbarkeit erfolgreich gespeichert' + }); + }; + + const handleAvailabilityCancel = () => { + setShowAvailabilityManager(false); + }; + + if (!currentUser) { + return
Nicht eingeloggt
; + } + + if (showAvailabilityManager) { + return ( + + ); + } + return ( -
+

⚙️ Einstellungen

+ {/* Tab Navigation */}
-
⚙️
-

System Einstellungen

-

Hier können Sie Systemweite Einstellungen vornehmen.

-

- Diese Seite wird demnächst mit Funktionen gefüllt. -

+ + +
+ + {/* Profile Tab */} + {activeTab === 'profile' && ( +
+

Profilinformationen

+ +
+
+ {/* Read-only information */} +
+

Systeminformationen

+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + {/* Editable name */} +
+ + +
+
+ +
+ +
+
+
+ )} + + {/* Password Tab */} + {activeTab === 'password' && ( +
+

Passwort ändern

+ +
+
+
+ + +
+ +
+ + +
+ Das Passwort muss mindestens 6 Zeichen lang sein. +
+
+ +
+ + +
+
+ +
+ +
+
+
+ )} + + {/* Availability Tab */} + {activeTab === 'availability' && ( +
+

Meine Verfügbarkeit

+ +
+
📅
+

Verfügbarkeit verwalten

+

+ Hier können Sie Ihre persönliche Verfügbarkeit für Schichtpläne festlegen. + Legen Sie für jeden Tag und jede Schicht fest, ob Sie bevorzugt, möglicherweise + oder nicht verfügbar sind. +

+ + + +
+ 💡 Informationen: +
    +
  • Bevorzugt: Sie möchten diese Schicht arbeiten
  • +
  • Möglich: Sie können diese Schicht arbeiten
  • +
  • Nicht möglich: Sie können diese Schicht nicht arbeiten
  • +
+
+
+
+ )}
); }; diff --git a/frontend/src/services/employeeService.ts b/frontend/src/services/employeeService.ts index 2f6903e..ce9ac38 100644 --- a/frontend/src/services/employeeService.ts +++ b/frontend/src/services/employeeService.ts @@ -117,6 +117,19 @@ export class EmployeeService { return response.json(); } + + async changePassword(id: string, data: { currentPassword: string, newPassword: string }): Promise { + const response = await fetch(`${API_BASE_URL}/employees/${id}/password`, { + method: 'PUT', + headers: getAuthHeaders(), + body: JSON.stringify(data), + }); + + if (!response.ok) { + const error = await response.json(); + throw new Error(error.error || 'Failed to change password'); + } + } } export const employeeService = new EmployeeService(); \ No newline at end of file diff --git a/frontend/src/services/shiftPlanService.ts b/frontend/src/services/shiftPlanService.ts index e423ff2..16818d0 100644 --- a/frontend/src/services/shiftPlanService.ts +++ b/frontend/src/services/shiftPlanService.ts @@ -138,14 +138,14 @@ export const shiftPlanService = { }, // Create plan from template - createFromTemplate: async (data: CreateShiftFromTemplateRequest): Promise => { + /*createFromTemplate: async (data: CreateShiftFromTemplateRequest): Promise => { const response = await fetch(`${API_BASE}/from-template`, { method: 'POST', headers: getAuthHeaders(), body: JSON.stringify(data), }); return handleResponse(response); - }, + },*/ // Create new plan createPlan: async (data: CreateShiftPlanRequest): Promise => {