@burdenoff/microfe-movethewheels 2026.1031.5 → 2026.1101.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/MoveTheWheelsRoot.js +141 -133
- package/dist/MoveTheWheelsRoutes.js +29 -25
- package/dist/hooks/useCarrierIntegrations.js +141 -0
- package/dist/movethewheels/hooks/useCarrierIntegrations.d.ts +39 -0
- package/dist/movethewheels/pages/FinancePage.d.ts +3 -1
- package/dist/movethewheels/pages/IntegrationsPage.d.ts +6 -0
- package/dist/movethewheels/utils/carrierIntegrations.d.ts +88 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/copy.js +16 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/file-check.js +18 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/pause.js +19 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/play.js +8 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/receipt.js +18 -0
- package/dist/pages/FinancePage.js +99 -71
- package/dist/pages/IntegrationsPage.js +616 -0
- package/dist/pages/SettingsPage.js +168 -150
- package/dist/utils/carrierIntegrations.js +105 -0
- package/package.json +3 -2
- package/src/movethewheels/MoveTheWheelsRoot.tsx +8 -0
- package/src/movethewheels/MoveTheWheelsRoutes.tsx +4 -0
- package/src/movethewheels/components/UpcomingDemandPanel.tsx +1 -3
- package/src/movethewheels/hooks/useCarrierIntegrations.ts +221 -0
- package/src/movethewheels/pages/FinancePage.tsx +33 -3
- package/src/movethewheels/pages/IntegrationsPage.tsx +752 -0
- package/src/movethewheels/pages/SettingsPage.tsx +19 -3
- package/src/movethewheels/utils/carrierIntegrations.ts +252 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { useMoveTheWheels as e } from "../providers/MoveTheWheelsProvider.js";
|
|
2
|
+
import { executeGraphQL as t } from "../utils/graphqlClient.js";
|
|
3
|
+
import { CREATE_INTEGRATION_MUTATION as n, DELETE_INTEGRATION_MUTATION as r, INTEGRATION_COUNTS_QUERY as i, INTEGRATION_EVENTS_QUERY as a, LIST_INTEGRATIONS_QUERY as o, UPDATE_INTEGRATION_MUTATION as s } from "../utils/carrierIntegrations.js";
|
|
4
|
+
import { useCallback as c, useEffect as l, useMemo as u, useRef as d, useState as f } from "react";
|
|
5
|
+
//#region src/movethewheels/hooks/useCarrierIntegrations.ts
|
|
6
|
+
function p() {
|
|
7
|
+
let { apiGatewayUrl: t, authToken: n, workspaceToken: r, workspaceId: i, tenantId: a, currentUser: o } = e();
|
|
8
|
+
return u(() => ({
|
|
9
|
+
apiGatewayUrl: t,
|
|
10
|
+
authToken: n,
|
|
11
|
+
workspaceToken: r,
|
|
12
|
+
workspaceId: i,
|
|
13
|
+
tenantId: a,
|
|
14
|
+
actorId: o?.id
|
|
15
|
+
}), [
|
|
16
|
+
t,
|
|
17
|
+
n,
|
|
18
|
+
r,
|
|
19
|
+
i,
|
|
20
|
+
a,
|
|
21
|
+
o?.id
|
|
22
|
+
]);
|
|
23
|
+
}
|
|
24
|
+
function m(e, t) {
|
|
25
|
+
return e instanceof Error ? e : Error(t);
|
|
26
|
+
}
|
|
27
|
+
function h(e) {
|
|
28
|
+
let n = p(), { status: r, limit: i, offset: a } = e, [s, u] = f([]), [h, g] = f(0), [_, v] = f(!1), [y, b] = f(null), x = d(0), S = c(async () => {
|
|
29
|
+
let e = ++x.current;
|
|
30
|
+
v(!0), b(null);
|
|
31
|
+
try {
|
|
32
|
+
let s = await t(n, o, {
|
|
33
|
+
filter: r ? { status: r } : void 0,
|
|
34
|
+
pagination: {
|
|
35
|
+
limit: i,
|
|
36
|
+
offset: a
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
if (e !== x.current) return;
|
|
40
|
+
u(s.integrations), g(s.integrationsCount);
|
|
41
|
+
} catch (t) {
|
|
42
|
+
if (e !== x.current) return;
|
|
43
|
+
b(m(t, "Failed to load integrations"));
|
|
44
|
+
} finally {
|
|
45
|
+
e === x.current && v(!1);
|
|
46
|
+
}
|
|
47
|
+
}, [
|
|
48
|
+
n,
|
|
49
|
+
r,
|
|
50
|
+
i,
|
|
51
|
+
a
|
|
52
|
+
]);
|
|
53
|
+
l(() => (S(), () => {
|
|
54
|
+
x.current += 1;
|
|
55
|
+
}), [S]);
|
|
56
|
+
let C = d(S);
|
|
57
|
+
return l(() => {
|
|
58
|
+
C.current = S;
|
|
59
|
+
}, [S]), {
|
|
60
|
+
integrations: s,
|
|
61
|
+
total: h,
|
|
62
|
+
isLoading: _,
|
|
63
|
+
error: y,
|
|
64
|
+
refetch: c(() => C.current(), [])
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function g() {
|
|
68
|
+
let e = p(), [n, r] = f({
|
|
69
|
+
all: 0,
|
|
70
|
+
active: 0
|
|
71
|
+
}), a = d(0), o = c(async () => {
|
|
72
|
+
let n = ++a.current;
|
|
73
|
+
try {
|
|
74
|
+
let o = await t(e, i, {});
|
|
75
|
+
n === a.current && r({
|
|
76
|
+
all: o.all,
|
|
77
|
+
active: o.active
|
|
78
|
+
});
|
|
79
|
+
} catch {}
|
|
80
|
+
}, [e]);
|
|
81
|
+
return l(() => (o(), () => {
|
|
82
|
+
a.current += 1;
|
|
83
|
+
}), [o]), {
|
|
84
|
+
...n,
|
|
85
|
+
refetch: o
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function _(e, n, r = 20) {
|
|
89
|
+
let i = p(), [o, s] = f([]), [c, u] = f(!1), [d, h] = f(null);
|
|
90
|
+
return l(() => {
|
|
91
|
+
if (!e) {
|
|
92
|
+
s([]);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
let n = !1;
|
|
96
|
+
return u(!0), h(null), t(i, a, {
|
|
97
|
+
integrationId: e,
|
|
98
|
+
pagination: {
|
|
99
|
+
limit: r,
|
|
100
|
+
offset: 0
|
|
101
|
+
}
|
|
102
|
+
}).then((e) => {
|
|
103
|
+
n || s(e.integrationEvents);
|
|
104
|
+
}).catch((e) => {
|
|
105
|
+
n || h(m(e, "Failed to load deliveries"));
|
|
106
|
+
}).finally(() => {
|
|
107
|
+
n || u(!1);
|
|
108
|
+
}), () => {
|
|
109
|
+
n = !0;
|
|
110
|
+
};
|
|
111
|
+
}, [
|
|
112
|
+
i,
|
|
113
|
+
e,
|
|
114
|
+
n,
|
|
115
|
+
r
|
|
116
|
+
]), {
|
|
117
|
+
events: o,
|
|
118
|
+
isLoading: c,
|
|
119
|
+
error: d
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function v() {
|
|
123
|
+
let e = p();
|
|
124
|
+
return u(() => ({
|
|
125
|
+
create: async (r) => (await t(e, n, { input: {
|
|
126
|
+
...r,
|
|
127
|
+
name: r.name.trim(),
|
|
128
|
+
status: "ACTIVE"
|
|
129
|
+
} }, "SINGLE_ATTEMPT")).createIntegration,
|
|
130
|
+
update: async (n, r) => (await t(e, s, {
|
|
131
|
+
id: n,
|
|
132
|
+
input: r.name === void 0 ? r : {
|
|
133
|
+
...r,
|
|
134
|
+
name: r.name.trim()
|
|
135
|
+
}
|
|
136
|
+
})).updateIntegration,
|
|
137
|
+
remove: async (n) => (await t(e, r, { id: n })).deleteIntegration
|
|
138
|
+
}), [e]);
|
|
139
|
+
}
|
|
140
|
+
//#endregion
|
|
141
|
+
export { v as useCarrierIntegrationActions, g as useCarrierIntegrationCounts, _ as useCarrierIntegrationEvents, h as useCarrierIntegrations };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { CarrierIntegration, CarrierIntegrationEvent, IntegrationStatus, SupportedProvider } from '../utils/carrierIntegrations';
|
|
2
|
+
export interface CreateCarrierIntegrationInput {
|
|
3
|
+
provider: SupportedProvider;
|
|
4
|
+
name: string;
|
|
5
|
+
apiSecret: string;
|
|
6
|
+
}
|
|
7
|
+
export interface UpdateCarrierIntegrationInput {
|
|
8
|
+
name?: string;
|
|
9
|
+
status?: IntegrationStatus;
|
|
10
|
+
apiSecret?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function useCarrierIntegrations(options: {
|
|
13
|
+
status?: IntegrationStatus;
|
|
14
|
+
limit: number;
|
|
15
|
+
offset: number;
|
|
16
|
+
}): {
|
|
17
|
+
integrations: CarrierIntegration[];
|
|
18
|
+
total: number;
|
|
19
|
+
isLoading: boolean;
|
|
20
|
+
error: Error | null;
|
|
21
|
+
refetch: () => Promise<void>;
|
|
22
|
+
};
|
|
23
|
+
/** Workspace-wide feed counts (all and active) in one request, independent of the list filter. */
|
|
24
|
+
export declare function useCarrierIntegrationCounts(): {
|
|
25
|
+
refetch: () => Promise<void>;
|
|
26
|
+
all: number;
|
|
27
|
+
active: number;
|
|
28
|
+
};
|
|
29
|
+
/** The most recent deliveries for one integration, newest first. */
|
|
30
|
+
export declare function useCarrierIntegrationEvents(integrationId: string | null, refreshKey: number, limit?: number): {
|
|
31
|
+
events: CarrierIntegrationEvent[];
|
|
32
|
+
isLoading: boolean;
|
|
33
|
+
error: Error | null;
|
|
34
|
+
};
|
|
35
|
+
export declare function useCarrierIntegrationActions(): {
|
|
36
|
+
create: (input: CreateCarrierIntegrationInput) => Promise<CarrierIntegration>;
|
|
37
|
+
update: (id: string, input: UpdateCarrierIntegrationInput) => Promise<CarrierIntegration>;
|
|
38
|
+
remove: (id: string) => Promise<boolean>;
|
|
39
|
+
};
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* revenue, fuel cost, open invoices) plus the real revenue trend series — no
|
|
6
6
|
* simulated paid/pending splits and no fabricated cost categories. Invoicing and
|
|
7
7
|
* settlement are handled by the platform Billing module, which this page
|
|
8
|
-
* deep-links to rather than re-implementing an Invoice ledger here.
|
|
8
|
+
* deep-links to rather than re-implementing an Invoice ledger here. Carrier
|
|
9
|
+
* invoices are checked against the agreed freight estimate on the freight
|
|
10
|
+
* invoice comparison page, which this page links to (BOFF-4395).
|
|
9
11
|
*/
|
|
10
12
|
export default function FinancePage(): import('react').JSX.Element;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remounts the page whenever the host switches tenant or workspace, so no
|
|
3
|
+
* server state, filter, page, pending reveal or in-flight mutation from the
|
|
4
|
+
* previous workspace survives into the next one.
|
|
5
|
+
*/
|
|
6
|
+
export default function IntegrationsPage(): import('react').JSX.Element;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/** Providers the service can ingest today. Vendor adapters are refused by the API. */
|
|
2
|
+
export declare const SUPPORTED_PROVIDERS: readonly ["GENERIC_WEBHOOK"];
|
|
3
|
+
export type SupportedProvider = (typeof SUPPORTED_PROVIDERS)[number];
|
|
4
|
+
/** Named networks that need a signed vendor agreement before an adapter can ship. */
|
|
5
|
+
export declare const ROADMAP_PROVIDERS: readonly [{
|
|
6
|
+
readonly id: "PROJECT44";
|
|
7
|
+
readonly label: "project44";
|
|
8
|
+
}, {
|
|
9
|
+
readonly id: "FOURKITES";
|
|
10
|
+
readonly label: "FourKites";
|
|
11
|
+
}, {
|
|
12
|
+
readonly id: "DESCARTES";
|
|
13
|
+
readonly label: "Descartes";
|
|
14
|
+
}];
|
|
15
|
+
export declare const INTEGRATION_STATUSES: readonly ["ACTIVE", "PAUSED", "DISABLED"];
|
|
16
|
+
export type IntegrationStatus = (typeof INTEGRATION_STATUSES)[number];
|
|
17
|
+
export type IntegrationProvider = 'GENERIC_WEBHOOK' | 'PROJECT44' | 'FOURKITES' | 'DESCARTES';
|
|
18
|
+
export type IntegrationEventStatus = 'RECEIVED' | 'ACCEPTED' | 'PARTIAL' | 'REJECTED' | 'DUPLICATE' | 'CONFLICT';
|
|
19
|
+
/** Tracking event types a delivery may carry (the service's TrackingEventType). */
|
|
20
|
+
export declare const TRACKING_EVENT_TYPES: readonly ["CREATED", "PICKED_UP", "IN_TRANSIT", "ARRIVED", "DEPARTED", "OUT_FOR_DELIVERY", "DELIVERED", "FAILED", "RETURNED"];
|
|
21
|
+
/** The service refuses shorter secrets; the generator always produces 64 hex characters. */
|
|
22
|
+
export declare const MIN_SECRET_LENGTH = 32;
|
|
23
|
+
/** Header the workspace gateway forwards; custom headers do not survive the gateway. */
|
|
24
|
+
export declare const SIGNATURE_HEADER = "x-webhook-signature";
|
|
25
|
+
/** Updates accepted in one delivery (a JSON array of at most this many objects). */
|
|
26
|
+
export declare const MAX_UPDATES_PER_DELIVERY = 5;
|
|
27
|
+
export interface CarrierIntegration {
|
|
28
|
+
id: string;
|
|
29
|
+
workspaceId: string;
|
|
30
|
+
provider: IntegrationProvider;
|
|
31
|
+
name: string;
|
|
32
|
+
status: IntegrationStatus;
|
|
33
|
+
lastSyncAt: string | null;
|
|
34
|
+
lastErrorAt: string | null;
|
|
35
|
+
lastErrorMessage: string | null;
|
|
36
|
+
createdAt: string;
|
|
37
|
+
updatedAt: string;
|
|
38
|
+
}
|
|
39
|
+
export interface CarrierIntegrationEvent {
|
|
40
|
+
id: string;
|
|
41
|
+
integrationId: string;
|
|
42
|
+
externalEventId: string | null;
|
|
43
|
+
shipmentId: string | null;
|
|
44
|
+
eventType: string | null;
|
|
45
|
+
status: IntegrationEventStatus;
|
|
46
|
+
errorMessage: string | null;
|
|
47
|
+
receivedAt: string;
|
|
48
|
+
processedAt: string | null;
|
|
49
|
+
}
|
|
50
|
+
export declare const LIST_INTEGRATIONS_QUERY = "\n query MtwCarrierIntegrations($filter: IntegrationFilterInput, $pagination: PaginationInput) {\n integrations(filter: $filter, pagination: $pagination) {\n \n id\n workspaceId\n provider\n name\n status\n lastSyncAt\n lastErrorAt\n lastErrorMessage\n createdAt\n updatedAt\n\n }\n integrationsCount(filter: $filter)\n }\n";
|
|
51
|
+
export declare const INTEGRATION_COUNTS_QUERY = "\n query MtwCarrierIntegrationCounts {\n all: integrationsCount\n active: integrationsCount(filter: { status: ACTIVE })\n }\n";
|
|
52
|
+
export declare const INTEGRATION_EVENTS_QUERY = "\n query MtwCarrierIntegrationEvents($integrationId: ID, $pagination: PaginationInput) {\n integrationEvents(integrationId: $integrationId, pagination: $pagination) {\n id\n integrationId\n externalEventId\n shipmentId\n eventType\n status\n errorMessage\n receivedAt\n processedAt\n }\n }\n";
|
|
53
|
+
export declare const CREATE_INTEGRATION_MUTATION = "\n mutation CreateMtwCarrierIntegration($input: CreateIntegrationInput!) {\n createIntegration(input: $input) {\n \n id\n workspaceId\n provider\n name\n status\n lastSyncAt\n lastErrorAt\n lastErrorMessage\n createdAt\n updatedAt\n\n }\n }\n";
|
|
54
|
+
export declare const UPDATE_INTEGRATION_MUTATION = "\n mutation UpdateMtwCarrierIntegration($id: ID!, $input: UpdateIntegrationInput!) {\n updateIntegration(id: $id, input: $input) {\n \n id\n workspaceId\n provider\n name\n status\n lastSyncAt\n lastErrorAt\n lastErrorMessage\n createdAt\n updatedAt\n\n }\n }\n";
|
|
55
|
+
export declare const DELETE_INTEGRATION_MUTATION = "\n mutation DeleteMtwCarrierIntegration($id: ID!) {\n deleteIntegration(id: $id)\n }\n";
|
|
56
|
+
/** Minimal source of randomness, so tests can supply a deterministic one. */
|
|
57
|
+
export interface RandomSource {
|
|
58
|
+
getRandomValues<T extends ArrayBufferView>(array: T): T;
|
|
59
|
+
}
|
|
60
|
+
/** A fresh 256-bit signing secret as 64 lowercase hex characters. */
|
|
61
|
+
export declare function generateSigningSecret(random?: RandomSource): string;
|
|
62
|
+
/** Returns an error message for an unusable secret, or null when it is fine. */
|
|
63
|
+
export declare function signingSecretError(secret: string): string | null;
|
|
64
|
+
/** Returns an error message for an unusable name, or null when it is fine. */
|
|
65
|
+
export declare function integrationNameError(name: string): string | null;
|
|
66
|
+
/**
|
|
67
|
+
* The public URL a carrier posts deliveries to. It lives on the same workspace
|
|
68
|
+
* gateway host the app already talks to: `/api/integrations/webhook/<workspace>/<integration>`.
|
|
69
|
+
* A relative gateway URL (local development behind the dev proxy) resolves
|
|
70
|
+
* against `fallbackOrigin`.
|
|
71
|
+
*/
|
|
72
|
+
export declare function webhookUrl(apiGatewayUrl: string | undefined, workspaceId: string, integrationId: string, fallbackOrigin: string): string;
|
|
73
|
+
/** An example delivery body for the setup instructions. */
|
|
74
|
+
export declare function examplePayload(now?: Date): string;
|
|
75
|
+
/** Quotes a value for a POSIX shell: single quotes, with embedded ones escaped. */
|
|
76
|
+
export declare function shellQuote(value: string): string;
|
|
77
|
+
/**
|
|
78
|
+
* A copy-paste shell example that signs and sends one delivery. The secret is
|
|
79
|
+
* referenced as an environment variable so it never appears in the snippet.
|
|
80
|
+
*/
|
|
81
|
+
export declare function curlExample(url: string): string;
|
|
82
|
+
/** "OUT_FOR_DELIVERY" → "Out for delivery". */
|
|
83
|
+
export declare function humanize(value: string): string;
|
|
84
|
+
export type BadgeTone = 'default' | 'secondary' | 'outline' | 'destructive';
|
|
85
|
+
export declare function integrationStatusTone(status: IntegrationStatus): BadgeTone;
|
|
86
|
+
export declare function eventStatusTone(status: IntegrationEventStatus): BadgeTone;
|
|
87
|
+
/** What a webhook sender sees for each status, shown next to the status control. */
|
|
88
|
+
export declare function statusExplanation(status: IntegrationStatus): string;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createLucideIcon as e } from "../createLucideIcon.js";
|
|
2
|
+
//#region node_modules/lucide-react/dist/esm/icons/copy.js
|
|
3
|
+
var t = e("Copy", [["rect", {
|
|
4
|
+
width: "14",
|
|
5
|
+
height: "14",
|
|
6
|
+
x: "8",
|
|
7
|
+
y: "8",
|
|
8
|
+
rx: "2",
|
|
9
|
+
ry: "2",
|
|
10
|
+
key: "17jyea"
|
|
11
|
+
}], ["path", {
|
|
12
|
+
d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2",
|
|
13
|
+
key: "zix9uf"
|
|
14
|
+
}]]);
|
|
15
|
+
//#endregion
|
|
16
|
+
export { t as Copy };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createLucideIcon as e } from "../createLucideIcon.js";
|
|
2
|
+
//#region node_modules/lucide-react/dist/esm/icons/file-check.js
|
|
3
|
+
var t = e("FileCheck", [
|
|
4
|
+
["path", {
|
|
5
|
+
d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z",
|
|
6
|
+
key: "1rqfz7"
|
|
7
|
+
}],
|
|
8
|
+
["path", {
|
|
9
|
+
d: "M14 2v4a2 2 0 0 0 2 2h4",
|
|
10
|
+
key: "tnqrlb"
|
|
11
|
+
}],
|
|
12
|
+
["path", {
|
|
13
|
+
d: "m9 15 2 2 4-4",
|
|
14
|
+
key: "1grp1n"
|
|
15
|
+
}]
|
|
16
|
+
]);
|
|
17
|
+
//#endregion
|
|
18
|
+
export { t as FileCheck };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createLucideIcon as e } from "../createLucideIcon.js";
|
|
2
|
+
//#region node_modules/lucide-react/dist/esm/icons/pause.js
|
|
3
|
+
var t = e("Pause", [["rect", {
|
|
4
|
+
x: "14",
|
|
5
|
+
y: "4",
|
|
6
|
+
width: "4",
|
|
7
|
+
height: "16",
|
|
8
|
+
rx: "1",
|
|
9
|
+
key: "zuxfzm"
|
|
10
|
+
}], ["rect", {
|
|
11
|
+
x: "6",
|
|
12
|
+
y: "4",
|
|
13
|
+
width: "4",
|
|
14
|
+
height: "16",
|
|
15
|
+
rx: "1",
|
|
16
|
+
key: "1okwgv"
|
|
17
|
+
}]]);
|
|
18
|
+
//#endregion
|
|
19
|
+
export { t as Pause };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createLucideIcon as e } from "../createLucideIcon.js";
|
|
2
|
+
//#region node_modules/lucide-react/dist/esm/icons/receipt.js
|
|
3
|
+
var t = e("Receipt", [
|
|
4
|
+
["path", {
|
|
5
|
+
d: "M4 2v20l2-1 2 1 2-1 2 1 2-1 2 1 2-1 2 1V2l-2 1-2-1-2 1-2-1-2 1-2-1-2 1Z",
|
|
6
|
+
key: "q3az6g"
|
|
7
|
+
}],
|
|
8
|
+
["path", {
|
|
9
|
+
d: "M16 8h-6a2 2 0 1 0 0 4h4a2 2 0 1 1 0 4H8",
|
|
10
|
+
key: "1h4pet"
|
|
11
|
+
}],
|
|
12
|
+
["path", {
|
|
13
|
+
d: "M12 17.5v-11",
|
|
14
|
+
key: "1jc1ny"
|
|
15
|
+
}]
|
|
16
|
+
]);
|
|
17
|
+
//#endregion
|
|
18
|
+
export { t as Receipt };
|
|
@@ -1,160 +1,188 @@
|
|
|
1
1
|
import { ArrowRight as e } from "../node_modules/lucide-react/dist/esm/icons/arrow-right.js";
|
|
2
2
|
import { CreditCard as t } from "../node_modules/lucide-react/dist/esm/icons/credit-card.js";
|
|
3
3
|
import { DollarSign as n } from "../node_modules/lucide-react/dist/esm/icons/dollar-sign.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
4
|
+
import { FileCheck as r } from "../node_modules/lucide-react/dist/esm/icons/file-check.js";
|
|
5
|
+
import { FileText as i } from "../node_modules/lucide-react/dist/esm/icons/file-text.js";
|
|
6
|
+
import { Fuel as a } from "../node_modules/lucide-react/dist/esm/icons/fuel.js";
|
|
7
|
+
import { Receipt as o } from "../node_modules/lucide-react/dist/esm/icons/receipt.js";
|
|
8
|
+
import { TrendingUp as s } from "../node_modules/lucide-react/dist/esm/icons/trending-up.js";
|
|
9
|
+
import { joinPath as c } from "../utils/navigation.js";
|
|
10
|
+
import { useMoveTheWheels as l } from "../providers/MoveTheWheelsProvider.js";
|
|
11
|
+
import { formatCurrency as u } from "../utils/formatters.js";
|
|
12
|
+
import { useAnalytics as d, useFinanceMetrics as f } from "../hooks/useAnalytics.js";
|
|
13
|
+
import { Button as p, Card as m, CardContent as h, CardHeader as g, CardTitle as _ } from "../components/ui.js";
|
|
14
|
+
import { PageLayout as v } from "../components/PageLayout.js";
|
|
15
|
+
import { chartTokens as y } from "../components/charts/chartTokens.js";
|
|
16
|
+
import { MetricCard as b, MetricGrid as x } from "../components/MetricCard.js";
|
|
17
|
+
import { TrendChart as S } from "../components/charts/TrendChart.js";
|
|
18
|
+
import { VolumeBar as C } from "../components/charts/VolumeBar.js";
|
|
16
19
|
import "../components/charts/index.js";
|
|
17
|
-
import { useMemo as
|
|
18
|
-
import { jsx as
|
|
19
|
-
import { EmphasisPanel as
|
|
20
|
+
import { useMemo as w } from "react";
|
|
21
|
+
import { jsx as T, jsxs as E } from "react/jsx-runtime";
|
|
22
|
+
import { EmphasisPanel as D, PagePurpose as O } from "@burdenoff/fe-libs/ui";
|
|
20
23
|
//#region src/movethewheels/pages/FinancePage.tsx
|
|
21
|
-
function
|
|
22
|
-
let { analytics:
|
|
24
|
+
function k() {
|
|
25
|
+
let { analytics: k } = d(), { metrics: A } = f(), { navigate: j, basePath: M } = l(), N = () => j("/billing"), P = () => j(c(M, "finance/freight-estimates")), F = () => j(c(M, "finance/freight-invoice-comparisons")), I = w(() => k.trends.revenue.map((e) => ({
|
|
23
26
|
month: e.month,
|
|
24
27
|
revenue: e.revenue
|
|
25
|
-
})), [
|
|
28
|
+
})), [k.trends.revenue]), L = w(() => [
|
|
26
29
|
{
|
|
27
30
|
label: "Total revenue",
|
|
28
|
-
amount:
|
|
31
|
+
amount: A.totalRevenue
|
|
29
32
|
},
|
|
30
33
|
{
|
|
31
34
|
label: "Collected",
|
|
32
|
-
amount:
|
|
35
|
+
amount: A.deliveredRevenue
|
|
33
36
|
},
|
|
34
37
|
{
|
|
35
38
|
label: "Fuel cost",
|
|
36
|
-
amount:
|
|
39
|
+
amount: A.fuelCost
|
|
37
40
|
}
|
|
38
41
|
], [
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
]),
|
|
43
|
-
return /* @__PURE__ */
|
|
42
|
+
A.totalRevenue,
|
|
43
|
+
A.deliveredRevenue,
|
|
44
|
+
A.fuelCost
|
|
45
|
+
]), R = L.some((e) => e.amount > 0);
|
|
46
|
+
return /* @__PURE__ */ E(v, {
|
|
44
47
|
title: "Finance",
|
|
45
48
|
subtitle: "Financial overview and reports",
|
|
46
|
-
icon: /* @__PURE__ */
|
|
47
|
-
headerActions: /* @__PURE__ */
|
|
49
|
+
icon: /* @__PURE__ */ T(n, { size: 24 }),
|
|
50
|
+
headerActions: /* @__PURE__ */ E(p, {
|
|
48
51
|
variant: "outline",
|
|
49
|
-
onClick:
|
|
50
|
-
children: [/* @__PURE__ */
|
|
52
|
+
onClick: N,
|
|
53
|
+
children: [/* @__PURE__ */ T(i, { size: 16 }), " Invoices & Billing"]
|
|
51
54
|
}),
|
|
52
55
|
children: [
|
|
53
|
-
/* @__PURE__ */
|
|
54
|
-
icon: /* @__PURE__ */
|
|
56
|
+
/* @__PURE__ */ T(O, {
|
|
57
|
+
icon: /* @__PURE__ */ T(n, { size: 18 }),
|
|
55
58
|
children: "Follow the money behind your deliveries. See total revenue, how much has already been collected on delivered orders, fuel cost and how many invoices are still open — then head to Billing to raise and settle invoices."
|
|
56
59
|
}),
|
|
57
|
-
/* @__PURE__ */
|
|
60
|
+
/* @__PURE__ */ E(x, {
|
|
58
61
|
columns: 4,
|
|
59
62
|
children: [
|
|
60
|
-
/* @__PURE__ */
|
|
63
|
+
/* @__PURE__ */ T(b, {
|
|
61
64
|
treatment: "glass",
|
|
62
65
|
glow: !0,
|
|
63
66
|
label: "Total Revenue",
|
|
64
|
-
value:
|
|
65
|
-
icon: /* @__PURE__ */
|
|
67
|
+
value: u(A.totalRevenue),
|
|
68
|
+
icon: /* @__PURE__ */ T(n, { size: 20 }),
|
|
66
69
|
accent: "success"
|
|
67
70
|
}),
|
|
68
|
-
/* @__PURE__ */
|
|
71
|
+
/* @__PURE__ */ T(b, {
|
|
69
72
|
label: "Collected (Delivered)",
|
|
70
|
-
value:
|
|
71
|
-
icon: /* @__PURE__ */
|
|
73
|
+
value: u(A.deliveredRevenue),
|
|
74
|
+
icon: /* @__PURE__ */ T(s, { size: 20 }),
|
|
72
75
|
accent: "brand"
|
|
73
76
|
}),
|
|
74
|
-
/* @__PURE__ */
|
|
77
|
+
/* @__PURE__ */ T(b, {
|
|
75
78
|
label: "Fuel Cost",
|
|
76
|
-
value:
|
|
77
|
-
icon: /* @__PURE__ */
|
|
79
|
+
value: u(A.fuelCost),
|
|
80
|
+
icon: /* @__PURE__ */ T(a, { size: 20 }),
|
|
78
81
|
accent: "warning"
|
|
79
82
|
}),
|
|
80
|
-
/* @__PURE__ */
|
|
83
|
+
/* @__PURE__ */ T(b, {
|
|
81
84
|
label: "Open Invoices",
|
|
82
|
-
value: String(
|
|
83
|
-
icon: /* @__PURE__ */
|
|
85
|
+
value: String(A.openInvoices),
|
|
86
|
+
icon: /* @__PURE__ */ T(t, { size: 20 }),
|
|
84
87
|
accent: "blue"
|
|
85
88
|
})
|
|
86
89
|
]
|
|
87
90
|
}),
|
|
88
|
-
/* @__PURE__ */
|
|
91
|
+
/* @__PURE__ */ E("div", {
|
|
89
92
|
className: "grid grid-cols-1 lg:grid-cols-3 gap-6",
|
|
90
|
-
children: [/* @__PURE__ */
|
|
93
|
+
children: [/* @__PURE__ */ E(m, {
|
|
91
94
|
className: "lg:col-span-2",
|
|
92
|
-
children: [/* @__PURE__ */
|
|
95
|
+
children: [/* @__PURE__ */ T(g, { children: /* @__PURE__ */ T(_, { children: "Revenue trend" }) }), /* @__PURE__ */ T(h, { children: /* @__PURE__ */ T(S, {
|
|
93
96
|
xKey: "month",
|
|
94
97
|
variant: "area",
|
|
95
98
|
height: 280,
|
|
96
99
|
showLegend: !1,
|
|
97
|
-
data:
|
|
100
|
+
data: I,
|
|
98
101
|
series: [{
|
|
99
102
|
dataKey: "revenue",
|
|
100
103
|
name: "Revenue",
|
|
101
|
-
color:
|
|
104
|
+
color: y.brandBlue
|
|
102
105
|
}],
|
|
103
|
-
valueFormatter: (e) =>
|
|
106
|
+
valueFormatter: (e) => u(Number(e)),
|
|
104
107
|
emptyMessage: "No revenue recorded yet. Revenue appears here as orders are delivered."
|
|
105
108
|
}) })]
|
|
106
|
-
}), /* @__PURE__ */
|
|
109
|
+
}), /* @__PURE__ */ E(D, {
|
|
107
110
|
className: "space-y-4",
|
|
108
111
|
children: [
|
|
109
|
-
/* @__PURE__ */
|
|
112
|
+
/* @__PURE__ */ T("h2", {
|
|
110
113
|
className: "text-base font-semibold text-text-primary",
|
|
111
114
|
children: "Invoices & Settlement"
|
|
112
115
|
}),
|
|
113
|
-
/* @__PURE__ */
|
|
116
|
+
/* @__PURE__ */ T("p", {
|
|
114
117
|
className: "text-sm text-text-secondary",
|
|
115
118
|
children: "Invoicing, payments and settlement run through the platform Billing module — the single source of truth for what's been billed and paid. MoveTheWheels doesn't keep its own invoice ledger."
|
|
116
119
|
}),
|
|
117
|
-
/* @__PURE__ */
|
|
120
|
+
/* @__PURE__ */ E("div", {
|
|
118
121
|
className: "flex items-center justify-between p-4 bg-status-warning-bg/10 rounded-lg",
|
|
119
|
-
children: [/* @__PURE__ */
|
|
122
|
+
children: [/* @__PURE__ */ E("div", {
|
|
120
123
|
className: "flex items-center gap-3",
|
|
121
|
-
children: [/* @__PURE__ */
|
|
124
|
+
children: [/* @__PURE__ */ T("div", {
|
|
122
125
|
className: "w-10 h-10 rounded-full bg-status-warning-bg/20 flex items-center justify-center",
|
|
123
|
-
children: /* @__PURE__ */
|
|
126
|
+
children: /* @__PURE__ */ T(t, {
|
|
124
127
|
size: 20,
|
|
125
128
|
className: "text-status-warning-text"
|
|
126
129
|
})
|
|
127
|
-
}), /* @__PURE__ */
|
|
130
|
+
}), /* @__PURE__ */ E("div", { children: [/* @__PURE__ */ T("p", {
|
|
128
131
|
className: "font-medium",
|
|
129
132
|
children: "Open invoices"
|
|
130
|
-
}), /* @__PURE__ */
|
|
133
|
+
}), /* @__PURE__ */ E("p", {
|
|
131
134
|
className: "text-sm text-text-secondary",
|
|
132
|
-
children: [
|
|
135
|
+
children: [A.openInvoices, " awaiting settlement"]
|
|
133
136
|
})] })]
|
|
134
|
-
}), /* @__PURE__ */
|
|
137
|
+
}), /* @__PURE__ */ T("span", {
|
|
135
138
|
className: "text-xl font-bold text-status-warning-text",
|
|
136
|
-
children:
|
|
139
|
+
children: A.openInvoices
|
|
137
140
|
})]
|
|
138
141
|
}),
|
|
139
|
-
/* @__PURE__ */
|
|
142
|
+
/* @__PURE__ */ E(p, {
|
|
140
143
|
className: "w-full",
|
|
141
|
-
onClick:
|
|
142
|
-
children: ["Open Billing ", /* @__PURE__ */
|
|
144
|
+
onClick: N,
|
|
145
|
+
children: ["Open Billing ", /* @__PURE__ */ T(e, { size: 16 })]
|
|
146
|
+
}),
|
|
147
|
+
/* @__PURE__ */ E("div", {
|
|
148
|
+
className: "space-y-2 border-t border-border-default pt-4",
|
|
149
|
+
children: [
|
|
150
|
+
/* @__PURE__ */ T("h3", {
|
|
151
|
+
className: "text-sm font-semibold text-text-primary",
|
|
152
|
+
children: "Freight cost control"
|
|
153
|
+
}),
|
|
154
|
+
/* @__PURE__ */ T("p", {
|
|
155
|
+
className: "text-sm text-text-secondary",
|
|
156
|
+
children: "Record the agreed freight estimate for a shipment, then check the carrier's invoice against it line by line before it is settled in Billing."
|
|
157
|
+
}),
|
|
158
|
+
/* @__PURE__ */ E("div", {
|
|
159
|
+
className: "grid grid-cols-1 gap-2 sm:grid-cols-2",
|
|
160
|
+
children: [/* @__PURE__ */ E(p, {
|
|
161
|
+
variant: "outline",
|
|
162
|
+
onClick: P,
|
|
163
|
+
children: [/* @__PURE__ */ T(o, { size: 16 }), " Freight estimates"]
|
|
164
|
+
}), /* @__PURE__ */ E(p, {
|
|
165
|
+
variant: "outline",
|
|
166
|
+
onClick: F,
|
|
167
|
+
children: [/* @__PURE__ */ T(r, { size: 16 }), " Invoice comparisons"]
|
|
168
|
+
})]
|
|
169
|
+
})
|
|
170
|
+
]
|
|
143
171
|
})
|
|
144
172
|
]
|
|
145
173
|
})]
|
|
146
174
|
}),
|
|
147
|
-
/* @__PURE__ */
|
|
175
|
+
/* @__PURE__ */ E(m, { children: [/* @__PURE__ */ T(g, { children: /* @__PURE__ */ T(_, { children: "Revenue vs cost" }) }), /* @__PURE__ */ T(h, { children: R ? /* @__PURE__ */ T(C, {
|
|
148
176
|
xKey: "label",
|
|
149
177
|
height: 260,
|
|
150
178
|
showLegend: !1,
|
|
151
|
-
data:
|
|
179
|
+
data: L,
|
|
152
180
|
series: [{
|
|
153
181
|
dataKey: "amount",
|
|
154
182
|
name: "Amount"
|
|
155
183
|
}],
|
|
156
|
-
valueFormatter: (e) =>
|
|
157
|
-
}) : /* @__PURE__ */
|
|
184
|
+
valueFormatter: (e) => u(Number(e))
|
|
185
|
+
}) : /* @__PURE__ */ T("p", {
|
|
158
186
|
className: "py-8 text-center text-sm text-text-secondary",
|
|
159
187
|
children: "Revenue and cost figures appear here as orders are delivered."
|
|
160
188
|
}) })] })
|
|
@@ -162,4 +190,4 @@ function E() {
|
|
|
162
190
|
});
|
|
163
191
|
}
|
|
164
192
|
//#endregion
|
|
165
|
-
export {
|
|
193
|
+
export { k as default };
|