Compare commits

...

1 Commits

Author SHA1 Message Date
0473a3b5bf added sorting to time table entries 2025-11-06 00:11:24 +01:00
6 changed files with 64 additions and 25 deletions

View File

@@ -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",

View File

@@ -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<any>(`
@@ -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<void> => {
try {

View File

@@ -86,7 +86,8 @@ export async function seedTestData(): Promise<void> {
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<void> {
// 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<void> {
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];

View File

@@ -317,7 +317,17 @@ const AvailabilityManager: React.FC<AvailabilityManagerProps> = ({
// 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 (

View File

@@ -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 = () => {
<div style={{
display: 'flex',
alignItems: 'center',
position: 'relative',
marginLeft: '10px'
justifyContent: 'flex-end',
marginTop: '20px',
gap: '10px'
}}>
{/* Export Dropdown */}
{/* Export Dropdown Container */}
<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'
position: 'relative'
}}
>
<select
@@ -1467,7 +1477,7 @@ const ShiftPlanView: React.FC = () => {
</select>
</div>
{/* Export Button */}
{/* Export Button - erscheint nur wenn eine Option ausgewählt ist */}
{exportType && (
<button
onClick={handleExport}
@@ -1480,7 +1490,6 @@ const ShiftPlanView: React.FC = () => {
borderRadius: '4px',
cursor: exporting ? 'not-allowed' : 'pointer',
fontWeight: 'bold',
marginLeft: '10px',
opacity: exporting ? 0.7 : 1,
transition: 'opacity 0.2s ease'
}}