mirror of
https://github.com/donpat1to/Schichtenplaner.git
synced 2025-12-01 06:55:45 +01:00
added Validation rules
This commit is contained in:
25
backend/src/middleware/Validation/Access.md
Normal file
25
backend/src/middleware/Validation/Access.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
## User Settings
|
||||||
|
|
||||||
|
### \[UPDATE\] Personal availability
|
||||||
|
* Only the employee themselves can manage their availability
|
||||||
|
* Must select a valid shift plan with defined shifts
|
||||||
|
* All changes require explicit save action
|
||||||
|
|
||||||
|
### \[VIEW\] ShiftPlan assignments
|
||||||
|
* Published plans show actual assignments
|
||||||
|
* Draft plans show preview assignments (if calculated)
|
||||||
|
* Regular users can only view, not modify assignments
|
||||||
|
|
||||||
|
## System-wide
|
||||||
|
|
||||||
|
### \[ACCESS\] Role-based restrictions
|
||||||
|
* `admin`: Full access to all features
|
||||||
|
* `maintenance`: Access to shift plans and employee management (except admin users)
|
||||||
|
* `user`: Read-only access to shift plans, can manage own availability and profile
|
||||||
|
|
||||||
|
### \[DATA\] Validation rules
|
||||||
|
* Email addresses are automatically generated from firstname/lastname
|
||||||
|
* Employee status (`isActive`) controls login and planning eligibility
|
||||||
|
* Trainee status affects independence (`canWorkAlone`) automatically
|
||||||
|
* Date ranges must be valid (start before end)
|
||||||
|
* All required fields must be filled before form submission
|
||||||
20
backend/src/middleware/Validation/Assignment.md
Normal file
20
backend/src/middleware/Validation/Assignment.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
## Shift Assignment
|
||||||
|
|
||||||
|
### \[ACTION: update scheduled shift\]
|
||||||
|
* Requires valid scheduled shift ID
|
||||||
|
* Only updates assignedEmployees array
|
||||||
|
* Requires authentication with valid token
|
||||||
|
* Handles both JSON and non-JSON responses
|
||||||
|
|
||||||
|
### \[ACTION: assign shifts automatically\]
|
||||||
|
* Requires shift plan, employees, and availabilities
|
||||||
|
* Availability preferenceLevel must be 1, 2, or 3
|
||||||
|
* Constraints must be an array (converts non-array to empty array)
|
||||||
|
* All employees must have valid availability data
|
||||||
|
|
||||||
|
### \[ACTION: get scheduled shifts\]
|
||||||
|
* Requires valid plan ID
|
||||||
|
* Automatically fixes data structure inconsistencies:
|
||||||
|
- timeSlotId mapping (handles both naming conventions)
|
||||||
|
- requiredEmployees fallback to 2 if missing
|
||||||
|
- assignedEmployees fallback to empty array if missing
|
||||||
16
backend/src/middleware/Validation/Authentication.md
Normal file
16
backend/src/middleware/Validation/Authentication.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
## Authentication
|
||||||
|
|
||||||
|
### \[ACTION: login\]
|
||||||
|
* Requires valid email and password format
|
||||||
|
* Server validates credentials before issuing token
|
||||||
|
* Token and employee data stored in localStorage upon success
|
||||||
|
|
||||||
|
### \[ACTION: register\]
|
||||||
|
* Requires email, password, and name
|
||||||
|
* Role is optional during registration
|
||||||
|
* Automatically logs in user after successful registration
|
||||||
|
|
||||||
|
### \[ACTION: access protected resources\]
|
||||||
|
* Requires valid JWT token in Authorization header
|
||||||
|
* Token is automatically retrieved from localStorage
|
||||||
|
* Unauthorized requests (401) trigger automatic logout
|
||||||
23
backend/src/middleware/Validation/DataIntegrity
Normal file
23
backend/src/middleware/Validation/DataIntegrity
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
## Data Integrity
|
||||||
|
|
||||||
|
### \[GENERAL\] API communication
|
||||||
|
* All fetch requests include error handling
|
||||||
|
* Failed responses throw descriptive errors
|
||||||
|
* Token validation before protected operations
|
||||||
|
* Automatic localStorage cleanup on logout
|
||||||
|
|
||||||
|
### \[GENERAL\] data persistence
|
||||||
|
* Employee data cached in localStorage after login
|
||||||
|
* Token automatically retrieved from localStorage
|
||||||
|
* Data structure normalization for scheduled shifts
|
||||||
|
|
||||||
|
### \[GENERAL\] error handling
|
||||||
|
* Network errors are caught and logged
|
||||||
|
* HTTP errors include status codes and messages
|
||||||
|
* Failed authentication triggers cleanup and logout
|
||||||
|
|
||||||
|
## Role & Permission Notes
|
||||||
|
* The frontend services don't explicitly restrict actions by role
|
||||||
|
* Role-based restrictions are likely handled by the backend
|
||||||
|
* Frontend assumes user has permissions for requested operations
|
||||||
|
* 401 responses indicate insufficient permissions at backend level
|
||||||
67
backend/src/middleware/Validation/Employee.md
Normal file
67
backend/src/middleware/Validation/Employee.md
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
## Employee Management
|
||||||
|
|
||||||
|
### \[CREATE/UPDATE\] employee
|
||||||
|
* All employee operations require authentication
|
||||||
|
* Password changes require current password + new password
|
||||||
|
* Only authenticated users can create/update employees
|
||||||
|
|
||||||
|
### \[ACTION: delete employee\]
|
||||||
|
* Requires authentication
|
||||||
|
* Server validates permissions before deletion
|
||||||
|
|
||||||
|
### \[ACTION: update availability\]
|
||||||
|
* Requires employee ID and plan ID
|
||||||
|
* Availability updates must include valid preference levels
|
||||||
|
* Only authenticated users can update availabilities
|
||||||
|
|
||||||
|
### \[ACTION: update last login\]
|
||||||
|
* Requires employee ID
|
||||||
|
* Fails silently if update fails (logs error but doesn't block user)
|
||||||
|
|
||||||
|
## Employee
|
||||||
|
|
||||||
|
### \[CREATE\] Employee
|
||||||
|
* `firstname` must not be empty
|
||||||
|
* `lastname` must not be empty
|
||||||
|
* `password` must be at least 6 characters (in create mode)
|
||||||
|
* `employeeType` must be selected
|
||||||
|
* Contract type validation:
|
||||||
|
* `manager`, `apprentice` => `contractType` = flexible
|
||||||
|
* `guest` => `contractType` = undefined/NONE
|
||||||
|
* `personell` => `contractType` = small || large
|
||||||
|
|
||||||
|
### \[UPDATE\] Employee profile
|
||||||
|
* `firstname` must not be empty
|
||||||
|
* `lastname` must not be empty
|
||||||
|
* Only the employee themselves or admins can update
|
||||||
|
|
||||||
|
### \[UPDATE\] Employee password
|
||||||
|
* `newPassword` must be at least 6 characters
|
||||||
|
* `newPassword` must match `confirmPassword`
|
||||||
|
* For admin password reset: no `currentPassword` required
|
||||||
|
* For self-password change: `currentPassword` required
|
||||||
|
|
||||||
|
### \[UPDATE\] Employee roles
|
||||||
|
* Only users with role 'admin' can modify roles
|
||||||
|
* At least one employee must maintain 'admin' role
|
||||||
|
* Users cannot remove their own admin role
|
||||||
|
|
||||||
|
### \[UPDATE\] Employee availability
|
||||||
|
* Only active employees can set availability
|
||||||
|
* Contract type requirements:
|
||||||
|
* `small` contract: minimum 2 available shifts (preference level 1 or 2)
|
||||||
|
* `large` contract: minimum 3 available shifts (preference level 1 or 2)
|
||||||
|
* `flexible` contract: no minimum requirement
|
||||||
|
* Availability can only be set for valid shift patterns in selected plan
|
||||||
|
* `shiftId` must be valid and exist in the current plan
|
||||||
|
|
||||||
|
### \[ACTION: delete\] Employee
|
||||||
|
* Only users with role 'admin' can delete employees
|
||||||
|
* Cannot delete yourself
|
||||||
|
* Cannot delete the last admin user
|
||||||
|
* User confirmation required before deletion
|
||||||
|
|
||||||
|
### \[ACTION: edit\] Employee
|
||||||
|
* Admins can edit all employees
|
||||||
|
* Maintenance users can edit non-admin employees or themselves
|
||||||
|
* Regular users can only edit themselves
|
||||||
66
backend/src/middleware/Validation/Shiftplan.md
Normal file
66
backend/src/middleware/Validation/Shiftplan.md
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
## Shift Plan Management
|
||||||
|
|
||||||
|
### \[CREATE\] shift plan
|
||||||
|
* All operations require authentication
|
||||||
|
* 401 responses trigger automatic logout
|
||||||
|
* Scheduled shifts array is guaranteed to exist (empty array if none)
|
||||||
|
|
||||||
|
### \[CREATE\] shift plan from preset
|
||||||
|
* presetName must match existing TEMPLATE_PRESETS
|
||||||
|
* Requires name, startDate, and endDate
|
||||||
|
* isTemplate is optional (defaults to false)
|
||||||
|
|
||||||
|
### \[UPDATE\] shift plan
|
||||||
|
* Requires valid shift plan ID
|
||||||
|
* Partial updates allowed
|
||||||
|
* Authentication required
|
||||||
|
|
||||||
|
### \[ACTION: delete shift plan\]
|
||||||
|
* Requires authentication
|
||||||
|
* 401 responses trigger automatic logout
|
||||||
|
|
||||||
|
### \[ACTION: regenerate scheduled shifts\]
|
||||||
|
* Requires valid plan ID
|
||||||
|
* Authentication required
|
||||||
|
* Fails silently if regeneration fails (logs error but continues)
|
||||||
|
|
||||||
|
### \[ACTION: clear assignments\]
|
||||||
|
* Requires valid plan ID
|
||||||
|
* Authentication required
|
||||||
|
* Clears all employee assignments from scheduled shifts
|
||||||
|
|
||||||
|
## ShiftPlan
|
||||||
|
|
||||||
|
### \[CREATE\] ShiftPlan from template
|
||||||
|
* `planName` must not be empty
|
||||||
|
* `startDate` must be set
|
||||||
|
* `endDate` must be set
|
||||||
|
* `endDate` must be after `startDate`
|
||||||
|
* `selectedPreset` must be chosen (template must be selected)
|
||||||
|
* Only available template presets can be used
|
||||||
|
|
||||||
|
### \[ACTION: publish\] ShiftPlan
|
||||||
|
* Plan must be in 'draft' status
|
||||||
|
* All active employees must have set their availabilities for the plan
|
||||||
|
* Only users with roles \['admin', 'maintenance'\] can publish
|
||||||
|
* Assignment algorithm must not have critical violations (ERROR or ❌ KRITISCH)
|
||||||
|
* employee && employee.contract_type === small => mind. 1 mal availability === 1 || availability === 2
|
||||||
|
* employee && employee.contract_type === large => mind. 3 mal availability === 1 || availability === 2
|
||||||
|
|
||||||
|
### \[ACTION: recreate assignments\]
|
||||||
|
* Plan must be in 'published' status
|
||||||
|
* Only users with roles \['admin', 'maintenance'\] can recreate
|
||||||
|
* User confirmation required before clearing all assignments
|
||||||
|
|
||||||
|
### \[ACTION: delete\] ShiftPlan
|
||||||
|
* Only users with roles \['admin', 'maintenance'\] can delete
|
||||||
|
* User confirmation required before deletion
|
||||||
|
|
||||||
|
### \[ACTION: edit\] ShiftPlan
|
||||||
|
* Only users with roles \['admin', 'maintenance'\] can edit
|
||||||
|
* Can only edit plans in 'draft' status
|
||||||
|
|
||||||
|
### \[UPDATE\] ShiftPlan shifts
|
||||||
|
* `timeSlotId` must be selected from available time slots
|
||||||
|
* `requiredEmployees` must be at least 1
|
||||||
|
* `dayOfWeek` must be between 1-7
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,5 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useAuth } from '../../contexts/AuthContext';
|
import { useAuth } from '../../contexts/AuthContext';
|
||||||
import StepSetup, { Step } from '../../components/StepSetup/StepSetup';
|
|
||||||
|
|
||||||
const API_BASE_URL = '/api';
|
const API_BASE_URL = '/api';
|
||||||
|
|
||||||
@@ -32,16 +31,16 @@ const useSetup = () => {
|
|||||||
const { checkSetupStatus } = useAuth();
|
const { checkSetupStatus } = useAuth();
|
||||||
|
|
||||||
const steps: SetupStep[] = [
|
const steps: SetupStep[] = [
|
||||||
{
|
|
||||||
id: 'password-setup',
|
|
||||||
title: 'Passwort erstellen',
|
|
||||||
subtitle: 'Legen Sie ein sicheres Passwort fest'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: 'profile-setup',
|
id: 'profile-setup',
|
||||||
title: 'Profilinformationen',
|
title: 'Profilinformationen',
|
||||||
subtitle: 'Geben Sie Ihre persönlichen Daten ein'
|
subtitle: 'Geben Sie Ihre persönlichen Daten ein'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'password-setup',
|
||||||
|
title: 'Passwort erstellen',
|
||||||
|
subtitle: 'Legen Sie ein sicheres Passwort fest'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'confirmation',
|
id: 'confirmation',
|
||||||
title: 'Bestätigung',
|
title: 'Bestätigung',
|
||||||
@@ -51,18 +50,6 @@ const useSetup = () => {
|
|||||||
|
|
||||||
// ===== VALIDIERUNGS-FUNKTIONEN =====
|
// ===== VALIDIERUNGS-FUNKTIONEN =====
|
||||||
const validateStep1 = (): boolean => {
|
const validateStep1 = (): boolean => {
|
||||||
if (formData.password.length < 6) {
|
|
||||||
setError('Das Passwort muss mindestens 6 Zeichen lang sein.');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (formData.password !== formData.confirmPassword) {
|
|
||||||
setError('Die Passwörter stimmen nicht überein.');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const validateStep2 = (): boolean => {
|
|
||||||
if (!formData.firstname.trim()) {
|
if (!formData.firstname.trim()) {
|
||||||
setError('Bitte geben Sie einen Vornamen ein.');
|
setError('Bitte geben Sie einen Vornamen ein.');
|
||||||
return false;
|
return false;
|
||||||
@@ -74,6 +61,18 @@ const useSetup = () => {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const validateStep2 = (): boolean => {
|
||||||
|
if (formData.password.length < 6) {
|
||||||
|
setError('Das Passwort muss mindestens 6 Zeichen lang sein.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (formData.password !== formData.confirmPassword) {
|
||||||
|
setError('Die Passwörter stimmen nicht überein.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
const validateCurrentStep = (stepIndex: number): boolean => {
|
const validateCurrentStep = (stepIndex: number): boolean => {
|
||||||
switch (stepIndex) {
|
switch (stepIndex) {
|
||||||
case 0:
|
case 0:
|
||||||
@@ -225,69 +224,6 @@ interface StepContentProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Step1Content: React.FC<StepContentProps> = ({
|
const Step1Content: React.FC<StepContentProps> = ({
|
||||||
formData,
|
|
||||||
onInputChange
|
|
||||||
}) => (
|
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '1.5rem' }}>
|
|
||||||
<div>
|
|
||||||
<label style={{
|
|
||||||
display: 'block',
|
|
||||||
marginBottom: '0.5rem',
|
|
||||||
fontWeight: '600',
|
|
||||||
color: '#495057'
|
|
||||||
}}>
|
|
||||||
Passwort
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
name="password"
|
|
||||||
value={formData.password}
|
|
||||||
onChange={onInputChange}
|
|
||||||
style={{
|
|
||||||
width: '100%',
|
|
||||||
padding: '0.75rem',
|
|
||||||
border: '1px solid #ced4da',
|
|
||||||
borderRadius: '6px',
|
|
||||||
fontSize: '1rem',
|
|
||||||
transition: 'border-color 0.3s ease'
|
|
||||||
}}
|
|
||||||
placeholder="Mindestens 6 Zeichen"
|
|
||||||
required
|
|
||||||
autoComplete="new-password"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label style={{
|
|
||||||
display: 'block',
|
|
||||||
marginBottom: '0.5rem',
|
|
||||||
fontWeight: '600',
|
|
||||||
color: '#495057'
|
|
||||||
}}>
|
|
||||||
Passwort bestätigen
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
name="confirmPassword"
|
|
||||||
value={formData.confirmPassword}
|
|
||||||
onChange={onInputChange}
|
|
||||||
style={{
|
|
||||||
width: '100%',
|
|
||||||
padding: '0.75rem',
|
|
||||||
border: '1px solid #ced4da',
|
|
||||||
borderRadius: '6px',
|
|
||||||
fontSize: '1rem',
|
|
||||||
transition: 'border-color 0.3s ease'
|
|
||||||
}}
|
|
||||||
placeholder="Passwort wiederholen"
|
|
||||||
required
|
|
||||||
autoComplete="new-password"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const Step2Content: React.FC<StepContentProps> = ({
|
|
||||||
formData,
|
formData,
|
||||||
onInputChange,
|
onInputChange,
|
||||||
getEmailPreview
|
getEmailPreview
|
||||||
@@ -378,6 +314,70 @@ const Step2Content: React.FC<StepContentProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
const Step2Content: React.FC<StepContentProps> = ({
|
||||||
|
formData,
|
||||||
|
onInputChange
|
||||||
|
}) => (
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '1.5rem' }}>
|
||||||
|
<div>
|
||||||
|
<label style={{
|
||||||
|
display: 'block',
|
||||||
|
marginBottom: '0.5rem',
|
||||||
|
fontWeight: '600',
|
||||||
|
color: '#495057'
|
||||||
|
}}>
|
||||||
|
Passwort
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
name="password"
|
||||||
|
value={formData.password}
|
||||||
|
onChange={onInputChange}
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
padding: '0.75rem',
|
||||||
|
border: '1px solid #ced4da',
|
||||||
|
borderRadius: '6px',
|
||||||
|
fontSize: '1rem',
|
||||||
|
transition: 'border-color 0.3s ease'
|
||||||
|
}}
|
||||||
|
placeholder="Mindestens 6 Zeichen"
|
||||||
|
required
|
||||||
|
autoComplete="new-password"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label style={{
|
||||||
|
display: 'block',
|
||||||
|
marginBottom: '0.5rem',
|
||||||
|
fontWeight: '600',
|
||||||
|
color: '#495057'
|
||||||
|
}}>
|
||||||
|
Passwort bestätigen
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
name="confirmPassword"
|
||||||
|
value={formData.confirmPassword}
|
||||||
|
onChange={onInputChange}
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
padding: '0.75rem',
|
||||||
|
border: '1px solid #ced4da',
|
||||||
|
borderRadius: '6px',
|
||||||
|
fontSize: '1rem',
|
||||||
|
transition: 'border-color 0.3s ease'
|
||||||
|
}}
|
||||||
|
placeholder="Passwort wiederholen"
|
||||||
|
required
|
||||||
|
autoComplete="new-password"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
const Step3Content: React.FC<StepContentProps> = ({
|
const Step3Content: React.FC<StepContentProps> = ({
|
||||||
formData,
|
formData,
|
||||||
getEmailPreview
|
getEmailPreview
|
||||||
@@ -477,6 +477,83 @@ const Setup: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Inline Step Indicator Komponente
|
||||||
|
const StepIndicator: React.FC = () => (
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: '2.5rem',
|
||||||
|
position: 'relative',
|
||||||
|
width: '100%'
|
||||||
|
}}>
|
||||||
|
{/* Verbindungslinien */}
|
||||||
|
<div style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '12px',
|
||||||
|
left: '0',
|
||||||
|
right: '0',
|
||||||
|
height: '2px',
|
||||||
|
backgroundColor: '#e9ecef',
|
||||||
|
zIndex: 1
|
||||||
|
}} />
|
||||||
|
|
||||||
|
{steps.map((step, index) => {
|
||||||
|
const isCompleted = index < currentStep;
|
||||||
|
const isCurrent = index === currentStep;
|
||||||
|
const isClickable = index <= currentStep + 1;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={step.id}
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
zIndex: 2,
|
||||||
|
position: 'relative',
|
||||||
|
flex: 1
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
onClick={() => isClickable && handleStepChange(index)}
|
||||||
|
disabled={!isClickable}
|
||||||
|
style={{
|
||||||
|
width: '28px',
|
||||||
|
height: '28px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
border: '2px solid',
|
||||||
|
borderColor: isCompleted || isCurrent ? '#51258f' : '#e9ecef',
|
||||||
|
backgroundColor: isCompleted ? '#51258f' : 'white',
|
||||||
|
color: isCompleted ? 'white' : (isCurrent ? '#51258f' : '#6c757d'),
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
fontSize: '14px',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
cursor: isClickable ? 'pointer' : 'not-allowed',
|
||||||
|
transition: 'all 0.3s ease',
|
||||||
|
marginBottom: '8px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{index + 1}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div style={{ textAlign: 'center' }}>
|
||||||
|
<div style={{
|
||||||
|
fontSize: '14px',
|
||||||
|
fontWeight: isCurrent ? '600' : '400',
|
||||||
|
color: isCurrent ? '#51258f' : '#6c757d'
|
||||||
|
}}>
|
||||||
|
{step.title}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{
|
<div style={{
|
||||||
minHeight: '100vh',
|
minHeight: '100vh',
|
||||||
@@ -533,18 +610,8 @@ const Setup: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* StepSetup Komponente - nur die Steps ohne Beschreibung */}
|
{/* Inline Step Indicator */}
|
||||||
<div style={{ marginBottom: '2.5rem' }}>
|
<StepIndicator />
|
||||||
<StepSetup
|
|
||||||
steps={steps.map(step => ({ ...step, subtitle: undefined }))}
|
|
||||||
current={currentStep}
|
|
||||||
onChange={handleStepChange}
|
|
||||||
orientation="horizontal"
|
|
||||||
clickable={true}
|
|
||||||
size="md"
|
|
||||||
animated={true}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Fehleranzeige */}
|
{/* Fehleranzeige */}
|
||||||
{error && (
|
{error && (
|
||||||
|
|||||||
Reference in New Issue
Block a user