@evenicanpm/storefront-core 2.1.0 → 2.3.0
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/package.json +9 -4
- package/src/api-manager/README.md +52 -27
- package/src/api-manager/datasources/d365/d365-cart.datasource.ts +166 -17
- package/src/api-manager/datasources/d365/d365-invoice.datasource.ts +100 -0
- package/src/api-manager/datasources/d365/d365-order.datasource.ts +75 -18
- package/src/api-manager/datasources/d365/d365-product.datasource.ts +19 -1
- package/src/api-manager/datasources/d365/d365-user.datasource.ts +96 -22
- package/src/api-manager/datasources/d365/d365.datasource.ts +3 -0
- package/src/api-manager/datasources/d365/utils/get-context-cookie.ts +14 -5
- package/src/api-manager/datasources/e4/e4.datasource.ts +9 -0
- package/src/api-manager/datasources/e4/middleware.ts +35 -0
- package/src/api-manager/datasources/e4/order/e4-order.datasource.ts +7 -4
- package/src/api-manager/datasources/e4/user/e4-user.datasource.ts +4 -0
- package/src/api-manager/index.ts +54 -3
- package/src/api-manager/lib/get-graphql-client.ts +13 -6
- package/src/api-manager/schemas/cart.schema.ts +10 -1
- package/src/api-manager/schemas/invoice.schema.ts +55 -0
- package/src/api-manager/schemas/order.schema.ts +13 -1
- package/src/api-manager/schemas/user.schema.ts +8 -0
- package/src/api-manager/services/create-query.ts +5 -56
- package/src/api-manager/services/invoice/queries/get-invoice-details.ts +18 -0
- package/src/api-manager/services/invoice/queries/get-invoices.ts +18 -0
- package/src/api-manager/services/order/queries/get-order-details.ts +1 -1
- package/src/api-manager/services/order/queries/get-orders.ts +6 -3
- package/src/api-manager/services/user/queries/get-customer-balance.ts +20 -0
- package/src/api-manager/types/Datasource.ts +21 -2
- package/src/auth/better-auth.ts +89 -0
- package/src/auth/signout.ts +5 -34
- package/src/cms/blocks/block-manager.tsx +18 -3
- package/src/cms/blocks/components/cta-banner.tsx +65 -0
- package/src/cms/blocks/components/hero-image.tsx +83 -0
- package/src/cms/blocks/index.tsx +2 -0
- package/src/cms/blocks/interfaces.ts +20 -0
- package/src/cms/draft-mode-badge.tsx +26 -0
- package/src/cms/queries.ts +81 -0
- package/src/components/flex-box/flex-box.tsx +2 -0
- package/src/components/header/__tests__/header.test.tsx +9 -2
- package/src/components/header/__tests__/user.test.tsx +34 -19
- package/src/components/header/components/user.tsx +70 -29
- package/src/components/mini-cart/mini-cart.tsx +3 -3
- package/src/components/wishlist-dialogs/add-to-wishlist/compound/add-to-wishlist-dialog.tsx +3 -1
- package/src/components/wishlist-dialogs/add-to-wishlist/compound/wishlist-dialog-header.tsx +4 -1
- package/src/hooks/use-nextauth-session.ts +10 -14
- package/src/pages/account/account-navigation.tsx +88 -60
- package/src/pages/account/account-routes.ts +52 -0
- package/src/pages/account/orders/order-history-filter-types.tsx +26 -0
- package/src/pages/account/orders/order-history-filters-panel.tsx +375 -0
- package/src/pages/account/orders/order-history-header.tsx +54 -0
- package/src/pages/account/orders/order-history-pagination.tsx +55 -0
- package/src/pages/account/orders/order-history-root.tsx +125 -0
- package/src/pages/account/orders/order-history-row.tsx +110 -0
- package/src/pages/account/orders/order-history-search-and-filter.tsx +449 -0
- package/src/pages/account/orders/order-history-search-bar.tsx +84 -0
- package/src/pages/account/orders/order-history-search-dropdown.tsx +53 -0
- package/src/pages/account/orders/order-history-sort.tsx +90 -0
- package/src/pages/account/orders/order-history-table.tsx +54 -0
- package/src/pages/account/orders/order-row.tsx +2 -2
- package/src/pages/account/orders/orders-drop-down-handler.tsx +19 -0
- package/src/pages/account/table-header.tsx +20 -0
- package/src/pages/account/wishlist/wishlist-item.tsx +1 -1
- package/src/pages/blog/blog-card.tsx +9 -3
- package/src/pages/blog/blog-detail-view.tsx +150 -0
- package/src/pages/blog/blog-list-view.tsx +59 -0
- package/src/pages/cart/__tests__/checkout-form.test.tsx +4 -2
- package/src/pages/cart/estimate-shipping.tsx +6 -5
- package/src/pages/checkout/checkout-alt-form/checkout-form.tsx +43 -21
- package/src/pages/checkout/checkout-alt-form/steps/payment/payment-details.tsx +144 -19
- package/src/pages/cms-page-view.tsx +53 -0
- package/src/pages/quickorder/order-upload.tsx +12 -6
- package/src/pages/quickorder/quick-order.tsx +25 -20
- package/src/api-manager/datasources/d365/utils/decode-jwt.ts +0 -10
- package/src/auth/auth-options.test.ts +0 -70
- package/src/auth/auth-options.ts +0 -101
- package/src/auth/next-auth-cookie-manager.ts +0 -87
- package/src/auth/providers/aadb2c-provider.ts +0 -32
- package/src/auth/providers/authentik-provider.ts +0 -24
- package/src/auth/providers/entra-external-id-provider.ts +0 -50
- package/src/auth/providers/index.ts +0 -16
- package/src/auth/providers/keycloak-provider.ts +0 -22
- package/src/auth/refresh-token.ts +0 -132
- package/src/auth/types/next-auth.d.ts +0 -38
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { SalesOrder } from "@evenicanpm/storefront-core/src/api-manager/schemas/order.schema";
|
|
2
|
+
// GLOBAL CUSTOM COMPONENT
|
|
3
|
+
import {
|
|
4
|
+
H6,
|
|
5
|
+
Paragraph,
|
|
6
|
+
} from "@evenicanpm/storefront-core/src/components/Typography";
|
|
7
|
+
import useCurrencyFormatter from "@evenicanpm/storefront-core/src/lib/currency-formatter";
|
|
8
|
+
// CUSTOM UTILS LIBRARY FUNCTION;
|
|
9
|
+
import TableRow from "@evenicanpm/storefront-core/src/pages/account/table-row";
|
|
10
|
+
import type { ChipProps } from "@mui/material";
|
|
11
|
+
import Box from "@mui/material/Box";
|
|
12
|
+
import Chip from "@mui/material/Chip";
|
|
13
|
+
import IconButton from "@mui/material/IconButton";
|
|
14
|
+
import { format } from "date-fns";
|
|
15
|
+
import Link from "next/link";
|
|
16
|
+
import { createContext, useContext } from "react";
|
|
17
|
+
import { MdEast as East } from "react-icons/md";
|
|
18
|
+
|
|
19
|
+
const STATUS_MAP: Record<number, { label: string; color: ChipProps["color"] }> =
|
|
20
|
+
{
|
|
21
|
+
1: { label: "Created", color: "secondary" },
|
|
22
|
+
2: { label: "Processing", color: "secondary" },
|
|
23
|
+
3: { label: "Delivered", color: "success" },
|
|
24
|
+
4: { label: "Invoiced", color: "primary" },
|
|
25
|
+
7: { label: "Cancelled", color: "primary" },
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const getStatusInfo = (status: number) => {
|
|
29
|
+
return STATUS_MAP[status] ?? { label: "Pending", color: "default" };
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type OrderHistoryTableRowContextType = {
|
|
33
|
+
order: SalesOrder;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const OrderHistoryTableRowContext = createContext<
|
|
37
|
+
OrderHistoryTableRowContextType | undefined
|
|
38
|
+
>(undefined);
|
|
39
|
+
|
|
40
|
+
export const useOrderHistoryTableRow = () => {
|
|
41
|
+
const context = useContext(OrderHistoryTableRowContext);
|
|
42
|
+
if (!context) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
"Context must be used within a OrderHistoryTableRowContextProvider",
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
return context;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const OrderHistoryTableRow = ({ children }: { children: React.ReactNode }) => {
|
|
51
|
+
const { order } = useOrderHistoryTableRow();
|
|
52
|
+
const useTransactionId = order.Id && order.Id !== " ";
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<Link
|
|
56
|
+
href={`/account/orders/${useTransactionId ? order.Id : order.SalesId}/?id=${useTransactionId ? "TransactionId" : "SalesId"}`}
|
|
57
|
+
>
|
|
58
|
+
<TableRow sx={{ gridTemplateColumns: "2fr 1fr 1fr 1fr 1fr" }}>
|
|
59
|
+
{children}
|
|
60
|
+
</TableRow>
|
|
61
|
+
</Link>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
OrderHistoryTableRow.OrderNumber = () => {
|
|
66
|
+
const { order } = useOrderHistoryTableRow();
|
|
67
|
+
return <H6 ellipsis>{order?.ChannelReferenceId}</H6>;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
OrderHistoryTableRow.OrderStatus = () => {
|
|
71
|
+
const { order } = useOrderHistoryTableRow();
|
|
72
|
+
const { label, color } = getStatusInfo(order.StatusValue ?? 0);
|
|
73
|
+
return (
|
|
74
|
+
<Box textAlign="center">
|
|
75
|
+
<Chip size="small" label={label} color={color} />
|
|
76
|
+
</Box>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
OrderHistoryTableRow.TransactionDate = () => {
|
|
81
|
+
const { order } = useOrderHistoryTableRow();
|
|
82
|
+
return (
|
|
83
|
+
<Paragraph textAlign={{ sm: "center", xs: "left" }}>
|
|
84
|
+
{format(new Date(order.CreatedDateTime ?? ""), "MMM dd, yyyy")}
|
|
85
|
+
</Paragraph>
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
OrderHistoryTableRow.TransactionAmount = () => {
|
|
90
|
+
const { order } = useOrderHistoryTableRow();
|
|
91
|
+
const { formatCurrency } = useCurrencyFormatter();
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<Paragraph textAlign="center">
|
|
95
|
+
{formatCurrency(order.TotalAmount ?? 0)}
|
|
96
|
+
</Paragraph>
|
|
97
|
+
);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
OrderHistoryTableRow.DetailsIcon = () => {
|
|
101
|
+
return (
|
|
102
|
+
<Box display={{ sm: "inline-flex", xs: "none" }} justifyContent="end">
|
|
103
|
+
<IconButton sx={{ color: "grey.500" }}>
|
|
104
|
+
<East fontSize="large" />
|
|
105
|
+
</IconButton>
|
|
106
|
+
</Box>
|
|
107
|
+
);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export default OrderHistoryTableRow;
|
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
import getStoresList from "@evenicanpm/storefront-core/src/api-manager/services/organization/queries/get-stores";
|
|
2
|
+
import type {
|
|
3
|
+
OrderSearchCriteria,
|
|
4
|
+
SalesStatus,
|
|
5
|
+
} from "@msdyn365-commerce/retail-proxy";
|
|
6
|
+
import { Box } from "@mui/material";
|
|
7
|
+
import { createContext, useContext, useState } from "react";
|
|
8
|
+
import {
|
|
9
|
+
type CreatedDateFilter,
|
|
10
|
+
FilterSourceType,
|
|
11
|
+
type OrderStatusFilter,
|
|
12
|
+
type OriginFilter,
|
|
13
|
+
} from "./order-history-filter-types";
|
|
14
|
+
import OrdersFilterPanel from "./order-history-filters-panel";
|
|
15
|
+
import { useOrderHistory } from "./order-history-root";
|
|
16
|
+
import OrdersSearchBar from "./order-history-search-bar";
|
|
17
|
+
|
|
18
|
+
// interface OrdersSearchAndFilterProps {
|
|
19
|
+
// handleSearch: (criteria: OrderSearchCriteria) => void;
|
|
20
|
+
// }
|
|
21
|
+
|
|
22
|
+
// export default function OrdersSearchAndFilter({
|
|
23
|
+
// handleSearch,
|
|
24
|
+
// }: OrdersSearchAndFilterProps) {
|
|
25
|
+
// const { data: stores } = getStoresList.useData({
|
|
26
|
+
// queryResultSettings: {
|
|
27
|
+
// Paging: {
|
|
28
|
+
// Skip: 0,
|
|
29
|
+
// Top: 1000,
|
|
30
|
+
// },
|
|
31
|
+
// },
|
|
32
|
+
// searchArea: {
|
|
33
|
+
// DistanceUnitValue: 0,
|
|
34
|
+
// Latitude: 0,
|
|
35
|
+
// Longitude: 0,
|
|
36
|
+
// Radius: 0,
|
|
37
|
+
// },
|
|
38
|
+
// });
|
|
39
|
+
|
|
40
|
+
// const originOptions: OriginFilter[] =
|
|
41
|
+
// stores?.map((store) => ({
|
|
42
|
+
// sourceType: FilterSourceType.Origin,
|
|
43
|
+
// label: store.location.OrgUnitName || "",
|
|
44
|
+
// value: store.location.ChannelId || 0,
|
|
45
|
+
// })) || [];
|
|
46
|
+
|
|
47
|
+
// // Search bar
|
|
48
|
+
// const [searchText, setSearchText] = useState<string>("");
|
|
49
|
+
// const [selectedSearchOption, setSelectedSearchOption] = useState<number>(0);
|
|
50
|
+
|
|
51
|
+
// // Filters
|
|
52
|
+
// const [selectedOrigins, setSelectedOrigins] = useState<OriginFilter[]>([]);
|
|
53
|
+
// const [createdDate, setCreatedDate] = useState<CreatedDateFilter>();
|
|
54
|
+
// const [orderStatus, setOrderStatus] = useState<OrderStatusFilter>();
|
|
55
|
+
|
|
56
|
+
// const handleSelectCreatedDate = (
|
|
57
|
+
// date: CreatedDateFilter | undefined,
|
|
58
|
+
// toggle = true,
|
|
59
|
+
// ) => {
|
|
60
|
+
// toggle
|
|
61
|
+
// ? setCreatedDate((prev) =>
|
|
62
|
+
// prev?.label === date?.label ? undefined : date,
|
|
63
|
+
// )
|
|
64
|
+
// : setCreatedDate(date);
|
|
65
|
+
// };
|
|
66
|
+
|
|
67
|
+
// const handleSelectOrderStatus = (status: OrderStatusFilter | undefined) => {
|
|
68
|
+
// setOrderStatus((prev) =>
|
|
69
|
+
// prev?.label === status?.label ? undefined : status,
|
|
70
|
+
// );
|
|
71
|
+
// };
|
|
72
|
+
|
|
73
|
+
// const handleSelectOrigin = (origin: OriginFilter) => {
|
|
74
|
+
// setSelectedOrigins((prev) => {
|
|
75
|
+
// const exists = prev.some((o) => o.label === origin.label);
|
|
76
|
+
// return exists
|
|
77
|
+
// ? prev.filter((o) => o.label !== origin.label)
|
|
78
|
+
// : [...prev, origin];
|
|
79
|
+
// });
|
|
80
|
+
// };
|
|
81
|
+
|
|
82
|
+
// const executeSearch = () => {
|
|
83
|
+
// const searchCriteria = createSearchCriteria();
|
|
84
|
+
// handleSearch(searchCriteria);
|
|
85
|
+
// };
|
|
86
|
+
|
|
87
|
+
// const createSearchCriteria = (): OrderSearchCriteria => {
|
|
88
|
+
// const orderSearchCriteria: OrderSearchCriteria = {};
|
|
89
|
+
|
|
90
|
+
// const channelIds: number[] = selectedOrigins.map((origin) => origin.value);
|
|
91
|
+
// if (channelIds.length > 0) {
|
|
92
|
+
// orderSearchCriteria.ChannelIds = channelIds;
|
|
93
|
+
// }
|
|
94
|
+
// const salesStatus = orderStatus?.value ?? undefined;
|
|
95
|
+
// const salesStatuses: SalesStatus[] =
|
|
96
|
+
// salesStatus !== undefined ? [salesStatus] : [];
|
|
97
|
+
// if (salesStatuses.length > 0) {
|
|
98
|
+
// orderSearchCriteria.OrderStatusValues = salesStatuses;
|
|
99
|
+
// }
|
|
100
|
+
|
|
101
|
+
// if (createdDate) {
|
|
102
|
+
// const { startDate, endDate } = getDateRange(createdDate);
|
|
103
|
+
// orderSearchCriteria.StartDateTime = startDate;
|
|
104
|
+
// orderSearchCriteria.EndDateTime = endDate;
|
|
105
|
+
// }
|
|
106
|
+
|
|
107
|
+
// if (searchText) {
|
|
108
|
+
// if (selectedSearchOption === 0) {
|
|
109
|
+
// orderSearchCriteria.ChannelReferenceId = searchText;
|
|
110
|
+
// } else if (selectedSearchOption === 1) {
|
|
111
|
+
// orderSearchCriteria.SalesId = searchText;
|
|
112
|
+
// } else if (selectedSearchOption === 2) {
|
|
113
|
+
// const channelIds = stores
|
|
114
|
+
// ?.filter((store) =>
|
|
115
|
+
// store.location.OrgUnitName?.toLocaleLowerCase().includes(
|
|
116
|
+
// searchText.toLowerCase(),
|
|
117
|
+
// ),
|
|
118
|
+
// )
|
|
119
|
+
// .map((store) => store.location.ChannelId)
|
|
120
|
+
// .filter((id): id is number => id !== undefined);
|
|
121
|
+
|
|
122
|
+
// if (channelIds?.length) {
|
|
123
|
+
// if (orderSearchCriteria.ChannelIds?.length) {
|
|
124
|
+
// // Find interesting channels between searched name and selected filters
|
|
125
|
+
// const intersectingChannelIds = channelIds.filter((id) =>
|
|
126
|
+
// orderSearchCriteria.ChannelIds?.includes(id),
|
|
127
|
+
// );
|
|
128
|
+
|
|
129
|
+
// orderSearchCriteria.ChannelIds = intersectingChannelIds;
|
|
130
|
+
// } else {
|
|
131
|
+
// orderSearchCriteria.ChannelIds = channelIds;
|
|
132
|
+
// }
|
|
133
|
+
// }
|
|
134
|
+
// }
|
|
135
|
+
// }
|
|
136
|
+
|
|
137
|
+
// return orderSearchCriteria;
|
|
138
|
+
// };
|
|
139
|
+
|
|
140
|
+
// const getDateRange = (
|
|
141
|
+
// option: CreatedDateFilter | undefined,
|
|
142
|
+
// ): {
|
|
143
|
+
// startDate: Date;
|
|
144
|
+
// endDate: Date;
|
|
145
|
+
// } => {
|
|
146
|
+
// const endDate = new Date(); // today
|
|
147
|
+
// const startDate = new Date();
|
|
148
|
+
|
|
149
|
+
// switch (option?.label) {
|
|
150
|
+
// case "Last 7 days":
|
|
151
|
+
// startDate.setDate(endDate.getDate() - 7);
|
|
152
|
+
// break;
|
|
153
|
+
// case "Last 30 days":
|
|
154
|
+
// startDate.setDate(endDate.getDate() - 30);
|
|
155
|
+
// break;
|
|
156
|
+
// case "Last 3 months":
|
|
157
|
+
// startDate.setMonth(endDate.getMonth() - 3);
|
|
158
|
+
// break;
|
|
159
|
+
// case "Last 6 months":
|
|
160
|
+
// startDate.setMonth(endDate.getMonth() - 6);
|
|
161
|
+
// break;
|
|
162
|
+
// case "Custom Dates":
|
|
163
|
+
// if (option.startValue && option.endValue) {
|
|
164
|
+
// const customStart = parseLocalDate(option.startValue);
|
|
165
|
+
// const customEnd = parseLocalDate(option.endValue, true);
|
|
166
|
+
// if (isNaN(customStart.getTime()) || isNaN(customEnd.getTime())) {
|
|
167
|
+
// throw new Error("Invalid date format in customized date range.");
|
|
168
|
+
// }
|
|
169
|
+
|
|
170
|
+
// startDate.setTime(customStart.getTime());
|
|
171
|
+
// endDate.setTime(customEnd.getTime());
|
|
172
|
+
// } else {
|
|
173
|
+
// throw new Error(
|
|
174
|
+
// "Customized date range must have start and end values.",
|
|
175
|
+
// );
|
|
176
|
+
// }
|
|
177
|
+
// break;
|
|
178
|
+
// default:
|
|
179
|
+
// throw new Error(`Unsupported date filter option: ${option}`);
|
|
180
|
+
// }
|
|
181
|
+
|
|
182
|
+
// return { startDate, endDate };
|
|
183
|
+
// };
|
|
184
|
+
|
|
185
|
+
// const parseLocalDate = (dateStr: string, endOfDay = false): Date => {
|
|
186
|
+
// const [year, month, day] = dateStr.split("-").map(Number);
|
|
187
|
+
// // month is 0-based in JS Date
|
|
188
|
+
// return endOfDay
|
|
189
|
+
// ? new Date(year, month - 1, day, 23, 59, 59, 999)
|
|
190
|
+
// : new Date(year, month - 1, day, 0, 0, 0, 0);
|
|
191
|
+
// };
|
|
192
|
+
|
|
193
|
+
// return (
|
|
194
|
+
// <Box>
|
|
195
|
+
// {/* SEARCH BAR */}
|
|
196
|
+
// <OrdersSearchBar
|
|
197
|
+
// setSearchText={setSearchText}
|
|
198
|
+
// selectedSearchOption={selectedSearchOption}
|
|
199
|
+
// setSelectedSearchOption={setSelectedSearchOption}
|
|
200
|
+
// executeSearch={executeSearch}
|
|
201
|
+
// />
|
|
202
|
+
|
|
203
|
+
// {/* FITLERS BAR */}
|
|
204
|
+
// <OrdersFilterPanel
|
|
205
|
+
// originOptions={originOptions}
|
|
206
|
+
// selectedOrigins={selectedOrigins}
|
|
207
|
+
// handleSelectOrigin={handleSelectOrigin}
|
|
208
|
+
// createdDate={createdDate}
|
|
209
|
+
// setCreatedDate={handleSelectCreatedDate}
|
|
210
|
+
// orderStatus={orderStatus}
|
|
211
|
+
// setOrderStatus={handleSelectOrderStatus}
|
|
212
|
+
// />
|
|
213
|
+
// </Box>
|
|
214
|
+
// );
|
|
215
|
+
// }
|
|
216
|
+
|
|
217
|
+
type OrdersSearchAndFilterContextType = {
|
|
218
|
+
// Search states
|
|
219
|
+
searchText: string;
|
|
220
|
+
setSearchText: (text: string) => void;
|
|
221
|
+
selectedSearchOption: number;
|
|
222
|
+
setSelectedSearchOption: (option: number) => void;
|
|
223
|
+
|
|
224
|
+
// Filter states
|
|
225
|
+
originOptions: OriginFilter[];
|
|
226
|
+
selectedOrigins: OriginFilter[];
|
|
227
|
+
handleSelectOrigin: (origin: OriginFilter) => void;
|
|
228
|
+
createdDate?: CreatedDateFilter;
|
|
229
|
+
handleSelectCreatedDate: (
|
|
230
|
+
date: CreatedDateFilter | undefined,
|
|
231
|
+
toggle?: boolean,
|
|
232
|
+
) => void;
|
|
233
|
+
orderStatus?: OrderStatusFilter;
|
|
234
|
+
handleSelectOrderStatus: (status: OrderStatusFilter | undefined) => void;
|
|
235
|
+
|
|
236
|
+
// Actions
|
|
237
|
+
executeSearch: () => void;
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const OrdersSearchAndFilterContext = createContext<
|
|
241
|
+
OrdersSearchAndFilterContextType | undefined
|
|
242
|
+
>(undefined);
|
|
243
|
+
|
|
244
|
+
export const useOrdersSearchAndFilter = () => {
|
|
245
|
+
const context = useContext(OrdersSearchAndFilterContext);
|
|
246
|
+
if (!context) {
|
|
247
|
+
throw new Error("Must be used within OrdersSearchAndFilterProvider");
|
|
248
|
+
}
|
|
249
|
+
return context;
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const OrdersSearchAndFilter = ({ children }: { children: React.ReactNode }) => {
|
|
253
|
+
const { setOrderSearchCriteria } = useOrderHistory();
|
|
254
|
+
const { data: stores } = getStoresList.useData({
|
|
255
|
+
queryResultSettings: {
|
|
256
|
+
Paging: {
|
|
257
|
+
Skip: 0,
|
|
258
|
+
Top: 1000,
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
searchArea: {
|
|
262
|
+
DistanceUnitValue: 0,
|
|
263
|
+
Latitude: 0,
|
|
264
|
+
Longitude: 0,
|
|
265
|
+
Radius: 0,
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// Search states
|
|
270
|
+
const [searchText, setSearchText] = useState<string>("");
|
|
271
|
+
const [selectedSearchOption, setSelectedSearchOption] = useState<number>(0);
|
|
272
|
+
|
|
273
|
+
// Filter states
|
|
274
|
+
const [selectedOrigins, setSelectedOrigins] = useState<OriginFilter[]>([]);
|
|
275
|
+
const [createdDate, setCreatedDate] = useState<CreatedDateFilter>();
|
|
276
|
+
const [orderStatus, setOrderStatus] = useState<OrderStatusFilter>();
|
|
277
|
+
|
|
278
|
+
const originOptions: OriginFilter[] =
|
|
279
|
+
stores?.map((store) => ({
|
|
280
|
+
sourceType: FilterSourceType.Origin,
|
|
281
|
+
label: store.location.OrgUnitName || "",
|
|
282
|
+
value: store.location.ChannelId || 0,
|
|
283
|
+
})) || [];
|
|
284
|
+
|
|
285
|
+
const handleSelectOrigin = (origin: OriginFilter) => {
|
|
286
|
+
setSelectedOrigins((prev) => {
|
|
287
|
+
const exists = prev.some((o) => o.label === origin.label);
|
|
288
|
+
return exists
|
|
289
|
+
? prev.filter((o) => o.label !== origin.label)
|
|
290
|
+
: [...prev, origin];
|
|
291
|
+
});
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
const handleSelectCreatedDate = (
|
|
295
|
+
date: CreatedDateFilter | undefined,
|
|
296
|
+
toggle = true,
|
|
297
|
+
) => {
|
|
298
|
+
toggle
|
|
299
|
+
? setCreatedDate((prev) =>
|
|
300
|
+
prev?.label === date?.label ? undefined : date,
|
|
301
|
+
)
|
|
302
|
+
: setCreatedDate(date);
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const handleSelectOrderStatus = (status: OrderStatusFilter | undefined) => {
|
|
306
|
+
setOrderStatus((prev) =>
|
|
307
|
+
prev?.label === status?.label ? undefined : status,
|
|
308
|
+
);
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
const executeSearch = () => {
|
|
312
|
+
const searchCriteria = createSearchCriteria();
|
|
313
|
+
setOrderSearchCriteria(searchCriteria);
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
const createSearchCriteria = (): OrderSearchCriteria => {
|
|
317
|
+
const orderSearchCriteria: OrderSearchCriteria = {};
|
|
318
|
+
|
|
319
|
+
const channelIds: number[] = selectedOrigins.map((origin) => origin.value);
|
|
320
|
+
if (channelIds.length > 0) {
|
|
321
|
+
orderSearchCriteria.ChannelIds = channelIds;
|
|
322
|
+
}
|
|
323
|
+
const salesStatus = orderStatus?.value ?? undefined;
|
|
324
|
+
const salesStatuses: SalesStatus[] =
|
|
325
|
+
salesStatus !== undefined ? [salesStatus] : [];
|
|
326
|
+
if (salesStatuses.length > 0) {
|
|
327
|
+
orderSearchCriteria.OrderStatusValues = salesStatuses;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (createdDate) {
|
|
331
|
+
const { startDate, endDate } = getDateRange(createdDate);
|
|
332
|
+
orderSearchCriteria.StartDateTime = startDate;
|
|
333
|
+
orderSearchCriteria.EndDateTime = endDate;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (searchText) {
|
|
337
|
+
if (selectedSearchOption === 0) {
|
|
338
|
+
orderSearchCriteria.ChannelReferenceId = searchText;
|
|
339
|
+
} else if (selectedSearchOption === 1) {
|
|
340
|
+
orderSearchCriteria.SalesId = searchText;
|
|
341
|
+
} else if (selectedSearchOption === 2) {
|
|
342
|
+
const channelIds = stores
|
|
343
|
+
?.filter((store) =>
|
|
344
|
+
store.location.OrgUnitName?.toLocaleLowerCase().includes(
|
|
345
|
+
searchText.toLowerCase(),
|
|
346
|
+
),
|
|
347
|
+
)
|
|
348
|
+
.map((store) => store.location.ChannelId)
|
|
349
|
+
.filter((id): id is number => id !== undefined);
|
|
350
|
+
|
|
351
|
+
if (channelIds?.length) {
|
|
352
|
+
if (orderSearchCriteria.ChannelIds?.length) {
|
|
353
|
+
const intersectingChannelIds = channelIds.filter((id) =>
|
|
354
|
+
orderSearchCriteria.ChannelIds?.includes(id),
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
orderSearchCriteria.ChannelIds = intersectingChannelIds;
|
|
358
|
+
} else {
|
|
359
|
+
orderSearchCriteria.ChannelIds = channelIds;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return orderSearchCriteria;
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
const getDateRange = (
|
|
369
|
+
option: CreatedDateFilter | undefined,
|
|
370
|
+
): {
|
|
371
|
+
startDate: Date;
|
|
372
|
+
endDate: Date;
|
|
373
|
+
} => {
|
|
374
|
+
const endDate = new Date();
|
|
375
|
+
const startDate = new Date();
|
|
376
|
+
|
|
377
|
+
switch (option?.label) {
|
|
378
|
+
case "Last 7 days":
|
|
379
|
+
startDate.setDate(endDate.getDate() - 7);
|
|
380
|
+
break;
|
|
381
|
+
case "Last 30 days":
|
|
382
|
+
startDate.setDate(endDate.getDate() - 30);
|
|
383
|
+
break;
|
|
384
|
+
case "Last 3 months":
|
|
385
|
+
startDate.setMonth(endDate.getMonth() - 3);
|
|
386
|
+
break;
|
|
387
|
+
case "Last 6 months":
|
|
388
|
+
startDate.setMonth(endDate.getMonth() - 6);
|
|
389
|
+
break;
|
|
390
|
+
case "Custom Dates":
|
|
391
|
+
if (option.startValue && option.endValue) {
|
|
392
|
+
const customStart = parseLocalDate(option.startValue);
|
|
393
|
+
const customEnd = parseLocalDate(option.endValue, true);
|
|
394
|
+
if (
|
|
395
|
+
Number.isNaN(customStart.getTime()) ||
|
|
396
|
+
Number.isNaN(customEnd.getTime())
|
|
397
|
+
) {
|
|
398
|
+
throw new Error("Invalid date format in customized date range.");
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
startDate.setTime(customStart.getTime());
|
|
402
|
+
endDate.setTime(customEnd.getTime());
|
|
403
|
+
} else {
|
|
404
|
+
throw new Error(
|
|
405
|
+
"Customized date range must have start and end values.",
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
break;
|
|
409
|
+
default:
|
|
410
|
+
throw new Error(`Unsupported date filter option: ${option}`);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return { startDate, endDate };
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
const parseLocalDate = (dateStr: string, endOfDay = false): Date => {
|
|
417
|
+
const [year, month, day] = dateStr.split("-").map(Number);
|
|
418
|
+
// month is 0-based in JS Date
|
|
419
|
+
return endOfDay
|
|
420
|
+
? new Date(year, month - 1, day, 23, 59, 59, 999)
|
|
421
|
+
: new Date(year, month - 1, day, 0, 0, 0, 0);
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
const value = {
|
|
425
|
+
searchText,
|
|
426
|
+
setSearchText,
|
|
427
|
+
selectedSearchOption,
|
|
428
|
+
setSelectedSearchOption,
|
|
429
|
+
originOptions,
|
|
430
|
+
selectedOrigins,
|
|
431
|
+
handleSelectOrigin,
|
|
432
|
+
createdDate,
|
|
433
|
+
handleSelectCreatedDate,
|
|
434
|
+
orderStatus,
|
|
435
|
+
handleSelectOrderStatus,
|
|
436
|
+
executeSearch,
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
return (
|
|
440
|
+
<OrdersSearchAndFilterContext.Provider value={value}>
|
|
441
|
+
<Box>{children}</Box>
|
|
442
|
+
</OrdersSearchAndFilterContext.Provider>
|
|
443
|
+
);
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
OrdersSearchAndFilter.Search = OrdersSearchBar;
|
|
447
|
+
OrdersSearchAndFilter.FiltersPanel = OrdersFilterPanel;
|
|
448
|
+
|
|
449
|
+
export default OrdersSearchAndFilter;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Box, Button, TextField } from "@mui/material";
|
|
2
|
+
import { useTranslations } from "next-intl";
|
|
3
|
+
import { useOrdersSearchAndFilter } from "./order-history-search-and-filter";
|
|
4
|
+
import OrdersSearchDropdown from "./order-history-search-dropdown";
|
|
5
|
+
|
|
6
|
+
export type DisplaySearchOption = {
|
|
7
|
+
name: string;
|
|
8
|
+
id: number;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const searchOptions: DisplaySearchOption[] = [
|
|
12
|
+
{ id: 0, name: "Order Number" },
|
|
13
|
+
{ id: 1, name: "Sales ID" },
|
|
14
|
+
{ id: 2, name: "Channel Name" },
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const OrdersSearchBar = () => {
|
|
18
|
+
const {
|
|
19
|
+
setSearchText,
|
|
20
|
+
selectedSearchOption,
|
|
21
|
+
setSelectedSearchOption,
|
|
22
|
+
executeSearch,
|
|
23
|
+
} = useOrdersSearchAndFilter();
|
|
24
|
+
const t = useTranslations("Account.Orders");
|
|
25
|
+
|
|
26
|
+
const findOptionNameById = () => {
|
|
27
|
+
return (
|
|
28
|
+
searchOptions.find((opt) => opt.id === selectedSearchOption)?.name || ""
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const INPUT_PROPS = {
|
|
33
|
+
sx: {
|
|
34
|
+
height: 44,
|
|
35
|
+
padding: 0,
|
|
36
|
+
overflow: "hidden",
|
|
37
|
+
backgroundColor: "grey.200",
|
|
38
|
+
"& .MuiOutlinedInput-notchedOutline": { border: 0 },
|
|
39
|
+
"& .MuiOutlinedInput-input": {
|
|
40
|
+
paddingLeft: 2,
|
|
41
|
+
paddingRight: 2,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
startAdornment: (
|
|
45
|
+
<OrdersSearchDropdown
|
|
46
|
+
title={findOptionNameById()}
|
|
47
|
+
handleChange={setSelectedSearchOption}
|
|
48
|
+
searchOptions={searchOptions}
|
|
49
|
+
/>
|
|
50
|
+
),
|
|
51
|
+
endAdornment: (
|
|
52
|
+
<Button
|
|
53
|
+
onClick={executeSearch}
|
|
54
|
+
color="primary"
|
|
55
|
+
disableElevation
|
|
56
|
+
variant="contained"
|
|
57
|
+
sx={{ px: "3rem", height: "100%", borderRadius: "0 4px 4px 0" }}
|
|
58
|
+
>
|
|
59
|
+
{t("searchBtn")}
|
|
60
|
+
</Button>
|
|
61
|
+
),
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<Box
|
|
66
|
+
position="relative"
|
|
67
|
+
flex="1 1 0"
|
|
68
|
+
maxWidth="670px"
|
|
69
|
+
mx="auto"
|
|
70
|
+
mr="auto"
|
|
71
|
+
ml="0px"
|
|
72
|
+
>
|
|
73
|
+
<TextField
|
|
74
|
+
fullWidth
|
|
75
|
+
variant="outlined"
|
|
76
|
+
placeholder={t("searchOrderHistoryPlaceholder")}
|
|
77
|
+
onChange={(e) => setSearchText(e.target.value)}
|
|
78
|
+
InputProps={INPUT_PROPS}
|
|
79
|
+
/>
|
|
80
|
+
</Box>
|
|
81
|
+
);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export default OrdersSearchBar;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import Menu from "@evenicanpm/storefront-core/src/components/BazaarMenu";
|
|
4
|
+
import TouchRipple from "@mui/material/ButtonBase";
|
|
5
|
+
import MenuItem from "@mui/material/MenuItem";
|
|
6
|
+
import useTheme from "@mui/material/styles/useTheme";
|
|
7
|
+
import { MdKeyboardArrowDown as KeyboardArrowDownOutlined } from "react-icons/md";
|
|
8
|
+
import type { DisplaySearchOption } from "./order-history-search-bar";
|
|
9
|
+
import { OrdersDropDownHandler } from "./orders-drop-down-handler";
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
title: string;
|
|
13
|
+
handleChange: (optionId: number) => void;
|
|
14
|
+
searchOptions: DisplaySearchOption[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default function OrdersSearchDropdown({
|
|
18
|
+
title,
|
|
19
|
+
handleChange,
|
|
20
|
+
searchOptions,
|
|
21
|
+
}: Props) {
|
|
22
|
+
const { breakpoints } = useTheme();
|
|
23
|
+
const _searchOptions = Array.isArray(searchOptions) ? searchOptions : [];
|
|
24
|
+
const options: DisplaySearchOption[] = [
|
|
25
|
+
..._searchOptions.map((opt) => ({ name: opt?.name ?? "", id: opt.id })),
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<Menu
|
|
30
|
+
direction="right"
|
|
31
|
+
sx={{ zIndex: breakpoints.down("md") ? 99999 : 1502 }}
|
|
32
|
+
handler={(e) => (
|
|
33
|
+
<OrdersDropDownHandler as={TouchRipple} onClick={e}>
|
|
34
|
+
{title}
|
|
35
|
+
<KeyboardArrowDownOutlined fontSize="small" color="inherit" />
|
|
36
|
+
</OrdersDropDownHandler>
|
|
37
|
+
)}
|
|
38
|
+
options={(onClose) => {
|
|
39
|
+
return options.map((item: DisplaySearchOption) => (
|
|
40
|
+
<MenuItem
|
|
41
|
+
key={item.id}
|
|
42
|
+
onClick={() => {
|
|
43
|
+
handleChange(item.id);
|
|
44
|
+
onClose();
|
|
45
|
+
}}
|
|
46
|
+
>
|
|
47
|
+
{item.name}
|
|
48
|
+
</MenuItem>
|
|
49
|
+
));
|
|
50
|
+
}}
|
|
51
|
+
/>
|
|
52
|
+
);
|
|
53
|
+
}
|