diff --git a/frontend/package.json b/frontend/package.json index bd48498..caff72c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -27,7 +27,10 @@ "esbuild": "^0.21.0", "terser": "5.44.0", "babel-plugin-transform-remove-console": "6.9.4", - "framer-motion": "12.23.24" + "framer-motion": "12.23.24", + "file-saver": "2.0.5", + "@types/file-saver": "2.0.5" + }, "scripts": { "dev": "vite dev", diff --git a/frontend/src/pages/ShiftPlans/ShiftPlanView.tsx b/frontend/src/pages/ShiftPlans/ShiftPlanView.tsx index eb83b9c..1c9e8b8 100644 --- a/frontend/src/pages/ShiftPlans/ShiftPlanView.tsx +++ b/frontend/src/pages/ShiftPlans/ShiftPlanView.tsx @@ -10,6 +10,7 @@ import { ShiftPlan, TimeSlot, ScheduledShift } from '../../models/ShiftPlan'; import { Employee, EmployeeAvailability } from '../../models/Employee'; import { useNotification } from '../../contexts/NotificationContext'; import { formatDate, formatTime } from '../../utils/foramatters'; +import { saveAs } from 'file-saver'; // Local interface extensions (same as AvailabilityManager) interface ExtendedTimeSlot extends TimeSlot { @@ -54,6 +55,7 @@ const ShiftPlanView: React.FC = () => { const [scheduledShifts, setScheduledShifts] = useState([]); const [showAssignmentPreview, setShowAssignmentPreview] = useState(false); const [recreating, setRecreating] = useState(false); + const [exporting, setExporting] = useState(false); useEffect(() => { loadShiftPlanData(); @@ -240,6 +242,66 @@ const ShiftPlanView: React.FC = () => { }; }; + const handleExportExcel = async () => { + if (!shiftPlan) return; + + try { + setExporting(true); + + // Call the export service + const blob = await shiftPlanService.exportShiftPlanToExcel(shiftPlan.id); + + // Use file-saver to download the file + saveAs(blob, `Schichtplan_${shiftPlan.name}_${new Date().toISOString().split('T')[0]}.xlsx`); + + showNotification({ + type: 'success', + title: 'Export erfolgreich', + message: 'Der Schichtplan wurde als Excel-Datei exportiert.' + }); + + } catch (error) { + console.error('Error exporting to Excel:', error); + showNotification({ + type: 'error', + title: 'Export fehlgeschlagen', + message: 'Der Excel-Export konnte nicht durchgeführt werden.' + }); + } finally { + setExporting(false); + } + }; + + const handleExportPDF = async () => { + if (!shiftPlan) return; + + try { + setExporting(true); + + // Call the PDF export service + const blob = await shiftPlanService.exportShiftPlanToPDF(shiftPlan.id); + + // Use file-saver to download the file + saveAs(blob, `Schichtplan_${shiftPlan.name}_${new Date().toISOString().split('T')[0]}.pdf`); + + showNotification({ + type: 'success', + title: 'Export erfolgreich', + message: 'Der Schichtplan wurde als PDF exportiert.' + }); + + } catch (error) { + console.error('Error exporting to PDF:', error); + showNotification({ + type: 'error', + title: 'Export fehlgeschlagen', + message: 'Der PDF-Export konnte nicht durchgeführt werden.' + }); + } finally { + setExporting(false); + } + }; + const loadShiftPlanData = async () => { if (!id) return; @@ -399,12 +461,12 @@ const ShiftPlanView: React.FC = () => { console.log('- Scheduled Shifts:', scheduledShifts.length); // DEBUG: Show shift pattern IDs - if (shiftPlan.shifts) { + /*if (shiftPlan.shifts) { console.log('📋 SHIFT PATTERN IDs:'); shiftPlan.shifts.forEach((shift, index) => { console.log(` ${index + 1}. ${shift.id} (Day ${shift.dayOfWeek}, TimeSlot ${shift.timeSlotId})`); }); - } + }*/ const constraints = { enforceNoTraineeAlone: true, @@ -650,6 +712,20 @@ const ShiftPlanView: React.FC = () => { 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( @@ -1005,7 +1081,50 @@ const ShiftPlanView: React.FC = () => {
- {shiftPlan.status === 'published' && hasRole(['admin', 'maintenance']) && ( + {shiftPlan.status === 'published' && hasRole(['admin', 'maintenance']) && ( + <> + + + + + )} + + {/* Your existing "Zuweisungen neu berechnen" button */} + {shiftPlan.status === 'published' && hasRole(['admin', 'maintenance']) && (
)} - {/* KORRIGIERTE ZUSAMMENFASSUNG */} + {/* ZUSAMMENFASSUNG */} {assignmentResult && (

Zusammenfassung:

{/* Entscheidung basierend auf tatsächlichen kritischen Problemen */} - {assignmentResult.violations.filter(v => - v.includes('ERROR:') || v.includes('❌ KRITISCH:') - ).length === 0 ? ( + {(assignmentResult.violations.length === 0) || assignmentResult.success == true ? (
{ {/* KORRIGIERTER BUTTON MIT TYPESCRIPT-FIX */}