mirror of
https://github.com/donpat1to/Schichtenplaner.git
synced 2025-12-01 06:55:45 +01:00
user creation works; saving employee availibilities works
This commit is contained in:
@@ -45,7 +45,7 @@ export const EMPLOYEE_TYPE_CONFIG = [
|
|||||||
|
|
||||||
export const ROLE_CONFIG = [
|
export const ROLE_CONFIG = [
|
||||||
{ value: 'user', label: 'Mitarbeiter', description: 'Kann eigene Schichten einsehen', color: '#27ae60' },
|
{ value: 'user', label: 'Mitarbeiter', description: 'Kann eigene Schichten einsehen', color: '#27ae60' },
|
||||||
{ value: 'instandhalter', label: 'Instandhalter', description: 'Kann Schichtpläne erstellen und Mitarbeiter verwalten', color: '#3498db' },
|
{ value: 'maintenance', label: 'Instandhalter', description: 'Kann Schichtpläne erstellen und Mitarbeiter verwalten', color: '#3498db' },
|
||||||
{ value: 'admin', label: 'Administrator', description: 'Voller Zugriff auf alle Funktionen', color: '#e74c3c' }
|
{ value: 'admin', label: 'Administrator', description: 'Voller Zugriff auf alle Funktionen', color: '#e74c3c' }
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const [availabilities, setAvailabilities] = useState<Availability[]>([]);
|
const [availabilities, setAvailabilities] = useState<Availability[]>([]);
|
||||||
const [shiftPlans, setShiftPlans] = useState<ShiftPlan[]>([]);
|
const [shiftPlans, setShiftPlans] = useState<ShiftPlan[]>([]);
|
||||||
|
const [usedDays, setUsedDays] = useState<{id: number, name: string}[]>([]);
|
||||||
const [selectedPlanId, setSelectedPlanId] = useState<string>('');
|
const [selectedPlanId, setSelectedPlanId] = useState<string>('');
|
||||||
const [selectedPlan, setSelectedPlan] = useState<ShiftPlan | null>(null);
|
const [selectedPlan, setSelectedPlan] = useState<ShiftPlan | null>(null);
|
||||||
const [timeSlots, setTimeSlots] = useState<ExtendedTimeSlot[]>([]);
|
const [timeSlots, setTimeSlots] = useState<ExtendedTimeSlot[]>([]);
|
||||||
@@ -61,10 +62,50 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedPlanId) {
|
if (selectedPlanId) {
|
||||||
loadSelectedPlan();
|
loadSelectedPlan();
|
||||||
|
} else {
|
||||||
|
setTimeSlots([]);
|
||||||
}
|
}
|
||||||
}, [selectedPlanId]);
|
}, [selectedPlanId]);
|
||||||
|
|
||||||
// Load time slots from shift plans
|
const getUsedDaysFromPlan = (plan: ShiftPlan | null) => {
|
||||||
|
if (!plan || !plan.shifts) return [];
|
||||||
|
|
||||||
|
const usedDays = new Set<number>();
|
||||||
|
plan.shifts.forEach(shift => {
|
||||||
|
usedDays.add(shift.dayOfWeek);
|
||||||
|
});
|
||||||
|
|
||||||
|
const daysArray = Array.from(usedDays).sort();
|
||||||
|
console.log('📅 VERWENDETE TAGE IM PLAN:', daysArray);
|
||||||
|
|
||||||
|
return daysArray.map(dayId => {
|
||||||
|
return daysOfWeek.find(day => day.id === dayId) || { id: dayId, name: `Tag ${dayId}` };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getUsedTimeSlotsFromPlan = (plan: ShiftPlan | null): ExtendedTimeSlot[] => {
|
||||||
|
if (!plan || !plan.shifts || !plan.timeSlots) return [];
|
||||||
|
|
||||||
|
const usedTimeSlotIds = new Set<string>();
|
||||||
|
plan.shifts.forEach(shift => {
|
||||||
|
usedTimeSlotIds.add(shift.timeSlotId);
|
||||||
|
});
|
||||||
|
|
||||||
|
const usedTimeSlots = plan.timeSlots
|
||||||
|
.filter(timeSlot => usedTimeSlotIds.has(timeSlot.id))
|
||||||
|
.map(timeSlot => ({
|
||||||
|
...timeSlot,
|
||||||
|
displayName: `${timeSlot.name} (${formatTime(timeSlot.startTime)}-${formatTime(timeSlot.endTime)})`,
|
||||||
|
source: `Plan: ${plan.name}`
|
||||||
|
}))
|
||||||
|
.sort((a, b) => a.startTime.localeCompare(b.startTime));
|
||||||
|
|
||||||
|
console.log('⏰ VERWENDETE ZEIT-SLOTS IM PLAN:', usedTimeSlots);
|
||||||
|
return usedTimeSlots;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Load time slots from shift plans - CORRECTED VERSION
|
||||||
const extractTimeSlotsFromPlans = (plans: ShiftPlan[]): ExtendedTimeSlot[] => {
|
const extractTimeSlotsFromPlans = (plans: ShiftPlan[]): ExtendedTimeSlot[] => {
|
||||||
console.log('🔄 EXTRAHIERE ZEIT-SLOTS AUS SCHICHTPLÄNEN:', plans);
|
console.log('🔄 EXTRAHIERE ZEIT-SLOTS AUS SCHICHTPLÄNEN:', plans);
|
||||||
|
|
||||||
@@ -78,10 +119,10 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Use timeSlots from plan if available
|
// Use timeSlots from plan if available
|
||||||
if (plan.timeSlots && Array.isArray(plan.timeSlots)) {
|
if (plan.timeSlots && Array.isArray(plan.timeSlots) && plan.timeSlots.length > 0) {
|
||||||
plan.timeSlots.forEach(timeSlot => {
|
plan.timeSlots.forEach(timeSlot => {
|
||||||
console.log(` 🔍 ZEIT-SLOT:`, timeSlot);
|
console.log(` 🔍 ZEIT-SLOT:`, timeSlot);
|
||||||
const key = `${timeSlot.startTime}-${timeSlot.endTime}`;
|
const key = timeSlot.id; // Use ID as key to avoid duplicates
|
||||||
if (!allTimeSlots.has(key)) {
|
if (!allTimeSlots.has(key)) {
|
||||||
allTimeSlots.set(key, {
|
allTimeSlots.set(key, {
|
||||||
...timeSlot,
|
...timeSlot,
|
||||||
@@ -90,23 +131,33 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
console.warn(`⚠️ PLAN ${plan.name} HAT KEINE TIME_SLOTS:`, plan.timeSlots);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also extract from shifts if timeSlots is empty
|
// Alternative: Extract from shifts if timeSlots array exists but is empty
|
||||||
if ((!plan.timeSlots || plan.timeSlots.length === 0) && plan.shifts && Array.isArray(plan.shifts)) {
|
if (plan.shifts && Array.isArray(plan.shifts) && plan.shifts.length > 0) {
|
||||||
plan.shifts.forEach(shift => {
|
console.log(`🔍 VERSUCHE TIME_SLOTS AUS SHIFTS ZU EXTRAHIEREN:`, plan.shifts.length);
|
||||||
console.log(` 🔍 SCHICHT:`, shift);
|
|
||||||
// For shifts, we need to find the corresponding time slot
|
// Create a set of unique timeSlotIds from shifts
|
||||||
const timeSlot = plan.timeSlots?.find(ts => ts.id === shift.timeSlotId);
|
const uniqueTimeSlotIds = new Set(plan.shifts.map(shift => shift.timeSlotId));
|
||||||
if (timeSlot) {
|
|
||||||
const key = `${timeSlot.startTime}-${timeSlot.endTime}`;
|
uniqueTimeSlotIds.forEach(timeSlotId => {
|
||||||
|
// Try to find time slot in plan's timeSlots first
|
||||||
|
const existingTimeSlot = plan.timeSlots?.find(ts => ts.id === timeSlotId);
|
||||||
|
|
||||||
|
if (existingTimeSlot) {
|
||||||
|
const key = existingTimeSlot.id;
|
||||||
if (!allTimeSlots.has(key)) {
|
if (!allTimeSlots.has(key)) {
|
||||||
allTimeSlots.set(key, {
|
allTimeSlots.set(key, {
|
||||||
...timeSlot,
|
...existingTimeSlot,
|
||||||
displayName: `${timeSlot.name} (${formatTime(timeSlot.startTime)}-${formatTime(timeSlot.endTime)})`,
|
displayName: `${existingTimeSlot.name} (${formatTime(existingTimeSlot.startTime)}-${formatTime(existingTimeSlot.endTime)})`,
|
||||||
source: `Plan: ${plan.name}`
|
source: `Plan: ${plan.name} (from shift)`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// If time slot not found in plan.timeSlots, create a basic one from the ID
|
||||||
|
console.warn(`⚠️ TIME_SLOT MIT ID ${timeSlotId} NICHT IN PLAN.TIME_SLOTS GEFUNDEN`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -116,42 +167,13 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
|||||||
a.startTime.localeCompare(b.startTime)
|
a.startTime.localeCompare(b.startTime)
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log('✅ ZEIT-SLOTS AUS PLÄNEN:', result);
|
console.log('✅ ZEIT-SLOTS AUS PLÄNEN GEFUNDEN:', result.length, result);
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*const getDefaultTimeSlots = (): ExtendedTimeSlot[] => {
|
|
||||||
console.log('⚠️ VERWENDE STANDARD-ZEIT-SLOTS');
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
id: 'slot-0800-1200',
|
|
||||||
name: 'Vormittag',
|
|
||||||
startTime: '08:00',
|
|
||||||
endTime: '12:00',
|
|
||||||
displayName: 'Vormittag (08:00-12:00)',
|
|
||||||
source: 'Standard'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'slot-1200-1600',
|
|
||||||
name: 'Nachmittag',
|
|
||||||
startTime: '12:00',
|
|
||||||
endTime: '16:00',
|
|
||||||
displayName: 'Nachmittag (12:00-16:00)',
|
|
||||||
source: 'Standard'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'slot-1600-2000',
|
|
||||||
name: 'Abend',
|
|
||||||
startTime: '16:00',
|
|
||||||
endTime: '20:00',
|
|
||||||
displayName: 'Abend (16:00-20:00)',
|
|
||||||
source: 'Standard'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};*/
|
|
||||||
|
|
||||||
const formatTime = (time: string): string => {
|
const formatTime = (time: string): string => {
|
||||||
return time.substring(0, 5);
|
if (!time) return '--:--';
|
||||||
|
return time.substring(0, 5); // Ensure HH:MM format
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
@@ -169,57 +191,40 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
|||||||
}));
|
}));
|
||||||
console.log('✅ VERFÜGBARKEITEN GELADEN:', existingAvailabilities.length);
|
console.log('✅ VERFÜGBARKEITEN GELADEN:', existingAvailabilities.length);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('⚠️ KEINE VERFÜGBARKEITEN GEFUNDEN');
|
console.log('⚠️ KEINE VERFÜGBARKEITEN GEFUNDEN ODER FEHLER:', err);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Load shift plans
|
// 2. Load shift plans
|
||||||
console.log('🔄 LADE SCHICHTPLÄNE...');
|
console.log('🔄 LADE SCHICHTPLÄNE...');
|
||||||
const plans = await shiftPlanService.getShiftPlans();
|
const plans = await shiftPlanService.getShiftPlans();
|
||||||
console.log('✅ SCHICHTPLÄNE GELADEN:', plans.length, plans);
|
console.log('✅ SCHICHTPLÄNE GELADEN:', plans.length);
|
||||||
|
|
||||||
// 3. Extract time slots from plans
|
|
||||||
let extractedTimeSlots = extractTimeSlotsFromPlans(plans);
|
|
||||||
|
|
||||||
/* 4. Fallback to default slots if none found
|
|
||||||
if (extractedTimeSlots.length === 0) {
|
|
||||||
console.log('⚠️ KEINE ZEIT-SLOTS GEFUNDEN, VERWENDE STANDARD-SLOTS');
|
|
||||||
extractedTimeSlots = getDefaultTimeSlots();
|
|
||||||
}*/
|
|
||||||
|
|
||||||
console.log('✅ GEFUNDENE ZEIT-SLOTS:', extractedTimeSlots.length, extractedTimeSlots);
|
|
||||||
|
|
||||||
setTimeSlots(extractedTimeSlots);
|
|
||||||
setShiftPlans(plans);
|
setShiftPlans(plans);
|
||||||
|
|
||||||
// 5. Create default availabilities if needed
|
// 3. Select first plan with actual shifts if available
|
||||||
if (existingAvailabilities.length === 0) {
|
if (plans.length > 0) {
|
||||||
const defaultAvailabilities: Availability[] = daysOfWeek.flatMap(day =>
|
// Find a plan that actually has shifts and time slots
|
||||||
extractedTimeSlots.map(slot => ({
|
const planWithShifts = plans.find(plan =>
|
||||||
id: `temp-${day.id}-${slot.id}`,
|
plan.shifts && plan.shifts.length > 0 &&
|
||||||
employeeId: employee.id,
|
plan.timeSlots && plan.timeSlots.length > 0
|
||||||
planId: '', // Will be set when saving
|
) || plans[0]; // Fallback to first plan
|
||||||
dayOfWeek: day.id,
|
|
||||||
timeSlotId: slot.id,
|
setSelectedPlanId(planWithShifts.id);
|
||||||
preferenceLevel: 3 as AvailabilityLevel,
|
console.log('✅ SCHICHTPLAN AUSGEWÄHLT:', planWithShifts.name);
|
||||||
isAvailable: false
|
|
||||||
}))
|
// Load the selected plan to get its actual used time slots and days
|
||||||
);
|
await loadSelectedPlan();
|
||||||
setAvailabilities(defaultAvailabilities);
|
|
||||||
console.log('✅ STANDARD-VERFÜGBARKEITEN ERSTELLT:', defaultAvailabilities.length);
|
|
||||||
} else {
|
} else {
|
||||||
setAvailabilities(existingAvailabilities);
|
setTimeSlots([]);
|
||||||
|
setUsedDays([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. Select first plan
|
// 4. Set existing availabilities
|
||||||
if (plans.length > 0) {
|
setAvailabilities(existingAvailabilities);
|
||||||
const publishedPlan = plans.find(plan => plan.status === 'published');
|
|
||||||
const firstPlan = publishedPlan || plans[0];
|
|
||||||
setSelectedPlanId(firstPlan.id);
|
|
||||||
console.log('✅ SCHICHTPLAN AUSGEWÄHLT:', firstPlan.name);
|
|
||||||
}
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('❌ FEHLER BEIM LADEN DER DATEN:', err);
|
console.error('❌ FEHLER BEIM LADEN DER DATEN:', err);
|
||||||
setError('Daten konnten nicht geladen werden');
|
setError('Daten konnten nicht geladen werden: ' + (err.message || 'Unbekannter Fehler'));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -233,11 +238,26 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
|||||||
console.log('✅ SCHICHTPLAN GELADEN:', {
|
console.log('✅ SCHICHTPLAN GELADEN:', {
|
||||||
name: plan.name,
|
name: plan.name,
|
||||||
timeSlotsCount: plan.timeSlots?.length || 0,
|
timeSlotsCount: plan.timeSlots?.length || 0,
|
||||||
shiftsCount: plan.shifts?.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
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Only show time slots and days that are actually used in the plan
|
||||||
|
const usedTimeSlots = getUsedTimeSlotsFromPlan(plan);
|
||||||
|
const usedDays = getUsedDaysFromPlan(plan);
|
||||||
|
|
||||||
|
console.log('✅ VERWENDETE DATEN:', {
|
||||||
|
timeSlots: usedTimeSlots.length,
|
||||||
|
days: usedDays.length,
|
||||||
|
dayIds: usedDays.map(d => d.id)
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeSlots(usedTimeSlots);
|
||||||
|
setUsedDays(usedDays); // We'll add this state variable
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('❌ FEHLER BEIM LADEN DES SCHICHTPLANS:', err);
|
console.error('❌ FEHLER BEIM LADEN DES SCHICHTPLANS:', err);
|
||||||
setError('Schichtplan konnte nicht geladen werden');
|
setError('Schichtplan konnte nicht geladen werden: ' + (err.message || 'Unbekannter Fehler'));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -267,7 +287,7 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
|||||||
const newAvailability: Availability = {
|
const newAvailability: Availability = {
|
||||||
id: `temp-${dayId}-${timeSlotId}-${Date.now()}`,
|
id: `temp-${dayId}-${timeSlotId}-${Date.now()}`,
|
||||||
employeeId: employee.id,
|
employeeId: employee.id,
|
||||||
planId: selectedPlanId || '', // Use selected plan if available
|
planId: selectedPlanId || '',
|
||||||
dayOfWeek: dayId,
|
dayOfWeek: dayId,
|
||||||
timeSlotId: timeSlotId,
|
timeSlotId: timeSlotId,
|
||||||
preferenceLevel: level,
|
preferenceLevel: level,
|
||||||
@@ -296,18 +316,34 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
|||||||
setSaving(true);
|
setSaving(true);
|
||||||
setError('');
|
setError('');
|
||||||
|
|
||||||
// Convert to EmployeeAvailability format for API
|
if (!selectedPlanId) {
|
||||||
const availabilitiesToSave: EmployeeAvailability[] = availabilities.map(avail => ({
|
setError('Bitte wählen Sie einen Schichtplan aus');
|
||||||
id: avail.id,
|
return;
|
||||||
employeeId: avail.employeeId,
|
}
|
||||||
planId: avail.planId || selectedPlanId, // Use selected plan if planId is empty
|
|
||||||
|
// Filter availabilities to only include those with actual time slots
|
||||||
|
const validAvailabilities = availabilities.filter(avail =>
|
||||||
|
timeSlots.some(slot => slot.id === avail.timeSlotId)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (validAvailabilities.length === 0) {
|
||||||
|
setError('Keine gültigen Verfügbarkeiten zum Speichern gefunden');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to the format expected by the API
|
||||||
|
const requestData = {
|
||||||
|
planId: selectedPlanId,
|
||||||
|
availabilities: validAvailabilities.map(avail => ({
|
||||||
|
planId: selectedPlanId,
|
||||||
dayOfWeek: avail.dayOfWeek,
|
dayOfWeek: avail.dayOfWeek,
|
||||||
timeSlotId: avail.timeSlotId,
|
timeSlotId: avail.timeSlotId,
|
||||||
preferenceLevel: avail.preferenceLevel,
|
preferenceLevel: avail.preferenceLevel,
|
||||||
notes: avail.notes
|
notes: avail.notes
|
||||||
}));
|
}))
|
||||||
|
};
|
||||||
|
|
||||||
await employeeService.updateAvailabilities(employee.id, availabilitiesToSave);
|
await employeeService.updateAvailabilities(employee.id, requestData);
|
||||||
console.log('✅ VERFÜGBARKEITEN ERFOLGREICH GESPEICHERT');
|
console.log('✅ VERFÜGBARKEITEN ERFOLGREICH GESPEICHERT');
|
||||||
|
|
||||||
onSave();
|
onSave();
|
||||||
@@ -358,26 +394,39 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
|||||||
margin: '0 0 10px 0',
|
margin: '0 0 10px 0',
|
||||||
color: timeSlots.length === 0 ? '#721c24' : '#0c5460'
|
color: timeSlots.length === 0 ? '#721c24' : '#0c5460'
|
||||||
}}>
|
}}>
|
||||||
{timeSlots.length === 0 ? '❌ PROBLEM: Keine Zeit-Slots gefunden' : '✅ Zeit-Slots geladen'}
|
{timeSlots.length === 0 ? '❌ PROBLEM: Keine Zeit-Slots gefunden' : '✅ Plan-Daten geladen'}
|
||||||
</h4>
|
</h4>
|
||||||
<div style={{ fontSize: '12px', fontFamily: 'monospace' }}>
|
<div style={{ fontSize: '12px', fontFamily: 'monospace' }}>
|
||||||
<div><strong>Zeit-Slots gefunden:</strong> {timeSlots.length}</div>
|
<div><strong>Ausgewählter Plan:</strong> {selectedPlan?.name || 'Keiner'}</div>
|
||||||
<div><strong>Quelle:</strong> {timeSlots[0]?.source || 'Unbekannt'}</div>
|
<div><strong>Verwendete Zeit-Slots:</strong> {timeSlots.length}</div>
|
||||||
<div><strong>Schichtpläne:</strong> {shiftPlans.length}</div>
|
<div><strong>Verwendete Tage:</strong> {usedDays.length} ({usedDays.map(d => d.name).join(', ')})</div>
|
||||||
|
<div><strong>Gesamte Shifts im Plan:</strong> {selectedPlan?.shifts?.length || 0}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{timeSlots.length > 0 && (
|
{selectedPlan && selectedPlan.shifts && (
|
||||||
<div style={{ marginTop: '10px' }}>
|
<div style={{ marginTop: '10px' }}>
|
||||||
<strong>Gefundene Zeit-Slots:</strong>
|
<strong>Shifts im Plan:</strong>
|
||||||
{timeSlots.map(slot => (
|
{selectedPlan.shifts.map((shift, index) => (
|
||||||
<div key={slot.id} style={{ fontSize: '11px', marginLeft: '10px' }}>
|
<div key={index} style={{ fontSize: '11px', marginLeft: '10px' }}>
|
||||||
• {slot.displayName}
|
• Tag {shift.dayOfWeek}: {shift.timeSlotId} ({shift.requiredEmployees} Personen)
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div style={{
|
||||||
|
backgroundColor: '#2c3e50',
|
||||||
|
color: 'white',
|
||||||
|
padding: '15px 20px',
|
||||||
|
fontWeight: 'bold'
|
||||||
|
}}>
|
||||||
|
Verfügbarkeit für: {selectedPlan?.name || 'Kein Plan ausgewählt'}
|
||||||
|
<div style={{ fontSize: '14px', fontWeight: 'normal', marginTop: '5px' }}>
|
||||||
|
{timeSlots.length} Schichttypen • {usedDays.length} Tage • Nur tatsächlich im Plan verwendete Schichten und Tage
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Employee Info */}
|
{/* Employee Info */}
|
||||||
<div style={{ marginBottom: '20px' }}>
|
<div style={{ marginBottom: '20px' }}>
|
||||||
<h3 style={{ margin: '0 0 10px 0', color: '#34495e' }}>
|
<h3 style={{ margin: '0 0 10px 0', color: '#34495e' }}>
|
||||||
@@ -447,7 +496,7 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
|||||||
border: '1px solid #e9ecef'
|
border: '1px solid #e9ecef'
|
||||||
}}>
|
}}>
|
||||||
<h4 style={{ margin: '0 0 15px 0', color: '#495057' }}>
|
<h4 style={{ margin: '0 0 15px 0', color: '#495057' }}>
|
||||||
Verfügbarkeit für Schichtplan prüfen
|
Verfügbarkeit für Schichtplan
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<div style={{ display: 'flex', gap: '15px', alignItems: 'center', flexWrap: 'wrap' }}>
|
<div style={{ display: 'flex', gap: '15px', alignItems: 'center', flexWrap: 'wrap' }}>
|
||||||
@@ -468,11 +517,19 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
|||||||
<option value="">Bitte auswählen...</option>
|
<option value="">Bitte auswählen...</option>
|
||||||
{shiftPlans.map(plan => (
|
{shiftPlans.map(plan => (
|
||||||
<option key={plan.id} value={plan.id}>
|
<option key={plan.id} value={plan.id}>
|
||||||
{plan.name} ({plan.timeSlots?.length || 0} Zeit-Slots)
|
{plan.name} {plan.timeSlots && `(${plan.timeSlots.length} Zeit-Slots)`}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{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>Status:</strong> {selectedPlan.status}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -492,7 +549,7 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
|||||||
}}>
|
}}>
|
||||||
Verfügbarkeit definieren
|
Verfügbarkeit definieren
|
||||||
<div style={{ fontSize: '14px', fontWeight: 'normal', marginTop: '5px' }}>
|
<div style={{ fontSize: '14px', fontWeight: 'normal', marginTop: '5px' }}>
|
||||||
{timeSlots.length} Schichttypen verfügbar
|
{timeSlots.length} Schichttypen verfügbar • Wählen Sie für jeden Tag und jede Schicht die Verfügbarkeit
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -513,7 +570,7 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
|||||||
}}>
|
}}>
|
||||||
Schicht (Zeit)
|
Schicht (Zeit)
|
||||||
</th>
|
</th>
|
||||||
{daysOfWeek.map(weekday => (
|
{usedDays.map(weekday => (
|
||||||
<th key={weekday.id} style={{
|
<th key={weekday.id} style={{
|
||||||
padding: '12px 16px',
|
padding: '12px 16px',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
@@ -538,8 +595,11 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
|||||||
backgroundColor: '#f8f9fa'
|
backgroundColor: '#f8f9fa'
|
||||||
}}>
|
}}>
|
||||||
{timeSlot.displayName}
|
{timeSlot.displayName}
|
||||||
|
<div style={{ fontSize: '11px', color: '#666', marginTop: '4px' }}>
|
||||||
|
{timeSlot.source}
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
{daysOfWeek.map(weekday => {
|
{usedDays.map(weekday => {
|
||||||
const currentLevel = getAvailabilityForDayAndSlot(weekday.id, timeSlot.id);
|
const currentLevel = getAvailabilityForDayAndSlot(weekday.id, timeSlot.id);
|
||||||
const levelConfig = availabilityLevels.find(l => l.level === currentLevel);
|
const levelConfig = availabilityLevels.find(l => l.level === currentLevel);
|
||||||
|
|
||||||
@@ -604,8 +664,12 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
|||||||
<h4>Keine Schichttypen konfiguriert</h4>
|
<h4>Keine Schichttypen konfiguriert</h4>
|
||||||
<p>Es wurden keine Zeit-Slots in den Schichtplänen gefunden.</p>
|
<p>Es wurden keine Zeit-Slots in den Schichtplänen gefunden.</p>
|
||||||
<p style={{ fontSize: '14px', marginTop: '10px' }}>
|
<p style={{ fontSize: '14px', marginTop: '10px' }}>
|
||||||
Bitte erstellen Sie zuerst Schichtpläne mit Zeit-Slots.
|
Bitte erstellen Sie zuerst Schichtpläne mit Zeit-Slots oder wählen Sie einen anderen Schichtplan aus.
|
||||||
</p>
|
</p>
|
||||||
|
<div style={{ marginTop: '20px', fontSize: '12px', color: '#999' }}>
|
||||||
|
Gefundene Schichtpläne: {shiftPlans.length}<br />
|
||||||
|
Schichtpläne mit TimeSlots: {shiftPlans.filter(p => p.timeSlots && p.timeSlots.length > 0).length}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -633,14 +697,14 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={saving || timeSlots.length === 0}
|
disabled={saving || timeSlots.length === 0 || !selectedPlanId}
|
||||||
style={{
|
style={{
|
||||||
padding: '12px 24px',
|
padding: '12px 24px',
|
||||||
backgroundColor: saving ? '#bdc3c7' : (timeSlots.length === 0 ? '#95a5a6' : '#3498db'),
|
backgroundColor: saving ? '#bdc3c7' : (timeSlots.length === 0 || !selectedPlanId ? '#95a5a6' : '#3498db'),
|
||||||
color: 'white',
|
color: 'white',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
borderRadius: '6px',
|
borderRadius: '6px',
|
||||||
cursor: (saving || timeSlots.length === 0) ? 'not-allowed' : 'pointer',
|
cursor: (saving || timeSlots.length === 0 || !selectedPlanId) ? 'not-allowed' : 'pointer',
|
||||||
fontWeight: 'bold'
|
fontWeight: 'bold'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -55,9 +55,19 @@ const EmployeeList: React.FC<EmployeeListProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Using shared configuration for consistent styling
|
// Using shared configuration for consistent styling
|
||||||
const getEmployeeTypeBadge = (type: 'manager' | 'trainee' | 'experienced') => {
|
type EmployeeType = typeof EMPLOYEE_TYPE_CONFIG[number]['value'];
|
||||||
const config = EMPLOYEE_TYPE_CONFIG.find(t => t.value === type);
|
|
||||||
return config || EMPLOYEE_TYPE_CONFIG[0];
|
const getEmployeeTypeBadge = (type: EmployeeType) => {
|
||||||
|
const config = EMPLOYEE_TYPE_CONFIG.find(t => t.value === type)!;
|
||||||
|
|
||||||
|
const bgColor =
|
||||||
|
type === 'manager'
|
||||||
|
? '#fadbd8'
|
||||||
|
: type === 'trainee'
|
||||||
|
? '#d5f4e6'
|
||||||
|
: '#d6eaf8'; // experienced
|
||||||
|
|
||||||
|
return { text: config.label, color: config.color, bgColor };
|
||||||
};
|
};
|
||||||
|
|
||||||
const getStatusBadge = (isActive: boolean) => {
|
const getStatusBadge = (isActive: boolean) => {
|
||||||
@@ -72,6 +82,21 @@ const EmployeeList: React.FC<EmployeeListProps> = ({
|
|||||||
: { text: '❌ Betreuung', color: '#e74c3c', bgColor: '#fadbd8' };
|
: { text: '❌ Betreuung', color: '#e74c3c', bgColor: '#fadbd8' };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type Role = typeof ROLE_CONFIG[number]['value'];
|
||||||
|
|
||||||
|
const getRoleBadge = (role: Role) => {
|
||||||
|
const { label, color } = ROLE_CONFIG.find(r => r.value === role)!;
|
||||||
|
|
||||||
|
const bgColor =
|
||||||
|
role === 'user'
|
||||||
|
? '#d5f4e6'
|
||||||
|
: role === 'maintenance'
|
||||||
|
? '#d6eaf8'
|
||||||
|
: '#fadbd8'; // admin
|
||||||
|
|
||||||
|
return { text: label, color, bgColor };
|
||||||
|
};
|
||||||
|
|
||||||
if (employees.length === 0) {
|
if (employees.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div style={{
|
<div style={{
|
||||||
@@ -161,7 +186,7 @@ const EmployeeList: React.FC<EmployeeListProps> = ({
|
|||||||
alignItems: 'center'
|
alignItems: 'center'
|
||||||
}}>
|
}}>
|
||||||
<div>Name & E-Mail</div>
|
<div>Name & E-Mail</div>
|
||||||
<div>Typ</div>
|
<div style={{ textAlign: 'center' }}>Typ</div>
|
||||||
<div style={{ textAlign: 'center' }}>Eigenständigkeit</div>
|
<div style={{ textAlign: 'center' }}>Eigenständigkeit</div>
|
||||||
<div style={{ textAlign: 'center' }}>Rolle</div>
|
<div style={{ textAlign: 'center' }}>Rolle</div>
|
||||||
<div style={{ textAlign: 'center' }}>Status</div>
|
<div style={{ textAlign: 'center' }}>Status</div>
|
||||||
@@ -172,7 +197,7 @@ const EmployeeList: React.FC<EmployeeListProps> = ({
|
|||||||
{filteredEmployees.map(employee => {
|
{filteredEmployees.map(employee => {
|
||||||
const employeeType = getEmployeeTypeBadge(employee.employeeType);
|
const employeeType = getEmployeeTypeBadge(employee.employeeType);
|
||||||
const independence = getIndependenceBadge(employee.canWorkAlone);
|
const independence = getIndependenceBadge(employee.canWorkAlone);
|
||||||
const roleColor = '#d5f4e6'; // Default color
|
const roleColor = getRoleBadge(employee.role);
|
||||||
const status = getStatusBadge(employee.isActive);
|
const status = getStatusBadge(employee.isActive);
|
||||||
const canEdit = canEditEmployee(employee);
|
const canEdit = canEditEmployee(employee);
|
||||||
const canDelete = canDeleteEmployee(employee);
|
const canDelete = canDeleteEmployee(employee);
|
||||||
@@ -213,7 +238,7 @@ const EmployeeList: React.FC<EmployeeListProps> = ({
|
|||||||
<div style={{ textAlign: 'center' }}>
|
<div style={{ textAlign: 'center' }}>
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: employeeType.color,
|
backgroundColor: employeeType.bgColor,
|
||||||
color: employeeType.color,
|
color: employeeType.color,
|
||||||
padding: '6px 12px',
|
padding: '6px 12px',
|
||||||
borderRadius: '15px',
|
borderRadius: '15px',
|
||||||
@@ -222,7 +247,7 @@ const EmployeeList: React.FC<EmployeeListProps> = ({
|
|||||||
display: 'inline-block'
|
display: 'inline-block'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{employeeType.label}
|
{employeeType.text}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -247,8 +272,8 @@ const EmployeeList: React.FC<EmployeeListProps> = ({
|
|||||||
<div style={{ textAlign: 'center' }}>
|
<div style={{ textAlign: 'center' }}>
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: roleColor,
|
backgroundColor: roleColor.bgColor,
|
||||||
color: 'white',
|
color: roleColor.color,
|
||||||
padding: '6px 12px',
|
padding: '6px 12px',
|
||||||
borderRadius: '15px',
|
borderRadius: '15px',
|
||||||
fontSize: '12px',
|
fontSize: '12px',
|
||||||
|
|||||||
@@ -102,11 +102,12 @@ export class EmployeeService {
|
|||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateAvailabilities(employeeId: string, availabilities: EmployeeAvailability[]): Promise<EmployeeAvailability[]> {
|
async updateAvailabilities(employeeId: string, data: { planId: string, availabilities: Omit<EmployeeAvailability, 'id' | 'employeeId'>[] }): Promise<EmployeeAvailability[]> {
|
||||||
|
console.log('🔄 Updating availabilities for employee:', employeeId);
|
||||||
const response = await fetch(`${API_BASE_URL}/employees/${employeeId}/availabilities`, {
|
const response = await fetch(`${API_BASE_URL}/employees/${employeeId}/availabilities`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: getAuthHeaders(),
|
headers: getAuthHeaders(),
|
||||||
body: JSON.stringify(availabilities),
|
body: JSON.stringify(data),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|||||||
Reference in New Issue
Block a user