@fleetbase/fleetops-engine
Version:
Fleet & Transport Management Extension for Fleetbase
282 lines (262 loc) • 12 kB
JavaScript
import Controller from '@ember/controller';
import { isArray } from '@ember/array';
import { inject as service } from '@ember/service';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
const CACHE_KEY = 'fleetops:maintenance-schedules:layout';
/** Map schedule status to a FullCalendar event background colour. */
function statusColor(status) {
switch (status) {
case 'overdue':
return '#EF4444'; // red-500
case 'paused':
return '#9CA3AF'; // gray-400
case 'active':
default:
return '#F59E0B'; // amber-500
}
}
export default class MaintenanceSchedulesIndexController extends Controller {
maintenanceScheduleActions;
fetch;
intl;
appCache;
notifications;
/** Alias used by the tabular layout template (@onSearch={{perform this.scheduleActions.controllerSearchTask this}}) */
get scheduleActions() {
return this.maintenanceScheduleActions;
}
queryParams = ['status', 'page', 'limit', 'sort', 'query', 'public_id', 'created_at', 'updated_at'];
page = 1;
limit;
sort = '-created_at';
public_id;
status;
/** 'list' | 'calendar' — persisted via appCache (same pattern as vehicles layout) */
layout = this.appCache.get(CACHE_KEY, 'list');
/** FullCalendar API instance (set via @onInit) */
calendarApi = null;
// ─── View mode ────────────────────────────────────────────────────────────
setCalendarApi(api) {
this.calendarApi = api;
}
// ─── Calendar feed (function-based event source for FullCalendar v6) ─────
//
// NOTE: layout is toggled via the dropdown-button in actionButtons (same
// pattern as the vehicles index layout switcher).
//
// FullCalendar v6 supports passing a function as the `events` option:
// events: function(fetchInfo, successCallback, failureCallback)
// The function is called automatically whenever the calendar needs to
// (re-)fetch events — on initial render and on navigation. `fetchInfo`
// contains `startStr` and `endStr` (ISO date strings) for the visible range.
//
// We bind this method and pass it as @events to the <FullCalendar> component.
// The component passes it straight through to `new Calendar(el, { events })`.
get calendarEventSource() {
// Return a bound function so `this` is always the controller.
// FullCalendar v6 calls this function with (fetchInfo, successCallback,
// failureCallback) whenever it needs to (re-)fetch events.
return (fetchInfo, successCallback, failureCallback) => {
this.fetch
.get('maintenance-schedules/calendar-feed', {
start: fetchInfo.startStr,
end: fetchInfo.endStr,
})
.then((response) => {
// The API returns { events: [...] } — unwrap the array.
const raw = isArray(response) ? response : (response?.events ?? []);
const events = raw.map((event) => ({
id: event.id,
title: event.title,
start: event.start,
end: event.end,
allDay: event.allDay !== false,
// Use the colour the backend already computed; fall back
// to the local statusColor helper if absent.
backgroundColor: event.color ?? statusColor(event.status),
borderColor: event.color ?? statusColor(event.status),
extendedProps: {
public_id: event.id,
uuid: event.uuid,
subject_name: event.subject_name,
assignee_name: event.assignee_name,
status: event.status,
priority: event.priority,
type: event.type,
},
}));
successCallback(events);
})
.catch((error) => {
failureCallback(error);
});
};
}
// ─── Calendar event click ─────────────────────────────────────────────────
onCalendarEventClick({ event }) {
const publicId = event?.extendedProps?.public_id;
if (publicId) {
// transition.view expects an object whose public_id is used as the URL segment
this.maintenanceScheduleActions.transition.view(publicId);
}
}
// ─── Action buttons ───────────────────────────────────────────────────────
setLayoutList() {
this.layout = 'list';
this.appCache.set(CACHE_KEY, 'list');
}
setLayoutCalendar() {
this.layout = 'calendar';
this.appCache.set(CACHE_KEY, 'calendar');
}
get actionButtons() {
return [
{
component: 'dropdown-button',
icon: 'display',
size: 'xs',
items: [
{
label: this.intl.t('common.table-view'),
icon: 'table-list',
onClick: this.setLayoutList,
},
{
label: this.intl.t('common.calendar-view'),
icon: 'calendar',
onClick: this.setLayoutCalendar,
},
],
renderInPlace: true,
helpText: this.intl.t('common.change-layout'),
},
{ icon: 'refresh', onClick: this.maintenanceScheduleActions.refresh, helpText: this.intl.t('common.refresh') },
{ text: this.intl.t('common.new'), type: 'primary', icon: 'plus', onClick: this.maintenanceScheduleActions.transition.create },
{ text: this.intl.t('common.import'), type: 'magic', icon: 'upload', onClick: this.maintenanceScheduleActions.import },
{ text: this.intl.t('common.export'), icon: 'long-arrow-up', iconClass: 'rotate-icon-45', wrapperClass: 'hidden md:flex', onClick: this.maintenanceScheduleActions.export },
];
}
get bulkActions() {
return [{ label: 'Delete selected...', class: 'text-red-500', fn: this.maintenanceScheduleActions.bulkDelete }];
}
get columns() {
return [
{
label: this.intl.t('column.id'),
valuePath: 'public_id',
cellComponent: 'table/cell/anchor',
cellClassNames: 'uppercase',
action: this.maintenanceScheduleActions.transition.view,
permission: 'fleet-ops view maintenance-schedule',
resizable: true,
sortable: true,
filterable: true,
filterParam: 'public_id',
filterComponent: 'filter/string',
},
{
label: this.intl.t('column.name'),
valuePath: 'name',
resizable: true,
sortable: true,
filterable: true,
filterParam: 'name',
filterComponent: 'filter/string',
},
{
label: this.intl.t('column.subject'),
valuePath: 'subject.name',
resizable: true,
sortable: false,
},
{
label: this.intl.t('column.type'),
valuePath: 'type',
cellComponent: 'table/cell/base',
humanize: true,
resizable: true,
sortable: true,
filterable: true,
filterParam: 'type',
filterComponent: 'filter/string',
},
{
label: this.intl.t('column.status'),
valuePath: 'status',
cellComponent: 'table/cell/status',
resizable: true,
sortable: true,
filterable: true,
filterParam: 'status',
filterComponent: 'filter/string',
},
{
label: this.intl.t('column.next-due'),
valuePath: 'nextDueAtShort',
sortParam: 'next_due_date',
resizable: true,
sortable: true,
filterable: true,
filterComponent: 'filter/date',
},
{
label: this.intl.t('column.created-at'),
valuePath: 'createdAt',
sortParam: 'created_at',
resizable: true,
sortable: true,
filterable: true,
filterComponent: 'filter/date',
},
{
label: '',
cellComponent: 'table/cell/dropdown',
ddButtonText: false,
ddButtonIcon: 'ellipsis-h',
ddButtonIconPrefix: 'fas',
ddMenuLabel: this.intl.t('common.resource-actions', { resource: this.intl.t('resource.maintenance-schedule') }),
cellClassNames: 'overflow-visible',
wrapperClass: 'flex items-center justify-end mx-2',
actions: [
{
label: this.intl.t('common.view-resource', { resource: this.intl.t('resource.maintenance-schedule') }),
fn: this.maintenanceScheduleActions.transition.view,
permission: 'fleet-ops view maintenance-schedule',
},
{
label: this.intl.t('common.edit-resource', { resource: this.intl.t('resource.maintenance-schedule') }),
fn: this.maintenanceScheduleActions.transition.edit,
permission: 'fleet-ops update maintenance-schedule',
},
{
label: this.intl.t('maintenance-schedule.actions.trigger-now'),
fn: this.maintenanceScheduleActions.triggerNow,
permission: 'fleet-ops update maintenance-schedule',
},
{ separator: true },
{
label: this.intl.t('maintenance-schedule.actions.pause'),
fn: this.maintenanceScheduleActions.pause,
permission: 'fleet-ops update maintenance-schedule',
},
{
label: this.intl.t('maintenance-schedule.actions.resume'),
fn: this.maintenanceScheduleActions.resume,
permission: 'fleet-ops update maintenance-schedule',
},
{ separator: true },
{
label: this.intl.t('common.delete-resource', { resource: this.intl.t('resource.maintenance-schedule') }),
fn: this.maintenanceScheduleActions.delete,
permission: 'fleet-ops delete maintenance-schedule',
},
],
sortable: false,
filterable: false,
resizable: false,
searchable: false,
},
];
}
}