mirror of
https://github.com/donpat1to/Schichtenplaner.git
synced 2025-12-01 06:55:45 +01:00
updated more simple modern layout
This commit is contained in:
@@ -20,9 +20,9 @@ interface DashboardData {
|
||||
}>;
|
||||
teamStats: {
|
||||
totalEmployees: number;
|
||||
availableToday: number;
|
||||
onVacation: number;
|
||||
sickLeave: number;
|
||||
manager: number;
|
||||
trainee: number;
|
||||
experienced: number;
|
||||
};
|
||||
recentPlans: ShiftPlan[];
|
||||
}
|
||||
@@ -36,9 +36,9 @@ const Dashboard: React.FC = () => {
|
||||
upcomingShifts: [],
|
||||
teamStats: {
|
||||
totalEmployees: 0,
|
||||
availableToday: 0,
|
||||
onVacation: 0,
|
||||
sickLeave: 0
|
||||
manager: 0,
|
||||
trainee: 0,
|
||||
experienced: 0
|
||||
},
|
||||
recentPlans: []
|
||||
});
|
||||
@@ -205,16 +205,17 @@ const Dashboard: React.FC = () => {
|
||||
|
||||
const calculateTeamStats = (employees: Employee[]) => {
|
||||
const totalEmployees = employees.length;
|
||||
|
||||
// For now, we'll use simpler calculations
|
||||
// In a real app, you'd check actual availability from the database
|
||||
const availableToday = Math.max(1, Math.floor(totalEmployees * 0.7)); // 70% available as estimate
|
||||
|
||||
|
||||
// Count by type
|
||||
const managerCount = employees.filter(e => e.employeeType === 'manager').length;
|
||||
const traineeCount = employees.filter(e => e.employeeType === 'trainee').length;
|
||||
const experiencedCount = employees.filter(e => e.employeeType === 'experienced').length;
|
||||
|
||||
return {
|
||||
totalEmployees,
|
||||
availableToday,
|
||||
onVacation: 0, // Would need vacation tracking
|
||||
sickLeave: 0 // Would need sick leave tracking
|
||||
manager: managerCount,
|
||||
trainee: traineeCount,
|
||||
experienced: experiencedCount,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -367,19 +368,6 @@ const Dashboard: React.FC = () => {
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleRefresh}
|
||||
style={{
|
||||
padding: '8px 16px',
|
||||
backgroundColor: '#3498db',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
🔄 Aktualisieren
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<PlanDebugInfo />
|
||||
@@ -557,27 +545,27 @@ const Dashboard: React.FC = () => {
|
||||
<h3 style={{ margin: '0 0 15px 0', color: '#2c3e50' }}>👥 Team-Übersicht</h3>
|
||||
<div style={{ display: 'grid', gap: '12px' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<span>Gesamt Mitarbeiter:</span>
|
||||
<span>Mitarbeiter:</span>
|
||||
<span style={{ fontWeight: 'bold', fontSize: '18px' }}>
|
||||
{data.teamStats.totalEmployees}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<span>Verfügbar heute:</span>
|
||||
<span>Chef:</span>
|
||||
<span style={{ fontWeight: 'bold', color: '#2ecc71' }}>
|
||||
{data.teamStats.availableToday}
|
||||
{data.teamStats.manager}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<span>Im Urlaub:</span>
|
||||
<span>Erfahrene:</span>
|
||||
<span style={{ fontWeight: 'bold', color: '#f39c12' }}>
|
||||
{data.teamStats.onVacation}
|
||||
{data.teamStats.experienced}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<span>Krankgeschrieben:</span>
|
||||
<span>Neue:</span>
|
||||
<span style={{ fontWeight: 'bold', color: '#e74c3c' }}>
|
||||
{data.teamStats.sickLeave}
|
||||
{data.teamStats.trainee}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -649,7 +637,7 @@ const Dashboard: React.FC = () => {
|
||||
border: '1px solid #e0e0e0',
|
||||
boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
|
||||
}}>
|
||||
<h3 style={{ margin: '0 0 15px 0', color: '#2c3e50' }}>📝 Letzte Schichtpläne</h3>
|
||||
<h3 style={{ margin: '0 0 15px 0', color: '#2c3e50' }}>📝 Schichtpläne</h3>
|
||||
{data.recentPlans.length > 0 ? (
|
||||
<div style={{ display: 'grid', gap: '12px' }}>
|
||||
{data.recentPlans.map(plan => (
|
||||
|
||||
@@ -313,28 +313,42 @@ const Settings: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Editable name */}
|
||||
<div>
|
||||
<label style={{ display: 'block', marginBottom: '8px', fontWeight: 'bold', color: '#2c3e50' }}>
|
||||
Vollständiger Name *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
value={profileForm.name}
|
||||
onChange={handleProfileChange}
|
||||
required
|
||||
style={{
|
||||
width: '97%',
|
||||
padding: '10px',
|
||||
border: '1px solid #ddd',
|
||||
borderRadius: '4px',
|
||||
fontSize: '16px'
|
||||
}}
|
||||
placeholder="Ihr vollständiger Name"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
marginTop: '10px',
|
||||
padding: '15px',
|
||||
backgroundColor: '#f8f9fa',
|
||||
borderRadius: '6px',
|
||||
border: '1px solid #e9ecef',
|
||||
}}
|
||||
>
|
||||
<label
|
||||
style={{
|
||||
display: 'block',
|
||||
marginBottom: '8px',
|
||||
fontWeight: 'bold',
|
||||
color: '#2c3e50',
|
||||
}}
|
||||
>
|
||||
Vollständiger Name *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
value={profileForm.name}
|
||||
onChange={handleProfileChange}
|
||||
required
|
||||
style={{
|
||||
width: '97.5%',
|
||||
padding: '10px',
|
||||
border: '1px solid #ddd',
|
||||
borderRadius: '4px',
|
||||
fontSize: '16px',
|
||||
}}
|
||||
placeholder="Ihr vollständiger Name"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
|
||||
@@ -6,7 +6,7 @@ import { shiftPlanService } from '../../services/shiftPlanService';
|
||||
import { employeeService } from '../../services/employeeService';
|
||||
import { shiftAssignmentService, ShiftAssignmentService } from '../../services/shiftAssignmentService';
|
||||
import { AssignmentResult } from '../../services/scheduling';
|
||||
import { ShiftPlan, TimeSlot } from '../../models/ShiftPlan';
|
||||
import { ShiftPlan, TimeSlot, ScheduledShift } from '../../models/ShiftPlan';
|
||||
import { Employee, EmployeeAvailability } from '../../models/Employee';
|
||||
import { useNotification } from '../../contexts/NotificationContext';
|
||||
import { formatDate, formatTime } from '../../utils/foramatters';
|
||||
@@ -39,11 +39,13 @@ const ShiftPlanView: React.FC = () => {
|
||||
const [assignmentResult, setAssignmentResult] = useState<AssignmentResult | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [publishing, setPublishing] = useState(false);
|
||||
const [scheduledShifts, setScheduledShifts] = useState<ScheduledShift[]>([]);
|
||||
const [reverting, setReverting] = useState(false);
|
||||
const [showAssignmentPreview, setShowAssignmentPreview] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
loadShiftPlanData();
|
||||
debugScheduledShifts();
|
||||
}, [id]);
|
||||
|
||||
const loadShiftPlanData = async () => {
|
||||
@@ -51,13 +53,15 @@ const ShiftPlanView: React.FC = () => {
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
const [plan, employeesData] = await Promise.all([
|
||||
const [plan, employeesData, shiftsData] = await Promise.all([
|
||||
shiftPlanService.getShiftPlan(id),
|
||||
employeeService.getEmployees()
|
||||
employeeService.getEmployees(),
|
||||
shiftAssignmentService.getScheduledShiftsForPlan(id) // Load shifts here
|
||||
]);
|
||||
|
||||
setShiftPlan(plan);
|
||||
setEmployees(employeesData.filter(emp => emp.isActive));
|
||||
setScheduledShifts(shiftsData);
|
||||
|
||||
// Load availabilities for all employees
|
||||
const availabilityPromises = employeesData
|
||||
@@ -73,6 +77,7 @@ const ShiftPlanView: React.FC = () => {
|
||||
);
|
||||
|
||||
setAvailabilities(planAvailabilities);
|
||||
debugAvailabilities();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading shift plan data:', error);
|
||||
@@ -86,8 +91,70 @@ const ShiftPlanView: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const debugAvailabilities = () => {
|
||||
if (!shiftPlan || !employees.length || !availabilities.length) return;
|
||||
|
||||
console.log('🔍 AVAILABILITY ANALYSIS:', {
|
||||
totalAvailabilities: availabilities.length,
|
||||
employeesWithAvailabilities: new Set(availabilities.map(a => a.employeeId)).size,
|
||||
totalEmployees: employees.length,
|
||||
availabilityByEmployee: employees.map(emp => {
|
||||
const empAvailabilities = availabilities.filter(a => a.employeeId === emp.id);
|
||||
return {
|
||||
employee: emp.name,
|
||||
availabilities: empAvailabilities.length,
|
||||
preferences: empAvailabilities.map(a => ({
|
||||
day: a.dayOfWeek,
|
||||
timeSlot: a.timeSlotId,
|
||||
preference: a.preferenceLevel
|
||||
}))
|
||||
};
|
||||
})
|
||||
});
|
||||
|
||||
// Prüfe spezifisch für Manager/Admin
|
||||
const manager = employees.find(emp => emp.role === 'admin');
|
||||
if (manager) {
|
||||
const managerAvailabilities = availabilities.filter(a => a.employeeId === manager.id);
|
||||
console.log('🔍 MANAGER AVAILABILITIES:', {
|
||||
manager: manager.name,
|
||||
availabilities: managerAvailabilities.length,
|
||||
details: managerAvailabilities.map(a => ({
|
||||
day: a.dayOfWeek,
|
||||
timeSlot: a.timeSlotId,
|
||||
preference: a.preferenceLevel
|
||||
}))
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const debugScheduledShifts = async () => {
|
||||
if (!shiftPlan) return;
|
||||
|
||||
try {
|
||||
const shifts = await shiftAssignmentService.getScheduledShiftsForPlan(shiftPlan.id);
|
||||
console.log('🔍 SCHEDULED SHIFTS IN DATABASE:', {
|
||||
total: shifts.length,
|
||||
shifts: shifts.map(s => ({
|
||||
id: s.id,
|
||||
date: s.date,
|
||||
timeSlotId: s.timeSlotId,
|
||||
requiredEmployees: s.requiredEmployees
|
||||
}))
|
||||
});
|
||||
|
||||
// Check if we have any shifts at all
|
||||
if (shifts.length === 0) {
|
||||
console.error('❌ NO SCHEDULED SHIFTS IN DATABASE - This is the problem!');
|
||||
console.log('💡 Solution: Regenerate scheduled shifts for this plan');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Error loading scheduled shifts:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Extract plan-specific shifts using the same logic as AvailabilityManager
|
||||
const getTimetableData = () => {
|
||||
const getTimetableData = () => {
|
||||
if (!shiftPlan || !shiftPlan.shifts || !shiftPlan.timeSlots) {
|
||||
return { days: [], timeSlotsByDay: {}, allTimeSlots: [] };
|
||||
}
|
||||
@@ -199,11 +266,53 @@ const ShiftPlanView: React.FC = () => {
|
||||
});
|
||||
};*/
|
||||
|
||||
const debugAssignments = async () => {
|
||||
if (!shiftPlan) return;
|
||||
|
||||
try {
|
||||
const shifts = await shiftAssignmentService.getScheduledShiftsForPlan(shiftPlan.id);
|
||||
console.log('🔍 DEBUG - Scheduled Shifts nach Veröffentlichung:', {
|
||||
totalShifts: shifts.length,
|
||||
shiftsWithAssignments: shifts.filter(s => s.assignedEmployees && s.assignedEmployees.length > 0).length,
|
||||
allShifts: shifts.map(s => ({
|
||||
id: s.id,
|
||||
date: s.date,
|
||||
timeSlotId: s.timeSlotId,
|
||||
assignedEmployees: s.assignedEmployees,
|
||||
assignedCount: s.assignedEmployees?.length || 0
|
||||
}))
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Debug error:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePreviewAssignment = async () => {
|
||||
if (!shiftPlan) return;
|
||||
debugScheduledShifts();
|
||||
|
||||
try {
|
||||
setPublishing(true);
|
||||
|
||||
// DEBUG: Überprüfe die Eingabedaten
|
||||
console.log('🔍 INPUT DATA FOR SCHEDULING:', {
|
||||
shiftPlan: {
|
||||
id: shiftPlan.id,
|
||||
name: shiftPlan.name,
|
||||
shifts: shiftPlan.shifts?.length,
|
||||
timeSlots: shiftPlan.timeSlots?.length
|
||||
},
|
||||
employees: employees.length,
|
||||
availabilities: availabilities.length,
|
||||
employeeDetails: employees.map(emp => ({
|
||||
id: emp.id,
|
||||
name: emp.name,
|
||||
role: emp.role,
|
||||
employeeType: emp.employeeType,
|
||||
canWorkAlone: emp.canWorkAlone
|
||||
}))
|
||||
});
|
||||
|
||||
const result = await ShiftAssignmentService.assignShifts(
|
||||
shiftPlan,
|
||||
employees,
|
||||
@@ -215,6 +324,18 @@ const ShiftPlanView: React.FC = () => {
|
||||
}
|
||||
);
|
||||
|
||||
// DEBUG: Detaillierte Analyse des Results
|
||||
console.log('🔍 DETAILED ASSIGNMENT RESULT:', {
|
||||
totalAssignments: Object.keys(result.assignments).length,
|
||||
assignments: result.assignments,
|
||||
violations: result.violations,
|
||||
hasResolutionReport: !!result.resolutionReport,
|
||||
assignmentDetails: Object.entries(result.assignments).map(([shiftId, empIds]) => ({
|
||||
shiftId,
|
||||
employeeCount: empIds.length,
|
||||
employees: empIds
|
||||
}))
|
||||
});
|
||||
// DEBUG: Überprüfe die tatsächlichen Violations
|
||||
console.log('🔍 VIOLATIONS ANALYSIS:', {
|
||||
allViolations: result.violations,
|
||||
@@ -283,24 +404,24 @@ const ShiftPlanView: React.FC = () => {
|
||||
|
||||
console.log('🔄 Starting to publish assignments...');
|
||||
|
||||
const scheduledShifts = await shiftAssignmentService.getScheduledShiftsForPlan(shiftPlan.id);
|
||||
|
||||
// ✅ KORREKTUR: Verwende die neu geladenen Shifts
|
||||
const updatedShifts = await shiftAssignmentService.getScheduledShiftsForPlan(shiftPlan.id);
|
||||
|
||||
// Debug: Check if scheduled shifts exist
|
||||
if (!scheduledShifts || scheduledShifts.length === 0) {
|
||||
if (!updatedShifts || updatedShifts.length === 0) {
|
||||
throw new Error('No scheduled shifts found in the plan');
|
||||
}
|
||||
|
||||
// Update scheduled shifts with assignments
|
||||
const updatePromises = scheduledShifts.map(async (scheduledShift) => {
|
||||
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] || [];
|
||||
|
||||
console.log(`📝 Updating shift ${scheduledShift.id} with`, assignedEmployees.length, 'employees');
|
||||
console.log(`📝 Updating shift ${scheduledShift.id} with`, assignedEmployees, 'employees');
|
||||
|
||||
try {
|
||||
// First, verify the shift exists
|
||||
await shiftAssignmentService.getScheduledShift(scheduledShift.id);
|
||||
|
||||
// Then update it
|
||||
// Update the shift with assigned employees
|
||||
await shiftAssignmentService.updateScheduledShift(scheduledShift.id, {
|
||||
assignedEmployees
|
||||
});
|
||||
@@ -320,26 +441,43 @@ const ShiftPlanView: React.FC = () => {
|
||||
status: 'published'
|
||||
});
|
||||
|
||||
// ✅ KORREKTUR: Explizit alle Daten neu laden und State aktualisieren
|
||||
const [reloadedPlan, reloadedShifts] = await Promise.all([
|
||||
shiftPlanService.getShiftPlan(shiftPlan.id),
|
||||
shiftAssignmentService.getScheduledShiftsForPlan(shiftPlan.id)
|
||||
]);
|
||||
|
||||
setShiftPlan(reloadedPlan);
|
||||
setScheduledShifts(reloadedShifts);
|
||||
|
||||
// Debug: Überprüfe die aktualisierten Daten
|
||||
console.log('🔍 After publish - Reloaded data:', {
|
||||
planStatus: reloadedPlan.status,
|
||||
scheduledShiftsCount: reloadedShifts.length,
|
||||
shiftsWithAssignments: reloadedShifts.filter(s => s.assignedEmployees && s.assignedEmployees.length > 0).length,
|
||||
allAssignments: reloadedShifts.map(s => ({
|
||||
id: s.id,
|
||||
date: s.date,
|
||||
assigned: s.assignedEmployees
|
||||
}))
|
||||
});
|
||||
|
||||
showNotification({
|
||||
type: 'success',
|
||||
title: 'Erfolg',
|
||||
message: 'Schichtplan wurde erfolgreich veröffentlicht!'
|
||||
});
|
||||
|
||||
// Reload the plan to reflect changes
|
||||
loadShiftPlanData();
|
||||
setShowAssignmentPreview(false);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error publishing shift plan:', error);
|
||||
|
||||
|
||||
let message = 'Unbekannter Fehler';
|
||||
if (error instanceof Error) {
|
||||
message = error.message;
|
||||
} else if (typeof error === 'string') {
|
||||
message = error;
|
||||
}
|
||||
|
||||
|
||||
showNotification({
|
||||
type: 'error',
|
||||
title: 'Fehler',
|
||||
@@ -378,7 +516,7 @@ const ShiftPlanView: React.FC = () => {
|
||||
message: 'Schichtplan wurde erfolgreich zurück in den Entwurfsstatus gesetzt. Alle Daten wurden neu geladen.'
|
||||
});
|
||||
|
||||
const scheduledShifts = await shiftAssignmentService.getScheduledShiftsForPlan(shiftPlan.id);
|
||||
//const scheduledShifts = await shiftAssignmentService.getScheduledShiftsForPlan(shiftPlan.id);
|
||||
console.log('Scheduled shifts after revert:', {
|
||||
hasScheduledShifts: !! scheduledShifts,
|
||||
count: scheduledShifts.length || 0,
|
||||
@@ -422,15 +560,38 @@ const ShiftPlanView: React.FC = () => {
|
||||
};
|
||||
};
|
||||
|
||||
const debugCurrentState = () => {
|
||||
console.log('🔍 CURRENT STATE DEBUG:', {
|
||||
shiftPlan: shiftPlan ? {
|
||||
id: shiftPlan.id,
|
||||
name: shiftPlan.name,
|
||||
status: shiftPlan.status
|
||||
} : null,
|
||||
scheduledShifts: {
|
||||
count: scheduledShifts.length,
|
||||
withAssignments: scheduledShifts.filter(s => s.assignedEmployees && s.assignedEmployees.length > 0).length,
|
||||
details: scheduledShifts.map(s => ({
|
||||
id: s.id,
|
||||
date: s.date,
|
||||
timeSlotId: s.timeSlotId,
|
||||
assignedEmployees: s.assignedEmployees
|
||||
}))
|
||||
},
|
||||
employees: employees.length
|
||||
});
|
||||
};
|
||||
|
||||
// Render timetable using the same structure as AvailabilityManager
|
||||
const renderTimetable = async () => {
|
||||
const renderTimetable = () => {
|
||||
debugAssignments();
|
||||
debugCurrentState();
|
||||
const { days, allTimeSlots, timeSlotsByDay } = getTimetableData();
|
||||
if (!shiftPlan?.id) {
|
||||
console.warn("Shift plan ID is missing");
|
||||
return []; // safely exit
|
||||
return null;
|
||||
}
|
||||
|
||||
const scheduledShifts = await shiftAssignmentService.getScheduledShiftsForPlan(shiftPlan.id);
|
||||
//const scheduledShifts = await shiftAssignmentService.getScheduledShiftsForPlan(shiftPlan.id);
|
||||
|
||||
|
||||
if (days.length === 0 || allTimeSlots.length === 0) {
|
||||
@@ -534,10 +695,8 @@ const ShiftPlanView: React.FC = () => {
|
||||
// Get assigned employees for this shift
|
||||
let assignedEmployees: string[] = [];
|
||||
let displayText = '';
|
||||
|
||||
|
||||
|
||||
if (shiftPlan?.status === 'published' && scheduledShifts) {
|
||||
if (shiftPlan?.status === 'published') {
|
||||
// For published plans, use actual assignments from scheduled shifts
|
||||
const scheduledShift = scheduledShifts.find(scheduled => {
|
||||
const scheduledDayOfWeek = getDayOfWeek(scheduled.date);
|
||||
@@ -554,7 +713,7 @@ const ShiftPlanView: React.FC = () => {
|
||||
}
|
||||
} else if (assignmentResult) {
|
||||
// For draft with preview, use assignment result
|
||||
const scheduledShift = scheduledShifts?.find(scheduled => {
|
||||
const scheduledShift = scheduledShifts.find(scheduled => {
|
||||
const scheduledDayOfWeek = getDayOfWeek(scheduled.date);
|
||||
return scheduledDayOfWeek === weekday.id &&
|
||||
scheduled.timeSlotId === timeSlot.id;
|
||||
|
||||
Reference in New Issue
Block a user