mirror of
https://github.com/donpat1to/Schichtenplaner.git
synced 2025-11-30 22:45:46 +01:00
backend working
This commit is contained in:
@@ -1,43 +1,141 @@
|
||||
// src/components/Scheduler.tsx
|
||||
import React from 'react';
|
||||
import { useScheduling } from '../hooks/useScheduling';
|
||||
import { useScheduling } from '../services/scheduling/useScheduling';
|
||||
import { ScheduleRequest } from '../models/scheduling';
|
||||
|
||||
interface Props {
|
||||
interface SchedulerProps {
|
||||
scheduleRequest: ScheduleRequest;
|
||||
onScheduleGenerated?: (result: any) => void;
|
||||
}
|
||||
|
||||
export const Scheduler: React.FC<Props> = ({ scheduleRequest }) => {
|
||||
export const Scheduler: React.FC<SchedulerProps> = ({
|
||||
scheduleRequest,
|
||||
onScheduleGenerated
|
||||
}) => {
|
||||
const { generateSchedule, loading, error, result } = useScheduling();
|
||||
|
||||
const handleGenerateSchedule = async () => {
|
||||
try {
|
||||
await generateSchedule(scheduleRequest);
|
||||
const scheduleResult = await generateSchedule(scheduleRequest);
|
||||
if (onScheduleGenerated) {
|
||||
onScheduleGenerated(scheduleResult);
|
||||
}
|
||||
} catch (err) {
|
||||
// Error handling
|
||||
console.error('Scheduling failed:', err);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ padding: '20px', border: '1px solid #e0e0e0', borderRadius: '8px' }}>
|
||||
<h3>Automatic Schedule Generation</h3>
|
||||
|
||||
<button
|
||||
onClick={handleGenerateSchedule}
|
||||
disabled={loading}
|
||||
style={{
|
||||
padding: '12px 24px',
|
||||
backgroundColor: loading ? '#95a5a6' : '#3498db',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
cursor: loading ? 'not-allowed' : 'pointer',
|
||||
fontSize: '16px',
|
||||
fontWeight: 'bold'
|
||||
}}
|
||||
>
|
||||
{loading ? 'Generating Schedule...' : 'Generate Optimal Schedule'}
|
||||
{loading ? '🔄 Generating Schedule...' : '🚀 Generate Optimal Schedule'}
|
||||
</button>
|
||||
|
||||
{loading && (
|
||||
<div>
|
||||
<progress max="100" value="70" />
|
||||
<p>Optimizing schedule... (max 2 minutes)</p>
|
||||
<div style={{ marginTop: '15px' }}>
|
||||
<div style={{
|
||||
width: '100%',
|
||||
height: '8px',
|
||||
backgroundColor: '#ecf0f1',
|
||||
borderRadius: '4px',
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
<div style={{
|
||||
width: '70%',
|
||||
height: '100%',
|
||||
backgroundColor: '#3498db',
|
||||
animation: 'pulse 2s infinite',
|
||||
borderRadius: '4px'
|
||||
}} />
|
||||
</div>
|
||||
<p style={{ color: '#7f8c8d', fontSize: '14px', marginTop: '8px' }}>
|
||||
Optimizing schedule... (max 2 minutes)
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && <div className="error">{error}</div>}
|
||||
{error && (
|
||||
<div style={{
|
||||
marginTop: '15px',
|
||||
padding: '12px',
|
||||
backgroundColor: '#f8d7da',
|
||||
border: '1px solid #f5c6cb',
|
||||
borderRadius: '4px',
|
||||
color: '#721c24'
|
||||
}}>
|
||||
<strong>Error:</strong> {error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{result && (
|
||||
<ScheduleResultView result={result} />
|
||||
<div style={{ marginTop: '20px' }}>
|
||||
<ScheduleResultView result={result} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
const ScheduleResultView: React.FC<{ result: any }> = ({ result }) => {
|
||||
return (
|
||||
<div style={{
|
||||
padding: '15px',
|
||||
backgroundColor: result.success ? '#d4edda' : '#f8d7da',
|
||||
border: `1px solid ${result.success ? '#c3e6cb' : '#f5c6cb'}`,
|
||||
borderRadius: '4px'
|
||||
}}>
|
||||
<h4 style={{
|
||||
color: result.success ? '#155724' : '#721c24',
|
||||
marginTop: 0
|
||||
}}>
|
||||
{result.success ? '✅ Schedule Generated Successfully' : '❌ Schedule Generation Failed'}
|
||||
</h4>
|
||||
|
||||
<div style={{ marginBottom: '10px' }}>
|
||||
<strong>Assignments:</strong> {Object.keys(result.assignments || {}).length} shifts assigned
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: '10px' }}>
|
||||
<strong>Violations:</strong> {result.violations?.length || 0}
|
||||
</div>
|
||||
|
||||
{result.resolution_report && result.resolution_report.length > 0 && (
|
||||
<details style={{ marginTop: '10px' }}>
|
||||
<summary style={{ cursor: 'pointer', fontWeight: 'bold' }}>
|
||||
Resolution Report
|
||||
</summary>
|
||||
<div style={{
|
||||
marginTop: '10px',
|
||||
maxHeight: '200px',
|
||||
overflow: 'auto',
|
||||
fontSize: '12px',
|
||||
fontFamily: 'monospace',
|
||||
backgroundColor: 'rgba(0,0,0,0.05)',
|
||||
padding: '10px',
|
||||
borderRadius: '4px'
|
||||
}}>
|
||||
{result.resolution_report.map((line: string, index: number) => (
|
||||
<div key={index} style={{ marginBottom: '2px' }}>{line}</div>
|
||||
))}
|
||||
</div>
|
||||
</details>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Scheduler;
|
||||
@@ -1,37 +0,0 @@
|
||||
// frontend/src/services/scheduling/scheduling.ts
|
||||
|
||||
import { useState, useCallback } from 'react';
|
||||
import { ScheduleRequest, ScheduleResult } from '../types/scheduling';
|
||||
|
||||
export const useScheduling = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [result, setResult] = useState<ScheduleResult | null>(null);
|
||||
|
||||
const generateSchedule = useCallback(async (request: ScheduleRequest) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/scheduling/generate-schedule', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(request)
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Scheduling request failed');
|
||||
|
||||
const data: ScheduleResult = await response.json();
|
||||
setResult(data);
|
||||
return data;
|
||||
} catch (err) {
|
||||
const errorMessage = err instanceof Error ? err.message : 'Unknown error';
|
||||
setError(errorMessage);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return { generateSchedule, loading, error, result };
|
||||
};
|
||||
122
frontend/src/models/scheduling.ts
Normal file
122
frontend/src/models/scheduling.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
// backend/src/models/scheduling.ts
|
||||
import { Employee } from './Employee.js';
|
||||
import { ShiftPlan } from './ShiftPlan.js';
|
||||
|
||||
// Add the missing type definitions
|
||||
export interface Availability {
|
||||
id: string;
|
||||
employeeId: string;
|
||||
planId: string;
|
||||
dayOfWeek: number; // 1=Monday, 7=Sunday
|
||||
timeSlotId: string;
|
||||
preferenceLevel: 1 | 2 | 3; // 1:preferred, 2:available, 3:unavailable
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
export interface Constraint {
|
||||
type: string;
|
||||
severity: 'hard' | 'soft';
|
||||
parameters: {
|
||||
maxShiftsPerDay?: number;
|
||||
minEmployeesPerShift?: number;
|
||||
maxEmployeesPerShift?: number;
|
||||
enforceTraineeSupervision?: boolean;
|
||||
contractHoursLimit?: boolean;
|
||||
maxHoursPerWeek?: number;
|
||||
[key: string]: any;
|
||||
};
|
||||
weight?: number; // For soft constraints
|
||||
}
|
||||
|
||||
export interface ScheduleRequest {
|
||||
shiftPlan: ShiftPlan;
|
||||
employees: Employee[];
|
||||
availabilities: Availability[];
|
||||
constraints: Constraint[];
|
||||
}
|
||||
|
||||
export interface ScheduleResult {
|
||||
assignments: Assignment[];
|
||||
violations: Violation[];
|
||||
success: boolean;
|
||||
resolutionReport: string[];
|
||||
processingTime: number;
|
||||
}
|
||||
|
||||
export interface Assignment {
|
||||
shiftId: string;
|
||||
employeeId: string;
|
||||
assignedAt: Date;
|
||||
score: number; // Qualität der Zuweisung (1-100)
|
||||
}
|
||||
|
||||
export interface Violation {
|
||||
type: string;
|
||||
severity: 'critical' | 'warning';
|
||||
message: string;
|
||||
involvedEmployees?: string[];
|
||||
shiftId?: string;
|
||||
details?: any;
|
||||
}
|
||||
|
||||
export interface SolverOptions {
|
||||
maxTimeInSeconds: number;
|
||||
numSearchWorkers: number;
|
||||
logSearchProgress: boolean;
|
||||
}
|
||||
|
||||
export interface Solution {
|
||||
assignments: Assignment[];
|
||||
violations: Violation[];
|
||||
success: boolean;
|
||||
metadata: {
|
||||
solveTime: number;
|
||||
constraintsAdded: number;
|
||||
variablesCreated: number;
|
||||
optimal: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
// Additional helper types for the scheduling system
|
||||
export interface SchedulingConfig {
|
||||
maxRepairAttempts: number;
|
||||
targetEmployeesPerShift: number;
|
||||
enforceNoTraineeAlone: boolean;
|
||||
enforceExperiencedWithChef: boolean;
|
||||
preferEmployeePreferences: boolean;
|
||||
}
|
||||
|
||||
export interface AssignmentResult {
|
||||
assignments: { [shiftId: string]: string[] }; // shiftId -> employeeIds
|
||||
violations: string[];
|
||||
resolutionReport: string[];
|
||||
success: boolean;
|
||||
statistics?: {
|
||||
totalAssignments: number;
|
||||
preferredAssignments: number;
|
||||
availableAssignments: number;
|
||||
coverageRate: number;
|
||||
violationCount: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface EmployeeAvailabilitySummary {
|
||||
employeeId: string;
|
||||
employeeName: string;
|
||||
preferredSlots: number;
|
||||
availableSlots: number;
|
||||
unavailableSlots: number;
|
||||
totalSlots: number;
|
||||
}
|
||||
|
||||
export interface ShiftRequirement {
|
||||
shiftId: string;
|
||||
timeSlotId: string;
|
||||
dayOfWeek: number;
|
||||
date?: string;
|
||||
requiredEmployees: number;
|
||||
minEmployees: number;
|
||||
maxEmployees: number;
|
||||
assignedEmployees: string[];
|
||||
isPriority: boolean;
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import { useAuth } from '../../contexts/AuthContext';
|
||||
import { shiftPlanService } from '../../services/shiftPlanService';
|
||||
import { employeeService } from '../../services/employeeService';
|
||||
import { shiftAssignmentService, ShiftAssignmentService } from '../../services/shiftAssignmentService';
|
||||
import { IntelligentShiftScheduler, SchedulingResult, AssignmentResult } from '../../hooks/useScheduling';
|
||||
import { IntelligentShiftScheduler, SchedulingResult, AssignmentResult } from '../../services/scheduling/useScheduling';
|
||||
import { ShiftPlan, TimeSlot, ScheduledShift } from '../../models/ShiftPlan';
|
||||
import { Employee, EmployeeAvailability } from '../../models/Employee';
|
||||
import { useNotification } from '../../contexts/NotificationContext';
|
||||
|
||||
71
frontend/src/services/scheduling/useScheduling.ts
Normal file
71
frontend/src/services/scheduling/useScheduling.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { ScheduleRequest, ScheduleResult } from '../../models/scheduling';
|
||||
|
||||
export const useScheduling = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [result, setResult] = useState<ScheduleResult | null>(null);
|
||||
|
||||
const generateSchedule = useCallback(async (request: ScheduleRequest) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
console.log('📤 Sending scheduling request:', {
|
||||
shiftPlan: request.shiftPlan.name,
|
||||
employees: request.employees.length,
|
||||
availabilities: request.availabilities.length,
|
||||
constraints: request.constraints.length
|
||||
});
|
||||
|
||||
const response = await fetch('/api/scheduling/generate-schedule', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(request)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`Scheduling request failed: ${response.status} ${errorText}`);
|
||||
}
|
||||
|
||||
const data: ScheduleResult = await response.json();
|
||||
|
||||
console.log('📥 Received scheduling result:', {
|
||||
success: data.success,
|
||||
assignments: Object.keys(data.assignments).length,
|
||||
violations: data.violations.length,
|
||||
processingTime: data.processingTime
|
||||
});
|
||||
|
||||
setResult(data);
|
||||
return data;
|
||||
} catch (err) {
|
||||
const errorMessage = err instanceof Error ? err.message : 'Unknown scheduling error';
|
||||
console.error('❌ Scheduling error:', errorMessage);
|
||||
setError(errorMessage);
|
||||
throw err;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
setLoading(false);
|
||||
setError(null);
|
||||
setResult(null);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
generateSchedule,
|
||||
loading,
|
||||
error,
|
||||
result,
|
||||
reset
|
||||
};
|
||||
};
|
||||
|
||||
// Export for backward compatibility
|
||||
export default useScheduling;
|
||||
@@ -2,7 +2,7 @@
|
||||
import { ShiftPlan, ScheduledShift } from '../models/ShiftPlan';
|
||||
import { Employee, EmployeeAvailability } from '../models/Employee';
|
||||
import { authService } from './authService';
|
||||
import { IntelligentShiftScheduler, AssignmentResult, WeeklyPattern } from '../hooks/useScheduling';
|
||||
//import { IntelligentShiftScheduler, AssignmentResult, WeeklyPattern } from './scheduling/useScheduling';
|
||||
import { isScheduledShift } from '../models/helpers';
|
||||
|
||||
const API_BASE_URL = 'http://localhost:3002/api/scheduled-shifts';
|
||||
|
||||
Reference in New Issue
Block a user