removed debug infor

This commit is contained in:
2025-10-21 14:32:08 +02:00
parent 59b09b663e
commit b3a1c5ff59
13 changed files with 443 additions and 226 deletions

View File

@@ -0,0 +1,32 @@
{
"timestamp": "2025-10-21T11:16:11.693081",
"success": true,
"metadata": {
"solveTime": 0.0218085,
"constraintsAdded": 217,
"variablesCreated": 90,
"optimal": true
},
"progress": [
{
"timestamp": 0.00886988639831543,
"objective": 1050.0,
"bound": 2100.0,
"solution_count": 1
},
{
"timestamp": 0.009304285049438477,
"objective": 1300.0,
"bound": 1300.0,
"solution_count": 2
}
],
"solution_summary": {
"assignments_count": 15,
"violations_count": 0,
"variables_count": 90,
"constraints_count": 217,
"solve_time": 0.0218085,
"optimal": true
}
}

View File

@@ -0,0 +1,38 @@
{
"timestamp": "2025-10-21T11:16:16.813066",
"success": true,
"metadata": {
"solveTime": 0.0158702,
"constraintsAdded": 217,
"variablesCreated": 90,
"optimal": true
},
"progress": [
{
"timestamp": 0.008541107177734375,
"objective": 1050.0,
"bound": 2000.0,
"solution_count": 1
},
{
"timestamp": 0.00941777229309082,
"objective": 1250.0,
"bound": 1300.0,
"solution_count": 2
},
{
"timestamp": 0.009499549865722656,
"objective": 1300.0,
"bound": 1300.0,
"solution_count": 3
}
],
"solution_summary": {
"assignments_count": 15,
"violations_count": 0,
"variables_count": 90,
"constraints_count": 217,
"solve_time": 0.0158702,
"optimal": true
}
}

View File

@@ -0,0 +1,32 @@
{
"timestamp": "2025-10-21T10:54:26.093544",
"success": false,
"metadata": {
"solveTime": 0,
"constraintsAdded": 0,
"variablesCreated": 0,
"optimal": false
},
"progress": [],
"solution_summary": {
"assignments_count": 0,
"violations_count": 1,
"variables_count": 0,
"constraints_count": 0,
"solve_time": 0,
"optimal": false
},
"full_result": {
"assignments": [],
"violations": [
"Error: 'SolutionCallback' object has no attribute 'HasObjective'"
],
"success": false,
"metadata": {
"solveTime": 0,
"constraintsAdded": 0,
"variablesCreated": 0,
"optimal": false
}
}
}

View File

@@ -0,0 +1,19 @@
{
"timestamp": "2025-10-21T10:59:36.646855",
"success": false,
"metadata": {
"solveTime": 0,
"constraintsAdded": 0,
"variablesCreated": 0,
"optimal": false
},
"progress": [],
"solution_summary": {
"assignments_count": 0,
"violations_count": 1,
"variables_count": 0,
"constraints_count": 0,
"solve_time": 0,
"optimal": false
}
}

View File

@@ -0,0 +1,38 @@
{
"timestamp": "2025-10-21T11:03:36.697986",
"success": true,
"metadata": {
"solveTime": 0.025875500000000003,
"constraintsAdded": 217,
"variablesCreated": 90,
"optimal": true
},
"progress": [
{
"timestamp": 0.008769989013671875,
"objective": 1050.0,
"bound": 2000.0,
"solution_count": 1
},
{
"timestamp": 0.009685516357421875,
"objective": 1250.0,
"bound": 1700.0,
"solution_count": 2
},
{
"timestamp": 0.010709047317504883,
"objective": 1300.0,
"bound": 1300.0,
"solution_count": 3
}
],
"solution_summary": {
"assignments_count": 15,
"violations_count": 0,
"variables_count": 90,
"constraints_count": 217,
"solve_time": 0.025875500000000003,
"optimal": true
}
}

View File

@@ -3,8 +3,151 @@ from ortools.sat.python import cp_model
import json import json
import sys import sys
import re import re
import os
import logging
import time
from pathlib import Path
from datetime import datetime, timedelta
from collections import defaultdict from collections import defaultdict
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class ProductionProgressManager:
def __init__(self, script_dir, max_files=1000, retention_days=7):
self.script_dir = Path(script_dir)
self.progress_dir = self.script_dir / "progress_data"
self.max_files = max_files
self.retention_days = retention_days
self._ensure_directory()
self._cleanup_old_files()
def _ensure_directory(self):
"""Create progress directory if it doesn't exist"""
try:
self.progress_dir.mkdir(exist_ok=True)
# Set secure permissions (read/write for owner only)
self.progress_dir.chmod(0o700)
except Exception as e:
logger.warning(f"Could not create progress directory: {e}")
def _cleanup_old_files(self):
"""Remove old progress files based on retention policy"""
try:
cutoff_time = datetime.now() - timedelta(days=self.retention_days)
files = list(self.progress_dir.glob("run_*.json"))
# Sort by modification time and remove oldest if over limit
if len(files) > self.max_files:
files.sort(key=lambda x: x.stat().st_mtime)
for file_to_delete in files[:len(files) - self.max_files]:
file_to_delete.unlink()
logger.info(f"Cleaned up old progress file: {file_to_delete}")
# Remove files older than retention period
for file_path in files:
if datetime.fromtimestamp(file_path.stat().st_mtime) < cutoff_time:
file_path.unlink()
logger.info(f"Removed expired progress file: {file_path}")
except Exception as e:
logger.warning(f"Progress cleanup failed: {e}")
def save_progress(self, result, progress_data):
"""Safely save progress data with production considerations"""
try:
# Check disk space before writing (min 100MB free)
if not self._check_disk_space():
logger.warning("Insufficient disk space, skipping progress save")
return None
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
success_status = "success" if result.get('success', False) else "failure"
filename = f"run_{timestamp}_{success_status}.json"
filepath = self.progress_dir / filename
# Prepare safe data (exclude sensitive information)
safe_data = {
'timestamp': datetime.now().isoformat(),
'success': result.get('success', False),
'metadata': result.get('metadata', {}),
'progress': progress_data,
'solution_summary': {
'assignments_count': len(result.get('assignments', [])),
'violations_count': len(result.get('violations', [])),
'variables_count': result.get('metadata', {}).get('variablesCreated', 0),
'constraints_count': result.get('metadata', {}).get('constraintsAdded', 0),
'solve_time': result.get('metadata', {}).get('solveTime', 0),
'optimal': result.get('metadata', {}).get('optimal', False)
}
# ❌ REMOVED: 'full_result' containing potentially sensitive data
}
# Atomic write with temporary file
temp_filepath = filepath.with_suffix('.tmp')
with open(temp_filepath, 'w', encoding='utf-8') as f:
json.dump(safe_data, f, indent=2, ensure_ascii=False)
# Atomic rename
temp_filepath.rename(filepath)
# Set secure file permissions
filepath.chmod(0o600)
logger.info(f"Progress data saved: {filename}")
return str(filepath)
except Exception as e:
logger.error(f"Failed to save progress data: {e}")
return None
def _check_disk_space(self, min_free_mb=100):
"""Check if there's sufficient disk space"""
try:
stat = os.statvfs(self.progress_dir)
free_mb = (stat.f_bavail * stat.f_frsize) / (1024 * 1024)
return free_mb >= min_free_mb
except:
return True # Continue if we can't check disk space
class SimpleSolutionCallback(cp_model.CpSolverSolutionCallback):
"""A simplified callback that only counts solutions"""
def __init__(self):
cp_model.CpSolverSolutionCallback.__init__(self)
self.__solution_count = 0
self.start_time = time.time()
self.solutions = []
def on_solution_callback(self):
current_time = time.time() - self.start_time
self.__solution_count += 1
# Try to get objective value safely
try:
objective_value = self.ObjectiveValue()
except:
objective_value = 0
# Try to get bound safely
try:
best_bound = self.BestObjectiveBound()
except:
best_bound = 0
solution_info = {
'timestamp': current_time,
'objective': objective_value,
'bound': best_bound,
'solution_count': self.__solution_count
}
self.solutions.append(solution_info)
print(f"Progress: Solution {self.__solution_count}, Objective: {objective_value}, Time: {current_time:.2f}s", file=sys.stderr)
def solution_count(self):
return self.__solution_count
class UniversalSchedulingSolver: class UniversalSchedulingSolver:
def __init__(self): def __init__(self):
self.model = cp_model.CpModel() self.model = cp_model.CpModel()
@@ -12,6 +155,14 @@ class UniversalSchedulingSolver:
self.solver.parameters.max_time_in_seconds = 30 self.solver.parameters.max_time_in_seconds = 30
self.solver.parameters.num_search_workers = 8 self.solver.parameters.num_search_workers = 8
self.solver.parameters.log_search_progress = False self.solver.parameters.log_search_progress = False
# 🆕 Initialize production-safe progress manager
script_dir = os.path.dirname(os.path.abspath(__file__))
self.progress_manager = ProductionProgressManager(
script_dir=script_dir,
max_files=1000, # Keep last 1000 runs
retention_days=7 # Keep files for 7 days
)
def solve_from_model_data(self, model_data): def solve_from_model_data(self, model_data):
"""Solve from pre-built model data (variables, constraints, objective)""" """Solve from pre-built model data (variables, constraints, objective)"""
@@ -48,36 +199,70 @@ class UniversalSchedulingSolver:
# Add a default objective if main objective fails # Add a default objective if main objective fails
self.model.Maximize(sum(cp_vars.values())) self.model.Maximize(sum(cp_vars.values()))
# Solve # Solve with callback
status = self.solver.Solve(self.model) callback = SimpleSolutionCallback()
status = self.solver.SolveWithSolutionCallback(self.model, callback)
result = self._format_solution(status, cp_vars, model_data) result = self._format_solution(status, cp_vars, model_data)
result['metadata']['constraintsAdded'] = constraints_added result['metadata']['constraintsAdded'] = constraints_added
# 🆕 Production-safe progress saving
if callback.solutions:
result['progress'] = callback.solutions
self.progress_manager.save_progress(result, callback.solutions)
else:
result['progress'] = []
self.progress_manager.save_progress(result, [])
return result return result
except Exception as e: except Exception as e:
return self._error_result(str(e)) error_result = self._error_result(str(e))
self.progress_manager.save_progress(error_result, [])
return error_result
def _save_progress_data(self, result, progress_data):
"""Save progress data to file in the same directory as this script"""
try:
# Get current script directory
script_dir = os.path.dirname(os.path.abspath(__file__))
# Create filename with timestamp and success status
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
success_status = "success" if result.get('success', False) else "failure"
filename = f"run_{timestamp}_{success_status}.json"
filepath = os.path.join(script_dir, filename)
# Prepare data to save
data_to_save = {
'timestamp': datetime.now().isoformat(),
'success': result.get('success', False),
'metadata': result.get('metadata', {}),
'progress': progress_data,
'solution_summary': {
'assignments_count': len(result.get('assignments', [])),
'violations_count': len(result.get('violations', [])),
'variables_count': result.get('metadata', {}).get('variablesCreated', 0),
'constraints_count': result.get('metadata', {}).get('constraintsAdded', 0),
'solve_time': result.get('metadata', {}).get('solveTime', 0),
'optimal': result.get('metadata', {}).get('optimal', False)
}
}
# Write to file
with open(filepath, 'w', encoding='utf-8') as f:
json.dump(data_to_save, f, indent=2, ensure_ascii=False)
print(f"Progress data saved to: {filepath}", file=sys.stderr)
except Exception as e:
print(f"Failed to save progress data: {e}", file=sys.stderr)
def _add_constraint(self, expression, cp_vars): def _add_constraint(self, expression, cp_vars):
"""Add constraint from expression string with enhanced parsing""" """Add constraint from expression string with enhanced parsing"""
try: try:
expression = expression.strip() expression = expression.strip()
# Handle implication constraints (=>)
if '=>' in expression:
left, right = expression.split('=>', 1)
left_expr = self._parse_expression(left.strip(), cp_vars)
right_expr = self._parse_expression(right.strip(), cp_vars)
# A => B is equivalent to (not A) or B
# In CP-SAT: AddBoolOr([A.Not(), B])
if hasattr(left_expr, 'Not') and hasattr(right_expr, 'Index'):
self.model.AddImplication(left_expr, right_expr)
else:
# Fallback: treat as linear constraint
self.model.Add(left_expr <= right_expr)
return True
# Handle equality # Handle equality
if ' == ' in expression: if ' == ' in expression:
left, right = expression.split(' == ', 1) left, right = expression.split(' == ', 1)
@@ -198,12 +383,11 @@ class UniversalSchedulingSolver:
assignments.append({ assignments.append({
'shiftId': shift_id, 'shiftId': shift_id,
'employeeId': employee_id, 'employeeId': employee_id,
'assignedAt': '2024-01-01T00:00:00Z', 'assignedAt': datetime.now().isoformat() + 'Z',
'score': 100 'score': 100
}) })
print(f"Debug: Found {len(assignments)} assignments", file=sys.stderr) print(f"Debug: Found {len(assignments)} assignments", file=sys.stderr)
print(f"Debug: First 5 assignments: {assignments[:5]}", file=sys.stderr)
else: else:
print(f"Debug: Solver failed with status {status}", file=sys.stderr) print(f"Debug: Solver failed with status {status}", file=sys.stderr)
@@ -213,7 +397,7 @@ class UniversalSchedulingSolver:
'assignments': assignments, 'assignments': assignments,
'violations': [], 'violations': [],
'success': success, 'success': success,
'variables': variables, # Include ALL variables for debugging 'variables': variables,
'metadata': { 'metadata': {
'solveTime': self.solver.WallTime(), 'solveTime': self.solver.WallTime(),
'constraintsAdded': len(model_data.get('constraints', [])), 'constraintsAdded': len(model_data.get('constraints', [])),
@@ -227,9 +411,9 @@ class UniversalSchedulingSolver:
status_map = { status_map = {
cp_model.OPTIMAL: 'OPTIMAL', cp_model.OPTIMAL: 'OPTIMAL',
cp_model.FEASIBLE: 'FEASIBLE', cp_model.FEASIBLE: 'FEASIBLE',
cp_MODEL.INFEASIBLE: 'INFEASIBLE', cp_model.INFEASIBLE: 'INFEASIBLE',
cp_MODEL.MODEL_INVALID: 'MODEL_INVALID', cp_model.MODEL_INVALID: 'MODEL_INVALID',
cp_MODEL.UNKNOWN: 'UNKNOWN' cp_model.UNKNOWN: 'UNKNOWN'
} }
return status_map.get(status, f'UNKNOWN_STATUS_{status}') return status_map.get(status, f'UNKNOWN_STATUS_{status}')
@@ -248,7 +432,6 @@ class UniversalSchedulingSolver:
} }
# Main execution # Main execution
if __name__ == "__main__": if __name__ == "__main__":
try: try:

View File

@@ -8,6 +8,17 @@ import { SolverOptions, Solution, Assignment } from '../models/scheduling.js';
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);
export interface ProgressStep {
timestamp: number;
objective: number;
bound: number;
solution_count: number;
}
export interface SolutionWithProgress extends Solution {
progress?: ProgressStep[];
}
export class CPModel { export class CPModel {
private modelData: any; private modelData: any;
@@ -86,7 +97,7 @@ export class CPSolver {
console.log('Using Python script at:', pythonScriptPath); console.log('Using Python script at:', pythonScriptPath);
const modelData = model.export(); const modelData = model.export();
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const pythonProcess = spawn('python', [pythonScriptPath], { const pythonProcess = spawn('python', [pythonScriptPath], {
@@ -103,6 +114,10 @@ export class CPSolver {
pythonProcess.stderr.on('data', (data) => { pythonProcess.stderr.on('data', (data) => {
stderr += data.toString(); stderr += data.toString();
// 🆕 Real-time progress monitoring from stderr
if (data.toString().includes('Progress:')) {
console.log('Python Progress:', data.toString().trim());
}
}); });
pythonProcess.on('close', (code) => { pythonProcess.on('close', (code) => {
@@ -116,15 +131,16 @@ export class CPSolver {
} }
try { try {
console.log('Python raw output:', stdout.substring(0, 500)); // Debug log console.log('Python raw output:', stdout.substring(0, 500));
const result = JSON.parse(stdout); const result = JSON.parse(stdout);
// ENHANCED: Better solution parsing // Enhanced solution parsing with progress data
const solution: Solution = { const solution: SolutionWithProgress = {
success: result.success || false, success: result.success || false,
assignments: result.assignments || [], assignments: result.assignments || [],
violations: result.violations || [], violations: result.violations || [],
progress: result.progress || [], // 🆕 Parse progress data
metadata: { metadata: {
solveTime: result.metadata?.solveTime || 0, solveTime: result.metadata?.solveTime || 0,
constraintsAdded: result.metadata?.constraintsAdded || 0, constraintsAdded: result.metadata?.constraintsAdded || 0,
@@ -133,8 +149,8 @@ export class CPSolver {
}, },
variables: result.variables || {} variables: result.variables || {}
}; };
console.log(`Python solver result: success=${solution.success}, assignments=${solution.assignments.length}`); console.log(`Python solver result: success=${solution.success}, assignments=${solution.assignments.length}, progress_steps=${solution.progress?.length}`);
resolve(solution); resolve(solution);
} catch (parseError) { } catch (parseError) {

View File

@@ -343,30 +343,53 @@ const Dashboard: React.FC = () => {
return ( return (
<div> <div>
{/* Willkommens-Bereich */} {/* Minimalist Welcome Section */}
<div style={{ <div
backgroundColor: '#e8f4fd', style={{
padding: '25px', width: '100vw',
borderRadius: '8px', position: 'relative',
marginBottom: '30px', left: '50%',
border: '1px solid #b6d7e8', right: '50%',
display: 'flex', marginLeft: '-50vw',
justifyContent: 'space-between', marginRight: '-50vw',
alignItems: 'center' background: `
}}> radial-gradient(ellipse farthest-corner at center 53%,
<div> #d9b9f3ff 10%,
<h1 style={{ margin: '0 0 10px 0', color: '#2c3e50' }}> #ddc5f1ff 22%,
Willkommen zurück, {user?.firstname} {user?.lastname} ! 👋 #e9d4f8ff 32%,
</h1> #FBFAF6 55%)
<p style={{ margin: 0, color: '#546e7a', fontSize: '16px' }}> `,
{new Date().toLocaleDateString('de-DE', { textAlign: 'center',
weekday: 'long', padding: '10vh 0',
year: 'numeric', color: '#161718',
month: 'long', fontFamily: "'Poppins', 'Inter', 'Manrope', sans-serif",
day: 'numeric' }}
})} >
</p> <h1
</div> style={{
fontSize: '3rem',
fontWeight: 100,
letterSpacing: '0.08em',
marginBottom: '0.5rem',
opacity: 0.995,
filter: 'blur(0.2px)',
}}
>
Willkommen
</h1>
<p
style={{
fontSize: '1.1rem',
color: '#3e2069',
letterSpacing: '0.05em',
fontWeight: 300,
opacity: 0.85,
transition: 'color 0.3s ease',
}}
>
{user?.firstname} {user?.lastname}
</p>
</div> </div>
{/* Quick Actions - Nur für Admins/Instandhalter */} {/* Quick Actions - Nur für Admins/Instandhalter */}
@@ -483,7 +506,7 @@ const Dashboard: React.FC = () => {
}}> }}>
<div style={{ <div style={{
width: `${progress.percentage}%`, width: `${progress.percentage}%`,
backgroundColor: progress.percentage > 0 ? '#3498db' : '#95a5a6', backgroundColor: progress.percentage > 0 ? '#854eca' : '#95a5a6',
height: '8px', height: '8px',
borderRadius: '10px', borderRadius: '10px',
transition: 'width 0.3s ease' transition: 'width 0.3s ease'

View File

@@ -190,7 +190,7 @@ const EmployeeManagement: React.FC = () => {
onClick={handleCreateEmployee} onClick={handleCreateEmployee}
style={{ style={{
padding: '12px 24px', padding: '12px 24px',
backgroundColor: '#27ae60', backgroundColor: '#51258f',
color: 'white', color: 'white',
border: 'none', border: 'none',
borderRadius: '6px', borderRadius: '6px',

View File

@@ -372,18 +372,6 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
</div> </div>
)} )}
{/* Timetable Structure Info */}
<div style={{
backgroundColor: '#d1ecf1',
border: '1px solid #bee5eb',
padding: '10px 15px',
margin: '10px',
borderRadius: '4px',
fontSize: '12px'
}}>
<strong>Struktur-Info:</strong> {sortedTimeSlots.length} Zeitslots × {days.length} Tage = {sortedTimeSlots.length * days.length} Zellen
</div>
<div style={{ overflowX: 'auto' }}> <div style={{ overflowX: 'auto' }}>
<table style={{ <table style={{
width: '100%', width: '100%',
@@ -397,7 +385,7 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
textAlign: 'left', textAlign: 'left',
border: '1px solid #dee2e6', border: '1px solid #dee2e6',
fontWeight: 'bold', fontWeight: 'bold',
minWidth: '200px' minWidth: '120px'
}}> }}>
Zeitslot Zeitslot
</th> </th>
@@ -558,15 +546,9 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
color: '#666' color: '#666'
}}> }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div>
<strong>Zusammenfassung:</strong> {sortedTimeSlots.length} Zeitslots × {days.length} Tage = {sortedTimeSlots.length * days.length} mögliche Shifts
</div>
<div> <div>
<strong>Aktive Verfügbarkeiten:</strong> {availabilities.filter(a => a.preferenceLevel !== 3).length} <strong>Aktive Verfügbarkeiten:</strong> {availabilities.filter(a => a.preferenceLevel !== 3).length}
</div> </div>
<div>
<strong>Validierungsfehler:</strong> {validationErrors.length}
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -667,70 +649,9 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
borderBottom: '2px solid #f0f0f0', borderBottom: '2px solid #f0f0f0',
paddingBottom: '15px' paddingBottom: '15px'
}}> }}>
📅 Verfügbarkeit verwalten (Shift-ID basiert) 📅 Verfügbarkeit verwalten
</h2> </h2>
{/* Debug-Info */}
<div style={{
backgroundColor: !selectedPlan ? '#f8d7da' : (shiftsCount === 0 ? '#fff3cd' : '#d1ecf1'),
border: `1px solid ${!selectedPlan ? '#f5c6cb' : (shiftsCount === 0 ? '#ffeaa7' : '#bee5eb')}`,
borderRadius: '6px',
padding: '15px',
marginBottom: '20px'
}}>
<h4 style={{
margin: '0 0 10px 0',
color: !selectedPlan ? '#721c24' : (shiftsCount === 0 ? '#856404' : '#0c5460')
}}>
{!selectedPlan ? '❌ KEIN PLAN AUSGEWÄHLT' :
shiftsCount === 0 ? '⚠️ KEINE SHIFTS GEFUNDEN' : '✅ PLAN-DATEN GELADEN'}
</h4>
<div style={{ fontSize: '12px', fontFamily: 'monospace' }}>
<div><strong>Ausgewählter Plan:</strong> {selectedPlan?.name || 'Keiner'}</div>
<div><strong>Plan ID:</strong> {selectedPlanId || 'Nicht gesetzt'}</div>
<div><strong>Geladene Pläne:</strong> {shiftPlans.length}</div>
<div><strong>Einzigartige Shifts:</strong> {shiftsCount}</div>
<div><strong>Geladene Verfügbarkeiten:</strong> {availabilities.length}</div>
{selectedPlan && (
<>
<div><strong>Verwendete Tage:</strong> {days.length} ({days.map(d => d.name).join(', ')})</div>
<div><strong>Gesamte Shifts im Plan:</strong> {selectedPlan.shifts?.length || 0}</div>
</>
)}
</div>
{/* Show existing preferences */}
{availabilities.length > 0 && (
<div style={{ marginTop: '10px', paddingTop: '10px', borderTop: '1px solid #bee5eb' }}>
<strong>Vorhandene Präferenzen:</strong>
{availabilities.slice(0, 5).map(avail => {
// SICHERHEITSCHECK: Stelle sicher, dass shiftId existiert
if (!avail.shiftId) {
return (
<div key={avail.id} style={{ fontSize: '11px', color: 'red' }}>
UNGÜLTIG: Keine Shift-ID
</div>
);
}
const shift = selectedPlan?.shifts?.find(s => s.id === avail.shiftId);
const shiftIdDisplay = avail.shiftId ? avail.shiftId.substring(0, 8) + '...' : 'KEINE ID';
return (
<div key={avail.id} style={{ fontSize: '11px' }}>
Shift {shiftIdDisplay} (Day {shift?.dayOfWeek || '?'}): Level {avail.preferenceLevel}
</div>
);
})}
{availabilities.length > 5 && (
<div style={{ fontSize: '11px', fontStyle: 'italic' }}>
... und {availabilities.length - 5} weitere
</div>
)}
</div>
)}
</div>
{/* 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' }}>
@@ -739,9 +660,6 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
<p style={{ margin: 0, color: '#7f8c8d' }}> <p style={{ margin: 0, color: '#7f8c8d' }}>
<strong>Email:</strong> {employee.email} <strong>Email:</strong> {employee.email}
</p> </p>
<p style={{ margin: '5px 0 0 0', color: '#7f8c8d' }}>
Legen Sie die Verfügbarkeit für {employeeFullName} fest (basierend auf Shift-IDs).
</p>
</div> </div>
{error && ( {error && (

View File

@@ -397,14 +397,6 @@ const Settings: React.FC = () => {
/> />
</div> </div>
</div> </div>
<div style={{ marginTop: '1rem', padding: '1rem', background: '#f8f9fa', borderRadius: '8px' }}>
<div style={{ fontSize: '0.9rem', color: '#666' }}>
<strong>Vorschau:</strong> {getFullName() || '(Kein Name)'}
</div>
<div style={{ fontSize: '0.8rem', color: '#888', marginTop: '0.5rem' }}>
E-Mail: {currentUser.email}
</div>
</div>
</div> </div>
</div> </div>

View File

@@ -74,7 +74,7 @@ const ShiftPlanList: React.FC = () => {
<Link to="/shift-plans/new"> <Link to="/shift-plans/new">
<button style={{ <button style={{
padding: '10px 20px', padding: '10px 20px',
backgroundColor: '#3498db', backgroundColor: '#51258f',
color: 'white', color: 'white',
border: 'none', border: 'none',
borderRadius: '4px', borderRadius: '4px',

View File

@@ -771,19 +771,6 @@ const ShiftPlanView: React.FC = () => {
</ul> </ul>
</div> </div>
)} )}
{/* Timetable Structure Info - SAME AS AVAILABILITYMANAGER */}
<div style={{
backgroundColor: '#d1ecf1',
border: '1px solid #bee5eb',
padding: '10px 15px',
margin: '10px',
borderRadius: '4px',
fontSize: '12px'
}}>
<strong>Struktur-Info:</strong> {allTimeSlots.length} Zeitslots × {days.length} Tage = {allTimeSlots.length * days.length} Zellen
</div>
<div style={{ overflowX: 'auto' }}> <div style={{ overflowX: 'auto' }}>
<table style={{ <table style={{
width: '100%', width: '100%',
@@ -797,7 +784,7 @@ const ShiftPlanView: React.FC = () => {
textAlign: 'left', textAlign: 'left',
border: '1px solid #dee2e6', border: '1px solid #dee2e6',
fontWeight: 'bold', fontWeight: 'bold',
minWidth: '200px' minWidth: '120px'
}}> }}>
Schicht (Zeit) Schicht (Zeit)
</th> </th>
@@ -850,7 +837,7 @@ const ShiftPlanView: React.FC = () => {
color: '#ccc', color: '#ccc',
fontStyle: 'italic' fontStyle: 'italic'
}}> }}>
Kein Shift Keine Schicht
</td> </td>
); );
} }
@@ -977,23 +964,6 @@ const ShiftPlanView: React.FC = () => {
</table> </table>
</div> </div>
{/* Summary Statistics - SAME AS AVAILABILITYMANAGER */}
<div style={{
backgroundColor: '#f8f9fa',
padding: '15px',
borderTop: '1px solid #dee2e6',
fontSize: '12px',
color: '#666'
}}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div>
<strong>Zusammenfassung:</strong> {allTimeSlots.length} Zeitslots × {days.length} Tage = {allTimeSlots.length * days.length} mögliche Shifts
</div>
<div>
<strong>Validierungsfehler:</strong> {validation.errors.length}
</div>
</div>
</div>
</div> </div>
); );
}; };
@@ -1069,50 +1039,6 @@ const ShiftPlanView: React.FC = () => {
</div> </div>
</div> </div>
{/* Debug Info - Enhanced */}
<div style={{
backgroundColor: validation.errors.length > 0 ? '#fff3cd' : (allTimeSlots.length === 0 ? '#f8d7da' : '#d1ecf1'),
border: `1px solid ${validation.errors.length > 0 ? '#ffeaa7' : (allTimeSlots.length === 0 ? '#f5c6cb' : '#bee5eb')}`,
borderRadius: '6px',
padding: '15px',
marginBottom: '20px'
}}>
<h4 style={{
margin: '0 0 10px 0',
color: validation.errors.length > 0 ? '#856404' : (allTimeSlots.length === 0 ? '#721c24' : '#0c5460')
}}>
{validation.errors.length > 0 ? '⚠️ VALIDIERUNGSPROBLEME' :
allTimeSlots.length === 0 ? '❌ KEINE SHIFTS GEFUNDEN' : '✅ PLAN-DATEN GELADEN'}
</h4>
<div style={{ fontSize: '12px', fontFamily: 'monospace' }}>
<div><strong>Ausgewählter Plan:</strong> {shiftPlan.name}</div>
<div><strong>Plan ID:</strong> {shiftPlan.id}</div>
<div><strong>Einzigartige Zeitslots:</strong> {allTimeSlots.length}</div>
<div><strong>Verwendete Tage:</strong> {days.length} ({days.map(d => d.name).join(', ')})</div>
<div><strong>Shift Patterns:</strong> {shiftPlan.shifts?.length || 0}</div>
<div><strong>Scheduled Shifts:</strong> {scheduledShifts.length}</div>
<div><strong>Geladene Verfügbarkeiten:</strong> {availabilities.length}</div>
<div><strong>Aktive Mitarbeiter:</strong> {employees.length}</div>
{assignmentResult && (
<div><strong>Assignment Keys:</strong> {Object.keys(assignmentResult.assignments).length}</div>
)}
</div>
{/* Show shift pattern vs scheduled shift matching */}
{shiftPlan.shifts && scheduledShifts.length > 0 && (
<div style={{ marginTop: '10px', paddingTop: '10px', borderTop: '1px solid #bee5eb' }}>
<strong>Shift Matching:</strong>
<div style={{ fontSize: '11px' }}>
{shiftPlan.shifts.length} Patterns {scheduledShifts.length} Scheduled Shifts
{assignmentResult && (
<div> {Object.keys(assignmentResult.assignments).length} Assignment Keys</div>
)}
</div>
</div>
)}
</div>
{/* Rest of the component remains the same... */}
{/* Availability Status - only show for drafts */} {/* Availability Status - only show for drafts */}
{shiftPlan.status === 'draft' && ( {shiftPlan.status === 'draft' && (
<div style={{ <div style={{