@burdenoff/microfe-movethewheels 2026.830.5 → 2026.918.1
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/dist/components/AssignmentSuggestions.js +135 -0
- package/dist/components/FleetAlertsPanel.js +114 -0
- package/dist/components/VehicleServiceCard.js +176 -0
- package/dist/hooks/useFleet.js +4 -3
- package/dist/hooks/useFleetIntelligence.js +91 -0
- package/dist/movethewheels/components/AssignmentSuggestions.d.ts +17 -0
- package/dist/movethewheels/components/FleetAlertsPanel.d.ts +14 -0
- package/dist/movethewheels/components/VehicleServiceCard.d.ts +10 -0
- package/dist/movethewheels/hooks/useFleetIntelligence.d.ts +64 -0
- package/dist/movethewheels/types/index.d.ts +2 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/sparkles.js +26 -0
- package/dist/pages/FleetPage.js +156 -146
- package/dist/pages/RouteDetailsPage.js +202 -184
- package/dist/pages/VehicleDetailsPage.js +149 -131
- package/dist/utils/graphqlClient.js +14 -5
- package/package.json +1 -1
- package/src/movethewheels/components/AssignmentSuggestions.tsx +234 -0
- package/src/movethewheels/components/FleetAlertsPanel.tsx +148 -0
- package/src/movethewheels/components/VehicleServiceCard.tsx +220 -0
- package/src/movethewheels/hooks/useFleet.ts +12 -2
- package/src/movethewheels/hooks/useFleetIntelligence.ts +226 -0
- package/src/movethewheels/pages/FleetPage.tsx +21 -0
- package/src/movethewheels/pages/RouteDetailsPage.tsx +33 -1
- package/src/movethewheels/pages/VehicleDetailsPage.tsx +27 -0
- package/src/movethewheels/types/index.ts +2 -0
- package/src/movethewheels/utils/graphqlClient.ts +20 -1
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { Sparkles as e } from "../node_modules/lucide-react/dist/esm/icons/sparkles.js";
|
|
2
|
+
import { Badge as t, Button as n, Card as r, CardContent as i, CardHeader as a, CardTitle as o, Skeleton as s } from "./ui.js";
|
|
3
|
+
import { FormError as c } from "./FormError.js";
|
|
4
|
+
import { useState as l } from "react";
|
|
5
|
+
import { Fragment as u, jsx as d, jsxs as f } from "react/jsx-runtime";
|
|
6
|
+
//#region src/movethewheels/components/AssignmentSuggestions.tsx
|
|
7
|
+
function p({ id: e, label: r, reasons: i, isBest: a, isAssigned: o, isPending: s, disabled: c, onAssign: l, t: u }) {
|
|
8
|
+
return /* @__PURE__ */ f("li", {
|
|
9
|
+
className: "flex items-start justify-between gap-3 py-3",
|
|
10
|
+
"data-suggestion-id": e,
|
|
11
|
+
children: [/* @__PURE__ */ f("div", {
|
|
12
|
+
className: "flex-1 min-w-0 space-y-1",
|
|
13
|
+
children: [/* @__PURE__ */ f("div", {
|
|
14
|
+
className: "flex flex-wrap items-center gap-x-2 gap-y-1",
|
|
15
|
+
children: [/* @__PURE__ */ d("span", {
|
|
16
|
+
className: "text-sm font-medium break-words",
|
|
17
|
+
children: r
|
|
18
|
+
}), a && /* @__PURE__ */ d(t, {
|
|
19
|
+
variant: "secondary",
|
|
20
|
+
children: u("movethewheels.routes.suggest.best", "Best match")
|
|
21
|
+
})]
|
|
22
|
+
}), /* @__PURE__ */ d("ul", {
|
|
23
|
+
className: "space-y-0.5",
|
|
24
|
+
children: i.map((e) => /* @__PURE__ */ d("li", {
|
|
25
|
+
className: "text-xs text-text-secondary",
|
|
26
|
+
children: e
|
|
27
|
+
}, e))
|
|
28
|
+
})]
|
|
29
|
+
}), /* @__PURE__ */ d(n, {
|
|
30
|
+
size: "sm",
|
|
31
|
+
className: "shrink-0",
|
|
32
|
+
variant: o ? "ghost" : "outline",
|
|
33
|
+
disabled: c || o,
|
|
34
|
+
onClick: l,
|
|
35
|
+
children: o ? u("movethewheels.routes.suggest.assigned", "Assigned") : s ? u("movethewheels.routes.suggest.assigning", "Assigning…") : u("movethewheels.routes.suggest.assign", "Assign")
|
|
36
|
+
})]
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
function m({ suggestions: t, isLoading: m, error: h, onRetry: g, assignedVehicleId: _, assignedDriverId: v, onAssign: y, t: b }) {
|
|
40
|
+
let [x, S] = l(null), [C, w] = l(null), T = b("movethewheels.routes.suggest.title", "Suggested assignment"), E = async (e, t) => {
|
|
41
|
+
S(e), w(null);
|
|
42
|
+
try {
|
|
43
|
+
await y(t);
|
|
44
|
+
} catch (e) {
|
|
45
|
+
w(e instanceof Error ? e.message : "Could not assign.");
|
|
46
|
+
} finally {
|
|
47
|
+
S(null);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
if (h && !m) return /* @__PURE__ */ d(r, {
|
|
51
|
+
className: "border-status-error-border",
|
|
52
|
+
children: /* @__PURE__ */ f(i, {
|
|
53
|
+
className: "p-6 text-center space-y-3",
|
|
54
|
+
children: [
|
|
55
|
+
/* @__PURE__ */ d("p", {
|
|
56
|
+
className: "text-status-error-text font-medium",
|
|
57
|
+
children: b("movethewheels.routes.suggest.error", "Couldn't load suggestions")
|
|
58
|
+
}),
|
|
59
|
+
/* @__PURE__ */ d("p", {
|
|
60
|
+
className: "text-sm text-text-secondary",
|
|
61
|
+
children: h.message
|
|
62
|
+
}),
|
|
63
|
+
/* @__PURE__ */ d(n, {
|
|
64
|
+
variant: "outline",
|
|
65
|
+
onClick: g,
|
|
66
|
+
children: b("movethewheels.fleet.error.retry", "Retry")
|
|
67
|
+
})
|
|
68
|
+
]
|
|
69
|
+
})
|
|
70
|
+
});
|
|
71
|
+
if (m) return /* @__PURE__ */ f(r, { children: [/* @__PURE__ */ d(a, { children: /* @__PURE__ */ d(o, { children: T }) }), /* @__PURE__ */ f(i, {
|
|
72
|
+
className: "space-y-3",
|
|
73
|
+
children: [/* @__PURE__ */ d(s, { className: "h-14 w-full" }), /* @__PURE__ */ d(s, { className: "h-14 w-full" })]
|
|
74
|
+
})] });
|
|
75
|
+
let { vehicles: D, drivers: O } = t, k = D.length === 0 && O.length === 0;
|
|
76
|
+
return /* @__PURE__ */ f(r, { children: [/* @__PURE__ */ d(a, { children: /* @__PURE__ */ f(o, {
|
|
77
|
+
className: "flex items-center gap-2",
|
|
78
|
+
children: [/* @__PURE__ */ d(e, {
|
|
79
|
+
size: 18,
|
|
80
|
+
"aria-hidden": "true"
|
|
81
|
+
}), T]
|
|
82
|
+
}) }), /* @__PURE__ */ f(i, {
|
|
83
|
+
className: "space-y-4",
|
|
84
|
+
children: [/* @__PURE__ */ d(c, { message: C }), k ? /* @__PURE__ */ d("p", {
|
|
85
|
+
className: "text-sm text-text-secondary",
|
|
86
|
+
children: b("movethewheels.routes.suggest.none", "No vehicle or driver is free right now. Vehicles must be in service and drivers must hold a valid licence and not be on another live route.")
|
|
87
|
+
}) : /* @__PURE__ */ f(u, { children: [/* @__PURE__ */ f("section", {
|
|
88
|
+
"aria-label": b("movethewheels.routes.suggest.vehicles", "Vehicles"),
|
|
89
|
+
children: [/* @__PURE__ */ d("h4", {
|
|
90
|
+
className: "text-xs font-semibold uppercase tracking-wide text-text-secondary",
|
|
91
|
+
children: b("movethewheels.routes.suggest.vehicles", "Vehicles")
|
|
92
|
+
}), D.length === 0 ? /* @__PURE__ */ d("p", {
|
|
93
|
+
className: "py-3 text-sm text-text-secondary",
|
|
94
|
+
children: b("movethewheels.routes.suggest.noVehicles", "No vehicle is free right now.")
|
|
95
|
+
}) : /* @__PURE__ */ d("ul", {
|
|
96
|
+
className: "divide-y",
|
|
97
|
+
children: D.map((e, t) => /* @__PURE__ */ d(p, {
|
|
98
|
+
id: e.vehicle.id,
|
|
99
|
+
label: [e.vehicle.vehicleNumber, [e.vehicle.make, e.vehicle.model].filter(Boolean).join(" ")].filter(Boolean).join(" — "),
|
|
100
|
+
reasons: e.reasons,
|
|
101
|
+
isBest: t === 0,
|
|
102
|
+
isAssigned: _ === e.vehicle.id,
|
|
103
|
+
isPending: x === e.vehicle.id,
|
|
104
|
+
disabled: x !== null,
|
|
105
|
+
onAssign: () => E(e.vehicle.id, { vehicleId: e.vehicle.id }),
|
|
106
|
+
t: b
|
|
107
|
+
}, e.vehicle.id))
|
|
108
|
+
})]
|
|
109
|
+
}), /* @__PURE__ */ f("section", {
|
|
110
|
+
"aria-label": b("movethewheels.routes.suggest.drivers", "Drivers"),
|
|
111
|
+
children: [/* @__PURE__ */ d("h4", {
|
|
112
|
+
className: "text-xs font-semibold uppercase tracking-wide text-text-secondary",
|
|
113
|
+
children: b("movethewheels.routes.suggest.drivers", "Drivers")
|
|
114
|
+
}), O.length === 0 ? /* @__PURE__ */ d("p", {
|
|
115
|
+
className: "py-3 text-sm text-text-secondary",
|
|
116
|
+
children: b("movethewheels.routes.suggest.noDrivers", "No driver is free right now.")
|
|
117
|
+
}) : /* @__PURE__ */ d("ul", {
|
|
118
|
+
className: "divide-y",
|
|
119
|
+
children: O.map((e, t) => /* @__PURE__ */ d(p, {
|
|
120
|
+
id: e.driver.id,
|
|
121
|
+
label: `${e.driver.firstName} ${e.driver.lastName}`.trim(),
|
|
122
|
+
reasons: e.reasons,
|
|
123
|
+
isBest: t === 0,
|
|
124
|
+
isAssigned: v === e.driver.id,
|
|
125
|
+
isPending: x === e.driver.id,
|
|
126
|
+
disabled: x !== null,
|
|
127
|
+
onAssign: () => E(e.driver.id, { driverId: e.driver.id }),
|
|
128
|
+
t: b
|
|
129
|
+
}, e.driver.id))
|
|
130
|
+
})]
|
|
131
|
+
})] })]
|
|
132
|
+
})] });
|
|
133
|
+
}
|
|
134
|
+
//#endregion
|
|
135
|
+
export { m as AssignmentSuggestions };
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { ChevronRight as e } from "../node_modules/lucide-react/dist/esm/icons/chevron-right.js";
|
|
2
|
+
import { CircleCheckBig as t } from "../node_modules/lucide-react/dist/esm/icons/circle-check-big.js";
|
|
3
|
+
import { TriangleAlert as n } from "../node_modules/lucide-react/dist/esm/icons/triangle-alert.js";
|
|
4
|
+
import { Button as r, Card as i, CardContent as a, CardHeader as o, CardTitle as s, Skeleton as c } from "./ui.js";
|
|
5
|
+
import { StatusBadge as l } from "./StatusBadge.js";
|
|
6
|
+
import { jsx as u, jsxs as d } from "react/jsx-runtime";
|
|
7
|
+
//#region src/movethewheels/components/FleetAlertsPanel.tsx
|
|
8
|
+
var f = {
|
|
9
|
+
CRITICAL: {
|
|
10
|
+
bg: "bg-status-error-bg-subtle",
|
|
11
|
+
text: "text-status-error-text"
|
|
12
|
+
},
|
|
13
|
+
WARNING: {
|
|
14
|
+
bg: "bg-status-warning-bg-subtle",
|
|
15
|
+
text: "text-status-warning-text"
|
|
16
|
+
}
|
|
17
|
+
}, p = (e, t) => `${e} ${t}${e === 1 ? "" : "s"}`;
|
|
18
|
+
function m(e) {
|
|
19
|
+
let t = [], { daysUntilDue: n, kmUntilDue: r } = e;
|
|
20
|
+
if (n !== null && (n < 0 ? t.push(`${p(-n, "day")} overdue`) : n === 0 ? t.push("due today") : t.push(`${p(n, "day")} left`)), r !== null) {
|
|
21
|
+
let e = Math.round(Math.abs(r));
|
|
22
|
+
t.push(r < 0 ? `${e} km overdue` : `${e} km left`);
|
|
23
|
+
}
|
|
24
|
+
return t.join(" · ");
|
|
25
|
+
}
|
|
26
|
+
function h({ alerts: p, isLoading: h, error: g, onRetry: _, onOpen: v, t: y }) {
|
|
27
|
+
let b = y("movethewheels.fleet.alerts.title", "Needs attention");
|
|
28
|
+
return g && !h ? /* @__PURE__ */ u(i, {
|
|
29
|
+
className: "border-status-error-border",
|
|
30
|
+
children: /* @__PURE__ */ d(a, {
|
|
31
|
+
className: "p-6 text-center space-y-3",
|
|
32
|
+
children: [
|
|
33
|
+
/* @__PURE__ */ u("p", {
|
|
34
|
+
className: "text-status-error-text font-medium",
|
|
35
|
+
children: y("movethewheels.fleet.alerts.error", "Couldn't load fleet alerts")
|
|
36
|
+
}),
|
|
37
|
+
/* @__PURE__ */ u("p", {
|
|
38
|
+
className: "text-sm text-text-secondary",
|
|
39
|
+
children: g.message
|
|
40
|
+
}),
|
|
41
|
+
/* @__PURE__ */ u(r, {
|
|
42
|
+
variant: "outline",
|
|
43
|
+
onClick: _,
|
|
44
|
+
children: y("movethewheels.fleet.error.retry", "Retry")
|
|
45
|
+
})
|
|
46
|
+
]
|
|
47
|
+
})
|
|
48
|
+
}) : h ? /* @__PURE__ */ d(i, { children: [/* @__PURE__ */ u(o, { children: /* @__PURE__ */ u(s, { children: b }) }), /* @__PURE__ */ d(a, {
|
|
49
|
+
className: "space-y-3",
|
|
50
|
+
children: [/* @__PURE__ */ u(c, { className: "h-12 w-full" }), /* @__PURE__ */ u(c, { className: "h-12 w-full" })]
|
|
51
|
+
})] }) : p.length === 0 ? /* @__PURE__ */ d(i, { children: [/* @__PURE__ */ u(o, { children: /* @__PURE__ */ d(s, {
|
|
52
|
+
className: "flex items-center gap-2",
|
|
53
|
+
children: [/* @__PURE__ */ u(t, {
|
|
54
|
+
size: 18,
|
|
55
|
+
"aria-hidden": "true",
|
|
56
|
+
className: "text-status-success-text"
|
|
57
|
+
}), b]
|
|
58
|
+
}) }), /* @__PURE__ */ u(a, { children: /* @__PURE__ */ u("p", {
|
|
59
|
+
className: "text-sm text-text-secondary",
|
|
60
|
+
children: y("movethewheels.fleet.alerts.allClear", "All clear — every licence is valid and no vehicle is due for service.")
|
|
61
|
+
}) })] }) : /* @__PURE__ */ d(i, { children: [/* @__PURE__ */ u(o, { children: /* @__PURE__ */ d(s, {
|
|
62
|
+
className: "flex items-center gap-2",
|
|
63
|
+
children: [
|
|
64
|
+
/* @__PURE__ */ u(n, {
|
|
65
|
+
size: 18,
|
|
66
|
+
"aria-hidden": "true",
|
|
67
|
+
className: "text-status-warning-text"
|
|
68
|
+
}),
|
|
69
|
+
b,
|
|
70
|
+
" (",
|
|
71
|
+
p.length,
|
|
72
|
+
")"
|
|
73
|
+
]
|
|
74
|
+
}) }), /* @__PURE__ */ u(a, {
|
|
75
|
+
className: "p-0",
|
|
76
|
+
children: /* @__PURE__ */ u("ul", {
|
|
77
|
+
className: "divide-y",
|
|
78
|
+
"aria-label": b,
|
|
79
|
+
children: p.map((t) => {
|
|
80
|
+
let n = m(t);
|
|
81
|
+
return /* @__PURE__ */ u("li", { children: /* @__PURE__ */ d("button", {
|
|
82
|
+
type: "button",
|
|
83
|
+
onClick: () => v(t),
|
|
84
|
+
className: "flex w-full items-center gap-3 px-6 py-3 text-left hover:bg-bg-sunken/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-action-primary-bg",
|
|
85
|
+
children: [
|
|
86
|
+
/* @__PURE__ */ u(l, {
|
|
87
|
+
status: t.severity === "CRITICAL" ? "Critical" : "Warning",
|
|
88
|
+
customColors: f[t.severity],
|
|
89
|
+
size: "sm",
|
|
90
|
+
showDot: !1
|
|
91
|
+
}),
|
|
92
|
+
/* @__PURE__ */ d("span", {
|
|
93
|
+
className: "flex-1 min-w-0",
|
|
94
|
+
children: [/* @__PURE__ */ u("span", {
|
|
95
|
+
className: "block text-sm font-medium truncate",
|
|
96
|
+
children: t.message
|
|
97
|
+
}), n && /* @__PURE__ */ u("span", {
|
|
98
|
+
className: "block text-xs text-text-secondary",
|
|
99
|
+
children: n
|
|
100
|
+
})]
|
|
101
|
+
}),
|
|
102
|
+
/* @__PURE__ */ u(e, {
|
|
103
|
+
size: 16,
|
|
104
|
+
"aria-hidden": "true",
|
|
105
|
+
className: "shrink-0"
|
|
106
|
+
})
|
|
107
|
+
]
|
|
108
|
+
}) }, t.id);
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
})] });
|
|
112
|
+
}
|
|
113
|
+
//#endregion
|
|
114
|
+
export { h as FleetAlertsPanel };
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { Wrench as e } from "../node_modules/lucide-react/dist/esm/icons/wrench.js";
|
|
2
|
+
import { Button as t, Card as n, CardContent as r, CardHeader as i, CardTitle as a, Input as o, Label as s } from "./ui.js";
|
|
3
|
+
import { FormError as c } from "./FormError.js";
|
|
4
|
+
import { useState as l } from "react";
|
|
5
|
+
import { jsx as u, jsxs as d } from "react/jsx-runtime";
|
|
6
|
+
//#region src/movethewheels/components/VehicleServiceCard.tsx
|
|
7
|
+
var f = 90, p = 1e4, m = (e) => e ? e.toLocaleDateString(void 0, {
|
|
8
|
+
year: "numeric",
|
|
9
|
+
month: "short",
|
|
10
|
+
day: "numeric"
|
|
11
|
+
}) : null, h = (e) => e.toISOString().slice(0, 10), g = (e) => {
|
|
12
|
+
if (e.trim() === "") return;
|
|
13
|
+
let t = Number(e);
|
|
14
|
+
return Number.isFinite(t) && t >= 0 ? t : NaN;
|
|
15
|
+
};
|
|
16
|
+
function _({ vehicle: _, onRecord: v, t: y }) {
|
|
17
|
+
let [b, x] = l(!1), [S, C] = l(!1), [w, T] = l(null), [E, D] = l({
|
|
18
|
+
serviceDate: h(/* @__PURE__ */ new Date()),
|
|
19
|
+
mileage: _.mileage === void 0 ? "" : String(_.mileage),
|
|
20
|
+
intervalDays: String(f),
|
|
21
|
+
intervalKm: String(p)
|
|
22
|
+
}), O = m(_.lastMaintenance), k = m(_.nextMaintenance), A = _.nextMaintenanceMileage, j = k !== null || A !== void 0;
|
|
23
|
+
return /* @__PURE__ */ d(n, { children: [/* @__PURE__ */ u(i, { children: /* @__PURE__ */ d(a, {
|
|
24
|
+
className: "flex items-center gap-2",
|
|
25
|
+
children: [/* @__PURE__ */ u(e, {
|
|
26
|
+
size: 18,
|
|
27
|
+
"aria-hidden": "true"
|
|
28
|
+
}), y("movethewheels.vehicles.service.title", "Service schedule")]
|
|
29
|
+
}) }), /* @__PURE__ */ d(r, {
|
|
30
|
+
className: "space-y-4",
|
|
31
|
+
children: [/* @__PURE__ */ d("dl", {
|
|
32
|
+
className: "grid grid-cols-2 gap-4 text-sm",
|
|
33
|
+
children: [/* @__PURE__ */ d("div", { children: [/* @__PURE__ */ u("dt", {
|
|
34
|
+
className: "text-text-secondary",
|
|
35
|
+
children: y("movethewheels.vehicles.service.last", "Last service")
|
|
36
|
+
}), /* @__PURE__ */ u("dd", {
|
|
37
|
+
className: "font-medium",
|
|
38
|
+
children: O ?? y("movethewheels.vehicles.service.none", "Not recorded")
|
|
39
|
+
})] }), /* @__PURE__ */ d("div", { children: [/* @__PURE__ */ u("dt", {
|
|
40
|
+
className: "text-text-secondary",
|
|
41
|
+
children: y("movethewheels.vehicles.service.next", "Next service due")
|
|
42
|
+
}), /* @__PURE__ */ u("dd", {
|
|
43
|
+
className: "font-medium",
|
|
44
|
+
children: j ? [k, A === void 0 ? null : `${A.toLocaleString()} km`].filter(Boolean).join(" / ") : y("movethewheels.vehicles.service.noSchedule", "No schedule set")
|
|
45
|
+
})] })]
|
|
46
|
+
}), b ? /* @__PURE__ */ d("form", {
|
|
47
|
+
onSubmit: async (e) => {
|
|
48
|
+
e.preventDefault();
|
|
49
|
+
let t = g(E.mileage), n = g(E.intervalDays), r = g(E.intervalKm);
|
|
50
|
+
if ([
|
|
51
|
+
t,
|
|
52
|
+
n,
|
|
53
|
+
r
|
|
54
|
+
].some((e) => e !== void 0 && Number.isNaN(e))) {
|
|
55
|
+
T(y("movethewheels.vehicles.service.invalid", "Enter valid, non-negative numbers."));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (r !== void 0 && t === void 0 && _.mileage === void 0) {
|
|
59
|
+
T(y("movethewheels.vehicles.service.needsMileage", "Enter the odometer reading to set a service-by-distance interval."));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
C(!0), T(null);
|
|
63
|
+
try {
|
|
64
|
+
await v({
|
|
65
|
+
serviceDate: `${E.serviceDate}T12:00:00.000Z`,
|
|
66
|
+
...t === void 0 ? {} : { mileage: t },
|
|
67
|
+
...n === void 0 ? {} : { intervalDays: Math.round(n) },
|
|
68
|
+
...r === void 0 ? {} : { intervalKm: r }
|
|
69
|
+
}), x(!1);
|
|
70
|
+
} catch (e) {
|
|
71
|
+
T(e instanceof Error ? e.message : "Could not record the service.");
|
|
72
|
+
} finally {
|
|
73
|
+
C(!1);
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
className: "space-y-3",
|
|
77
|
+
children: [
|
|
78
|
+
/* @__PURE__ */ u(c, { message: w }),
|
|
79
|
+
/* @__PURE__ */ d("div", {
|
|
80
|
+
className: "grid grid-cols-2 gap-3",
|
|
81
|
+
children: [
|
|
82
|
+
/* @__PURE__ */ d("div", {
|
|
83
|
+
className: "space-y-1",
|
|
84
|
+
children: [/* @__PURE__ */ u(s, {
|
|
85
|
+
htmlFor: "service-date",
|
|
86
|
+
children: y("movethewheels.vehicles.service.date", "Service date")
|
|
87
|
+
}), /* @__PURE__ */ u(o, {
|
|
88
|
+
id: "service-date",
|
|
89
|
+
type: "date",
|
|
90
|
+
required: !0,
|
|
91
|
+
value: E.serviceDate,
|
|
92
|
+
onChange: (e) => D((t) => ({
|
|
93
|
+
...t,
|
|
94
|
+
serviceDate: e.target.value
|
|
95
|
+
}))
|
|
96
|
+
})]
|
|
97
|
+
}),
|
|
98
|
+
/* @__PURE__ */ d("div", {
|
|
99
|
+
className: "space-y-1",
|
|
100
|
+
children: [/* @__PURE__ */ u(s, {
|
|
101
|
+
htmlFor: "service-mileage",
|
|
102
|
+
children: y("movethewheels.vehicles.service.mileage", "Odometer (km)")
|
|
103
|
+
}), /* @__PURE__ */ u(o, {
|
|
104
|
+
id: "service-mileage",
|
|
105
|
+
type: "number",
|
|
106
|
+
min: 0,
|
|
107
|
+
value: E.mileage,
|
|
108
|
+
onChange: (e) => D((t) => ({
|
|
109
|
+
...t,
|
|
110
|
+
mileage: e.target.value
|
|
111
|
+
}))
|
|
112
|
+
})]
|
|
113
|
+
}),
|
|
114
|
+
/* @__PURE__ */ d("div", {
|
|
115
|
+
className: "space-y-1",
|
|
116
|
+
children: [/* @__PURE__ */ u(s, {
|
|
117
|
+
htmlFor: "service-interval-days",
|
|
118
|
+
children: y("movethewheels.vehicles.service.intervalDays", "Next service in (days)")
|
|
119
|
+
}), /* @__PURE__ */ u(o, {
|
|
120
|
+
id: "service-interval-days",
|
|
121
|
+
type: "number",
|
|
122
|
+
min: 0,
|
|
123
|
+
value: E.intervalDays,
|
|
124
|
+
onChange: (e) => D((t) => ({
|
|
125
|
+
...t,
|
|
126
|
+
intervalDays: e.target.value
|
|
127
|
+
}))
|
|
128
|
+
})]
|
|
129
|
+
}),
|
|
130
|
+
/* @__PURE__ */ d("div", {
|
|
131
|
+
className: "space-y-1",
|
|
132
|
+
children: [/* @__PURE__ */ u(s, {
|
|
133
|
+
htmlFor: "service-interval-km",
|
|
134
|
+
children: y("movethewheels.vehicles.service.intervalKm", "Next service in (km)")
|
|
135
|
+
}), /* @__PURE__ */ u(o, {
|
|
136
|
+
id: "service-interval-km",
|
|
137
|
+
type: "number",
|
|
138
|
+
min: 0,
|
|
139
|
+
value: E.intervalKm,
|
|
140
|
+
onChange: (e) => D((t) => ({
|
|
141
|
+
...t,
|
|
142
|
+
intervalKm: e.target.value
|
|
143
|
+
}))
|
|
144
|
+
})]
|
|
145
|
+
})
|
|
146
|
+
]
|
|
147
|
+
}),
|
|
148
|
+
/* @__PURE__ */ d("div", {
|
|
149
|
+
className: "flex gap-2",
|
|
150
|
+
children: [/* @__PURE__ */ u(t, {
|
|
151
|
+
type: "submit",
|
|
152
|
+
size: "sm",
|
|
153
|
+
disabled: S,
|
|
154
|
+
children: S ? y("movethewheels.vehicles.service.saving", "Saving…") : y("movethewheels.vehicles.service.save", "Save service")
|
|
155
|
+
}), /* @__PURE__ */ u(t, {
|
|
156
|
+
type: "button",
|
|
157
|
+
variant: "ghost",
|
|
158
|
+
size: "sm",
|
|
159
|
+
disabled: S,
|
|
160
|
+
onClick: () => {
|
|
161
|
+
x(!1), T(null);
|
|
162
|
+
},
|
|
163
|
+
children: y("movethewheels.vehicles.service.cancel", "Cancel")
|
|
164
|
+
})]
|
|
165
|
+
})
|
|
166
|
+
]
|
|
167
|
+
}) : /* @__PURE__ */ u(t, {
|
|
168
|
+
variant: "outline",
|
|
169
|
+
size: "sm",
|
|
170
|
+
onClick: () => x(!0),
|
|
171
|
+
children: y("movethewheels.vehicles.service.record", "Record service")
|
|
172
|
+
})]
|
|
173
|
+
})] });
|
|
174
|
+
}
|
|
175
|
+
//#endregion
|
|
176
|
+
export { _ as VehicleServiceCard };
|
package/dist/hooks/useFleet.js
CHANGED
|
@@ -2,7 +2,7 @@ import { useMoveTheWheels as e } from "../providers/MoveTheWheelsProvider.js";
|
|
|
2
2
|
import { executeGraphQL as t } from "../utils/graphqlClient.js";
|
|
3
3
|
import { useCallback as n, useEffect as r, useMemo as i, useState as a } from "react";
|
|
4
4
|
//#region src/movethewheels/hooks/useFleet.ts
|
|
5
|
-
var o = "\n query ListVehicles($filter: VehicleFilterInput, $pagination: PaginationInput) {\n vehicles(filter: $filter, pagination: $pagination) {\n id\n vehicleNumber\n type\n make\n model\n licensePlate\n ownershipType\n year\n capacity\n status\n currentLocation\n fuelLevel\n mileage\n createdAt\n updatedAt\n }\n }\n", s = "\n query ListDrivers($filter: DriverFilterInput, $pagination: PaginationInput) {\n drivers(filter: $filter, pagination: $pagination) {\n id\n userId\n firstName\n lastName\n email\n phone\n employeeId\n licenseNumber\n licenseClass\n licenseExpiry\n status\n currentLocation\n rating\n vehicleId\n createdAt\n }\n }\n", c = "\n query GetVehicle($id: ID!) {\n vehicle(id: $id) {\n id\n vehicleNumber\n type\n make\n model\n licensePlate\n ownershipType\n year\n capacity\n status\n currentLocation\n fuelLevel\n mileage\n createdAt\n updatedAt\n }\n }\n", l = "\n query GetDriver($id: ID!) {\n driver(id: $id) {\n id\n userId\n firstName\n lastName\n email\n phone\n employeeId\n licenseNumber\n licenseClass\n licenseExpiry\n status\n currentLocation\n rating\n vehicleId\n createdAt\n }\n }\n", u = "\n mutation UpdateVehicleStatus($id: ID!, $status: VehicleStatus!) {\n updateVehicleStatus(id: $id, status: $status) {\n id\n vehicleNumber\n type\n make\n model\n licensePlate\n ownershipType\n year\n capacity\n status\n currentLocation\n fuelLevel\n mileage\n createdAt\n updatedAt\n }\n }\n", d = "\n mutation AssignDriverVehicle($id: ID!, $vehicleId: ID) {\n assignDriverVehicle(id: $id, vehicleId: $vehicleId) {\n id\n userId\n firstName\n lastName\n email\n phone\n employeeId\n licenseNumber\n licenseClass\n licenseExpiry\n status\n currentLocation\n rating\n vehicleId\n createdAt\n }\n }\n", f = "\n mutation UpdateDriverStatus($id: ID!, $status: DriverStatus!) {\n updateDriverStatus(id: $id, status: $status) {\n id\n userId\n firstName\n lastName\n email\n phone\n employeeId\n licenseNumber\n licenseClass\n licenseExpiry\n status\n currentLocation\n rating\n vehicleId\n createdAt\n }\n }\n", p = "\n mutation CreateVehicle($input: CreateVehicleInput!) {\n createVehicle(input: $input) {\n id\n vehicleNumber\n type\n make\n model\n licensePlate\n ownershipType\n year\n capacity\n status\n currentLocation\n fuelLevel\n mileage\n createdAt\n updatedAt\n }\n }\n", m = "\n mutation CreateDriver($input: CreateDriverInput!) {\n createDriver(input: $input) {\n id\n userId\n firstName\n lastName\n email\n phone\n employeeId\n licenseNumber\n licenseClass\n licenseExpiry\n status\n currentLocation\n rating\n vehicleId\n createdAt\n }\n }\n", h = "\n mutation UpdateVehicle($id: ID!, $input: UpdateVehicleInput!) {\n updateVehicle(id: $id, input: $input) {\n id\n vehicleNumber\n type\n make\n model\n licensePlate\n ownershipType\n year\n capacity\n status\n currentLocation\n fuelLevel\n mileage\n createdAt\n updatedAt\n }\n }\n", g = "\n mutation UpdateDriver($id: ID!, $input: UpdateDriverInput!) {\n updateDriver(id: $id, input: $input) {\n id\n userId\n firstName\n lastName\n email\n phone\n employeeId\n licenseNumber\n licenseClass\n licenseExpiry\n status\n currentLocation\n rating\n vehicleId\n createdAt\n }\n }\n", _ = "\n query GetDriverStatusCounts {\n onBreak: driversCount(filter: { status: ON_BREAK })\n offDuty: driversCount(filter: { status: OFF_DUTY })\n inactive: driversCount(filter: { status: INACTIVE })\n }\n", v = "\n query GetFleetStats {\n fleetStats {\n totalVehicles\n activeVehicles\n inTransitVehicles\n maintenanceVehicles\n totalDrivers\n availableDrivers\n onDeliveryDrivers\n avgDriverRating\n }\n }\n";
|
|
5
|
+
var o = "\n query ListVehicles($filter: VehicleFilterInput, $pagination: PaginationInput) {\n vehicles(filter: $filter, pagination: $pagination) {\n id\n vehicleNumber\n type\n make\n model\n licensePlate\n ownershipType\n year\n capacity\n status\n currentLocation\n fuelLevel\n mileage\n lastServiceDate\n nextServiceDueDate\n nextServiceDueMileage\n createdAt\n updatedAt\n }\n }\n", s = "\n query ListDrivers($filter: DriverFilterInput, $pagination: PaginationInput) {\n drivers(filter: $filter, pagination: $pagination) {\n id\n userId\n firstName\n lastName\n email\n phone\n employeeId\n licenseNumber\n licenseClass\n licenseExpiry\n status\n currentLocation\n rating\n vehicleId\n createdAt\n }\n }\n", c = "\n query GetVehicle($id: ID!) {\n vehicle(id: $id) {\n id\n vehicleNumber\n type\n make\n model\n licensePlate\n ownershipType\n year\n capacity\n status\n currentLocation\n fuelLevel\n mileage\n lastServiceDate\n nextServiceDueDate\n nextServiceDueMileage\n createdAt\n updatedAt\n }\n }\n", l = "\n query GetDriver($id: ID!) {\n driver(id: $id) {\n id\n userId\n firstName\n lastName\n email\n phone\n employeeId\n licenseNumber\n licenseClass\n licenseExpiry\n status\n currentLocation\n rating\n vehicleId\n createdAt\n }\n }\n", u = "\n mutation UpdateVehicleStatus($id: ID!, $status: VehicleStatus!) {\n updateVehicleStatus(id: $id, status: $status) {\n id\n vehicleNumber\n type\n make\n model\n licensePlate\n ownershipType\n year\n capacity\n status\n currentLocation\n fuelLevel\n mileage\n createdAt\n updatedAt\n }\n }\n", d = "\n mutation AssignDriverVehicle($id: ID!, $vehicleId: ID) {\n assignDriverVehicle(id: $id, vehicleId: $vehicleId) {\n id\n userId\n firstName\n lastName\n email\n phone\n employeeId\n licenseNumber\n licenseClass\n licenseExpiry\n status\n currentLocation\n rating\n vehicleId\n createdAt\n }\n }\n", f = "\n mutation UpdateDriverStatus($id: ID!, $status: DriverStatus!) {\n updateDriverStatus(id: $id, status: $status) {\n id\n userId\n firstName\n lastName\n email\n phone\n employeeId\n licenseNumber\n licenseClass\n licenseExpiry\n status\n currentLocation\n rating\n vehicleId\n createdAt\n }\n }\n", p = "\n mutation CreateVehicle($input: CreateVehicleInput!) {\n createVehicle(input: $input) {\n id\n vehicleNumber\n type\n make\n model\n licensePlate\n ownershipType\n year\n capacity\n status\n currentLocation\n fuelLevel\n mileage\n createdAt\n updatedAt\n }\n }\n", m = "\n mutation CreateDriver($input: CreateDriverInput!) {\n createDriver(input: $input) {\n id\n userId\n firstName\n lastName\n email\n phone\n employeeId\n licenseNumber\n licenseClass\n licenseExpiry\n status\n currentLocation\n rating\n vehicleId\n createdAt\n }\n }\n", h = "\n mutation UpdateVehicle($id: ID!, $input: UpdateVehicleInput!) {\n updateVehicle(id: $id, input: $input) {\n id\n vehicleNumber\n type\n make\n model\n licensePlate\n ownershipType\n year\n capacity\n status\n currentLocation\n fuelLevel\n mileage\n createdAt\n updatedAt\n }\n }\n", g = "\n mutation UpdateDriver($id: ID!, $input: UpdateDriverInput!) {\n updateDriver(id: $id, input: $input) {\n id\n userId\n firstName\n lastName\n email\n phone\n employeeId\n licenseNumber\n licenseClass\n licenseExpiry\n status\n currentLocation\n rating\n vehicleId\n createdAt\n }\n }\n", _ = "\n query GetDriverStatusCounts {\n onBreak: driversCount(filter: { status: ON_BREAK })\n offDuty: driversCount(filter: { status: OFF_DUTY })\n inactive: driversCount(filter: { status: INACTIVE })\n }\n", v = "\n query GetFleetStats {\n fleetStats {\n totalVehicles\n activeVehicles\n inTransitVehicles\n maintenanceVehicles\n totalDrivers\n availableDrivers\n onDeliveryDrivers\n avgDriverRating\n }\n }\n";
|
|
6
6
|
function y(e) {
|
|
7
7
|
switch (e) {
|
|
8
8
|
case "ACTIVE": return "available";
|
|
@@ -58,8 +58,9 @@ function C(e) {
|
|
|
58
58
|
address: e.currentLocation.address ?? void 0,
|
|
59
59
|
timestamp: /* @__PURE__ */ new Date()
|
|
60
60
|
} : void 0,
|
|
61
|
-
lastMaintenance: void 0,
|
|
62
|
-
nextMaintenance: void 0,
|
|
61
|
+
lastMaintenance: e.lastServiceDate ? new Date(e.lastServiceDate) : void 0,
|
|
62
|
+
nextMaintenance: e.nextServiceDueDate ? new Date(e.nextServiceDueDate) : void 0,
|
|
63
|
+
nextMaintenanceMileage: e.nextServiceDueMileage ?? void 0,
|
|
63
64
|
fuelLevel: e.fuelLevel ?? void 0,
|
|
64
65
|
mileage: e.mileage ?? void 0,
|
|
65
66
|
documents: [],
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { useMoveTheWheels as e } from "../providers/MoveTheWheelsProvider.js";
|
|
2
|
+
import { executeGraphQL as t } from "../utils/graphqlClient.js";
|
|
3
|
+
import { useCallback as n, useEffect as r, useMemo as i, useState as a } from "react";
|
|
4
|
+
//#region src/movethewheels/hooks/useFleetIntelligence.ts
|
|
5
|
+
var o = "\n query FleetAlerts {\n fleetAlerts {\n id\n type\n severity\n subjectType\n subjectId\n subjectName\n message\n dueDate\n daysUntilDue\n kmUntilDue\n }\n }\n", s = "\n query AssignmentSuggestions($routeId: ID!, $limit: Int) {\n assignmentSuggestions(routeId: $routeId, limit: $limit) {\n routeId\n vehicles {\n score\n reasons\n vehicle {\n id\n vehicleNumber\n make\n model\n }\n }\n drivers {\n score\n reasons\n driver {\n id\n firstName\n lastName\n rating\n }\n }\n }\n }\n", c = "\n mutation RecordVehicleService($id: ID!, $input: RecordVehicleServiceInput!) {\n recordVehicleService(id: $id, input: $input) {\n id\n status\n mileage\n lastServiceDate\n nextServiceDueDate\n nextServiceDueMileage\n }\n }\n";
|
|
6
|
+
function l() {
|
|
7
|
+
let { apiGatewayUrl: t, authToken: n, workspaceToken: r, workspaceId: a, tenantId: o, currentUser: s } = e();
|
|
8
|
+
return i(() => ({
|
|
9
|
+
apiGatewayUrl: t,
|
|
10
|
+
authToken: n,
|
|
11
|
+
workspaceToken: r,
|
|
12
|
+
workspaceId: a,
|
|
13
|
+
tenantId: o,
|
|
14
|
+
actorId: s?.id
|
|
15
|
+
}), [
|
|
16
|
+
t,
|
|
17
|
+
n,
|
|
18
|
+
r,
|
|
19
|
+
a,
|
|
20
|
+
o,
|
|
21
|
+
s?.id
|
|
22
|
+
]);
|
|
23
|
+
}
|
|
24
|
+
function u() {
|
|
25
|
+
let e = l(), [i, s] = a([]), [c, u] = a(!0), [d, f] = a(null), [p, m] = a(0);
|
|
26
|
+
return r(() => {
|
|
27
|
+
let n = !0;
|
|
28
|
+
return u(!0), f(null), t(e, o).then((e) => {
|
|
29
|
+
n && s(e.fleetAlerts);
|
|
30
|
+
}).catch((e) => {
|
|
31
|
+
n && f(e instanceof Error ? e : /* @__PURE__ */ Error("Failed to load fleet alerts"));
|
|
32
|
+
}).finally(() => {
|
|
33
|
+
n && u(!1);
|
|
34
|
+
}), () => {
|
|
35
|
+
n = !1;
|
|
36
|
+
};
|
|
37
|
+
}, [e, p]), {
|
|
38
|
+
data: i,
|
|
39
|
+
isLoading: c,
|
|
40
|
+
error: d,
|
|
41
|
+
refetch: n(() => m((e) => e + 1), [])
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
var d = {
|
|
45
|
+
routeId: "",
|
|
46
|
+
vehicles: [],
|
|
47
|
+
drivers: []
|
|
48
|
+
};
|
|
49
|
+
function f(e, i = 3) {
|
|
50
|
+
let o = l(), [c, u] = a(d), [f, p] = a(!!e), [m, h] = a(null), [g, _] = a(0);
|
|
51
|
+
return r(() => {
|
|
52
|
+
if (!e) {
|
|
53
|
+
u(d), p(!1);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
let n = !0;
|
|
57
|
+
return p(!0), h(null), t(o, s, {
|
|
58
|
+
routeId: e,
|
|
59
|
+
limit: i
|
|
60
|
+
}).then((e) => {
|
|
61
|
+
n && u(e.assignmentSuggestions);
|
|
62
|
+
}).catch((e) => {
|
|
63
|
+
n && h(e instanceof Error ? e : /* @__PURE__ */ Error("Failed to load suggestions"));
|
|
64
|
+
}).finally(() => {
|
|
65
|
+
n && p(!1);
|
|
66
|
+
}), () => {
|
|
67
|
+
n = !1;
|
|
68
|
+
};
|
|
69
|
+
}, [
|
|
70
|
+
o,
|
|
71
|
+
e,
|
|
72
|
+
i,
|
|
73
|
+
g
|
|
74
|
+
]), {
|
|
75
|
+
data: c,
|
|
76
|
+
isLoading: f,
|
|
77
|
+
error: m,
|
|
78
|
+
refetch: n(() => _((e) => e + 1), [])
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function p() {
|
|
82
|
+
let e = l();
|
|
83
|
+
return n(async (n, r) => {
|
|
84
|
+
await t(e, c, {
|
|
85
|
+
id: n,
|
|
86
|
+
input: r
|
|
87
|
+
});
|
|
88
|
+
}, [e]);
|
|
89
|
+
}
|
|
90
|
+
//#endregion
|
|
91
|
+
export { f as useAssignmentSuggestions, u as useFleetAlerts, p as useRecordVehicleService };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { AssignmentSuggestions as Suggestions } from '../hooks/useFleetIntelligence';
|
|
2
|
+
|
|
3
|
+
interface AssignmentSuggestionsProps {
|
|
4
|
+
suggestions: Suggestions;
|
|
5
|
+
isLoading: boolean;
|
|
6
|
+
error: Error | null;
|
|
7
|
+
onRetry: () => void;
|
|
8
|
+
assignedVehicleId?: string;
|
|
9
|
+
assignedDriverId?: string;
|
|
10
|
+
onAssign: (patch: {
|
|
11
|
+
vehicleId?: string;
|
|
12
|
+
driverId?: string;
|
|
13
|
+
}) => Promise<void>;
|
|
14
|
+
t: (key: string, fallback: string) => string;
|
|
15
|
+
}
|
|
16
|
+
export declare function AssignmentSuggestions({ suggestions, isLoading, error, onRetry, assignedVehicleId, assignedDriverId, onAssign, t, }: AssignmentSuggestionsProps): import("react").JSX.Element;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { FleetAlert } from '../hooks/useFleetIntelligence';
|
|
2
|
+
|
|
3
|
+
interface FleetAlertsPanelProps {
|
|
4
|
+
alerts: FleetAlert[];
|
|
5
|
+
isLoading: boolean;
|
|
6
|
+
error: Error | null;
|
|
7
|
+
onRetry: () => void;
|
|
8
|
+
onOpen: (alert: FleetAlert) => void;
|
|
9
|
+
t: (key: string, fallback: string) => string;
|
|
10
|
+
}
|
|
11
|
+
/** Human-readable "how close / how late" for an alert, from its structured due data. */
|
|
12
|
+
export declare function describeDue(alert: Pick<FleetAlert, 'daysUntilDue' | 'kmUntilDue'>): string;
|
|
13
|
+
export declare function FleetAlertsPanel({ alerts, isLoading, error, onRetry, onOpen, t, }: FleetAlertsPanelProps): import("react").JSX.Element;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { RecordVehicleServiceInput } from '../hooks/useFleetIntelligence';
|
|
2
|
+
import { Vehicle } from '../types';
|
|
3
|
+
|
|
4
|
+
interface VehicleServiceCardProps {
|
|
5
|
+
vehicle: Vehicle;
|
|
6
|
+
onRecord: (input: RecordVehicleServiceInput) => Promise<void>;
|
|
7
|
+
t: (key: string, fallback: string) => string;
|
|
8
|
+
}
|
|
9
|
+
export declare function VehicleServiceCard({ vehicle, onRecord, t }: VehicleServiceCardProps): import("react").JSX.Element;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fleet intelligence hooks — compliance/maintenance alerts, dispatch
|
|
3
|
+
* suggestions for a route, and logging a completed vehicle service.
|
|
4
|
+
*
|
|
5
|
+
* All three sit on top of wspace-movethewheels-svc's fleet module; the ranking
|
|
6
|
+
* and alert rules live server-side so every surface (web, CLI, MCP) agrees.
|
|
7
|
+
*/
|
|
8
|
+
export type FleetAlertType = 'LICENSE_EXPIRED' | 'LICENSE_EXPIRING' | 'MAINTENANCE_OVERDUE' | 'MAINTENANCE_DUE_SOON';
|
|
9
|
+
export type FleetAlertSeverity = 'CRITICAL' | 'WARNING';
|
|
10
|
+
export type FleetAlertSubjectType = 'VEHICLE' | 'DRIVER';
|
|
11
|
+
export interface FleetAlert {
|
|
12
|
+
id: string;
|
|
13
|
+
type: FleetAlertType;
|
|
14
|
+
severity: FleetAlertSeverity;
|
|
15
|
+
subjectType: FleetAlertSubjectType;
|
|
16
|
+
subjectId: string;
|
|
17
|
+
subjectName: string;
|
|
18
|
+
message: string;
|
|
19
|
+
dueDate: string | null;
|
|
20
|
+
daysUntilDue: number | null;
|
|
21
|
+
kmUntilDue: number | null;
|
|
22
|
+
}
|
|
23
|
+
export interface VehicleSuggestion {
|
|
24
|
+
score: number;
|
|
25
|
+
reasons: string[];
|
|
26
|
+
vehicle: {
|
|
27
|
+
id: string;
|
|
28
|
+
vehicleNumber: string;
|
|
29
|
+
make: string | null;
|
|
30
|
+
model: string | null;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export interface DriverSuggestion {
|
|
34
|
+
score: number;
|
|
35
|
+
reasons: string[];
|
|
36
|
+
driver: {
|
|
37
|
+
id: string;
|
|
38
|
+
firstName: string;
|
|
39
|
+
lastName: string;
|
|
40
|
+
rating: number | null;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export interface AssignmentSuggestions {
|
|
44
|
+
routeId: string;
|
|
45
|
+
vehicles: VehicleSuggestion[];
|
|
46
|
+
drivers: DriverSuggestion[];
|
|
47
|
+
}
|
|
48
|
+
export interface RecordVehicleServiceInput {
|
|
49
|
+
/** ISO date-time. Defaults to now on the server. */
|
|
50
|
+
serviceDate?: string;
|
|
51
|
+
mileage?: number;
|
|
52
|
+
intervalDays?: number;
|
|
53
|
+
intervalKm?: number;
|
|
54
|
+
}
|
|
55
|
+
interface AsyncResource<T> {
|
|
56
|
+
data: T;
|
|
57
|
+
isLoading: boolean;
|
|
58
|
+
error: Error | null;
|
|
59
|
+
refetch: () => void;
|
|
60
|
+
}
|
|
61
|
+
export declare function useFleetAlerts(): AsyncResource<FleetAlert[]>;
|
|
62
|
+
export declare function useAssignmentSuggestions(routeId: string | undefined, limit?: number): AsyncResource<AssignmentSuggestions>;
|
|
63
|
+
export declare function useRecordVehicleService(): (vehicleId: string, input: RecordVehicleServiceInput) => Promise<void>;
|
|
64
|
+
export {};
|
|
@@ -277,6 +277,8 @@ export interface Vehicle {
|
|
|
277
277
|
currentLocation?: Location;
|
|
278
278
|
lastMaintenance?: Date;
|
|
279
279
|
nextMaintenance?: Date;
|
|
280
|
+
/** Odometer reading (km) at which the next service is due. */
|
|
281
|
+
nextMaintenanceMileage?: number;
|
|
280
282
|
fuelLevel?: number;
|
|
281
283
|
mileage?: number;
|
|
282
284
|
documents: VehicleDocument[];
|