Compare commits

...

2 Commits

Author SHA1 Message Date
f6e19bc1ed added dropdown menu 2025-11-05 14:18:18 +01:00
e66c0f9e28 export drop down menu doesnt disappear when exporttype is selected 2025-11-05 13:22:00 +01:00
2 changed files with 104 additions and 127 deletions

View File

@@ -18,7 +18,7 @@ const ShiftPlanCreate: React.FC = () => {
const [searchParams] = useSearchParams();
const { showNotification } = useNotification();
const { executeWithValidation, isSubmitting } = useBackendValidation();
const [planName, setPlanName] = useState('');
const [startDate, setStartDate] = useState('');
const [endDate, setEndDate] = useState('');
@@ -35,9 +35,9 @@ const ShiftPlanCreate: React.FC = () => {
console.log('🔄 Lade verfügbare Vorlagen-Presets...');
const data = await shiftPlanService.getTemplatePresets();
console.log('✅ Presets geladen:', data);
setPresets(data);
// Setze das erste Preset als Standard, falls vorhanden
if (data.length > 0) {
setSelectedPreset(data[0].name);
@@ -75,7 +75,7 @@ const ShiftPlanCreate: React.FC = () => {
if (!endDate) {
showNotification({
type: 'error',
title: 'Fehlende Angaben',
title: 'Fehlende Angaben',
message: 'Bitte wählen Sie ein Enddatum'
});
return;
@@ -115,14 +115,14 @@ const ShiftPlanCreate: React.FC = () => {
});
console.log('✅ Plan erstellt:', createdPlan);
// Erfolgsmeldung und Weiterleitung
showNotification({
type: 'success',
title: 'Erfolg',
message: 'Schichtplan erfolgreich erstellt!'
});
setTimeout(() => {
navigate(`/shift-plans/${createdPlan.id}`);
}, 1500);
@@ -146,20 +146,20 @@ const ShiftPlanCreate: React.FC = () => {
<div className={styles.container}>
<div className={styles.header}>
<h1>Neuen Schichtplan erstellen</h1>
<button
onClick={() => navigate(-1)}
<button
onClick={() => navigate(-1)}
className={styles.backButton}
disabled={isSubmitting}
>
Zurück
</button>
</div>
<div className={styles.form}>
<div className={styles.formGroup}>
<label>Plan Name:</label>
<input
type="text"
<input
type="text"
value={planName}
onChange={(e) => setPlanName(e.target.value)}
placeholder="z.B. KW 42 2025"
@@ -171,8 +171,8 @@ const ShiftPlanCreate: React.FC = () => {
<div className={styles.dateGroup}>
<div className={styles.formGroup}>
<label>Von:</label>
<input
type="date"
<input
type="date"
value={startDate}
onChange={(e) => setStartDate(e.target.value)}
className={styles.input}
@@ -182,8 +182,8 @@ const ShiftPlanCreate: React.FC = () => {
<div className={styles.formGroup}>
<label>Bis:</label>
<input
type="date"
<input
type="date"
value={endDate}
onChange={(e) => setEndDate(e.target.value)}
className={styles.input}
@@ -194,8 +194,8 @@ const ShiftPlanCreate: React.FC = () => {
<div className={styles.formGroup}>
<label>Vorlage verwenden:</label>
<select
value={selectedPreset}
<select
value={selectedPreset}
onChange={(e) => setSelectedPreset(e.target.value)}
className={`${styles.select} ${presets.length === 0 ? styles.empty : ''}`}
disabled={isSubmitting}
@@ -207,7 +207,7 @@ const ShiftPlanCreate: React.FC = () => {
</option>
))}
</select>
{selectedPreset && (
<div className={styles.presetDescription}>
{getSelectedPresetDescription()}
@@ -222,9 +222,9 @@ const ShiftPlanCreate: React.FC = () => {
</div>
<div className={styles.actions}>
<button
onClick={handleCreate}
className={styles.createButton}
<button
onClick={handleCreate}
className={styles.createButton}
disabled={isSubmitting || !selectedPreset || !planName.trim() || !startDate || !endDate}
>
{isSubmitting ? 'Wird erstellt...' : 'Schichtplan erstellen'}

View File

@@ -56,11 +56,8 @@ const ShiftPlanView: React.FC = () => {
const [showAssignmentPreview, setShowAssignmentPreview] = useState(false);
const [recreating, setRecreating] = useState(false);
const [exporting, setExporting] = useState(false);
const [exportDropdownOpen, setExportDropdownOpen] = useState(false);
const [selectedExportType, setSelectedExportType] = useState('');
const [showExportButton, setShowExportButton] = useState(false);
const [exportType, setExportType] = useState<'pdf' | 'excel' | null>(null);
const [dropdownWidth, setDropdownWidth] = useState(0);
const dropdownRef = useRef<HTMLDivElement>(null);
useEffect(() => {
@@ -96,13 +93,6 @@ const ShiftPlanView: React.FC = () => {
};
}, []);
// Measure dropdown width when it opens
useEffect(() => {
if (exportDropdownOpen && dropdownRef.current) {
setDropdownWidth(dropdownRef.current.offsetWidth);
}
}, [exportDropdownOpen]);
// Add this useEffect to debug state changes
useEffect(() => {
console.log('🔍 STATE DEBUG - showAssignmentPreview:', showAssignmentPreview);
@@ -134,6 +124,12 @@ const ShiftPlanView: React.FC = () => {
}
}, [availabilities]);
useEffect(() => {
if (dropdownRef.current) {
setDropdownWidth(dropdownRef.current.offsetWidth);
}
}, [exportType]);
// Create a data structure that maps days to their shifts with time slot info - SAME AS AVAILABILITYMANAGER
const getTimetableData = () => {
if (!shiftPlan || !shiftPlan.shifts || !shiftPlan.timeSlots) {
@@ -256,52 +252,38 @@ const ShiftPlanView: React.FC = () => {
};
const handleExport = async () => {
if (!shiftPlan || !selectedExportType) return;
if (!shiftPlan || !exportType) return;
try {
setExporting(true);
let blob: Blob;
if (selectedExportType === 'PDF') {
// Call the PDF export service
blob = await shiftPlanService.exportShiftPlanToPDF(shiftPlan.id);
} else {
// Call the Excel export service
if (exportType === 'excel') {
blob = await shiftPlanService.exportShiftPlanToExcel(shiftPlan.id);
saveAs(blob, `Schichtplan_${shiftPlan.name}_${new Date().toISOString().split('T')[0]}.xlsx`);
} else {
blob = await shiftPlanService.exportShiftPlanToPDF(shiftPlan.id);
saveAs(blob, `Schichtplan_${shiftPlan.name}_${new Date().toISOString().split('T')[0]}.pdf`);
}
// Use file-saver to download the file
const fileExtension = selectedExportType.toLowerCase();
saveAs(blob, `Schichtplan_${shiftPlan.name}_${new Date().toISOString().split('T')[0]}.${fileExtension}`);
showNotification({
type: 'success',
title: 'Export erfolgreich',
message: `Der Schichtplan wurde als ${selectedExportType}-Datei exportiert.`
message: `Der Schichtplan wurde als ${exportType === 'excel' ? 'Excel' : 'PDF'} exportiert.`
});
// Reset export state
setSelectedExportType('');
setShowExportButton(false);
} catch (error) {
console.error(`Error exporting to ${selectedExportType}:`, error);
console.error(`Error exporting to ${exportType}:`, error);
showNotification({
type: 'error',
title: 'Export fehlgeschlagen',
message: `Der ${selectedExportType}-Export konnte nicht durchgeführt werden.`
message: `Der ${exportType === 'excel' ? 'Excel' : 'PDF'}-Export konnte nicht durchgeführt werden.`
});
} finally {
setExporting(false);
}
};
const handleExportTypeSelect = (type: string) => {
setSelectedExportType(type);
setExportDropdownOpen(false);
setShowExportButton(true);
};
const loadShiftPlanData = async () => {
if (!id) return;
@@ -460,6 +442,14 @@ const ShiftPlanView: React.FC = () => {
console.log('- Shift Patterns:', shiftPlan.shifts?.length || 0);
console.log('- Scheduled Shifts:', scheduledShifts.length);
// DEBUG: Show shift pattern IDs
/*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,
enforceExperiencedWithChef: true,
@@ -1076,78 +1066,6 @@ const ShiftPlanView: React.FC = () => {
</tbody>
</table>
</div>
{/* Export Dropdown - Only show when plan is published */}
{shiftPlan?.status === 'published' && (
<div style={{
padding: '15px 20px',
backgroundColor: '#f8f9fa',
borderTop: '1px solid #e0e0e0',
display: 'flex',
justifyContent: 'flex-end',
alignItems: 'center',
gap: '10px'
}}>
<div style={{ position: 'relative', display: 'inline-block' }}>
{/* Export Dropdown */}
<div
ref={dropdownRef}
style={{
position: 'relative',
transform: showExportButton ? `translateX(-${dropdownWidth}px)` : 'translateX(0)',
opacity: showExportButton ? 0 : 1,
transition: 'all 0.3s ease',
pointerEvents: showExportButton ? 'none' : 'auto'
}}
>
<select
value=""
onChange={(e) => handleExportTypeSelect(e.target.value)}
onFocus={() => setExportDropdownOpen(true)}
onBlur={() => setTimeout(() => setExportDropdownOpen(false), 200)}
style={{
padding: '8px 16px',
border: '1px solid #ddd',
borderRadius: '4px',
backgroundColor: 'white',
cursor: 'pointer',
minWidth: '120px'
}}
>
<option value="">Export</option>
<option value="PDF">PDF</option>
<option value="Excel">Excel</option>
</select>
</div>
{/* Export Button */}
{showExportButton && (
<button
onClick={handleExport}
disabled={exporting}
style={{
padding: '8px 16px',
backgroundColor: '#51258f',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: exporting ? 'not-allowed' : 'pointer',
fontWeight: 'bold',
position: 'absolute',
right: 0,
top: 0,
opacity: showExportButton ? 1 : 0,
transform: showExportButton ? 'translateX(0)' : 'translateX(20px)',
transition: 'all 0.3s ease',
minWidth: '120px'
}}
>
{exporting ? 'Exportiert...' : 'EXPORT'}
</button>
)}
</div>
</div>
)}
</div>
);
};
@@ -1514,6 +1432,65 @@ const ShiftPlanView: React.FC = () => {
{renderTimetable()}
{shiftPlan.status === 'published' && hasRole(['admin', 'maintenance']) && (
<div style={{
display: 'flex',
alignItems: 'center',
position: 'relative',
marginLeft: '10px'
}}>
{/* Export Dropdown */}
<div
ref={dropdownRef}
style={{
transform: exportType ? `translateX(-${dropdownWidth}px)` : 'translateX(0)',
transition: 'transform 0.3s ease-in-out',
position: exportType ? 'absolute' : 'relative',
right: exportType ? `-${dropdownWidth}px` : '0'
}}
>
<select
value={exportType || ''}
onChange={(e) => setExportType(e.target.value as 'pdf' | 'excel' | null)}
style={{
padding: '10px 20px',
backgroundColor: 'white',
border: '1px solid #ddd',
borderRadius: '4px',
cursor: 'pointer',
minWidth: '120px'
}}
>
<option value="">Export</option>
<option value="pdf">PDF</option>
<option value="excel">Excel</option>
</select>
</div>
{/* Export Button */}
{exportType && (
<button
onClick={handleExport}
disabled={exporting}
style={{
padding: '10px 20px',
backgroundColor: '#51258f',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: exporting ? 'not-allowed' : 'pointer',
fontWeight: 'bold',
marginLeft: '10px',
opacity: exporting ? 0.7 : 1,
transition: 'opacity 0.2s ease'
}}
>
{exporting ? '🔄 Exportiert...' : 'EXPORT'}
</button>
)}
</div>
)}
{/* Summary */}
{days.length > 0 && (
<div style={{