diff --git a/addon/controllers/operations/scheduler/index.js b/addon/controllers/operations/scheduler/index.js index 78b95528..eb886d98 100644 --- a/addon/controllers/operations/scheduler/index.js +++ b/addon/controllers/operations/scheduler/index.js @@ -16,10 +16,20 @@ import createFullCalendarEventFromScheduleItem from '../../../utils/create-full- * Unified order dispatch board controller. * All scheduling domain logic is delegated to the injected `scheduling` service. * + * Calendar library: @event-calendar/core (MIT licensed). + * This replaces FullCalendar Premium resource-timeline plugins which are + * incompatible with Fleetbase's dual AGPL v3 / commercial license. + * * Data flow: * Route -> store.query() -> Ember Data store * Controller computed getters -> store.peekAll() -> reactive UI * Socket service -> store.pushPayload() -> reactive UI (no page refresh) + * + * External drag-and-drop: + * Sidebar cards use native HTML5 draggable="true". + * onSidebarDragStart stores the dragged order reference. + * onCalendarDrop uses calendar.dateFromPoint(x, y) to resolve the target + * date and resource, then delegates to SchedulingService.assignOrder(). */ export default class OperationsSchedulerIndexController extends Controller { @service scheduling; @@ -41,6 +51,9 @@ export default class OperationsSchedulerIndexController extends Controller { @tracked drivers = []; @tracked sidebarCollapsed = false; + // Holds the order being dragged from the sidebar so onCalendarDrop can access it. + _draggedOrder = null; + // ------------------------------------------------------------------------- // Reactive Computed Getters // ------------------------------------------------------------------------- @@ -76,7 +89,9 @@ export default class OperationsSchedulerIndexController extends Controller { @computed('allActiveOrders.@each.{scheduled_at,driver_uuid,status}') get calendarEvents() { - return this.allActiveOrders.filter((o) => !isNone(o.scheduled_at) && isValidDate(new Date(o.scheduled_at))).map((o) => createFullCalendarEventFromOrder(o)); + return this.allActiveOrders + .filter((o) => !isNone(o.scheduled_at) && isValidDate(new Date(o.scheduled_at))) + .map((o) => createFullCalendarEventFromOrder(o)); } @computed('drivers.[]', 'allActiveOrders.@each.{scheduled_at,driver_uuid}') @@ -119,6 +134,66 @@ export default class OperationsSchedulerIndexController extends Controller { return [...this.calendarEvents, ...this.backgroundEvents]; } + /** + * The view name string passed to EventCalendar's @view arg. + * @event-calendar/core uses 'resourceTimelineDay' / 'resourceTimelineWeek' + * identical to FullCalendar's naming convention. + */ + get currentCalendarView() { + const viewMap = { day: 'resourceTimelineDay', week: 'resourceTimelineWeek' }; + return viewMap[this.viewRange] ?? 'resourceTimelineDay'; + } + + // ------------------------------------------------------------------------- + // EventCalendar Render Hooks + // ------------------------------------------------------------------------- + + /** + * Renders the resource label cell for each driver row. + * Returns an HTML string that EventCalendar injects into the label cell. + * Shows driver name and a Tailwind capacity bar. + */ + @action renderResourceLabel({ resource }) { + const { driver, workload } = resource.extendedProps ?? {}; + if (!driver) return resource.title ?? ''; + const { assigned = 0, capacity = 10, percentage = 0 } = workload ?? {}; + const barColour = percentage >= 90 ? '#ef4444' : percentage >= 70 ? '#f59e0b' : '#6366f1'; + return { + html: `