Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
246 changes: 4 additions & 242 deletions addon/components/map/leaflet-live-map.hbs

Large diffs are not rendered by default.

57 changes: 57 additions & 0 deletions addon/components/map/marker-card/driver.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{{!
The driver card the fleet-ops live map shows in a marker's popup and hover tooltip.
Shared with the dashboard's Live Fleet widget so both look the same. @driver is a driver
model or its index resource payload (the same attributes, plain JSON).
@variant: "popup" (default, a dark panel) or "tooltip" (inherits the tooltip's panel).
}}
<div class="w-[280px] max-w-[calc(100vw-2rem)] {{unless (eq @variant 'tooltip') 'rounded-lg bg-gray-900 shadow-lg p-1.5 text-white'}}" ...attributes>
<div class="flex items-center gap-1.5 text-xs font-semibold mb-1.5">
<span class="inline-block w-2 h-2 rounded-full {{if @driver.online 'bg-green-400' 'bg-red-500'}}"></span>
<span>{{@driver.name}}</span>
<div
class="status-badge
{{if @driver.status (dasherize @driver.status) 'default'}}-status-badge status-badge-{{if @driver.status (dasherize @driver.status) 'default'}}
shadow-none ml-auto"
>
<span class="rounded-full text-[9px] px-1.5 py-0.5">{{n-a @driver.meta.status_label (smart-humanize @driver.status)}}</span>
</div>
</div>
<div class="grid grid-cols-2 gap-1">
<div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
<div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">ID</div>
<div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a @driver.public_id @driver.id}}</div>
</div>
<div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
<div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Phone</div>
<div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a @driver.phone}}</div>
</div>
<div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
<div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Vehicle</div>
<div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a @driver.vehicle_name}}</div>
</div>
<div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
<div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Email</div>
<div class="text-[11px] text-white leading-tight truncate min-w-0 w-full">{{n-a @driver.email}}</div>
</div>
<div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
<div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Order</div>
<div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a @driver.meta.current_order_reference}}</div>
</div>
<div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
<div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Speed</div>
<div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a @driver.meta.speed_label}}</div>
</div>
<div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
<div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Heading</div>
<div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a @driver.meta.heading_label}}</div>
</div>
<div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
<div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Location</div>
<div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a
@driver.meta.location_coordinates
(point-coordinates @driver.location)
}}</div>
</div>
</div>
</div>

3 changes: 3 additions & 0 deletions addon/components/map/marker-card/driver.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import templateOnly from '@ember/component/template-only';

export default templateOnly();
78 changes: 78 additions & 0 deletions addon/components/map/marker-card/vehicle.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
{{!
The vehicle card the fleet-ops live map shows in a marker's popup and hover tooltip.
Shared with the dashboard's Live Fleet widget so both look the same. @vehicle is a vehicle
model or its index resource payload (the same attributes, plain JSON).
@variant: "popup" (default, a dark panel) or "tooltip" (inherits the tooltip's panel).
}}
<div class="w-[280px] max-w-[calc(100vw-2rem)] {{unless (eq @variant 'tooltip') 'rounded-lg bg-gray-900 shadow-lg p-1.5 text-white'}}" ...attributes>
<div class="flex items-center gap-1.5 text-xs font-semibold mb-1.5">
<span class="inline-block w-2 h-2 rounded-full {{if @vehicle.online 'bg-green-400' 'bg-red-500'}}"></span>
<span>{{or @vehicle.displayName @vehicle.display_name @vehicle.name}}</span>
<div
class="status-badge
{{if @vehicle.status (dasherize @vehicle.status) 'default'}}-status-badge status-badge-{{if @vehicle.status (dasherize @vehicle.status) 'default'}}
shadow-none ml-auto"
>
<span class="rounded-full text-[9px] px-1.5 py-0.5">{{n-a @vehicle.meta.status_label}}</span>
</div>
</div>
<div class="grid grid-cols-2 gap-1">
<div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
<div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Vehicle #</div>
<div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a
@vehicle.internal_id
@vehicle.public_id
@vehicle.plate_number
@vehicle.serial_number
@vehicle.vin
}}</div>
</div>
<div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
<div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Driver</div>
<div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a @vehicle.driver_name}}</div>
</div>
<div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
<div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">{{t "resource.trailers"}}</div>
<div class="text-[11px] text-white leading-tight whitespace-normal break-words">
{{#each @vehicle.trailers as |trailer|}}
<div class="flex items-center gap-1 min-w-0">
<span class="inline-block w-1.5 h-1.5 rounded-full shrink-0 {{if trailer.online 'bg-green-400' 'bg-gray-500'}}"></span>
<span class="truncate">{{or trailer.displayName trailer.display_name trailer.name trailer.public_id}}</span>
</div>
{{else}}
-
{{/each}}
</div>
</div>
<div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
<div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">{{t "resource.devices"}}</div>
<div class="text-[11px] text-white leading-tight whitespace-normal break-words">
{{#each @vehicle.devices as |device|}}
<div class="flex items-center gap-1 min-w-0">
<span class="inline-block w-1.5 h-1.5 rounded-full shrink-0 {{if device.online 'bg-green-400' 'bg-gray-500'}}"></span>
<span class="truncate">{{or device.displayName device.display_name device.name device.device_id device.public_id}}</span>
</div>
{{else}}
-
{{/each}}
</div>
</div>
<div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
<div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Order</div>
<div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a @vehicle.meta.current_order_reference}}</div>
</div>
<div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
<div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Speed</div>
<div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a @vehicle.meta.speed_label}}</div>
</div>
<div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
<div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Heading</div>
<div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a @vehicle.meta.heading_label}}</div>
</div>
<div class="rounded bg-gray-900 shadow-md px-1.5 py-1 min-w-0">
<div class="text-[9px] font-semibold uppercase text-gray-400 leading-none">Location</div>
<div class="text-[11px] text-white leading-tight whitespace-normal break-words">{{n-a @vehicle.meta.location_coordinates}}</div>
</div>
</div>
</div>

3 changes: 3 additions & 0 deletions addon/components/map/marker-card/vehicle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import templateOnly from '@ember/component/template-only';

export default templateOnly();
48 changes: 8 additions & 40 deletions addon/components/widget/live-fleet.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@

{{#if this.load.isIdle}}
{{! Drivers — fleetops' rotating tracking-marker matches the operational
live-map's look and animates on heading changes. }}
live-map's look and animates on heading changes. The popup and tooltip
are the live map's own cards, fed from each marker's index-resource card. }}
{{#each this.drivers as |driver|}}
<layers.tracking-marker
@id={{driver.uuid}}
Expand All @@ -39,28 +40,11 @@
@draggable={{false}}
as |marker|
>
<marker.popup @maxWidth="240" @minWidth="180">
<div class="flex flex-row gap-2">
<div class="w-9 h-9 flex-shrink-0">
<img src={{or driver.avatar_url driver.vehicle_avatar}} alt={{driver.name}} class="rounded-md w-9 h-9 object-cover shadow-sm" />
</div>
<div class="flex-1 min-w-0">
<div class="text-xs font-semibold truncate">{{or driver.name "Driver"}}</div>
<div class="text-[11px]">
{{#if driver.current_order_uuid}}
<span class="text-blue-500"><FaIcon @icon="bolt" class="mr-1" />On order</span>
{{else if driver.online}}
<span class="text-green-500"><FaIcon @icon="circle" @size="2xs" class="mr-1" />Online</span>
{{else}}
<span class="text-gray-400"><FaIcon @icon="circle" @size="2xs" class="mr-1" />Offline</span>
{{/if}}
</div>
<div class="text-[10px] text-gray-400 mt-0.5 truncate">{{driver.lat}}, {{driver.lng}}</div>
</div>
</div>
<marker.popup @maxWidth="500" @minWidth="225">
<Map::MarkerCard::Driver @driver={{or driver.card driver}} />
</marker.popup>
<marker.tooltip @permanent={{false}} @sticky={{true}} @direction="top">
<div class="text-[11px] font-semibold">{{or driver.name "Driver"}}</div>
<Map::MarkerCard::Driver @driver={{or driver.card driver}} @variant="tooltip" />
</marker.tooltip>
</layers.tracking-marker>
{{/each}}
Expand All @@ -77,27 +61,11 @@
@draggable={{false}}
as |marker|
>
<marker.popup @maxWidth="240" @minWidth="180">
<div class="flex flex-row gap-2">
<div class="w-9 h-9 flex-shrink-0">
<img src={{or vehicle.photo_url vehicle.avatar_url}} alt={{vehicle.name}} class="rounded-md w-9 h-9 object-cover shadow-sm" />
</div>
<div class="flex-1 min-w-0">
<div class="text-xs font-semibold truncate">{{or vehicle.name "Vehicle"}}</div>
{{#if vehicle.plate_number}}
<div class="text-[10px] text-gray-500 truncate">{{vehicle.plate_number}}</div>
{{/if}}
{{#if vehicle.driver_name}}
<div class="text-[11px] text-gray-600 dark:text-gray-300 truncate">
<FaIcon @icon="id-card" class="mr-1 text-gray-400" />{{vehicle.driver_name}}
</div>
{{/if}}
<div class="text-[10px] text-gray-400 mt-0.5 truncate">{{vehicle.lat}}, {{vehicle.lng}}</div>
</div>
</div>
<marker.popup @maxWidth="500" @minWidth="225">
<Map::MarkerCard::Vehicle @vehicle={{or vehicle.card vehicle}} />
</marker.popup>
<marker.tooltip @permanent={{false}} @sticky={{true}} @direction="top">
<div class="text-[11px] font-semibold">{{or vehicle.name "Vehicle"}}</div>
<Map::MarkerCard::Vehicle @vehicle={{or vehicle.card vehicle}} @variant="tooltip" />
</marker.tooltip>
</layers.tracking-marker>
{{/each}}
Expand Down
27 changes: 21 additions & 6 deletions addon/extension.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,14 @@ export default {
},

registerWidgets(widgetService) {
// `order` places a default widget on the default dashboard, which mixes widgets from
// every extension; the grid fills rows first-fit, lowest order first (widgets without
// one follow). The default layout is:
// 10-40 KPI row: Radar, Revenue (ledger, 20), Active Orders, Drivers Online
// 41-44 Ledger KPI row: Expenses, Net Income, Outstanding AR, Overdue AR
// 50 Live Fleet Map, full width
// 60-80 Revenue Trend, Top Drivers, Maintenance Overview, a third of the width each
// 150-170 Ledger Recent Financial Activity + Cash Flow Summary | console Blog + GitHub
const widgets = [
// Legacy monolithic 13-tile widget — kept registered for one release as
// users have it pinned to existing dashboards. The new KPI tile widgets
Expand All @@ -186,7 +194,7 @@ export default {
component: new ExtensionComponent('@fleetbase/fleetops-engine', 'widget/kpi-earnings'),
grid_options: { w: 3, h: 4, minW: 3, minH: 4 },
category: 'KPI Tiles',
default: true,
default: false,
}),
new Widget({
id: 'fleet-ops-kpi-aov-widget',
Expand All @@ -196,7 +204,7 @@ export default {
component: new ExtensionComponent('@fleetbase/fleetops-engine', 'widget/kpi-aov'),
grid_options: { w: 3, h: 4, minW: 3, minH: 4 },
category: 'KPI Tiles',
default: true,
default: false,
}),
new Widget({
id: 'fleet-ops-kpi-distance-widget',
Expand All @@ -216,6 +224,7 @@ export default {
component: new ExtensionComponent('@fleetbase/fleetops-engine', 'widget/kpi-active-orders'),
grid_options: { w: 3, h: 4, minW: 3, minH: 4 },
category: 'KPI Tiles',
order: 30,
default: true,
}),
new Widget({
Expand All @@ -226,6 +235,7 @@ export default {
component: new ExtensionComponent('@fleetbase/fleetops-engine', 'widget/kpi-drivers-online'),
grid_options: { w: 3, h: 4, minW: 3, minH: 4 },
category: 'KPI Tiles',
order: 40,
default: true,
}),
new Widget({
Expand All @@ -236,6 +246,7 @@ export default {
component: new ExtensionComponent('@fleetbase/fleetops-engine', 'widget/radar'),
grid_options: { w: 3, h: 4, minW: 3, minH: 4 },
category: 'KPI Tiles',
order: 10,
default: true,
}),
new Widget({
Expand Down Expand Up @@ -266,8 +277,9 @@ export default {
description: 'Real-time driver positions and active routes.',
icon: 'map-location-dot',
component: new ExtensionComponent('@fleetbase/fleetops-engine', 'widget/live-fleet'),
grid_options: { w: 8, h: 11, minW: 8, minH: 8 },
grid_options: { w: 12, h: 12, minW: 8, minH: 8 },
category: 'Maps',
order: 50,
default: true,
}),
new Widget({
Expand All @@ -276,8 +288,9 @@ export default {
description: 'Revenue over time with period comparison.',
icon: 'chart-line',
component: new ExtensionComponent('@fleetbase/fleetops-engine', 'widget/revenue-trend'),
grid_options: { w: 4, h: 11, minW: 4, minH: 8 },
grid_options: { w: 4, h: 9, minW: 4, minH: 7 },
category: 'Analytics',
order: 60,
default: true,
}),
new Widget({
Expand Down Expand Up @@ -306,8 +319,9 @@ export default {
description: 'Driver leaderboard sortable by orders, on-time %, or distance.',
icon: 'medal',
component: new ExtensionComponent('@fleetbase/fleetops-engine', 'widget/top-drivers'),
grid_options: { w: 6, h: 6, minW: 5, minH: 5 },
grid_options: { w: 4, h: 9, minW: 4, minH: 6 },
category: 'Analytics',
order: 70,
default: true,
}),
new Widget({
Expand Down Expand Up @@ -346,8 +360,9 @@ export default {
description: 'Overdue, scheduled, and YTD maintenance spend.',
icon: 'wrench',
component: new ExtensionComponent('@fleetbase/fleetops-engine', 'widget/maintenance-overview'),
grid_options: { w: 6, h: 6, minW: 5, minH: 5 },
grid_options: { w: 4, h: 9, minW: 4, minH: 6 },
category: 'Analytics',
order: 80,
default: true,
}),
new Widget({
Expand Down
1 change: 1 addition & 0 deletions app/components/map/marker-card/driver.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from '@fleetbase/fleetops-engine/components/map/marker-card/driver';
1 change: 1 addition & 0 deletions app/components/map/marker-card/vehicle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from '@fleetbase/fleetops-engine/components/map/marker-card/vehicle';
Loading
Loading