updated more simple modern layout

This commit is contained in:
2025-10-16 20:41:51 +02:00
parent 7b2256c0ed
commit b86040dc04
13 changed files with 1167 additions and 226 deletions

View File

@@ -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 => (

View File

@@ -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={{

View File

@@ -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;