@fleetbase/fleetops-engine 0.6.25 → 0.6.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DRIVER_SCHEDULING.md +186 -0
- package/addon/components/driver/schedule.hbs +100 -0
- package/addon/components/driver/schedule.js +267 -0
- package/addon/components/order/kanban-card.hbs +2 -2
- package/addon/components/vehicle/details.hbs +594 -4
- package/addon/components/vehicle/form.hbs +467 -41
- package/addon/controllers/analytics/reports/index.js +3 -2
- package/addon/controllers/connectivity/devices/index.js +3 -3
- package/addon/controllers/connectivity/events/index.js +3 -2
- package/addon/controllers/connectivity/sensors/index.js +3 -5
- package/addon/controllers/connectivity/telematics/index.js +3 -1
- package/addon/controllers/maintenance/equipment/index.js +4 -4
- package/addon/controllers/maintenance/parts/index.js +4 -4
- package/addon/controllers/maintenance/work-orders/index.js +4 -4
- package/addon/controllers/management/contacts/customers.js +12 -10
- package/addon/controllers/management/contacts/index.js +3 -10
- package/addon/controllers/management/drivers/index/details.js +26 -13
- package/addon/controllers/management/drivers/index.js +4 -16
- package/addon/controllers/management/fleets/index.js +3 -13
- package/addon/controllers/management/fuel-reports/index.js +3 -10
- package/addon/controllers/management/issues/index.js +3 -12
- package/addon/controllers/management/places/index.js +4 -12
- package/addon/controllers/management/vehicles/index.js +3 -13
- package/addon/controllers/management/vendors/index.js +3 -13
- package/addon/controllers/operations/orders/index.js +5 -22
- package/addon/controllers/operations/scheduler/index.js +195 -6
- package/addon/controllers/operations/service-rates/index.js +34 -34
- package/addon/controllers/settings/payments/index.js +0 -6
- package/addon/routes.js +1 -0
- package/addon/services/driver-scheduling.js +171 -0
- package/addon/services/leaflet-layer-visibility-manager.js +4 -1
- package/addon/services/service-rate-actions.js +5 -1
- package/addon/templates/management/drivers/index/details/positions.hbs +1 -2
- package/addon/templates/management/drivers/index/details/schedule.hbs +1 -2
- package/addon/templates/operations/scheduler/index.hbs +48 -10
- package/addon/utils/fleet-ops-options.js +86 -0
- package/app/services/driver-scheduling.js +1 -0
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +3 -3
- package/server/migrations/2025_11_17_033648_add_additional_columns_to_vehicles_table.php +142 -0
- package/server/src/Constraints/HOSConstraint.php +244 -0
- package/server/src/Http/Controllers/Api/v1/OrderController.php +1 -1
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +8 -3
- package/server/src/Http/Resources/v1/Vehicle.php +197 -19
- package/server/src/Http/Resources/v1/VehicleWithoutDriver.php +211 -28
- package/server/src/Models/Driver.php +12 -8
- package/server/src/Models/Vehicle.php +101 -15
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# FleetOps Driver Scheduling Module
|
|
2
|
+
|
|
3
|
+
This module implements driver scheduling with Hours of Service (HOS) compliance for FleetOps.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Fleet-Wide Scheduler**: Manage all drivers' schedules in a single calendar view
|
|
8
|
+
- **Driver Schedule Management**: Create, edit, and manage driver shifts
|
|
9
|
+
- **HOS Compliance**: Real-time validation against FMCSA regulations
|
|
10
|
+
- **Calendar View**: Visual schedule management with drag-and-drop
|
|
11
|
+
- **Resource Timeline**: View multiple drivers' schedules simultaneously
|
|
12
|
+
- **Individual Driver Schedules**: Dedicated schedule view on driver detail pages
|
|
13
|
+
- **Availability Management**: Track driver availability and time-off requests
|
|
14
|
+
- **Conflict Detection**: Automatic detection of scheduling conflicts
|
|
15
|
+
- **Dual View Modes**: Toggle between order scheduling and driver scheduling
|
|
16
|
+
|
|
17
|
+
## Components
|
|
18
|
+
|
|
19
|
+
### Operations/Scheduler (Fleet-Wide View)
|
|
20
|
+
|
|
21
|
+
The main scheduler view at `operations/scheduler` now supports both order scheduling and driver scheduling.
|
|
22
|
+
|
|
23
|
+
**Location**: `addon/controllers/operations/scheduler/index.js`
|
|
24
|
+
|
|
25
|
+
**Features**:
|
|
26
|
+
- **View Mode Toggle**: Switch between "Orders" and "Driver Schedules" modes
|
|
27
|
+
- **Resource Timeline**: In driver mode, shows all drivers as resources with their shifts
|
|
28
|
+
- **Drag-and-Drop**: Drag shifts between drivers or reschedule by dragging
|
|
29
|
+
- **Add Shift**: Quick action button to create new driver shifts
|
|
30
|
+
- **Real-Time Updates**: Calendar updates automatically when shifts are modified
|
|
31
|
+
- **Status Colors**: Visual indicators for shift status (pending, confirmed, etc.)
|
|
32
|
+
|
|
33
|
+
**View Modes**:
|
|
34
|
+
1. **Orders Mode** (default):
|
|
35
|
+
- Month calendar view
|
|
36
|
+
- Drag unscheduled orders to calendar
|
|
37
|
+
- Manage order scheduling
|
|
38
|
+
|
|
39
|
+
2. **Driver Schedules Mode**:
|
|
40
|
+
- Resource timeline view (week view with drivers as resources)
|
|
41
|
+
- View all drivers' shifts simultaneously
|
|
42
|
+
- Drag shifts between drivers
|
|
43
|
+
- Reschedule shifts by dragging
|
|
44
|
+
- Click shifts to view/edit details
|
|
45
|
+
|
|
46
|
+
**Usage**:
|
|
47
|
+
Navigate to Operations → Scheduler, then toggle between "Orders" and "Driver Schedules" using the header buttons.
|
|
48
|
+
|
|
49
|
+
### Driver::Schedule
|
|
50
|
+
|
|
51
|
+
Displays and manages a driver's schedule from their detail page.
|
|
52
|
+
|
|
53
|
+
**Location**: `addon/components/driver/schedule.js`
|
|
54
|
+
|
|
55
|
+
**Features**:
|
|
56
|
+
- HOS compliance dashboard with visual indicators
|
|
57
|
+
- Weekly calendar view of driver shifts
|
|
58
|
+
- Upcoming shifts list (next 5 shifts)
|
|
59
|
+
- Availability and time-off management
|
|
60
|
+
- Quick actions for adding shifts and requesting time off
|
|
61
|
+
|
|
62
|
+
**Usage**:
|
|
63
|
+
```handlebars
|
|
64
|
+
<Driver::Schedule @driver={{@model}} />
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Integration**: Add a "Schedule" tab to the driver detail page.
|
|
68
|
+
|
|
69
|
+
## Backend Components
|
|
70
|
+
|
|
71
|
+
### HOSConstraint
|
|
72
|
+
|
|
73
|
+
Validates schedule items against FMCSA Hours of Service regulations.
|
|
74
|
+
|
|
75
|
+
**Location**: `server/src/Constraints/HOSConstraint.php`
|
|
76
|
+
|
|
77
|
+
**Regulations Enforced**:
|
|
78
|
+
1. **11-Hour Driving Limit**: Maximum 11 hours driving after 10 consecutive hours off duty
|
|
79
|
+
2. **14-Hour Duty Window**: Cannot drive beyond 14th hour after coming on duty
|
|
80
|
+
3. **60/70-Hour Weekly Limit**: Cannot drive after 60/70 hours in 7/8 consecutive days
|
|
81
|
+
4. **30-Minute Break**: Required after 8 cumulative hours of driving
|
|
82
|
+
|
|
83
|
+
**Violation Severity**:
|
|
84
|
+
- `critical`: Immediate compliance issue, shift cannot be scheduled
|
|
85
|
+
- `warning`: Approaching limit, requires attention
|
|
86
|
+
|
|
87
|
+
**Registration**:
|
|
88
|
+
```php
|
|
89
|
+
// In FleetOps ServiceProvider boot() method
|
|
90
|
+
$constraintService = app(\Fleetbase\Services\Scheduling\ConstraintService::class);
|
|
91
|
+
$constraintService->register('driver', \Fleetbase\FleetOps\Constraints\HOSConstraint::class);
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## HOS Compliance Dashboard
|
|
95
|
+
|
|
96
|
+
The HOS dashboard displays:
|
|
97
|
+
- **Daily Driving Hours**: X/11 hours with circular progress indicator
|
|
98
|
+
- **Weekly Hours**: X/70 hours with circular progress indicator
|
|
99
|
+
- **Compliance Status**: Badge indicating compliance level
|
|
100
|
+
- Green: Compliant
|
|
101
|
+
- Yellow: Approaching Limit
|
|
102
|
+
- Red: At Limit
|
|
103
|
+
|
|
104
|
+
## API Endpoints
|
|
105
|
+
|
|
106
|
+
FleetOps extends the core scheduling endpoints with driver-specific functionality:
|
|
107
|
+
|
|
108
|
+
- `GET /drivers/{id}/hos-status` - Get HOS compliance status for a driver
|
|
109
|
+
- `GET /drivers/{id}/schedule` - Get schedule items for a driver
|
|
110
|
+
- `POST /drivers/{id}/schedule` - Create a new shift for a driver
|
|
111
|
+
- `PUT /schedule-items/{id}` - Update a shift (with HOS validation)
|
|
112
|
+
- `DELETE /schedule-items/{id}` - Delete a shift
|
|
113
|
+
|
|
114
|
+
## Workflow
|
|
115
|
+
|
|
116
|
+
### Creating a Driver Shift
|
|
117
|
+
|
|
118
|
+
1. User clicks "Add Shift" button
|
|
119
|
+
2. Modal opens with shift form
|
|
120
|
+
3. User selects date, time, vehicle, and other details
|
|
121
|
+
4. System validates against HOS constraints
|
|
122
|
+
5. If violations found, display warnings/errors
|
|
123
|
+
6. If valid, create schedule item
|
|
124
|
+
7. Update driver schedule view and HOS dashboard
|
|
125
|
+
|
|
126
|
+
### HOS Validation
|
|
127
|
+
|
|
128
|
+
1. When a schedule item is created/updated
|
|
129
|
+
2. `HOSConstraint::validate()` is called
|
|
130
|
+
3. Checks all four HOS regulations
|
|
131
|
+
4. Returns violations array if any
|
|
132
|
+
5. Frontend displays violations to user
|
|
133
|
+
6. User can adjust shift or override (with proper permissions)
|
|
134
|
+
|
|
135
|
+
## Integration with Core Scheduling
|
|
136
|
+
|
|
137
|
+
FleetOps uses the core scheduling module with driver-specific customizations:
|
|
138
|
+
|
|
139
|
+
**Core Components Used**:
|
|
140
|
+
- `Schedule` model (subject_type: 'fleet', subject_uuid: fleet.id)
|
|
141
|
+
- `ScheduleItem` model (assignee_type: 'driver', assignee_uuid: driver.id)
|
|
142
|
+
- `ScheduleAvailability` model (subject_type: 'driver')
|
|
143
|
+
- `ScheduleConstraint` model (subject_type: 'driver')
|
|
144
|
+
|
|
145
|
+
**FleetOps Extensions**:
|
|
146
|
+
- `HOSConstraint` for compliance validation
|
|
147
|
+
- `Driver::Schedule` component for driver-specific UI
|
|
148
|
+
- HOS status API endpoint
|
|
149
|
+
- Driver-specific scheduling logic
|
|
150
|
+
|
|
151
|
+
## Future Enhancements
|
|
152
|
+
|
|
153
|
+
- **Automatic Schedule Generation**: AI-powered schedule optimization
|
|
154
|
+
- **ELD Integration**: Sync with Electronic Logging Devices
|
|
155
|
+
- **Predictive HOS**: Forecast HOS availability for future dates
|
|
156
|
+
- **Mobile App**: Driver-facing mobile app for schedule viewing
|
|
157
|
+
- **Notifications**: Real-time alerts for schedule changes and HOS warnings
|
|
158
|
+
- **Reporting**: HOS compliance reports and analytics
|
|
159
|
+
|
|
160
|
+
## Testing
|
|
161
|
+
|
|
162
|
+
### HOS Constraint Tests
|
|
163
|
+
|
|
164
|
+
Test cases should cover:
|
|
165
|
+
- 11-hour driving limit enforcement
|
|
166
|
+
- 14-hour duty window enforcement
|
|
167
|
+
- 60/70-hour weekly limit enforcement
|
|
168
|
+
- 30-minute break requirement
|
|
169
|
+
- Edge cases (consecutive shifts, split shifts, etc.)
|
|
170
|
+
|
|
171
|
+
### Integration Tests
|
|
172
|
+
|
|
173
|
+
- Create driver shift via API
|
|
174
|
+
- Validate HOS constraints are enforced
|
|
175
|
+
- Update shift and verify re-validation
|
|
176
|
+
- Delete shift and verify HOS recalculation
|
|
177
|
+
- Test driver schedule view rendering
|
|
178
|
+
|
|
179
|
+
## Compliance Notes
|
|
180
|
+
|
|
181
|
+
This implementation follows FMCSA Hours of Service regulations as of 2025. Regulations may vary by:
|
|
182
|
+
- Jurisdiction (US Federal, state-specific, Canada, etc.)
|
|
183
|
+
- Vehicle type (property-carrying vs. passenger-carrying)
|
|
184
|
+
- Industry (short-haul vs. long-haul)
|
|
185
|
+
|
|
186
|
+
**Important**: This is a software implementation and should not be the sole method of HOS compliance. Proper driver training, ELD integration, and regular audits are essential for full compliance.
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
<div class="driver-schedule-container" ...attributes>
|
|
2
|
+
<ContentPanel @title="Hours of Service Status" @open={{true}} @isLoading={{this.loadHOSStatus.isRunning}} @wrapperClass="bordered-top">
|
|
3
|
+
{{#if this.hosStatus}}
|
|
4
|
+
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
5
|
+
<div class="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-700 rounded">
|
|
6
|
+
<div>
|
|
7
|
+
<div class="text-sm text-gray-500 dark:text-gray-400">Daily Driving Hours</div>
|
|
8
|
+
<div class="text-2xl font-bold">{{this.hosStatus.daily_hours}}/11</div>
|
|
9
|
+
</div>
|
|
10
|
+
<div class="w-16 h-16">
|
|
11
|
+
<CircularProgress @value={{this.hosStatus.daily_hours}} @max={{11}} />
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<div class="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-700 rounded">
|
|
16
|
+
<div>
|
|
17
|
+
<div class="text-sm text-gray-500 dark:text-gray-400">Weekly Hours</div>
|
|
18
|
+
<div class="text-2xl font-bold">{{this.hosStatus.weekly_hours}}/70</div>
|
|
19
|
+
</div>
|
|
20
|
+
<div class="w-16 h-16">
|
|
21
|
+
<CircularProgress @value={{this.hosStatus.weekly_hours}} @max={{70}} />
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div class="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-700 rounded">
|
|
26
|
+
<div>
|
|
27
|
+
<div class="text-sm text-gray-500 dark:text-gray-400">Compliance Status</div>
|
|
28
|
+
<span
|
|
29
|
+
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-{{this.hosComplianceBadge.color}}-100 text-{{this.hosComplianceBadge.color}}-800"
|
|
30
|
+
>
|
|
31
|
+
{{this.hosComplianceBadge.text}}
|
|
32
|
+
</span>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
{{/if}}
|
|
37
|
+
</ContentPanel>
|
|
38
|
+
<ContentPanel @title="Schedule Calendar" @open={{true}} @isLoading={{this.loadDriverSchedule.isRunning}} @actionButtons={{this.scheduleActionButtons}} @wrapperClass="bordered-top">
|
|
39
|
+
<ScheduleCalendar @items={{this.scheduleItems}} @view="timeGridWeek" @onItemClick={{this.handleItemClick}}>
|
|
40
|
+
<:item as |ctx|>
|
|
41
|
+
<div class="schedule-item-content">
|
|
42
|
+
<div class="font-medium">{{ctx.item.title}}</div>
|
|
43
|
+
<div class="text-xs">{{ctx.item.duration}} min</div>
|
|
44
|
+
</div>
|
|
45
|
+
</:item>
|
|
46
|
+
</ScheduleCalendar>
|
|
47
|
+
</ContentPanel>
|
|
48
|
+
|
|
49
|
+
<ContentPanel @title="Upcoming Shifts" @open={{true}} @actionButtons={{this.shiftActionButtons}} @wrapperClass="bordered-top">
|
|
50
|
+
{{#if this.upcomingShifts.length}}
|
|
51
|
+
<div class="space-y-2">
|
|
52
|
+
{{#each this.upcomingShifts as |shift|}}
|
|
53
|
+
<ScheduleItemCard @item={{shift}} @onClick={{this.handleItemClick}}>
|
|
54
|
+
<:actions>
|
|
55
|
+
<button type="button" class="text-blue-600 hover:text-blue-800 text-sm" {{on "click" (fn this.editScheduleItem shift)}}>
|
|
56
|
+
Edit
|
|
57
|
+
</button>
|
|
58
|
+
</:actions>
|
|
59
|
+
</ScheduleItemCard>
|
|
60
|
+
{{/each}}
|
|
61
|
+
</div>
|
|
62
|
+
{{else}}
|
|
63
|
+
<div class="text-center py-4 text-gray-500 dark:text-gray-400">
|
|
64
|
+
No upcoming shifts scheduled
|
|
65
|
+
</div>
|
|
66
|
+
{{/if}}
|
|
67
|
+
</ContentPanel>
|
|
68
|
+
|
|
69
|
+
<ContentPanel @title="Availability & Time Off" @open={{true}} @actionButtons={{this.availabilityActionButtons}} @wrapperClass="bordered-top">
|
|
70
|
+
{{#if this.availability.length}}
|
|
71
|
+
<div class="space-y-2">
|
|
72
|
+
{{#each this.availability as |avail|}}
|
|
73
|
+
<div class="flex items-center justify-between p-2 bg-gray-50 dark:bg-gray-700 rounded">
|
|
74
|
+
<div>
|
|
75
|
+
<div class="font-medium">
|
|
76
|
+
{{#if avail.is_available}}
|
|
77
|
+
<span class="text-green-600">Available</span>
|
|
78
|
+
{{else}}
|
|
79
|
+
<span class="text-red-600">Unavailable</span>
|
|
80
|
+
{{/if}}
|
|
81
|
+
</div>
|
|
82
|
+
<div class="text-sm text-gray-500">
|
|
83
|
+
{{format-date-fns avail.start_at "MMM DD, YYYY"}}
|
|
84
|
+
-
|
|
85
|
+
{{format-date-fns avail.end_at "MMM DD, YYYY"}}
|
|
86
|
+
</div>
|
|
87
|
+
{{#if avail.reason}}
|
|
88
|
+
<div class="text-xs text-gray-400">{{avail.reason}}</div>
|
|
89
|
+
{{/if}}
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
{{/each}}
|
|
93
|
+
</div>
|
|
94
|
+
{{else}}
|
|
95
|
+
<div class="text-center py-4 text-gray-500 dark:text-gray-400">
|
|
96
|
+
No availability restrictions set
|
|
97
|
+
</div>
|
|
98
|
+
{{/if}}
|
|
99
|
+
</ContentPanel>
|
|
100
|
+
</div>
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import Component from '@glimmer/component';
|
|
2
|
+
import { tracked } from '@glimmer/tracking';
|
|
3
|
+
import { inject as service } from '@ember/service';
|
|
4
|
+
import { action } from '@ember/object';
|
|
5
|
+
import { task } from 'ember-concurrency';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Driver::Schedule Component
|
|
9
|
+
*
|
|
10
|
+
* Displays and manages a driver's schedule from their detail page.
|
|
11
|
+
* Includes HOS compliance tracking, upcoming shifts, and availability management.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* <Driver::Schedule @resource={{@model}} />
|
|
15
|
+
*/
|
|
16
|
+
export default class DriverScheduleComponent extends Component {
|
|
17
|
+
@service driverScheduling;
|
|
18
|
+
@service notifications;
|
|
19
|
+
@service modalsManager;
|
|
20
|
+
@service store;
|
|
21
|
+
@tracked scheduleItems = [];
|
|
22
|
+
@tracked upcomingShifts = [];
|
|
23
|
+
@tracked hosStatus = null;
|
|
24
|
+
@tracked availability = [];
|
|
25
|
+
@tracked timeOffRequests = [];
|
|
26
|
+
@tracked selectedItem = null;
|
|
27
|
+
|
|
28
|
+
get scheduleActionButtons() {
|
|
29
|
+
return [
|
|
30
|
+
{
|
|
31
|
+
type: 'default',
|
|
32
|
+
text: 'Request Time Off',
|
|
33
|
+
icon: 'calendar-times',
|
|
34
|
+
iconPrefix: 'fas',
|
|
35
|
+
permission: 'fleet-ops update driver',
|
|
36
|
+
onClick: this.requestTimeOff,
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
get shiftActionButtons() {
|
|
42
|
+
return [
|
|
43
|
+
{
|
|
44
|
+
type: 'default',
|
|
45
|
+
text: 'Add Shift',
|
|
46
|
+
icon: 'plus',
|
|
47
|
+
iconPrefix: 'fas',
|
|
48
|
+
permission: 'fleet-ops update driver',
|
|
49
|
+
onClick: this.addShift,
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
get availabilityActionButtons() {
|
|
55
|
+
return [
|
|
56
|
+
{
|
|
57
|
+
type: 'default',
|
|
58
|
+
text: 'Set Availability',
|
|
59
|
+
icon: 'clock',
|
|
60
|
+
iconPrefix: 'fas',
|
|
61
|
+
permission: 'fleet-ops update driver',
|
|
62
|
+
onClick: this.setAvailability,
|
|
63
|
+
},
|
|
64
|
+
];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
constructor() {
|
|
68
|
+
super(...arguments);
|
|
69
|
+
this.loadDriverSchedule.perform();
|
|
70
|
+
this.loadHOSStatus.perform();
|
|
71
|
+
this.loadAvailability.perform();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Load driver schedule items
|
|
76
|
+
*/
|
|
77
|
+
@task *loadDriverSchedule() {
|
|
78
|
+
try {
|
|
79
|
+
const items = yield this.driverScheduling.getScheduleItemsForAssignee.perform('driver', this.args.resource.id, {
|
|
80
|
+
start_at: this.startDate,
|
|
81
|
+
end_at: this.endDate,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
this.scheduleItems = items.toArray();
|
|
85
|
+
this.upcomingShifts = this.scheduleItems.filter((item) => new Date(item.start_at) > new Date()).slice(0, 5);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.error('Failed to load driver schedule:', error);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Load HOS status for the driver
|
|
93
|
+
*/
|
|
94
|
+
@task *loadHOSStatus() {
|
|
95
|
+
try {
|
|
96
|
+
const response = yield this.fetch.get(`drivers/${this.args.resource.id}/hos-status`);
|
|
97
|
+
this.hosStatus = response;
|
|
98
|
+
} catch (error) {
|
|
99
|
+
console.error('Failed to load HOS status:', error);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Load driver availability
|
|
105
|
+
*/
|
|
106
|
+
@task *loadAvailability() {
|
|
107
|
+
try {
|
|
108
|
+
const availability = yield this.store.query('schedule-availability', {
|
|
109
|
+
subject_type: 'driver',
|
|
110
|
+
subject_uuid: this.args.resource.id,
|
|
111
|
+
start_at: this.startDate,
|
|
112
|
+
end_at: this.endDate,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
this.availability = availability.toArray();
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.error('Failed to load availability:', error);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Get start date for schedule query (current week)
|
|
123
|
+
*/
|
|
124
|
+
get startDate() {
|
|
125
|
+
const now = new Date();
|
|
126
|
+
const dayOfWeek = now.getDay();
|
|
127
|
+
const diff = now.getDate() - dayOfWeek;
|
|
128
|
+
return new Date(now.setDate(diff)).toISOString();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Get end date for schedule query (4 weeks out)
|
|
133
|
+
*/
|
|
134
|
+
get endDate() {
|
|
135
|
+
const now = new Date();
|
|
136
|
+
return new Date(now.setDate(now.getDate() + 28)).toISOString();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get HOS compliance badge color
|
|
141
|
+
*/
|
|
142
|
+
get hosComplianceBadge() {
|
|
143
|
+
if (!this.hosStatus) {
|
|
144
|
+
return { color: 'gray', text: 'Unknown' };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const { daily_hours, weekly_hours } = this.hosStatus;
|
|
148
|
+
|
|
149
|
+
if (daily_hours >= 11 || weekly_hours >= 70) {
|
|
150
|
+
return { color: 'red', text: 'At Limit' };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (daily_hours >= 9 || weekly_hours >= 60) {
|
|
154
|
+
return { color: 'yellow', text: 'Approaching Limit' };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return { color: 'green', text: 'Compliant' };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Handle item click
|
|
162
|
+
*/
|
|
163
|
+
@action
|
|
164
|
+
handleItemClick(item) {
|
|
165
|
+
this.selectedItem = item;
|
|
166
|
+
this.modalsManager.show('modals/schedule-item-details', {
|
|
167
|
+
item,
|
|
168
|
+
onEdit: this.editScheduleItem,
|
|
169
|
+
onDelete: this.deleteScheduleItem,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Add new shift
|
|
175
|
+
*/
|
|
176
|
+
@action
|
|
177
|
+
addShift() {
|
|
178
|
+
this.modalsManager.show('modals/add-shift', {
|
|
179
|
+
driver: this.args.resource,
|
|
180
|
+
onSave: this.handleShiftAdded,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Edit schedule item
|
|
186
|
+
*/
|
|
187
|
+
@action
|
|
188
|
+
async editScheduleItem(item) {
|
|
189
|
+
this.modalsManager.show('modals/edit-shift', {
|
|
190
|
+
item,
|
|
191
|
+
driver: this.args.resource,
|
|
192
|
+
onSave: this.handleShiftUpdated,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Delete schedule item
|
|
198
|
+
*/
|
|
199
|
+
@action
|
|
200
|
+
async deleteScheduleItem(item) {
|
|
201
|
+
if (confirm('Are you sure you want to delete this shift?')) {
|
|
202
|
+
try {
|
|
203
|
+
await this.driverScheduling.deleteScheduleItem.perform(item);
|
|
204
|
+
await this.loadDriverSchedule.perform();
|
|
205
|
+
} catch (error) {
|
|
206
|
+
console.error('Failed to delete shift:', error);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Handle shift added
|
|
213
|
+
*/
|
|
214
|
+
@action
|
|
215
|
+
async handleShiftAdded() {
|
|
216
|
+
await this.loadDriverSchedule.perform();
|
|
217
|
+
await this.loadHOSStatus.perform();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Handle shift updated
|
|
222
|
+
*/
|
|
223
|
+
@action
|
|
224
|
+
async handleShiftUpdated() {
|
|
225
|
+
await this.loadDriverSchedule.perform();
|
|
226
|
+
await this.loadHOSStatus.perform();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Request time off
|
|
231
|
+
*/
|
|
232
|
+
@action
|
|
233
|
+
requestTimeOff() {
|
|
234
|
+
this.modalsManager.show('modals/request-time-off', {
|
|
235
|
+
driver: this.args.resource,
|
|
236
|
+
onSave: this.handleTimeOffRequested,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Handle time off requested
|
|
242
|
+
*/
|
|
243
|
+
@action
|
|
244
|
+
async handleTimeOffRequested() {
|
|
245
|
+
await this.loadAvailability.perform();
|
|
246
|
+
await this.loadDriverSchedule.perform();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Set availability
|
|
251
|
+
*/
|
|
252
|
+
@action
|
|
253
|
+
setAvailability() {
|
|
254
|
+
this.modalsManager.show('modals/set-availability', {
|
|
255
|
+
driver: this.args.resource,
|
|
256
|
+
onSave: this.handleAvailabilitySet,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Handle availability set
|
|
262
|
+
*/
|
|
263
|
+
@action
|
|
264
|
+
async handleAvailabilitySet() {
|
|
265
|
+
await this.loadAvailability.perform();
|
|
266
|
+
}
|
|
267
|
+
}
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
<div class="flex flex-row">
|
|
48
48
|
<div class="flex-1">
|
|
49
49
|
<div class="flex flex-row space-x-2">
|
|
50
|
-
<div class="resource-assigned-photo">
|
|
50
|
+
<div class="resource-assigned-photo relative">
|
|
51
51
|
<Image
|
|
52
52
|
src={{avatar-url @card.customer.photo_url}}
|
|
53
53
|
@fallbackSrc={{config "defaultValues.contactImage"}}
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
</div>
|
|
65
65
|
<div class="flex-1">
|
|
66
66
|
<div class="flex flex-row space-x-2">
|
|
67
|
-
<div class="resource-assigned-photo">
|
|
67
|
+
<div class="resource-assigned-photo relative">
|
|
68
68
|
<Image
|
|
69
69
|
src={{avatar-url @card.driver_assigned.photo_url}}
|
|
70
70
|
@fallbackSrc={{config "defaultValues.driverImage"}}
|