@burdenoff/microfe-movethewheels 2026.1030.1 → 2026.1031.2

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.
Files changed (44) hide show
  1. package/dist/components/ExportStatusBanner.js +53 -0
  2. package/dist/components/ShipmentRiskDialog.js +228 -0
  3. package/dist/components/TrackingAlertList.js +149 -0
  4. package/dist/generated/tracking-quality-operations.d.ts +1070 -2
  5. package/dist/generated/wspace-types.d.ts +2895 -50
  6. package/dist/hooks/useEntityExport.js +85 -0
  7. package/dist/hooks/useShipmentRisk.js +72 -0
  8. package/dist/hooks/useTracking.js +96 -82
  9. package/dist/movethewheels/components/ExportStatusBanner.d.ts +7 -0
  10. package/dist/movethewheels/components/ShipmentRiskDialog.d.ts +20 -0
  11. package/dist/movethewheels/components/TrackingAlertList.d.ts +14 -0
  12. package/dist/movethewheels/hooks/useEntityExport.d.ts +32 -0
  13. package/dist/movethewheels/hooks/useShipmentRisk.d.ts +41 -0
  14. package/dist/movethewheels/types/index.d.ts +15 -1
  15. package/dist/movethewheels/utils/entityExport.d.ts +64 -0
  16. package/dist/movethewheels/utils/trackingAlerts.d.ts +36 -0
  17. package/dist/pages/CustomersPage.js +64 -58
  18. package/dist/pages/DashboardPage.js +147 -151
  19. package/dist/pages/DriversPage.js +113 -107
  20. package/dist/pages/LiveTrackingPage.js +169 -169
  21. package/dist/pages/OrdersListPage.js +85 -79
  22. package/dist/pages/ProductsPage.js +63 -57
  23. package/dist/pages/VehiclesPage.js +91 -85
  24. package/dist/utils/entityExport.js +54 -0
  25. package/dist/utils/trackingAlerts.js +86 -0
  26. package/package.json +4 -2
  27. package/src/generated/tracking-quality-operations.ts +1409 -13
  28. package/src/generated/wspace-types.ts +3248 -51
  29. package/src/movethewheels/components/ExportStatusBanner.tsx +78 -0
  30. package/src/movethewheels/components/ShipmentRiskDialog.tsx +315 -0
  31. package/src/movethewheels/components/TrackingAlertList.tsx +188 -0
  32. package/src/movethewheels/hooks/useEntityExport.ts +130 -0
  33. package/src/movethewheels/hooks/useShipmentRisk.ts +195 -0
  34. package/src/movethewheels/hooks/useTracking.ts +66 -22
  35. package/src/movethewheels/pages/CustomersPage.tsx +11 -2
  36. package/src/movethewheels/pages/DashboardPage.tsx +10 -20
  37. package/src/movethewheels/pages/DriversPage.tsx +11 -2
  38. package/src/movethewheels/pages/LiveTrackingPage.tsx +20 -22
  39. package/src/movethewheels/pages/OrdersListPage.tsx +13 -2
  40. package/src/movethewheels/pages/ProductsPage.tsx +11 -2
  41. package/src/movethewheels/pages/VehiclesPage.tsx +11 -2
  42. package/src/movethewheels/types/index.ts +16 -1
  43. package/src/movethewheels/utils/entityExport.ts +140 -0
  44. package/src/movethewheels/utils/trackingAlerts.ts +151 -0
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Entity export — starts a MoveTheWheels export job and waits for its archive.
3
+ *
4
+ * `exportMtwEntities` hands the request to wspace-export-svc, which builds a
5
+ * ZIP of the workspace's records in the background. The job is read back with
6
+ * `getExportJob` until it reports COMPLETED (with a presigned `downloadUrl`),
7
+ * FAILED/CANCELLED/EXPIRED, or the wait budget runs out. A job that is still
8
+ * running when the budget is spent keeps going server side and stays
9
+ * downloadable from the shared Exports page.
10
+ *
11
+ * Kept free of React so the polling contract can be unit-tested.
12
+ */
13
+ /** List pages that offer an export, mapped to wspace-export-svc entity types. */
14
+ export declare const MTW_EXPORT_ENTITY_TYPES: {
15
+ readonly customers: "movethewheels_customers";
16
+ readonly drivers: "movethewheels_drivers";
17
+ readonly orders: "movethewheels_delivery_jobs";
18
+ readonly products: "movethewheels_cargo_items";
19
+ readonly vehicles: "movethewheels_vehicles";
20
+ };
21
+ export type MtwExportEntity = keyof typeof MTW_EXPORT_ENTITY_TYPES;
22
+ export declare const START_MTW_EXPORT_MUTATION = "\n mutation StartMovethewheelsEntityExport($input: ExportMtwEntitiesInput!) {\n exportMtwEntities(input: $input) {\n id\n status\n downloadUrl\n }\n }\n";
23
+ export declare const GET_MTW_EXPORT_JOB_QUERY = "\n query GetMovethewheelsExportJob($id: ID!) {\n getExportJob(id: $id) {\n id\n status\n progress\n downloadUrl\n errorMessage\n }\n }\n";
24
+ export interface ExportJobSnapshot {
25
+ id: string;
26
+ status: string;
27
+ progress?: number | null;
28
+ downloadUrl?: string | null;
29
+ errorMessage?: string | null;
30
+ }
31
+ export type ExportJobOutcome = {
32
+ kind: 'ready';
33
+ jobId: string;
34
+ downloadUrl: string;
35
+ } | {
36
+ kind: 'failed';
37
+ jobId: string;
38
+ message: string;
39
+ } | {
40
+ kind: 'pending';
41
+ jobId: string;
42
+ };
43
+ /** Classify one job snapshot; `null` means keep waiting. */
44
+ export declare function classifyExportJob(job: ExportJobSnapshot): ExportJobOutcome | null;
45
+ export interface WaitForExportOptions {
46
+ /** Delay between reads of the job. */
47
+ intervalMs?: number;
48
+ /** Total time to wait before reporting the job as still pending. */
49
+ timeoutMs?: number;
50
+ /** Injectable for tests. */
51
+ sleep?: (ms: number) => Promise<void>;
52
+ /** Injectable for tests. */
53
+ now?: () => number;
54
+ /** Stops waiting (e.g. the page unmounted); resolves as pending. */
55
+ signal?: AbortSignal;
56
+ }
57
+ /**
58
+ * Read the job until it settles or the wait budget is spent. `initial` is the
59
+ * snapshot returned by the start mutation, so an already-finished job resolves
60
+ * without another round trip.
61
+ */
62
+ export declare function waitForExportJob(initial: ExportJobSnapshot, fetchJob: (id: string) => Promise<ExportJobSnapshot | null>, { intervalMs, timeoutMs, sleep, now, signal, }?: WaitForExportOptions): Promise<ExportJobOutcome>;
63
+ /** Start the archive download in the current tab without navigating away. */
64
+ export declare function triggerUrlDownload(url: string): void;
@@ -0,0 +1,36 @@
1
+ import { TrackingAlert, TrackingAlertSeverity } from '../types';
2
+ export type RiskSignalCode = 'LEG_SLIP' | 'OPEN_LEG_OVERDUE' | 'ROUTE_DURATION_OVERRUN' | 'EXCESS_DWELL' | 'TRACKING_STALE' | 'VEHICLE_UNAVAILABLE' | 'CARRIER_LATE_HISTORY';
3
+ export type TrackingAlertKind = 'DELAYED' | 'AT_RISK';
4
+ export interface ApiTrackingAlert {
5
+ id: string;
6
+ shipmentId: string;
7
+ shipmentNumber: string;
8
+ alertType: string;
9
+ message: string;
10
+ riskScore?: number | null;
11
+ promisedDelivery?: string | null;
12
+ predictedDelivery?: string | null;
13
+ signals?: RiskSignalCode[] | null;
14
+ acknowledged: boolean;
15
+ acknowledgedAt?: string | null;
16
+ }
17
+ /** Risk score at or above which an AT_RISK alert is shown as high severity (matches the backend HIGH level). */
18
+ export declare const HIGH_RISK_SCORE = 70;
19
+ /**
20
+ * DELAYED is always high: the promise is already broken. AT_RISK follows the
21
+ * forecast's risk score; the backend only raises AT_RISK at MEDIUM or above,
22
+ * so anything below the HIGH band is medium.
23
+ */
24
+ export declare function trackingAlertSeverity(alertType: string, riskScore: number | null | undefined): TrackingAlertSeverity;
25
+ export declare function mapTrackingAlert(alert: ApiTrackingAlert): TrackingAlert;
26
+ export declare function sortTrackingAlerts(alerts: TrackingAlert[]): TrackingAlert[];
27
+ /** Translation key + English fallback for each risk signal code. */
28
+ export declare const RISK_SIGNAL_LABELS: Record<RiskSignalCode, {
29
+ key: string;
30
+ fallback: string;
31
+ }>;
32
+ export declare function riskSignalLabel(code: string, t: (key: string, fallback: string) => string): string;
33
+ /** Minutes between the promise and the predicted delivery, or null when unknown / not late. */
34
+ export declare function predictedSlipMinutes(alert: Pick<TrackingAlert, 'promisedDelivery' | 'predictedDelivery'>): number | null;
35
+ /** "45 min", "3 h 20 min", "2 d 4 h" — compact duration for alert cards. */
36
+ export declare function formatSlip(minutes: number): string;
@@ -14,80 +14,86 @@ import { TypeBadge as g } from "../components/StatusBadge.js";
14
14
  import { DataTable as _ } from "../components/DataTable.js";
15
15
  import { formatDate as v } from "../utils/formatters.js";
16
16
  import { useDebouncedValue as y } from "../hooks/useDebouncedValue.js";
17
- import { useState as b } from "react";
18
- import { jsx as x, jsxs as S } from "react/jsx-runtime";
19
- import { CTAOverflowMenu as C, PagePurpose as w } from "@burdenoff/fe-libs/ui";
17
+ import { useEntityExport as b } from "../hooks/useEntityExport.js";
18
+ import { ExportStatusBanner as x } from "../components/ExportStatusBanner.js";
19
+ import { useState as S } from "react";
20
+ import { jsx as C, jsxs as w } from "react/jsx-runtime";
21
+ import { CTAOverflowMenu as T, PagePurpose as E } from "@burdenoff/fe-libs/ui";
20
22
  //#region src/movethewheels/pages/CustomersPage.tsx
21
- function T() {
22
- let { basePath: T, navigate: E } = s(), [D, O] = b(0), [k, A] = b(25), [j, M] = b(""), N = y(j), { customers: P, totalCustomers: F, hasMore: I, isLoading: L, error: R, refetch: z } = l({
23
- limit: k,
24
- offset: D * k,
25
- filters: { search: N }
26
- }), { stats: B } = c(), V = (e) => E?.(o(T, e));
27
- return /* @__PURE__ */ S(p, {
23
+ function D() {
24
+ let { basePath: D, navigate: O } = s(), { state: k, isExporting: A, startExport: j, dismiss: M } = b("customers", "customer"), [N, P] = S(0), [F, I] = S(25), [L, R] = S(""), z = y(L), { customers: B, totalCustomers: V, hasMore: H, isLoading: U, error: W, refetch: G } = l({
25
+ limit: F,
26
+ offset: N * F,
27
+ filters: { search: z }
28
+ }), { stats: K } = c(), q = (e) => O?.(o(D, e));
29
+ return /* @__PURE__ */ w(p, {
28
30
  title: "Customers",
29
- subtitle: `${B.total} customers`,
30
- icon: /* @__PURE__ */ x(a, { size: 24 }),
31
- headerActions: /* @__PURE__ */ x(C, {
31
+ subtitle: `${K.total} customers`,
32
+ icon: /* @__PURE__ */ C(a, { size: 24 }),
33
+ headerActions: /* @__PURE__ */ C(T, {
32
34
  primary: {
33
35
  label: "Add Customer",
34
- icon: /* @__PURE__ */ x(n, { size: 16 }),
35
- onSelect: () => V("customers/new")
36
+ icon: /* @__PURE__ */ C(n, { size: 16 }),
37
+ onSelect: () => q("customers/new")
36
38
  },
37
39
  actions: [{
38
40
  label: "Refresh",
39
- icon: /* @__PURE__ */ x(r, { size: 16 }),
40
- onSelect: () => z()
41
+ icon: /* @__PURE__ */ C(r, { size: 16 }),
42
+ onSelect: () => G()
41
43
  }, {
42
- label: "Export",
43
- icon: /* @__PURE__ */ x(t, { size: 16 }),
44
- onSelect: () => V("import-export")
44
+ label: A ? "Exporting…" : "Export",
45
+ icon: /* @__PURE__ */ C(t, { size: 16 }),
46
+ onSelect: j
45
47
  }]
46
48
  }),
47
49
  children: [
48
- /* @__PURE__ */ x(w, {
49
- icon: /* @__PURE__ */ x(a, { size: 18 }),
50
+ /* @__PURE__ */ C(E, {
51
+ icon: /* @__PURE__ */ C(a, { size: 18 }),
50
52
  children: "The businesses and individuals you deliver for. Keep contact details, locations and satisfaction ratings here, and open a customer to see their order history before booking the next shipment."
51
53
  }),
52
- /* @__PURE__ */ S("div", { children: [/* @__PURE__ */ x("p", {
54
+ /* @__PURE__ */ C(x, {
55
+ state: k,
56
+ onDismiss: M
57
+ }),
58
+ /* @__PURE__ */ w("div", { children: [/* @__PURE__ */ C("p", {
53
59
  className: "mb-3 text-sm font-medium text-text-secondary",
54
60
  children: "Customer base at a glance"
55
- }), /* @__PURE__ */ S(h, {
61
+ }), /* @__PURE__ */ w(h, {
56
62
  columns: 3,
57
63
  children: [
58
- /* @__PURE__ */ x(m, {
64
+ /* @__PURE__ */ C(m, {
59
65
  label: "Total customers",
60
- value: B.total,
61
- icon: /* @__PURE__ */ x(a, { size: 20 }),
66
+ value: K.total,
67
+ icon: /* @__PURE__ */ C(a, { size: 20 }),
62
68
  accent: "brand"
63
69
  }),
64
- /* @__PURE__ */ x(m, {
70
+ /* @__PURE__ */ C(m, {
65
71
  label: "Avg. rating",
66
- value: B.avgRating > 0 ? B.avgRating.toFixed(1) : "—",
67
- icon: /* @__PURE__ */ x(i, { size: 20 }),
72
+ value: K.avgRating > 0 ? K.avgRating.toFixed(1) : "—",
73
+ icon: /* @__PURE__ */ C(i, { size: 20 }),
68
74
  accent: "success",
69
75
  subtitle: "Across all customers"
70
76
  }),
71
- /* @__PURE__ */ x(m, {
77
+ /* @__PURE__ */ C(m, {
72
78
  label: "Business accounts",
73
- value: B.b2b,
74
- icon: /* @__PURE__ */ x(e, { size: 20 }),
79
+ value: K.b2b,
80
+ icon: /* @__PURE__ */ C(e, { size: 20 }),
75
81
  accent: "blue",
76
82
  subtitle: "B2B relationships"
77
83
  })
78
84
  ]
79
85
  })] }),
80
- /* @__PURE__ */ x(_, {
86
+ /* @__PURE__ */ C(_, {
81
87
  columns: [
82
88
  {
83
89
  id: "customer",
84
90
  header: "Customer",
85
- cell: (e) => /* @__PURE__ */ S("div", {
91
+ cell: (e) => /* @__PURE__ */ w("div", {
86
92
  className: "flex items-center gap-3",
87
- children: [/* @__PURE__ */ x(u, { children: /* @__PURE__ */ x(d, { children: e.name.split(" ").map((e) => e[0]).join("") }) }), /* @__PURE__ */ S("div", { children: [/* @__PURE__ */ x("p", {
93
+ children: [/* @__PURE__ */ C(u, { children: /* @__PURE__ */ C(d, { children: e.name.split(" ").map((e) => e[0]).join("") }) }), /* @__PURE__ */ w("div", { children: [/* @__PURE__ */ C("p", {
88
94
  className: "font-medium",
89
95
  children: e.name
90
- }), /* @__PURE__ */ x("p", {
96
+ }), /* @__PURE__ */ C("p", {
91
97
  className: "text-sm text-text-secondary",
92
98
  children: e.email
93
99
  })] })]
@@ -97,7 +103,7 @@ function T() {
97
103
  id: "type",
98
104
  header: "Type",
99
105
  accessorKey: "type",
100
- cell: (e) => /* @__PURE__ */ x(g, { type: e.type })
106
+ cell: (e) => /* @__PURE__ */ C(g, { type: e.type })
101
107
  },
102
108
  {
103
109
  id: "company",
@@ -118,12 +124,12 @@ function T() {
118
124
  {
119
125
  id: "rating",
120
126
  header: "Rating",
121
- cell: (e) => /* @__PURE__ */ S("div", {
127
+ cell: (e) => /* @__PURE__ */ w("div", {
122
128
  className: "flex items-center gap-1",
123
- children: [/* @__PURE__ */ x(i, {
129
+ children: [/* @__PURE__ */ C(i, {
124
130
  size: 14,
125
131
  className: "text-status-warning-text fill-current"
126
- }), /* @__PURE__ */ x("span", { children: e.rating === void 0 ? "Not provided" : e.rating.toFixed(1) })]
132
+ }), /* @__PURE__ */ C("span", { children: e.rating === void 0 ? "Not provided" : e.rating.toFixed(1) })]
127
133
  })
128
134
  },
129
135
  {
@@ -134,10 +140,10 @@ function T() {
134
140
  cell: (e) => v(e.createdAt, "short")
135
141
  }
136
142
  ],
137
- data: P,
138
- isLoading: L,
139
- error: R,
140
- onRetry: z,
143
+ data: B,
144
+ isLoading: U,
145
+ error: W,
146
+ onRetry: G,
141
147
  searchable: !0,
142
148
  searchPlaceholder: "Search customers...",
143
149
  searchFields: [
@@ -145,26 +151,26 @@ function T() {
145
151
  "email",
146
152
  "company"
147
153
  ],
148
- searchQuery: j,
154
+ searchQuery: L,
149
155
  onSearchQueryChange: (e) => {
150
- M(e), O(0);
156
+ R(e), P(0);
151
157
  },
152
- onRowClick: (e) => V(`customers/${e.id}`),
158
+ onRowClick: (e) => q(`customers/${e.id}`),
153
159
  getRowId: (e) => e.id,
154
- pageIndex: D,
155
- pageSize: k,
156
- onPageChange: O,
160
+ pageIndex: N,
161
+ pageSize: F,
162
+ onPageChange: P,
157
163
  onPageSizeChange: (e) => {
158
- A(e), O(0);
164
+ I(e), P(0);
159
165
  },
160
- totalItems: F,
161
- hasNextPage: I,
166
+ totalItems: V,
167
+ hasNextPage: H,
162
168
  emptyIllustration: "empty-people",
163
169
  emptyTitle: "No customers yet",
164
170
  emptyDescription: "Add your first customer to start booking deliveries for them.",
165
- emptyAction: /* @__PURE__ */ S(f, {
166
- onClick: () => V("customers/new"),
167
- children: [/* @__PURE__ */ x(n, {
171
+ emptyAction: /* @__PURE__ */ w(f, {
172
+ onClick: () => q("customers/new"),
173
+ children: [/* @__PURE__ */ C(n, {
168
174
  size: 16,
169
175
  className: "mr-1.5"
170
176
  }), "Add Customer"]
@@ -174,4 +180,4 @@ function T() {
174
180
  });
175
181
  }
176
182
  //#endregion
177
- export { T as default };
183
+ export { D as default };