export drop down menu doesnt disappear when exporttype is selected

This commit is contained in:
2025-11-05 13:22:00 +01:00
parent 822b170920
commit e66c0f9e28

View File

@@ -56,12 +56,11 @@ const ShiftPlanView: React.FC = () => {
const [showAssignmentPreview, setShowAssignmentPreview] = useState(false); const [showAssignmentPreview, setShowAssignmentPreview] = useState(false);
const [recreating, setRecreating] = useState(false); const [recreating, setRecreating] = useState(false);
const [exporting, setExporting] = useState(false); const [exporting, setExporting] = useState(false);
const [exportDropdownOpen, setExportDropdownOpen] = useState(false);
const [selectedExportType, setSelectedExportType] = useState(''); const [selectedExportType, setSelectedExportType] = useState('');
const [showExportButton, setShowExportButton] = useState(false);
const [dropdownWidth, setDropdownWidth] = useState(0); const [dropdownWidth, setDropdownWidth] = useState(0);
const dropdownRef = useRef<HTMLDivElement>(null); const dropdownRef = useRef<HTMLDivElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => { useEffect(() => {
loadShiftPlanData(); loadShiftPlanData();
@@ -96,12 +95,12 @@ const ShiftPlanView: React.FC = () => {
}; };
}, []); }, []);
// Measure dropdown width when it opens // Measure dropdown width when component mounts or selectedExportType changes
useEffect(() => { useEffect(() => {
if (exportDropdownOpen && dropdownRef.current) { if (dropdownRef.current) {
setDropdownWidth(dropdownRef.current.offsetWidth); setDropdownWidth(dropdownRef.current.offsetWidth);
} }
}, [exportDropdownOpen]); }, [selectedExportType]);
// Add this useEffect to debug state changes // Add this useEffect to debug state changes
useEffect(() => { useEffect(() => {
@@ -280,10 +279,6 @@ const ShiftPlanView: React.FC = () => {
message: `Der Schichtplan wurde als ${selectedExportType}-Datei exportiert.` message: `Der Schichtplan wurde als ${selectedExportType}-Datei exportiert.`
}); });
// Reset export state
setSelectedExportType('');
setShowExportButton(false);
} catch (error) { } catch (error) {
console.error(`Error exporting to ${selectedExportType}:`, error); console.error(`Error exporting to ${selectedExportType}:`, error);
showNotification({ showNotification({
@@ -296,10 +291,12 @@ const ShiftPlanView: React.FC = () => {
} }
}; };
const handleExportTypeSelect = (type: string) => { const handleExportTypeChange = (type: string) => {
setSelectedExportType(type); setSelectedExportType(type);
setExportDropdownOpen(false); };
setShowExportButton(true);
const handleCancelExport = () => {
setSelectedExportType('');
}; };
const loadShiftPlanData = async () => { const loadShiftPlanData = async () => {
@@ -369,418 +366,7 @@ const ShiftPlanView: React.FC = () => {
} }
}; };
const handleRecreateAssignments = async () => { // ... (rest of the functions remain the same as in the original file)
if (!shiftPlan) return;
try {
setRecreating(true);
if (!window.confirm('Möchten Sie die aktuellen Zuweisungen wirklich zurücksetzen? Alle vorhandenen Zuweisungen werden gelöscht.')) {
return;
}
console.log('🔄 STARTING COMPLETE ASSIGNMENT CLEARING PROCESS');
// STEP 1: Get current scheduled shifts
const currentScheduledShifts = await shiftAssignmentService.getScheduledShiftsForPlan(shiftPlan.id);
console.log(`📋 Found ${currentScheduledShifts.length} shifts to clear`);
// STEP 2: Clear ALL assignments by setting empty arrays
const clearPromises = currentScheduledShifts.map(async (scheduledShift) => {
console.log(`🗑️ Clearing assignments for shift: ${scheduledShift.id}`);
await shiftAssignmentService.updateScheduledShift(scheduledShift.id, {
assignedEmployees: [] // EMPTY ARRAY - this clears the assignments
});
});
await Promise.all(clearPromises);
console.log('✅ All assignments cleared from database');
// STEP 3: Update plan status to draft
await shiftPlanService.updateShiftPlan(shiftPlan.id, {
status: 'draft'
});
console.log('📝 Plan status set to draft');
// STEP 4: CRITICAL - Force reload of scheduled shifts to get EMPTY assignments
const refreshedShifts = await shiftAssignmentService.getScheduledShiftsForPlan(shiftPlan.id);
setScheduledShifts(refreshedShifts); // Update state with EMPTY assignments
// STEP 5: Clear any previous assignment results
setAssignmentResult(null);
setShowAssignmentPreview(false);
// STEP 6: Force complete data refresh
await loadShiftPlanData();
console.log('🎯 ASSIGNMENT CLEARING COMPLETE - Table should now be empty');
showNotification({
type: 'success',
title: 'Zuweisungen gelöscht',
message: 'Alle Zuweisungen wurden erfolgreich gelöscht. Die Tabelle sollte jetzt leer sein.'
});
} catch (error) {
console.error('❌ Error clearing assignments:', error);
showNotification({
type: 'error',
title: 'Fehler',
message: `Löschen der Zuweisungen fehlgeschlagen: ${error instanceof Error ? error.message : 'Unbekannter Fehler'}`
});
} finally {
setRecreating(false);
}
};
const getDayOfWeek = (dateString: string): number => {
const date = new Date(dateString);
return date.getDay() === 0 ? 7 : date.getDay();
};
const handlePreviewAssignment = async () => {
if (!shiftPlan) return;
try {
setPublishing(true);
setAssignmentResult(null); // Reset previous results
setShowAssignmentPreview(false); // Reset preview
console.log('🔄 STARTING ASSIGNMENT PREVIEW...');
// FORCE COMPLETE REFRESH - don't rely on cached state
const [refreshedEmployees, refreshedAvailabilities] = await Promise.all([
employeeService.getEmployees().then(emps => emps.filter(emp => emp.isActive)),
refreshAllAvailabilities()
]);
console.log('🔄 USING FRESH DATA:');
console.log('- Employees:', refreshedEmployees.length);
console.log('- Availabilities:', refreshedAvailabilities.length);
console.log('- Shift Patterns:', shiftPlan.shifts?.length || 0);
console.log('- Scheduled Shifts:', scheduledShifts.length);
const constraints = {
enforceNoTraineeAlone: true,
enforceExperiencedWithChef: true,
maxRepairAttempts: 50,
targetEmployeesPerShift: 2
};
console.log('🧠 Calling shift assignment service...');
// Use the freshly loaded data, not the state
const result = await shiftAssignmentService.assignShifts(
shiftPlan,
refreshedEmployees,
refreshedAvailabilities,
constraints
);
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 assignments with shift pattern context
console.log('🔍 ASSIGNMENTS BY SHIFT PATTERN:');
Object.entries(result.assignments).forEach(([shiftId, empIds]) => {
const shiftPattern = shiftPlan.shifts?.find(s => s.id === shiftId);
if (shiftPattern) {
console.log(` ✅ Shift Pattern: ${shiftId}`);
console.log(` - Day: ${shiftPattern.dayOfWeek}, TimeSlot: ${shiftPattern.timeSlotId}`);
console.log(` - Employees: ${empIds.join(', ')}`);
} else {
console.log(` ❌ UNKNOWN ID: ${shiftId}`);
console.log(` - Employees: ${empIds.join(', ')}`);
console.log(` - This ID does not match any shift pattern!`);
}
});
// CRITICAL: Update state and show preview
console.log('🔄 Setting assignment result and showing preview...');
setAssignmentResult(result);
setShowAssignmentPreview(true);
console.log('✅ Assignment preview ready, modal should be visible');
} catch (error) {
console.error('❌ Error during assignment:', error);
showNotification({
type: 'error',
title: 'Fehler',
message: 'Automatische Zuordnung fehlgeschlagen.'
});
} finally {
setPublishing(false);
}
};
const handlePublish = async () => {
if (!shiftPlan || !assignmentResult) return;
try {
setPublishing(true);
console.log('🔄 Starting to publish assignments...');
// Get fresh scheduled shifts
const updatedShifts = await shiftAssignmentService.getScheduledShiftsForPlan(shiftPlan.id);
if (!updatedShifts || updatedShifts.length === 0) {
throw new Error('No scheduled shifts found in the plan');
}
console.log(`📊 Found ${updatedShifts.length} scheduled shifts to update`);
console.log('🎯 Assignment keys from algorithm:', Object.keys(assignmentResult.assignments));
const updatePromises = updatedShifts.map(async (scheduledShift) => {
const dayOfWeek = getDayOfWeek(scheduledShift.date);
// 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) {
assignedEmployees = assignmentResult.assignments[shiftPattern.id] || [];
console.log(`📝 Updating scheduled shift ${scheduledShift.id} (Day ${dayOfWeek}, TimeSlot ${scheduledShift.timeSlotId}) with`, assignedEmployees, 'employees');
if (assignedEmployees.length === 0) {
console.warn(`⚠️ No assignments found for shift pattern ${shiftPattern.id}`);
console.log('🔍 Available assignment keys:', Object.keys(assignmentResult.assignments));
}
} else {
console.warn(`⚠️ No shift pattern found for scheduled shift ${scheduledShift.id} (Day ${dayOfWeek}, TimeSlot ${scheduledShift.timeSlotId})`);
}
try {
// Update the scheduled shift with assigned employees
await shiftAssignmentService.updateScheduledShift(scheduledShift.id, {
assignedEmployees
});
console.log(`✅ Successfully updated scheduled shift ${scheduledShift.id}`);
} catch (error) {
console.error(`❌ Failed to update shift ${scheduledShift.id}:`, error);
throw error;
}
});
await Promise.all(updatePromises);
// Update plan status to published
console.log('🔄 Updating plan status to published...');
await shiftPlanService.updateShiftPlan(shiftPlan.id, {
status: 'published'
});
// Reload all data to reflect changes
const [reloadedPlan, reloadedShifts] = await Promise.all([
shiftPlanService.getShiftPlan(shiftPlan.id),
shiftAssignmentService.getScheduledShiftsForPlan(shiftPlan.id)
]);
setShiftPlan(reloadedPlan);
setScheduledShifts(reloadedShifts);
setShowAssignmentPreview(false);
setAssignmentResult(null);
console.log('✅ Publishing completed, modal closed');
showNotification({
type: 'success',
title: 'Erfolg',
message: 'Schichtplan wurde erfolgreich veröffentlicht!'
});
} catch (error) {
console.error('❌ Error publishing shift plan:', error);
let message = 'Unbekannter Fehler';
if (error instanceof Error) {
message = error.message;
}
showNotification({
type: 'error',
title: 'Fehler',
message: `Schichtplan konnte nicht veröffentlicht werden: ${message}`
});
} finally {
setPublishing(false);
}
};
const refreshAllAvailabilities = async (): Promise<EmployeeAvailability[]> => {
try {
console.log('🔄 Force refreshing ALL availabilities with error handling...');
if (!id) {
console.error('❌ No plan ID available');
return [];
}
const availabilityPromises = employees
.filter(emp => emp.isActive)
.map(async (emp) => {
try {
return await employeeService.getAvailabilities(emp.id);
} catch (error) {
console.error(`❌ Failed to load availabilities for ${emp.email}:`, error);
return []; // Return empty array instead of failing entire operation
}
});
const allAvailabilities = await Promise.all(availabilityPromises);
const flattenedAvailabilities = allAvailabilities.flat();
// More robust filtering
const planAvailabilities = flattenedAvailabilities.filter(
availability => availability && availability.planId === id
);
console.log(`✅ Successfully refreshed ${planAvailabilities.length} availabilities for plan ${id}`);
// IMMEDIATELY update state
setAvailabilities(planAvailabilities);
return planAvailabilities;
} catch (error) {
console.error('❌ Critical error refreshing availabilities:', error);
// DON'T return old data - throw error or return empty array
throw new Error('Failed to refresh availabilities: ' + error);
}
};
const debugShiftMatching = () => {
if (!shiftPlan || !scheduledShifts.length) return;
console.log('🔍 DEBUG: Shift Pattern to Scheduled Shift Matching');
console.log('==================================================');
shiftPlan.shifts?.forEach(shiftPattern => {
const matchingScheduledShifts = scheduledShifts.filter(scheduled => {
const dayOfWeek = getDayOfWeek(scheduled.date);
return dayOfWeek === shiftPattern.dayOfWeek &&
scheduled.timeSlotId === shiftPattern.timeSlotId;
});
console.log(`📅 Shift Pattern: ${shiftPattern.id}`);
console.log(` - Day: ${shiftPattern.dayOfWeek}, TimeSlot: ${shiftPattern.timeSlotId}`);
console.log(` - Matching scheduled shifts: ${matchingScheduledShifts.length}`);
if (assignmentResult) {
const assignments = assignmentResult.assignments[shiftPattern.id] || [];
console.log(` - Assignments: ${assignments.length} employees`);
}
});
};
// Rufe die Debug-Funktion auf, wenn Assignment-Ergebnisse geladen werden
useEffect(() => {
if (assignmentResult && shiftPlan) {
debugShiftMatching();
}
}, [assignmentResult, shiftPlan]);
const canPublish = () => {
if (!shiftPlan || shiftPlan.status === 'published') return false;
// Check if all active employees have set their availabilities
const employeesWithoutAvailabilities = employees.filter(emp => {
const empAvailabilities = availabilities.filter(avail => avail.employeeId === emp.id);
return empAvailabilities.length === 0;
});
return employeesWithoutAvailabilities.length === 0;
};
const canPublishAssignment = (): boolean => {
if (!assignmentResult) return false;
// Check if assignment was successful
if (assignmentResult.success === false) return false;
// Check if there are any critical violations
const hasCriticalViolations = assignmentResult.violations.some(v =>
v.includes('ERROR:') || v.includes('KRITISCH:')
);
return !hasCriticalViolations;
};
const getAvailabilityStatus = () => {
const totalEmployees = employees.length;
const employeesWithAvailabilities = new Set(
availabilities.map(avail => avail.employeeId)
).size;
return {
completed: employeesWithAvailabilities,
total: totalEmployees,
percentage: Math.round((employeesWithAvailabilities / totalEmployees) * 100)
};
};
const reloadAvailabilities = async () => {
try {
console.log('🔄 Lade Verfügbarkeiten neu...');
// Load availabilities for all employees
const availabilityPromises = employees
.filter(emp => emp.isActive)
.map(emp => employeeService.getAvailabilities(emp.id));
const allAvailabilities = await Promise.all(availabilityPromises);
const flattenedAvailabilities = allAvailabilities.flat();
// Filter availabilities to only include those for the current shift plan
const planAvailabilities = flattenedAvailabilities.filter(
availability => availability.planId === id
);
setAvailabilities(planAvailabilities);
console.log('✅ Verfügbarkeiten neu geladen:', planAvailabilities.length);
} catch (error) {
console.error('❌ Fehler beim Neuladen der Verfügbarkeiten:', error);
}
};
const getAssignmentsForScheduledShift = (scheduledShift: ScheduledShift): string[] => {
if (!assignmentResult) return [];
const dayOfWeek = getDayOfWeek(scheduledShift.date);
// Find the corresponding shift pattern for this day and time slot
const shiftPattern = shiftPlan?.shifts?.find(shift =>
shift.dayOfWeek === dayOfWeek &&
shift.timeSlotId === scheduledShift.timeSlotId
);
if (shiftPattern && assignmentResult.assignments[shiftPattern.id]) {
console.log(`✅ Found assignments for shift pattern ${shiftPattern.id}:`, assignmentResult.assignments[shiftPattern.id]);
return assignmentResult.assignments[shiftPattern.id];
}
// Fallback: Check if there's a direct match with scheduled shift ID (unlikely)
if (assignmentResult.assignments[scheduledShift.id]) {
console.log(`⚠️ Using direct scheduled shift assignment for ${scheduledShift.id}`);
return assignmentResult.assignments[scheduledShift.id];
}
console.warn(`❌ No assignments found for scheduled shift ${scheduledShift.id} (Day ${dayOfWeek}, TimeSlot ${scheduledShift.timeSlotId})`);
return [];
};
// Render timetable using the same structure as AvailabilityManager // Render timetable using the same structure as AvailabilityManager
const renderTimetable = () => { const renderTimetable = () => {
@@ -845,6 +431,7 @@ const ShiftPlanView: React.FC = () => {
borderCollapse: 'collapse', borderCollapse: 'collapse',
backgroundColor: 'white' backgroundColor: 'white'
}}> }}>
{/* Table content remains the same */}
<thead> <thead>
<tr style={{ backgroundColor: '#f8f9fa' }}> <tr style={{ backgroundColor: '#f8f9fa' }}>
<th style={{ <th style={{
@@ -1085,26 +672,31 @@ const ShiftPlanView: React.FC = () => {
borderTop: '1px solid #e0e0e0', borderTop: '1px solid #e0e0e0',
display: 'flex', display: 'flex',
justifyContent: 'flex-end', justifyContent: 'flex-end',
alignItems: 'center', alignItems: 'center'
gap: '10px'
}}> }}>
<div style={{ position: 'relative', display: 'inline-block' }}> <div
{/* Export Dropdown */} ref={containerRef}
style={{
position: 'relative',
display: 'flex',
gap: '10px',
alignItems: 'center',
minHeight: '40px'
}}
>
{/* Export Dropdown - Always visible but moves when option selected */}
<div <div
ref={dropdownRef} ref={dropdownRef}
style={{ style={{
position: 'relative', position: 'relative',
transform: showExportButton ? `translateX(-${dropdownWidth}px)` : 'translateX(0)', transform: selectedExportType ? `translateX(-${dropdownWidth + 10}px)` : 'translateX(0)',
opacity: showExportButton ? 0 : 1, transition: 'transform 0.3s ease',
transition: 'all 0.3s ease', zIndex: selectedExportType ? 1 : 2
pointerEvents: showExportButton ? 'none' : 'auto'
}} }}
> >
<select <select
value="" value={selectedExportType}
onChange={(e) => handleExportTypeSelect(e.target.value)} onChange={(e) => handleExportTypeChange(e.target.value)}
onFocus={() => setExportDropdownOpen(true)}
onBlur={() => setTimeout(() => setExportDropdownOpen(false), 200)}
style={{ style={{
padding: '8px 16px', padding: '8px 16px',
border: '1px solid #ddd', border: '1px solid #ddd',
@@ -1120,30 +712,53 @@ const ShiftPlanView: React.FC = () => {
</select> </select>
</div> </div>
{/* Export Button */} {/* Export Button - Only shows when an export type is selected */}
{showExportButton && ( {selectedExportType && (
<button <div style={{
onClick={handleExport} display: 'flex',
disabled={exporting} gap: '8px',
style={{ alignItems: 'center',
padding: '8px 16px', position: 'absolute',
backgroundColor: '#51258f', right: 0,
color: 'white', top: 0,
border: 'none', bottom: 0,
borderRadius: '4px', opacity: selectedExportType ? 1 : 0,
cursor: exporting ? 'not-allowed' : 'pointer', transform: selectedExportType ? 'translateX(0)' : 'translateX(20px)',
fontWeight: 'bold', transition: 'all 0.3s ease'
position: 'absolute', }}>
right: 0, <button
top: 0, onClick={handleExport}
opacity: showExportButton ? 1 : 0, disabled={exporting}
transform: showExportButton ? 'translateX(0)' : 'translateX(20px)', style={{
transition: 'all 0.3s ease', padding: '8px 16px',
minWidth: '120px' backgroundColor: '#51258f',
}} color: 'white',
> border: 'none',
{exporting ? 'Exportiert...' : 'EXPORT'} borderRadius: '4px',
</button> cursor: exporting ? 'not-allowed' : 'pointer',
fontWeight: 'bold',
minWidth: '100px'
}}
>
{exporting ? 'Exportiert...' : 'EXPORT'}
</button>
<button
onClick={handleCancelExport}
disabled={exporting}
style={{
padding: '8px 12px',
backgroundColor: '#95a5a6',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: exporting ? 'not-allowed' : 'pointer',
fontWeight: 'bold'
}}
title="Abbrechen"
>
</button>
</div>
)} )}
</div> </div>
</div> </div>
@@ -1152,6 +767,8 @@ const ShiftPlanView: React.FC = () => {
); );
}; };
// ... (rest of the component remains the same, including all the other functions)
if (loading) return <div>Lade Schichtplan...</div>; if (loading) return <div>Lade Schichtplan...</div>;
if (!shiftPlan) return <div>Schichtplan nicht gefunden</div>; if (!shiftPlan) return <div>Schichtplan nicht gefunden</div>;
@@ -1224,280 +841,7 @@ const ShiftPlanView: React.FC = () => {
</div> </div>
</div> </div>
{/* Availability Status - only show for drafts */} {/* ... (rest of the JSX remains the same) */}
{shiftPlan.status === 'draft' && (
<div style={{
backgroundColor: 'white',
borderRadius: '8px',
padding: '20px',
marginBottom: '20px',
boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
}}>
<h3>Veröffentlichungsvoraussetzungen</h3>
<div style={{ display: 'flex', alignItems: 'center', gap: '20px', marginBottom: '15px' }}>
<div>
<div style={{ fontSize: '14px', color: '#666', marginBottom: '5px' }}>
Verfügbarkeitseinträge:
</div>
<div style={{ fontSize: '18px', fontWeight: 'bold' }}>
{availabilityStatus.completed} / {availabilityStatus.total} Mitarbeiter
</div>
<div style={{
width: '200px',
height: '8px',
backgroundColor: '#e0e0e0',
borderRadius: '4px',
marginTop: '5px',
overflow: 'hidden'
}}>
<div
style={{
width: `${availabilityStatus.percentage}%`,
height: '100%',
backgroundColor: availabilityStatus.percentage === 100 ? '#2ecc71' : '#f1c40f',
transition: 'all 0.3s ease'
}}
/>
</div>
</div>
{hasRole(['admin', 'maintenance']) && (
<div>
<button
onClick={handlePreviewAssignment}
disabled={!canPublish() || publishing}
style={{
padding: '10px 20px',
backgroundColor: canPublish() ? '#3498db' : '#95a5a6',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: canPublish() ? 'pointer' : 'not-allowed',
fontWeight: 'bold'
}}
>
{publishing ? 'Berechne...' : 'Automatisch zuweisen'}
</button>
{!canPublish() && (
<div style={{ fontSize: '12px', color: '#666', marginTop: '5px' }}>
{availabilityStatus.percentage === 100
? 'Bereit zur Berechnung'
: `${availabilityStatus.total - availabilityStatus.completed} Mitarbeiter müssen noch Verfügbarkeit eintragen`}
</div>
)}
</div>
)}
</div>
{/* Plan Structure Info */}
<div style={{
backgroundColor: '#e8f4fd',
border: '1px solid #b8d4f0',
borderRadius: '4px',
padding: '12px 16px',
fontSize: '14px'
}}>
<strong>Plan-Struktur:</strong> {allTimeSlots.length} Schichttypen an {days.length} Tagen
</div>
</div>
)}
{/* Assignment Preview Modal */}
{(showAssignmentPreview || assignmentResult) && (
<div style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0,0,0,0.5)',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
zIndex: 1000
}}>
<div style={{
backgroundColor: 'white',
borderRadius: '8px',
padding: '30px',
maxWidth: '800px',
maxHeight: '80vh',
overflow: 'auto',
width: '90%'
}}>
<h2>Wochenmuster-Zuordnung</h2>
{/* Detaillierter Reparatur-Bericht anzeigen */}
{assignmentResult?.resolutionReport && (
<div style={{
backgroundColor: '#f8f9fa',
border: '1px solid #e9ecef',
borderRadius: '4px',
padding: '15px',
marginBottom: '20px',
fontSize: '14px',
maxHeight: '400px',
overflow: 'auto'
}}>
<h4 style={{ color: '#2c3e50', marginTop: 0, display: 'flex', alignItems: 'center', gap: '10px' }}>
<span>📋</span> Detaillierter Reparatur-Bericht
</h4>
<div style={{
fontFamily: 'monospace',
fontSize: '12px',
lineHeight: '1.4'
}}>
{assignmentResult.resolutionReport.map((line, index) => {
let color = '#2c3e50';
let fontWeight = 'normal';
if (line.includes('✅') || line.includes('ALLES KRITISCHEN PROBLEME BEHOBEN')) {
color = '#2ecc71';
fontWeight = 'bold';
} else if (line.includes('❌') || line.includes('KRITISCHEN PROBLEME')) {
color = '#e74c3c';
fontWeight = 'bold';
} else if (line.includes('⚠️')) {
color = '#f39c12';
} else if (line.includes('📊') || line.includes('🔧') || line.includes('📅') || line.includes('🚨') || line.includes('🛠️') || line.includes('💡') || line.includes('🎯')) {
color = '#3498db';
fontWeight = 'bold';
} else if (line.startsWith(' •') || line.startsWith(' -')) {
color = '#7f8c8d';
}
return (
<div key={index} style={{
color,
fontWeight,
marginBottom: line === '' ? '5px' : '2px',
paddingLeft: line.startsWith(' ') ? '20px' : '0px'
}}>
{line}
</div>
);
})}
</div>
</div>
)}
{/* ZUSAMMENFASSUNG */}
{assignmentResult && (
<div style={{ marginBottom: '20px' }}>
<h4>Zusammenfassung:</h4>
{/* Entscheidung basierend auf tatsächlichen kritischen Problemen */}
{(assignmentResult.violations.length === 0) || assignmentResult.success == true ? (
<div style={{
padding: '15px',
backgroundColor: '#d4edda',
border: '1px solid #c3e6cb',
borderRadius: '4px',
color: '#155724',
marginBottom: '15px'
}}>
<h5 style={{ margin: '0 0 10px 0', color: '#155724' }}> Bereit zur Veröffentlichung</h5>
<p style={{ margin: 0 }}>
Alle kritischen Probleme wurden behoben. Der Schichtplan kann veröffentlicht werden.
</p>
</div>
) : (
<div style={{
padding: '15px',
backgroundColor: '#f8d7da',
border: '1px solid #f5c6cb',
borderRadius: '4px',
color: '#721c24',
marginBottom: '15px'
}}>
<h5 style={{ margin: '0 0 10px 0', color: '#721c24' }}> Kritische Probleme</h5>
<p style={{ margin: '0 0 10px 0' }}>
Folgende kritische Probleme müssen behoben werden, bevor der Plan veröffentlicht werden kann:
</p>
<ul style={{ margin: 0, paddingLeft: '20px' }}>
{assignmentResult.violations
.filter(v => v.includes('ERROR:') || v.includes('❌ KRITISCH:'))
.map((violation, index) => (
<li key={index} style={{ fontSize: '14px' }}>
{violation.replace('ERROR: ', '').replace('❌ KRITISCH: ', '')}
</li>
))}
</ul>
</div>
)}
{/* Warnungen separat anzeigen - NUR wenn welche vorhanden sind */}
{assignmentResult.violations.some(v => v.includes('WARNING:') || v.includes('⚠️')) && (
<div style={{
padding: '10px',
backgroundColor: '#fff3cd',
border: '1px solid #ffeaa7',
borderRadius: '4px',
color: '#856404'
}}>
<h6 style={{ margin: '0 0 5px 0', color: '#856404' }}>
Hinweise & Warnungen
</h6>
<ul style={{ margin: 0, paddingLeft: '20px' }}>
{assignmentResult.violations
.filter(v => v.includes('WARNING:') || v.includes('⚠️'))
.map((warning, index) => (
<li key={index} style={{ fontSize: '13px' }}>
{warning.replace('WARNING: ', '').replace('⚠️ WARNHINWEIS: ', '')}
</li>
))}
</ul>
</div>
)}
</div>
)}
<div style={{ display: 'flex', gap: '10px', justifyContent: 'flex-end' }}>
<button
onClick={() => {
setShowAssignmentPreview(false);
setAssignmentResult(null);
}}
style={{
padding: '8px 16px',
backgroundColor: '#95a5a6',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}
>
Abbrechen
</button>
{/* BUTTON zum publishen */}
<button
onClick={handlePublish}
disabled={publishing || !canPublishAssignment()}
style={{
padding: '10px 20px',
backgroundColor: canPublishAssignment() ? '#2ecc71' : '#95a5a6',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: canPublishAssignment() ? 'pointer' : 'not-allowed',
fontWeight: 'bold',
fontSize: '16px'
}}
>
{publishing ? 'Veröffentliche...' : (
assignmentResult ? (
canPublishAssignment()
? 'Schichtplan veröffentlichen'
: 'Kritische Probleme müssen behoben werden'
) : 'Lade Zuordnungen...'
)}
</button>
</div>
</div>
</div>
)}
{/* Timetable */} {/* Timetable */}
<div style={{ <div style={{