@fleetbase/fleetops-engine 0.6.51 → 0.6.53
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 +0 -2
- package/addon/components/cell/telematic-provider.hbs +16 -0
- package/addon/components/cell/telematic-provider.js +36 -0
- package/addon/components/cell/telematic-status.hbs +1 -0
- package/addon/components/cell/telematic-status.js +39 -0
- package/addon/components/device/manager.hbs +5 -6
- package/addon/components/device/manager.js +12 -15
- package/addon/components/driver/details.hbs +1 -1
- package/addon/components/fleet/vehicle-listing.js +1 -0
- package/addon/components/fuel-integration/form.hbs +200 -0
- package/addon/components/fuel-integration/form.js +242 -0
- package/addon/components/fuel-integration/hub.hbs +154 -0
- package/addon/components/fuel-integration/hub.js +165 -0
- package/addon/components/fuel-integration/matching-priority.hbs +76 -0
- package/addon/components/fuel-integration/matching-priority.js +145 -0
- package/addon/components/fuel-report/details.hbs +13 -1
- package/addon/components/issue/details.hbs +3 -57
- package/addon/components/issue/details.js +0 -96
- package/addon/components/layout/fleet-ops-sidebar/operations-monitor.hbs +201 -0
- package/addon/components/layout/fleet-ops-sidebar/operations-monitor.js +589 -0
- package/addon/components/layout/fleet-ops-sidebar.hbs +19 -233
- package/addon/components/layout/fleet-ops-sidebar.js +287 -376
- package/addon/components/maintenance-schedule/form.js +1 -0
- package/addon/components/map/drawer/device-event-listing.hbs +12 -2
- package/addon/components/map/drawer/driver-listing.hbs +14 -1
- package/addon/components/map/drawer/place-listing.hbs +14 -1
- package/addon/components/map/drawer/position-listing.hbs +12 -2
- package/addon/components/map/drawer/vehicle-listing.hbs +14 -1
- package/addon/components/modals/assigned-orders.hbs +40 -0
- package/addon/components/modals/attach-device.hbs +4 -3
- package/addon/components/modals/attach-telematic-device.hbs +25 -0
- package/addon/components/modals/attach-telematic-device.js +3 -0
- package/addon/components/modals/driver-assign-order.hbs +7 -58
- package/addon/components/modals/driver-assign-vehicle.hbs +10 -2
- package/addon/components/modals/fleet-assign-driver.hbs +18 -0
- package/addon/components/modals/fleet-assign-driver.js +3 -0
- package/addon/components/modals/fleet-assign-vehicle.hbs +18 -0
- package/addon/components/modals/fleet-assign-vehicle.js +3 -0
- package/addon/components/modals/telematic-connection-diagnostics.hbs +75 -0
- package/addon/components/modals/telematic-connection-diagnostics.js +287 -0
- package/addon/components/modals/vehicle-details.hbs +6 -1
- package/addon/components/modals/vehicle-form.hbs +8 -1
- package/addon/components/order/card.hbs +106 -0
- package/addon/components/order/card.js +257 -0
- package/addon/components/order/form/payload.js +3 -1
- package/addon/components/order/form/service-rate.hbs +2 -0
- package/addon/components/service-rate/form.hbs +3 -2
- package/addon/components/telematic/details.hbs +98 -87
- package/addon/components/telematic/details.js +230 -1
- package/addon/components/telematic/form.hbs +303 -72
- package/addon/components/telematic/form.js +519 -3
- package/addon/components/telematic/hub.hbs +149 -0
- package/addon/components/telematic/hub.js +142 -0
- package/addon/components/telematic/settings.hbs +152 -0
- package/addon/components/telematic/settings.js +45 -0
- package/addon/components/telematic/tab-empty-state.hbs +29 -0
- package/addon/components/telematic/tab-empty-state.js +33 -0
- package/addon/components/vehicle/details.hbs +6 -1
- package/addon/components/vehicle/form.hbs +2 -1
- package/addon/components/widget/fuel-providers.hbs +43 -0
- package/addon/components/widget/fuel-providers.js +27 -0
- package/addon/controllers/connectivity/devices/index/details.js +27 -1
- package/addon/controllers/connectivity/devices/index.js +18 -1
- package/addon/controllers/connectivity/fuel-providers/details/sync.js +18 -0
- package/addon/controllers/connectivity/fuel-providers/details/transactions.js +24 -0
- package/addon/controllers/connectivity/fuel-providers/details.js +69 -0
- package/addon/controllers/connectivity/fuel-providers/edit.js +24 -0
- package/addon/controllers/connectivity/fuel-providers/index.js +177 -0
- package/addon/controllers/connectivity/fuel-providers/new.js +37 -0
- package/addon/controllers/connectivity/telematics/details/attachments.js +247 -0
- package/addon/controllers/connectivity/telematics/details/devices.js +368 -0
- package/addon/controllers/connectivity/telematics/details/events.js +244 -0
- package/addon/controllers/connectivity/telematics/details/index.js +3 -0
- package/addon/controllers/connectivity/telematics/details/logs.js +43 -0
- package/addon/controllers/connectivity/telematics/details/sensors.js +270 -0
- package/addon/controllers/connectivity/telematics/details.js +149 -0
- package/addon/controllers/connectivity/telematics/{index/edit.js → edit.js} +15 -4
- package/addon/controllers/connectivity/telematics/index.js +25 -5
- package/addon/controllers/connectivity/telematics/{index/new.js → new.js} +8 -2
- package/addon/controllers/maintenance/index.js +100 -0
- package/addon/controllers/management/drivers/index/details.js +78 -1
- package/addon/controllers/management/drivers/index.js +39 -0
- package/addon/controllers/management/fleets/index/details.js +23 -1
- package/addon/controllers/management/fleets/index.js +6 -1
- package/addon/controllers/management/fuel-reports/index.js +37 -1
- package/addon/controllers/management/fuel-transactions/index/details.js +70 -0
- package/addon/controllers/management/fuel-transactions/index.js +240 -0
- package/addon/controllers/management/index.js +88 -0
- package/addon/controllers/management/vehicles/index/details.js +32 -0
- package/addon/controllers/management/vehicles/index.js +26 -0
- package/addon/controllers/operations/orders/index.js +4 -0
- package/addon/controllers/settings/index.js +142 -0
- package/addon/controllers/settings/payments/index.js +5 -0
- package/addon/extension.js +31 -0
- package/addon/routes/analytics/index.js +3 -0
- package/addon/routes/connectivity/devices/index.js +3 -0
- package/addon/routes/connectivity/fuel-providers/details/index.js +7 -0
- package/addon/routes/connectivity/fuel-providers/details/matching.js +7 -0
- package/addon/routes/connectivity/fuel-providers/details/settings.js +7 -0
- package/addon/routes/connectivity/fuel-providers/details/sync.js +18 -0
- package/addon/routes/connectivity/fuel-providers/details/transactions.js +14 -0
- package/addon/routes/connectivity/fuel-providers/details.js +18 -0
- package/addon/routes/connectivity/fuel-providers/edit.js +18 -0
- package/addon/routes/connectivity/fuel-providers/index.js +20 -0
- package/addon/routes/connectivity/fuel-providers/new.js +3 -0
- package/addon/routes/connectivity/fuel-providers.js +3 -0
- package/addon/routes/connectivity/telematics/details/attachments.js +28 -0
- package/addon/routes/connectivity/telematics/details/devices.js +30 -0
- package/addon/routes/connectivity/telematics/details/events.js +33 -0
- package/addon/routes/connectivity/telematics/details/index.js +3 -0
- package/addon/routes/connectivity/telematics/details/logs.js +17 -0
- package/addon/routes/connectivity/telematics/details/sensors.js +29 -0
- package/addon/routes/connectivity/telematics/{index/details.js → details.js} +1 -1
- package/addon/routes/connectivity/telematics/{index/edit.js → edit.js} +1 -1
- package/addon/routes/connectivity/telematics/new.js +3 -0
- package/addon/routes/maintenance/index.js +3 -0
- package/addon/routes/maintenance/work-orders/index.js +2 -0
- package/addon/routes/management/drivers/index/details/activity.js +3 -0
- package/addon/routes/management/fuel-reports/index.js +2 -0
- package/addon/routes/management/fuel-transactions/index/details.js +18 -0
- package/addon/routes/management/fuel-transactions/index.js +21 -0
- package/addon/routes/management/fuel-transactions.js +3 -0
- package/addon/routes/settings/index.js +3 -0
- package/addon/routes.js +28 -9
- package/addon/services/device-actions.js +64 -1
- package/addon/services/device-event-actions.js +12 -1
- package/addon/services/driver-actions.js +147 -11
- package/addon/services/fleet-actions.js +66 -1
- package/addon/services/fuel-integration-actions.js +31 -0
- package/addon/services/issue-actions.js +1 -1
- package/addon/services/telematic-actions.js +9 -3
- package/addon/services/vehicle-actions.js +125 -0
- package/addon/styles/fleetops-engine.css +2237 -162
- package/addon/templates/analytics/index.hbs +14 -0
- package/addon/templates/analytics/reports/index.hbs +19 -1
- package/addon/templates/connectivity/devices/index.hbs +19 -1
- package/addon/templates/connectivity/events/index.hbs +16 -1
- package/addon/templates/connectivity/fuel-providers/details/index.hbs +38 -0
- package/addon/templates/connectivity/fuel-providers/details/matching.hbs +13 -0
- package/addon/templates/connectivity/fuel-providers/details/settings.hbs +11 -0
- package/addon/templates/connectivity/fuel-providers/details/sync.hbs +48 -0
- package/addon/templates/connectivity/fuel-providers/details/transactions.hbs +21 -0
- package/addon/templates/connectivity/fuel-providers/details.hbs +51 -0
- package/addon/templates/connectivity/fuel-providers/edit.hbs +24 -0
- package/addon/templates/connectivity/fuel-providers/index.hbs +12 -0
- package/addon/templates/connectivity/fuel-providers/new.hbs +22 -0
- package/addon/templates/connectivity/fuel-providers.hbs +1 -0
- package/addon/templates/connectivity/sensors/index.hbs +19 -1
- package/addon/templates/connectivity/telematics/details/attachments.hbs +163 -0
- package/addon/templates/connectivity/telematics/details/devices.hbs +88 -0
- package/addon/templates/connectivity/telematics/details/events.hbs +101 -0
- package/addon/templates/connectivity/telematics/{index/details → details}/index.hbs +0 -1
- package/addon/templates/connectivity/telematics/details/logs.hbs +69 -0
- package/addon/templates/connectivity/telematics/details/sensors.hbs +88 -0
- package/addon/templates/connectivity/telematics/details.hbs +53 -0
- package/addon/templates/connectivity/telematics/edit.hbs +27 -0
- package/addon/templates/connectivity/telematics/index.hbs +6 -8
- package/addon/templates/connectivity/telematics/new.hbs +24 -0
- package/addon/templates/maintenance/equipment/index.hbs +19 -1
- package/addon/templates/maintenance/index.hbs +113 -0
- package/addon/templates/maintenance/maintenances/index.hbs +15 -1
- package/addon/templates/maintenance/parts/index.hbs +19 -1
- package/addon/templates/maintenance/schedules/index.hbs +19 -1
- package/addon/templates/maintenance/work-orders/index.hbs +19 -1
- package/addon/templates/management/contacts/customers.hbs +15 -1
- package/addon/templates/management/contacts/index.hbs +19 -1
- package/addon/templates/management/drivers/index/details/activity.hbs +1 -0
- package/addon/templates/management/drivers/index.hbs +19 -1
- package/addon/templates/management/fleets/index.hbs +19 -1
- package/addon/templates/management/fuel-reports/index.hbs +19 -1
- package/addon/templates/management/fuel-transactions/index/details.hbs +73 -0
- package/addon/templates/management/fuel-transactions/index.hbs +58 -0
- package/addon/templates/management/fuel-transactions.hbs +1 -0
- package/addon/templates/management/index.hbs +109 -73
- package/addon/templates/management/issues/index.hbs +19 -1
- package/addon/templates/management/places/index.hbs +19 -1
- package/addon/templates/management/vehicles/index.hbs +19 -1
- package/addon/templates/management/vendors/index.hbs +19 -1
- package/addon/templates/management/vendors/integrated.hbs +13 -1
- package/addon/templates/operations/orders/index.hbs +19 -1
- package/addon/templates/operations/service-rates/index.hbs +19 -1
- package/addon/templates/settings/index.hbs +83 -0
- package/addon/templates/settings/payments/index.hbs +15 -1
- package/addon/templates/settings/routing.hbs +2 -2
- package/addon/utils/fleet-ops-options.js +3 -0
- package/app/components/cell/telematic-provider.js +1 -0
- package/app/components/cell/telematic-status.js +1 -0
- package/app/components/fuel-integration/form.js +1 -0
- package/app/components/fuel-integration/hub.js +1 -0
- package/app/components/fuel-integration/matching-priority.js +1 -0
- package/app/components/modals/assigned-orders.js +1 -0
- package/app/components/modals/attach-telematic-device.js +1 -0
- package/app/components/modals/fleet-assign-driver.js +1 -0
- package/app/components/modals/fleet-assign-vehicle.js +1 -0
- package/app/components/modals/telematic-connection-diagnostics.js +1 -0
- package/app/components/order/card.js +1 -0
- package/app/components/telematic/hub.js +1 -0
- package/app/components/telematic/settings.js +1 -0
- package/app/components/telematic/tab-empty-state.js +1 -0
- package/app/components/widget/fuel-providers.js +1 -0
- package/app/controllers/connectivity/fuel-providers/details/sync.js +1 -0
- package/app/controllers/connectivity/{telematics/index/details/devices.js → fuel-providers/details/transactions.js} +1 -1
- package/app/controllers/connectivity/{telematics/index → fuel-providers}/details.js +1 -1
- package/app/controllers/connectivity/{telematics/index → fuel-providers}/edit.js +1 -1
- package/app/controllers/connectivity/{telematics/index/details → fuel-providers}/index.js +1 -1
- package/app/controllers/connectivity/{telematics/index → fuel-providers}/new.js +1 -1
- package/app/controllers/connectivity/telematics/details/attachments.js +1 -0
- package/app/controllers/connectivity/telematics/details/devices.js +1 -0
- package/app/controllers/connectivity/telematics/details/events.js +1 -0
- package/app/controllers/connectivity/telematics/details/index.js +1 -0
- package/app/controllers/connectivity/telematics/details/logs.js +1 -0
- package/app/controllers/connectivity/telematics/details/sensors.js +1 -0
- package/app/controllers/connectivity/telematics/details.js +1 -0
- package/app/controllers/connectivity/telematics/edit.js +1 -0
- package/app/controllers/connectivity/telematics/new.js +1 -0
- package/app/controllers/maintenance/index.js +1 -0
- package/app/controllers/management/fuel-transactions/index/details.js +1 -0
- package/app/controllers/management/fuel-transactions/index.js +1 -0
- package/app/controllers/management/index.js +1 -0
- package/app/controllers/settings/index.js +1 -0
- package/app/routes/analytics/index.js +1 -0
- package/app/routes/connectivity/{telematics/index → fuel-providers}/details/index.js +1 -1
- package/app/routes/connectivity/{telematics/index/details/events.js → fuel-providers/details/matching.js} +1 -1
- package/app/routes/connectivity/fuel-providers/details/settings.js +1 -0
- package/app/routes/connectivity/fuel-providers/details/sync.js +1 -0
- package/app/routes/connectivity/{telematics/index/details/devices.js → fuel-providers/details/transactions.js} +1 -1
- package/app/routes/connectivity/{telematics/index → fuel-providers}/details.js +1 -1
- package/app/routes/connectivity/{telematics/index → fuel-providers}/edit.js +1 -1
- package/app/routes/connectivity/fuel-providers/index.js +1 -0
- package/app/routes/connectivity/{telematics/index → fuel-providers}/new.js +1 -1
- package/app/routes/connectivity/fuel-providers.js +1 -0
- package/app/routes/connectivity/telematics/details/attachments.js +1 -0
- package/app/routes/connectivity/telematics/details/devices.js +1 -0
- package/app/routes/connectivity/telematics/details/events.js +1 -0
- package/app/routes/connectivity/telematics/details/index.js +1 -0
- package/app/routes/connectivity/telematics/details/logs.js +1 -0
- package/app/routes/connectivity/telematics/details/sensors.js +1 -0
- package/app/routes/connectivity/telematics/details.js +1 -0
- package/app/routes/connectivity/telematics/edit.js +1 -0
- package/app/routes/connectivity/telematics/new.js +1 -0
- package/app/routes/maintenance/index.js +1 -0
- package/app/routes/management/drivers/index/details/activity.js +1 -0
- package/app/routes/management/fuel-transactions/index/details.js +1 -0
- package/app/routes/management/fuel-transactions/index.js +1 -0
- package/app/routes/management/fuel-transactions.js +1 -0
- package/app/routes/settings/index.js +1 -0
- package/app/services/fuel-integration-actions.js +1 -0
- package/app/templates/analytics/index.js +1 -0
- package/app/templates/connectivity/{telematics/index → fuel-providers}/details/index.js +1 -1
- package/app/templates/connectivity/{telematics/index/details/events.js → fuel-providers/details/matching.js} +1 -1
- package/app/templates/connectivity/fuel-providers/details/settings.js +1 -0
- package/app/templates/connectivity/fuel-providers/details/sync.js +1 -0
- package/app/templates/connectivity/{telematics/index/details/devices.js → fuel-providers/details/transactions.js} +1 -1
- package/app/templates/connectivity/{telematics/index → fuel-providers}/details.js +1 -1
- package/app/templates/connectivity/{telematics/index → fuel-providers}/edit.js +1 -1
- package/app/templates/connectivity/fuel-providers/index.js +1 -0
- package/app/templates/connectivity/{telematics/index → fuel-providers}/new.js +1 -1
- package/app/templates/connectivity/fuel-providers.js +1 -0
- package/app/templates/connectivity/telematics/details/attachments.js +1 -0
- package/app/templates/connectivity/telematics/details/devices.js +1 -0
- package/app/templates/connectivity/telematics/details/events.js +1 -0
- package/app/templates/connectivity/telematics/details/index.js +1 -0
- package/app/templates/connectivity/telematics/details/logs.js +1 -0
- package/app/templates/connectivity/telematics/details/sensors.js +1 -0
- package/app/templates/connectivity/telematics/details.js +1 -0
- package/app/templates/connectivity/telematics/edit.js +1 -0
- package/app/templates/connectivity/telematics/new.js +1 -0
- package/app/templates/maintenance/index.js +1 -0
- package/app/templates/management/drivers/index/details/activity.js +1 -0
- package/app/templates/management/fuel-transactions/index/details.js +1 -0
- package/app/templates/management/fuel-transactions/index.js +1 -0
- package/app/templates/management/fuel-transactions.js +1 -0
- package/app/templates/management/index.js +1 -0
- package/app/templates/settings/index.js +1 -0
- package/assets/images/telematics/providers/afaqy.webp +0 -0
- package/assets/images/telematics/providers/default.webp +0 -0
- package/assets/images/telematics/providers/flespi.webp +0 -0
- package/assets/images/telematics/providers/geotab.webp +0 -0
- package/assets/images/telematics/providers/safee.webp +0 -0
- package/assets/images/telematics/providers/samsara.webp +0 -0
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +4 -4
- package/server/config/fuel-providers.php +42 -0
- package/server/config/telematics.php +139 -130
- package/server/migrations/2026_06_06_000001_harden_device_events_telematics_contract.php +59 -0
- package/server/migrations/2026_06_08_000001_create_fuel_provider_connections_table.php +37 -0
- package/server/migrations/2026_06_08_000002_create_fuel_provider_transactions_table.php +53 -0
- package/server/migrations/2026_06_08_000003_create_fuel_provider_sync_runs_table.php +40 -0
- package/server/migrations/2026_06_10_000001_add_fuel_card_number_to_vehicles_table.php +25 -0
- package/server/migrations/2026_06_10_000002_add_vehicle_identity_columns_to_fuel_provider_transactions_table.php +39 -0
- package/server/migrations/2026_06_15_000001_normalize_device_attachable_vehicle_morph_types.php +30 -0
- package/server/src/Console/Commands/FixInvalidPolymorphicRelationTypeNamespaces.php +1 -0
- package/server/src/Contracts/FuelProvider.php +30 -0
- package/server/src/Contracts/TelematicProviderDescriptor.php +3 -1
- package/server/src/Events/FuelProviderTransactionImported.php +17 -0
- package/server/src/Events/FuelProviderTransactionMatched.php +17 -0
- package/server/src/Events/FuelProviderTransactionUnmatched.php +17 -0
- package/server/src/Events/FuelReportCreatedFromProvider.php +18 -0
- package/server/src/Http/Controllers/Internal/v1/AnalyticsController.php +6 -0
- package/server/src/Http/Controllers/Internal/v1/DeviceController.php +140 -2
- package/server/src/Http/Controllers/Internal/v1/DeviceEventController.php +44 -0
- package/server/src/Http/Controllers/Internal/v1/DriverController.php +158 -7
- package/server/src/Http/Controllers/Internal/v1/FuelProviderConnectionController.php +124 -0
- package/server/src/Http/Controllers/Internal/v1/FuelProviderSyncRunController.php +10 -0
- package/server/src/Http/Controllers/Internal/v1/FuelProviderTransactionController.php +76 -0
- package/server/src/Http/Controllers/Internal/v1/HubController.php +300 -0
- package/server/src/Http/Controllers/Internal/v1/SearchController.php +506 -0
- package/server/src/Http/Controllers/Internal/v1/SensorController.php +1 -1
- package/server/src/Http/Controllers/Internal/v1/TelematicController.php +179 -16
- package/server/src/Http/Controllers/Internal/v1/VehicleController.php +249 -0
- package/server/src/Http/Controllers/TelematicWebhookController.php +103 -19
- package/server/src/Http/Filter/DeviceFilter.php +74 -0
- package/server/src/Http/Filter/DriverFilter.php +6 -0
- package/server/src/Http/Filter/FuelProviderConnectionFilter.php +33 -0
- package/server/src/Http/Filter/FuelProviderSyncRunFilter.php +28 -0
- package/server/src/Http/Filter/FuelProviderTransactionFilter.php +59 -0
- package/server/src/Http/Filter/FuelReportFilter.php +10 -0
- package/server/src/Http/Filter/VehicleFilter.php +6 -0
- package/server/src/Http/Resources/v1/Driver.php +15 -0
- package/server/src/Http/Resources/v1/FuelProviderConnection.php +30 -0
- package/server/src/Http/Resources/v1/FuelProviderSyncRun.php +36 -0
- package/server/src/Http/Resources/v1/FuelProviderTransaction.php +54 -0
- package/server/src/Http/Resources/v1/FuelReport.php +28 -22
- package/server/src/Http/Resources/v1/Index/Driver.php +26 -20
- package/server/src/Http/Resources/v1/Index/Vehicle.php +29 -22
- package/server/src/Http/Resources/v1/Vehicle.php +17 -0
- package/server/src/Http/Resources/v1/VehicleWithoutDriver.php +2 -0
- package/server/src/Jobs/SyncFuelProviderTransactionsJob.php +57 -0
- package/server/src/Jobs/SyncTelematicDevicesJob.php +105 -17
- package/server/src/Jobs/TestTelematicConnectionJob.php +9 -22
- package/server/src/Models/Asset.php +2 -1
- package/server/src/Models/Contact.php +2 -1
- package/server/src/Models/Device.php +27 -4
- package/server/src/Models/DeviceEvent.php +40 -3
- package/server/src/Models/Driver.php +3 -2
- package/server/src/Models/Equipment.php +2 -1
- package/server/src/Models/Fleet.php +2 -1
- package/server/src/Models/FuelProviderConnection.php +60 -0
- package/server/src/Models/FuelProviderSyncRun.php +58 -0
- package/server/src/Models/FuelProviderTransaction.php +119 -0
- package/server/src/Models/FuelReport.php +16 -1
- package/server/src/Models/Part.php +2 -1
- package/server/src/Models/Sensor.php +21 -4
- package/server/src/Models/ServiceRate.php +39 -2
- package/server/src/Models/Vehicle.php +14 -2
- package/server/src/Models/Vendor.php +2 -1
- package/server/src/Providers/FleetOpsServiceProvider.php +20 -0
- package/server/src/Support/Analytics/FuelProviderSummary.php +42 -0
- package/server/src/Support/FuelProviders/FuelProviderDescriptor.php +55 -0
- package/server/src/Support/FuelProviders/FuelProviderRegistry.php +75 -0
- package/server/src/Support/FuelProviders/FuelProviderService.php +447 -0
- package/server/src/Support/FuelProviders/Providers/AbstractFuelProvider.php +98 -0
- package/server/src/Support/FuelProviders/Providers/PetroAppFuelProvider.php +167 -0
- package/server/src/Support/Telematics/Providers/AbstractProvider.php +27 -6
- package/server/src/Support/Telematics/Providers/AfaqyProvider.php +105 -38
- package/server/src/Support/Telematics/Providers/FlespiProvider.php +1 -0
- package/server/src/Support/Telematics/Providers/GeotabProvider.php +1 -0
- package/server/src/Support/Telematics/Providers/SafeeProvider.php +32 -22
- package/server/src/Support/Telematics/Providers/SamsaraProvider.php +3 -1
- package/server/src/Support/Telematics/TelematicService.php +340 -21
- package/server/src/routes.php +50 -4
- package/server/tests/FuelProviderRegistryTest.php +416 -0
- package/server/tests/HubRoutesTest.php +62 -0
- package/server/tests/ServiceRateAlgorithmWeightVariablesTest.php +44 -0
- package/server/tests/TelematicsHardeningTest.php +359 -0
- package/server/tests/VehicleDriverAssignmentTest.php +51 -0
- package/translations/ar-ae.yml +23 -1
- package/translations/bg-bg.yaml +72 -0
- package/translations/en-us.yaml +83 -0
- package/translations/es-pa.yaml +72 -0
- package/translations/fr-fr.yaml +72 -0
- package/translations/mn-mn.yaml +72 -0
- package/translations/pt-br.yaml +72 -0
- package/translations/ru-ru.yaml +72 -0
- package/translations/vi-vn.yaml +98 -1
- package/addon/controllers/connectivity/telematics/index/details/devices.js +0 -3
- package/addon/controllers/connectivity/telematics/index/details/events.js +0 -3
- package/addon/controllers/connectivity/telematics/index/details/index.js +0 -3
- package/addon/controllers/connectivity/telematics/index/details/sensors.js +0 -3
- package/addon/controllers/connectivity/telematics/index/details.js +0 -24
- package/addon/routes/connectivity/telematics/index/details/devices.js +0 -3
- package/addon/routes/connectivity/telematics/index/details/events.js +0 -3
- package/addon/routes/connectivity/telematics/index/details/index.js +0 -3
- package/addon/routes/connectivity/telematics/index/details/sensors.js +0 -3
- package/addon/routes/connectivity/telematics/index/new.js +0 -3
- package/addon/templates/connectivity/telematics/index/details/devices.hbs +0 -1
- package/addon/templates/connectivity/telematics/index/details/events.hbs +0 -1
- package/addon/templates/connectivity/telematics/index/details/sensors.hbs +0 -1
- package/addon/templates/connectivity/telematics/index/details.hbs +0 -14
- package/addon/templates/connectivity/telematics/index/edit.hbs +0 -12
- package/addon/templates/connectivity/telematics/index/new.hbs +0 -12
- package/app/controllers/connectivity/telematics/index/details/events.js +0 -1
- package/app/controllers/connectivity/telematics/index/details/sensors.js +0 -1
- package/app/routes/connectivity/telematics/index/details/sensors.js +0 -1
- package/app/templates/connectivity/telematics/index/details/sensors.js +0 -1
|
@@ -0,0 +1,242 @@
|
|
|
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
|
+
import { task } from 'ember-concurrency';
|
|
6
|
+
import copyToClipboard from '@fleetbase/ember-core/utils/copy-to-clipboard';
|
|
7
|
+
|
|
8
|
+
export default class FuelIntegrationFormComponent extends Component {
|
|
9
|
+
@service fetch;
|
|
10
|
+
@service notifications;
|
|
11
|
+
|
|
12
|
+
@tracked providers = [];
|
|
13
|
+
@tracked activeStep = 0;
|
|
14
|
+
@tracked connectionTestResult;
|
|
15
|
+
@tracked showDiagnostics = false;
|
|
16
|
+
|
|
17
|
+
constructor() {
|
|
18
|
+
super(...arguments);
|
|
19
|
+
this.loadProviders.perform();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get setupSteps() {
|
|
23
|
+
return [
|
|
24
|
+
{ icon: 'gas-pump', label: 'Provider', complete: Boolean(this.selectedProvider) },
|
|
25
|
+
{ icon: 'key', label: 'Credentials', complete: this.hasRequiredCredentials },
|
|
26
|
+
{ icon: 'plug', label: 'Test', complete: this.connectionTestResult?.success === true },
|
|
27
|
+
{ icon: 'clock-rotate-left', label: 'Sync', complete: Boolean(this.syncSettings.window_days) },
|
|
28
|
+
{ icon: 'route', label: 'Matching', complete: this.matchingOrder.length > 0 },
|
|
29
|
+
].map((step, index) => ({
|
|
30
|
+
...step,
|
|
31
|
+
active: this.activeStep === index,
|
|
32
|
+
complete: index < this.activeStep && step.complete,
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get selectedProvider() {
|
|
37
|
+
return this.providers.find((provider) => provider.key === this.args.resource?.provider);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get providerCards() {
|
|
41
|
+
return this.providers.map((provider) => ({
|
|
42
|
+
...provider,
|
|
43
|
+
selected: provider.key === this.args.resource?.provider,
|
|
44
|
+
categoryLabel: provider.category ?? provider.metadata?.category ?? 'Fuel card integration',
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
get credentials() {
|
|
49
|
+
return this.args.resource?.credentials ?? {};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
get syncSettings() {
|
|
53
|
+
return this.args.resource?.sync_settings ?? {};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
get matchingOrder() {
|
|
57
|
+
return this.syncSettings.matching_order;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
get hasRequiredCredentials() {
|
|
61
|
+
const requiredFields = this.selectedProvider?.required_fields?.filter((field) => field.required) ?? [];
|
|
62
|
+
|
|
63
|
+
return requiredFields.every((field) => {
|
|
64
|
+
const value = this.credentials[field.name];
|
|
65
|
+
return value !== null && value !== undefined && String(value).trim() !== '';
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
get connectionState() {
|
|
70
|
+
if (this.testConnection.isRunning) {
|
|
71
|
+
return 'testing';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!this.connectionTestResult) {
|
|
75
|
+
return 'idle';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return this.connectionTestResult.success ? 'success' : 'failed';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
get connectionStateTitle() {
|
|
82
|
+
switch (this.connectionState) {
|
|
83
|
+
case 'testing':
|
|
84
|
+
return 'Testing provider credentials...';
|
|
85
|
+
case 'success':
|
|
86
|
+
return 'Connection verified';
|
|
87
|
+
case 'failed':
|
|
88
|
+
return 'Connection failed';
|
|
89
|
+
default:
|
|
90
|
+
return 'Ready to test';
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
get connectionStateMessage() {
|
|
95
|
+
if (this.connectionState === 'idle') {
|
|
96
|
+
return 'Run a live credential test before saving so operators know the integration can import fuel data.';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (this.connectionState === 'testing') {
|
|
100
|
+
return 'The configured credentials are being checked with the fuel integration provider.';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return this.connectionTestResult?.message;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
get connectionMetadataEntries() {
|
|
107
|
+
return Object.entries(this.connectionTestResult?.metadata ?? {}).map(([key, value]) => ({
|
|
108
|
+
label: key.replaceAll('_', ' '),
|
|
109
|
+
value,
|
|
110
|
+
}));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
get diagnosticEntries() {
|
|
114
|
+
const entries = [
|
|
115
|
+
{
|
|
116
|
+
time: new Date().toLocaleTimeString(),
|
|
117
|
+
tone: 'info',
|
|
118
|
+
text: this.selectedProvider ? `Provider selected: ${this.selectedProvider.label}` : 'No provider selected',
|
|
119
|
+
},
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
if (this.connectionTestResult) {
|
|
123
|
+
entries.push({
|
|
124
|
+
time: new Date().toLocaleTimeString(),
|
|
125
|
+
tone: this.connectionTestResult.success ? 'success' : 'danger',
|
|
126
|
+
text: this.connectionTestResult.message ?? 'Connection test returned without a message',
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return entries;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
get diagnosticsText() {
|
|
134
|
+
return this.diagnosticEntries.map((entry) => `[${entry.time}] ${entry.text}`).join('\n');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
@task *loadProviders() {
|
|
138
|
+
try {
|
|
139
|
+
const providers = yield this.fetch.get('fuel-provider-connections/providers');
|
|
140
|
+
this.providers = providers;
|
|
141
|
+
|
|
142
|
+
if (!this.args.resource?.provider) {
|
|
143
|
+
const initialProvider = this.args.initialProviderKey ? providers.find((provider) => provider.key === this.args.initialProviderKey) : providers[0];
|
|
144
|
+
if (initialProvider) {
|
|
145
|
+
this.selectProvider(initialProvider);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
} catch (error) {
|
|
149
|
+
this.notifications.serverError(error);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
@task *testConnection() {
|
|
154
|
+
if (!this.selectedProvider) {
|
|
155
|
+
this.notifications.warning('Choose a fuel integration provider first.');
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (!this.hasRequiredCredentials) {
|
|
160
|
+
this.notifications.warning('Enter the required credentials before testing.');
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
this.connectionTestResult = yield this.fetch.post(`fuel-provider-connections/providers/${this.selectedProvider.key}/test-credentials`, {
|
|
166
|
+
credentials: this.credentials,
|
|
167
|
+
connection_id: this.args.resource?.id,
|
|
168
|
+
});
|
|
169
|
+
} catch (error) {
|
|
170
|
+
this.connectionTestResult = {
|
|
171
|
+
success: false,
|
|
172
|
+
message: error.message ?? 'Connection test failed.',
|
|
173
|
+
metadata: {},
|
|
174
|
+
};
|
|
175
|
+
this.notifications.serverError(error);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
@action selectProvider(provider) {
|
|
180
|
+
this.args.resource?.setProperties?.({
|
|
181
|
+
provider: provider.key,
|
|
182
|
+
name: this.args.resource?.name || provider.label,
|
|
183
|
+
credentials: (provider.required_fields ?? []).reduce((credentials, field) => {
|
|
184
|
+
credentials[field.name] = this.credentials[field.name] ?? field.default ?? null;
|
|
185
|
+
return credentials;
|
|
186
|
+
}, {}),
|
|
187
|
+
});
|
|
188
|
+
this.connectionTestResult = null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
@action setCredential(field, event) {
|
|
192
|
+
this.args.resource?.set('credentials', {
|
|
193
|
+
...this.credentials,
|
|
194
|
+
[field.name]: event.target.value,
|
|
195
|
+
});
|
|
196
|
+
this.connectionTestResult = null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
@action setSyncSetting(key, eventOrValue) {
|
|
200
|
+
const value = eventOrValue?.target ? (eventOrValue.target.type === 'checkbox' ? eventOrValue.target.checked : eventOrValue.target.value) : eventOrValue;
|
|
201
|
+
this.args.resource?.set('sync_settings', {
|
|
202
|
+
...this.syncSettings,
|
|
203
|
+
[key]: value,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
@action setName(event) {
|
|
208
|
+
this.args.resource?.set('name', event.target.value);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
@action toggleMatchingField(field) {
|
|
212
|
+
const current = this.matchingOrder;
|
|
213
|
+
const next = current.includes(field) ? current.filter((item) => item !== field) : [...current, field];
|
|
214
|
+
|
|
215
|
+
this.setMatchingOrder(next);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
@action setMatchingOrder(next) {
|
|
219
|
+
this.args.resource?.set('sync_settings', {
|
|
220
|
+
...this.syncSettings,
|
|
221
|
+
matching_order: next,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
@action goToStep(index) {
|
|
226
|
+
this.activeStep = index;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
@action nextStep() {
|
|
230
|
+
this.activeStep = Math.min(this.activeStep + 1, this.setupSteps.length - 1);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
@action previousStep() {
|
|
234
|
+
this.activeStep = Math.max(this.activeStep - 1, 0);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
@action copyDiagnostics() {
|
|
238
|
+
copyToClipboard(this.diagnosticsText)
|
|
239
|
+
.then(() => this.notifications.success('Connection diagnostics copied.'))
|
|
240
|
+
.catch(() => this.notifications.error('Unable to copy diagnostics.'));
|
|
241
|
+
}
|
|
242
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
<div class="h-full overflow-y-auto bg-gray-50 dark:bg-gray-900" ...attributes>
|
|
2
|
+
<div class="flex w-full flex-col">
|
|
3
|
+
<section class="sticky top-0 z-50 border-b border-gray-200 bg-white px-4 py-4 shadow-sm dark:border-gray-700 dark:bg-gray-900 md:px-6">
|
|
4
|
+
<div class="mx-auto flex w-full max-w-7xl flex-col gap-3 md:flex-row md:items-start md:justify-between">
|
|
5
|
+
<div class="min-w-0">
|
|
6
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
7
|
+
<h1 class="text-lg font-bold text-gray-900 dark:text-gray-100">Fuel Integrations</h1>
|
|
8
|
+
<Badge @status={{if this.syncErrorCount "warning" "success"}} @hideStatusDot={{true}}>
|
|
9
|
+
{{this.syncErrorCount}} sync issues
|
|
10
|
+
</Badge>
|
|
11
|
+
</div>
|
|
12
|
+
<p class="mt-1 max-w-3xl text-sm leading-5 text-gray-500 dark:text-gray-300">
|
|
13
|
+
Connect fuel card providers or fuel management systems, import provider bills as fuel transactions, match them to vehicles and trips, then create linked fuel reports for operations.
|
|
14
|
+
</p>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="flex flex-shrink-0 items-center gap-2 whitespace-nowrap">
|
|
17
|
+
<Button @icon="refresh" @size="sm" @helpText={{t "common.reload"}} @onClick={{@onRefresh}} />
|
|
18
|
+
<Button @icon="plus" @text="Connect Integration" @type="primary" @size="sm" @onClick={{this.createIntegration}} />
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
</section>
|
|
22
|
+
|
|
23
|
+
<div class="mx-auto flex w-full max-w-7xl flex-col gap-4 px-4 py-4 md:px-6">
|
|
24
|
+
<section class="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4">
|
|
25
|
+
{{#each this.kpiWidgets as |widget|}}
|
|
26
|
+
<div class="fleet-ops-widget fleet-ops-kpi-tile fleetops-connectivity-kpi-tile {{widget.accentClass}} relative flex min-h-[118px] flex-col overflow-hidden rounded-md border bg-white shadow-sm transition hover:-translate-y-px hover:shadow-lg dark:bg-gray-800">
|
|
27
|
+
<div class="flex h-full flex-col justify-between p-3">
|
|
28
|
+
<div class="flex items-start justify-between gap-2">
|
|
29
|
+
<div class="min-w-0">
|
|
30
|
+
<div class="truncate text-[10px] font-bold uppercase text-gray-500 dark:text-gray-400">{{widget.label}}</div>
|
|
31
|
+
<div class="mt-2 truncate text-[1.35rem] font-extrabold leading-none text-gray-900 dark:text-gray-50">{{widget.value}}</div>
|
|
32
|
+
</div>
|
|
33
|
+
<div class="fleetops-connectivity-kpi-icon flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-md text-[13px]">
|
|
34
|
+
<FaIcon @icon={{widget.icon}} />
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<div class="mt-3 flex items-center justify-between gap-2 text-[11px]">
|
|
39
|
+
<span class="min-w-0 truncate text-gray-500 dark:text-gray-400">{{widget.help}}</span>
|
|
40
|
+
<button type="button" class="inline-flex flex-shrink-0 items-center gap-1 font-semibold text-blue-600 hover:text-blue-700 dark:text-blue-300 dark:hover:text-blue-200" {{on "click" (fn this.runKpiAction widget)}}>
|
|
41
|
+
<span>{{widget.action}}</span>
|
|
42
|
+
<FaIcon @icon="arrow-right" class="text-[9px]" />
|
|
43
|
+
</button>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
{{/each}}
|
|
48
|
+
</section>
|
|
49
|
+
|
|
50
|
+
{{#if this.hasConnections}}
|
|
51
|
+
<section class="fleetops-provider-connections-panel min-w-0 overflow-hidden rounded-md border border-gray-200 bg-white shadow-sm dark:border-gray-700 dark:bg-gray-800">
|
|
52
|
+
<div class="fleetops-provider-connections-header border-b border-gray-200 px-4 py-3 dark:border-gray-700">
|
|
53
|
+
<div class="min-w-0 max-w-md">
|
|
54
|
+
<h2 class="text-sm font-bold text-gray-900 dark:text-gray-100">Connected Fuel Systems</h2>
|
|
55
|
+
<p class="mt-1 text-xs leading-5 text-gray-500 dark:text-gray-300">Manage credentials, connection health, sync windows, and imported transaction status for each fuel integration.</p>
|
|
56
|
+
</div>
|
|
57
|
+
<div class="fleetops-provider-connections-actions flex min-w-0 flex-wrap items-center gap-2 lg:ml-auto lg:flex-nowrap lg:justify-end">
|
|
58
|
+
<Layout::Resource::TabularActions
|
|
59
|
+
@title="Connected Fuel Systems"
|
|
60
|
+
@searchQuery={{@searchQuery}}
|
|
61
|
+
@onSearch={{@onSearch}}
|
|
62
|
+
@columns={{this.columns}}
|
|
63
|
+
@bulkActions={{@bulkActions}}
|
|
64
|
+
@controller={{@controller}}
|
|
65
|
+
@table={{this.table}}
|
|
66
|
+
@searchInputClass="fleetops-provider-connections-search"
|
|
67
|
+
@filterPickerButtonClass="fleetops-provider-connections-action-button"
|
|
68
|
+
@columnPickerButtonClass="fleetops-provider-connections-action-button"
|
|
69
|
+
/>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
<Layout::Resource::Tabular
|
|
73
|
+
@resource="fuel-provider-connection"
|
|
74
|
+
@title=""
|
|
75
|
+
@withoutHeader={{true}}
|
|
76
|
+
@searchQuery={{@searchQuery}}
|
|
77
|
+
@onSearch={{@onSearch}}
|
|
78
|
+
@data={{@connections}}
|
|
79
|
+
@columns={{this.columns}}
|
|
80
|
+
@page={{@page}}
|
|
81
|
+
@onPageChange={{@onPageChange}}
|
|
82
|
+
@bulkActions={{@bulkActions}}
|
|
83
|
+
@controller={{@controller}}
|
|
84
|
+
@setupTable={{this.setupTable}}
|
|
85
|
+
@emptyStateComponent={{component
|
|
86
|
+
"table/empty-state"
|
|
87
|
+
variant="compact"
|
|
88
|
+
icon="gas-pump"
|
|
89
|
+
title="No fuel connections"
|
|
90
|
+
description="Connect a fuel provider to import transactions and match them to reports."
|
|
91
|
+
primaryText="Connect provider"
|
|
92
|
+
primaryIcon="plus"
|
|
93
|
+
primaryAction=this.createIntegration
|
|
94
|
+
}}
|
|
95
|
+
/>
|
|
96
|
+
</section>
|
|
97
|
+
{{else}}
|
|
98
|
+
<section class="rounded-md border border-dashed border-gray-300 bg-white p-6 text-center shadow-sm dark:border-gray-700 dark:bg-gray-800">
|
|
99
|
+
<div class="mx-auto flex h-11 w-11 items-center justify-center rounded-md bg-blue-50 text-blue-600 dark:bg-blue-900/20 dark:text-blue-300">
|
|
100
|
+
<FaIcon @icon="gas-pump" />
|
|
101
|
+
</div>
|
|
102
|
+
<h2 class="mt-3 text-sm font-bold text-gray-900 dark:text-gray-100">No fuel integrations connected yet</h2>
|
|
103
|
+
<p class="mx-auto mt-1 max-w-2xl text-xs leading-5 text-gray-500 dark:text-gray-300">
|
|
104
|
+
Start by connecting PetroApp or another fuel card/fuel management system. Provider bills import into Fuel Transactions, match to vehicles and trips, and create linked Fuel Reports once matched.
|
|
105
|
+
</p>
|
|
106
|
+
<Button class="mt-4" @icon="plus" @text="Connect Integration" @type="primary" @size="sm" @onClick={{this.createIntegration}} />
|
|
107
|
+
</section>
|
|
108
|
+
{{/if}}
|
|
109
|
+
|
|
110
|
+
<section class="rounded-md border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-700 dark:bg-gray-800">
|
|
111
|
+
<div class="mb-3 flex flex-col gap-1 md:flex-row md:items-end md:justify-between">
|
|
112
|
+
<div>
|
|
113
|
+
<h2 class="text-sm font-bold text-gray-900 dark:text-gray-100">Available Fuel Integrations</h2>
|
|
114
|
+
<p class="text-xs leading-5 text-gray-500 dark:text-gray-300">Connect providers that supply fuel card bills, purchase transactions, vehicle identifiers, station locations, or trip references.</p>
|
|
115
|
+
</div>
|
|
116
|
+
{{#if @providersLoading}}
|
|
117
|
+
<div class="flex items-center gap-2 text-xs text-gray-500 dark:text-gray-300"><Spinner /> Loading integrations</div>
|
|
118
|
+
{{/if}}
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
<div class="grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-4">
|
|
122
|
+
{{#each this.providerCards as |provider|}}
|
|
123
|
+
<button type="button" class="group flex min-h-[166px] flex-col rounded-md border border-gray-200 bg-white p-3 text-left transition hover:border-blue-300 hover:shadow-sm dark:border-gray-700 dark:bg-gray-800 dark:hover:border-blue-400" {{on "click" (fn this.openProvider provider)}}>
|
|
124
|
+
<div class="flex items-start justify-between gap-3">
|
|
125
|
+
<div class="flex min-w-0 items-start gap-3">
|
|
126
|
+
<div class="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-md bg-blue-50 text-blue-600 dark:bg-blue-900/20 dark:text-blue-300">
|
|
127
|
+
<FaIcon @icon={{or provider.icon "gas-pump"}} />
|
|
128
|
+
</div>
|
|
129
|
+
<div class="min-w-0">
|
|
130
|
+
<div class="truncate text-sm font-bold text-gray-900 dark:text-gray-100">{{provider.label}}</div>
|
|
131
|
+
<div class="truncate text-[10px] uppercase text-gray-400 dark:text-gray-500">{{provider.categoryLabel}}</div>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
<Badge @status={{if provider.connected "success" "info"}} @hideStatusDot={{true}}>
|
|
135
|
+
{{if provider.connected "Connected" "Available"}}
|
|
136
|
+
</Badge>
|
|
137
|
+
</div>
|
|
138
|
+
<p class="mt-3 line-clamp-2 text-xs leading-5 text-gray-500 dark:text-gray-300">{{provider.description}}</p>
|
|
139
|
+
<div class="mt-3 flex flex-wrap gap-1">
|
|
140
|
+
{{#each provider.capabilities as |capability|}}
|
|
141
|
+
<span class="rounded bg-gray-100 px-2 py-0.5 text-[11px] text-gray-600 dark:bg-gray-700 dark:text-gray-300">{{capability}}</span>
|
|
142
|
+
{{/each}}
|
|
143
|
+
</div>
|
|
144
|
+
<div class="mt-auto flex items-center justify-between pt-3 text-xs font-semibold text-blue-600 dark:text-blue-300">
|
|
145
|
+
<span>{{if provider.connected "Manage integration" "Connect integration"}}</span>
|
|
146
|
+
<FaIcon @icon="arrow-right" class="transition group-hover:translate-x-0.5" />
|
|
147
|
+
</div>
|
|
148
|
+
</button>
|
|
149
|
+
{{/each}}
|
|
150
|
+
</div>
|
|
151
|
+
</section>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
@@ -0,0 +1,165 @@
|
|
|
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 WARNING_STATUSES = ['error', 'disabled'];
|
|
7
|
+
|
|
8
|
+
export default class FuelIntegrationHubComponent extends Component {
|
|
9
|
+
@service fuelIntegrationActions;
|
|
10
|
+
@service currentUser;
|
|
11
|
+
@tracked table;
|
|
12
|
+
@tracked columns = this.args.columns ?? [];
|
|
13
|
+
|
|
14
|
+
get connections() {
|
|
15
|
+
return Array.from(this.args.connections ?? []);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get providers() {
|
|
19
|
+
return Array.from(this.args.providers ?? []);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get hasConnections() {
|
|
23
|
+
return this.connections.length > 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get connectedProviderKeys() {
|
|
27
|
+
return new Set(this.connections.map((connection) => connection.provider).filter(Boolean));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get providerCards() {
|
|
31
|
+
return this.providers.map((provider) => {
|
|
32
|
+
const connection = this.connections.find((record) => record.provider === provider.key);
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
...provider,
|
|
36
|
+
connection,
|
|
37
|
+
connected: Boolean(connection),
|
|
38
|
+
status: connection?.status ?? 'available',
|
|
39
|
+
categoryLabel: provider.category ?? provider.metadata?.category ?? 'Fuel card integration',
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get activeConnectionCount() {
|
|
45
|
+
return this.connections.filter((connection) => !['disabled', 'archived'].includes(connection.status)).length;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
get unmatchedTransactionsCount() {
|
|
49
|
+
return this.connections.reduce((total, connection) => total + Number(connection.last_sync_state?.summary?.unmatched ?? 0), 0);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
get syncErrorCount() {
|
|
53
|
+
return this.connections.filter((connection) => WARNING_STATUSES.includes(connection.status) || connection.last_error).length;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
get totalLiters() {
|
|
57
|
+
return this.connections.reduce((total, connection) => total + Number(connection.last_sync_state?.summary?.liters ?? 0), 0);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
get totalSpend() {
|
|
61
|
+
return this.connections.reduce((total, connection) => total + Number(connection.last_sync_state?.summary?.amount ?? 0), 0);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
get lastSyncLabel() {
|
|
65
|
+
const syncedAt = this.connections
|
|
66
|
+
.map((connection) => connection.last_synced_at)
|
|
67
|
+
.filter(Boolean)
|
|
68
|
+
.sort()
|
|
69
|
+
.pop();
|
|
70
|
+
|
|
71
|
+
return syncedAt ? new Date(syncedAt).toLocaleString() : 'Not synced';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
get kpiWidgets() {
|
|
75
|
+
return [
|
|
76
|
+
{
|
|
77
|
+
icon: 'gas-pump',
|
|
78
|
+
label: 'Connected integrations',
|
|
79
|
+
value: this.activeConnectionCount,
|
|
80
|
+
help: 'Fuel card and fuel management accounts',
|
|
81
|
+
action: 'Connect integration',
|
|
82
|
+
actionType: 'create',
|
|
83
|
+
accentClass: 'fleetops-connectivity-kpi-accent-blue',
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
icon: 'clock-rotate-left',
|
|
87
|
+
label: 'Last sync',
|
|
88
|
+
value: this.lastSyncLabel,
|
|
89
|
+
help: 'Most recent completed import',
|
|
90
|
+
action: 'Review sync',
|
|
91
|
+
actionType: 'review',
|
|
92
|
+
accentClass: 'fleetops-connectivity-kpi-accent-green',
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
icon: 'triangle-exclamation',
|
|
96
|
+
label: 'Unmatched transactions',
|
|
97
|
+
value: this.unmatchedTransactionsCount,
|
|
98
|
+
help: this.unmatchedTransactionsCount > 0 ? 'Need vehicle or trip matching' : 'No unmatched count',
|
|
99
|
+
action: 'Review matches',
|
|
100
|
+
actionType: 'transactions',
|
|
101
|
+
accentClass: 'fleetops-connectivity-kpi-accent-amber',
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
icon: 'receipt',
|
|
105
|
+
label: 'Imported spend',
|
|
106
|
+
value: this.formatSpend,
|
|
107
|
+
help: `${this.formatLiters} imported`,
|
|
108
|
+
action: 'Open ledger',
|
|
109
|
+
actionType: 'transactions',
|
|
110
|
+
accentClass: 'fleetops-connectivity-kpi-accent-rose',
|
|
111
|
+
},
|
|
112
|
+
];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
get formatSpend() {
|
|
116
|
+
const currency = this.currentUser?.company?.currency || this.currentUser?.currency || 'USD';
|
|
117
|
+
|
|
118
|
+
if (!this.totalSpend) {
|
|
119
|
+
return `${currency} 0`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return `${currency} ${(this.totalSpend / 100).toLocaleString(undefined, { maximumFractionDigits: 2 })}`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
get formatLiters() {
|
|
126
|
+
if (!this.totalLiters) {
|
|
127
|
+
return '0 L';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return `${Number(this.totalLiters).toLocaleString(undefined, { maximumFractionDigits: 1 })} L`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
@action openProvider(provider) {
|
|
134
|
+
if (provider.connection) {
|
|
135
|
+
return this.fuelIntegrationActions.transition.view(provider.connection);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return this.fuelIntegrationActions.transition.create(provider.key);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
@action createIntegration() {
|
|
142
|
+
return this.fuelIntegrationActions.transition.create();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
@action runKpiAction(widget) {
|
|
146
|
+
if (widget.actionType === 'create') {
|
|
147
|
+
return this.fuelIntegrationActions.transition.create();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (widget.actionType === 'transactions') {
|
|
151
|
+
return this.fuelIntegrationActions.transitionTo('management.fuel-transactions.index');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const connection = this.connections[0];
|
|
155
|
+
if (connection) {
|
|
156
|
+
return this.fuelIntegrationActions.transition.view(connection);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return this.fuelIntegrationActions.transition.create();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
@action setupTable(table) {
|
|
163
|
+
this.table = table;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<div class="space-y-4" ...attributes>
|
|
2
|
+
<div class="rounded-md border border-blue-100 bg-blue-50 p-3 text-xs leading-5 text-blue-800 dark:border-blue-900/60 dark:bg-blue-900/10 dark:text-blue-200">
|
|
3
|
+
<div class="font-semibold text-blue-900 dark:text-blue-100">How matching works</div>
|
|
4
|
+
<p class="mt-1">
|
|
5
|
+
Fuel transactions arrive with vehicle identifiers from the provider. Matching checks this list from top to bottom and links the transaction to the first vehicle or order found.
|
|
6
|
+
</p>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<div>
|
|
10
|
+
<div class="mb-2 flex items-center justify-between gap-3">
|
|
11
|
+
<h3 class="text-xs font-bold uppercase text-gray-500 dark:text-gray-400">Priority list</h3>
|
|
12
|
+
{{#if this.hasSelectedFields}}
|
|
13
|
+
<span class="text-[11px] text-gray-400 dark:text-gray-500">{{this.selectedFields.length}} active</span>
|
|
14
|
+
{{/if}}
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
{{#if this.hasSelectedFields}}
|
|
18
|
+
<div class="space-y-2">
|
|
19
|
+
{{#each this.selectedFields as |field|}}
|
|
20
|
+
<div class="rounded-md border border-gray-200 bg-white p-3 text-xs dark:border-gray-700 dark:bg-gray-800" data-test-matching-priority-row={{field.key}}>
|
|
21
|
+
<div class="flex flex-col gap-3 md:flex-row md:items-start md:justify-between">
|
|
22
|
+
<div class="min-w-0">
|
|
23
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
24
|
+
<span class="rounded bg-blue-100 px-2 py-0.5 text-[10px] font-bold uppercase text-blue-700 dark:bg-blue-900/40 dark:text-blue-200" data-test-matching-priority-rank>
|
|
25
|
+
Priority
|
|
26
|
+
{{field.priority}}
|
|
27
|
+
</span>
|
|
28
|
+
<span class="font-semibold text-gray-900 dark:text-gray-100">{{field.label}}</span>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="mt-2 text-gray-700 dark:text-gray-200" data-test-matching-priority-map>{{field.source}} -> {{field.target}}</div>
|
|
31
|
+
<p class="mt-1 text-gray-500 dark:text-gray-300">{{field.description}}</p>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
{{#if @editable}}
|
|
35
|
+
<div class="flex flex-shrink-0 items-center gap-1">
|
|
36
|
+
<Button data-test-matching-move-up={{field.key}} @icon="arrow-up" @size="xs" @helpText="Move earlier" @disabled={{not field.canMoveUp}} @onClick={{fn this.moveField field -1}} />
|
|
37
|
+
<Button data-test-matching-move-down={{field.key}} @icon="arrow-down" @size="xs" @helpText="Move later" @disabled={{not field.canMoveDown}} @onClick={{fn this.moveField field 1}} />
|
|
38
|
+
<Button data-test-matching-remove={{field.key}} @icon="times" @size="xs" @helpText="Remove from matching" @onClick={{fn this.removeField field}} />
|
|
39
|
+
</div>
|
|
40
|
+
{{/if}}
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
{{/each}}
|
|
44
|
+
</div>
|
|
45
|
+
{{else}}
|
|
46
|
+
<div class="rounded-md border border-dashed border-gray-300 bg-white p-5 text-center text-xs leading-5 text-gray-500 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300" data-test-matching-empty-state>
|
|
47
|
+
No automatic identifiers are selected. Imported transactions will stay unmatched until reviewed manually.
|
|
48
|
+
</div>
|
|
49
|
+
{{/if}}
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
{{#if @editable}}
|
|
53
|
+
<div>
|
|
54
|
+
<div class="mb-2 flex items-center justify-between gap-3">
|
|
55
|
+
<h3 class="text-xs font-bold uppercase text-gray-500 dark:text-gray-400">Available identifiers</h3>
|
|
56
|
+
{{#unless this.availableFields.length}}
|
|
57
|
+
<span class="text-[11px] text-gray-400 dark:text-gray-500">All identifiers are active</span>
|
|
58
|
+
{{/unless}}
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
{{#if this.availableFields.length}}
|
|
62
|
+
<div class="grid grid-cols-1 gap-2 md:grid-cols-2">
|
|
63
|
+
{{#each this.availableFields as |field|}}
|
|
64
|
+
<button type="button" class="rounded-md border border-gray-200 bg-white p-3 text-left text-xs transition hover:border-blue-300 hover:bg-blue-50 dark:border-gray-700 dark:bg-gray-800 dark:hover:border-blue-400 dark:hover:bg-blue-900/10" data-test-matching-available={{field.key}} {{on "click" (fn this.addField field)}}>
|
|
65
|
+
<div class="flex items-center justify-between gap-2">
|
|
66
|
+
<span class="font-semibold text-gray-900 dark:text-gray-100">{{field.label}}</span>
|
|
67
|
+
<FaIcon @icon="plus" class="text-blue-600 dark:text-blue-300" />
|
|
68
|
+
</div>
|
|
69
|
+
<div class="mt-2 text-gray-600 dark:text-gray-300">{{field.source}} -> {{field.target}}</div>
|
|
70
|
+
</button>
|
|
71
|
+
{{/each}}
|
|
72
|
+
</div>
|
|
73
|
+
{{/if}}
|
|
74
|
+
</div>
|
|
75
|
+
{{/if}}
|
|
76
|
+
</div>
|