185 lines
5.9 KiB
TypeScript
185 lines
5.9 KiB
TypeScript
interface ScheduleEvent {
|
|
id: string;
|
|
title: string;
|
|
location: string;
|
|
startTime: string;
|
|
endTime: string;
|
|
assignedDriverId?: string;
|
|
vipId: string;
|
|
vipName: string;
|
|
}
|
|
|
|
interface ConflictInfo {
|
|
type: 'overlap' | 'tight_turnaround' | 'back_to_back';
|
|
severity: 'low' | 'medium' | 'high';
|
|
message: string;
|
|
conflictingEvent: ScheduleEvent;
|
|
timeDifference?: number; // minutes
|
|
}
|
|
|
|
interface DriverAvailability {
|
|
driverId: string;
|
|
driverName: string;
|
|
status: 'available' | 'scheduled' | 'overlapping' | 'tight_turnaround';
|
|
assignmentCount: number;
|
|
conflicts: ConflictInfo[];
|
|
currentAssignments: ScheduleEvent[];
|
|
}
|
|
|
|
class DriverConflictService {
|
|
|
|
// Check for conflicts when assigning a driver to an event
|
|
checkDriverConflicts(
|
|
driverId: string,
|
|
newEvent: { startTime: string; endTime: string; location: string },
|
|
allSchedules: { [vipId: string]: ScheduleEvent[] },
|
|
drivers: any[]
|
|
): ConflictInfo[] {
|
|
const conflicts: ConflictInfo[] = [];
|
|
const driver = drivers.find(d => d.id === driverId);
|
|
if (!driver) return conflicts;
|
|
|
|
// Get all events assigned to this driver
|
|
const driverEvents = this.getDriverEvents(driverId, allSchedules);
|
|
|
|
const newStartTime = new Date(newEvent.startTime);
|
|
const newEndTime = new Date(newEvent.endTime);
|
|
|
|
for (const existingEvent of driverEvents) {
|
|
const existingStart = new Date(existingEvent.startTime);
|
|
const existingEnd = new Date(existingEvent.endTime);
|
|
|
|
// Check for direct time overlap
|
|
if (this.hasTimeOverlap(newStartTime, newEndTime, existingStart, existingEnd)) {
|
|
conflicts.push({
|
|
type: 'overlap',
|
|
severity: 'high',
|
|
message: `Direct time conflict with "${existingEvent.title}" for ${existingEvent.vipName}`,
|
|
conflictingEvent: existingEvent
|
|
});
|
|
}
|
|
// Check for tight turnaround (less than 15 minutes between events)
|
|
else {
|
|
const timeBetween = this.getTimeBetweenEvents(
|
|
newStartTime, newEndTime, existingStart, existingEnd
|
|
);
|
|
|
|
if (timeBetween !== null && timeBetween < 15) {
|
|
conflicts.push({
|
|
type: 'tight_turnaround',
|
|
severity: timeBetween < 5 ? 'high' : 'medium',
|
|
message: `Only ${timeBetween} minutes between events. Previous: "${existingEvent.title}"`,
|
|
conflictingEvent: existingEvent,
|
|
timeDifference: timeBetween
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
return conflicts;
|
|
}
|
|
|
|
// Get availability status for all drivers for a specific time slot
|
|
getDriverAvailability(
|
|
eventTime: { startTime: string; endTime: string; location: string },
|
|
allSchedules: { [vipId: string]: ScheduleEvent[] },
|
|
drivers: any[]
|
|
): DriverAvailability[] {
|
|
return drivers.map(driver => {
|
|
const conflicts = this.checkDriverConflicts(driver.id, eventTime, allSchedules, drivers);
|
|
const driverEvents = this.getDriverEvents(driver.id, allSchedules);
|
|
|
|
let status: DriverAvailability['status'] = 'available';
|
|
|
|
if (conflicts.length > 0) {
|
|
const hasOverlap = conflicts.some(c => c.type === 'overlap');
|
|
const hasTightTurnaround = conflicts.some(c => c.type === 'tight_turnaround');
|
|
|
|
if (hasOverlap) {
|
|
status = 'overlapping';
|
|
} else if (hasTightTurnaround) {
|
|
status = 'tight_turnaround';
|
|
}
|
|
} else if (driverEvents.length > 0) {
|
|
status = 'scheduled';
|
|
}
|
|
|
|
return {
|
|
driverId: driver.id,
|
|
driverName: driver.name,
|
|
status,
|
|
assignmentCount: driverEvents.length,
|
|
conflicts,
|
|
currentAssignments: driverEvents
|
|
};
|
|
});
|
|
}
|
|
|
|
// Get all events assigned to a specific driver
|
|
private getDriverEvents(driverId: string, allSchedules: { [vipId: string]: ScheduleEvent[] }): ScheduleEvent[] {
|
|
const driverEvents: ScheduleEvent[] = [];
|
|
|
|
Object.entries(allSchedules).forEach(([vipId, events]) => {
|
|
events.forEach(event => {
|
|
if (event.assignedDriverId === driverId) {
|
|
driverEvents.push({
|
|
...event,
|
|
vipId,
|
|
vipName: event.title // We'll need to get actual VIP name from VIP data
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
// Sort by start time
|
|
return driverEvents.sort((a, b) =>
|
|
new Date(a.startTime).getTime() - new Date(b.startTime).getTime()
|
|
);
|
|
}
|
|
|
|
// Check if two time periods overlap
|
|
private hasTimeOverlap(
|
|
start1: Date, end1: Date,
|
|
start2: Date, end2: Date
|
|
): boolean {
|
|
return start1 < end2 && start2 < end1;
|
|
}
|
|
|
|
// Get minutes between two events (null if they overlap)
|
|
private getTimeBetweenEvents(
|
|
newStart: Date, newEnd: Date,
|
|
existingStart: Date, existingEnd: Date
|
|
): number | null {
|
|
// If new event is after existing event
|
|
if (newStart >= existingEnd) {
|
|
return Math.floor((newStart.getTime() - existingEnd.getTime()) / (1000 * 60));
|
|
}
|
|
// If new event is before existing event
|
|
else if (newEnd <= existingStart) {
|
|
return Math.floor((existingStart.getTime() - newEnd.getTime()) / (1000 * 60));
|
|
}
|
|
// Events overlap
|
|
return null;
|
|
}
|
|
|
|
// Generate summary message for driver status
|
|
getDriverStatusSummary(availability: DriverAvailability): string {
|
|
switch (availability.status) {
|
|
case 'available':
|
|
return `✅ Fully available (${availability.assignmentCount} assignments)`;
|
|
case 'scheduled':
|
|
return `🟡 Has ${availability.assignmentCount} assignment(s) but available for this time`;
|
|
case 'tight_turnaround':
|
|
const tightConflict = availability.conflicts.find(c => c.type === 'tight_turnaround');
|
|
return `⚡ Tight turnaround - ${tightConflict?.timeDifference} min between events`;
|
|
case 'overlapping':
|
|
return `🔴 Time conflict with existing assignment`;
|
|
default:
|
|
return 'Unknown status';
|
|
}
|
|
}
|
|
}
|
|
|
|
export default new DriverConflictService();
|
|
export { DriverAvailability, ConflictInfo, ScheduleEvent };
|