mirror of
https://github.com/donpat1to/Schichtenplaner.git
synced 2025-11-30 22:45:46 +01:00
fixed role handling for employees
This commit is contained in:
@@ -24,7 +24,7 @@ export interface LoginRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface JWTPayload {
|
export interface JWTPayload {
|
||||||
id: string; // ← VON number ZU string ÄNDERN
|
id: string;
|
||||||
email: string;
|
email: string;
|
||||||
role: string;
|
role: string;
|
||||||
iat?: number;
|
iat?: number;
|
||||||
@@ -32,16 +32,26 @@ export interface JWTPayload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface RegisterRequest {
|
export interface RegisterRequest {
|
||||||
// REMOVED: email - will be auto-generated
|
|
||||||
password: string;
|
password: string;
|
||||||
firstname: string;
|
firstname: string;
|
||||||
lastname: string;
|
lastname: string;
|
||||||
role?: string;
|
roles?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateEmail(firstname: string, lastname: string): string {
|
function generateEmail(firstname: string, lastname: string): string {
|
||||||
const cleanFirstname = firstname.toLowerCase().replace(/[^a-z0-9]/g, '');
|
// Convert German umlauts to their expanded forms
|
||||||
const cleanLastname = lastname.toLowerCase().replace(/[^a-z0-9]/g, '');
|
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`;
|
return `${cleanFirstname}.${cleanLastname}@sp.de`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,9 +66,17 @@ export const login = async (req: Request, res: Response) => {
|
|||||||
return res.status(400).json({ error: 'E-Mail und Passwort sind erforderlich' });
|
return res.status(400).json({ error: 'E-Mail und Passwort sind erforderlich' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get user from database
|
// UPDATED: Get user from database with role from employee_roles table
|
||||||
const user = await db.get<EmployeeWithPassword>(
|
const user = await db.get<any>(
|
||||||
'SELECT id, email, password, firstname, lastname, role, employee_type as employeeType, contract_type as contractType, can_work_alone as canWorkAlone, is_active as isActive FROM employees WHERE email = ? AND is_active = 1',
|
`SELECT
|
||||||
|
e.id, e.email, e.password, e.firstname, e.lastname,
|
||||||
|
e.employee_type as employeeType, e.contract_type as contractType,
|
||||||
|
e.can_work_alone as canWorkAlone, e.is_active as isActive,
|
||||||
|
er.role
|
||||||
|
FROM employees e
|
||||||
|
LEFT JOIN employee_roles er ON e.id = er.employee_id
|
||||||
|
WHERE e.email = ? AND e.is_active = 1
|
||||||
|
LIMIT 1`,
|
||||||
[email]
|
[email]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -78,11 +96,11 @@ export const login = async (req: Request, res: Response) => {
|
|||||||
return res.status(401).json({ error: 'Ungültige Anmeldedaten' });
|
return res.status(401).json({ error: 'Ungültige Anmeldedaten' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create token payload - KORREKT: id field verwenden
|
// Create token payload
|
||||||
const tokenPayload = {
|
const tokenPayload = {
|
||||||
id: user.id.toString(), // ← WICHTIG: Dies wird als 'id' im JWT gespeichert
|
id: user.id.toString(),
|
||||||
email: user.email,
|
email: user.email,
|
||||||
role: user.role
|
role: user.role || 'user' // Fallback to 'user' if no role found
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('🎫 Creating JWT with payload:', tokenPayload);
|
console.log('🎫 Creating JWT with payload:', tokenPayload);
|
||||||
@@ -94,13 +112,17 @@ export const login = async (req: Request, res: Response) => {
|
|||||||
{ expiresIn: '24h' }
|
{ expiresIn: '24h' }
|
||||||
);
|
);
|
||||||
|
|
||||||
// Remove password from user object
|
// Remove password from user object and format response
|
||||||
const { password: _, ...userWithoutPassword } = user;
|
const { password: _, ...userWithoutPassword } = user;
|
||||||
|
const userResponse = {
|
||||||
|
...userWithoutPassword,
|
||||||
|
roles: user.role ? [user.role] : ['user'] // Convert single role to array for frontend compatibility
|
||||||
|
};
|
||||||
|
|
||||||
console.log('✅ Login successful for:', user.email);
|
console.log('✅ Login successful for:', user.email);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
user: userWithoutPassword,
|
user: userResponse,
|
||||||
token
|
token
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -121,8 +143,17 @@ export const getCurrentUser = async (req: Request, res: Response) => {
|
|||||||
return res.status(401).json({ error: 'Nicht authentifiziert' });
|
return res.status(401).json({ error: 'Nicht authentifiziert' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await db.get<Employee>(
|
// UPDATED: Get user with role from employee_roles table
|
||||||
'SELECT id, email, firstname, lastname, role, employee_type as employeeType, contract_type as contractType, can_work_alone as canWorkAlone, is_active as isActive FROM employees WHERE id = ? AND is_active = 1',
|
const user = await db.get<any>(
|
||||||
|
`SELECT
|
||||||
|
e.id, e.email, e.firstname, e.lastname,
|
||||||
|
e.employee_type as employeeType, e.contract_type as contractType,
|
||||||
|
e.can_work_alone as canWorkAlone, e.is_active as isActive,
|
||||||
|
er.role
|
||||||
|
FROM employees e
|
||||||
|
LEFT JOIN employee_roles er ON e.id = er.employee_id
|
||||||
|
WHERE e.id = ? AND e.is_active = 1
|
||||||
|
LIMIT 1`,
|
||||||
[jwtUser.userId]
|
[jwtUser.userId]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -133,8 +164,14 @@ export const getCurrentUser = async (req: Request, res: Response) => {
|
|||||||
return res.status(404).json({ error: 'Benutzer nicht gefunden' });
|
return res.status(404).json({ error: 'Benutzer nicht gefunden' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Format user response with roles array
|
||||||
|
const userResponse = {
|
||||||
|
...user,
|
||||||
|
roles: user.role ? [user.role] : ['user']
|
||||||
|
};
|
||||||
|
|
||||||
console.log('✅ Returning user:', user.email);
|
console.log('✅ Returning user:', user.email);
|
||||||
res.json({ user });
|
res.json({ user: userResponse });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Get current user error:', error);
|
console.error('Get current user error:', error);
|
||||||
res.status(500).json({ error: 'Ein Fehler ist beim Abrufen des Benutzers aufgetreten' });
|
res.status(500).json({ error: 'Ein Fehler ist beim Abrufen des Benutzers aufgetreten' });
|
||||||
@@ -168,9 +205,9 @@ export const validateToken = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
export const register = async (req: Request, res: Response) => {
|
export const register = async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const { password, firstname, lastname, role = 'user' } = req.body as RegisterRequest;
|
const { password, firstname, lastname, roles = ['user'] } = req.body as RegisterRequest;
|
||||||
|
|
||||||
// Validate required fields - REMOVED email
|
// Validate required fields
|
||||||
if (!password || !firstname || !lastname) {
|
if (!password || !firstname || !lastname) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
error: 'Password, firstname und lastname sind erforderlich'
|
error: 'Password, firstname und lastname sind erforderlich'
|
||||||
@@ -192,27 +229,60 @@ export const register = async (req: Request, res: Response) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash password and create user
|
// Hash password
|
||||||
const hashedPassword = await bcrypt.hash(password, 10);
|
const hashedPassword = await bcrypt.hash(password, 10);
|
||||||
|
const employeeId = uuidv4();
|
||||||
|
|
||||||
// Insert user with generated email
|
// Start transaction for registration
|
||||||
|
await db.run('BEGIN TRANSACTION');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Insert user without role (role is now in employee_roles table)
|
||||||
const result = await db.run(
|
const result = await db.run(
|
||||||
`INSERT INTO employees (id, email, password, firstname, lastname, role, employee_type, contract_type, can_work_alone, is_active)
|
`INSERT INTO employees (id, email, password, firstname, lastname, employee_type, contract_type, can_work_alone, is_active)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
[uuidv4(), email, hashedPassword, firstname, lastname, role, 'experienced', 'small', false, 1]
|
[employeeId, email, hashedPassword, firstname, lastname, 'experienced', 'small', false, 1]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!result.lastID) {
|
if (!result.lastID) {
|
||||||
throw new Error('Benutzer konnte nicht erstellt werden');
|
throw new Error('Benutzer konnte nicht erstellt werden');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get created user
|
// UPDATED: Insert roles into employee_roles table
|
||||||
const newUser = await db.get<Employee>(
|
for (const role of roles) {
|
||||||
'SELECT id, email, firstname, lastname, role FROM employees WHERE id = ?',
|
await db.run(
|
||||||
[result.lastID]
|
`INSERT INTO employee_roles (employee_id, role) VALUES (?, ?)`,
|
||||||
|
[employeeId, role]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.run('COMMIT');
|
||||||
|
|
||||||
|
// Get created user with role
|
||||||
|
const newUser = await db.get<any>(
|
||||||
|
`SELECT
|
||||||
|
e.id, e.email, e.firstname, e.lastname,
|
||||||
|
er.role
|
||||||
|
FROM employees e
|
||||||
|
LEFT JOIN employee_roles er ON e.id = er.employee_id
|
||||||
|
WHERE e.id = ?
|
||||||
|
LIMIT 1`,
|
||||||
|
[employeeId]
|
||||||
);
|
);
|
||||||
|
|
||||||
res.status(201).json({ user: newUser });
|
// Format response with roles array
|
||||||
|
const userResponse = {
|
||||||
|
...newUser,
|
||||||
|
roles: newUser.role ? [newUser.role] : ['user']
|
||||||
|
};
|
||||||
|
|
||||||
|
res.status(201).json({ user: userResponse });
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
await db.run('ROLLBACK');
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Registration error:', error);
|
console.error('Registration error:', error);
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
|
|||||||
@@ -33,25 +33,35 @@ export const getEmployees = async (req: AuthRequest, res: Response): Promise<voi
|
|||||||
|
|
||||||
let query = `
|
let query = `
|
||||||
SELECT
|
SELECT
|
||||||
id, email, firstname, lastname, role, is_active as isActive,
|
e.id, e.email, e.firstname, e.lastname,
|
||||||
employee_type as employeeType,
|
e.is_active as isActive,
|
||||||
contract_type as contractType,
|
e.employee_type as employeeType,
|
||||||
can_work_alone as canWorkAlone,
|
e.contract_type as contractType,
|
||||||
created_at as createdAt,
|
e.can_work_alone as canWorkAlone,
|
||||||
last_login as lastLogin
|
e.created_at as createdAt,
|
||||||
FROM employees
|
e.last_login as lastLogin,
|
||||||
|
er.role
|
||||||
|
FROM employees e
|
||||||
|
LEFT JOIN employee_roles er ON e.id = er.employee_id
|
||||||
`;
|
`;
|
||||||
|
|
||||||
if (!includeInactiveFlag) {
|
if (!includeInactiveFlag) {
|
||||||
query += ' WHERE is_active = 1';
|
query += ' WHERE e.is_active = 1';
|
||||||
}
|
}
|
||||||
|
|
||||||
query += ' ORDER BY name';
|
// UPDATED: Order by firstname and lastname
|
||||||
|
query += ' ORDER BY e.firstname, e.lastname';
|
||||||
|
|
||||||
const employees = await db.all<any>(query);
|
const employees = await db.all<any>(query);
|
||||||
|
|
||||||
console.log('✅ Employees found:', employees.length);
|
// Format employees with roles array for frontend compatibility
|
||||||
res.json(employees);
|
const employeesWithRoles = employees.map(emp => ({
|
||||||
|
...emp,
|
||||||
|
roles: emp.role ? [emp.role] : ['user']
|
||||||
|
}));
|
||||||
|
|
||||||
|
console.log('✅ Employees found:', employeesWithRoles.length);
|
||||||
|
res.json(employeesWithRoles);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Error fetching employees:', error);
|
console.error('❌ Error fetching employees:', error);
|
||||||
res.status(500).json({ error: 'Internal server error' });
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
@@ -62,16 +72,21 @@ export const getEmployee = async (req: AuthRequest, res: Response): Promise<void
|
|||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
|
|
||||||
|
// UPDATED: Query with role from employee_roles table
|
||||||
const employee = await db.get<any>(`
|
const employee = await db.get<any>(`
|
||||||
SELECT
|
SELECT
|
||||||
id, email, firstname, lastname, role, is_active as isActive,
|
e.id, e.email, e.firstname, e.lastname,
|
||||||
employee_type as employeeType,
|
e.is_active as isActive,
|
||||||
contract_type as contractType,
|
e.employee_type as employeeType,
|
||||||
can_work_alone as canWorkAlone,
|
e.contract_type as contractType,
|
||||||
created_at as createdAt,
|
e.can_work_alone as canWorkAlone,
|
||||||
last_login as lastLogin
|
e.created_at as createdAt,
|
||||||
FROM employees
|
e.last_login as lastLogin,
|
||||||
WHERE id = ?
|
er.role
|
||||||
|
FROM employees e
|
||||||
|
LEFT JOIN employee_roles er ON e.id = er.employee_id
|
||||||
|
WHERE e.id = ?
|
||||||
|
LIMIT 1
|
||||||
`, [id]);
|
`, [id]);
|
||||||
|
|
||||||
if (!employee) {
|
if (!employee) {
|
||||||
@@ -79,7 +94,13 @@ export const getEmployee = async (req: AuthRequest, res: Response): Promise<void
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json(employee);
|
// Format employee with roles array
|
||||||
|
const employeeWithRoles = {
|
||||||
|
...employee,
|
||||||
|
roles: employee.role ? [employee.role] : ['user']
|
||||||
|
};
|
||||||
|
|
||||||
|
res.json(employeeWithRoles);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching employee:', error);
|
console.error('Error fetching employee:', error);
|
||||||
res.status(500).json({ error: 'Internal server error' });
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
@@ -97,17 +118,17 @@ export const createEmployee = async (req: AuthRequest, res: Response): Promise<v
|
|||||||
password,
|
password,
|
||||||
firstname,
|
firstname,
|
||||||
lastname,
|
lastname,
|
||||||
role,
|
roles = ['user'], // UPDATED: Now uses roles array
|
||||||
employeeType,
|
employeeType,
|
||||||
contractType,
|
contractType,
|
||||||
canWorkAlone
|
canWorkAlone
|
||||||
} = req.body as CreateEmployeeRequest;
|
} = req.body as CreateEmployeeRequest;
|
||||||
|
|
||||||
// Validation - REMOVED email check
|
// Validation
|
||||||
if (!password || !firstname || !lastname || !role || !employeeType || !contractType) {
|
if (!password || !firstname || !lastname || !employeeType || !contractType) {
|
||||||
console.log('❌ Validation failed: Missing required fields');
|
console.log('❌ Validation failed: Missing required fields');
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
error: 'Password, firstname, lastname, role, employeeType und contractType sind erforderlich'
|
error: 'Password, firstname, lastname, employeeType und contractType sind erforderlich'
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -131,18 +152,22 @@ export const createEmployee = async (req: AuthRequest, res: Response): Promise<v
|
|||||||
const hashedPassword = await bcrypt.hash(password, 10);
|
const hashedPassword = await bcrypt.hash(password, 10);
|
||||||
const employeeId = uuidv4();
|
const employeeId = uuidv4();
|
||||||
|
|
||||||
|
// Start transaction for employee creation and role assignment
|
||||||
|
await db.run('BEGIN TRANSACTION');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// UPDATED: Insert employee without role (role is now in employee_roles table)
|
||||||
await db.run(
|
await db.run(
|
||||||
`INSERT INTO employees (
|
`INSERT INTO employees (
|
||||||
id, email, password, firstname, lastname, role, employee_type, contract_type, can_work_alone,
|
id, email, password, firstname, lastname, employee_type, contract_type, can_work_alone,
|
||||||
is_active
|
is_active
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
[
|
[
|
||||||
employeeId,
|
employeeId,
|
||||||
email,
|
email,
|
||||||
hashedPassword,
|
hashedPassword,
|
||||||
firstname,
|
firstname,
|
||||||
lastname,
|
lastname,
|
||||||
role,
|
|
||||||
employeeType,
|
employeeType,
|
||||||
contractType,
|
contractType,
|
||||||
canWorkAlone ? 1 : 0,
|
canWorkAlone ? 1 : 0,
|
||||||
@@ -150,20 +175,45 @@ export const createEmployee = async (req: AuthRequest, res: Response): Promise<v
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Return created employee with generated email
|
// UPDATED: Insert roles into employee_roles table
|
||||||
|
for (const role of roles) {
|
||||||
|
await db.run(
|
||||||
|
`INSERT INTO employee_roles (employee_id, role) VALUES (?, ?)`,
|
||||||
|
[employeeId, role]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.run('COMMIT');
|
||||||
|
|
||||||
|
// Return created employee with role from employee_roles
|
||||||
const newEmployee = await db.get<any>(`
|
const newEmployee = await db.get<any>(`
|
||||||
SELECT
|
SELECT
|
||||||
id, email, firstname, lastname, role, is_active as isActive,
|
e.id, e.email, e.firstname, e.lastname,
|
||||||
employee_type as employeeType,
|
e.is_active as isActive,
|
||||||
contract_type as contractType,
|
e.employee_type as employeeType,
|
||||||
can_work_alone as canWorkAlone,
|
e.contract_type as contractType,
|
||||||
created_at as createdAt,
|
e.can_work_alone as canWorkAlone,
|
||||||
last_login as lastLogin
|
e.created_at as createdAt,
|
||||||
FROM employees
|
e.last_login as lastLogin,
|
||||||
WHERE id = ?
|
er.role
|
||||||
|
FROM employees e
|
||||||
|
LEFT JOIN employee_roles er ON e.id = er.employee_id
|
||||||
|
WHERE e.id = ?
|
||||||
|
LIMIT 1
|
||||||
`, [employeeId]);
|
`, [employeeId]);
|
||||||
|
|
||||||
res.status(201).json(newEmployee);
|
// Format response with roles array
|
||||||
|
const employeeWithRoles = {
|
||||||
|
...newEmployee,
|
||||||
|
roles: newEmployee.role ? [newEmployee.role] : ['user']
|
||||||
|
};
|
||||||
|
|
||||||
|
res.status(201).json(employeeWithRoles);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
await db.run('ROLLBACK');
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error creating employee:', error);
|
console.error('Error creating employee:', error);
|
||||||
res.status(500).json({ error: 'Internal server error' });
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
@@ -173,9 +223,9 @@ export const createEmployee = async (req: AuthRequest, res: Response): Promise<v
|
|||||||
export const updateEmployee = async (req: AuthRequest, res: Response): Promise<void> => {
|
export const updateEmployee = async (req: AuthRequest, res: Response): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const { firstname, lastname, role, isActive, employeeType, contractType, canWorkAlone } = req.body;
|
const { firstname, lastname, roles, isActive, employeeType, contractType, canWorkAlone } = req.body;
|
||||||
|
|
||||||
console.log('📝 Update Employee Request:', { id, firstname, lastname, role, isActive, employeeType, contractType, canWorkAlone });
|
console.log('📝 Update Employee Request:', { id, firstname, lastname, roles, isActive, employeeType, contractType, canWorkAlone });
|
||||||
|
|
||||||
// Check if employee exists and get current data
|
// Check if employee exists and get current data
|
||||||
const existingEmployee = await db.get<any>('SELECT * FROM employees WHERE id = ?', [id]);
|
const existingEmployee = await db.get<any>('SELECT * FROM employees WHERE id = ?', [id]);
|
||||||
@@ -205,37 +255,71 @@ export const updateEmployee = async (req: AuthRequest, res: Response): Promise<v
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update employee with potentially new email
|
// Start transaction for employee update and role management
|
||||||
|
await db.run('BEGIN TRANSACTION');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// UPDATED: Update employee without role (role is now in employee_roles table)
|
||||||
await db.run(
|
await db.run(
|
||||||
`UPDATE employees
|
`UPDATE employees
|
||||||
SET firstname = COALESCE(?, firstname),
|
SET firstname = COALESCE(?, firstname),
|
||||||
lastname = COALESCE(?, lastname),
|
lastname = COALESCE(?, lastname),
|
||||||
email = ?,
|
email = ?,
|
||||||
role = COALESCE(?, role),
|
|
||||||
is_active = COALESCE(?, is_active),
|
is_active = COALESCE(?, is_active),
|
||||||
employee_type = COALESCE(?, employee_type),
|
employee_type = COALESCE(?, employee_type),
|
||||||
contract_type = COALESCE(?, contract_type),
|
contract_type = COALESCE(?, contract_type),
|
||||||
can_work_alone = COALESCE(?, can_work_alone)
|
can_work_alone = COALESCE(?, can_work_alone)
|
||||||
WHERE id = ?`,
|
WHERE id = ?`,
|
||||||
[firstname, lastname, email, role, isActive, employeeType, contractType, canWorkAlone, id]
|
[firstname, lastname, email, isActive, employeeType, contractType, canWorkAlone, id]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// UPDATED: Update roles if provided
|
||||||
|
if (roles) {
|
||||||
|
// Delete existing roles
|
||||||
|
await db.run('DELETE FROM employee_roles WHERE employee_id = ?', [id]);
|
||||||
|
|
||||||
|
// Insert new roles
|
||||||
|
for (const role of roles) {
|
||||||
|
await db.run(
|
||||||
|
`INSERT INTO employee_roles (employee_id, role) VALUES (?, ?)`,
|
||||||
|
[id, role]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.run('COMMIT');
|
||||||
|
|
||||||
console.log('✅ Employee updated successfully with email:', email);
|
console.log('✅ Employee updated successfully with email:', email);
|
||||||
|
|
||||||
// Return updated employee
|
// Return updated employee with role from employee_roles
|
||||||
const updatedEmployee = await db.get<any>(`
|
const updatedEmployee = await db.get<any>(`
|
||||||
SELECT
|
SELECT
|
||||||
id, email, firstname, lastname, role, is_active as isActive,
|
e.id, e.email, e.firstname, e.lastname,
|
||||||
employee_type as employeeType,
|
e.is_active as isActive,
|
||||||
contract_type as contractType,
|
e.employee_type as employeeType,
|
||||||
can_work_alone as canWorkAlone,
|
e.contract_type as contractType,
|
||||||
created_at as createdAt,
|
e.can_work_alone as canWorkAlone,
|
||||||
last_login as lastLogin
|
e.created_at as createdAt,
|
||||||
FROM employees
|
e.last_login as lastLogin,
|
||||||
WHERE id = ?
|
er.role
|
||||||
|
FROM employees e
|
||||||
|
LEFT JOIN employee_roles er ON e.id = er.employee_id
|
||||||
|
WHERE e.id = ?
|
||||||
|
LIMIT 1
|
||||||
`, [id]);
|
`, [id]);
|
||||||
|
|
||||||
res.json(updatedEmployee);
|
// Format response with roles array
|
||||||
|
const employeeWithRoles = {
|
||||||
|
...updatedEmployee,
|
||||||
|
roles: updatedEmployee.role ? [updatedEmployee.role] : ['user']
|
||||||
|
};
|
||||||
|
|
||||||
|
res.json(employeeWithRoles);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
await db.run('ROLLBACK');
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error updating employee:', error);
|
console.error('Error updating employee:', error);
|
||||||
res.status(500).json({ error: 'Internal server error' });
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
@@ -247,11 +331,15 @@ export const deleteEmployee = async (req: AuthRequest, res: Response): Promise<v
|
|||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
console.log('🗑️ Starting deletion process for employee ID:', id);
|
console.log('🗑️ Starting deletion process for employee ID:', id);
|
||||||
|
|
||||||
// Check if employee exists
|
// UPDATED: Check if employee exists with role from employee_roles
|
||||||
const existingEmployee = await db.get<any>(`
|
const existingEmployee = await db.get<any>(`
|
||||||
SELECT id, email, name, is_active, role
|
SELECT
|
||||||
FROM employees
|
e.id, e.email, e.firstname, e.lastname, e.is_active,
|
||||||
WHERE id = ?
|
er.role
|
||||||
|
FROM employees e
|
||||||
|
LEFT JOIN employee_roles er ON e.id = er.employee_id
|
||||||
|
WHERE e.id = ?
|
||||||
|
LIMIT 1
|
||||||
`, [id]);
|
`, [id]);
|
||||||
|
|
||||||
if (!existingEmployee) {
|
if (!existingEmployee) {
|
||||||
@@ -297,10 +385,13 @@ export const deleteEmployee = async (req: AuthRequest, res: Response): Promise<v
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Nullify created_by references
|
// 3. Remove roles from employee_roles
|
||||||
|
await db.run('DELETE FROM employee_roles WHERE employee_id = ?', [id]);
|
||||||
|
|
||||||
|
// 4. Nullify created_by references
|
||||||
await db.run('UPDATE shift_plans SET created_by = NULL WHERE created_by = ?', [id]);
|
await db.run('UPDATE shift_plans SET created_by = NULL WHERE created_by = ?', [id]);
|
||||||
|
|
||||||
// 4. Finally delete the employee
|
// 5. Finally delete the employee
|
||||||
await db.run('DELETE FROM employees WHERE id = ?', [id]);
|
await db.run('DELETE FROM employees WHERE id = ?', [id]);
|
||||||
|
|
||||||
await db.run('COMMIT');
|
await db.run('COMMIT');
|
||||||
@@ -339,8 +430,6 @@ export const getAvailabilities = async (req: AuthRequest, res: Response): Promis
|
|||||||
ORDER BY s.day_of_week, s.time_slot_id
|
ORDER BY s.day_of_week, s.time_slot_id
|
||||||
`, [employeeId]);
|
`, [employeeId]);
|
||||||
|
|
||||||
//console.log('✅ Successfully got availabilities from employee:', availabilities);
|
|
||||||
|
|
||||||
res.json(availabilities.map(avail => ({
|
res.json(availabilities.map(avail => ({
|
||||||
id: avail.id,
|
id: avail.id,
|
||||||
employeeId: avail.employee_id,
|
employeeId: avail.employee_id,
|
||||||
@@ -393,14 +482,13 @@ export const updateAvailabilities = async (req: AuthRequest, res: Response): Pro
|
|||||||
|
|
||||||
await db.run('COMMIT');
|
await db.run('COMMIT');
|
||||||
|
|
||||||
console.log('✅ Successfully updated availablities employee:', );
|
|
||||||
|
|
||||||
|
|
||||||
// Return updated availabilities
|
// Return updated availabilities
|
||||||
const updatedAvailabilities = await db.all<any>(`
|
const updatedAvailabilities = await db.all<any>(`
|
||||||
SELECT * FROM employee_availability
|
SELECT ea.*, s.day_of_week, s.time_slot_id
|
||||||
WHERE employee_id = ? AND plan_id = ?
|
FROM employee_availability ea
|
||||||
ORDER BY day_of_week, time_slot_id
|
JOIN shifts s ON ea.shift_id = s.id
|
||||||
|
WHERE ea.employee_id = ? AND ea.plan_id = ?
|
||||||
|
ORDER BY s.day_of_week, s.time_slot_id
|
||||||
`, [employeeId, planId]);
|
`, [employeeId, planId]);
|
||||||
|
|
||||||
res.json(updatedAvailabilities.map(avail => ({
|
res.json(updatedAvailabilities.map(avail => ({
|
||||||
@@ -413,7 +501,7 @@ export const updateAvailabilities = async (req: AuthRequest, res: Response): Pro
|
|||||||
notes: avail.notes
|
notes: avail.notes
|
||||||
})));
|
})));
|
||||||
|
|
||||||
console.log('✅ Successfully updated employee:', updateAvailabilities);
|
console.log('✅ Successfully updated employee availabilities');
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await db.run('ROLLBACK');
|
await db.run('ROLLBACK');
|
||||||
|
|||||||
@@ -27,7 +27,10 @@ function generateEmail(firstname: string, lastname: string): string {
|
|||||||
export const checkSetupStatus = async (req: Request, res: Response): Promise<void> => {
|
export const checkSetupStatus = async (req: Request, res: Response): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const adminExists = await db.get<{ 'COUNT(*)': number }>(
|
const adminExists = await db.get<{ 'COUNT(*)': number }>(
|
||||||
'SELECT COUNT(*) FROM employees WHERE role = ? AND is_active = 1',
|
`SELECT COUNT(*)
|
||||||
|
FROM employees e
|
||||||
|
JOIN employee_roles er ON e.id = er.employee_id
|
||||||
|
WHERE er.role = ? AND e.is_active = 1`,
|
||||||
['admin']
|
['admin']
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -50,7 +53,10 @@ export const setupAdmin = async (req: Request, res: Response): Promise<void> =>
|
|||||||
try {
|
try {
|
||||||
// Check if admin already exists
|
// Check if admin already exists
|
||||||
const adminExists = await db.get<{ 'COUNT(*)': number }>(
|
const adminExists = await db.get<{ 'COUNT(*)': number }>(
|
||||||
'SELECT COUNT(*) FROM employees WHERE role = ? AND is_active = 1',
|
`SELECT COUNT(*)
|
||||||
|
FROM employees e
|
||||||
|
JOIN employee_roles er ON e.id = er.employee_id
|
||||||
|
WHERE er.role = ? AND e.is_active = 1`,
|
||||||
['admin']
|
['admin']
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -62,11 +68,11 @@ export const setupAdmin = async (req: Request, res: Response): Promise<void> =>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { password, firstname, lastname } = req.body; // Changed from name to firstname/lastname
|
const { password, firstname, lastname } = req.body;
|
||||||
|
|
||||||
console.log('👤 Creating admin with data:', { firstname, lastname });
|
console.log('👤 Creating admin with data:', { firstname, lastname });
|
||||||
|
|
||||||
// Validation - updated for firstname/lastname
|
// Validation
|
||||||
if (!password || !firstname || !lastname) {
|
if (!password || !firstname || !lastname) {
|
||||||
res.status(400).json({ error: 'Passwort, Vorname und Nachname sind erforderlich' });
|
res.status(400).json({ error: 'Passwort, Vorname und Nachname sind erforderlich' });
|
||||||
return;
|
return;
|
||||||
@@ -78,7 +84,7 @@ export const setupAdmin = async (req: Request, res: Response): Promise<void> =>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate email automatically using the same pattern
|
// Generate email automatically
|
||||||
const email = generateEmail(firstname, lastname);
|
const email = generateEmail(firstname, lastname);
|
||||||
console.log('📧 Generated admin email:', email);
|
console.log('📧 Generated admin email:', email);
|
||||||
|
|
||||||
@@ -92,11 +98,17 @@ export const setupAdmin = async (req: Request, res: Response): Promise<void> =>
|
|||||||
await db.run('BEGIN TRANSACTION');
|
await db.run('BEGIN TRANSACTION');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Create admin user with generated email
|
// Create admin user in employees table
|
||||||
await db.run(
|
await db.run(
|
||||||
`INSERT INTO employees (id, email, password, firstname, lastname, role, employee_type, contract_type, can_work_alone, is_active)
|
`INSERT INTO employees (id, email, password, firstname, lastname, employee_type, contract_type, can_work_alone, is_active)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
[adminId, email, hashedPassword, firstname, lastname, 'admin', 'manager', 'large', true, 1]
|
[adminId, email, hashedPassword, firstname, lastname, 'manager', 'large', true, 1]
|
||||||
|
);
|
||||||
|
|
||||||
|
// UPDATED: Assign admin role in employee_roles table
|
||||||
|
await db.run(
|
||||||
|
`INSERT INTO employee_roles (employee_id, role) VALUES (?, ?)`,
|
||||||
|
[adminId, 'admin']
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log('✅ Admin user created successfully with email:', email);
|
console.log('✅ Admin user created successfully with email:', email);
|
||||||
|
|||||||
@@ -25,6 +25,11 @@ CREATE TABLE IF NOT EXISTS employee_roles (
|
|||||||
PRIMARY KEY (employee_id, role)
|
PRIMARY KEY (employee_id, role)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- Insert default roles if they don't exist
|
||||||
|
INSERT OR IGNORE INTO roles (role) VALUES ('admin');
|
||||||
|
INSERT OR IGNORE INTO roles (role) VALUES ('user');
|
||||||
|
INSERT OR IGNORE INTO roles (role) VALUES ('maintenance');
|
||||||
|
|
||||||
-- Shift plans table
|
-- Shift plans table
|
||||||
CREATE TABLE IF NOT EXISTS shift_plans (
|
CREATE TABLE IF NOT EXISTS shift_plans (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
|
|||||||
@@ -4,20 +4,20 @@ export interface Employee {
|
|||||||
email: string;
|
email: string;
|
||||||
firstname: string;
|
firstname: string;
|
||||||
lastname: string;
|
lastname: string;
|
||||||
role: 'admin' | 'maintenance' | 'user';
|
|
||||||
employeeType: 'manager' | 'trainee' | 'experienced';
|
employeeType: 'manager' | 'trainee' | 'experienced';
|
||||||
contractType: 'small' | 'large';
|
contractType: 'small' | 'large';
|
||||||
canWorkAlone: boolean;
|
canWorkAlone: boolean;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
lastLogin?: string | null;
|
lastLogin?: string | null;
|
||||||
|
roles?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateEmployeeRequest {
|
export interface CreateEmployeeRequest {
|
||||||
password: string;
|
password: string;
|
||||||
firstname: string;
|
firstname: string;
|
||||||
lastname: string;
|
lastname: string;
|
||||||
role: 'admin' | 'maintenance' | 'user';
|
roles?: string[];
|
||||||
employeeType: 'manager' | 'trainee' | 'experienced';
|
employeeType: 'manager' | 'trainee' | 'experienced';
|
||||||
contractType: 'small' | 'large';
|
contractType: 'small' | 'large';
|
||||||
canWorkAlone: boolean;
|
canWorkAlone: boolean;
|
||||||
@@ -26,7 +26,7 @@ export interface CreateEmployeeRequest {
|
|||||||
export interface UpdateEmployeeRequest {
|
export interface UpdateEmployeeRequest {
|
||||||
firstname?: string;
|
firstname?: string;
|
||||||
lastname?: string;
|
lastname?: string;
|
||||||
role?: 'admin' | 'maintenance' | 'user';
|
roles?: string[];
|
||||||
employeeType?: 'manager' | 'trainee' | 'experienced';
|
employeeType?: 'manager' | 'trainee' | 'experienced';
|
||||||
contractType?: 'small' | 'large';
|
contractType?: 'small' | 'large';
|
||||||
canWorkAlone?: boolean;
|
canWorkAlone?: boolean;
|
||||||
|
|||||||
@@ -57,7 +57,13 @@ export const isExperienced = (employee: Employee): boolean =>
|
|||||||
employee.employeeType === 'experienced';
|
employee.employeeType === 'experienced';
|
||||||
|
|
||||||
export const isAdmin = (employee: Employee): boolean =>
|
export const isAdmin = (employee: Employee): boolean =>
|
||||||
employee.role === 'admin';
|
employee.roles?.includes('admin') || false;
|
||||||
|
|
||||||
|
export const isMaintenance = (employee: Employee): boolean =>
|
||||||
|
employee.roles?.includes('maintenance') || false;
|
||||||
|
|
||||||
|
export const isUser = (employee: Employee): boolean =>
|
||||||
|
employee.roles?.includes('user') || false;
|
||||||
|
|
||||||
export const canEmployeeWorkAlone = (employee: Employee): boolean =>
|
export const canEmployeeWorkAlone = (employee: Employee): boolean =>
|
||||||
employee.canWorkAlone && isExperienced(employee);
|
employee.canWorkAlone && isExperienced(employee);
|
||||||
|
|||||||
@@ -14,10 +14,12 @@ export async function initializeDatabase(): Promise<void> {
|
|||||||
try {
|
try {
|
||||||
console.log('Starting database initialization...');
|
console.log('Starting database initialization...');
|
||||||
|
|
||||||
// Check if users table exists and has data
|
|
||||||
try {
|
try {
|
||||||
const existingAdmin = await db.get<{ count: number }>(
|
const existingAdmin = await db.get<{ count: number }>(
|
||||||
"SELECT COUNT(*) as count FROM employees WHERE role = 'admin'"
|
`SELECT COUNT(*) as count
|
||||||
|
FROM employees e
|
||||||
|
JOIN employee_roles er ON e.id = er.employee_id
|
||||||
|
WHERE er.role = 'admin' AND e.is_active = 1`
|
||||||
);
|
);
|
||||||
|
|
||||||
if (existingAdmin && existingAdmin.count > 0) {
|
if (existingAdmin && existingAdmin.count > 0) {
|
||||||
@@ -40,22 +42,28 @@ export async function initializeDatabase(): Promise<void> {
|
|||||||
|
|
||||||
console.log('Existing tables found:', existingTables.map(t => t.name).join(', ') || 'none');
|
console.log('Existing tables found:', existingTables.map(t => t.name).join(', ') || 'none');
|
||||||
|
|
||||||
// Drop existing tables in reverse order of dependencies if they exist
|
// UPDATED: Drop tables in correct dependency order
|
||||||
const tablesToDrop = [
|
const tablesToDrop = [
|
||||||
'employees',
|
|
||||||
'time_slots',
|
|
||||||
'shifts',
|
|
||||||
'scheduled_shifts',
|
|
||||||
'shift_assignments',
|
|
||||||
'employee_availability',
|
'employee_availability',
|
||||||
'applied_migrations',
|
'shift_assignments',
|
||||||
'shift_plans'
|
'scheduled_shifts',
|
||||||
|
'shifts',
|
||||||
|
'time_slots',
|
||||||
|
'employee_roles',
|
||||||
|
'shift_plans',
|
||||||
|
'roles',
|
||||||
|
'employees',
|
||||||
|
'applied_migrations'
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const table of tablesToDrop) {
|
for (const table of tablesToDrop) {
|
||||||
if (existingTables.some(t => t.name === table)) {
|
if (existingTables.some(t => t.name === table)) {
|
||||||
console.log(`Dropping table: ${table}`);
|
console.log(`Dropping table: ${table}`);
|
||||||
|
try {
|
||||||
await db.run(`DROP TABLE IF EXISTS ${table}`);
|
await db.run(`DROP TABLE IF EXISTS ${table}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Could not drop table ${table}:`, error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -92,6 +100,19 @@ export async function initializeDatabase(): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UPDATED: Insert default roles after creating the tables
|
||||||
|
try {
|
||||||
|
console.log('Inserting default roles...');
|
||||||
|
await db.run(`INSERT OR IGNORE INTO roles (role) VALUES ('admin')`);
|
||||||
|
await db.run(`INSERT OR IGNORE INTO roles (role) VALUES ('user')`);
|
||||||
|
await db.run(`INSERT OR IGNORE INTO roles (role) VALUES ('maintenance')`);
|
||||||
|
console.log('✅ Default roles inserted');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error inserting default roles:', error);
|
||||||
|
await db.run('ROLLBACK');
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
await db.run('COMMIT');
|
await db.run('COMMIT');
|
||||||
console.log('✅ Database schema successfully initialized');
|
console.log('✅ Database schema successfully initialized');
|
||||||
|
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ const Navigation: React.FC = () => {
|
|||||||
{/* User Menu - Rechts */}
|
{/* User Menu - Rechts */}
|
||||||
<div style={styles.userMenu}>
|
<div style={styles.userMenu}>
|
||||||
<span style={styles.userInfo}>
|
<span style={styles.userInfo}>
|
||||||
{user?.name} <span style={{color: '#999'}}>({user?.role})</span>
|
{user?.firstname} {user?.lastname} <span style={{color: '#999'}}>({user?.roles})</span>
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
onClick={handleLogout}
|
onClick={handleLogout}
|
||||||
@@ -266,8 +266,8 @@ const Navigation: React.FC = () => {
|
|||||||
))}
|
))}
|
||||||
<div style={styles.mobileUserInfo}>
|
<div style={styles.mobileUserInfo}>
|
||||||
<div style={{marginBottom: '0.5rem'}}>
|
<div style={{marginBottom: '0.5rem'}}>
|
||||||
<span style={{fontWeight: 500}}>{user?.name}</span>
|
<span style={{fontWeight: 500}}>{user?.firstname} {user?.lastname}</span>
|
||||||
<span style={{color: '#999', marginLeft: '0.5rem'}}>({user?.role})</span>
|
<span style={{color: '#999', marginLeft: '0.5rem'}}>({user?.roles})</span>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={handleLogout}
|
onClick={handleLogout}
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
|
|||||||
|
|
||||||
const hasRole = (roles: string[]): boolean => {
|
const hasRole = (roles: string[]): boolean => {
|
||||||
if (!user) return false;
|
if (!user) return false;
|
||||||
return roles.includes(user.role);
|
return roles.length != 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -4,20 +4,20 @@ export interface Employee {
|
|||||||
email: string;
|
email: string;
|
||||||
firstname: string;
|
firstname: string;
|
||||||
lastname: string;
|
lastname: string;
|
||||||
role: 'admin' | 'maintenance' | 'user';
|
|
||||||
employeeType: 'manager' | 'trainee' | 'experienced';
|
employeeType: 'manager' | 'trainee' | 'experienced';
|
||||||
contractType: 'small' | 'large';
|
contractType: 'small' | 'large';
|
||||||
canWorkAlone: boolean;
|
canWorkAlone: boolean;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
lastLogin?: string | null;
|
lastLogin?: string | null;
|
||||||
|
roles?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateEmployeeRequest {
|
export interface CreateEmployeeRequest {
|
||||||
password: string;
|
password: string;
|
||||||
firstname: string;
|
firstname: string;
|
||||||
lastname: string;
|
lastname: string;
|
||||||
role: 'admin' | 'maintenance' | 'user';
|
roles: string[];
|
||||||
employeeType: 'manager' | 'trainee' | 'experienced';
|
employeeType: 'manager' | 'trainee' | 'experienced';
|
||||||
contractType: 'small' | 'large';
|
contractType: 'small' | 'large';
|
||||||
canWorkAlone: boolean;
|
canWorkAlone: boolean;
|
||||||
@@ -26,7 +26,7 @@ export interface CreateEmployeeRequest {
|
|||||||
export interface UpdateEmployeeRequest {
|
export interface UpdateEmployeeRequest {
|
||||||
firstname?: string;
|
firstname?: string;
|
||||||
lastname?: string;
|
lastname?: string;
|
||||||
role?: 'admin' | 'maintenance' | 'user';
|
roles?: string[];
|
||||||
employeeType?: 'manager' | 'trainee' | 'experienced';
|
employeeType?: 'manager' | 'trainee' | 'experienced';
|
||||||
contractType?: 'small' | 'large';
|
contractType?: 'small' | 'large';
|
||||||
canWorkAlone?: boolean;
|
canWorkAlone?: boolean;
|
||||||
|
|||||||
@@ -57,8 +57,13 @@ export const isExperienced = (employee: Employee): boolean =>
|
|||||||
employee.employeeType === 'experienced';
|
employee.employeeType === 'experienced';
|
||||||
|
|
||||||
export const isAdmin = (employee: Employee): boolean =>
|
export const isAdmin = (employee: Employee): boolean =>
|
||||||
employee.role === 'admin';
|
employee.roles?.includes('admin') || false;
|
||||||
|
|
||||||
|
export const isMaintenance = (employee: Employee): boolean =>
|
||||||
|
employee.roles?.includes('maintenance') || false;
|
||||||
|
|
||||||
|
export const isUser = (employee: Employee): boolean =>
|
||||||
|
employee.roles?.includes('user') || false;
|
||||||
export const canEmployeeWorkAlone = (employee: Employee): boolean =>
|
export const canEmployeeWorkAlone = (employee: Employee): boolean =>
|
||||||
employee.canWorkAlone && isExperienced(employee);
|
employee.canWorkAlone && isExperienced(employee);
|
||||||
|
|
||||||
|
|||||||
@@ -352,7 +352,7 @@ const Dashboard: React.FC = () => {
|
|||||||
}}>
|
}}>
|
||||||
<div>
|
<div>
|
||||||
<h1 style={{ margin: '0 0 10px 0', color: '#2c3e50' }}>
|
<h1 style={{ margin: '0 0 10px 0', color: '#2c3e50' }}>
|
||||||
Willkommen zurück, {user?.name}! 👋
|
Willkommen zurück, {user?.firstname} {user?.lastname} ! 👋
|
||||||
</h1>
|
</h1>
|
||||||
<p style={{ margin: 0, color: '#546e7a', fontSize: '16px' }}>
|
<p style={{ margin: 0, color: '#546e7a', fontSize: '16px' }}>
|
||||||
{new Date().toLocaleDateString('de-DE', {
|
{new Date().toLocaleDateString('de-DE', {
|
||||||
|
|||||||
@@ -84,16 +84,26 @@ const EmployeeManagement: React.FC = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Helper function to get full name
|
||||||
|
const getFullName = (employee: Employee): string => {
|
||||||
|
return `${employee.firstname} ${employee.lastname}`;
|
||||||
|
};
|
||||||
|
|
||||||
// Verbesserte Lösch-Funktion mit Bestätigungs-Dialog
|
// Verbesserte Lösch-Funktion mit Bestätigungs-Dialog
|
||||||
const handleDeleteEmployee = async (employee: Employee) => {
|
const handleDeleteEmployee = async (employee: Employee) => {
|
||||||
try {
|
try {
|
||||||
|
const fullName = getFullName(employee);
|
||||||
|
|
||||||
// Bestätigungs-Dialog basierend auf Rolle
|
// Bestätigungs-Dialog basierend auf Rolle
|
||||||
let confirmMessage = `Möchten Sie den Mitarbeiter "${employee.name}" wirklich PERMANENT LÖSCHEN?\n\nDie Daten des Mitarbeiters werden unwiderruflich gelöscht. Diese Aktion kann nicht rückgängig gemacht werden.`;
|
let confirmMessage = `Möchten Sie den Mitarbeiter "${fullName}" (${employee.email}) wirklich PERMANENT LÖSCHEN?\n\nDie Daten des Mitarbeiters werden unwiderruflich gelöscht. Diese Aktion kann nicht rückgängig gemacht werden.`;
|
||||||
let confirmTitle = 'Mitarbeiter löschen';
|
let confirmTitle = 'Mitarbeiter löschen';
|
||||||
|
|
||||||
if (employee.role === 'admin') {
|
// Check if employee has admin role (now in roles array)
|
||||||
|
const isAdmin = employee.roles?.includes('admin') || false;
|
||||||
|
|
||||||
|
if (isAdmin) {
|
||||||
const adminCount = employees.filter(emp =>
|
const adminCount = employees.filter(emp =>
|
||||||
emp.role === 'admin' && emp.isActive
|
(emp.roles?.includes('admin') || false) && emp.isActive
|
||||||
).length;
|
).length;
|
||||||
|
|
||||||
if (adminCount <= 1) {
|
if (adminCount <= 1) {
|
||||||
@@ -106,7 +116,7 @@ const EmployeeManagement: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
confirmTitle = 'Administrator löschen';
|
confirmTitle = 'Administrator löschen';
|
||||||
confirmMessage = `Möchten Sie den Administrator "${employee.name}" wirklich PERMANENT LÖSCHEN?\n\nAchtung: Diese Aktion ist permanent und kann nicht rückgängig gemacht werden.`;
|
confirmMessage = `Möchten Sie den Administrator "${fullName}" (${employee.email}) wirklich PERMANENT LÖSCHEN?\n\nAchtung: Diese Aktion ist permanent und kann nicht rückgängig gemacht werden.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const confirmed = await confirmDialog({
|
const confirmed = await confirmDialog({
|
||||||
@@ -119,7 +129,7 @@ const EmployeeManagement: React.FC = () => {
|
|||||||
|
|
||||||
if (!confirmed) return;
|
if (!confirmed) return;
|
||||||
|
|
||||||
console.log('Starting deletion process for employee:', employee.name);
|
console.log('Starting deletion process for employee:', fullName);
|
||||||
await employeeService.deleteEmployee(employee.id);
|
await employeeService.deleteEmployee(employee.id);
|
||||||
console.log('Employee deleted, reloading list');
|
console.log('Employee deleted, reloading list');
|
||||||
|
|
||||||
@@ -130,7 +140,7 @@ const EmployeeManagement: React.FC = () => {
|
|||||||
showNotification({
|
showNotification({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
title: 'Erfolg',
|
title: 'Erfolg',
|
||||||
message: `Mitarbeiter "${employee.name}" wurde erfolgreich gelöscht`
|
message: `Mitarbeiter "${fullName}" wurde erfolgreich gelöscht`
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
@@ -218,7 +228,7 @@ const EmployeeManagement: React.FC = () => {
|
|||||||
<EmployeeList
|
<EmployeeList
|
||||||
employees={employees}
|
employees={employees}
|
||||||
onEdit={handleEditEmployee}
|
onEdit={handleEditEmployee}
|
||||||
onDelete={handleDeleteEmployee} // Jetzt mit Employee-Objekt
|
onDelete={handleDeleteEmployee}
|
||||||
onManageAvailability={handleManageAvailability}
|
onManageAvailability={handleManageAvailability}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// frontend/src/pages/Employees/components/AvailabilityManager.tsx
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { employeeService } from '../../../services/employeeService';
|
import { employeeService } from '../../../services/employeeService';
|
||||||
import { shiftPlanService } from '../../../services/shiftPlanService';
|
import { shiftPlanService } from '../../../services/shiftPlanService';
|
||||||
@@ -200,19 +199,11 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
|||||||
return updated;
|
return updated;
|
||||||
} else {
|
} else {
|
||||||
// Create new availability using shiftId directly
|
// Create new availability using shiftId directly
|
||||||
const shift = selectedPlan?.shifts?.find(s => s.id === shiftId);
|
|
||||||
if (!shift) {
|
|
||||||
console.error('❌ Shift nicht gefunden:', shiftId);
|
|
||||||
return prev;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newAvailability: Availability = {
|
const newAvailability: Availability = {
|
||||||
id: `temp-${shiftId}-${Date.now()}`,
|
id: `temp-${shiftId}-${Date.now()}`,
|
||||||
employeeId: employee.id,
|
employeeId: employee.id,
|
||||||
planId: selectedPlanId,
|
planId: selectedPlanId,
|
||||||
shiftId: shiftId, // Use shiftId directly
|
shiftId: shiftId,
|
||||||
dayOfWeek: shift.dayOfWeek, // Keep for backward compatibility if needed
|
|
||||||
timeSlotId: shift.timeSlotId, // Keep for backward compatibility if needed
|
|
||||||
preferenceLevel: level,
|
preferenceLevel: level,
|
||||||
isAvailable: level !== 3
|
isAvailable: level !== 3
|
||||||
};
|
};
|
||||||
@@ -433,9 +424,7 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
|||||||
planId: selectedPlanId,
|
planId: selectedPlanId,
|
||||||
availabilities: validAvailabilities.map(avail => ({
|
availabilities: validAvailabilities.map(avail => ({
|
||||||
planId: selectedPlanId,
|
planId: selectedPlanId,
|
||||||
shiftId: avail.shiftId, // Use shiftId directly
|
shiftId: avail.shiftId,
|
||||||
dayOfWeek: avail.dayOfWeek, // Keep for backward compatibility
|
|
||||||
timeSlotId: avail.timeSlotId, // Keep for backward compatibility
|
|
||||||
preferenceLevel: avail.preferenceLevel,
|
preferenceLevel: avail.preferenceLevel,
|
||||||
notes: avail.notes
|
notes: avail.notes
|
||||||
}))
|
}))
|
||||||
@@ -472,6 +461,9 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
|||||||
});
|
});
|
||||||
const shiftsCount = allShiftIds.size;
|
const shiftsCount = allShiftIds.size;
|
||||||
|
|
||||||
|
// Get full name for display
|
||||||
|
const employeeFullName = `${employee.firstname} ${employee.lastname}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{
|
<div style={{
|
||||||
maxWidth: '1900px',
|
maxWidth: '1900px',
|
||||||
@@ -517,10 +509,13 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
|||||||
{/* Employee Info */}
|
{/* Employee Info */}
|
||||||
<div style={{ marginBottom: '20px' }}>
|
<div style={{ marginBottom: '20px' }}>
|
||||||
<h3 style={{ margin: '0 0 10px 0', color: '#34495e' }}>
|
<h3 style={{ margin: '0 0 10px 0', color: '#34495e' }}>
|
||||||
{employee.name}
|
{employeeFullName}
|
||||||
</h3>
|
</h3>
|
||||||
<p style={{ margin: 0, color: '#7f8c8d' }}>
|
<p style={{ margin: 0, color: '#7f8c8d' }}>
|
||||||
Legen Sie die Verfügbarkeit für {employee.name} fest (basierend auf Shift-IDs).
|
<strong>Email:</strong> {employee.email}
|
||||||
|
</p>
|
||||||
|
<p style={{ margin: '5px 0 0 0', color: '#7f8c8d' }}>
|
||||||
|
Legen Sie die Verfügbarkeit für {employeeFullName} fest (basierend auf Shift-IDs).
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// frontend/src/pages/Employees/components/EmployeeForm.tsx
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Employee, CreateEmployeeRequest, UpdateEmployeeRequest } from '../../../models/Employee';
|
import { Employee, CreateEmployeeRequest, UpdateEmployeeRequest } from '../../../models/Employee';
|
||||||
import { ROLE_CONFIG, EMPLOYEE_TYPE_CONFIG } from '../../../models/defaults/employeeDefaults';
|
import { ROLE_CONFIG, EMPLOYEE_TYPE_CONFIG } from '../../../models/defaults/employeeDefaults';
|
||||||
@@ -19,10 +18,11 @@ const EmployeeForm: React.FC<EmployeeFormProps> = ({
|
|||||||
onCancel
|
onCancel
|
||||||
}) => {
|
}) => {
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
name: '',
|
firstname: '',
|
||||||
email: '',
|
lastname: '',
|
||||||
|
email: '', // Will be auto-generated and display only
|
||||||
password: '',
|
password: '',
|
||||||
role: 'user' as 'admin' | 'maintenance' | 'user',
|
roles: ['user'] as string[], // Changed from single role to array
|
||||||
employeeType: 'trainee' as 'manager' | 'trainee' | 'experienced',
|
employeeType: 'trainee' as 'manager' | 'trainee' | 'experienced',
|
||||||
contractType: 'small' as 'small' | 'large',
|
contractType: 'small' as 'small' | 'large',
|
||||||
canWorkAlone: false,
|
canWorkAlone: false,
|
||||||
@@ -37,13 +37,33 @@ const EmployeeForm: React.FC<EmployeeFormProps> = ({
|
|||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
const { hasRole } = useAuth();
|
const { hasRole } = useAuth();
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (mode === 'edit' && employee) {
|
if (mode === 'edit' && employee) {
|
||||||
setFormData({
|
setFormData({
|
||||||
name: employee.name,
|
firstname: employee.firstname,
|
||||||
|
lastname: employee.lastname,
|
||||||
email: employee.email,
|
email: employee.email,
|
||||||
password: '', // Passwort wird beim Bearbeiten nicht angezeigt
|
password: '', // Password wird beim Bearbeiten nicht angezeigt
|
||||||
role: employee.role,
|
roles: employee.roles || ['user'], // Use roles array
|
||||||
employeeType: employee.employeeType,
|
employeeType: employee.employeeType,
|
||||||
contractType: employee.contractType,
|
contractType: employee.contractType,
|
||||||
canWorkAlone: employee.canWorkAlone,
|
canWorkAlone: employee.canWorkAlone,
|
||||||
@@ -69,6 +89,24 @@ const EmployeeForm: React.FC<EmployeeFormProps> = ({
|
|||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleRoleChange = (role: string, checked: boolean) => {
|
||||||
|
setFormData(prev => {
|
||||||
|
if (checked) {
|
||||||
|
// Add role if checked
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
roles: [...prev.roles, role]
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// Remove role if unchecked
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
roles: prev.roles.filter(r => r !== role)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handleEmployeeTypeChange = (employeeType: 'manager' | 'trainee' | 'experienced') => {
|
const handleEmployeeTypeChange = (employeeType: 'manager' | 'trainee' | 'experienced') => {
|
||||||
// Manager and experienced can work alone, trainee cannot
|
// Manager and experienced can work alone, trainee cannot
|
||||||
const canWorkAlone = employeeType === 'manager' || employeeType === 'experienced';
|
const canWorkAlone = employeeType === 'manager' || employeeType === 'experienced';
|
||||||
@@ -95,10 +133,10 @@ const EmployeeForm: React.FC<EmployeeFormProps> = ({
|
|||||||
try {
|
try {
|
||||||
if (mode === 'create') {
|
if (mode === 'create') {
|
||||||
const createData: CreateEmployeeRequest = {
|
const createData: CreateEmployeeRequest = {
|
||||||
name: formData.name.trim(),
|
firstname: formData.firstname.trim(),
|
||||||
email: formData.email.trim(),
|
lastname: formData.lastname.trim(),
|
||||||
password: formData.password,
|
password: formData.password,
|
||||||
role: formData.role,
|
roles: formData.roles, // Use roles array
|
||||||
employeeType: formData.employeeType,
|
employeeType: formData.employeeType,
|
||||||
contractType: formData.contractType,
|
contractType: formData.contractType,
|
||||||
canWorkAlone: formData.canWorkAlone
|
canWorkAlone: formData.canWorkAlone
|
||||||
@@ -106,8 +144,9 @@ const EmployeeForm: React.FC<EmployeeFormProps> = ({
|
|||||||
await employeeService.createEmployee(createData);
|
await employeeService.createEmployee(createData);
|
||||||
} else if (employee) {
|
} else if (employee) {
|
||||||
const updateData: UpdateEmployeeRequest = {
|
const updateData: UpdateEmployeeRequest = {
|
||||||
name: formData.name.trim(),
|
firstname: formData.firstname.trim(),
|
||||||
role: formData.role,
|
lastname: formData.lastname.trim(),
|
||||||
|
roles: formData.roles, // Use roles array
|
||||||
employeeType: formData.employeeType,
|
employeeType: formData.employeeType,
|
||||||
contractType: formData.contractType,
|
contractType: formData.contractType,
|
||||||
canWorkAlone: formData.canWorkAlone,
|
canWorkAlone: formData.canWorkAlone,
|
||||||
@@ -141,8 +180,8 @@ const EmployeeForm: React.FC<EmployeeFormProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const isFormValid = mode === 'create'
|
const isFormValid = mode === 'create'
|
||||||
? formData.name.trim() && formData.email.trim() && formData.password.length >= 6
|
? formData.firstname.trim() && formData.lastname.trim() && formData.password.length >= 6
|
||||||
: formData.name.trim() && formData.email.trim();
|
: formData.firstname.trim() && formData.lastname.trim();
|
||||||
|
|
||||||
const availableRoles = hasRole(['admin'])
|
const availableRoles = hasRole(['admin'])
|
||||||
? ROLE_CONFIG
|
? ROLE_CONFIG
|
||||||
@@ -200,12 +239,12 @@ const EmployeeForm: React.FC<EmployeeFormProps> = ({
|
|||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '15px' }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '15px' }}>
|
||||||
<div>
|
<div>
|
||||||
<label style={{ display: 'block', marginBottom: '8px', fontWeight: 'bold', color: '#2c3e50' }}>
|
<label style={{ display: 'block', marginBottom: '8px', fontWeight: 'bold', color: '#2c3e50' }}>
|
||||||
Vollständiger Name *
|
Vorname *
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="name"
|
name="firstname"
|
||||||
value={formData.name}
|
value={formData.firstname}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
required
|
required
|
||||||
style={{
|
style={{
|
||||||
@@ -215,34 +254,54 @@ const EmployeeForm: React.FC<EmployeeFormProps> = ({
|
|||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
fontSize: '16px'
|
fontSize: '16px'
|
||||||
}}
|
}}
|
||||||
placeholder="Max Mustermann"
|
placeholder="Max"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label style={{ display: 'block', marginBottom: '8px', fontWeight: 'bold', color: '#2c3e50' }}>
|
<label style={{ display: 'block', marginBottom: '8px', fontWeight: 'bold', color: '#2c3e50' }}>
|
||||||
E-Mail Adresse *
|
Nachname *
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="text"
|
||||||
name="email"
|
name="lastname"
|
||||||
value={formData.email}
|
value={formData.lastname}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
required
|
required
|
||||||
disabled={mode === 'edit'} // Email cannot be changed in edit mode
|
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
padding: '10px',
|
padding: '10px',
|
||||||
border: '1px solid #ddd',
|
border: '1px solid #ddd',
|
||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
fontSize: '16px',
|
fontSize: '16px'
|
||||||
backgroundColor: mode === 'edit' ? '#f8f9fa' : 'white'
|
|
||||||
}}
|
}}
|
||||||
placeholder="max.mustermann@example.com"
|
placeholder="Mustermann"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Email Preview */}
|
||||||
|
<div style={{ marginTop: '15px' }}>
|
||||||
|
<label style={{ display: 'block', marginBottom: '8px', fontWeight: 'bold', color: '#2c3e50' }}>
|
||||||
|
E-Mail Adresse (automatisch generiert)
|
||||||
|
</label>
|
||||||
|
<div style={{
|
||||||
|
width: '100%',
|
||||||
|
padding: '10px',
|
||||||
|
border: '1px solid #ddd',
|
||||||
|
borderRadius: '4px',
|
||||||
|
fontSize: '16px',
|
||||||
|
backgroundColor: '#f8f9fa',
|
||||||
|
color: '#6c757d'
|
||||||
|
}}>
|
||||||
|
{emailPreview || 'max.mustermann@sp.de'}
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: '12px', color: '#7f8c8d', marginTop: '5px' }}>
|
||||||
|
Die E-Mail Adresse wird automatisch aus Vorname und Nachname generiert.
|
||||||
|
{formData.firstname && formData.lastname && ` Beispiel: ${emailPreview}`}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{mode === 'create' && (
|
{mode === 'create' && (
|
||||||
<div style={{ marginTop: '15px' }}>
|
<div style={{ marginTop: '15px' }}>
|
||||||
<label style={{ display: 'block', marginBottom: '8px', fontWeight: 'bold', color: '#2c3e50' }}>
|
<label style={{ display: 'block', marginBottom: '8px', fontWeight: 'bold', color: '#2c3e50' }}>
|
||||||
@@ -353,7 +412,7 @@ const EmployeeForm: React.FC<EmployeeFormProps> = ({
|
|||||||
<h3 style={{ margin: '0 0 15px 0', color: '#495057' }}>👥 Mitarbeiter Kategorie</h3>
|
<h3 style={{ margin: '0 0 15px 0', color: '#495057' }}>👥 Mitarbeiter Kategorie</h3>
|
||||||
|
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
||||||
{EMPLOYEE_TYPE_CONFIG.map(type => (
|
{Object.values(EMPLOYEE_TYPE_CONFIG).map(type => (
|
||||||
<div
|
<div
|
||||||
key={type.value}
|
key={type.value}
|
||||||
style={{
|
style={{
|
||||||
@@ -573,7 +632,7 @@ const EmployeeForm: React.FC<EmployeeFormProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Systemrolle (nur für Admins) */}
|
{/* Systemrollen (nur für Admins) */}
|
||||||
{hasRole(['admin']) && (
|
{hasRole(['admin']) && (
|
||||||
<div style={{
|
<div style={{
|
||||||
padding: '20px',
|
padding: '20px',
|
||||||
@@ -581,7 +640,7 @@ const EmployeeForm: React.FC<EmployeeFormProps> = ({
|
|||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
border: '1px solid #ffeaa7'
|
border: '1px solid #ffeaa7'
|
||||||
}}>
|
}}>
|
||||||
<h3 style={{ margin: '0 0 15px 0', color: '#856404' }}>⚙️ Systemrolle</h3>
|
<h3 style={{ margin: '0 0 15px 0', color: '#856404' }}>⚙️ Systemrollen</h3>
|
||||||
|
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
||||||
{availableRoles.map(role => (
|
{availableRoles.map(role => (
|
||||||
@@ -591,29 +650,19 @@ const EmployeeForm: React.FC<EmployeeFormProps> = ({
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'flex-start',
|
alignItems: 'flex-start',
|
||||||
padding: '12px',
|
padding: '12px',
|
||||||
border: `2px solid ${formData.role === role.value ? '#f39c12' : '#e0e0e0'}`,
|
border: `2px solid ${formData.roles.includes(role.value) ? '#f39c12' : '#e0e0e0'}`,
|
||||||
borderRadius: '6px',
|
borderRadius: '6px',
|
||||||
backgroundColor: formData.role === role.value ? '#fef9e7' : 'white',
|
backgroundColor: formData.roles.includes(role.value) ? '#fef9e7' : 'white',
|
||||||
cursor: 'pointer'
|
cursor: 'pointer'
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => handleRoleChange(role.value, !formData.roles.includes(role.value))}
|
||||||
setFormData(prev => ({
|
|
||||||
...prev,
|
|
||||||
role: role.value as 'admin' | 'maintenance' | 'user'
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="checkbox"
|
||||||
name="role"
|
name="roles"
|
||||||
value={role.value}
|
value={role.value}
|
||||||
checked={formData.role === role.value}
|
checked={formData.roles.includes(role.value)}
|
||||||
onChange={(e) => {
|
onChange={(e) => handleRoleChange(role.value, e.target.checked)}
|
||||||
setFormData(prev => ({
|
|
||||||
...prev,
|
|
||||||
role: e.target.value as 'admin' | 'maintenance' | 'user'
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
style={{
|
style={{
|
||||||
marginRight: '10px',
|
marginRight: '10px',
|
||||||
marginTop: '2px'
|
marginTop: '2px'
|
||||||
@@ -630,6 +679,9 @@ const EmployeeForm: React.FC<EmployeeFormProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
<div style={{ fontSize: '12px', color: '#7f8c8d', marginTop: '10px' }}>
|
||||||
|
<strong>Hinweis:</strong> Ein Mitarbeiter kann mehrere Rollen haben.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// frontend/src/pages/Employees/components/EmployeeList.tsx
|
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { ROLE_CONFIG, EMPLOYEE_TYPE_CONFIG } from '../../../models/defaults/employeeDefaults';
|
import { ROLE_CONFIG, EMPLOYEE_TYPE_CONFIG } from '../../../models/defaults/employeeDefaults';
|
||||||
import { Employee } from '../../../models/Employee';
|
import { Employee } from '../../../models/Employee';
|
||||||
@@ -33,11 +32,12 @@ const EmployeeList: React.FC<EmployeeListProps> = ({
|
|||||||
|
|
||||||
if (searchTerm) {
|
if (searchTerm) {
|
||||||
const term = searchTerm.toLowerCase();
|
const term = searchTerm.toLowerCase();
|
||||||
|
const fullName = `${employee.firstname} ${employee.lastname}`.toLowerCase();
|
||||||
return (
|
return (
|
||||||
employee.name.toLowerCase().includes(term) ||
|
fullName.includes(term) ||
|
||||||
employee.email.toLowerCase().includes(term) ||
|
employee.email.toLowerCase().includes(term) ||
|
||||||
employee.employeeType.toLowerCase().includes(term) ||
|
employee.employeeType.toLowerCase().includes(term) ||
|
||||||
employee.role.toLowerCase().includes(term)
|
(employee.roles && employee.roles.some(role => role.toLowerCase().includes(term)))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,8 +51,8 @@ const EmployeeList: React.FC<EmployeeListProps> = ({
|
|||||||
|
|
||||||
switch (sortField) {
|
switch (sortField) {
|
||||||
case 'name':
|
case 'name':
|
||||||
aValue = a.name.toLowerCase();
|
aValue = `${a.firstname} ${a.lastname}`.toLowerCase();
|
||||||
bValue = b.name.toLowerCase();
|
bValue = `${b.firstname} ${b.lastname}`.toLowerCase();
|
||||||
break;
|
break;
|
||||||
case 'employeeType':
|
case 'employeeType':
|
||||||
aValue = a.employeeType;
|
aValue = a.employeeType;
|
||||||
@@ -63,8 +63,9 @@ const EmployeeList: React.FC<EmployeeListProps> = ({
|
|||||||
bValue = b.canWorkAlone;
|
bValue = b.canWorkAlone;
|
||||||
break;
|
break;
|
||||||
case 'role':
|
case 'role':
|
||||||
aValue = a.role;
|
// Use the highest role for sorting
|
||||||
bValue = b.role;
|
aValue = getHighestRole(a.roles || []);
|
||||||
|
bValue = getHighestRole(b.roles || []);
|
||||||
break;
|
break;
|
||||||
case 'lastLogin':
|
case 'lastLogin':
|
||||||
// Handle null values for lastLogin (put them at the end)
|
// Handle null values for lastLogin (put them at the end)
|
||||||
@@ -82,6 +83,13 @@ const EmployeeList: React.FC<EmployeeListProps> = ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Helper to get highest role for sorting
|
||||||
|
const getHighestRole = (roles: string[]): string => {
|
||||||
|
if (roles.includes('admin')) return 'admin';
|
||||||
|
if (roles.includes('maintenance')) return 'maintenance';
|
||||||
|
return 'user';
|
||||||
|
};
|
||||||
|
|
||||||
const handleSort = (field: SortField) => {
|
const handleSort = (field: SortField) => {
|
||||||
if (sortField === field) {
|
if (sortField === field) {
|
||||||
// Toggle direction if same field
|
// Toggle direction if same field
|
||||||
@@ -102,23 +110,23 @@ const EmployeeList: React.FC<EmployeeListProps> = ({
|
|||||||
const canDeleteEmployee = (employee: Employee): boolean => {
|
const canDeleteEmployee = (employee: Employee): boolean => {
|
||||||
if (!hasRole(['admin'])) return false;
|
if (!hasRole(['admin'])) return false;
|
||||||
if (employee.id === currentUser?.id) return false;
|
if (employee.id === currentUser?.id) return false;
|
||||||
if (employee.role === 'admin' && !hasRole(['admin'])) return false;
|
if (employee.roles?.includes('admin') && !hasRole(['admin'])) return false;
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const canEditEmployee = (employee: Employee): boolean => {
|
const canEditEmployee = (employee: Employee): boolean => {
|
||||||
if (hasRole(['admin'])) return true;
|
if (hasRole(['admin'])) return true;
|
||||||
if (hasRole(['maintenance'])) {
|
if (hasRole(['maintenance'])) {
|
||||||
return employee.role === 'user' || employee.id === currentUser?.id;
|
return !employee.roles?.includes('admin') || employee.id === currentUser?.id;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Using shared configuration for consistent styling
|
// Using shared configuration for consistent styling
|
||||||
type EmployeeType = typeof EMPLOYEE_TYPE_CONFIG[number]['value'];
|
type EmployeeType = 'manager' | 'trainee' | 'experienced';
|
||||||
|
|
||||||
const getEmployeeTypeBadge = (type: EmployeeType) => {
|
const getEmployeeTypeBadge = (type: EmployeeType) => {
|
||||||
const config = EMPLOYEE_TYPE_CONFIG.find(t => t.value === type)!;
|
const config = EMPLOYEE_TYPE_CONFIG[type];
|
||||||
|
|
||||||
const bgColor =
|
const bgColor =
|
||||||
type === 'manager'
|
type === 'manager'
|
||||||
@@ -142,19 +150,27 @@ const EmployeeList: React.FC<EmployeeListProps> = ({
|
|||||||
: { text: 'Betreuung', color: '#e74c3c', bgColor: '#fadbd8' };
|
: { text: 'Betreuung', color: '#e74c3c', bgColor: '#fadbd8' };
|
||||||
};
|
};
|
||||||
|
|
||||||
type Role = typeof ROLE_CONFIG[number]['value'];
|
type Role = 'admin' | 'maintenance' | 'user';
|
||||||
|
|
||||||
const getRoleBadge = (role: Role) => {
|
const getRoleBadge = (roles: string[] = []) => {
|
||||||
const { label, color } = ROLE_CONFIG.find(r => r.value === role)!;
|
const highestRole = getHighestRole(roles);
|
||||||
|
const { label, color } = ROLE_CONFIG.find(r => r.value === highestRole)!;
|
||||||
|
|
||||||
const bgColor =
|
const bgColor =
|
||||||
role === 'user'
|
highestRole === 'user'
|
||||||
? '#d5f4e6'
|
? '#d5f4e6'
|
||||||
: role === 'maintenance'
|
: highestRole === 'maintenance'
|
||||||
? '#d6eaf8'
|
? '#d6eaf8'
|
||||||
: '#fadbd8'; // admin
|
: '#fadbd8'; // admin
|
||||||
|
|
||||||
return { text: label, color, bgColor };
|
return { text: label, color, bgColor, roles };
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatRoleDisplay = (roles: string[] = []) => {
|
||||||
|
if (roles.length === 0) return 'MITARBEITER';
|
||||||
|
if (roles.includes('admin')) return 'ADMIN';
|
||||||
|
if (roles.includes('maintenance')) return 'INSTANDHALTER';
|
||||||
|
return 'MITARBEITER';
|
||||||
};
|
};
|
||||||
|
|
||||||
if (employees.length === 0) {
|
if (employees.length === 0) {
|
||||||
@@ -282,7 +298,7 @@ const EmployeeList: React.FC<EmployeeListProps> = ({
|
|||||||
{sortedEmployees.map(employee => {
|
{sortedEmployees.map(employee => {
|
||||||
const employeeType = getEmployeeTypeBadge(employee.employeeType);
|
const employeeType = getEmployeeTypeBadge(employee.employeeType);
|
||||||
const independence = getIndependenceBadge(employee.canWorkAlone);
|
const independence = getIndependenceBadge(employee.canWorkAlone);
|
||||||
const roleColor = getRoleBadge(employee.role);
|
const roleInfo = getRoleBadge(employee.roles);
|
||||||
const status = getStatusBadge(employee.isActive);
|
const status = getStatusBadge(employee.isActive);
|
||||||
const canEdit = canEditEmployee(employee);
|
const canEdit = canEditEmployee(employee);
|
||||||
const canDelete = canDeleteEmployee(employee);
|
const canDelete = canDeleteEmployee(employee);
|
||||||
@@ -302,7 +318,7 @@ const EmployeeList: React.FC<EmployeeListProps> = ({
|
|||||||
{/* Name & E-Mail */}
|
{/* Name & E-Mail */}
|
||||||
<div>
|
<div>
|
||||||
<div style={{ fontWeight: 'bold', marginBottom: '4px' }}>
|
<div style={{ fontWeight: 'bold', marginBottom: '4px' }}>
|
||||||
{employee.name}
|
{employee.firstname} {employee.lastname}
|
||||||
{employee.id === currentUser?.id && (
|
{employee.id === currentUser?.id && (
|
||||||
<span style={{
|
<span style={{
|
||||||
marginLeft: '8px',
|
marginLeft: '8px',
|
||||||
@@ -357,8 +373,8 @@ const EmployeeList: React.FC<EmployeeListProps> = ({
|
|||||||
<div style={{ textAlign: 'center' }}>
|
<div style={{ textAlign: 'center' }}>
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: roleColor.bgColor,
|
backgroundColor: roleInfo.bgColor,
|
||||||
color: roleColor.color,
|
color: roleInfo.color,
|
||||||
padding: '6px 12px',
|
padding: '6px 12px',
|
||||||
borderRadius: '15px',
|
borderRadius: '15px',
|
||||||
fontSize: '12px',
|
fontSize: '12px',
|
||||||
@@ -366,9 +382,9 @@ const EmployeeList: React.FC<EmployeeListProps> = ({
|
|||||||
display: 'inline-block',
|
display: 'inline-block',
|
||||||
minWidth: '80px'
|
minWidth: '80px'
|
||||||
}}
|
}}
|
||||||
|
title={employee.roles?.join(', ') || 'user'}
|
||||||
>
|
>
|
||||||
{employee.role === 'admin' ? 'ADMIN' :
|
{formatRoleDisplay(employee.roles)}
|
||||||
employee.role === 'maintenance' ? 'INSTANDHALTER' : 'MITARBEITER'}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -406,7 +422,6 @@ const EmployeeList: React.FC<EmployeeListProps> = ({
|
|||||||
flexWrap: 'wrap'
|
flexWrap: 'wrap'
|
||||||
}}>
|
}}>
|
||||||
{/* Verfügbarkeit Button */}
|
{/* Verfügbarkeit Button */}
|
||||||
{(employee.role === 'admin' || employee.role === 'maintenance' || employee.role === 'user') && (
|
|
||||||
<button
|
<button
|
||||||
onClick={() => onManageAvailability(employee)}
|
onClick={() => onManageAvailability(employee)}
|
||||||
style={{
|
style={{
|
||||||
@@ -424,7 +439,6 @@ const EmployeeList: React.FC<EmployeeListProps> = ({
|
|||||||
>
|
>
|
||||||
📅
|
📅
|
||||||
</button>
|
</button>
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Bearbeiten Button */}
|
{/* Bearbeiten Button */}
|
||||||
{canEdit && (
|
{canEdit && (
|
||||||
@@ -469,7 +483,7 @@ const EmployeeList: React.FC<EmployeeListProps> = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Platzhalter für Symmetrie */}
|
{/* Platzhalter für Symmetrie */}
|
||||||
{!canEdit && !canDelete && (employee.role !== 'admin' && employee.role !== 'maintenance') && (
|
{!canEdit && !canDelete && (
|
||||||
<div style={{ width: '32px', height: '32px' }}></div>
|
<div style={{ width: '32px', height: '32px' }}></div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -319,7 +319,7 @@ const Settings: React.FC = () => {
|
|||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={currentUser.role}
|
value={currentUser.roles}
|
||||||
disabled
|
disabled
|
||||||
style={styles.fieldInputDisabled}
|
style={styles.fieldInputDisabled}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// frontend/src/pages/Setup/Setup.tsx - KORRIGIERT
|
// frontend/src/pages/Setup/Setup.tsx - UPDATED
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useAuth } from '../../contexts/AuthContext';
|
import { useAuth } from '../../contexts/AuthContext';
|
||||||
|
|
||||||
@@ -7,7 +7,8 @@ const Setup: React.FC = () => {
|
|||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
password: '',
|
password: '',
|
||||||
confirmPassword: '',
|
confirmPassword: '',
|
||||||
name: ''
|
firstname: '',
|
||||||
|
lastname: ''
|
||||||
});
|
});
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
@@ -34,8 +35,12 @@ const Setup: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const validateStep2 = () => {
|
const validateStep2 = () => {
|
||||||
if (!formData.name.trim()) {
|
if (!formData.firstname.trim()) {
|
||||||
setError('Bitte geben Sie einen Namen ein.');
|
setError('Bitte geben Sie einen Vornamen ein.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!formData.lastname.trim()) {
|
||||||
|
setError('Bitte geben Sie einen Nachnamen ein.');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -62,10 +67,11 @@ const Setup: React.FC = () => {
|
|||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
password: formData.password,
|
password: formData.password,
|
||||||
name: formData.name
|
firstname: formData.firstname,
|
||||||
|
lastname: formData.lastname
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('🚀 Sending setup request...');
|
console.log('🚀 Sending setup request...', payload);
|
||||||
|
|
||||||
const response = await fetch('http://localhost:3002/api/setup/admin', {
|
const response = await fetch('http://localhost:3002/api/setup/admin', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -94,6 +100,17 @@ const Setup: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Helper to display generated email preview
|
||||||
|
const getEmailPreview = () => {
|
||||||
|
if (!formData.firstname.trim() || !formData.lastname.trim()) {
|
||||||
|
return 'vorname.nachname@sp.de';
|
||||||
|
}
|
||||||
|
|
||||||
|
const cleanFirstname = formData.firstname.toLowerCase().replace(/[^a-z0-9]/g, '');
|
||||||
|
const cleanLastname = formData.lastname.toLowerCase().replace(/[^a-z0-9]/g, '');
|
||||||
|
return `${cleanFirstname}.${cleanLastname}@sp.de`;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{
|
<div style={{
|
||||||
minHeight: '100vh',
|
minHeight: '100vh',
|
||||||
@@ -144,34 +161,6 @@ const Setup: React.FC = () => {
|
|||||||
|
|
||||||
{step === 1 && (
|
{step === 1 && (
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '1.5rem' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '1.5rem' }}>
|
||||||
<div>
|
|
||||||
<label style={{
|
|
||||||
display: 'block',
|
|
||||||
marginBottom: '0.5rem',
|
|
||||||
fontWeight: '600',
|
|
||||||
color: '#495057'
|
|
||||||
}}>
|
|
||||||
Administrator E-Mail
|
|
||||||
</label>
|
|
||||||
<div style={{
|
|
||||||
padding: '0.75rem',
|
|
||||||
backgroundColor: '#e9ecef',
|
|
||||||
border: '1px solid #ced4da',
|
|
||||||
borderRadius: '6px',
|
|
||||||
color: '#495057',
|
|
||||||
fontWeight: '500'
|
|
||||||
}}>
|
|
||||||
admin@instandhaltung.de
|
|
||||||
</div>
|
|
||||||
<div style={{
|
|
||||||
fontSize: '0.875rem',
|
|
||||||
color: '#6c757d',
|
|
||||||
marginTop: '0.25rem'
|
|
||||||
}}>
|
|
||||||
Diese E-Mail wird für den Administrator-Account verwendet
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label style={{
|
<label style={{
|
||||||
display: 'block',
|
display: 'block',
|
||||||
@@ -237,12 +226,12 @@ const Setup: React.FC = () => {
|
|||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
color: '#495057'
|
color: '#495057'
|
||||||
}}>
|
}}>
|
||||||
Vollständiger Name
|
Vorname
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="name"
|
name="firstname"
|
||||||
value={formData.name}
|
value={formData.firstname}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@@ -251,10 +240,65 @@ const Setup: React.FC = () => {
|
|||||||
borderRadius: '6px',
|
borderRadius: '6px',
|
||||||
fontSize: '1rem'
|
fontSize: '1rem'
|
||||||
}}
|
}}
|
||||||
placeholder="Max Mustermann"
|
placeholder="Max"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label style={{
|
||||||
|
display: 'block',
|
||||||
|
marginBottom: '0.5rem',
|
||||||
|
fontWeight: '600',
|
||||||
|
color: '#495057'
|
||||||
|
}}>
|
||||||
|
Nachname
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="lastname"
|
||||||
|
value={formData.lastname}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
padding: '0.75rem',
|
||||||
|
border: '1px solid #ced4da',
|
||||||
|
borderRadius: '6px',
|
||||||
|
fontSize: '1rem'
|
||||||
|
}}
|
||||||
|
placeholder="Mustermann"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label style={{
|
||||||
|
display: 'block',
|
||||||
|
marginBottom: '0.5rem',
|
||||||
|
fontWeight: '600',
|
||||||
|
color: '#495057'
|
||||||
|
}}>
|
||||||
|
Automatisch generierte E-Mail
|
||||||
|
</label>
|
||||||
|
<div style={{
|
||||||
|
padding: '0.75rem',
|
||||||
|
backgroundColor: '#e9ecef',
|
||||||
|
border: '1px solid #ced4da',
|
||||||
|
borderRadius: '6px',
|
||||||
|
color: '#495057',
|
||||||
|
fontWeight: '500',
|
||||||
|
fontFamily: 'monospace'
|
||||||
|
}}>
|
||||||
|
{getEmailPreview()}
|
||||||
|
</div>
|
||||||
|
<div style={{
|
||||||
|
fontSize: '0.875rem',
|
||||||
|
color: '#6c757d',
|
||||||
|
marginTop: '0.25rem'
|
||||||
|
}}>
|
||||||
|
Die E-Mail wird automatisch aus Vor- und Nachname generiert
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -315,7 +359,7 @@ const Setup: React.FC = () => {
|
|||||||
border: '1px solid #b6d7e8'
|
border: '1px solid #b6d7e8'
|
||||||
}}>
|
}}>
|
||||||
💡 Nach dem erfolgreichen Setup werden Sie zur Anmeldeseite weitergeleitet,
|
💡 Nach dem erfolgreichen Setup werden Sie zur Anmeldeseite weitergeleitet,
|
||||||
wo Sie sich mit Ihren Zugangsdaten anmelden können.
|
wo Sie sich mit Ihrer automatisch generierten E-Mail anmelden können.
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user