diff --git a/backend/package.json b/backend/package.json index 220af2e..f2e361f 100644 --- a/backend/package.json +++ b/backend/package.json @@ -29,7 +29,7 @@ "helmet": "8.1.0", "express-validator": "7.3.0", "exceljs": "4.4.0", - "playwright": "^1.37.0" + "playwright-chromium": "^1.37.0" }, "devDependencies": { "@types/bcryptjs": "^2.4.2", diff --git a/backend/src/controllers/shiftPlanController.ts b/backend/src/controllers/shiftPlanController.ts index 22157bf..4f8abcb 100644 --- a/backend/src/controllers/shiftPlanController.ts +++ b/backend/src/controllers/shiftPlanController.ts @@ -9,7 +9,7 @@ import { import { AuthRequest } from '../middleware/auth.js'; import { TEMPLATE_PRESETS } from '../models/defaults/shiftPlanDefaults.js'; import ExcelJS from 'exceljs'; -import { chromium } from 'playwright'; +import { chromium } from 'playwright-chromium'; async function getPlanWithDetails(planId: string) { const plan = await db.get(` @@ -989,6 +989,20 @@ interface ExportTimetableData { allTimeSlots: ExportTimeSlot[]; } +function sortTimeSlotsByStartTime(timeSlots: any[]): any[] { + const timeToMinutes = (timeStr: string) => { + if (!timeStr) return 0; + const [hours, minutes] = timeStr.split(':').map(Number); + return hours * 60 + minutes; + }; + + return [...timeSlots].sort((a, b) => { + const minutesA = timeToMinutes(a.startTime); + const minutesB = timeToMinutes(b.startTime); + return minutesA - minutesB; // Ascending order (earliest first) + }); +} + function getTimetableDataForExport(plan: any): ExportTimetableData { const weekdays = [ { id: 1, name: 'Montag' }, @@ -1032,9 +1046,16 @@ function getTimetableDataForExport(plan: any): ExportTimetableData { Object.keys(shiftsByDay).forEach(day => { const dayNum = parseInt(day); shiftsByDay[dayNum].sort((a: any, b: any) => { - const timeA = a.startTime || ''; - const timeB = b.startTime || ''; - return timeA.localeCompare(timeB); + // Use numeric comparison for proper time sorting + const timeToMinutes = (timeStr: string) => { + if (!timeStr) return 0; + const [hours, minutes] = timeStr.split(':').map(Number); + return hours * 60 + minutes; + }; + + const minutesA = timeToMinutes(a.startTime); + const minutesB = timeToMinutes(b.startTime); + return minutesA - minutesB; }); }); @@ -1073,15 +1094,12 @@ function getTimetableDataForExport(plan: any): ExportTimetableData { }); }); - // Convert to array and sort by start time - const allTimeSlots = Array.from(allTimeSlotsMap.values()).sort((a: ExportTimeSlot, b: ExportTimeSlot) => { - return (a.startTime || '').localeCompare(b.startTime || ''); - }); + // Convert to array and sort by start time using numeric comparison + const allTimeSlots = sortTimeSlotsByStartTime(Array.from(allTimeSlotsMap.values())); return { days, allTimeSlots }; } -// Export shift plan to Excel // Export shift plan to Excel export const exportShiftPlanToExcel = async (req: Request, res: Response): Promise => { try { diff --git a/backend/src/scripts/seedTestData.ts b/backend/src/scripts/seedTestData.ts index 046435b..356c71d 100644 --- a/backend/src/scripts/seedTestData.ts +++ b/backend/src/scripts/seedTestData.ts @@ -86,7 +86,8 @@ export async function seedTestData(): Promise { console.log('🌱 Starting test data seeding...'); // Read test.json file - adjust path to be relative to project root - const testDataPath = path.resolve(process.cwd(), 'test.json'); + //const testDataPath = path.resolve(process.cwd(), './test.json'); + const testDataPath = path.resolve(__dirname, './test.json'); console.log('🔍 Looking for test.json at:', testDataPath); @@ -95,9 +96,10 @@ export async function seedTestData(): Promise { // Try alternative paths const alternativePaths = [ - path.resolve(__dirname, '../../../test.json'), - path.resolve(process.cwd(), '../test.json'), - path.resolve(__dirname, '../../test.json') + //path.resolve(__dirname, '../../../test.json'), + //path.resolve(process.cwd(), '../test.json'), + //path.resolve(__dirname, '../../test.json'), + path.resolve(__dirname, './test.json') ]; for (const altPath of alternativePaths) { @@ -136,7 +138,7 @@ export async function seedTestData(): Promise { const [firstname, lastname = ''] = name.split(' '); const email = generateEmail(firstname, lastname || 'Test'); - const passwordHash = await bcrypt.hash('test1234', 10); + const passwordHash = await bcrypt.hash('ZebraAux123!', 10); const contractType = mapContractType(testData.employee_info.contract_sizes[name]); const employeeType = testData.employee_info.employee_types[name]; diff --git a/backend/src/test.json b/backend/src/scripts/test.json similarity index 100% rename from backend/src/test.json rename to backend/src/scripts/test.json diff --git a/frontend/src/pages/Employees/components/AvailabilityManager.tsx b/frontend/src/pages/Employees/components/AvailabilityManager.tsx index ec215a5..aead402 100644 --- a/frontend/src/pages/Employees/components/AvailabilityManager.tsx +++ b/frontend/src/pages/Employees/components/AvailabilityManager.tsx @@ -317,7 +317,17 @@ const AvailabilityManager: React.FC = ({ // Convert to array and sort by start time const sortedTimeSlots = Array.from(allTimeSlots.values()).sort((a, b) => { - return (a.startTime || '').localeCompare(b.startTime || ''); + // Convert time strings to minutes for proper numeric comparison + const timeToMinutes = (timeStr: string) => { + if (!timeStr) return 0; + const [hours, minutes] = timeStr.split(':').map(Number); + return hours * 60 + minutes; + }; + + const minutesA = timeToMinutes(a.startTime); + const minutesB = timeToMinutes(b.startTime); + + return minutesA - minutesB; // Ascending order (earliest first) }); return ( diff --git a/frontend/src/pages/ShiftPlans/ShiftPlanView.tsx b/frontend/src/pages/ShiftPlans/ShiftPlanView.tsx index 6937daa..36e176b 100644 --- a/frontend/src/pages/ShiftPlans/ShiftPlanView.tsx +++ b/frontend/src/pages/ShiftPlans/ShiftPlanView.tsx @@ -126,7 +126,7 @@ const ShiftPlanView: React.FC = () => { useEffect(() => { if (dropdownRef.current) { - setDropdownWidth(dropdownRef.current.offsetWidth); + setDropdownWidth(dropdownRef.current.offsetWidth / 40); // Adjust divisor for desired slide distance } }, [exportType]); @@ -200,7 +200,17 @@ const ShiftPlanView: React.FC = () => { // Convert to array and sort by start time - SAME LOGIC AS AVAILABILITYMANAGER const allTimeSlots = Array.from(allTimeSlotsMap.values()).sort((a, b) => { - return (a.startTime || '').localeCompare(b.startTime || ''); + // Convert time strings to minutes for proper numeric comparison + const timeToMinutes = (timeStr: string) => { + if (!timeStr) return 0; + const [hours, minutes] = timeStr.split(':').map(Number); + return hours * 60 + minutes; + }; + + const minutesA = timeToMinutes(a.startTime); + const minutesB = timeToMinutes(b.startTime); + + return minutesA - minutesB; // Ascending order (earliest first) }); return { days, shiftsByDay, allTimeSlots }; @@ -1436,17 +1446,17 @@ const ShiftPlanView: React.FC = () => {
- {/* Export Dropdown */} + {/* Export Dropdown Container */}
- {/* Export Button */} + {/* Export Button - erscheint nur wenn eine Option ausgewählt ist */} {exportType && (