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

View File

@@ -56,11 +56,8 @@ const ShiftPlanView: React.FC = () => {
const [showAssignmentPreview, setShowAssignmentPreview] = useState(false); const [showAssignmentPreview, setShowAssignmentPreview] = useState(false);
const [recreating, setRecreating] = useState(false); const [recreating, setRecreating] = useState(false);
const [exporting, setExporting] = useState(false); const [exporting, setExporting] = useState(false);
const [exportDropdownOpen, setExportDropdownOpen] = useState(false); const [exportType, setExportType] = useState<'pdf' | 'excel' | null>(null);
const [selectedExportType, setSelectedExportType] = useState('');
const [showExportButton, setShowExportButton] = useState(false);
const [dropdownWidth, setDropdownWidth] = useState(0); const [dropdownWidth, setDropdownWidth] = useState(0);
const dropdownRef = useRef<HTMLDivElement>(null); const dropdownRef = useRef<HTMLDivElement>(null);
useEffect(() => { 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 // Add this useEffect to debug state changes
useEffect(() => { useEffect(() => {
console.log('🔍 STATE DEBUG - showAssignmentPreview:', showAssignmentPreview); console.log('🔍 STATE DEBUG - showAssignmentPreview:', showAssignmentPreview);
@@ -134,6 +124,12 @@ const ShiftPlanView: React.FC = () => {
} }
}, [availabilities]); }, [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 // Create a data structure that maps days to their shifts with time slot info - SAME AS AVAILABILITYMANAGER
const getTimetableData = () => { const getTimetableData = () => {
if (!shiftPlan || !shiftPlan.shifts || !shiftPlan.timeSlots) { if (!shiftPlan || !shiftPlan.shifts || !shiftPlan.timeSlots) {
@@ -256,52 +252,38 @@ const ShiftPlanView: React.FC = () => {
}; };
const handleExport = async () => { const handleExport = async () => {
if (!shiftPlan || !selectedExportType) return; if (!shiftPlan || !exportType) return;
try { try {
setExporting(true); setExporting(true);
let blob: Blob; let blob: Blob;
if (selectedExportType === 'PDF') { if (exportType === 'excel') {
// Call the PDF export service
blob = await shiftPlanService.exportShiftPlanToPDF(shiftPlan.id);
} else {
// Call the Excel export service
blob = await shiftPlanService.exportShiftPlanToExcel(shiftPlan.id); 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({ showNotification({
type: 'success', type: 'success',
title: 'Export erfolgreich', 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) { } catch (error) {
console.error(`Error exporting to ${selectedExportType}:`, error); console.error(`Error exporting to ${exportType}:`, error);
showNotification({ showNotification({
type: 'error', type: 'error',
title: 'Export fehlgeschlagen', 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 { } finally {
setExporting(false); setExporting(false);
} }
}; };
const handleExportTypeSelect = (type: string) => {
setSelectedExportType(type);
setExportDropdownOpen(false);
setShowExportButton(true);
};
const loadShiftPlanData = async () => { const loadShiftPlanData = async () => {
if (!id) return; if (!id) return;
@@ -460,6 +442,14 @@ const ShiftPlanView: React.FC = () => {
console.log('- Shift Patterns:', shiftPlan.shifts?.length || 0); console.log('- Shift Patterns:', shiftPlan.shifts?.length || 0);
console.log('- Scheduled Shifts:', scheduledShifts.length); 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 = { const constraints = {
enforceNoTraineeAlone: true, enforceNoTraineeAlone: true,
enforceExperiencedWithChef: true, enforceExperiencedWithChef: true,
@@ -1076,78 +1066,6 @@ const ShiftPlanView: React.FC = () => {
</tbody> </tbody>
</table> </table>
</div> </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> </div>
); );
}; };
@@ -1514,6 +1432,65 @@ const ShiftPlanView: React.FC = () => {
{renderTimetable()} {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 */} {/* Summary */}
{days.length > 0 && ( {days.length > 0 && (
<div style={{ <div style={{