Skip to content

Commit 0d669c3

Browse files
committed
feat: rich Send Work Order confirmation modal with vendor and subject details
- Created modals/send-work-order.hbs — a component-based confirmation modal that displays: - Work order subject (or target_name fallback), public_id, status, and due date so the user can confirm they have the correct work order - Vendor card showing name, email, and phone so the user knows exactly who the email will be sent to - Amber warning when no vendor is assigned - Red error banner when the vendor has no email address on file, with the Send button disabled to prevent a failed send - Created modals/send-work-order.js — minimal Glimmer component class - Updated work-order-actions.js sendEmail action: - Replaced modalsManager.confirm() with modalsManager.show() using the new component-based modal - Resolves vendorName, vendorEmail, vendorPhone from workOrder.assignee with fallback to assignee_name for display-only contexts - Disables the accept button when vendorEmail is absent - Runs the POST request inside modal.startLoading()/modal.done() for proper loading state feedback - Updated work-orders/index/details.js: - Added sendEmail @action that delegates to workOrderActions.sendEmail - Added 'Send to Vendor' button (paper-plane icon) as the first actionButton in the details panel toolbar
1 parent 873dca6 commit 0d669c3

4 files changed

Lines changed: 123 additions & 15 deletions

File tree

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<Modal::Default @modalIsOpened={{@modalIsOpened}} @options={{@options}} @confirm={{@onConfirm}} @decline={{@onDecline}}>
2+
<div class="modal-body-container">
3+
<div class="text-sm text-gray-600 dark:text-gray-400 mb-4">
4+
Review the details below before sending. The work order will be emailed to the assigned vendor.
5+
</div>
6+
7+
{{! ── Work Order details ── }}
8+
<div class="rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden mb-4">
9+
<div class="bg-gray-50 dark:bg-gray-800 px-3 py-2 border-b border-gray-200 dark:border-gray-700">
10+
<span class="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">Work Order</span>
11+
</div>
12+
<div class="px-3 py-3 space-y-2">
13+
<div class="flex items-start justify-between gap-4">
14+
<span class="text-xs text-gray-500 dark:text-gray-400 shrink-0 w-20">Subject</span>
15+
<span class="text-xs font-medium text-gray-800 dark:text-gray-100 text-right">
16+
{{n-a (or @options.workOrder.subject @options.workOrder.target_name)}}
17+
</span>
18+
</div>
19+
<div class="flex items-start justify-between gap-4">
20+
<span class="text-xs text-gray-500 dark:text-gray-400 shrink-0 w-20">ID</span>
21+
<span class="text-xs font-mono text-gray-600 dark:text-gray-300 text-right">
22+
{{n-a @options.workOrder.public_id}}
23+
</span>
24+
</div>
25+
<div class="flex items-start justify-between gap-4">
26+
<span class="text-xs text-gray-500 dark:text-gray-400 shrink-0 w-20">Status</span>
27+
<span class="text-xs text-right">
28+
<Badge @status={{@options.workOrder.status}}>{{smart-humanize @options.workOrder.status}}</Badge>
29+
</span>
30+
</div>
31+
{{#if @options.workOrder.due_at}}
32+
<div class="flex items-start justify-between gap-4">
33+
<span class="text-xs text-gray-500 dark:text-gray-400 shrink-0 w-20">Due</span>
34+
<span class="text-xs text-gray-600 dark:text-gray-300 text-right">
35+
{{format-date-fns @options.workOrder.due_at}}
36+
</span>
37+
</div>
38+
{{/if}}
39+
</div>
40+
</div>
41+
42+
{{! ── Recipient (vendor) details ── }}
43+
<div class="rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden">
44+
<div class="bg-gray-50 dark:bg-gray-800 px-3 py-2 border-b border-gray-200 dark:border-gray-700">
45+
<span class="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">Recipient</span>
46+
</div>
47+
{{#if @options.vendorName}}
48+
<div class="px-3 py-3 space-y-2">
49+
<div class="flex items-center gap-3">
50+
<div class="flex-shrink-0 w-8 h-8 rounded-full bg-blue-100 dark:bg-blue-900 flex items-center justify-center">
51+
<FaIcon @icon="building" class="text-blue-500 dark:text-blue-300 text-xs" />
52+
</div>
53+
<div class="flex-1 min-w-0">
54+
<div class="text-sm font-medium text-gray-800 dark:text-gray-100 truncate">
55+
{{@options.vendorName}}
56+
</div>
57+
{{#if @options.vendorEmail}}
58+
<div class="text-xs text-gray-500 dark:text-gray-400 truncate flex items-center gap-1 mt-0.5">
59+
<FaIcon @icon="envelope" class="text-gray-400 text-[10px]" />
60+
{{@options.vendorEmail}}
61+
</div>
62+
{{/if}}
63+
{{#if @options.vendorPhone}}
64+
<div class="text-xs text-gray-500 dark:text-gray-400 truncate flex items-center gap-1 mt-0.5">
65+
<FaIcon @icon="phone" class="text-gray-400 text-[10px]" />
66+
{{@options.vendorPhone}}
67+
</div>
68+
{{/if}}
69+
</div>
70+
</div>
71+
</div>
72+
{{else}}
73+
<div class="px-3 py-3">
74+
<div class="flex items-center gap-2 text-amber-600 dark:text-amber-400">
75+
<FaIcon @icon="triangle-exclamation" class="text-sm" />
76+
<span class="text-xs">No vendor assigned to this work order.</span>
77+
</div>
78+
</div>
79+
{{/if}}
80+
</div>
81+
82+
{{#unless @options.vendorEmail}}
83+
<div class="mt-3 flex items-start gap-2 p-3 rounded-lg bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800">
84+
<FaIcon @icon="circle-exclamation" class="text-red-500 dark:text-red-400 text-sm mt-0.5 shrink-0" />
85+
<span class="text-xs text-red-700 dark:text-red-300">
86+
The assigned vendor does not have an email address on file. Please update the vendor record before sending.
87+
</span>
88+
</div>
89+
{{/unless}}
90+
</div>
91+
</Modal::Default>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import Component from '@glimmer/component';
2+
export default class ModalsSendWorkOrderComponent extends Component {}

‎addon/controllers/maintenance/work-orders/index/details.js‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,16 @@ export default class MaintenanceWorkOrdersIndexDetailsController extends Control
1919

2020
get actionButtons() {
2121
return [
22+
{ icon: 'paper-plane', fn: this.sendEmail, text: 'Send to Vendor', permission: 'fleet-ops update work-order' },
2223
{ icon: 'edit', fn: this.edit, permission: 'fleet-ops update work-order' },
2324
{ icon: 'trash', fn: this.delete, type: 'danger', permission: 'fleet-ops delete work-order' },
2425
];
2526
}
2627

28+
@action sendEmail() {
29+
return this.workOrderActions.sendEmail(this.model);
30+
}
31+
2732
@action edit() {
2833
return this.hostRouter.transitionTo('maintenance.work-orders.index.edit', this.model);
2934
}

‎addon/services/work-order-actions.js‎

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -97,28 +97,38 @@ export default class WorkOrderActionsService extends ResourceActionService {
9797
};
9898
}
9999

100-
@action async sendEmail(workOrder, options = {}) {
101-
const confirmed = await this.modalsManager.confirm({
100+
@action sendEmail(workOrder, options = {}) {
101+
const assignee = workOrder.assignee;
102+
const vendorName = assignee?.name ?? workOrder.assignee_name ?? null;
103+
const vendorEmail = assignee?.email ?? null;
104+
const vendorPhone = assignee?.phone ?? null;
105+
106+
this.modalsManager.show('modals/send-work-order', {
102107
title: 'Send Work Order to Vendor',
103-
body: `This will email work order #${workOrder.public_id} to the assigned vendor. Do you want to proceed?`,
104-
acceptButtonText: 'Send',
108+
acceptButtonText: 'Send Work Order',
105109
acceptButtonIcon: 'paper-plane',
110+
acceptButtonDisabled: !vendorEmail,
106111
declineButtonText: 'Cancel',
112+
workOrder,
113+
vendorName,
114+
vendorEmail,
115+
vendorPhone,
116+
confirm: async (modal) => {
117+
modal.startLoading();
118+
try {
119+
const response = await this.fetch.post(`work-orders/${workOrder.id}/send`);
120+
this.notifications.success(response?.message ?? `Work order sent to ${vendorName ?? 'vendor'} successfully.`);
121+
modal.done();
122+
} catch (error) {
123+
this.notifications.serverError(error);
124+
modal.stopLoading();
125+
}
126+
},
107127
...options,
108128
});
109-
110-
if (!confirmed) {
111-
return;
112-
}
113-
114-
try {
115-
const response = await this.fetch.post(`work-orders/${workOrder.id}/send`);
116-
this.notifications.success(response?.message ?? 'Work order sent successfully.');
117-
} catch (error) {
118-
this.notifications.serverError(error);
119-
}
120129
}
121130

131+
122132
modal = {
123133
create: (attributes = {}, options = {}, saveOptions = {}) => {
124134
const workOrder = this.createNewInstance(attributes);

0 commit comments

Comments
 (0)