Skip to content
Draft
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
3 changes: 3 additions & 0 deletions addon/components/modals/recurring-order-schedule-form.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<Modal::Default @modalIsOpened={{@modalIsOpened}} @options={{@options}} @confirm={{@onConfirm}} @decline={{@onDecline}}>
<RecurringOrderSchedule::Form @resource={{@options.resource}} @sourceOrder={{@options.sourceOrder}} />
</Modal::Default>
3 changes: 3 additions & 0 deletions addon/components/modals/recurring-order-schedules-manager.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<Modal::Default @modalIsOpened={{@modalIsOpened}} @options={{@options}} @confirm={{@onConfirm}} @decline={{@onDecline}}>
<RecurringOrderSchedule::Manager />
</Modal::Default>
9 changes: 9 additions & 0 deletions addon/components/order/details/detail.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
{{#if @resource.adhoc}}
<Badge @hideStatusDot={{false}} @disableHumanize={{true}} @status="success">{{t "order.fields.ad-hoc"}}</Badge>
{{/if}}
{{#if @resource.meta.is_recurring_generated}}
<Badge @hideStatusDot={{false}} @status="info">Recurring</Badge>
{{/if}}
</div>
</:title>
<:default>
Expand Down Expand Up @@ -111,6 +114,12 @@
<div class="field-name">{{t "common.type"}}</div>
<div class="field-value">{{n-a (humanize @resource.type)}}</div>
</div>
{{#if @resource.meta.is_recurring_generated}}
<div class="field-info-container field-vertical-container dashed-bottom">
<div class="field-name">Recurring Schedule</div>
<div class="field-value">{{n-a @resource.meta.recurring_order_schedule_public_id @resource.meta.recurring_order_schedule_uuid}}</div>
</div>
{{/if}}
{{#if @resource.pod_required}}
<div class="field-info-container field-vertical-container">
<div class="field-name">{{t "order.fields.proof-of-delivery"}}</div>
Expand Down
3 changes: 3 additions & 0 deletions addon/components/order/panel-header.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
<div class="font-semibold">{{@resource.tracking}}</div>
<div class="flex flex-row space-x-1">
<Badge @status={{@resource.status}} />
{{#if @resource.meta.is_recurring_generated}}
<Badge @status="info">Recurring</Badge>
{{/if}}
{{#if @resource.dispatched_at}}
<Badge @status="dispatched">{{concat "Dispatched at " @resource.dispatchedAt}}</Badge>
{{/if}}
Expand Down
82 changes: 82 additions & 0 deletions addon/components/recurring-order-schedule/details.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<div class="next-content-panel-wrapper" ...attributes>
<ContentPanel @title="Overview" @open={{true}} @wrapperClass="bordered-top">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-2 text-xs dark:text-gray-100">
<div class="field-info-container">
<div class="field-name">ID</div>
<div class="field-value">{{n-a @resource.public_id}}</div>
</div>
<div class="field-info-container">
<div class="field-name">Status</div>
<div class="field-value"><Badge @status={{@resource.status}}>{{smart-humanize @resource.status}}</Badge></div>
</div>
<div class="field-info-container">
<div class="field-name">Timezone</div>
<div class="field-value">{{n-a @resource.timezone}}</div>
</div>
<div class="field-info-container">
<div class="field-name">Next Occurrence</div>
<div class="field-value">{{n-a @resource.next_occurrence_at}}</div>
</div>
<div class="field-info-container">
<div class="field-name">Customer</div>
<div class="field-value">{{n-a @resource.customer.name}}</div>
</div>
<div class="field-info-container">
<div class="field-name">Order Type</div>
<div class="field-value">{{n-a @resource.order_config.name}}</div>
</div>
<div class="field-info-container">
<div class="field-name">Service Rate</div>
<div class="field-value">{{n-a @resource.service_rate.service_name "No default service rate"}}</div>
</div>
<div class="field-info-container">
<div class="field-name">Starts At</div>
<div class="field-value">{{n-a @resource.starts_at}}</div>
</div>
<div class="field-info-container">
<div class="field-name">Ends At</div>
<div class="field-value">{{n-a @resource.ends_at}}</div>
</div>
<div class="field-info-container">
<div class="field-name">Recurrence Rule</div>
<div class="field-value break-all">{{n-a @resource.rrule}}</div>
</div>
</div>
</ContentPanel>

<ContentPanel @title="Upcoming Orders" @open={{true}} @wrapperClass="bordered-top">
{{#if this.upcomingOccurrences.length}}
<div class="space-y-2">
{{#each this.upcomingOccurrences as |occurrence|}}
<div class="rounded-md border border-gray-200 dark:border-gray-800 px-3 py-2 flex items-center justify-between gap-3">
<div>
<div class="text-sm font-medium text-gray-800 dark:text-gray-100">{{n-a occurrence.occurrence_at_local occurrence.occurrence_at}}</div>
<div class="text-xs text-gray-500 dark:text-gray-400">Status: {{smart-humanize occurrence.status}}</div>
{{#if occurrence.order}}
<div class="text-xs text-gray-500 dark:text-gray-400">Order: {{occurrence.order.public_id}}</div>
{{/if}}
</div>
<div class="flex items-center gap-2">
{{#if occurrence.order}}
<Button @icon="arrow-up-right-from-square" @size="xs" @text="View Order" @onClick={{fn this.viewOrder occurrence.order}} />
{{/if}}
{{#if (eq occurrence.status "scheduled")}}
<Button @type="danger" @icon="ban" @size="xs" @text="Cancel Occurrence" @onClick={{fn this.skipOccurrence occurrence}} />
{{/if}}
</div>
</div>
{{/each}}
</div>
{{else}}
<div class="text-sm text-gray-400">No upcoming occurrences found for this recurring schedule.</div>
{{/if}}
</ContentPanel>

{{#if @resource.description}}
<ContentPanel @title="Description" @open={{false}} @wrapperClass="bordered-top">
<div class="text-sm whitespace-pre-wrap text-gray-700 dark:text-gray-300">{{@resource.description}}</div>
</ContentPanel>
{{/if}}

<Spacer @height="200px" />
</div>
28 changes: 28 additions & 0 deletions addon/components/recurring-order-schedule/details.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
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 ?? [];
}

@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);
}
}
136 changes: 136 additions & 0 deletions addon/components/recurring-order-schedule/form.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
<div class="recurring-order-schedule-form" ...attributes>
<ContentPanel @title="Schedule Details" @open={{true}} @wrapperClass="bordered-top">
<div class="grid grid-cols-1 gap-2 lg:grid-cols-2 lg:gap-2 no-input-group-padding text-xs">
<InputGroup @name="Schedule Name" @wrapperClass="col-span-2">
<Input @value={{@resource.name}} @type="text" class="w-full form-input" placeholder="e.g. ACME Tue/Thu Delivery" />
</InputGroup>

<InputGroup @name="Description" @wrapperClass="col-span-2">
<Textarea @value={{@resource.description}} class="w-full form-input" rows="2" placeholder="Optional internal notes for this recurring schedule..." />
</InputGroup>

<InputGroup @name="Status">
<div class="fleetbase-model-select fleetbase-power-select ember-model-select">
<PowerSelect @options={{this.statusOptions}} @selected={{@resource.status}} @onChange={{fn (mut @resource.status)}} @triggerClass="form-select form-input" as |status|>
{{smart-humanize status}}
</PowerSelect>
</div>
</InputGroup>

<InputGroup @name="Timezone">
<Input @value={{@resource.timezone}} @type="text" class="w-full form-input" placeholder="Asia/Singapore" />
</InputGroup>

<InputGroup @name="Starts At">
<DateTimeInput class="form-input" @value={{@resource.starts_at}} @onUpdate={{this.updateStartsAt}} />
</InputGroup>

<InputGroup @name="Ends At">
<DateTimeInput class="form-input" @value={{@resource.ends_at}} @onUpdate={{this.updateEndsAt}} />
</InputGroup>

<InputGroup @name="Frequency">
<div class="fleetbase-model-select fleetbase-power-select ember-model-select">
<PowerSelect
@options={{this.frequencyOptions}}
@selected={{find-by "value" this.frequency this.frequencyOptions}}
@onChange={{this.updateFrequency}}
@triggerClass="form-select form-input"
as |option|
>
{{option.label}}
</PowerSelect>
</div>
</InputGroup>

<InputGroup @name="Repeat Every">
<Input @value={{this.interval}} @type="number" class="w-full form-input" min="1" {{on "input" this.updateInterval}} />
</InputGroup>

{{#if this.isWeekly}}
<div class="col-span-2">
<div class="text-[11px] uppercase tracking-wide text-gray-500 font-semibold mb-2">Days of Week</div>
<div class="flex flex-wrap gap-2">
{{#each this.weekdayOptions as |day|}}
<button
type="button"
class="px-3 py-1.5 rounded-md border text-xs transition-colors
{{if (this.isWeekdaySelected day.code) 'bg-blue-600 border-blue-600 text-white' 'border-gray-300 dark:border-gray-700 text-gray-600 dark:text-gray-300'}}"
{{on "click" (fn this.toggleWeekday day.code)}}
>
{{day.label}}
</button>
{{/each}}
</div>
</div>
{{/if}}

{{#if this.isMonthly}}
<InputGroup @name="Day of Month" @wrapperClass="col-span-2">
<Input @value={{this.monthday}} @type="number" class="w-full form-input" min="1" max="31" {{on "input" this.updateMonthday}} />
</InputGroup>
{{/if}}

<div class="col-span-2">
<div class="flex items-center justify-between mb-2">
<div class="text-[11px] uppercase tracking-wide text-gray-500 font-semibold">Upcoming Occurrences</div>
<Button @icon="refresh" @size="xs" @text="Refresh Preview" @onClick={{perform this.updatePreview}} @isLoading={{this.updatePreview.isRunning}} />
</div>
<div class="rounded-md border border-gray-200 dark:border-gray-800 px-3 py-2">
{{#if this.previewOccurrences.length}}
<div class="space-y-1">
{{#each this.previewOccurrences as |occurrence|}}
<div class="text-sm text-gray-700 dark:text-gray-200">
{{n-a occurrence.occurrence_at_local occurrence.occurrence_at}}
</div>
{{/each}}
</div>
{{else}}
<div class="text-sm text-gray-400">Set a start date and recurrence to preview upcoming orders.</div>
{{/if}}
</div>
</div>
</div>
</ContentPanel>

<Order::Form::Details @resource={{this.draftOrder}} />
<Order::Form::Route @resource={{this.draftOrder}} />
<Order::Form::Payload @resource={{this.draftOrder}} />

<ContentPanel @title="Service Rate" @open={{true}} @wrapperClass="bordered-top">
<div class="space-y-2">
<div class="flex items-center justify-between">
<div class="text-sm text-gray-500 dark:text-gray-400">
Select a default service rate to price each generated order at materialization time.
</div>
<Button
@icon="refresh"
@size="xs"
@text="Load Service Rates"
@onClick={{perform this.loadServiceRates}}
@disabled={{not this.canQueryServiceRates}}
@isLoading={{this.loadServiceRates.isRunning}}
/>
</div>
<div class="fleetbase-model-select fleetbase-power-select ember-model-select">
<PowerSelect
@options={{this.serviceRates}}
@selected={{this.selectedServiceRate}}
@onChange={{this.selectServiceRate}}
@placeholder="Select Service Rate"
@triggerClass="form-select form-input"
@disabled={{or (not this.canQueryServiceRates) this.loadServiceRates.isRunning}}
as |serviceRate|
>
<div class="text-sm">
<div class="font-semibold normalize-in-trigger">{{serviceRate.service_name}}</div>
<div class="hide-from-trigger">{{serviceRate.currency}}</div>
</div>
</PowerSelect>
</div>
</div>
</ContentPanel>

<Order::Form::Notes @resource={{this.draftOrder}} />
<Spacer @height="200px" />
</div>
Loading