Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions addon/components/admin/visibility-controls.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export default class AdminVisibilityControlsComponent extends Component {
@tracked visibilitySettings = [
{ name: 'Dashboard', route: 'operations.orders', visible: true },
{ name: 'Service Rates', route: 'operations.service-rates', visible: true },
{ name: 'Scheduler', route: 'operations.scheduler', visible: true },
{ name: 'Drivers', route: 'management.drivers', visible: true },
{ name: 'Vehicles', route: 'management.vehicles', visible: true },
{ name: 'Fleets', route: 'management.fleets', visible: true },
Expand Down
1 change: 0 additions & 1 deletion addon/components/full-calendar.js

This file was deleted.

1 change: 0 additions & 1 deletion addon/components/full-calendar/draggable.js

This file was deleted.

1 change: 1 addition & 0 deletions addon/components/layout/fleet-ops-sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export default class LayoutFleetOpsSidebarComponent extends Component {
const operationsItems = [
{ title: 'Dashboard', icon: 'home', route: 'operations.orders' },
{ title: 'Service Rates', icon: 'file-invoice-dollar', route: 'operations.service-rates' },
{ title: 'Scheduler', icon: 'calendar-day', route: 'operations.scheduler' },
];

const resourcesItems = [
Expand Down
2 changes: 1 addition & 1 deletion addon/components/modals/order-event.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@
</div>
</div>

<ScheduleCard class="border border-night-905" @itemClass="bg-green-600" @contentClass="bg-transparent-i py-3" @titleClass="hidden-i" @order={{@options.order}} />
<Order::ScheduleCard class="border border-night-905" @itemClass="bg-green-600" @contentClass="bg-transparent-i py-3" @titleClass="hidden-i" @order={{@options.order}} />
</div>
</Modal::Default>
8 changes: 4 additions & 4 deletions addon/components/order/schedule-card.hbs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<div class="order-schedule-card border border-gray-200 dark:border-gray-700" {{did-insert this.setupComponent}} ...attributes>
<div class="order-schedule-card border border-gray-200 dark:border-gray-700" ...attributes>
<a href="javascript:;" class="{{@titleClass}} card-title border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900" {{on "click" (fn this.onTitleClick @order)}}>
<span>{{@order.public_id}}</span>
<div>
Expand All @@ -11,21 +11,21 @@
<FaIcon @icon="calendar" class="mr-2" />
<span class="font-semibold">Date scheduled</span>
</div>
<span>{{@order.scheduledAt}}</span>
<span class={{unless @order.scheduled_at "dark:text-gray-600 text-gray-400"}}>{{n-a @order.scheduledAt "Not Scheduled"}}</span>
</div>
<div class="flex items-center justify-between">
<div>
<FaIcon @icon="calendar" class="mr-2" />
<span class="font-semibold">Date created</span>
</div>
<span>{{@order.createdAtWithTime}}</span>
<span>{{n-a @order.createdAtWithTime}}</span>
</div>
<div class="flex items-center justify-between">
<div>
<FaIcon @icon="calendar" class="mr-2" />
<span class="font-semibold">Driver Assigned</span>
</div>
<span>{{@order.driver_assigned.name "None"}}</span>
<span class={{unless @order.driver_assigned "dark:text-gray-600 text-gray-400"}}>{{n-a @order.driver_assigned.name "No Driver Assigned"}}</span>
</div>
<div class="z-20 relative">
<RouteList @order={{@order}} />
Expand Down
19 changes: 13 additions & 6 deletions addon/components/order/schedule-card.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,23 @@ import Component from '@glimmer/component';
import { action } from '@ember/object';

export default class OrderScheduleCardComponent extends Component {
@action setupComponent() {
const { order } = this.args;
constructor(owner, { order }) {
super(...arguments);

order?.loadDriver();
order?.loadPayload();
if (order && typeof order.loadDriver === 'function') {
order.loadDriver();
}

if (order && typeof order.loadPayload === 'function') {
order.loadPayload();
}
}

@action onTitleClick(order) {
if (typeof this.args.onTitleClick === 'function') {
this.args.onTitleClick(order);
const { onTitleClick } = this.args;

if (typeof onTitleClick === 'function') {
onTitleClick(order);
}
}
}
76 changes: 46 additions & 30 deletions addon/controllers/operations/scheduler/index.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,27 @@
import Controller from '@ember/controller';
import { tracked } from '@glimmer/tracking';
import { inject as service } from '@ember/service';
import { computed, action } from '@ember/object';
import { action } from '@ember/object';
import { format, isValid as isValidDate } from 'date-fns';
import { isNone } from '@ember/utils';
import createFullCalendarEventFromOrder from '../../../utils/create-full-calendar-event-from-order';

export default class OperationsSchedulerIndexController extends Controller {
@service modalsManager;
@service notifications;

@computed('model.@each.scheduled_at') get scheduledOrders() {
return this.model.filter((order) => isValidDate(order.scheduled_at));
}

@computed('model.@each.scheduled_at') get unscheduledOrders() {
return this.model.filter((order) => !order.scheduled_at);
}

@computed('scheduledOrders.@each.scheduled_at') get events() {
return this.scheduledOrders.map((order) => ({
id: order.id,
title: `${order.scheduledAtTime} - ${order.public_id}`,
start: order.scheduled_at,
allDay: true,
}));
@service store;
@service hostRouter;
@tracked scheduledOrders = [];
@tracked unscheduledOrders = [];
@tracked events = [];

@action setCalendarApi(calendar) {
this.calendar = calendar;
}

@action viewEvent(order, eventClickInfo) {
const { event } = eventClickInfo;
@action viewEvent(order) {
// get the event from the calendar
let event = this.calendar.getEventById(order.id);

this.modalsManager.show('modals/order-event', {
title: `Scheduling for ${order.public_id}`,
Expand All @@ -34,7 +30,7 @@ export default class OperationsSchedulerIndexController extends Controller {
hideDeclineButton: true,
order,
reschedule: (date) => {
if (typeof date?.toDate === 'function') {
if (date && typeof date.toDate === 'function') {
date = date.toDate();
}

Expand All @@ -51,15 +47,27 @@ export default class OperationsSchedulerIndexController extends Controller {
}

return order.save().then((order) => {
this.notifications.success(`'${order.public_id}' has been updated.`);
// remove event from calendar
if (event) {
event.remove();
}

// update event props
event?.setProp('title', order.eventTitle);
if (order.scheduled_at) {
// notify order has been scheduled
this.notifications.success(`Order '${order.public_id}' has been scheduled at ${order.scheduledAt}.`);
// add event to calendar
event = this.calendar.addEvent(createFullCalendarEventFromOrder(order));
} else {
this.notifications.info(`Order '${order.public_id}' has been unscheduled.`);
}

// remove event from calendar if unscheduled
if (!order.scheduled_at || !isValidDate(order.scheduled_at)) {
event.remove();
// update event props
if (event && typeof event.setProp === 'function') {
event.setProp('title', order.eventTitle);
}

// refresh route
this.hostRouter.refresh();
});
},
});
Expand All @@ -80,15 +88,19 @@ export default class OperationsSchedulerIndexController extends Controller {
const order = this.store.peekRecord('order', data.id);

order.set('scheduled_at', date);
order.save();
return order.save().then(() => {
this.hostRouter.refresh();
});
}

@action receivedEvent(eventReceivedInfo) {
const { event } = eventReceivedInfo;
const order = this.store.peekRecord('order', event.id);

// update event props
event?.setProp('title', order.eventTitle);
if (typeof event.setProp === 'function') {
event.setProp('title', order.eventTitle);
}
}

@action rescheduleEventFromDrag(eventDropInfo) {
Expand All @@ -102,9 +114,13 @@ export default class OperationsSchedulerIndexController extends Controller {

// set and save order props
order.set('scheduled_at', isValidDate(newDate) ? newDate : start);
order.save();
order.save().then(() => {
this.hostRouter.refresh();
});

// update event props
event?.setProp('title', order.eventTitle);
if (typeof event.setProp === 'function') {
event.setProp('title', order.eventTitle);
}
}
}
25 changes: 25 additions & 0 deletions addon/routes/operations/scheduler/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,35 @@
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
import { isValid as isValidDate } from 'date-fns';
import { isNone } from '@ember/utils';
import createFullCalendarEventFromOrder from '../../../utils/create-full-calendar-event-from-order';

const getUnscheduledOrder = (order) => {
return isNone(order.scheduled_at);
};

const getScheduledOrder = (order) => {
return isValidDate(order.scheduled_at);
};

export default class OperationsSchedulerIndexRoute extends Route {
@service store;

model() {
return this.store.query('order', { status: 'created', with: ['payload'] });
}

setupController(controller, model) {
// get orders
const orders = model.toArray();

// set unscheduled orders
controller.unscheduledOrders = orders.filter(getUnscheduledOrder);

// set scheduled orders
controller.scheduledOrders = orders.filter(getScheduledOrder);

// create events from scheduledOrders
controller.events = controller.scheduledOrders.map(createFullCalendarEventFromOrder);
}
}
28 changes: 28 additions & 0 deletions addon/styles/fleetops-engine.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
.scheduler-left-sidebar {
border-right: 1px rgba(229, 231, 235, 1) solid;
height: 100vh;
overflow-y: scroll;
}

body[data-theme="dark"] .scheduler-left-sidebar {
border-right: 1px rgba(55, 65, 81, 1) solid;
}

.scheduler-left-sidebar > .next-content-panel-wrapper > .next-content-panel-container > .next-content-panel > .next-content-panel-header.next-content-panel-is-closed {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.scheduler-left-sidebar > .next-content-panel-wrapper > .next-content-panel-container > .next-content-panel > .next-content-panel-header {
border-top-left-radius: 0;
border-top-right-radius: 0;
border-top: 0;
border-left: 0;
border-right: 0;
}

.scheduler-left-sidebar > .next-content-panel-wrapper > .next-content-panel-container > .next-content-panel > .next-content-panel-body {
border-left: 0;
border-right: 0;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
42 changes: 33 additions & 9 deletions addon/templates/operations/scheduler/index.hbs
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
<Layout::Section::Header @title="Scheduler" />

{{!-- <div>
<div class="p-6">
<div class="grid grid-cols-3 gap-4">
<div class="space-y-4">
<ContentPanel @title="Unscheduled Orders" @titleStatuRight={{this.unscheduledOrders.length}} @titleStatuRightClass="info-status-badge" @hideStatusDot={{true}} @open={{this.unscheduledOrders.length}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
<Layout::Section::Body class="overflow-y-scroll h-screen">
<div class="h-screen">
<div class="grid grid-cols-1 lg:grid-cols-3">
<div class="scheduler-left-sidebar">
<ContentPanel
@title="Unscheduled Orders"
@titleStatuRight={{this.unscheduledOrders.length}}
@titleStatuRightClass="info-status-badge"
@hideStatusDot={{true}}
@open={{true}}
@pad={{true}}
@panelBodyClass="bg-white dark:bg-gray-900"
>
<div class="space-y-4">
{{#each this.unscheduledOrders as |order|}}
<FullCalendar::Draggable @eventData={{json-hash id=order.id title=order.eventTitle allDay=true}}>
Expand All @@ -13,7 +21,15 @@
{{/each}}
</div>
</ContentPanel>
<ContentPanel @title="Scheduled Orders" @titleStatuRight={{this.scheduledOrders.length}} @titleStatusRightClass="info-status-badge" @hideStatusDot={{true}} @open={{this.scheduledOrders.length}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
<ContentPanel
@title="Scheduled Orders"
@titleStatuRight={{this.scheduledOrders.length}}
@titleStatusRightClass="info-status-badge"
@hideStatusDot={{true}}
@open={{true}}
@pad={{true}}
@panelBodyClass="bg-white dark:bg-gray-900"
>
<div class="space-y-4">
{{#each this.scheduledOrders as |order|}}
<Order::ScheduleCard @order={{order}} @titleClass="cursor-pointer" @onTitleClick={{fn this.viewEvent order}} />
Expand All @@ -22,9 +38,17 @@
</ContentPanel>
</div>

<div class="col-span-2">
<FullCalendar @events={{this.events}} @onDrop={{this.scheduleEventFromDrop}} @onEventReceive={{this.receivedEvent}} @onEventDrop={{this.rescheduleEventFromDrag}} @onEventClick={{this.viewOrderAsEvent}} />
<div class="col-span-2 p-4">
<FullCalendar
@events={{this.events}}
@editable={{true}}
@onDrop={{this.scheduleEventFromDrop}}
@onEventReceive={{this.receivedEvent}}
@onEventDrop={{this.rescheduleEventFromDrag}}
@onEventClick={{this.viewOrderAsEvent}}
@onInit={{this.setCalendarApi}}
/>
</div>
</div>
</div>
</div> --}}
</Layout::Section::Body>
8 changes: 8 additions & 0 deletions addon/utils/create-full-calendar-event-from-order.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default function createFullCalendarEventFromOrder(order) {
return {
id: order.id,
title: `${order.scheduledAtTime} - ${order.public_id}`,
start: order.scheduled_at,
allDay: true,
};
}
1 change: 0 additions & 1 deletion app/components/full-calendar.js

This file was deleted.

1 change: 0 additions & 1 deletion app/components/full-calendar/draggable.js

This file was deleted.

1 change: 1 addition & 0 deletions app/utils/create-full-calendar-event-from-order.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from '@fleetbase/fleetops-engine/utils/create-full-calendar-event-from-order';
22 changes: 22 additions & 0 deletions server/src/Models/Order.php
Original file line number Diff line number Diff line change
Expand Up @@ -947,6 +947,28 @@ public function setCustomer($model)
$this->customer_type = Utils::getModelClassName($model);
}

public function setCustomerTypeAttribute($type)
{
if (is_string($type)) {
if ($type === 'customer' || $type === 'vendor' || !Str::startsWith($type, 'fleet-ops')) {
$type = 'fleet-ops:' . $type;
}

$this->attributes['customer_type'] = Utils::getMutationType($type);
}
}

public function setFacilitatorTypeAttribute($type)
{
if (is_string($type)) {
if ($type === 'customer' || $type === 'vendor' || !Str::startsWith($type, 'fleet-ops')) {
$type = 'fleet-ops:' . $type;
}

$this->attributes['facilitator_type'] = Utils::getMutationType($type);
}
}

public function setDriverLocationAsPickup($force = false)
{
if ($force === true) {
Expand Down
Loading