mirror of
https://github.com/donpat1to/Schichtenplaner.git
synced 2025-12-01 06:55:45 +01:00
changed role handling, employee name handling and availibilitie handling with shift.id
This commit is contained in:
@@ -12,9 +12,11 @@ interface AvailabilityManagerProps {
|
||||
}
|
||||
|
||||
// Local interface extensions
|
||||
interface ExtendedTimeSlot extends TimeSlot {
|
||||
interface ExtendedShift extends Shift {
|
||||
timeSlotName?: string;
|
||||
startTime?: string;
|
||||
endTime?: string;
|
||||
displayName?: string;
|
||||
source?: string;
|
||||
}
|
||||
|
||||
interface Availability extends EmployeeAvailability {
|
||||
@@ -68,20 +70,42 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
||||
return time.substring(0, 5);
|
||||
};
|
||||
|
||||
// Create a data structure that maps days to their actual time slots
|
||||
// Create a data structure that maps days to their shifts with time slot info
|
||||
const getTimetableData = () => {
|
||||
if (!selectedPlan || !selectedPlan.shifts || !selectedPlan.timeSlots) {
|
||||
return { days: [], timeSlotsByDay: {} };
|
||||
return { days: [], shiftsByDay: {} };
|
||||
}
|
||||
|
||||
// Group shifts by day
|
||||
// Create a map for quick time slot lookups
|
||||
const timeSlotMap = new Map(selectedPlan.timeSlots.map(ts => [ts.id, ts]));
|
||||
|
||||
// Group shifts by day and enhance with time slot info
|
||||
const shiftsByDay = selectedPlan.shifts.reduce((acc, shift) => {
|
||||
if (!acc[shift.dayOfWeek]) {
|
||||
acc[shift.dayOfWeek] = [];
|
||||
}
|
||||
acc[shift.dayOfWeek].push(shift);
|
||||
|
||||
const timeSlot = timeSlotMap.get(shift.timeSlotId);
|
||||
const enhancedShift: ExtendedShift = {
|
||||
...shift,
|
||||
timeSlotName: timeSlot?.name,
|
||||
startTime: timeSlot?.startTime,
|
||||
endTime: timeSlot?.endTime,
|
||||
displayName: timeSlot ? `${timeSlot.name} (${formatTime(timeSlot.startTime)}-${formatTime(timeSlot.endTime)})` : shift.id
|
||||
};
|
||||
|
||||
acc[shift.dayOfWeek].push(enhancedShift);
|
||||
return acc;
|
||||
}, {} as Record<number, typeof selectedPlan.shifts>);
|
||||
}, {} as Record<number, ExtendedShift[]>);
|
||||
|
||||
// Sort shifts within each day by start time
|
||||
Object.keys(shiftsByDay).forEach(day => {
|
||||
shiftsByDay[parseInt(day)].sort((a, b) => {
|
||||
const timeA = a.startTime || '';
|
||||
const timeB = b.startTime || '';
|
||||
return timeA.localeCompare(timeB);
|
||||
});
|
||||
});
|
||||
|
||||
// Get unique days that have shifts
|
||||
const days = Array.from(new Set(selectedPlan.shifts.map(shift => shift.dayOfWeek)))
|
||||
@@ -90,24 +114,7 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
||||
return daysOfWeek.find(day => day.id === dayId) || { id: dayId, name: `Tag ${dayId}` };
|
||||
});
|
||||
|
||||
// For each day, get the time slots that actually have shifts
|
||||
const timeSlotsByDay: Record<number, ExtendedTimeSlot[]> = {};
|
||||
|
||||
days.forEach(day => {
|
||||
const shiftsForDay = shiftsByDay[day.id] || [];
|
||||
const timeSlotIdsForDay = new Set(shiftsForDay.map(shift => shift.timeSlotId));
|
||||
|
||||
timeSlotsByDay[day.id] = selectedPlan.timeSlots
|
||||
.filter(timeSlot => timeSlotIdsForDay.has(timeSlot.id))
|
||||
.map(timeSlot => ({
|
||||
...timeSlot,
|
||||
displayName: `${timeSlot.name} (${formatTime(timeSlot.startTime)}-${formatTime(timeSlot.endTime)})`,
|
||||
source: `Plan: ${selectedPlan.name}`
|
||||
}))
|
||||
.sort((a, b) => a.startTime.localeCompare(b.startTime));
|
||||
});
|
||||
|
||||
return { days, timeSlotsByDay };
|
||||
return { days, shiftsByDay };
|
||||
};
|
||||
|
||||
const loadData = async () => {
|
||||
@@ -137,7 +144,6 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
||||
|
||||
// 3. Select first plan with actual shifts if available
|
||||
if (plans.length > 0) {
|
||||
// Find a plan that actually has shifts and time slots
|
||||
const planWithShifts = plans.find(plan =>
|
||||
plan.shifts && plan.shifts.length > 0 &&
|
||||
plan.timeSlots && plan.timeSlots.length > 0
|
||||
@@ -146,7 +152,6 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
||||
setSelectedPlanId(planWithShifts.id);
|
||||
console.log('✅ SCHICHTPLAN AUSGEWÄHLT:', planWithShifts.name);
|
||||
|
||||
// Load the selected plan to get its actual used time slots and days
|
||||
await loadSelectedPlan();
|
||||
}
|
||||
|
||||
@@ -170,8 +175,7 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
||||
name: plan.name,
|
||||
timeSlotsCount: plan.timeSlots?.length || 0,
|
||||
shiftsCount: plan.shifts?.length || 0,
|
||||
usedDays: Array.from(new Set(plan.shifts?.map(s => s.dayOfWeek) || [])).sort(),
|
||||
usedTimeSlots: Array.from(new Set(plan.shifts?.map(s => s.timeSlotId) || [])).length
|
||||
usedDays: Array.from(new Set(plan.shifts?.map(s => s.dayOfWeek) || [])).sort()
|
||||
});
|
||||
} catch (err: any) {
|
||||
console.error('❌ FEHLER BEIM LADEN DES SCHICHTPLANS:', err);
|
||||
@@ -179,14 +183,11 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
const handleAvailabilityLevelChange = (dayId: number, timeSlotId: string, level: AvailabilityLevel) => {
|
||||
console.log(`🔄 ÄNDERE VERFÜGBARKEIT: Tag ${dayId}, Slot ${timeSlotId}, Level ${level}`);
|
||||
const handleAvailabilityLevelChange = (shiftId: string, level: AvailabilityLevel) => {
|
||||
console.log(`🔄 ÄNDERE VERFÜGBARKEIT: Shift ${shiftId}, Level ${level}`);
|
||||
|
||||
setAvailabilities(prev => {
|
||||
const existingIndex = prev.findIndex(avail =>
|
||||
avail.dayOfWeek === dayId &&
|
||||
avail.timeSlotId === timeSlotId
|
||||
);
|
||||
const existingIndex = prev.findIndex(avail => avail.shiftId === shiftId);
|
||||
|
||||
if (existingIndex >= 0) {
|
||||
// Update existing availability
|
||||
@@ -198,13 +199,20 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
||||
};
|
||||
return updated;
|
||||
} else {
|
||||
// Create new availability
|
||||
// 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 = {
|
||||
id: `temp-${dayId}-${timeSlotId}-${Date.now()}`,
|
||||
id: `temp-${shiftId}-${Date.now()}`,
|
||||
employeeId: employee.id,
|
||||
planId: selectedPlanId || '',
|
||||
dayOfWeek: dayId,
|
||||
timeSlotId: timeSlotId,
|
||||
planId: selectedPlanId,
|
||||
shiftId: shiftId, // Use shiftId directly
|
||||
dayOfWeek: shift.dayOfWeek, // Keep for backward compatibility if needed
|
||||
timeSlotId: shift.timeSlotId, // Keep for backward compatibility if needed
|
||||
preferenceLevel: level,
|
||||
isAvailable: level !== 3
|
||||
};
|
||||
@@ -213,21 +221,16 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
||||
});
|
||||
};
|
||||
|
||||
const getAvailabilityForDayAndSlot = (dayId: number, timeSlotId: string): AvailabilityLevel => {
|
||||
const availability = availabilities.find(avail =>
|
||||
avail.dayOfWeek === dayId &&
|
||||
avail.timeSlotId === timeSlotId
|
||||
);
|
||||
|
||||
const result = availability?.preferenceLevel || 3;
|
||||
return result;
|
||||
const getAvailabilityForShift = (shiftId: string): AvailabilityLevel => {
|
||||
const availability = availabilities.find(avail => avail.shiftId === shiftId);
|
||||
return availability?.preferenceLevel || 3;
|
||||
};
|
||||
|
||||
// Update the timetable rendering to use the new data structure
|
||||
// Update the timetable rendering to use shifts directly
|
||||
const renderTimetable = () => {
|
||||
const { days, timeSlotsByDay } = getTimetableData();
|
||||
const { days, shiftsByDay } = getTimetableData();
|
||||
|
||||
if (days.length === 0 || Object.keys(timeSlotsByDay).length === 0) {
|
||||
if (days.length === 0 || Object.keys(shiftsByDay).length === 0) {
|
||||
return (
|
||||
<div style={{
|
||||
padding: '40px',
|
||||
@@ -244,23 +247,25 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
||||
);
|
||||
}
|
||||
|
||||
// Get all unique time slots across all days for row headers
|
||||
const allTimeSlotIds = new Set<string>();
|
||||
// Get all unique shifts across all days for row headers
|
||||
const allShifts: ExtendedShift[] = [];
|
||||
const shiftIds = new Set<string>();
|
||||
|
||||
days.forEach(day => {
|
||||
timeSlotsByDay[day.id]?.forEach(timeSlot => {
|
||||
allTimeSlotIds.add(timeSlot.id);
|
||||
shiftsByDay[day.id]?.forEach(shift => {
|
||||
if (!shiftIds.has(shift.id)) {
|
||||
shiftIds.add(shift.id);
|
||||
allShifts.push(shift);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const allTimeSlots = Array.from(allTimeSlotIds)
|
||||
.map(id => selectedPlan?.timeSlots?.find(ts => ts.id === id))
|
||||
.filter(Boolean)
|
||||
.map(timeSlot => ({
|
||||
...timeSlot!,
|
||||
displayName: `${timeSlot!.name} (${formatTime(timeSlot!.startTime)}-${formatTime(timeSlot!.endTime)})`,
|
||||
source: `Plan: ${selectedPlan!.name}`
|
||||
}))
|
||||
.sort((a, b) => a.startTime.localeCompare(b.startTime));
|
||||
// Sort shifts by time slot start time
|
||||
allShifts.sort((a, b) => {
|
||||
const timeA = a.startTime || '';
|
||||
const timeB = b.startTime || '';
|
||||
return timeA.localeCompare(timeB);
|
||||
});
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
@@ -277,7 +282,7 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
||||
}}>
|
||||
Verfügbarkeit definieren
|
||||
<div style={{ fontSize: '14px', fontWeight: 'normal', marginTop: '5px' }}>
|
||||
{allTimeSlots.length} Schichttypen • {days.length} Tage • Nur tatsächlich im Plan verwendete Schichten
|
||||
{allShifts.length} Schichten • {days.length} Tage • Direkte Shift-ID Zuordnung
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -294,7 +299,7 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
||||
textAlign: 'left',
|
||||
border: '1px solid #dee2e6',
|
||||
fontWeight: 'bold',
|
||||
minWidth: '100px'
|
||||
minWidth: '150px'
|
||||
}}>
|
||||
Schicht (Zeit)
|
||||
</th>
|
||||
@@ -312,9 +317,9 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{allTimeSlots.map((timeSlot, timeIndex) => (
|
||||
<tr key={timeSlot.id} style={{
|
||||
backgroundColor: timeIndex % 2 === 0 ? 'white' : '#f8f9fa'
|
||||
{allShifts.map((shift, shiftIndex) => (
|
||||
<tr key={shift.id} style={{
|
||||
backgroundColor: shiftIndex % 2 === 0 ? 'white' : '#f8f9fa'
|
||||
}}>
|
||||
<td style={{
|
||||
padding: '12px 16px',
|
||||
@@ -322,16 +327,16 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
||||
fontWeight: '500',
|
||||
backgroundColor: '#f8f9fa'
|
||||
}}>
|
||||
{timeSlot.displayName}
|
||||
{shift.displayName}
|
||||
<div style={{ fontSize: '11px', color: '#666', marginTop: '4px' }}>
|
||||
{timeSlot.source}
|
||||
Shift-ID: {shift.id.substring(0, 8)}...
|
||||
</div>
|
||||
</td>
|
||||
{days.map(weekday => {
|
||||
// Check if this time slot exists for this day
|
||||
const timeSlotForDay = timeSlotsByDay[weekday.id]?.find(ts => ts.id === timeSlot.id);
|
||||
// Check if this shift exists for this day
|
||||
const shiftForDay = shiftsByDay[weekday.id]?.find(s => s.id === shift.id);
|
||||
|
||||
if (!timeSlotForDay) {
|
||||
if (!shiftForDay) {
|
||||
return (
|
||||
<td key={weekday.id} style={{
|
||||
padding: '12px 16px',
|
||||
@@ -346,7 +351,7 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
||||
);
|
||||
}
|
||||
|
||||
const currentLevel = getAvailabilityForDayAndSlot(weekday.id, timeSlot.id);
|
||||
const currentLevel = getAvailabilityForShift(shift.id);
|
||||
const levelConfig = availabilityLevels.find(l => l.level === currentLevel);
|
||||
|
||||
return (
|
||||
@@ -360,7 +365,7 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
||||
value={currentLevel}
|
||||
onChange={(e) => {
|
||||
const newLevel = parseInt(e.target.value) as AvailabilityLevel;
|
||||
handleAvailabilityLevelChange(weekday.id, timeSlot.id, newLevel);
|
||||
handleAvailabilityLevelChange(shift.id, newLevel);
|
||||
}}
|
||||
style={{
|
||||
padding: '8px 12px',
|
||||
@@ -410,12 +415,12 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const { days, timeSlotsByDay } = getTimetableData();
|
||||
const { days, shiftsByDay } = getTimetableData();
|
||||
|
||||
// Filter availabilities to only include those with actual shifts
|
||||
const validAvailabilities = availabilities.filter(avail => {
|
||||
const timeSlotsForDay = timeSlotsByDay[avail.dayOfWeek] || [];
|
||||
return timeSlotsForDay.some(slot => slot.id === avail.timeSlotId);
|
||||
// Check if this shiftId exists in the current plan
|
||||
return selectedPlan?.shifts?.some(shift => shift.id === avail.shiftId);
|
||||
});
|
||||
|
||||
if (validAvailabilities.length === 0) {
|
||||
@@ -423,13 +428,14 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert to the format expected by the API
|
||||
// Convert to the format expected by the API - using shiftId directly
|
||||
const requestData = {
|
||||
planId: selectedPlanId,
|
||||
availabilities: validAvailabilities.map(avail => ({
|
||||
planId: selectedPlanId,
|
||||
dayOfWeek: avail.dayOfWeek,
|
||||
timeSlotId: avail.timeSlotId,
|
||||
shiftId: avail.shiftId, // Use shiftId directly
|
||||
dayOfWeek: avail.dayOfWeek, // Keep for backward compatibility
|
||||
timeSlotId: avail.timeSlotId, // Keep for backward compatibility
|
||||
preferenceLevel: avail.preferenceLevel,
|
||||
notes: avail.notes
|
||||
}))
|
||||
@@ -457,14 +463,14 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
||||
);
|
||||
}
|
||||
|
||||
const { days, timeSlotsByDay } = getTimetableData();
|
||||
const allTimeSlotIds = new Set<string>();
|
||||
const { days, shiftsByDay } = getTimetableData();
|
||||
const allShiftIds = new Set<string>();
|
||||
days.forEach(day => {
|
||||
timeSlotsByDay[day.id]?.forEach(timeSlot => {
|
||||
allTimeSlotIds.add(timeSlot.id);
|
||||
shiftsByDay[day.id]?.forEach(shift => {
|
||||
allShiftIds.add(shift.id);
|
||||
});
|
||||
});
|
||||
const timeSlotsCount = allTimeSlotIds.size;
|
||||
const shiftsCount = allShiftIds.size;
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
@@ -482,40 +488,30 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
||||
borderBottom: '2px solid #f0f0f0',
|
||||
paddingBottom: '15px'
|
||||
}}>
|
||||
📅 Verfügbarkeit verwalten
|
||||
📅 Verfügbarkeit verwalten (Shift-ID basiert)
|
||||
</h2>
|
||||
|
||||
{/* Debug-Info */}
|
||||
<div style={{
|
||||
backgroundColor: timeSlotsCount === 0 ? '#f8d7da' : '#d1ecf1',
|
||||
border: `1px solid ${timeSlotsCount === 0 ? '#f5c6cb' : '#bee5eb'}`,
|
||||
backgroundColor: shiftsCount === 0 ? '#f8d7da' : '#d1ecf1',
|
||||
border: `1px solid ${shiftsCount === 0 ? '#f5c6cb' : '#bee5eb'}`,
|
||||
borderRadius: '6px',
|
||||
padding: '15px',
|
||||
marginBottom: '20px'
|
||||
}}>
|
||||
<h4 style={{
|
||||
margin: '0 0 10px 0',
|
||||
color: timeSlotsCount === 0 ? '#721c24' : '#0c5460'
|
||||
color: shiftsCount === 0 ? '#721c24' : '#0c5460'
|
||||
}}>
|
||||
{timeSlotsCount === 0 ? '❌ PROBLEM: Keine Zeit-Slots gefunden' : '✅ Plan-Daten geladen'}
|
||||
{shiftsCount === 0 ? '❌ PROBLEM: 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>Verwendete Zeit-Slots:</strong> {timeSlotsCount}</div>
|
||||
<div><strong>Einzigartige Shifts:</strong> {shiftsCount}</div>
|
||||
<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><strong>Methode:</strong> Direkte Shift-ID Zuordnung</div>
|
||||
</div>
|
||||
|
||||
{selectedPlan && selectedPlan.shifts && (
|
||||
<div style={{ marginTop: '10px' }}>
|
||||
<strong>Shifts im Plan:</strong>
|
||||
{selectedPlan.shifts.map((shift, index) => (
|
||||
<div key={index} style={{ fontSize: '11px', marginLeft: '10px' }}>
|
||||
• Tag {shift.dayOfWeek}: {shift.timeSlotId} ({shift.requiredEmployees} Personen)
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Employee Info */}
|
||||
@@ -524,7 +520,7 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
||||
{employee.name}
|
||||
</h3>
|
||||
<p style={{ margin: 0, color: '#7f8c8d' }}>
|
||||
Legen Sie die Verfügbarkeit für {employee.name} fest.
|
||||
Legen Sie die Verfügbarkeit für {employee.name} fest (basierend auf Shift-IDs).
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -608,7 +604,7 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
||||
<option value="">Bitte auswählen...</option>
|
||||
{shiftPlans.map(plan => (
|
||||
<option key={plan.id} value={plan.id}>
|
||||
{plan.name} {plan.timeSlots && `(${plan.timeSlots.length} Zeit-Slots)`}
|
||||
{plan.name} {plan.shifts && `(${plan.shifts.length} Shifts)`}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
@@ -617,7 +613,7 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
||||
{selectedPlan && (
|
||||
<div style={{ fontSize: '14px', color: '#666' }}>
|
||||
<div><strong>Plan:</strong> {selectedPlan.name}</div>
|
||||
<div><strong>Zeit-Slots:</strong> {selectedPlan.timeSlots?.length || 0}</div>
|
||||
<div><strong>Shifts:</strong> {selectedPlan.shifts?.length || 0}</div>
|
||||
<div><strong>Status:</strong> {selectedPlan.status}</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -651,14 +647,14 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
||||
|
||||
<button
|
||||
onClick={handleSave}
|
||||
disabled={saving || timeSlotsCount === 0 || !selectedPlanId}
|
||||
disabled={saving || shiftsCount === 0 || !selectedPlanId}
|
||||
style={{
|
||||
padding: '12px 24px',
|
||||
backgroundColor: saving ? '#bdc3c7' : (timeSlotsCount === 0 || !selectedPlanId ? '#95a5a6' : '#3498db'),
|
||||
backgroundColor: saving ? '#bdc3c7' : (shiftsCount === 0 || !selectedPlanId ? '#95a5a6' : '#3498db'),
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '6px',
|
||||
cursor: (saving || timeSlotsCount === 0 || !selectedPlanId) ? 'not-allowed' : 'pointer',
|
||||
cursor: (saving || shiftsCount === 0 || !selectedPlanId) ? 'not-allowed' : 'pointer',
|
||||
fontWeight: 'bold'
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -76,66 +76,87 @@ const ShiftPlanView: React.FC = () => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (assignmentResult) {
|
||||
console.log("🔄 assignmentResult UPDATED:", {
|
||||
success: assignmentResult.success,
|
||||
assignmentsCount: Object.keys(assignmentResult.assignments).length,
|
||||
assignmentKeys: Object.keys(assignmentResult.assignments).slice(0, 5), // First 5 keys
|
||||
violations: assignmentResult.violations.length
|
||||
});
|
||||
|
||||
// Log all assignments with their keys
|
||||
Object.entries(assignmentResult.assignments).forEach(([key, empIds]) => {
|
||||
console.log(` 🗂️ Assignment Key: ${key}`);
|
||||
console.log(` Employees: ${empIds.join(', ')}`);
|
||||
|
||||
// Try to identify what this key represents
|
||||
const isUuid = key.length === 36; // UUID format
|
||||
console.log(` Type: ${isUuid ? 'UUID (likely scheduled shift)' : 'Pattern (likely shift pattern)'}`);
|
||||
});
|
||||
}
|
||||
}, [assignmentResult]);
|
||||
|
||||
useEffect(() => {
|
||||
(window as any).debugRenderLogic = debugRenderLogic;
|
||||
return () => { (window as any).debugRenderLogic = undefined; };
|
||||
}, [shiftPlan, scheduledShifts]);
|
||||
|
||||
const loadShiftPlanData = async () => {
|
||||
if (!id) return;
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
if (!id) return;
|
||||
|
||||
// Load plan and employees first
|
||||
const [plan, employeesData] = await Promise.all([
|
||||
shiftPlanService.getShiftPlan(id),
|
||||
employeeService.getEmployees(),
|
||||
]);
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// Load plan and employees first
|
||||
const [plan, employeesData] = await Promise.all([
|
||||
shiftPlanService.getShiftPlan(id),
|
||||
employeeService.getEmployees(),
|
||||
]);
|
||||
|
||||
setShiftPlan(plan);
|
||||
setEmployees(employeesData.filter(emp => emp.isActive));
|
||||
setShiftPlan(plan);
|
||||
setEmployees(employeesData.filter(emp => emp.isActive));
|
||||
|
||||
// CRITICAL: Load scheduled shifts and verify they exist
|
||||
const shiftsData = await shiftAssignmentService.getScheduledShiftsForPlan(id);
|
||||
console.log('📋 Loaded scheduled shifts:', shiftsData.length);
|
||||
|
||||
if (shiftsData.length === 0) {
|
||||
console.warn('⚠️ No scheduled shifts found for plan:', id);
|
||||
// CRITICAL: Load scheduled shifts and verify they exist
|
||||
const shiftsData = await shiftAssignmentService.getScheduledShiftsForPlan(id);
|
||||
console.log('📋 Loaded scheduled shifts:', shiftsData.length);
|
||||
|
||||
if (shiftsData.length === 0) {
|
||||
console.warn('⚠️ No scheduled shifts found for plan:', id);
|
||||
showNotification({
|
||||
type: 'warning',
|
||||
title: 'Keine Schichten gefunden',
|
||||
message: 'Der Schichtplan hat keine generierten Schichten. Bitte überprüfen Sie die Plan-Konfiguration.'
|
||||
});
|
||||
}
|
||||
|
||||
setScheduledShifts(shiftsData);
|
||||
|
||||
// Load availabilities
|
||||
const availabilityPromises = employeesData
|
||||
.filter(emp => emp.isActive)
|
||||
.map(emp => employeeService.getAvailabilities(emp.id));
|
||||
|
||||
const allAvailabilities = await Promise.all(availabilityPromises);
|
||||
const flattenedAvailabilities = allAvailabilities.flat();
|
||||
|
||||
const planAvailabilities = flattenedAvailabilities.filter(
|
||||
availability => availability.planId === id
|
||||
);
|
||||
|
||||
setAvailabilities(planAvailabilities);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading shift plan data:', error);
|
||||
showNotification({
|
||||
type: 'warning',
|
||||
title: 'Keine Schichten gefunden',
|
||||
message: 'Der Schichtplan hat keine generierten Schichten. Bitte überprüfen Sie die Plan-Konfiguration.'
|
||||
type: 'error',
|
||||
title: 'Fehler',
|
||||
message: 'Daten konnten nicht geladen werden.'
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
setScheduledShifts(shiftsData);
|
||||
|
||||
// Load availabilities
|
||||
const availabilityPromises = employeesData
|
||||
.filter(emp => emp.isActive)
|
||||
.map(emp => employeeService.getAvailabilities(emp.id));
|
||||
|
||||
const allAvailabilities = await Promise.all(availabilityPromises);
|
||||
const flattenedAvailabilities = allAvailabilities.flat();
|
||||
|
||||
const planAvailabilities = flattenedAvailabilities.filter(
|
||||
availability => availability.planId === id
|
||||
);
|
||||
|
||||
setAvailabilities(planAvailabilities);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading shift plan data:', error);
|
||||
showNotification({
|
||||
type: 'error',
|
||||
title: 'Fehler',
|
||||
message: 'Daten konnten nicht geladen werden.'
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const handleRecreateAssignments = async () => {
|
||||
if (!shiftPlan) return;
|
||||
@@ -207,7 +228,7 @@ const ShiftPlanView: React.FC = () => {
|
||||
console.log('🔍 RENDER LOGIC DEBUG:');
|
||||
console.log('=====================');
|
||||
|
||||
const { days, allTimeSlots, timeSlotsByDay } = getExtendedTimetableData();
|
||||
const { days, allTimeSlots, timeSlotsByDay } = getTimetableData();
|
||||
|
||||
console.log('📊 TABLE STRUCTURE:');
|
||||
console.log('- Days in table:', days.length);
|
||||
@@ -280,54 +301,6 @@ const ShiftPlanView: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const getExtendedTimetableData = () => {
|
||||
if (!shiftPlan || !shiftPlan.timeSlots) {
|
||||
return { days: [], timeSlotsByDay: {}, allTimeSlots: [] };
|
||||
}
|
||||
|
||||
// Verwende alle Tage die tatsächlich in scheduledShifts vorkommen
|
||||
const allDaysInScheduledShifts = [...new Set(scheduledShifts.map(s => getDayOfWeek(s.date)))].sort();
|
||||
|
||||
const days = allDaysInScheduledShifts.map(dayId => {
|
||||
return weekdays.find(day => day.id === dayId) || { id: dayId, name: `Tag ${dayId}` };
|
||||
});
|
||||
|
||||
// Verwende alle TimeSlots die tatsächlich in scheduledShifts vorkommen
|
||||
const allTimeSlotIdsInScheduledShifts = [...new Set(scheduledShifts.map(s => s.timeSlotId))];
|
||||
|
||||
const allTimeSlots = allTimeSlotIdsInScheduledShifts
|
||||
.map(id => shiftPlan.timeSlots?.find(ts => ts.id === id))
|
||||
.filter(Boolean)
|
||||
.map(timeSlot => ({
|
||||
...timeSlot!,
|
||||
displayName: `${timeSlot!.name} (${formatTime(timeSlot!.startTime)}-${formatTime(timeSlot!.endTime)})`
|
||||
}))
|
||||
.sort((a, b) => a.startTime.localeCompare(b.startTime));
|
||||
|
||||
// TimeSlots pro Tag
|
||||
const timeSlotsByDay: Record<number, ExtendedTimeSlot[]> = {};
|
||||
|
||||
days.forEach(day => {
|
||||
const timeSlotIdsForDay = new Set(
|
||||
scheduledShifts
|
||||
.filter(shift => getDayOfWeek(shift.date) === day.id)
|
||||
.map(shift => shift.timeSlotId)
|
||||
);
|
||||
|
||||
timeSlotsByDay[day.id] = allTimeSlots
|
||||
.filter(timeSlot => timeSlotIdsForDay.has(timeSlot.id))
|
||||
.sort((a, b) => a.startTime.localeCompare(b.startTime));
|
||||
});
|
||||
|
||||
/*console.log('🔄 Extended timetable data:', {
|
||||
days: days.length,
|
||||
timeSlots: allTimeSlots.length,
|
||||
totalScheduledShifts: scheduledShifts.length
|
||||
});*/
|
||||
|
||||
return { days, timeSlotsByDay, allTimeSlots };
|
||||
};
|
||||
|
||||
// Extract plan-specific shifts using the same logic as AvailabilityManager
|
||||
const getTimetableData = () => {
|
||||
if (!shiftPlan || !shiftPlan.shifts || !shiftPlan.timeSlots) {
|
||||
@@ -409,9 +382,6 @@ const ShiftPlanView: React.FC = () => {
|
||||
console.log('- Employees:', refreshedEmployees.length);
|
||||
console.log('- Availabilities:', refreshedAvailabilities.length);
|
||||
|
||||
// DEBUG: Verify we have new data
|
||||
debugSchedulingInput(refreshedEmployees, refreshedAvailabilities);
|
||||
|
||||
// ADD THIS: Define constraints object
|
||||
const constraints = {
|
||||
enforceNoTraineeAlone: true,
|
||||
@@ -428,9 +398,38 @@ const ShiftPlanView: React.FC = () => {
|
||||
constraints
|
||||
);
|
||||
|
||||
// COMPREHENSIVE DEBUGGING
|
||||
console.log("🎯 RAW ASSIGNMENT RESULT FROM API:", {
|
||||
success: result.success,
|
||||
assignmentsCount: Object.keys(result.assignments).length,
|
||||
assignmentKeys: Object.keys(result.assignments),
|
||||
violations: result.violations.length,
|
||||
resolutionReport: result.resolutionReport?.length || 0
|
||||
});
|
||||
|
||||
// Log the actual assignments with more context
|
||||
Object.entries(result.assignments).forEach(([shiftId, empIds]) => {
|
||||
console.log(` 📅 Assignment Key: ${shiftId}`);
|
||||
console.log(` Employees: ${empIds.join(', ')}`);
|
||||
|
||||
// Try to identify what type of ID this is
|
||||
const isUuid = shiftId.length === 36; // UUID format
|
||||
console.log(` Type: ${isUuid ? 'UUID (likely scheduled shift)' : 'Pattern (likely shift pattern)'}`);
|
||||
|
||||
// If it's a UUID, check if it matches any scheduled shift
|
||||
if (isUuid) {
|
||||
const matchingScheduledShift = scheduledShifts.find(s => s.id === shiftId);
|
||||
if (matchingScheduledShift) {
|
||||
console.log(` ✅ Matches scheduled shift: ${matchingScheduledShift.date} - TimeSlot: ${matchingScheduledShift.timeSlotId}`);
|
||||
} else {
|
||||
console.log(` ❌ No matching scheduled shift found for UUID`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setAssignmentResult(result);
|
||||
setShowAssignmentPreview(true);
|
||||
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error during assignment:', error);
|
||||
showNotification({
|
||||
@@ -442,57 +441,6 @@ const ShiftPlanView: React.FC = () => {
|
||||
setPublishing(false);
|
||||
}
|
||||
};
|
||||
|
||||
const debugSchedulingInput = (employees: Employee[], availabilities: EmployeeAvailability[]) => {
|
||||
console.log('🔍 DEBUG SCHEDULING INPUT:');
|
||||
console.log('==========================');
|
||||
|
||||
// Check if we have the latest data
|
||||
console.log('📊 Employee Count:', employees.length);
|
||||
console.log('📊 Availability Count:', availabilities.length);
|
||||
|
||||
// Log each employee's availability
|
||||
employees.forEach(emp => {
|
||||
const empAvailabilities = availabilities.filter(avail => avail.employeeId === emp.id);
|
||||
console.log(`👤 ${emp.name} (${emp.role}, ${emp.employeeType}): ${empAvailabilities.length} availabilities`);
|
||||
|
||||
if (empAvailabilities.length > 0) {
|
||||
empAvailabilities.forEach(avail => {
|
||||
console.log(` - Day ${avail.dayOfWeek}, TimeSlot ${avail.timeSlotId}: Level ${avail.preferenceLevel}`);
|
||||
});
|
||||
} else {
|
||||
console.log(` ❌ NO AVAILABILITIES SET!`);
|
||||
}
|
||||
});
|
||||
|
||||
// REMOVED: The problematic code that tries to access shiftPlan.employees
|
||||
// We don't have old employee data stored in shiftPlan
|
||||
|
||||
console.log('🔄 All employees are considered "changed" since we loaded fresh data');
|
||||
};
|
||||
|
||||
const forceRefreshData = async () => {
|
||||
if (!id) return;
|
||||
|
||||
try {
|
||||
const [plan, employeesData, shiftsData] = await Promise.all([
|
||||
shiftPlanService.getShiftPlan(id),
|
||||
employeeService.getEmployees(),
|
||||
shiftAssignmentService.getScheduledShiftsForPlan(id)
|
||||
]);
|
||||
|
||||
setShiftPlan(plan);
|
||||
setEmployees(employeesData.filter(emp => emp.isActive));
|
||||
setScheduledShifts(shiftsData);
|
||||
|
||||
// Force refresh availabilities
|
||||
await refreshAllAvailabilities();
|
||||
|
||||
console.log('✅ All data force-refreshed');
|
||||
} catch (error) {
|
||||
console.error('Error force-refreshing data:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePublish = async () => {
|
||||
if (!shiftPlan || !assignmentResult) return;
|
||||
@@ -502,32 +450,42 @@ const ShiftPlanView: React.FC = () => {
|
||||
|
||||
console.log('🔄 Starting to publish assignments...');
|
||||
|
||||
// ✅ KORREKTUR: Verwende die neu geladenen Shifts
|
||||
// Get fresh scheduled shifts
|
||||
const updatedShifts = await shiftAssignmentService.getScheduledShiftsForPlan(shiftPlan.id);
|
||||
|
||||
// Debug: Check if scheduled shifts exist
|
||||
if (!updatedShifts || updatedShifts.length === 0) {
|
||||
throw new Error('No scheduled shifts found in the plan');
|
||||
}
|
||||
|
||||
console.log(`📊 Found ${updatedShifts.length} scheduled shifts to update`);
|
||||
|
||||
// ✅ KORREKTUR: Verwende updatedShifts statt scheduledShifts
|
||||
const updatePromises = updatedShifts.map(async (scheduledShift) => {
|
||||
const assignedEmployees = assignmentResult.assignments[scheduledShift.id] || [];
|
||||
// ✅ FIX: Map scheduled shift to shift pattern to find assignments
|
||||
const dayOfWeek = getDayOfWeek(scheduledShift.date);
|
||||
|
||||
//console.log(`📝 Updating shift ${scheduledShift.id} with`, assignedEmployees, 'employees');
|
||||
// Find the corresponding shift pattern for this day and time slot
|
||||
const shiftPattern = shiftPlan.shifts?.find(shift =>
|
||||
shift.dayOfWeek === dayOfWeek &&
|
||||
shift.timeSlotId === scheduledShift.timeSlotId
|
||||
);
|
||||
|
||||
let assignedEmployees: string[] = [];
|
||||
|
||||
if (shiftPattern) {
|
||||
// Look for assignments using the shift pattern ID (what scheduler uses)
|
||||
assignedEmployees = assignmentResult.assignments[shiftPattern.id] || [];
|
||||
console.log(`📝 Updating scheduled shift ${scheduledShift.id} (Day ${dayOfWeek}, TimeSlot ${scheduledShift.timeSlotId}) with`, assignedEmployees, 'employees');
|
||||
} else {
|
||||
console.warn(`⚠️ No shift pattern found for scheduled shift ${scheduledShift.id} (Day ${dayOfWeek}, TimeSlot ${scheduledShift.timeSlotId})`);
|
||||
}
|
||||
|
||||
try {
|
||||
// Update the shift with assigned employees
|
||||
const scheduledShifts = await shiftAssignmentService.getScheduledShiftsForPlan(shiftPlan.id);
|
||||
// Update the scheduled shift with assigned employees
|
||||
await shiftAssignmentService.updateScheduledShift(scheduledShift.id, {
|
||||
assignedEmployees
|
||||
});
|
||||
|
||||
if (scheduledShifts.some(s => s.id === scheduledShift.id)) {
|
||||
console.log(`✅ Successfully updated scheduled shift ${scheduledShift.id}`);
|
||||
}
|
||||
console.log(`✅ Successfully updated scheduled shift ${scheduledShift.id}`);
|
||||
} catch (error) {
|
||||
console.error(`❌ Failed to update shift ${scheduledShift.id}:`, error);
|
||||
throw error;
|
||||
@@ -542,7 +500,7 @@ const ShiftPlanView: React.FC = () => {
|
||||
status: 'published'
|
||||
});
|
||||
|
||||
// ✅ KORREKTUR: Explizit alle Daten neu laden und State aktualisieren
|
||||
// Reload all data to reflect changes
|
||||
const [reloadedPlan, reloadedShifts] = await Promise.all([
|
||||
shiftPlanService.getShiftPlan(shiftPlan.id),
|
||||
shiftAssignmentService.getScheduledShiftsForPlan(shiftPlan.id)
|
||||
@@ -699,19 +657,31 @@ const ShiftPlanView: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Füge diese Funktion zu den verfügbaren Aktionen hinzu
|
||||
const handleReloadData = async () => {
|
||||
await loadShiftPlanData();
|
||||
showNotification({
|
||||
type: 'success',
|
||||
title: 'Erfolg',
|
||||
message: 'Daten wurden neu geladen.'
|
||||
});
|
||||
const getAssignmentsForScheduledShift = (scheduledShift: ScheduledShift): string[] => {
|
||||
if (!assignmentResult) return [];
|
||||
|
||||
// First try direct match with scheduled shift ID
|
||||
if (assignmentResult.assignments[scheduledShift.id]) {
|
||||
return assignmentResult.assignments[scheduledShift.id];
|
||||
}
|
||||
|
||||
// If no direct match, try to find by day and timeSlot pattern
|
||||
const dayOfWeek = getDayOfWeek(scheduledShift.date);
|
||||
const shiftPattern = shiftPlan?.shifts?.find(shift =>
|
||||
shift.dayOfWeek === dayOfWeek &&
|
||||
shift.timeSlotId === scheduledShift.timeSlotId
|
||||
);
|
||||
|
||||
if (shiftPattern && assignmentResult.assignments[shiftPattern.id]) {
|
||||
return assignmentResult.assignments[shiftPattern.id];
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
// Render timetable using the same structure as AvailabilityManager
|
||||
const renderTimetable = () => {
|
||||
const { days, allTimeSlots, timeSlotsByDay } = getExtendedTimetableData();
|
||||
const { days, allTimeSlots, timeSlotsByDay } = getTimetableData();
|
||||
if (!shiftPlan?.id) {
|
||||
console.warn("Shift plan ID is missing");
|
||||
return null;
|
||||
@@ -847,8 +817,8 @@ const ShiftPlanView: React.FC = () => {
|
||||
scheduled.timeSlotId === timeSlot.id;
|
||||
});
|
||||
|
||||
if (scheduledShift && assignmentResult.assignments[scheduledShift.id]) {
|
||||
assignedEmployees = assignmentResult.assignments[scheduledShift.id];
|
||||
if (scheduledShift) {
|
||||
assignedEmployees = getAssignmentsForScheduledShift(scheduledShift);
|
||||
displayText = assignedEmployees.map(empId => {
|
||||
const employee = employees.find(emp => emp.id === empId);
|
||||
return employee ? employee.name : 'Unbekannt';
|
||||
@@ -900,7 +870,7 @@ const ShiftPlanView: React.FC = () => {
|
||||
if (loading) return <div>Lade Schichtplan...</div>;
|
||||
if (!shiftPlan) return <div>Schichtplan nicht gefunden</div>;
|
||||
|
||||
const { days, allTimeSlots } = getExtendedTimetableData();
|
||||
const { days, allTimeSlots } = getTimetableData();
|
||||
const availabilityStatus = getAvailabilityStatus();
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user