diff --git a/addon/components/cell/order-id-with-series.hbs b/addon/components/cell/order-id-with-series.hbs new file mode 100644 index 000000000..012fbffc9 --- /dev/null +++ b/addon/components/cell/order-id-with-series.hbs @@ -0,0 +1,63 @@ +
+ {{#if this.isSeriesOrder}} + + + +
+
+
+
Recurring series
+
{{n-a this.series.name "Recurring series"}}
+
+ {{smart-humanize this.series.status}} +
+ +
+
{{this.scheduleSummary}}
+
+ {{#each this.scheduleDays as |day|}} + {{day.label}} + {{/each}} +
+
+ +
+
+ Scheduled time + {{n-a this.occurrenceTime "--"}} +
+
+ Spawned + {{this.spawnedCount}} orders +
+
+ This occurrence + {{n-a this.occurrenceDateLabel "--"}} +
+
+ + {{#if this.series}} +
+ + + +
+ {{/if}} +
+
+
+ {{/if}} + + + {{@value}} + +
diff --git a/addon/components/cell/order-id-with-series.js b/addon/components/cell/order-id-with-series.js new file mode 100644 index 000000000..98ffa5af7 --- /dev/null +++ b/addon/components/cell/order-id-with-series.js @@ -0,0 +1,127 @@ +import Component from '@glimmer/component'; +import { inject as service } from '@ember/service'; +import { action } from '@ember/object'; +import { format } from 'date-fns'; +import { parseRrule, WEEKDAY_OPTIONS } from '../../utils/recurring-rrule'; + +export default class CellOrderIdWithSeriesComponent extends Component { + @service recurringOrderScheduleActions; + + get order() { + return this.args.row; + } + + get series() { + return this.order?.recurring_order_schedule; + } + + get isSeriesOrder() { + return Boolean( + this.order?.is_recurring_generated === true || + this.order?.meta?.is_recurring_generated === true || + (this.order?.recurring_order_schedule_uuid && this.order?.recurring_occurrence_at) || + (this.series?.public_id && this.order?.recurring_occurrence_at) + ); + } + + get recurrence() { + return parseRrule(this.series?.rrule); + } + + get occurrenceDate() { + const value = this.order?.recurring_occurrence_at ?? this.order?.scheduled_at ?? this.series?.next_occurrence_at; + if (!value) { + return null; + } + + const date = value instanceof Date ? value : new Date(value); + + return Number.isNaN(date.getTime()) ? null : date; + } + + get occurrenceWeekday() { + if (!this.occurrenceDate) { + return null; + } + + return ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'][this.occurrenceDate.getDay()]; + } + + get occurrenceTime() { + return this.occurrenceDate ? format(this.occurrenceDate, 'HH:mm') : null; + } + + get occurrenceDateLabel() { + return this.occurrenceDate ? format(this.occurrenceDate, 'EEE, d MMM yyyy') : null; + } + + get scheduleDays() { + const weekdays = this.recurrence.weekdays ?? []; + + if (this.recurrence.frequency === 'daily') { + return WEEKDAY_OPTIONS.map((day) => ({ ...day, scheduled: true, highlighted: day.code === this.occurrenceWeekday })); + } + + return WEEKDAY_OPTIONS.map((day) => ({ + ...day, + scheduled: weekdays.includes(day.code), + highlighted: day.code === this.occurrenceWeekday, + })); + } + + get scheduleSummary() { + const frequency = this.recurrence.frequency ?? 'weekly'; + const interval = this.recurrence.interval > 1 ? `Every ${this.recurrence.interval} ${frequency}s` : `Every ${frequency}`; + + if (frequency === 'weekly' && this.recurrence.weekdays?.length) { + const days = this.scheduleDays + .filter((day) => day.scheduled) + .map((day) => day.label) + .join(', '); + + return `${interval} on ${days}`; + } + + if (frequency === 'monthly' && this.recurrence.monthday) { + return `${interval} on day ${this.recurrence.monthday}`; + } + + return interval; + } + + get spawnedCount() { + return this.series?.generated_orders_count ?? this.series?.spawned_count ?? this.series?.meta?.spawned_count ?? this.series?.occurrences_count ?? 0; + } + + @action openOrder(event) { + event?.preventDefault?.(); + this.args.column?.onLinkClick?.(this.order); + } + + @action openSeries(event) { + event?.preventDefault?.(); + event?.stopPropagation?.(); + + if (this.series) { + return this.recurringOrderScheduleActions.transition.view(this.series); + } + } + + @action skipNext(event) { + event?.preventDefault?.(); + event?.stopPropagation?.(); + + if (this.series) { + return this.recurringOrderScheduleActions.skipNextOccurrence(this.series); + } + } + + @action pauseSeries(event) { + event?.preventDefault?.(); + event?.stopPropagation?.(); + + if (this.series) { + return this.recurringOrderScheduleActions.pause(this.series); + } + } +} diff --git a/addon/components/cell/recurring-series-badge.hbs b/addon/components/cell/recurring-series-badge.hbs new file mode 100644 index 000000000..953eb5023 --- /dev/null +++ b/addon/components/cell/recurring-series-badge.hbs @@ -0,0 +1,13 @@ +{{#if this.label}} + +{{else}} + -- +{{/if}} diff --git a/addon/components/cell/recurring-series-badge.js b/addon/components/cell/recurring-series-badge.js new file mode 100644 index 000000000..53b1d38c8 --- /dev/null +++ b/addon/components/cell/recurring-series-badge.js @@ -0,0 +1,28 @@ +import Component from '@glimmer/component'; +import { inject as service } from '@ember/service'; +import { action } from '@ember/object'; + +export default class CellRecurringSeriesBadgeComponent extends Component { + @service recurringOrderScheduleActions; + + get order() { + return this.args.row; + } + + get series() { + return this.order?.recurring_order_schedule; + } + + get label() { + return this.series?.name ?? this.series?.public_id ?? null; + } + + @action openSeries(event) { + event?.preventDefault?.(); + event?.stopPropagation?.(); + + if (this.series) { + return this.recurringOrderScheduleActions.transition.view(this.series); + } + } +} diff --git a/addon/components/cell/recurring-series-name.hbs b/addon/components/cell/recurring-series-name.hbs new file mode 100644 index 000000000..215d5f303 --- /dev/null +++ b/addon/components/cell/recurring-series-name.hbs @@ -0,0 +1,11 @@ +
+ + {{n-a @row.name "Untitled series"}} + +
+ {{n-a @row.public_id}} + {{#if @row.meta.spawned_count}} + - {{@row.meta.spawned_count}} spawned + {{/if}} +
+
diff --git a/addon/components/cell/recurring-series-name.js b/addon/components/cell/recurring-series-name.js new file mode 100644 index 000000000..96d6abd06 --- /dev/null +++ b/addon/components/cell/recurring-series-name.js @@ -0,0 +1,3 @@ +import Component from '@glimmer/component'; + +export default class CellRecurringSeriesNameComponent extends Component {} diff --git a/addon/components/cell/recurring-series-next-occurrence.hbs b/addon/components/cell/recurring-series-next-occurrence.hbs new file mode 100644 index 000000000..393a10741 --- /dev/null +++ b/addon/components/cell/recurring-series-next-occurrence.hbs @@ -0,0 +1,12 @@ +
+ + {{#if @value}} + {{format-date-fns @value "d MMM yyyy HH:mm"}} + {{else}} + -- + {{/if}} + + {{#if @row.timezone}} + {{@row.timezone}} + {{/if}} +
diff --git a/addon/components/cell/recurring-series-next-occurrence.js b/addon/components/cell/recurring-series-next-occurrence.js new file mode 100644 index 000000000..89da3a749 --- /dev/null +++ b/addon/components/cell/recurring-series-next-occurrence.js @@ -0,0 +1,3 @@ +import Component from '@glimmer/component'; + +export default class CellRecurringSeriesNextOccurrenceComponent extends Component {} diff --git a/addon/components/cell/recurring-series-pattern.hbs b/addon/components/cell/recurring-series-pattern.hbs new file mode 100644 index 000000000..3699a7cc1 --- /dev/null +++ b/addon/components/cell/recurring-series-pattern.hbs @@ -0,0 +1,3 @@ +
+ {{n-a @value}} +
diff --git a/addon/components/cell/recurring-series-pattern.js b/addon/components/cell/recurring-series-pattern.js new file mode 100644 index 000000000..843e77e6c --- /dev/null +++ b/addon/components/cell/recurring-series-pattern.js @@ -0,0 +1,3 @@ +import Component from '@glimmer/component'; + +export default class CellRecurringSeriesPatternComponent extends Component {} diff --git a/addon/components/modals/recurring-order-schedule-form.hbs b/addon/components/modals/recurring-order-schedule-form.hbs new file mode 100644 index 000000000..febdf3fac --- /dev/null +++ b/addon/components/modals/recurring-order-schedule-form.hbs @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/addon/components/modals/recurring-order-schedules-manager.hbs b/addon/components/modals/recurring-order-schedules-manager.hbs new file mode 100644 index 000000000..3b2f5f24d --- /dev/null +++ b/addon/components/modals/recurring-order-schedules-manager.hbs @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/addon/components/order/details/detail.hbs b/addon/components/order/details/detail.hbs index 0312d0dc2..def12ee0d 100644 --- a/addon/components/order/details/detail.hbs +++ b/addon/components/order/details/detail.hbs @@ -7,6 +7,9 @@ {{#if @resource.adhoc}} {{t "order.fields.ad-hoc"}} {{/if}} + {{#if @resource.meta.is_recurring_generated}} + Recurring + {{/if}} <:default> @@ -111,6 +114,12 @@
{{t "common.type"}}
{{n-a (humanize @resource.type)}}
+ {{#if @resource.meta.is_recurring_generated}} +
+
Recurring Schedule
+
{{n-a @resource.meta.recurring_order_schedule_public_id @resource.meta.recurring_order_schedule_uuid}}
+
+ {{/if}} {{#if @resource.pod_required}}
{{t "order.fields.proof-of-delivery"}}
diff --git a/addon/components/order/form.hbs b/addon/components/order/form.hbs index 04f404e8e..fe736903d 100644 --- a/addon/components/order/form.hbs +++ b/addon/components/order/form.hbs @@ -3,6 +3,8 @@ {{yield (hash Details=(component "order/form/details" resource=@resource) + Schedule=(component "order/form/schedule" resource=@resource repeatEnabled=@repeatEnabled seriesDraft=@seriesDraft onRepeatChange=@onRepeatChange) + OrchestratorConstraints=(component "order/form/orchestrator-constraints" resource=@resource) CustomFields=(component "order/form/custom-fields" resource=@resource customFields=@customFields) Route=(component "order/form/route" resource=@resource) RegistryYield=(component "registry-yield" registry="fleet-ops:component:order:form" order=@resource) @@ -14,7 +16,14 @@ ) }} {{else}} + {{#if @scheduleFirst}} + + {{/if}} + {{#unless @scheduleFirst}} + + {{/unless}} + @@ -25,4 +34,4 @@ {{/if}} -
\ No newline at end of file + diff --git a/addon/components/order/form/details.hbs b/addon/components/order/form/details.hbs index 652a13d5c..095ce8338 100644 --- a/addon/components/order/form/details.hbs +++ b/addon/components/order/form/details.hbs @@ -21,18 +21,6 @@ - - - - - -{{! ORCHESTRATOR CONSTRAINTS }} - -
-
- Delivery Window -
- - - - - - -
- Requirements & Priority -
-
- - - {{option.label}} - - -
- - - -
-
- - \ No newline at end of file + diff --git a/addon/components/order/form/details.js b/addon/components/order/form/details.js index 86a99a450..e115b2a4d 100644 --- a/addon/components/order/form/details.js +++ b/addon/components/order/form/details.js @@ -70,63 +70,6 @@ export default class OrderFormDetailsComponent extends Component { this.leafletLayerVisibilityManager.showModelLayer(this.args.resource.driver_assigned); } - /** - * Resolves the reference date to use when pre-populating the date portion - * of a time window field. Prefers scheduled_at, falls back to created_at, - * and finally falls back to now so there is always a valid date. - * - * @returns {Date} - */ - get _timeWindowReferenceDate() { - const raw = this.args.resource.scheduled_at ?? this.args.resource.created_at ?? new Date(); - return raw instanceof Date ? raw : new Date(raw); - } - - /** - * Called by DateTimeInput @onUpdate for time_window_start and time_window_end. - * - * When the user picks a time, we preserve their chosen time but replace the - * date portion with the order's scheduled_at date (or created_at if - * scheduled_at is not set). This means the user only ever needs to set the - * time — the date is always contextually correct. - * - * If the incoming value already carries a different date (e.g. the user - * explicitly changed it via the date part of the picker) we respect that - * and do not override it. - * - * @param {'time_window_start'|'time_window_end'} field - * @param {Date|string|null} value Value emitted by DateTimeInput - */ - @action setTimeWindow(field, value) { - if (!value) { - this.args.resource[field] = null; - return; - } - - const picked = value instanceof Date ? value : new Date(value); - if (isNaN(picked.getTime())) { - this.args.resource[field] = value; - return; - } - - const ref = this._timeWindowReferenceDate; - - // Only inject the reference date when the picked value has no meaningful - // date of its own — i.e. when the date portion is the Unix epoch - // (1970-01-01), which is what DateTimeInput emits when the user has only - // touched the time picker and not the date picker. - const isEpochDate = picked.getUTCFullYear() === 1970 && picked.getUTCMonth() === 0 && picked.getUTCDate() === 1; - - if (isEpochDate) { - const merged = new Date(ref); - merged.setHours(picked.getHours(), picked.getMinutes(), picked.getSeconds(), 0); - this.args.resource[field] = merged; - } else { - // User explicitly set a date — honour it as-is. - this.args.resource[field] = picked; - } - } - @action toggleAdhoc(toggled) { this.args.resource.adhoc = toggled; this.args.resource.adhoc_distance = this.currentUser.getCompanyOption('fleetops.adhoc_distance', 5000); diff --git a/addon/components/order/form/orchestrator-constraints.hbs b/addon/components/order/form/orchestrator-constraints.hbs new file mode 100644 index 000000000..74c87fb7e --- /dev/null +++ b/addon/components/order/form/orchestrator-constraints.hbs @@ -0,0 +1,40 @@ + +
+
+ Delivery Window +
+ + + + + + +
+ Requirements & Priority +
+
+ + + {{option.label}} + + +
+ + + +
+
diff --git a/addon/components/order/form/orchestrator-constraints.js b/addon/components/order/form/orchestrator-constraints.js new file mode 100644 index 000000000..95da97b87 --- /dev/null +++ b/addon/components/order/form/orchestrator-constraints.js @@ -0,0 +1,33 @@ +import Component from '@glimmer/component'; +import { action } from '@ember/object'; + +export default class OrderFormOrchestratorConstraintsComponent extends Component { + get _timeWindowReferenceDate() { + const raw = this.args.resource.scheduled_at ?? this.args.resource.created_at ?? new Date(); + return raw instanceof Date ? raw : new Date(raw); + } + + @action setTimeWindow(field, value) { + if (!value) { + this.args.resource[field] = null; + return; + } + + const picked = value instanceof Date ? value : new Date(value); + if (isNaN(picked.getTime())) { + this.args.resource[field] = value; + return; + } + + const ref = this._timeWindowReferenceDate; + const isEpochDate = picked.getUTCFullYear() === 1970 && picked.getUTCMonth() === 0 && picked.getUTCDate() === 1; + + if (isEpochDate) { + const merged = new Date(ref); + merged.setHours(picked.getHours(), picked.getMinutes(), picked.getSeconds(), 0); + this.args.resource[field] = merged; + } else { + this.args.resource[field] = picked; + } + } +} diff --git a/addon/components/order/form/schedule.hbs b/addon/components/order/form/schedule.hbs new file mode 100644 index 000000000..44c409dc7 --- /dev/null +++ b/addon/components/order/form/schedule.hbs @@ -0,0 +1,101 @@ + +
+ + + + + +
+
+
Repeat this order
+
Generate orders from this template.
+
+ +
+ + {{#if @repeatEnabled}} +
+
+ + +
+ +
+
+ +
+ + {{option.label}} + +
+
+ +
+ + +
+
+ + {{#if this.isWeekly}} +
+ +
+ {{#each this.weekdayOptions as |day|}} + + {{/each}} +
+
+ {{/if}} + + {{#if this.isMonthly}} +
+ + +
+ {{/if}} + +
+ + +
+ +
+
+ +
+
+ {{#if this.previewOccurrences.length}} +
+ {{#each this.previewOccurrences as |occurrence|}} +
{{format-date-fns occurrence.occurrence_at_local "MMM d, yyyy HH:mm"}}
+ {{/each}} +
+ {{else}} +
Preview appears after route and schedule are ready.
+ {{/if}} +
+
+
+ {{/if}} +
+
diff --git a/addon/components/order/form/schedule.js b/addon/components/order/form/schedule.js new file mode 100644 index 000000000..2a6360747 --- /dev/null +++ b/addon/components/order/form/schedule.js @@ -0,0 +1,154 @@ +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; +import { inject as service } from '@ember/service'; +import { action } from '@ember/object'; +import { task } from 'ember-concurrency'; +import { buildRrule, parseRrule, WEEKDAY_OPTIONS } from '../../../utils/recurring-rrule'; + +const FREQUENCY_OPTIONS = [ + { value: 'daily', label: 'Daily' }, + { value: 'weekly', label: 'Weekly' }, + { value: 'monthly', label: 'Monthly' }, + { value: 'yearly', label: 'Yearly' }, +]; + +export default class OrderFormScheduleComponent extends Component { + @service recurringOrderScheduleActions; + + @tracked frequency = 'weekly'; + @tracked interval = 1; + @tracked selectedWeekdays = ['MO']; + @tracked monthday = new Date().getDate(); + @tracked previewOccurrences = []; + + weekdayOptions = WEEKDAY_OPTIONS; + frequencyOptions = FREQUENCY_OPTIONS; + + constructor() { + super(...arguments); + this.syncFromDraft(); + + if (this.args.repeatEnabled) { + this.updatePreview.perform(); + } + } + + get seriesDraft() { + return this.args.seriesDraft; + } + + get isWeekly() { + return this.frequency === 'weekly'; + } + + get isMonthly() { + return this.frequency === 'monthly'; + } + + get currentRrule() { + return buildRrule({ + frequency: this.frequency, + interval: this.interval, + weekdays: this.selectedWeekdays, + monthday: this.monthday, + until: this.seriesDraft?.ends_at, + }); + } + + syncFromDraft() { + const parsedRule = parseRrule(this.seriesDraft?.rrule); + this.frequency = parsedRule.frequency; + this.interval = parsedRule.interval; + this.selectedWeekdays = parsedRule.weekdays.length > 0 ? parsedRule.weekdays : ['MO']; + this.monthday = parsedRule.monthday ?? new Date(this.args.resource?.scheduled_at ?? Date.now()).getDate(); + } + + commitRule() { + if (this.seriesDraft) { + this.seriesDraft.rrule = this.currentRrule; + } + } + + @task({ restartable: true }) *updatePreview() { + if (!this.seriesDraft || !this.args.repeatEnabled) { + this.previewOccurrences = []; + return; + } + + try { + this.seriesDraft.starts_at = this.args.resource?.scheduled_at ?? this.seriesDraft.starts_at ?? new Date(); + this.seriesDraft.draftOrder = this.args.resource; + const response = yield this.recurringOrderScheduleActions.preview(this.seriesDraft, 5, { rrule: this.currentRrule }); + this.previewOccurrences = response?.occurrences ?? []; + } catch { + this.previewOccurrences = []; + } + } + + @action updateScheduledAt(value) { + this.args.resource.scheduled_at = value; + + if (this.seriesDraft) { + this.seriesDraft.starts_at = value; + } + + this.updatePreview.perform(); + } + + @action toggleRepeat(enabled) { + this.args.onRepeatChange?.(enabled); + + if (enabled) { + this.commitRule(); + this.updatePreview.perform(); + } else { + this.previewOccurrences = []; + } + } + + @action updateFrequency(option) { + this.frequency = option.value; + this.commitRule(); + this.updatePreview.perform(); + } + + @action updateInterval({ target }) { + this.interval = Number(target.value) || 1; + this.commitRule(); + this.updatePreview.perform(); + } + + @action updateMonthday({ target }) { + this.monthday = Number(target.value) || 1; + this.commitRule(); + this.updatePreview.perform(); + } + + @action updateEndsAt(value) { + if (this.seriesDraft) { + this.seriesDraft.ends_at = value; + this.commitRule(); + } + + this.updatePreview.perform(); + } + + @action toggleWeekday(code) { + if (this.selectedWeekdays.includes(code)) { + this.selectedWeekdays = this.selectedWeekdays.filter((value) => value !== code); + } else { + this.selectedWeekdays = [...this.selectedWeekdays, code]; + } + + if (this.selectedWeekdays.length === 0) { + this.selectedWeekdays = ['MO']; + } + + this.commitRule(); + this.updatePreview.perform(); + } + + @action isWeekdaySelected(code) { + return this.selectedWeekdays.includes(code); + } +} diff --git a/addon/components/order/new-split-button.hbs b/addon/components/order/new-split-button.hbs new file mode 100644 index 000000000..483994113 --- /dev/null +++ b/addon/components/order/new-split-button.hbs @@ -0,0 +1,30 @@ + diff --git a/addon/components/order/new-split-button.js b/addon/components/order/new-split-button.js new file mode 100644 index 000000000..ccc88e143 --- /dev/null +++ b/addon/components/order/new-split-button.js @@ -0,0 +1,11 @@ +import Component from '@glimmer/component'; + +export default class OrderNewSplitButtonComponent extends Component { + get onNewOrder() { + return this.args.options?.onNewOrder; + } + + get onNewSeries() { + return this.args.options?.onNewSeries; + } +} diff --git a/addon/components/order/panel-header.hbs b/addon/components/order/panel-header.hbs index 21940ee15..44ae0d1df 100644 --- a/addon/components/order/panel-header.hbs +++ b/addon/components/order/panel-header.hbs @@ -10,6 +10,9 @@
{{@resource.tracking}}
+ {{#if @resource.meta.is_recurring_generated}} + Recurring + {{/if}} {{#if @resource.dispatched_at}} {{concat "Dispatched at " @resource.dispatchedAt}} {{/if}} diff --git a/addon/components/recurring-order-schedule/details.hbs b/addon/components/recurring-order-schedule/details.hbs new file mode 100644 index 000000000..347c5a755 --- /dev/null +++ b/addon/components/recurring-order-schedule/details.hbs @@ -0,0 +1,138 @@ +
+ +
+
+
ID
+
{{n-a @resource.public_id}}
+
+
+
Status
+
{{smart-humanize @resource.status}}
+
+
+
Timezone
+
{{n-a @resource.timezone}}
+
+
+
Next Occurrence
+
+ {{#if @resource.next_occurrence_at}} + {{format-date-fns @resource.next_occurrence_at "dd MMM yyyy, HH:mm"}} + {{else}} + -- + {{/if}} +
+
+
+
Customer
+
{{n-a @resource.customer_name}}
+
+
+
Order Type
+
{{n-a @resource.order_config_name}}
+
+
+
Service Rate
+
{{n-a @resource.service_rate_name "No default service rate"}}
+
+
+
Pattern
+
{{n-a @resource.rrule}}
+
+
+
+ +
+
+ + + +
+
+ + {{#if this.isUpcomingTab}} + + {{#if this.upcomingOccurrences.length}} +
+ {{#each this.upcomingOccurrences as |occurrence|}} +
+
+
{{n-a occurrence.occurrence_at_local occurrence.occurrence_at}}
+
Status: {{smart-humanize occurrence.status}}
+ {{#if occurrence.order}} +
Order: {{occurrence.order.public_id}}
+ {{/if}} +
+
+ {{#if occurrence.order}} +
+
+ {{/each}} +
+ {{else}} +
No upcoming occurrences found for this recurring series.
+ {{/if}} +
+ {{/if}} + + {{#if this.isHistoryTab}} + + {{#if this.historyOccurrences.length}} +
+ {{#each this.historyOccurrences as |occurrence|}} +
+
{{n-a occurrence.occurrence_at_local occurrence.occurrence_at}}
+
Status: {{smart-humanize occurrence.status}}
+
+ {{/each}} +
+ {{else}} +
No historical occurrences found for this recurring series.
+ {{/if}} +
+ {{/if}} + + {{#if this.isSettingsTab}} + +
+
+
Starts At
+
+ {{#if @resource.starts_at}} + {{format-date-fns @resource.starts_at "dd MMM yyyy, HH:mm"}} + {{else}} + -- + {{/if}} +
+
+
+
Ends At
+
+ {{#if @resource.ends_at}} + {{format-date-fns @resource.ends_at "dd MMM yyyy, HH:mm"}} + {{else}} + -- + {{/if}} +
+
+
+
Rule
+
{{n-a @resource.rrule}}
+
+
+
+ {{/if}} + + {{#if @resource.description}} + +
{{@resource.description}}
+
+ {{/if}} + + +
diff --git a/addon/components/recurring-order-schedule/details.js b/addon/components/recurring-order-schedule/details.js new file mode 100644 index 000000000..e1430a867 --- /dev/null +++ b/addon/components/recurring-order-schedule/details.js @@ -0,0 +1,52 @@ +import Component from '@glimmer/component'; +import { inject as service } from '@ember/service'; +import { action } from '@ember/object'; + +export default class RecurringOrderScheduleDetailsComponent extends Component { + @service recurringOrderScheduleActions; + @service hostRouter; + + get upcomingOccurrences() { + return this.args.resource?.upcoming_occurrences ?? this.args.resource?.meta?.upcoming_occurrences ?? []; + } + + get activeTab() { + return this.args.activeTab ?? 'upcoming'; + } + + get isUpcomingTab() { + return this.activeTab === 'upcoming'; + } + + get isHistoryTab() { + return this.activeTab === 'history'; + } + + get isSettingsTab() { + return this.activeTab === 'settings'; + } + + get historyOccurrences() { + return this.args.resource?.history_occurrences ?? this.args.resource?.meta?.history_occurrences ?? []; + } + + @action selectTab(tab) { + this.args.onSelectTab?.(tab); + } + + @action skipOccurrence(occurrence) { + return this.recurringOrderScheduleActions.skipOccurrence(this.args.resource, occurrence.occurrence_at).then(async () => { + if (typeof this.args.resource?.reload === 'function') { + await this.args.resource.reload(); + } + }); + } + + @action viewOrder(order) { + if (!order?.public_id) { + return; + } + + return this.hostRouter.transitionTo('console.fleet-ops.operations.orders.index.details', order.public_id); + } +} diff --git a/addon/components/recurring-order-schedule/form.hbs b/addon/components/recurring-order-schedule/form.hbs new file mode 100644 index 000000000..87231bbdf --- /dev/null +++ b/addon/components/recurring-order-schedule/form.hbs @@ -0,0 +1,136 @@ +
+ +
+ + + + + +