@burdenoff/microfe-movethewheels 2026.822.1 → 2026.828.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.
Files changed (71) hide show
  1. package/dist/generated/wspace-operations.d.ts +168 -912
  2. package/dist/generated/wspace-types.d.ts +78432 -35831
  3. package/dist/hooks/useFleet.js +142 -74
  4. package/dist/hooks/useImportExport.js +22 -51
  5. package/dist/hooks/useInventory.js +163 -70
  6. package/dist/hooks/useOrders.js +2 -2
  7. package/dist/hooks/useReports.js +63 -73
  8. package/dist/hooks/useSettings.js +52 -44
  9. package/dist/hooks/useTracking.js +5 -5
  10. package/dist/index.js +33 -33
  11. package/dist/movethewheels/hooks/index.d.ts +2 -2
  12. package/dist/movethewheels/hooks/useFleet.d.ts +32 -1
  13. package/dist/movethewheels/hooks/useImportExport.d.ts +10 -0
  14. package/dist/movethewheels/hooks/useInventory.d.ts +67 -12
  15. package/dist/movethewheels/hooks/useOrders.d.ts +25 -2
  16. package/dist/movethewheels/pages/MarketplacePage.d.ts +7 -4
  17. package/dist/movethewheels/types/index.d.ts +2 -0
  18. package/dist/node_modules/lucide-react/dist/esm/icons/arrow-down-right.js +11 -0
  19. package/dist/node_modules/lucide-react/dist/esm/icons/arrow-left-right.js +22 -0
  20. package/dist/node_modules/lucide-react/dist/esm/icons/arrow-up-right.js +11 -0
  21. package/dist/node_modules/lucide-react/dist/esm/icons/calendar-clock.js +32 -0
  22. package/dist/node_modules/lucide-react/dist/esm/icons/history.js +18 -0
  23. package/dist/node_modules/lucide-react/dist/esm/icons/lock.js +16 -0
  24. package/dist/node_modules/lucide-react/dist/esm/icons/user-round.js +13 -0
  25. package/dist/pages/CreateOrderPage.js +246 -195
  26. package/dist/pages/DeliveryCapturePage.js +91 -78
  27. package/dist/pages/DriversPage.js +202 -108
  28. package/dist/pages/InventoryPage.js +356 -131
  29. package/dist/pages/MarketplacePage.js +11 -432
  30. package/dist/pages/ProductDetailsPage.js +212 -156
  31. package/dist/pages/ReportsPage.js +120 -109
  32. package/dist/pages/VehicleDetailsPage.js +257 -193
  33. package/dist/utils/formatters.js +4 -1
  34. package/package.json +2 -2
  35. package/src/generated/wspace-operations.ts +134 -929
  36. package/src/generated/wspace-types.ts +91730 -40059
  37. package/src/movethewheels/hooks/index.ts +9 -0
  38. package/src/movethewheels/hooks/useFleet.ts +133 -3
  39. package/src/movethewheels/hooks/useImportExport.ts +36 -72
  40. package/src/movethewheels/hooks/useInventory.ts +311 -53
  41. package/src/movethewheels/hooks/useOrders.ts +31 -7
  42. package/src/movethewheels/hooks/useReports.ts +73 -87
  43. package/src/movethewheels/hooks/useSettings.ts +50 -53
  44. package/src/movethewheels/hooks/useTracking.ts +14 -14
  45. package/src/movethewheels/pages/CreateOrderPage.tsx +67 -10
  46. package/src/movethewheels/pages/DeliveryCapturePage.tsx +8 -0
  47. package/src/movethewheels/pages/DriversPage.tsx +181 -31
  48. package/src/movethewheels/pages/InventoryPage.tsx +299 -19
  49. package/src/movethewheels/pages/MarketplacePage.tsx +16 -539
  50. package/src/movethewheels/pages/ProductDetailsPage.tsx +68 -3
  51. package/src/movethewheels/pages/ReportsPage.tsx +23 -0
  52. package/src/movethewheels/pages/VehicleDetailsPage.tsx +86 -27
  53. package/src/movethewheels/types/index.ts +2 -0
  54. package/src/operations/wspace/movethewheels/fragments/core.graphql +23 -53
  55. package/src/operations/wspace/movethewheels/fragments/core.withdrawn.graphql +59 -0
  56. package/src/operations/wspace/movethewheels/mutations/entities.graphql +5 -5
  57. package/src/operations/wspace/movethewheels/mutations/marketplace-reports.graphql +18 -78
  58. package/src/operations/wspace/movethewheels/mutations/marketplace-reports.withdrawn.graphql +93 -0
  59. package/src/operations/wspace/movethewheels/mutations/orders.graphql +7 -7
  60. package/src/operations/wspace/movethewheels/queries/dashboard.graphql +3 -3
  61. package/src/operations/wspace/movethewheels/queries/entities.graphql +3 -3
  62. package/src/operations/wspace/movethewheels/queries/logistics.graphql +3 -3
  63. package/src/operations/wspace/movethewheels/queries/marketplace-reports.withdrawn.graphql +58 -0
  64. package/src/operations/wspace/movethewheels/queries/orders.graphql +3 -3
  65. package/dist/generated/global-operations.d.ts +0 -1
  66. package/dist/generated/global-types.d.ts +0 -23526
  67. package/dist/node_modules/lucide-react/dist/esm/icons/chevron-down.js +0 -8
  68. package/dist/node_modules/lucide-react/dist/esm/icons/chevron-up.js +0 -8
  69. package/src/generated/global-operations.ts +0 -2
  70. package/src/generated/global-types.ts +0 -27259
  71. package/src/operations/wspace/movethewheels/queries/marketplace-reports.graphql +0 -40
@@ -1,6 +1,5 @@
1
- import { useCallback, useEffect, useMemo, useState } from 'react';
1
+ import { useCallback, useEffect, useState } from 'react';
2
2
  import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
3
- import { executeGraphQL } from '../utils/graphqlClient';
4
3
 
5
4
  export interface MovethewheelsReport {
6
5
  id: string;
@@ -20,46 +19,6 @@ export interface GeneratedReport {
20
19
  report: MovethewheelsReport;
21
20
  }
22
21
 
23
- const REPORT_FIELDS = `
24
- id
25
- name
26
- description
27
- type
28
- status
29
- schedule
30
- lastGenerated
31
- createdAt
32
- updatedAt
33
- `;
34
-
35
- const LIST_REPORTS_QUERY = `
36
- query MovethewheelsReports($filter: MovethewheelsReportFilterInput, $pagination: PaginationInput) {
37
- movethewheelsReports(filter: $filter, pagination: $pagination) {
38
- ${REPORT_FIELDS}
39
- }
40
- }
41
- `;
42
-
43
- const CREATE_REPORT_MUTATION = `
44
- mutation CreateMovethewheelsReport($input: CreateMovethewheelsReportInput!) {
45
- createMovethewheelsReport(input: $input) {
46
- ${REPORT_FIELDS}
47
- }
48
- }
49
- `;
50
-
51
- const GENERATE_REPORT_MUTATION = `
52
- mutation GenerateMovethewheelsReport($input: GenerateMovethewheelsReportInput!) {
53
- generateMovethewheelsReport(input: $input) {
54
- generatedAt
55
- result
56
- report {
57
- ${REPORT_FIELDS}
58
- }
59
- }
60
- }
61
- `;
62
-
63
22
  export interface CreateReportInput {
64
23
  name: string;
65
24
  type: string;
@@ -87,44 +46,60 @@ interface UseReportsResult {
87
46
  ) => Promise<GeneratedReport>;
88
47
  }
89
48
 
49
+ function newId(): string {
50
+ if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {
51
+ return crypto.randomUUID();
52
+ }
53
+ return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
54
+ }
55
+
56
+ function storageKey(workspaceId: string | undefined): string {
57
+ return `movethewheels:reports:${workspaceId ?? 'default'}`;
58
+ }
59
+
60
+ function readReports(workspaceId: string | undefined): MovethewheelsReport[] {
61
+ if (typeof window === 'undefined') return [];
62
+ try {
63
+ const raw = window.localStorage.getItem(storageKey(workspaceId));
64
+ if (!raw) return [];
65
+ const parsed = JSON.parse(raw) as MovethewheelsReport[];
66
+ return Array.isArray(parsed) ? parsed : [];
67
+ } catch {
68
+ return [];
69
+ }
70
+ }
71
+
72
+ function writeReports(workspaceId: string | undefined, reports: MovethewheelsReport[]): void {
73
+ if (typeof window === 'undefined') return;
74
+ try {
75
+ window.localStorage.setItem(storageKey(workspaceId), JSON.stringify(reports));
76
+ } catch {
77
+ // Ignore storage errors.
78
+ }
79
+ }
80
+
90
81
  export function useReports(options: UseReportsOptions = {}): UseReportsResult {
91
82
  const { limit = 100, offset = 0 } = options;
92
- const { apiGatewayUrl, authToken, workspaceToken, workspaceId, tenantId, currentUser } =
93
- useMoveTheWheels();
83
+ const { workspaceId } = useMoveTheWheels();
94
84
  const [reports, setReports] = useState<MovethewheelsReport[]>([]);
95
85
  const [hasMore, setHasMore] = useState(false);
96
86
  const [isLoading, setIsLoading] = useState(false);
97
87
  const [error, setError] = useState<Error | null>(null);
98
88
 
99
- const graphQLOptions = useMemo(
100
- () => ({
101
- apiGatewayUrl,
102
- authToken,
103
- workspaceToken,
104
- workspaceId,
105
- tenantId,
106
- actorId: currentUser?.id,
107
- }),
108
- [apiGatewayUrl, authToken, workspaceToken, workspaceId, tenantId, currentUser?.id]
109
- );
110
-
111
- const load = useCallback(async () => {
89
+ const load = useCallback(() => {
112
90
  setIsLoading(true);
113
91
  setError(null);
114
92
  try {
115
- const data = await executeGraphQL<{ movethewheelsReports: MovethewheelsReport[] }>(
116
- graphQLOptions,
117
- LIST_REPORTS_QUERY,
118
- { pagination: { limit, offset } }
119
- );
120
- setHasMore(data.movethewheelsReports.length === limit);
121
- setReports(data.movethewheelsReports);
122
- } catch (fetchError) {
123
- setError(fetchError instanceof Error ? fetchError : new Error('Failed to load reports'));
93
+ const all = readReports(workspaceId);
94
+ const page = all.slice(offset, offset + limit);
95
+ setHasMore(page.length === limit);
96
+ setReports(page);
97
+ } catch (loadError) {
98
+ setError(loadError instanceof Error ? loadError : new Error('Failed to load reports'));
124
99
  } finally {
125
100
  setIsLoading(false);
126
101
  }
127
- }, [graphQLOptions, limit, offset]);
102
+ }, [workspaceId, limit, offset]);
128
103
 
129
104
  useEffect(() => {
130
105
  void load();
@@ -136,33 +111,44 @@ export function useReports(options: UseReportsOptions = {}): UseReportsResult {
136
111
 
137
112
  const createReport = useCallback(
138
113
  async (input: CreateReportInput): Promise<MovethewheelsReport> => {
139
- const data = await executeGraphQL<{ createMovethewheelsReport: MovethewheelsReport }>(
140
- graphQLOptions,
141
- CREATE_REPORT_MUTATION,
142
- { input }
143
- );
144
- const created = data.createMovethewheelsReport;
145
- setReports((previous) => [created, ...previous]);
114
+ const now = new Date().toISOString();
115
+ const created: MovethewheelsReport = {
116
+ id: newId(),
117
+ name: input.name,
118
+ description: input.description ?? null,
119
+ type: input.type,
120
+ status: input.status ?? 'ready',
121
+ schedule: input.schedule ?? null,
122
+ lastGenerated: null,
123
+ createdAt: now,
124
+ updatedAt: now,
125
+ };
126
+ setReports((previous) => {
127
+ const next = [created, ...previous];
128
+ writeReports(workspaceId, next);
129
+ return next;
130
+ });
146
131
  return created;
147
132
  },
148
- [graphQLOptions]
133
+ [workspaceId]
149
134
  );
150
135
 
151
136
  const generateReport = useCallback(
152
- async (reportId: string, parameters?: Record<string, unknown>): Promise<GeneratedReport> => {
153
- const data = await executeGraphQL<{ generateMovethewheelsReport: GeneratedReport }>(
154
- graphQLOptions,
155
- GENERATE_REPORT_MUTATION,
156
- { input: { reportId, parameters } }
157
- );
158
- const generated = data.generateMovethewheelsReport;
159
- setReports((previous) =>
160
- previous.map((item) => (item.id === reportId ? generated.report : item))
137
+ async (_reportId: string, _parameters?: Record<string, unknown>): Promise<GeneratedReport> => {
138
+ throw new Error(
139
+ 'Report generation is not available: the movethewheelsReports surface was withdrawn from the service.'
161
140
  );
162
- return generated;
163
141
  },
164
- [graphQLOptions]
142
+ []
165
143
  );
166
144
 
167
- return { reports, hasMore, isLoading, error, refetch, createReport, generateReport };
145
+ return {
146
+ reports,
147
+ hasMore,
148
+ isLoading,
149
+ error,
150
+ refetch,
151
+ createReport,
152
+ generateReport,
153
+ };
168
154
  }
@@ -1,6 +1,5 @@
1
1
  import { useCallback, useEffect, useMemo, useState } from 'react';
2
2
  import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
3
- import { executeGraphQL } from '../utils/graphqlClient';
4
3
 
5
4
  export type ModulePreferences = Record<string, unknown>;
6
5
 
@@ -10,26 +9,6 @@ export interface ModuleSettings {
10
9
  workspaceId: string;
11
10
  }
12
11
 
13
- const MODULE_SETTINGS_QUERY = `
14
- query MovethewheelsModuleSettings {
15
- moduleSettings {
16
- preferences
17
- updatedAt
18
- workspaceId
19
- }
20
- }
21
- `;
22
-
23
- const UPDATE_MODULE_SETTINGS_MUTATION = `
24
- mutation UpdateMovethewheelsModuleSettings($input: UpdateMovethewheelsModuleSettingsInput!) {
25
- updateModuleSettings(input: $input) {
26
- preferences
27
- updatedAt
28
- workspaceId
29
- }
30
- }
31
- `;
32
-
33
12
  interface UseModuleSettingsResult {
34
13
  settings: ModuleSettings | null;
35
14
  preferences: ModulePreferences;
@@ -40,41 +19,58 @@ interface UseModuleSettingsResult {
40
19
  savePreferences: (preferences: ModulePreferences) => Promise<ModuleSettings>;
41
20
  }
42
21
 
22
+ function readSettings(workspaceId: string): ModuleSettings | null {
23
+ if (typeof window === 'undefined') return null;
24
+ try {
25
+ const raw = window.localStorage.getItem(`movethewheels:moduleSettings:${workspaceId}`);
26
+ if (!raw) return null;
27
+ const parsed = JSON.parse(raw) as ModuleSettings;
28
+ if (parsed && typeof parsed === 'object' && 'preferences' in parsed) {
29
+ return parsed;
30
+ }
31
+ return null;
32
+ } catch {
33
+ return null;
34
+ }
35
+ }
36
+
37
+ function writeSettings(settings: ModuleSettings): void {
38
+ if (typeof window === 'undefined') return;
39
+ try {
40
+ window.localStorage.setItem(
41
+ `movethewheels:moduleSettings:${settings.workspaceId}`,
42
+ JSON.stringify(settings)
43
+ );
44
+ } catch {
45
+ // Ignore storage errors (e.g. private mode quota).
46
+ }
47
+ }
48
+
43
49
  export function useModuleSettings(): UseModuleSettingsResult {
44
- const { apiGatewayUrl, authToken, workspaceToken, workspaceId, tenantId, currentUser } =
45
- useMoveTheWheels();
50
+ const { workspaceId } = useMoveTheWheels();
46
51
  const [settings, setSettings] = useState<ModuleSettings | null>(null);
47
52
  const [isLoading, setIsLoading] = useState(false);
48
53
  const [isSaving, setIsSaving] = useState(false);
49
54
  const [error, setError] = useState<Error | null>(null);
50
55
 
51
- const graphQLOptions = useMemo(
52
- () => ({
53
- apiGatewayUrl,
54
- authToken,
55
- workspaceToken,
56
- workspaceId,
57
- tenantId,
58
- actorId: currentUser?.id,
59
- }),
60
- [apiGatewayUrl, authToken, workspaceToken, workspaceId, tenantId, currentUser?.id]
61
- );
62
-
63
- const load = useCallback(async () => {
56
+ const load = useCallback(() => {
64
57
  setIsLoading(true);
65
58
  setError(null);
66
59
  try {
67
- const data = await executeGraphQL<{ moduleSettings: ModuleSettings }>(
68
- graphQLOptions,
69
- MODULE_SETTINGS_QUERY
60
+ const stored = workspaceId ? readSettings(workspaceId) : null;
61
+ setSettings(
62
+ stored ?? {
63
+ preferences: {},
64
+ updatedAt: new Date().toISOString(),
65
+ workspaceId: workspaceId ?? '',
66
+ }
70
67
  );
71
- setSettings(data.moduleSettings);
72
- } catch (fetchError) {
73
- setError(fetchError instanceof Error ? fetchError : new Error('Failed to load settings'));
68
+ } catch (loadError) {
69
+ setError(loadError instanceof Error ? loadError : new Error('Failed to load settings'));
74
70
  } finally {
75
71
  setIsLoading(false);
76
72
  }
77
- }, [graphQLOptions]);
73
+ }, [workspaceId]);
78
74
 
79
75
  useEffect(() => {
80
76
  void load();
@@ -89,23 +85,24 @@ export function useModuleSettings(): UseModuleSettingsResult {
89
85
  setIsSaving(true);
90
86
  setError(null);
91
87
  try {
92
- const data = await executeGraphQL<{ updateModuleSettings: ModuleSettings }>(
93
- graphQLOptions,
94
- UPDATE_MODULE_SETTINGS_MUTATION,
95
- { input: { preferences } }
96
- );
97
- setSettings(data.updateModuleSettings);
98
- return data.updateModuleSettings;
99
- } catch (fetchError) {
88
+ const next: ModuleSettings = {
89
+ preferences,
90
+ updatedAt: new Date().toISOString(),
91
+ workspaceId: workspaceId ?? '',
92
+ };
93
+ writeSettings(next);
94
+ setSettings(next);
95
+ return next;
96
+ } catch (saveError) {
100
97
  const normalized =
101
- fetchError instanceof Error ? fetchError : new Error('Failed to save settings');
98
+ saveError instanceof Error ? saveError : new Error('Failed to save settings');
102
99
  setError(normalized);
103
100
  throw normalized;
104
101
  } finally {
105
102
  setIsSaving(false);
106
103
  }
107
104
  },
108
- [graphQLOptions]
105
+ [workspaceId]
109
106
  );
110
107
 
111
108
  // Stabilize the preferences reference. Returning `settings?.preferences ?? {}`
@@ -30,7 +30,7 @@ interface ApiTrackingEvent {
30
30
  interface ApiShipment {
31
31
  id: string;
32
32
  shipmentNumber: string;
33
- orderId: string;
33
+ deliveryJobId?: string | null;
34
34
  status: string;
35
35
  estimatedDelivery?: string | null;
36
36
  trackingEvents?: ApiTrackingEvent[];
@@ -41,7 +41,7 @@ const SHIPMENTS_QUERY = `
41
41
  shipments(filter: $filter, pagination: $pagination) {
42
42
  id
43
43
  shipmentNumber
44
- orderId
44
+ deliveryJobId
45
45
  status
46
46
  estimatedDelivery
47
47
  }
@@ -53,7 +53,7 @@ const SHIPMENT_QUERY = `
53
53
  shipment(id: $id) {
54
54
  id
55
55
  shipmentNumber
56
- orderId
56
+ deliveryJobId
57
57
  status
58
58
  estimatedDelivery
59
59
  trackingEvents {
@@ -68,9 +68,9 @@ const SHIPMENT_QUERY = `
68
68
  }
69
69
  `;
70
70
 
71
- const ORDER_TRACKING_QUERY = `
72
- query GetOrderTracking($orderId: ID!) {
73
- orderTracking(orderId: $orderId) {
71
+ const DELIVERY_JOB_TRACKING_QUERY = `
72
+ query GetDeliveryJobTracking($deliveryJobId: ID!) {
73
+ deliveryJobTracking(deliveryJobId: $deliveryJobId) {
74
74
  id
75
75
  shipmentId
76
76
  eventType
@@ -86,7 +86,7 @@ const ACTIVE_DELIVERIES_QUERY = `
86
86
  activeDeliveries {
87
87
  id
88
88
  shipmentNumber
89
- orderId
89
+ deliveryJobId
90
90
  status
91
91
  estimatedDelivery
92
92
  }
@@ -163,7 +163,7 @@ export function useTracking(options: UseTrackingOptions = {}): UseTrackingResult
163
163
 
164
164
  const mapped = data.shipments.map((shipment) => ({
165
165
  id: shipment.id,
166
- entityId: shipment.orderId,
166
+ entityId: shipment.deliveryJobId ?? shipment.id,
167
167
  entityType: 'order' as const,
168
168
  currentLocation: {
169
169
  latitude: 0,
@@ -224,14 +224,14 @@ export function useOrderTracking(orderId: string | undefined) {
224
224
  setIsLoading(true);
225
225
  setError(null);
226
226
  try {
227
- const data = await executeGraphQL<{ orderTracking: ApiTrackingEvent[] }>(
227
+ const data = await executeGraphQL<{ deliveryJobTracking: ApiTrackingEvent[] }>(
228
228
  graphQLOptions,
229
- ORDER_TRACKING_QUERY,
229
+ DELIVERY_JOB_TRACKING_QUERY,
230
230
  {
231
- orderId,
231
+ deliveryJobId: orderId,
232
232
  }
233
233
  );
234
- const latest = data.orderTracking[data.orderTracking.length - 1];
234
+ const latest = data.deliveryJobTracking[data.deliveryJobTracking.length - 1];
235
235
  setTracking(latest ? mapTrackingEventToTracking(latest, orderId) : null);
236
236
  } catch (fetchError) {
237
237
  setError(
@@ -327,7 +327,7 @@ export function useLiveTracking() {
327
327
 
328
328
  const shipmentEvents = shipmentData.shipment?.trackingEvents ?? [];
329
329
  const latestEvent = shipmentEvents[shipmentEvents.length - 1];
330
- const order = orders.find((item) => item.id === shipment.orderId);
330
+ const order = orders.find((item) => item.id === shipment.deliveryJobId);
331
331
  if (!order) {
332
332
  return null;
333
333
  }
@@ -346,7 +346,7 @@ export function useLiveTracking() {
346
346
  driver,
347
347
  vehicle,
348
348
  tracking: latestEvent
349
- ? mapTrackingEventToTracking(latestEvent, shipment.orderId)
349
+ ? mapTrackingEventToTracking(latestEvent, shipment.deliveryJobId ?? shipment.id)
350
350
  : null,
351
351
  estimatedArrival: shipment.estimatedDelivery
352
352
  ? new Date(shipment.estimatedDelivery)
@@ -6,7 +6,7 @@
6
6
 
7
7
  import { useEffect, useState } from 'react';
8
8
  import { useParams } from 'react-router';
9
- import { Package, ArrowLeft, Save, Plus, Trash2, Users, MapPin } from 'lucide-react';
9
+ import { Package, ArrowLeft, Save, Plus, Trash2, Users, MapPin, CalendarClock } from 'lucide-react';
10
10
  import { PageLayout, EmptyState } from '../components/PageLayout';
11
11
  import { FormError } from '../components/FormError';
12
12
  import {
@@ -31,6 +31,22 @@ import { useCustomers } from '../hooks/useCustomers';
31
31
  import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
32
32
  import { joinPath } from '../utils/navigation';
33
33
 
34
+ /**
35
+ * `<input type="datetime-local">` needs a `YYYY-MM-DDTHH:mm` string in LOCAL
36
+ * time — `Date.toISOString()` would shift the value by the UTC offset and show
37
+ * the operator a different slot than the one stored. Returns '' for no date.
38
+ */
39
+ function toDateTimeLocalValue(date: Date | undefined): string {
40
+ if (!date || Number.isNaN(date.getTime())) {
41
+ return '';
42
+ }
43
+ const pad = (value: number) => String(value).padStart(2, '0');
44
+ return (
45
+ `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}` +
46
+ `T${pad(date.getHours())}:${pad(date.getMinutes())}`
47
+ );
48
+ }
49
+
34
50
  export default function CreateOrderPage() {
35
51
  const { orderId } = useParams<{ orderId: string }>();
36
52
  const { basePath, navigate } = useMoveTheWheels();
@@ -58,6 +74,7 @@ export default function CreateOrderPage() {
58
74
  country: order?.deliveryAddress.country || 'USA',
59
75
  },
60
76
  items: order?.items || [],
77
+ scheduledDelivery: toDateTimeLocalValue(order?.scheduledDelivery),
61
78
  specialInstructions: order?.specialInstructions || '',
62
79
  });
63
80
 
@@ -89,6 +106,7 @@ export default function CreateOrderPage() {
89
106
  country: order.deliveryAddress.country || 'USA',
90
107
  },
91
108
  items: order.items || [],
109
+ scheduledDelivery: toDateTimeLocalValue(order.scheduledDelivery),
92
110
  specialInstructions: order.specialInstructions || '',
93
111
  });
94
112
  }, [order]);
@@ -113,6 +131,9 @@ export default function CreateOrderPage() {
113
131
  if (formData.items.length === 0) {
114
132
  return 'Add at least one item to the order.';
115
133
  }
134
+ if (formData.scheduledDelivery && Number.isNaN(Date.parse(formData.scheduledDelivery))) {
135
+ return 'Enter a valid scheduled delivery date and time.';
136
+ }
116
137
  return null;
117
138
  };
118
139
 
@@ -146,6 +167,8 @@ export default function CreateOrderPage() {
146
167
  pickupAddress: formData.pickupAddress,
147
168
  deliveryAddress: formData.deliveryAddress,
148
169
  items: formData.items,
170
+ // `null` clears an existing slot on edit; the service field is nullable.
171
+ scheduledDelivery: formData.scheduledDelivery ? new Date(formData.scheduledDelivery) : null,
149
172
  specialInstructions: formData.specialInstructions,
150
173
  };
151
174
 
@@ -466,7 +489,7 @@ export default function CreateOrderPage() {
466
489
  {formData.items.map((item, index) => (
467
490
  <div key={item.id}>
468
491
  {index > 0 && <Separator className="my-4" />}
469
- <div className="grid grid-cols-1 md:grid-cols-6 gap-4">
492
+ <div className="grid grid-cols-1 md:grid-cols-7 gap-4">
470
493
  <div className="md:col-span-2 space-y-2">
471
494
  <Label>Item Name</Label>
472
495
  <Input
@@ -494,6 +517,18 @@ export default function CreateOrderPage() {
494
517
  }
495
518
  />
496
519
  </div>
520
+ <div className="space-y-2">
521
+ <Label>Weight (kg)</Label>
522
+ <Input
523
+ type="number"
524
+ min="0"
525
+ step="0.1"
526
+ value={item.weight}
527
+ onChange={(e) =>
528
+ updateItem(index, 'weight', parseFloat(e.target.value) || 0)
529
+ }
530
+ />
531
+ </div>
497
532
  <div className="space-y-2">
498
533
  <Label>Value ($)</Label>
499
534
  <Input
@@ -525,18 +560,40 @@ export default function CreateOrderPage() {
525
560
  </CardContent>
526
561
  </Card>
527
562
 
528
- {/* Special Instructions */}
563
+ {/* Scheduling & handling notes */}
529
564
  <Card>
530
565
  <CardHeader>
531
- <CardTitle>Special Instructions</CardTitle>
566
+ <CardTitle>Scheduling &amp; Handling</CardTitle>
532
567
  </CardHeader>
533
568
  <CardContent>
534
- <Textarea
535
- value={formData.specialInstructions}
536
- onChange={(e) => updateField('specialInstructions', e.target.value)}
537
- placeholder="Any special handling or delivery instructions..."
538
- rows={4}
539
- />
569
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
570
+ <div className="space-y-2">
571
+ <Label htmlFor="scheduledDelivery" className="flex items-center gap-1.5">
572
+ <CalendarClock size={14} className="text-text-secondary" />
573
+ Scheduled Delivery
574
+ </Label>
575
+ <Input
576
+ id="scheduledDelivery"
577
+ type="datetime-local"
578
+ value={formData.scheduledDelivery}
579
+ onChange={(e) => updateField('scheduledDelivery', e.target.value)}
580
+ />
581
+ <p className="text-xs text-text-secondary">
582
+ The delivery window promised to the customer. Leave blank to dispatch as soon as a
583
+ driver is free.
584
+ </p>
585
+ </div>
586
+ <div className="md:col-span-2 space-y-2">
587
+ <Label htmlFor="specialInstructions">Special Instructions</Label>
588
+ <Textarea
589
+ id="specialInstructions"
590
+ value={formData.specialInstructions}
591
+ onChange={(e) => updateField('specialInstructions', e.target.value)}
592
+ placeholder="Any special handling or delivery instructions..."
593
+ rows={4}
594
+ />
595
+ </div>
596
+ </div>
540
597
  </CardContent>
541
598
  </Card>
542
599
  </form>
@@ -38,6 +38,8 @@ import {
38
38
  Textarea,
39
39
  } from '../components/ui';
40
40
  import { SignaturePad, type SignaturePadHandle } from '../components/SignaturePad';
41
+ import { StatusBadge, PriorityBadge } from '../components/StatusBadge';
42
+ import { mapOrderPriority, mapOrderStatus } from '../hooks/useOrders';
41
43
  import { FormError } from '../components/FormError';
42
44
  import { Notice, useNotice } from '../components/Notice';
43
45
  import {
@@ -284,6 +286,12 @@ export default function DeliveryCapturePage() {
284
286
  <p className="font-medium flex items-center gap-2">
285
287
  <Package size={16} /> {job.deliveryJobNumber}
286
288
  </p>
289
+ {/* Stop state + urgency, straight off the job record — a driver
290
+ needs to know which stop is already out for delivery. */}
291
+ <div className="flex flex-wrap items-center gap-1.5 py-1">
292
+ <StatusBadge status={mapOrderStatus(job.status)} type="order" size="sm" />
293
+ <PriorityBadge priority={mapOrderPriority(job.priority)} size="sm" />
294
+ </div>
287
295
  {job.deliveryAddress && (
288
296
  <p className="text-sm text-text-secondary truncate">
289
297
  {formatAddress(job.deliveryAddress)}