@fleetbase/fleetops-engine
Version:
Fleet & Transport Management Extension for Fleetbase
211 lines (181 loc) • 7.67 kB
JavaScript
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';
/**
* Modals::AddDriverShift
*
* Supports two modes toggled by the user:
* 1. "Single shift" — creates one ScheduleItem on a specific date/time
* 2. "Recurring schedule" — creates a ScheduleTemplate (RRULE) and triggers
* materialisation for the rolling 60-day horizon
*
* Plain text/textarea inputs bind @value directly to @options properties —
* Ember's two-way binding keeps them in sync without manual update actions.
* Custom components (DateTimeInput, InputGroup @type="time", ModelSelect)
* still use @onChange because they emit processed values, not DOM events.
*
* Options accepted:
* - drivers: array of Driver records (for the dropdown in the global scheduler)
* - selectedDriver: pre-selected Driver record (when opened from driver panel)
* - schedule: the driver's Schedule record (required for recurring mode)
*/
export default class ModalsAddDriverShiftComponent extends Component {
intl;
// ── Mode ──────────────────────────────────────────────────────────────────
isRecurring = true;
// ── Shared ────────────────────────────────────────────────────────────────
selectedDriver = null;
// ── Recurring-schedule fields that drive computed getters ─────────────────
selectedDays = [];
constructor() {
super(...arguments);
const opts = this.args.options;
// Seed all plain-text / date / time fields onto @options so the template
// can bind @value directly and the confirm callback reads them back.
opts.title = opts.title ?? '';
opts.notes = opts.notes ?? '';
opts.startAt = opts.startAt ?? null;
opts.endAt = opts.endAt ?? null;
opts.breakStartAt = opts.breakStartAt ?? null;
opts.breakEndAt = opts.breakEndAt ?? null;
opts.templateName = opts.templateName ?? '';
opts.templateColor = opts.templateColor ?? '#6366f1';
opts.shiftStartTime = opts.shiftStartTime ?? '08:00';
opts.shiftEndTime = opts.shiftEndTime ?? '16:00';
opts.breakStartTime = opts.breakStartTime ?? '';
opts.breakEndTime = opts.breakEndTime ?? '';
opts.recurrenceStartDate = opts.recurrenceStartDate ?? null;
opts.recurrenceEndDate = opts.recurrenceEndDate ?? null;
opts.isRecurring = true;
this.selectedDriver = opts.selectedDriver ?? null;
}
// ── Getters ───────────────────────────────────────────────────────────────
get drivers() {
return this.args.options?.drivers ?? [];
}
get hasManyDrivers() {
return this.drivers.length > 1;
}
get dayOptions() {
return [
{ code: 'MO', label: this.intl.t('scheduler.day-mon') },
{ code: 'TU', label: this.intl.t('scheduler.day-tue') },
{ code: 'WE', label: this.intl.t('scheduler.day-wed') },
{ code: 'TH', label: this.intl.t('scheduler.day-thu') },
{ code: 'FR', label: this.intl.t('scheduler.day-fri') },
{ code: 'SA', label: this.intl.t('scheduler.day-sat') },
{ code: 'SU', label: this.intl.t('scheduler.day-sun') },
];
}
get recurrenceSummary() {
if (!this.selectedDays.length) return null;
const dayMap = { MO: 'Mon', TU: 'Tue', WE: 'Wed', TH: 'Thu', FR: 'Fri', SA: 'Sat', SU: 'Sun' };
const days = this.selectedDays.map((d) => dayMap[d]).join(', ');
const opts = this.args.options;
const times = opts.shiftStartTime && opts.shiftEndTime ? ` · ${opts.shiftStartTime}–${opts.shiftEndTime}` : '';
return `Weekly on ${days}${times}`;
}
/**
* The toggle shows "One-off shift" and is ON when NOT recurring.
* isRecurring is the source of truth; isOneOff is the inverse for the toggle.
*/
get isOneOff() {
return !this.isRecurring;
}
get canConfirm() {
const opts = this.args.options;
if (this.isRecurring) {
return this.selectedDays.length > 0 && opts.shiftStartTime && opts.shiftEndTime;
}
return opts.startAt && opts.endAt;
}
// ── Actions ───────────────────────────────────────────────────────────────
toggleMode() {
this.isRecurring = !this.isRecurring;
this.args.options.isRecurring = this.isRecurring;
if (this.isRecurring) {
this.args.options.rrule = this.buildRrule();
}
}
toggleDay(code) {
if (this.selectedDays.includes(code)) {
this.selectedDays = this.selectedDays.filter((d) => d !== code);
} else {
this.selectedDays = [...this.selectedDays, code];
}
// Keep rrule on options in sync
this.args.options.rrule = this.buildRrule();
}
isDaySelected(code) {
return this.selectedDays.includes(code);
}
selectDriver(driver) {
this.selectedDriver = driver;
this.args.options.selectedDriver = driver;
}
// DateTimeInput emits a processed value — needs @onChange
updateStartAt(value) {
this.args.options.startAt = value;
}
updateEndAt(value) {
this.args.options.endAt = value;
}
updateBreakStartAt(value) {
this.args.options.breakStartAt = value;
}
updateBreakEndAt(value) {
this.args.options.breakEndAt = value;
}
// InputGroup @type="time" emits a processed value — needs @onChange
updateShiftStartTime(value) {
this.args.options.shiftStartTime = value;
// Recompute rrule summary reactivity trigger
this.args.options.rrule = this.buildRrule();
}
updateShiftEndTime(value) {
this.args.options.shiftEndTime = value;
this.args.options.rrule = this.buildRrule();
}
updateBreakStartTime(value) {
this.args.options.breakStartTime = value;
}
updateBreakEndTime(value) {
this.args.options.breakEndTime = value;
}
updateRecurrenceStartDate(value) {
this.args.options.recurrenceStartDate = value;
}
updateRecurrenceEndDate(value) {
this.args.options.recurrenceEndDate = value;
this.args.options.rrule = this.buildRrule();
}
/**
* Build the RRULE string from the selected days and optional end date.
* Example output: FREQ=WEEKLY;BYDAY=MO,TU,TH
*/
buildRrule() {
const parts = [`FREQ=WEEKLY`, `BYDAY=${this.selectedDays.join(',')}`];
const endDate = this.args.options.recurrenceEndDate;
if (endDate) {
const d = new Date(endDate);
const until = d.toISOString().replace(/[-:]/g, '').split('.')[0] + 'Z';
parts.push(`UNTIL=${until}`);
}
return parts.join(';');
}
}