@fleetbase/fleetops-engine 0.6.49 → 0.6.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +185 -67
- package/addon/components/contact/panel-header.hbs +11 -48
- package/addon/components/customer/create-order-form.hbs +2 -2
- package/addon/components/customer/details.hbs +1 -2
- package/addon/components/customer/form.hbs +62 -50
- package/addon/components/customer/form.js +27 -1
- package/addon/components/customer/order-form.hbs +2 -2
- package/addon/components/customer/orders.hbs +50 -2
- package/addon/components/customer/orders.js +67 -5
- package/addon/components/display-place.hbs +2 -2
- package/addon/components/driver/form.hbs +1 -1
- package/addon/components/home/getting-started-guidance.hbs +113 -0
- package/addon/components/home/getting-started-guidance.js +107 -0
- package/addon/components/issue/details/comments.hbs +3 -0
- package/addon/components/issue/details/comments.js +3 -0
- package/addon/components/issue/details/correspondence.hbs +9 -0
- package/addon/components/issue/details/correspondence.js +83 -0
- package/addon/components/issue/details/documents.hbs +69 -0
- package/addon/components/issue/details/documents.js +85 -0
- package/addon/components/issue/details.hbs +220 -59
- package/addon/components/issue/details.js +245 -0
- package/addon/components/issue/form.hbs +19 -1
- package/addon/components/issue/panel-header.hbs +43 -0
- package/addon/components/issue/panel-header.js +22 -0
- package/addon/components/issue/timeline.hbs +40 -0
- package/addon/components/issue/timeline.js +45 -0
- package/addon/components/leaflet-tracking-marker.js +3 -0
- package/addon/components/map/drawer/position-listing.js +3 -0
- package/addon/components/map/google-live-map.js +6 -2
- package/addon/components/map/leaflet-live-map.hbs +48 -14
- package/addon/components/map/leaflet-live-map.js +201 -20
- package/addon/components/map/order-list-overlay.hbs +4 -4
- package/addon/components/modals/convert-customer-to-vendor.hbs +30 -0
- package/addon/components/modals/convert-customer-to-vendor.js +55 -0
- package/addon/components/modals/issue-assign.hbs +20 -0
- package/addon/components/modals/issue-assign.js +49 -0
- package/addon/components/modals/issue-close.hbs +30 -0
- package/addon/components/modals/issue-close.js +73 -0
- package/addon/components/modals/issue-status.hbs +20 -0
- package/addon/components/modals/issue-status.js +43 -0
- package/addon/components/modals/order-change-destination.hbs +46 -0
- package/addon/components/modals/order-change-destination.js +106 -0
- package/addon/components/modals/reset-customer-credentials.js +12 -6
- package/addon/components/modals/stop-activity.hbs +28 -0
- package/addon/components/modals/stop-activity.js +52 -0
- package/addon/components/modals/update-order-activity.hbs +78 -2
- package/addon/components/modals/update-order-activity.js +197 -5
- package/addon/components/orchestrator/resource-panel.hbs +0 -10
- package/addon/components/orchestrator/resource-panel.js +2 -4
- package/addon/components/order/activity-list.hbs +2 -2
- package/addon/components/order/activity-timeline.hbs +31 -29
- package/addon/components/order/details/activity.hbs +3 -3
- package/addon/components/order/details/activity.js +13 -2
- package/addon/components/order/details/documents.js +2 -0
- package/addon/components/order/details/proof.hbs +40 -38
- package/addon/components/order/details/proof.js +31 -4
- package/addon/components/order/details/purchase-rate.hbs +2 -2
- package/addon/components/order/details/route.hbs +11 -3
- package/addon/components/order/details/route.js +91 -14
- package/addon/components/order/details/tracking.hbs +101 -72
- package/addon/components/order/details/tracking.js +149 -2
- package/addon/components/order/details.hbs +5 -5
- package/addon/components/order/details.js +19 -1
- package/addon/components/order/form/details.hbs +3 -3
- package/addon/components/order/form/documents.js +11 -4
- package/addon/components/order/form/route.hbs +108 -104
- package/addon/components/order/form/route.js +37 -1
- package/addon/components/order/form/service-rate.hbs +2 -2
- package/addon/components/order/header.hbs +1 -1
- package/addon/components/order-config-manager/activity-flow.hbs +4 -1
- package/addon/components/order-config-manager/activity-flow.js +178 -12
- package/addon/components/place/address.js +1 -1
- package/addon/components/route-list.hbs +17 -29
- package/addon/components/route-list.js +42 -10
- package/addon/components/service-rate/details.hbs +42 -1
- package/addon/components/service-rate/form.hbs +119 -1
- package/addon/components/service-rate/form.js +59 -0
- package/addon/components/vehicle/form.js +1 -1
- package/addon/components/vehicle/pill.hbs +1 -1
- package/addon/components/vendor/personnel-manager.hbs +166 -0
- package/addon/components/vendor/personnel-manager.js +143 -0
- package/addon/components/widget/live-fleet.hbs +1 -0
- package/addon/components/widget/live-fleet.js +2 -7
- package/addon/components/widget/revenue-trend.hbs +1 -1
- package/addon/components/widget/revenue-trend.js +75 -2
- package/addon/controllers/management/contacts/customers/details.js +31 -13
- package/addon/controllers/management/contacts/customers.js +16 -5
- package/addon/controllers/management/contacts/index/details.js +31 -13
- package/addon/controllers/management/contacts/index.js +7 -0
- package/addon/controllers/management/issues/index/details.js +52 -6
- package/addon/controllers/management/vehicles/index/new.js +1 -1
- package/addon/controllers/management/vendors/index/details.js +4 -0
- package/addon/controllers/operations/orders/index/details.js +15 -7
- package/addon/controllers/operations/orders/index.js +15 -1
- package/addon/extension.js +19 -12
- package/addon/helpers/format-duration.js +1 -38
- package/addon/helpers/place-address-html.js +2 -2
- package/addon/initializers/load-leaflet-assets.js +5 -6
- package/addon/initializers/patch-ember-leaflet-tooltip-layer.js +51 -0
- package/addon/routes/management/issues/index/details.js +1 -1
- package/addon/routes/operations/service-rates/index.js +1 -1
- package/addon/routes.js +1 -0
- package/addon/services/contact-actions.js +231 -1
- package/addon/services/geofence.js +163 -19
- package/addon/services/getting-started.js +44 -0
- package/addon/services/issue-actions.js +71 -0
- package/addon/services/leaflet-contextmenu-manager.js +27 -0
- package/addon/services/leaflet-map-manager.js +2 -0
- package/addon/services/map-adapter/leaflet.js +19 -0
- package/addon/services/service-areas.js +3 -0
- package/addon/services/vehicle-actions.js +1 -1
- package/addon/styles/fleetops-engine.css +1127 -1
- package/addon/templates/management/contacts/customers/details.hbs +1 -1
- package/addon/templates/management/contacts/index/details.hbs +1 -1
- package/addon/templates/management/contacts.hbs +2 -2
- package/addon/templates/management/issues/index/details/index.hbs +3 -1
- package/addon/templates/management/issues/index/details.hbs +3 -2
- package/addon/templates/management/vendors/index/details/personnel.hbs +1 -2
- package/addon/templates/operations/orders/index/details/index.hbs +2 -2
- package/addon/templates/operations/orders/index.hbs +1 -1
- package/addon/templates/operations/service-rates/index/edit.hbs +1 -0
- package/addon/templates/operations/service-rates/index/new.hbs +1 -0
- package/addon/templates/settings/payments/onboard.hbs +8 -8
- package/addon/utils/fleet-ops-options.js +2 -1
- package/addon/utils/leaflet-draw-namespace-guard.js +22 -0
- package/addon/utils/leaflet-plugin-loader.js +219 -0
- package/addon/utils/normalize-order-config-flow.js +41 -0
- package/addon/utils/place-address-html.js +3 -2
- package/addon/utils/route-visualization.js +54 -7
- package/addon/utils/setup-customer-portal.js +4 -64
- package/app/components/home/getting-started-guidance.js +1 -0
- package/app/components/issue/details/comments.js +1 -0
- package/app/components/issue/details/correspondence.js +1 -0
- package/app/components/issue/details/documents.js +1 -0
- package/app/components/issue/panel-header.js +1 -0
- package/app/components/modals/convert-customer-to-vendor.js +1 -0
- package/app/components/modals/issue-assign.js +1 -0
- package/app/components/modals/issue-close.js +1 -0
- package/app/components/modals/issue-status.js +1 -0
- package/app/components/modals/order-change-destination.js +1 -0
- package/app/components/modals/stop-activity.js +1 -0
- package/app/components/vendor/personnel-manager.js +1 -0
- package/app/helpers/format-duration.js +1 -1
- package/app/services/getting-started.js +1 -0
- package/app/utils/leaflet-draw-namespace-guard.js +1 -0
- package/app/utils/leaflet-plugin-loader.js +1 -0
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +5 -5
- package/server/migrations/2026_05_21_000001_add_geographic_rules_to_service_rate_fees_table.php +37 -0
- package/server/migrations/2026_05_25_000001_add_endpoint_tracking_refs_to_payloads_table.php +50 -0
- package/server/migrations/2026_05_27_000001_default_driver_vehicle_statuses_to_available.php +48 -0
- package/server/migrations/2026_05_31_000001_add_portal_fields_to_vendor_personnels_table.php +30 -0
- package/server/migrations/2026_06_03_000000_ensure_tags_column_on_issues_table.php +31 -0
- package/server/migrations/2026_06_03_000001_add_order_uuid_to_issues_table.php +25 -0
- package/server/migrations/2026_06_04_000001_backfill_order_transaction_subjects.php +39 -0
- package/server/migrations/2026_06_04_000002_backfill_order_transaction_directions.php +24 -0
- package/server/resources/views/labels/default.php +57 -49
- package/server/resources/views/labels/waypoint-label.php +27 -20
- package/server/resources/views/mail/customer-credentials.blade.php +16 -13
- package/server/seeders/Testing/ConnectivitySeeder.php +1 -0
- package/server/seeders/Testing/FleetSeeder.php +22 -22
- package/server/seeders/Testing/OrdersSeeder.php +6 -22
- package/server/seeders/Testing/VroomOrchestrationSeeder.php +21 -21
- package/server/src/Console/Commands/AuditCustomerUserConflicts.php +75 -0
- package/server/src/Exceptions/CustomerUserConflictException.php +13 -0
- package/server/src/Expansions/UserFilterExpansion.php +28 -0
- package/server/src/Http/Controllers/Api/v1/ContactController.php +17 -2
- package/server/src/Http/Controllers/Api/v1/CustomerController.php +876 -0
- package/server/src/Http/Controllers/Api/v1/DriverController.php +1 -1
- package/server/src/Http/Controllers/Api/v1/IssueController.php +2 -0
- package/server/src/Http/Controllers/Api/v1/OrderConfigController.php +54 -0
- package/server/src/Http/Controllers/Api/v1/OrderController.php +109 -80
- package/server/src/Http/Controllers/Api/v1/PlaceController.php +10 -58
- package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +2 -2
- package/server/src/Http/Controllers/Api/v1/VehicleController.php +20 -3
- package/server/src/Http/Controllers/Internal/v1/ContactController.php +154 -0
- package/server/src/Http/Controllers/Internal/v1/CustomerController.php +116 -6
- package/server/src/Http/Controllers/Internal/v1/GettingStartedController.php +17 -0
- package/server/src/Http/Controllers/Internal/v1/IssueController.php +251 -0
- package/server/src/Http/Controllers/Internal/v1/LiveController.php +101 -66
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +530 -52
- package/server/src/Http/Controllers/Internal/v1/PlaceController.php +10 -68
- package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +1 -1
- package/server/src/Http/Controllers/Internal/v1/SettingController.php +2 -2
- package/server/src/Http/Controllers/Internal/v1/VehicleController.php +54 -1
- package/server/src/Http/Controllers/Internal/v1/VendorController.php +89 -0
- package/server/src/Http/Middleware/AuthenticateCustomerToken.php +34 -0
- package/server/src/Http/Requests/CreateCustomerOrderRequest.php +63 -0
- package/server/src/Http/Requests/CreateCustomerRequest.php +48 -0
- package/server/src/Http/Requests/CreateDriverRequest.php +1 -1
- package/server/src/Http/Requests/CreateIssueRequest.php +4 -0
- package/server/src/Http/Requests/CreateVehicleRequest.php +25 -1
- package/server/src/Http/Requests/Internal/CreateDriverRequest.php +1 -1
- package/server/src/Http/Requests/UpdateIssueRequest.php +4 -0
- package/server/src/Http/Requests/VerifyCreateCustomerRequest.php +29 -0
- package/server/src/Http/Resources/Internal/v1/OrderConfig.php +41 -0
- package/server/src/Http/Resources/v1/Customer.php +79 -0
- package/server/src/Http/Resources/v1/Index/Order.php +12 -9
- package/server/src/Http/Resources/v1/Issue.php +25 -0
- package/server/src/Http/Resources/v1/Order.php +20 -18
- package/server/src/Http/Resources/v1/OrderConfig.php +74 -0
- package/server/src/Http/Resources/v1/Payload.php +23 -20
- package/server/src/Http/Resources/v1/ServiceArea.php +33 -19
- package/server/src/Http/Resources/v1/ServiceRateFee.php +7 -0
- package/server/src/Listeners/HandleOrderDispatched.php +2 -2
- package/server/src/Mail/CustomerCredentialsMail.php +5 -6
- package/server/src/Models/Contact.php +122 -66
- package/server/src/Models/Customer.php +64 -0
- package/server/src/Models/Driver.php +12 -3
- package/server/src/Models/Entity.php +2 -2
- package/server/src/Models/Issue.php +47 -6
- package/server/src/Models/Order.php +62 -10
- package/server/src/Models/Payload.php +17 -1
- package/server/src/Models/ServiceArea.php +1 -1
- package/server/src/Models/ServiceRate.php +273 -1
- package/server/src/Models/ServiceRateFee.php +39 -9
- package/server/src/Models/TrackingNumber.php +8 -2
- package/server/src/Models/Vehicle.php +18 -2
- package/server/src/Models/VendorPersonnel.php +10 -1
- package/server/src/Models/Waypoint.php +2 -2
- package/server/src/Models/Zone.php +1 -1
- package/server/src/Notifications/LateDeparture.php +2 -2
- package/server/src/Notifications/ProlongedStoppage.php +2 -2
- package/server/src/Notifications/RouteDeviation.php +2 -2
- package/server/src/Observers/ContactObserver.php +2 -0
- package/server/src/Observers/PurchaseRateObserver.php +20 -0
- package/server/src/Observers/ServiceRateObserver.php +6 -6
- package/server/src/Observers/ZoneObserver.php +56 -0
- package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +1 -1
- package/server/src/Providers/FleetOpsServiceProvider.php +2 -0
- package/server/src/Support/Analytics/AbstractAnalytics.php +6 -6
- package/server/src/Support/Analytics/FuelEfficiency.php +2 -2
- package/server/src/Support/Analytics/OrdersByStatus.php +1 -1
- package/server/src/Support/Analytics/RevenueTrend.php +0 -1
- package/server/src/Support/Analytics/TopDrivers.php +10 -2
- package/server/src/Support/CustomerAuth.php +98 -0
- package/server/src/Support/GettingStarted.php +144 -0
- package/server/src/Support/LabelPdf.php +52 -0
- package/server/src/Support/Metrics/AbstractMetric.php +4 -4
- package/server/src/Support/PlaceSearch.php +282 -0
- package/server/src/Support/ResolvesOrderServiceStops.php +522 -0
- package/server/src/Tracking/TrackingContextBuilder.php +41 -38
- package/server/src/Tracking/TrackingIntelligenceService.php +109 -9
- package/server/src/Tracking/TrackingStop.php +13 -11
- package/server/src/routes.php +76 -10
- package/server/tests/AnalyticsRoutesTest.php +8 -0
- package/server/tests/CapacityAllocationEngineTest.php +10 -10
- package/server/tests/CustomerContactIdentitySafetyTest.php +95 -0
- package/server/tests/CustomerEndpointTest.php +199 -0
- package/server/tests/DriverVehicleStatusDefaultTest.php +39 -0
- package/server/tests/GettingStartedStatusTest.php +21 -0
- package/server/tests/LabelPdfRenderingTest.php +23 -0
- package/server/tests/LiveControllerViewportTest.php +60 -0
- package/server/tests/MetricsRegistryTest.php +4 -8
- package/server/tests/OrchestrationPayloadBuilderTest.php +2 -2
- package/server/tests/OrderActivityFlowRegressionTest.php +116 -0
- package/server/tests/OrderConfigEndpointTest.php +86 -0
- package/server/tests/OrderFileAttachmentTest.php +20 -0
- package/server/tests/ServiceAreaZoneApiShapeTest.php +78 -0
- package/server/tests/VehicleDriverAssignmentTest.php +18 -0
- package/translations/ar-ae.yml +9 -0
- package/translations/bg-bg.yaml +9 -0
- package/translations/en-us.yaml +12 -0
- package/translations/es-pa.yaml +9 -0
- package/translations/fr-fr.yaml +9 -0
- package/translations/mn-mn.yaml +9 -0
- package/translations/pt-br.yaml +9 -0
- package/translations/ru-ru.yaml +9 -0
- package/translations/vi-vn.yaml +9 -0
- package/server/migrations/2026_05_13_000001_repair_customer_contact_user_types.php +0 -86
|
@@ -121,6 +121,7 @@
|
|
|
121
121
|
<Button @helpText="View Label" @icon="print" @onClick={{this.viewOrderLabel}} />
|
|
122
122
|
<Button @helpText="View Route" @icon="route" @onClick={{this.locateOrderRoute}} />
|
|
123
123
|
<Button @helpText="Locate Driver" @icon="truck" @onClick={{this.locateDriver}} @disabled={{not order.has_driver_assigned}} />
|
|
124
|
+
<Button @helpText="Cancel Order" @icon="ban" @onClick={{this.cancelOrder}} @disabled={{or order.isCompleted order.isCanceled}} />
|
|
124
125
|
</div>
|
|
125
126
|
</div>
|
|
126
127
|
</div>
|
|
@@ -161,6 +162,53 @@
|
|
|
161
162
|
</div>
|
|
162
163
|
</div>
|
|
163
164
|
<div class="customer-order-details-seperator"></div>
|
|
165
|
+
<div>
|
|
166
|
+
<div class="mb-2 ml-1">
|
|
167
|
+
<h1 class="font-bold text-sm">Delivery Options:</h1>
|
|
168
|
+
</div>
|
|
169
|
+
<div class="grid grid-cols-1 lg:grid-cols-2 gap-2">
|
|
170
|
+
<InputGroup @name="Scheduled At" @type="datetime-local" @value={{this.scheduledAt}} />
|
|
171
|
+
<div class="flex items-end">
|
|
172
|
+
<Button @type="primary" @icon="calendar" @text="Reschedule" @onClick={{this.rescheduleOrder}} @disabled={{or order.isCompleted order.isCanceled}} />
|
|
173
|
+
</div>
|
|
174
|
+
<InputGroup @name="Access Notes">
|
|
175
|
+
<Input
|
|
176
|
+
@value={{this.deliveryInstructions.access_notes}}
|
|
177
|
+
class="form-input w-full"
|
|
178
|
+
placeholder="Gate code, loading dock, reception notes"
|
|
179
|
+
{{on "input" (fn this.updateDeliveryInstruction "access_notes")}}
|
|
180
|
+
/>
|
|
181
|
+
</InputGroup>
|
|
182
|
+
<InputGroup @name="Safe Place">
|
|
183
|
+
<Input
|
|
184
|
+
@value={{this.deliveryInstructions.safe_place}}
|
|
185
|
+
class="form-input w-full"
|
|
186
|
+
placeholder="Leave-at-door or safe-place instructions"
|
|
187
|
+
{{on "input" (fn this.updateDeliveryInstruction "safe_place")}}
|
|
188
|
+
/>
|
|
189
|
+
</InputGroup>
|
|
190
|
+
<InputGroup @name="Contact Phone">
|
|
191
|
+
<Input
|
|
192
|
+
@value={{this.deliveryInstructions.contact_phone}}
|
|
193
|
+
class="form-input w-full"
|
|
194
|
+
placeholder="Delivery contact phone"
|
|
195
|
+
{{on "input" (fn this.updateDeliveryInstruction "contact_phone")}}
|
|
196
|
+
/>
|
|
197
|
+
</InputGroup>
|
|
198
|
+
<InputGroup @name="Special Handling">
|
|
199
|
+
<Input
|
|
200
|
+
@value={{this.deliveryInstructions.special_handling}}
|
|
201
|
+
class="form-input w-full"
|
|
202
|
+
placeholder="Fragile, temperature, access, or handling notes"
|
|
203
|
+
{{on "input" (fn this.updateDeliveryInstruction "special_handling")}}
|
|
204
|
+
/>
|
|
205
|
+
</InputGroup>
|
|
206
|
+
</div>
|
|
207
|
+
<div class="mt-2">
|
|
208
|
+
<Button @icon="save" @text="Save Delivery Instructions" @onClick={{this.saveDeliveryInstructions}} @disabled={{or order.isCompleted order.isCanceled}} />
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
<div class="customer-order-details-seperator"></div>
|
|
164
212
|
<div>
|
|
165
213
|
<div class="mb-2 ml-1">
|
|
166
214
|
<h1 class="font-bold text-sm">Items:</h1>
|
|
@@ -274,7 +322,7 @@
|
|
|
274
322
|
{{#each order.purchase_rate.service_quote.items as |item|}}
|
|
275
323
|
<tr>
|
|
276
324
|
<td>
|
|
277
|
-
<span class="font-semibold">{{
|
|
325
|
+
<span class="font-semibold">{{item.details}}</span>
|
|
278
326
|
</td>
|
|
279
327
|
<td>
|
|
280
328
|
<div class="flex flex-row items-center justify-end">{{format-currency
|
|
@@ -320,4 +368,4 @@
|
|
|
320
368
|
</div>
|
|
321
369
|
<div id="customer-portal-content-wormhole"></div>
|
|
322
370
|
{{/let}}
|
|
323
|
-
</div>
|
|
371
|
+
</div>
|
|
@@ -39,6 +39,8 @@ export default class CustomerOrdersComponent extends Component {
|
|
|
39
39
|
@tracked route;
|
|
40
40
|
@tracked query;
|
|
41
41
|
@tracked tileSourceUrl = 'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png';
|
|
42
|
+
@tracked scheduledAt;
|
|
43
|
+
@tracked deliveryInstructions = {};
|
|
42
44
|
|
|
43
45
|
get modalsManager() {
|
|
44
46
|
const owner = getOwner(this);
|
|
@@ -71,7 +73,7 @@ export default class CustomerOrdersComponent extends Component {
|
|
|
71
73
|
this.query = query;
|
|
72
74
|
|
|
73
75
|
try {
|
|
74
|
-
this.orders = yield this.store.query('order', { customer: this.
|
|
76
|
+
this.orders = yield this.store.query('order', { customer: this.customerAccountId, customer_type: this.customerTypeParam, ...params, query });
|
|
75
77
|
this.orders = this.orders.toArray().filter((_) => !_.isNew);
|
|
76
78
|
this.restoreSelectedOrder();
|
|
77
79
|
} catch (error) {
|
|
@@ -87,7 +89,7 @@ export default class CustomerOrdersComponent extends Component {
|
|
|
87
89
|
yield timeout(300);
|
|
88
90
|
|
|
89
91
|
try {
|
|
90
|
-
this.orders = yield this.store.query('order', { query, with_tracker_data: true });
|
|
92
|
+
this.orders = yield this.store.query('order', { query, customer: this.customerAccountId, customer_type: this.customerTypeParam, with_tracker_data: true });
|
|
91
93
|
} catch (error) {
|
|
92
94
|
this.notifications.serverError(error);
|
|
93
95
|
}
|
|
@@ -97,9 +99,9 @@ export default class CustomerOrdersComponent extends Component {
|
|
|
97
99
|
this.unselectOrder();
|
|
98
100
|
this.newOrder = this.store.createRecord('order', {
|
|
99
101
|
type: 'transport',
|
|
100
|
-
customer_uuid: this.
|
|
101
|
-
customer_type:
|
|
102
|
-
customer: this.customerSession.
|
|
102
|
+
customer_uuid: this.customerAccountId,
|
|
103
|
+
customer_type: this.customerTypeParam,
|
|
104
|
+
customer: this.customerSession.getAccount(),
|
|
103
105
|
meta: [],
|
|
104
106
|
});
|
|
105
107
|
this.urlSearchParams.addParamToCurrentUrl('creating', 1);
|
|
@@ -135,6 +137,8 @@ export default class CustomerOrdersComponent extends Component {
|
|
|
135
137
|
|
|
136
138
|
@action viewOrder(order, options = { resetOrderRoute: false }) {
|
|
137
139
|
this.selectedOrder = order;
|
|
140
|
+
this.scheduledAt = order.scheduled_at;
|
|
141
|
+
this.deliveryInstructions = order.meta?.customer_portal?.delivery_instructions ?? {};
|
|
138
142
|
// start loading order tracking activity
|
|
139
143
|
order.loadTrackingActivity();
|
|
140
144
|
this.urlSearchParams.addParamToCurrentUrl('order', order.public_id);
|
|
@@ -340,11 +344,69 @@ export default class CustomerOrdersComponent extends Component {
|
|
|
340
344
|
}
|
|
341
345
|
}
|
|
342
346
|
|
|
347
|
+
@action async cancelOrder() {
|
|
348
|
+
if (!this.selectedOrder) {
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
try {
|
|
353
|
+
const response = await this.fetch.post(`customer-portal/orders/${this.selectedOrder.public_id}/cancel`, {}, { namespace: 'fleet-ops/int/v1' });
|
|
354
|
+
this.selectedOrder.set('status', response.order?.status ?? 'canceled');
|
|
355
|
+
this.notifications.success('Order canceled.');
|
|
356
|
+
} catch (error) {
|
|
357
|
+
this.notifications.serverError(error);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
@action async rescheduleOrder() {
|
|
362
|
+
if (!this.selectedOrder || !this.scheduledAt) {
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
try {
|
|
367
|
+
const response = await this.fetch.post(
|
|
368
|
+
`customer-portal/orders/${this.selectedOrder.public_id}/reschedule`,
|
|
369
|
+
{ scheduled_at: this.scheduledAt },
|
|
370
|
+
{ namespace: 'fleet-ops/int/v1' }
|
|
371
|
+
);
|
|
372
|
+
this.selectedOrder.set('scheduled_at', response.order?.scheduled_at ?? this.scheduledAt);
|
|
373
|
+
this.notifications.success('Order rescheduled.');
|
|
374
|
+
} catch (error) {
|
|
375
|
+
this.notifications.serverError(error);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
@action updateDeliveryInstruction(key, event) {
|
|
380
|
+
this.deliveryInstructions = { ...this.deliveryInstructions, [key]: event.target.value };
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
@action async saveDeliveryInstructions() {
|
|
384
|
+
if (!this.selectedOrder) {
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
try {
|
|
389
|
+
await this.fetch.post(`customer-portal/orders/${this.selectedOrder.public_id}/delivery-instructions`, this.deliveryInstructions, { namespace: 'fleet-ops/int/v1' });
|
|
390
|
+
this.notifications.success('Delivery instructions saved.');
|
|
391
|
+
} catch (error) {
|
|
392
|
+
this.notifications.serverError(error);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
343
396
|
isCreatingOrder() {
|
|
344
397
|
const isCreating = this.urlSearchParams.get('creating');
|
|
345
398
|
return isCreating === '1' || isCreating === 1;
|
|
346
399
|
}
|
|
347
400
|
|
|
401
|
+
get customerAccountId() {
|
|
402
|
+
return this.customerSession.get('uuid') || this.customerSession.get('id');
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
get customerTypeParam() {
|
|
406
|
+
const customerType = this.customerSession.get('customer_type', 'contact');
|
|
407
|
+
return customerType === 'vendor' ? 'fleet-ops:vendor' : 'fleet-ops:contact';
|
|
408
|
+
}
|
|
409
|
+
|
|
348
410
|
cannotRouteWaypoints(waypoints = []) {
|
|
349
411
|
return !this.map || !isArray(waypoints) || waypoints.length < 2;
|
|
350
412
|
}
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
<div class="next-dd-menu-seperator"></div>
|
|
32
32
|
{{#each-in @waypointActions as |actionId action|}}
|
|
33
33
|
<div class="px-1">
|
|
34
|
-
<a href="javascript:;" class="next-dd-item" disabled={{cannot "fleet-ops view order"}} {{on "click" (dropdown-fn dd action.fn this.place)}}>
|
|
34
|
+
<a href="javascript:;" class="next-dd-item" disabled={{cannot "fleet-ops view order"}} {{on "click" (dropdown-fn dd action.fn (or @waypointActionContext this.place))}}>
|
|
35
35
|
{{action.label}}
|
|
36
36
|
</a>
|
|
37
37
|
</div>
|
|
@@ -65,4 +65,4 @@
|
|
|
65
65
|
{{/if}}
|
|
66
66
|
</address>
|
|
67
67
|
{{/if}}
|
|
68
|
-
</div>
|
|
68
|
+
</div>
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
@modelName="user"
|
|
7
7
|
@selectedModel={{@resource.user}}
|
|
8
8
|
@placeholder={{t "driver.fields.select-user"}}
|
|
9
|
-
@query={{hash doesnt_have_driver=true}}
|
|
9
|
+
@query={{hash doesnt_have_driver=true can_be_driver=true}}
|
|
10
10
|
@triggerClass="form-select form-input"
|
|
11
11
|
@infiniteScroll={{false}}
|
|
12
12
|
@renderInPlace={{true}}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
{{#unless this.isComplete}}
|
|
2
|
+
<section class="fleet-ops-home-guidance px-4 pt-4 lg:px-6 {{@class}}" ...attributes>
|
|
3
|
+
<div class="fleet-ops-home-guidance-grid">
|
|
4
|
+
<div class="fleet-ops-get-started rounded-md border border-gray-200 bg-white shadow-md dark:border-gray-700 dark:bg-gray-800">
|
|
5
|
+
<div class="flex items-center justify-between border-b border-gray-200 px-3 py-2 dark:border-gray-700">
|
|
6
|
+
<div class="min-w-0">
|
|
7
|
+
<div class="truncate text-sm font-bold text-gray-900 dark:text-gray-100">{{t "widget.get-started.title"}}</div>
|
|
8
|
+
<div class="truncate text-[11px] text-gray-500 dark:text-gray-400">{{t "widget.get-started.subtitle"}}</div>
|
|
9
|
+
</div>
|
|
10
|
+
<button type="button" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-200" {{on "click" this.refresh}} title={{t "widget.refresh"}}>
|
|
11
|
+
<FaIcon @icon="rotate" class={{if this.gettingStarted.load.isRunning "animate-spin"}} />
|
|
12
|
+
</button>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<div class="fleet-ops-get-started-content grid grid-cols-1 gap-3 p-3">
|
|
16
|
+
<div class="flex min-h-0 flex-col gap-2">
|
|
17
|
+
{{#if this.gettingStarted.load.isRunning}}
|
|
18
|
+
<div class="flex flex-1 items-center justify-center"><Spinner /></div>
|
|
19
|
+
{{else if this.gettingStarted.error}}
|
|
20
|
+
<div class="flex flex-1 items-center justify-center text-xs text-red-500">{{this.gettingStarted.error}}</div>
|
|
21
|
+
{{else}}
|
|
22
|
+
<div class="mb-1 flex items-center justify-between">
|
|
23
|
+
<span class="text-[11px] font-semibold text-gray-600 dark:text-gray-300">
|
|
24
|
+
{{this.progress.completed}}/{{this.progress.total}}
|
|
25
|
+
{{t "widget.get-started.completed"}}
|
|
26
|
+
</span>
|
|
27
|
+
<span class="text-[11px] font-semibold text-blue-600 dark:text-blue-300">{{this.progress.percent}}%</span>
|
|
28
|
+
</div>
|
|
29
|
+
<div class="h-1.5 overflow-hidden rounded-full bg-gray-100 dark:bg-gray-700">
|
|
30
|
+
<div class="h-full rounded-full bg-blue-600 transition-all" style={{html-safe (concat "width:" this.progress.percent "%")}}></div>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<div class="mt-1 flex min-h-0 flex-1 flex-col gap-1.5">
|
|
34
|
+
{{#each this.steps as |step index|}}
|
|
35
|
+
<button
|
|
36
|
+
type="button"
|
|
37
|
+
class="fleet-ops-get-started-step flex min-h-[58px] w-full items-center gap-2 rounded-md border p-2 text-left transition
|
|
38
|
+
{{if step.completed 'border-gray-200 bg-gray-50 dark:border-gray-700 dark:bg-gray-800/70'}}
|
|
39
|
+
{{if (this.isActiveStep step) 'border-blue-500 bg-blue-50 shadow-sm dark:border-blue-400 dark:bg-blue-950/30'}}
|
|
40
|
+
{{if (this.isInactiveStep step) 'border-gray-200 bg-white hover:border-gray-300 dark:border-gray-700 dark:bg-gray-800 dark:hover:border-gray-600'}}"
|
|
41
|
+
{{on "click" (fn this.startStep step)}}
|
|
42
|
+
>
|
|
43
|
+
<div
|
|
44
|
+
class="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full
|
|
45
|
+
{{if
|
|
46
|
+
step.completed
|
|
47
|
+
'bg-green-100 text-green-600 dark:bg-green-900/40 dark:text-green-300'
|
|
48
|
+
'bg-blue-100 text-blue-600 dark:bg-blue-900/40 dark:text-blue-300'
|
|
49
|
+
}}"
|
|
50
|
+
>
|
|
51
|
+
{{#if step.completed}}
|
|
52
|
+
<FaIcon @icon="check" />
|
|
53
|
+
{{else}}
|
|
54
|
+
<FaIcon @icon={{step.icon}} />
|
|
55
|
+
{{/if}}
|
|
56
|
+
</div>
|
|
57
|
+
<div class="min-w-0 flex-1">
|
|
58
|
+
<div class="flex items-center gap-1.5">
|
|
59
|
+
<span class="text-[12px] font-bold text-gray-900 dark:text-gray-100">{{add index 1}}. {{step.title}}</span>
|
|
60
|
+
<span class="hidden text-[10px] text-gray-400 sm:inline"><FaIcon @icon="clock" /> {{step.estimate}}</span>
|
|
61
|
+
</div>
|
|
62
|
+
<div class="truncate text-[11px] text-gray-500 dark:text-gray-400">{{step.description}}</div>
|
|
63
|
+
</div>
|
|
64
|
+
<span class="rounded-full px-2 py-0.5 text-[10px] font-bold {{this.statusClass step}}">{{this.statusText step}}</span>
|
|
65
|
+
</button>
|
|
66
|
+
{{/each}}
|
|
67
|
+
</div>
|
|
68
|
+
{{/if}}
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<div class="fleet-ops-recommended-features rounded-md border border-gray-200 bg-white shadow-md dark:border-gray-700 dark:bg-gray-800">
|
|
74
|
+
<div class="border-b border-gray-200 px-3 py-2 dark:border-gray-700">
|
|
75
|
+
<div class="truncate text-sm font-bold text-gray-900 dark:text-gray-100">{{t "widget.recommended-features.title"}}</div>
|
|
76
|
+
<div class="truncate text-[11px] text-gray-500 dark:text-gray-400">{{t "widget.recommended-features.subtitle"}}</div>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<div class="p-3">
|
|
80
|
+
{{#if this.gettingStarted.load.isRunning}}
|
|
81
|
+
<div class="fleet-ops-recommended-features-loading flex items-center justify-center"><Spinner /></div>
|
|
82
|
+
{{else if this.gettingStarted.error}}
|
|
83
|
+
<div class="fleet-ops-recommended-features-loading flex items-center justify-center text-xs text-red-500">{{this.gettingStarted.error}}</div>
|
|
84
|
+
{{else}}
|
|
85
|
+
<div class="grid gap-2 sm:grid-cols-2">
|
|
86
|
+
{{#each this.recommendations as |feature|}}
|
|
87
|
+
<button
|
|
88
|
+
type="button"
|
|
89
|
+
class="fleet-ops-recommended-feature
|
|
90
|
+
{{this.accentClass feature}}
|
|
91
|
+
group flex min-h-[92px] items-start gap-3 rounded-md border border-gray-200 bg-gray-50 p-3 text-left transition hover:border-gray-300 hover:bg-white hover:shadow-sm dark:border-gray-700 dark:bg-gray-800 dark:hover:border-gray-600 dark:hover:bg-gray-700"
|
|
92
|
+
{{on "click" (fn this.openFeature feature)}}
|
|
93
|
+
>
|
|
94
|
+
<div class="fleet-ops-recommended-feature-icon flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-md">
|
|
95
|
+
<FaIcon @icon={{feature.icon}} />
|
|
96
|
+
</div>
|
|
97
|
+
<div class="flex min-h-0 flex-1 flex-col">
|
|
98
|
+
<div class="truncate text-[12px] font-bold text-gray-900 dark:text-gray-100">{{feature.title}}</div>
|
|
99
|
+
<p class="mt-1 line-clamp-2 flex-1 text-[11px] leading-4 text-gray-500 dark:text-gray-400">{{feature.description}}</p>
|
|
100
|
+
<div class="mt-2 flex items-center gap-1 text-[11px] font-bold text-blue-600 dark:text-blue-300">
|
|
101
|
+
<span>{{t "widget.recommended-features.learn-more"}}</span>
|
|
102
|
+
<FaIcon @icon="arrow-right" class="transition group-hover:translate-x-0.5" />
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
</button>
|
|
106
|
+
{{/each}}
|
|
107
|
+
</div>
|
|
108
|
+
{{/if}}
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
</section>
|
|
113
|
+
{{/unless}}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import Component from '@glimmer/component';
|
|
2
|
+
import { action } from '@ember/object';
|
|
3
|
+
import { inject as service } from '@ember/service';
|
|
4
|
+
|
|
5
|
+
const ACCENT_CLASS = {
|
|
6
|
+
blue: 'feature-accent-blue',
|
|
7
|
+
green: 'feature-accent-green',
|
|
8
|
+
amber: 'feature-accent-amber',
|
|
9
|
+
purple: 'feature-accent-purple',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default class HomeGettingStartedGuidanceComponent extends Component {
|
|
13
|
+
@service gettingStarted;
|
|
14
|
+
@service router;
|
|
15
|
+
@service docsPanel;
|
|
16
|
+
|
|
17
|
+
constructor() {
|
|
18
|
+
super(...arguments);
|
|
19
|
+
this.gettingStarted.load.perform();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get steps() {
|
|
23
|
+
return this.gettingStarted.steps;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get recommendations() {
|
|
27
|
+
return this.gettingStarted.recommendations.slice(0, 4);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get progress() {
|
|
31
|
+
return this.gettingStarted.progress;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get nextStep() {
|
|
35
|
+
return this.gettingStarted.nextStep;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
get isComplete() {
|
|
39
|
+
return this.gettingStarted.isCompleted;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@action
|
|
43
|
+
isActiveStep(step) {
|
|
44
|
+
return this.nextStep?.key === step.key && !step.completed;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@action
|
|
48
|
+
isInactiveStep(step) {
|
|
49
|
+
return !step.completed && !this.isActiveStep(step);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@action
|
|
53
|
+
statusText(step) {
|
|
54
|
+
if (step.completed) {
|
|
55
|
+
return 'Done';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return this.isActiveStep(step) ? 'Next' : 'Not started';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@action
|
|
62
|
+
statusClass(step) {
|
|
63
|
+
if (step.completed) {
|
|
64
|
+
return 'bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-300';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (this.isActiveStep(step)) {
|
|
68
|
+
return 'bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return 'bg-gray-100 text-gray-500 dark:bg-gray-700 dark:text-gray-300';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@action
|
|
75
|
+
accentClass(feature) {
|
|
76
|
+
return ACCENT_CLASS[feature.accent] ?? ACCENT_CLASS.blue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@action
|
|
80
|
+
startStep(step) {
|
|
81
|
+
if (step?.route) {
|
|
82
|
+
return this.router.transitionTo(step.route);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (step?.docs_url) {
|
|
86
|
+
return this.openDocs(step.docs_url, step.title, 'fleet-ops-getting-started-guidance');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@action
|
|
91
|
+
openFeature(feature) {
|
|
92
|
+
return this.openDocs(feature.docs_url, feature.title, 'fleet-ops-recommended-features-guidance');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
openDocs(url, title, source) {
|
|
96
|
+
if (this.docsPanel?.open) {
|
|
97
|
+
return this.docsPanel.open(url, { title, source });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return window.open(url, '_docs');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@action
|
|
104
|
+
refresh() {
|
|
105
|
+
this.gettingStarted.load.perform();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<div class="issue-detail-comment-thread" {{did-insert this.loadComments}} {{did-update this.loadComments this.issueId}}>
|
|
2
|
+
<CommentThread
|
|
3
|
+
@subject={{@resource}}
|
|
4
|
+
@subjectType="Fleetbase\FleetOps\Models\Issue"
|
|
5
|
+
@comments={{this.comments}}
|
|
6
|
+
@onReloadComments={{this.reloadCommentsForIssue}}
|
|
7
|
+
@onPublishComment={{this.publishComment}}
|
|
8
|
+
/>
|
|
9
|
+
</div>
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import Component from '@glimmer/component';
|
|
2
|
+
import { tracked } from '@glimmer/tracking';
|
|
3
|
+
import { inject as service } from '@ember/service';
|
|
4
|
+
import { action } from '@ember/object';
|
|
5
|
+
|
|
6
|
+
const ISSUE_SUBJECT_TYPE = 'Fleetbase\\FleetOps\\Models\\Issue';
|
|
7
|
+
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
8
|
+
|
|
9
|
+
export default class IssueDetailsCorrespondenceComponent extends Component {
|
|
10
|
+
@service store;
|
|
11
|
+
@service notifications;
|
|
12
|
+
|
|
13
|
+
@tracked comments = [];
|
|
14
|
+
|
|
15
|
+
get resource() {
|
|
16
|
+
return this.args.resource;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
get issueId() {
|
|
20
|
+
return this.resource?.id || this.resource?.public_id || this.resource?.uuid;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get subjectUuid() {
|
|
24
|
+
if (this.resource?.uuid) {
|
|
25
|
+
return this.resource.uuid;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (UUID_PATTERN.test(this.resource?.id ?? '')) {
|
|
29
|
+
return this.resource.id;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get subjectPublicId() {
|
|
36
|
+
return this.resource?.public_id || this.resource?.id || this.resource?.uuid;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@action loadComments() {
|
|
40
|
+
this.reloadCommentsForIssue();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@action async reloadCommentsForIssue() {
|
|
44
|
+
const query = {
|
|
45
|
+
withoutParent: 1,
|
|
46
|
+
sort: '-created_at',
|
|
47
|
+
subject_type: ISSUE_SUBJECT_TYPE,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
if (this.subjectUuid) {
|
|
51
|
+
query.subject_uuid = this.subjectUuid;
|
|
52
|
+
} else {
|
|
53
|
+
query.subject = this.subjectPublicId;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const comments = await this.store.query('comment', query);
|
|
57
|
+
this.comments = comments;
|
|
58
|
+
|
|
59
|
+
return comments;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@action async publishComment(content) {
|
|
63
|
+
const subjectUuid = this.subjectUuid;
|
|
64
|
+
if (!subjectUuid) {
|
|
65
|
+
const error = new Error('Unable to publish comment because the issue UUID is missing.');
|
|
66
|
+
this.notifications.error(error.message);
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const comment = this.store.createRecord('comment', {
|
|
71
|
+
content,
|
|
72
|
+
subject_uuid: subjectUuid,
|
|
73
|
+
subject_type: ISSUE_SUBJECT_TYPE,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
await comment.save();
|
|
78
|
+
} catch (error) {
|
|
79
|
+
this.notifications.serverError(error);
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<div class="issue-detail-documents">
|
|
2
|
+
<div class="issue-detail-documents-header">
|
|
3
|
+
<div class="issue-detail-documents-title">
|
|
4
|
+
<FaIcon @icon="paperclip" />
|
|
5
|
+
<span>Attachments</span>
|
|
6
|
+
</div>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
{{#if this.queueFile.isRunning}}
|
|
10
|
+
<div class="issue-detail-dropzone is-loading">
|
|
11
|
+
<Spinner @loadingMessage="Uploading..." />
|
|
12
|
+
</div>
|
|
13
|
+
{{else}}
|
|
14
|
+
{{#let (file-queue name="issue-files" onFileAdded=(perform this.queueFile) accept=(join "," this.acceptedFileTypes)) as |queue|}}
|
|
15
|
+
<FileDropzone @queue={{queue}} class="issue-detail-dropzone file-dropzone" @disabled={{cannot-write @resource}} as |dropzone|>
|
|
16
|
+
{{#if dropzone.active}}
|
|
17
|
+
{{#if dropzone.valid}}
|
|
18
|
+
Drop files to attach them to this issue.
|
|
19
|
+
{{else}}
|
|
20
|
+
This file type is not supported.
|
|
21
|
+
{{/if}}
|
|
22
|
+
{{else if queue.files.length}}
|
|
23
|
+
<div class="issue-detail-dropzone-ready">
|
|
24
|
+
<FaIcon @icon="folder-open" />
|
|
25
|
+
<span>{{pluralize queue.files.length "file"}} ready for upload</span>
|
|
26
|
+
</div>
|
|
27
|
+
<div class="issue-detail-dropzone-progress">{{queue.progress}}%</div>
|
|
28
|
+
{{else}}
|
|
29
|
+
<div class="issue-detail-dropzone-empty">
|
|
30
|
+
<FaIcon @icon="folder-open" />
|
|
31
|
+
<span>Drop documents or images here</span>
|
|
32
|
+
<FileUpload
|
|
33
|
+
@name="issue-files"
|
|
34
|
+
@for="issue-files"
|
|
35
|
+
@accept={{join "," this.acceptedFileTypes}}
|
|
36
|
+
@multiple={{true}}
|
|
37
|
+
@onFileAdded={{perform this.queueFile}}
|
|
38
|
+
@disabled={{cannot-write @resource}}
|
|
39
|
+
>
|
|
40
|
+
<a tabindex={{0}} class="btn btn-sm btn-default">select files</a>
|
|
41
|
+
</FileUpload>
|
|
42
|
+
</div>
|
|
43
|
+
{{/if}}
|
|
44
|
+
</FileDropzone>
|
|
45
|
+
{{/let}}
|
|
46
|
+
{{/if}}
|
|
47
|
+
|
|
48
|
+
{{#if this.uploadQueue}}
|
|
49
|
+
<div class="issue-detail-upload-queue mt-4">
|
|
50
|
+
{{#each this.uploadQueue as |file|}}
|
|
51
|
+
<div class="issue-detail-upload-row">
|
|
52
|
+
<span>{{truncate-filename file.name 40}}</span>
|
|
53
|
+
<div class="flex flex-row items-center">
|
|
54
|
+
<Spinner class="mr-1" />
|
|
55
|
+
<strong>{{round file.progress}}%</strong>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
{{/each}}
|
|
59
|
+
</div>
|
|
60
|
+
{{/if}}
|
|
61
|
+
|
|
62
|
+
{{#if this.files.length}}
|
|
63
|
+
<div class="issue-detail-file-grid mt-4">
|
|
64
|
+
{{#each this.files as |file|}}
|
|
65
|
+
<File @file={{file}} @onDownload={{this.downloadFile}} @onDelete={{perform this.removeFile}} />
|
|
66
|
+
{{/each}}
|
|
67
|
+
</div>
|
|
68
|
+
{{/if}}
|
|
69
|
+
</div>
|