@burdenoff/microfe-movethewheels 2026.925.1 → 2026.925.3
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/StatusBadge.js +31 -31
- package/dist/constants/index.js +19 -5
- package/dist/hooks/useCustomers.js +0 -3
- package/dist/hooks/useFleet.js +197 -175
- package/dist/hooks/useInventory.js +144 -145
- package/dist/hooks/useRoutes.js +5 -14
- package/dist/movethewheels/components/StatusBadge.d.ts +3 -1
- package/dist/movethewheels/constants/index.d.ts +2 -0
- package/dist/movethewheels/hooks/useFleet.d.ts +53 -1
- package/dist/movethewheels/hooks/useInventory.d.ts +10 -0
- package/dist/movethewheels/types/index.d.ts +29 -108
- package/dist/node_modules/lucide-react/dist/esm/icons/sticky-note.js +11 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/user-x.js +30 -0
- package/dist/pages/CustomerDetailsPage.js +666 -344
- package/dist/pages/CustomersPage.js +71 -80
- package/dist/pages/DriverDetailsPage.js +702 -421
- package/dist/pages/DriversPage.js +106 -117
- package/dist/pages/FleetPage.js +5 -13
- package/dist/pages/InventoryPage.js +4 -4
- package/dist/pages/OrderDetailsPage.js +279 -274
- package/dist/pages/RouteDetailsPage.js +300 -263
- package/dist/pages/RoutesPage.js +130 -67
- package/dist/pages/VehicleDetailsPage.js +562 -287
- package/dist/pages/VehiclesPage.js +119 -70
- package/dist/pages/WarehouseDetailsPage.js +410 -370
- package/dist/pages/WarehousesPage.js +131 -82
- package/package.json +1 -1
- package/src/movethewheels/components/StatusBadge.tsx +16 -2
- package/src/movethewheels/constants/index.ts +21 -0
- package/src/movethewheels/hooks/useCustomers.ts +0 -3
- package/src/movethewheels/hooks/useFleet.ts +236 -62
- package/src/movethewheels/hooks/useInventory.ts +76 -32
- package/src/movethewheels/hooks/useRoutes.ts +17 -12
- package/src/movethewheels/pages/CustomerDetailsPage.tsx +376 -109
- package/src/movethewheels/pages/CustomersPage.tsx +6 -15
- package/src/movethewheels/pages/DriverDetailsPage.tsx +516 -156
- package/src/movethewheels/pages/DriversPage.tsx +28 -11
- package/src/movethewheels/pages/FleetPage.tsx +9 -11
- package/src/movethewheels/pages/InventoryPage.tsx +14 -5
- package/src/movethewheels/pages/OrderDetailsPage.tsx +12 -7
- package/src/movethewheels/pages/RouteDetailsPage.tsx +89 -41
- package/src/movethewheels/pages/RoutesPage.tsx +66 -2
- package/src/movethewheels/pages/VehicleDetailsPage.tsx +282 -20
- package/src/movethewheels/pages/VehiclesPage.tsx +68 -3
- package/src/movethewheels/pages/WarehouseDetailsPage.tsx +213 -135
- package/src/movethewheels/pages/WarehousesPage.tsx +77 -16
- package/src/movethewheels/types/index.ts +30 -108
|
@@ -1,85 +1,140 @@
|
|
|
1
1
|
import { CircleCheck as e } from "../node_modules/lucide-react/dist/esm/icons/circle-check.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
2
|
+
import { Filter as t } from "../node_modules/lucide-react/dist/esm/icons/filter.js";
|
|
3
|
+
import { Gauge as n } from "../node_modules/lucide-react/dist/esm/icons/gauge.js";
|
|
4
|
+
import { Plus as r } from "../node_modules/lucide-react/dist/esm/icons/plus.js";
|
|
5
|
+
import { RefreshCw as i } from "../node_modules/lucide-react/dist/esm/icons/refresh-cw.js";
|
|
6
|
+
import { Warehouse as a } from "../node_modules/lucide-react/dist/esm/icons/warehouse.js";
|
|
7
|
+
import { joinPath as o } from "../utils/navigation.js";
|
|
8
|
+
import { useMoveTheWheels as s } from "../providers/MoveTheWheelsProvider.js";
|
|
9
|
+
import { useWarehouseStats as c, useWarehouses as l } from "../hooks/useInventory.js";
|
|
10
|
+
import { Badge as u, Button as d, Card as f, CardContent as p, Progress as m, Select as h, SelectContent as g, SelectItem as _, SelectTrigger as v, SelectValue as y } from "../components/ui.js";
|
|
11
|
+
import { PageLayout as b } from "../components/PageLayout.js";
|
|
12
|
+
import { MetricCard as x, MetricGrid as S } from "../components/MetricCard.js";
|
|
13
|
+
import { DataTable as C } from "../components/DataTable.js";
|
|
14
|
+
import { useDebouncedValue as w } from "../hooks/useDebouncedValue.js";
|
|
15
|
+
import { useState as T } from "react";
|
|
16
|
+
import { jsx as E, jsxs as D } from "react/jsx-runtime";
|
|
17
|
+
import { CTAOverflowMenu as O, PagePurpose as k } from "@burdenoff/fe-libs/ui";
|
|
17
18
|
//#region src/movethewheels/pages/WarehousesPage.tsx
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
var A = [
|
|
20
|
+
{
|
|
21
|
+
value: "all",
|
|
22
|
+
label: "All statuses"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
value: "ACTIVE",
|
|
26
|
+
label: "Active"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
value: "INACTIVE",
|
|
30
|
+
label: "Inactive"
|
|
31
|
+
}
|
|
32
|
+
];
|
|
33
|
+
function j() {
|
|
34
|
+
let { basePath: j, navigate: M } = s(), [N, P] = T(0), [F, I] = T(25), [L, R] = T(""), [z, B] = T("all"), V = w(L), { warehouses: H, totalWarehouses: U, hasMore: W, isLoading: G, error: K, refetch: q } = l({
|
|
35
|
+
limit: F,
|
|
36
|
+
offset: N * F,
|
|
37
|
+
search: V,
|
|
38
|
+
status: z === "all" ? void 0 : z
|
|
39
|
+
}), J = z !== "all" || L.length > 0, { stats: Y } = c(), X = (e) => M?.(o(j, e));
|
|
40
|
+
return /* @__PURE__ */ D(b, {
|
|
25
41
|
title: "Warehouses",
|
|
26
|
-
subtitle: `${
|
|
27
|
-
icon: /* @__PURE__ */
|
|
28
|
-
headerActions: /* @__PURE__ */
|
|
42
|
+
subtitle: `${Y.total} warehouses`,
|
|
43
|
+
icon: /* @__PURE__ */ E(a, { size: 24 }),
|
|
44
|
+
headerActions: /* @__PURE__ */ E(O, {
|
|
29
45
|
primary: {
|
|
30
46
|
label: "Add Warehouse",
|
|
31
|
-
icon: /* @__PURE__ */
|
|
32
|
-
onSelect: () =>
|
|
47
|
+
icon: /* @__PURE__ */ E(r, { size: 16 }),
|
|
48
|
+
onSelect: () => X("inventory/warehouses/new")
|
|
33
49
|
},
|
|
34
50
|
actions: [{
|
|
35
51
|
label: "Refresh",
|
|
36
|
-
icon: /* @__PURE__ */
|
|
37
|
-
onSelect: () =>
|
|
52
|
+
icon: /* @__PURE__ */ E(i, { size: 16 }),
|
|
53
|
+
onSelect: () => q()
|
|
38
54
|
}]
|
|
39
55
|
}),
|
|
40
56
|
children: [
|
|
41
|
-
/* @__PURE__ */
|
|
42
|
-
icon: /* @__PURE__ */
|
|
57
|
+
/* @__PURE__ */ E(k, {
|
|
58
|
+
icon: /* @__PURE__ */ E(a, { size: 18 }),
|
|
43
59
|
children: "Your storage and fulfilment sites. Watch space and pallet utilization across each warehouse to balance stock, spot capacity crunches, and decide where to fulfil an order from."
|
|
44
60
|
}),
|
|
45
|
-
/* @__PURE__ */
|
|
61
|
+
/* @__PURE__ */ D("div", { children: [/* @__PURE__ */ E("p", {
|
|
46
62
|
className: "mb-3 text-sm font-medium text-text-secondary",
|
|
47
63
|
children: "Network at a glance"
|
|
48
|
-
}), /* @__PURE__ */
|
|
64
|
+
}), /* @__PURE__ */ D(S, {
|
|
49
65
|
columns: 3,
|
|
50
66
|
children: [
|
|
51
|
-
/* @__PURE__ */
|
|
67
|
+
/* @__PURE__ */ E(x, {
|
|
52
68
|
label: "Total sites",
|
|
53
|
-
value:
|
|
54
|
-
icon: /* @__PURE__ */
|
|
69
|
+
value: Y.total,
|
|
70
|
+
icon: /* @__PURE__ */ E(a, { size: 20 }),
|
|
55
71
|
accent: "brand"
|
|
56
72
|
}),
|
|
57
|
-
/* @__PURE__ */
|
|
73
|
+
/* @__PURE__ */ E(x, {
|
|
58
74
|
label: "Active",
|
|
59
|
-
value:
|
|
60
|
-
icon: /* @__PURE__ */
|
|
75
|
+
value: Y.active,
|
|
76
|
+
icon: /* @__PURE__ */ E(e, { size: 20 }),
|
|
61
77
|
accent: "success",
|
|
62
78
|
subtitle: "Fulfilling orders"
|
|
63
79
|
}),
|
|
64
|
-
/* @__PURE__ */
|
|
80
|
+
/* @__PURE__ */ E(x, {
|
|
65
81
|
label: "Avg utilization",
|
|
66
|
-
value:
|
|
67
|
-
icon: /* @__PURE__ */
|
|
82
|
+
value: Y.total > 0 ? `${Math.round(Y.avgUtilization * 100)}%` : "—",
|
|
83
|
+
icon: /* @__PURE__ */ E(n, { size: 20 }),
|
|
68
84
|
accent: "blue"
|
|
69
85
|
})
|
|
70
86
|
]
|
|
71
87
|
})] }),
|
|
72
|
-
/* @__PURE__ */
|
|
88
|
+
/* @__PURE__ */ E(f, { children: /* @__PURE__ */ E(p, {
|
|
89
|
+
className: "p-4",
|
|
90
|
+
children: /* @__PURE__ */ D("div", {
|
|
91
|
+
className: "flex flex-wrap items-center gap-4",
|
|
92
|
+
children: [
|
|
93
|
+
/* @__PURE__ */ D("div", {
|
|
94
|
+
className: "flex items-center gap-2",
|
|
95
|
+
children: [/* @__PURE__ */ E(t, {
|
|
96
|
+
size: 16,
|
|
97
|
+
className: "text-text-secondary"
|
|
98
|
+
}), /* @__PURE__ */ E("span", {
|
|
99
|
+
className: "text-sm font-medium",
|
|
100
|
+
children: "Filters:"
|
|
101
|
+
})]
|
|
102
|
+
}),
|
|
103
|
+
/* @__PURE__ */ D(h, {
|
|
104
|
+
value: z,
|
|
105
|
+
onValueChange: (e) => {
|
|
106
|
+
B(e), P(0);
|
|
107
|
+
},
|
|
108
|
+
children: [/* @__PURE__ */ E(v, {
|
|
109
|
+
className: "w-[180px]",
|
|
110
|
+
children: /* @__PURE__ */ E(y, { placeholder: "Status" })
|
|
111
|
+
}), /* @__PURE__ */ E(g, { children: A.map((e) => /* @__PURE__ */ E(_, {
|
|
112
|
+
value: e.value,
|
|
113
|
+
children: e.label
|
|
114
|
+
}, e.value)) })]
|
|
115
|
+
}),
|
|
116
|
+
J && /* @__PURE__ */ E(d, {
|
|
117
|
+
variant: "ghost",
|
|
118
|
+
size: "sm",
|
|
119
|
+
onClick: () => {
|
|
120
|
+
B("all"), R(""), P(0);
|
|
121
|
+
},
|
|
122
|
+
children: "Clear filters"
|
|
123
|
+
})
|
|
124
|
+
]
|
|
125
|
+
})
|
|
126
|
+
}) }),
|
|
127
|
+
/* @__PURE__ */ E(C, {
|
|
73
128
|
columns: [
|
|
74
129
|
{
|
|
75
130
|
id: "name",
|
|
76
131
|
header: "Warehouse",
|
|
77
132
|
accessorKey: "name",
|
|
78
133
|
sortable: !0,
|
|
79
|
-
cell: (e) => /* @__PURE__ */
|
|
134
|
+
cell: (e) => /* @__PURE__ */ D("div", { children: [/* @__PURE__ */ E("p", {
|
|
80
135
|
className: "font-medium",
|
|
81
136
|
children: e.name
|
|
82
|
-
}), /* @__PURE__ */
|
|
137
|
+
}), /* @__PURE__ */ E("p", {
|
|
83
138
|
className: "text-sm text-text-secondary",
|
|
84
139
|
children: e.code
|
|
85
140
|
})] })
|
|
@@ -87,11 +142,11 @@ function x() {
|
|
|
87
142
|
{
|
|
88
143
|
id: "location",
|
|
89
144
|
header: "Location",
|
|
90
|
-
cell: (e) => /* @__PURE__ */
|
|
145
|
+
cell: (e) => /* @__PURE__ */ D("div", { children: [/* @__PURE__ */ D("p", { children: [
|
|
91
146
|
e.address.city,
|
|
92
147
|
", ",
|
|
93
148
|
e.address.state
|
|
94
|
-
] }), /* @__PURE__ */
|
|
149
|
+
] }), /* @__PURE__ */ E("p", {
|
|
95
150
|
className: "text-sm text-text-secondary",
|
|
96
151
|
children: e.address.country
|
|
97
152
|
})] })
|
|
@@ -100,7 +155,7 @@ function x() {
|
|
|
100
155
|
id: "type",
|
|
101
156
|
header: "Type",
|
|
102
157
|
accessorKey: "type",
|
|
103
|
-
cell: (e) => /* @__PURE__ */
|
|
158
|
+
cell: (e) => /* @__PURE__ */ E(u, {
|
|
104
159
|
variant: "outline",
|
|
105
160
|
className: "capitalize",
|
|
106
161
|
children: e.type.replace("_", " ")
|
|
@@ -111,12 +166,12 @@ function x() {
|
|
|
111
166
|
header: "Utilization",
|
|
112
167
|
cell: (e) => {
|
|
113
168
|
let t = e.capacity.usedSpace / e.capacity.totalSpace * 100;
|
|
114
|
-
return /* @__PURE__ */
|
|
169
|
+
return /* @__PURE__ */ D("div", {
|
|
115
170
|
className: "flex items-center gap-2",
|
|
116
|
-
children: [/* @__PURE__ */
|
|
171
|
+
children: [/* @__PURE__ */ E(m, {
|
|
117
172
|
value: t,
|
|
118
173
|
className: "w-16 h-2"
|
|
119
|
-
}), /* @__PURE__ */
|
|
174
|
+
}), /* @__PURE__ */ D("span", {
|
|
120
175
|
className: "text-sm",
|
|
121
176
|
children: [t.toFixed(0), "%"]
|
|
122
177
|
})]
|
|
@@ -126,55 +181,49 @@ function x() {
|
|
|
126
181
|
{
|
|
127
182
|
id: "capacity",
|
|
128
183
|
header: "Capacity",
|
|
129
|
-
cell: (e) => /* @__PURE__ */
|
|
184
|
+
cell: (e) => /* @__PURE__ */ D("p", {
|
|
130
185
|
className: "text-sm",
|
|
131
|
-
children: [
|
|
186
|
+
children: [
|
|
132
187
|
e.capacity.usedSpace.toLocaleString(),
|
|
133
188
|
" / ",
|
|
134
189
|
e.capacity.totalSpace.toLocaleString(),
|
|
135
|
-
" "
|
|
136
|
-
|
|
137
|
-
] }), /* @__PURE__ */ v("p", {
|
|
138
|
-
className: "text-text-secondary",
|
|
139
|
-
children: [
|
|
140
|
-
e.capacity.usedPallets,
|
|
141
|
-
" / ",
|
|
142
|
-
e.capacity.totalPallets,
|
|
143
|
-
" pallets"
|
|
144
|
-
]
|
|
145
|
-
})]
|
|
190
|
+
" sq ft"
|
|
191
|
+
]
|
|
146
192
|
})
|
|
147
193
|
},
|
|
148
194
|
{
|
|
149
195
|
id: "status",
|
|
150
196
|
header: "Status",
|
|
151
|
-
cell: (e) =>
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
197
|
+
cell: (e) => {
|
|
198
|
+
let t = e.status.toUpperCase() === "ACTIVE";
|
|
199
|
+
return /* @__PURE__ */ E(u, {
|
|
200
|
+
variant: t ? "default" : "secondary",
|
|
201
|
+
children: t ? "Active" : "Inactive"
|
|
202
|
+
});
|
|
203
|
+
}
|
|
155
204
|
}
|
|
156
205
|
],
|
|
157
|
-
data:
|
|
158
|
-
isLoading:
|
|
159
|
-
error:
|
|
160
|
-
onRetry:
|
|
206
|
+
data: H,
|
|
207
|
+
isLoading: G,
|
|
208
|
+
error: K,
|
|
209
|
+
onRetry: q,
|
|
161
210
|
searchable: !0,
|
|
162
211
|
searchPlaceholder: "Search warehouses...",
|
|
163
212
|
searchFields: ["name", "code"],
|
|
164
|
-
searchQuery:
|
|
213
|
+
searchQuery: L,
|
|
165
214
|
onSearchQueryChange: (e) => {
|
|
166
|
-
|
|
215
|
+
R(e), P(0);
|
|
167
216
|
},
|
|
168
|
-
onRowClick: (e) =>
|
|
217
|
+
onRowClick: (e) => X(`inventory/warehouses/${e.id}`),
|
|
169
218
|
getRowId: (e) => e.id,
|
|
170
|
-
pageIndex:
|
|
171
|
-
pageSize:
|
|
172
|
-
onPageChange:
|
|
219
|
+
pageIndex: N,
|
|
220
|
+
pageSize: F,
|
|
221
|
+
onPageChange: P,
|
|
173
222
|
onPageSizeChange: (e) => {
|
|
174
|
-
|
|
223
|
+
I(e), P(0);
|
|
175
224
|
},
|
|
176
|
-
totalItems:
|
|
177
|
-
hasNextPage:
|
|
225
|
+
totalItems: U,
|
|
226
|
+
hasNextPage: W,
|
|
178
227
|
emptyIllustration: "empty-data",
|
|
179
228
|
emptyTitle: "No warehouses yet",
|
|
180
229
|
emptyDescription: "Add a warehouse to store inventory and fulfil orders from it."
|
|
@@ -183,4 +232,4 @@ function x() {
|
|
|
183
232
|
});
|
|
184
233
|
}
|
|
185
234
|
//#endregion
|
|
186
|
-
export {
|
|
235
|
+
export { j as default };
|
package/package.json
CHANGED
|
@@ -4,7 +4,9 @@ import type { OrderStatus, VehicleStatus, DriverStatus } from '../types';
|
|
|
4
4
|
import {
|
|
5
5
|
ORDER_STATUS_COLORS,
|
|
6
6
|
VEHICLE_STATUS_COLORS,
|
|
7
|
+
VEHICLE_STATUS_LABELS,
|
|
7
8
|
DRIVER_STATUS_COLORS,
|
|
9
|
+
DRIVER_STATUS_LABELS,
|
|
8
10
|
PRIORITY_COLORS,
|
|
9
11
|
} from '../constants';
|
|
10
12
|
|
|
@@ -17,6 +19,8 @@ interface StatusBadgeProps {
|
|
|
17
19
|
bg: string;
|
|
18
20
|
text: string;
|
|
19
21
|
};
|
|
22
|
+
/** Override the auto-derived display text (title-cased raw status). */
|
|
23
|
+
label?: string;
|
|
20
24
|
size?: 'sm' | 'md' | 'lg';
|
|
21
25
|
showDot?: boolean;
|
|
22
26
|
className?: string;
|
|
@@ -35,6 +39,7 @@ export function StatusBadge({
|
|
|
35
39
|
status,
|
|
36
40
|
type = 'custom',
|
|
37
41
|
customColors,
|
|
42
|
+
label,
|
|
38
43
|
size = 'md',
|
|
39
44
|
showDot = true,
|
|
40
45
|
className,
|
|
@@ -59,8 +64,17 @@ export function StatusBadge({
|
|
|
59
64
|
}
|
|
60
65
|
}
|
|
61
66
|
|
|
62
|
-
// Format status for display
|
|
63
|
-
|
|
67
|
+
// Format status for display — an explicit label always wins; driver status
|
|
68
|
+
// otherwise renders from the same shared label map the filters use, so the
|
|
69
|
+
// table and the filter dropdown can never drift apart again (BOFF-7909).
|
|
70
|
+
const sharedLabel =
|
|
71
|
+
type === 'driver'
|
|
72
|
+
? DRIVER_STATUS_LABELS[status]
|
|
73
|
+
: type === 'vehicle'
|
|
74
|
+
? VEHICLE_STATUS_LABELS[status]
|
|
75
|
+
: undefined;
|
|
76
|
+
const displayStatus =
|
|
77
|
+
label ?? sharedLabel ?? status.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
|
|
64
78
|
|
|
65
79
|
return (
|
|
66
80
|
<Badge
|
|
@@ -57,6 +57,15 @@ export const VEHICLE_STATUS_COLORS: Record<string, StatusColor> = {
|
|
|
57
57
|
assigned: INFO,
|
|
58
58
|
maintenance: WARNING,
|
|
59
59
|
out_of_service: ERROR,
|
|
60
|
+
decommissioned: NEUTRAL,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const VEHICLE_STATUS_LABELS: Record<string, string> = {
|
|
64
|
+
available: 'Available',
|
|
65
|
+
assigned: 'In Transit',
|
|
66
|
+
maintenance: 'Maintenance',
|
|
67
|
+
out_of_service: 'Out of Service',
|
|
68
|
+
decommissioned: 'Decommissioned',
|
|
60
69
|
};
|
|
61
70
|
|
|
62
71
|
// Driver status colors
|
|
@@ -65,6 +74,18 @@ export const DRIVER_STATUS_COLORS: Record<string, StatusColor> = {
|
|
|
65
74
|
busy: INFO,
|
|
66
75
|
offline: NEUTRAL,
|
|
67
76
|
on_break: WARNING,
|
|
77
|
+
inactive: ERROR,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// Single source of truth for driver status display text — used by both the
|
|
81
|
+
// DriversPage status filter and the roster table's StatusBadge, so the two
|
|
82
|
+
// can never drift apart again (BOFF-7909).
|
|
83
|
+
export const DRIVER_STATUS_LABELS: Record<string, string> = {
|
|
84
|
+
available: 'Available',
|
|
85
|
+
busy: 'On Delivery',
|
|
86
|
+
offline: 'Off Duty',
|
|
87
|
+
on_break: 'On Break',
|
|
88
|
+
inactive: 'Inactive',
|
|
68
89
|
};
|
|
69
90
|
|
|
70
91
|
// Priority colors
|
|
@@ -142,9 +142,6 @@ function mapCustomer(customer: ApiCustomer): Customer {
|
|
|
142
142
|
},
|
|
143
143
|
businessLicense: customer.businessLicense ?? undefined,
|
|
144
144
|
taxId: customer.taxId ?? undefined,
|
|
145
|
-
preferredContactMethod: 'email',
|
|
146
|
-
marketingOptIn: false,
|
|
147
|
-
isActive: !customer.deletedAt,
|
|
148
145
|
rating: customer.rating ?? 0,
|
|
149
146
|
createdAt: new Date(customer.createdAt),
|
|
150
147
|
updatedAt: new Date(customer.updatedAt),
|