@bjsdistribution/dms-client-return 0.1.22-prod → 0.1.23-prod

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 CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.1.22-prod",
2
+ "version": "0.1.23-prod",
3
3
  "license": "MIT",
4
4
  "main": "dist/index.js",
5
5
  "typings": "dist/index.d.ts",
@@ -1,3 +1,4 @@
1
+ import dayjs from 'dayjs';
1
2
  import { atom } from 'jotai';
2
3
 
3
4
  interface Filter {
@@ -30,21 +31,37 @@ const overviewFilterReducer = (
30
31
  const { startDate, endDate } = action.payload;
31
32
 
32
33
  const filters: Filter[] = [];
34
+ if (startDate && !endDate) {
35
+ const nextDay = dayjs(startDate)
36
+ .add(1, 'day')
37
+ .format('YYYY-MM-DD');
33
38
 
34
- if (startDate) {
35
39
  filters.push({
36
40
  field: 'created_at',
37
41
  op: '>=',
38
42
  value: `${startDate}T00:00:00+00:00`,
39
43
  });
40
- }
41
-
42
- if (endDate) {
43
44
  filters.push({
44
45
  field: 'created_at',
45
46
  op: '<=',
46
- value: `${endDate}T23:59:59+00:00`,
47
+ value: `${nextDay}T00:00:00+00:00`,
47
48
  });
49
+ } else {
50
+ if (startDate) {
51
+ filters.push({
52
+ field: 'created_at',
53
+ op: '>=',
54
+ value: `${startDate}T00:00:00+00:00`,
55
+ });
56
+ }
57
+
58
+ if (endDate) {
59
+ filters.push({
60
+ field: 'created_at',
61
+ op: '<=',
62
+ value: `${endDate}T23:59:59+00:00`,
63
+ });
64
+ }
48
65
  }
49
66
 
50
67
  return {
@@ -58,7 +58,19 @@ const ClientRouterHeader: React.FC<RouterHeaderProps> = ({
58
58
  }, [startDate, endDate]);
59
59
 
60
60
  const handleStartDateChange: DatePickerProps['onChange'] = date => {
61
- setStartDate(date ?? null);
61
+ if (!date) {
62
+ setStartDate(null);
63
+ setEndDate(null);
64
+ dispatch({
65
+ type: 'SET_CP_DATE_FILTER',
66
+ payload: {
67
+ startDate: null,
68
+ endDate: null,
69
+ },
70
+ });
71
+ } else {
72
+ setStartDate(date);
73
+ }
62
74
  };
63
75
 
64
76
  const handleEndDateChange: DatePickerProps['onChange'] = date => {
@@ -99,7 +111,7 @@ const ClientRouterHeader: React.FC<RouterHeaderProps> = ({
99
111
  value={startDate ? dayjs(startDate) : null}
100
112
  onChange={handleStartDateChange}
101
113
  format={'MMMM D, YYYY'}
102
- allowClear={false}
114
+ allowClear
103
115
  disabledDate={current =>
104
116
  !!endDate && current.isAfter(dayjs(endDate), 'day')
105
117
  }
@@ -12,6 +12,7 @@ import { atom, useAtom } from 'jotai';
12
12
  import {
13
13
  useActionTypeOptions,
14
14
  useClearState,
15
+ useClientLoading,
15
16
  useClientPortal,
16
17
  useCustomerData,
17
18
  useReturnPayload,
@@ -50,6 +51,7 @@ function SelectionFilter() {
50
51
  const [searchParams] = useSearchParams();
51
52
  const [clientPortal] = useClientPortal();
52
53
  const navigate = useNavigate();
54
+ const [clientLoading] = useClientLoading();
53
55
  //API for client data
54
56
  const { data: fetchedCustomerData, loading: loading, fetchData } = useAxios<
55
57
  ApiResponse
@@ -59,12 +61,12 @@ function SelectionFilter() {
59
61
  //@ts-ignore
60
62
  pagination: { is_pagination: false },
61
63
  });
62
-
64
+ //need to fix
63
65
  useEffect(() => {
64
- if (!clientPortal) {
66
+ if (!clientPortal && !clientLoading) {
65
67
  fetchData();
66
68
  }
67
- }, []);
69
+ }, [clientLoading]);
68
70
 
69
71
  useEffect(() => {
70
72
  if (fetchedCustomerData && fetchedCustomerData?.data) {
@@ -6,7 +6,12 @@ import {
6
6
  } from './store/store';
7
7
  import Filters from './filters/Filters';
8
8
  import styled from 'styled-components';
9
- import { useClientPortal, useCustomerData, useStickyStatus } from '../store';
9
+ import {
10
+ useClientLoading,
11
+ useClientPortal,
12
+ useCustomerData,
13
+ useStickyStatus,
14
+ } from '../store';
10
15
  import HeaderFilter from './header-filters/HeaderFilter';
11
16
  import {
12
17
  DocumentTitle,
@@ -37,6 +42,7 @@ function ReturnMainTable() {
37
42
  // const [tableName] = useTableName();
38
43
  const queryParams = new URLSearchParams(location.search);
39
44
  const [clientPortal] = useClientPortal();
45
+ const [clientLoading] = useClientLoading();
40
46
 
41
47
  const customerId = queryParams.get('customerId');
42
48
 
@@ -59,10 +65,10 @@ function ReturnMainTable() {
59
65
  });
60
66
 
61
67
  useEffect(() => {
62
- if (!clientPortal) {
68
+ if (!clientPortal && !clientLoading) {
63
69
  fetchData();
64
70
  }
65
- }, []);
71
+ }, [clientLoading]);
66
72
 
67
73
  useEffect(() => {
68
74
  if (fetchedCustomerData && fetchedCustomerData?.data) {
@@ -85,11 +91,12 @@ function ReturnMainTable() {
85
91
  filters: filters,
86
92
  showLoader: false,
87
93
  });
94
+
88
95
  useEffect(() => {
89
- if (!clientPortal) {
96
+ if (!clientPortal && !clientLoading) {
90
97
  fetchProductStats();
91
98
  }
92
- }, []);
99
+ }, [clientLoading]);
93
100
 
94
101
  useEffect(() => {
95
102
  setLoading(statsDataLoading);
@@ -1,32 +1,68 @@
1
- import React from 'react';
1
+ import React, { useState } from 'react';
2
2
  import styled from 'styled-components';
3
3
  import OptionsDropDown from './OptionsDropDown';
4
4
  import Print from '../../../general-information/dropdowns/Print';
5
5
  import BjsMenuDropDown from './MenuDropDown';
6
- import { useCurrentAction, useOrderDetails } from '../../../store';
6
+ import {
7
+ errorAtom,
8
+ useAtom,
9
+ useCurrentAction,
10
+ useOrderDetails,
11
+ } from '../../../store';
7
12
  // components
8
13
 
9
14
  function DropDowns() {
10
15
  const [orderDetails] = useOrderDetails();
11
16
  const [, setCurrentAction] = useCurrentAction();
17
+ const [errors] = useAtom(errorAtom);
18
+ const [hasNotifiedMobile, setHasNotifiedMobile] = useState(false);
19
+ const [hasNotifiedEmail, setHasNotifiedEmail] = useState(false);
12
20
 
13
21
  const isEmailId = orderDetails?.order_recipient?.contact_email;
14
- const isContactNum = orderDetails?.order_recipient?.contact_home;
22
+ // const isContactNum = orderDetails?.order_recipient?.contact_home;
23
+ const VALID_UK_MOBILE_REGEX = /^(07\d{8,12}|447\d{7,11})$/;
24
+ const VALID_IRISH_MOBILE_REGEX = /^(08[35679]\d{7}|3538[35679]\d{7}|\+3538[35679]\d{7})$/;
25
+
26
+ const isValidUKMobile = (number: string | null | undefined): boolean =>
27
+ typeof number === 'string' && VALID_UK_MOBILE_REGEX.test(number);
28
+ const isValidIrishMobile = (number: string | null | undefined): boolean =>
29
+ typeof number === 'string' && VALID_IRISH_MOBILE_REGEX.test(number);
30
+ const isValidMobile = (number: string | null | undefined): boolean =>
31
+ isValidUKMobile(number) || isValidIrishMobile(number);
32
+
33
+ const contactMobile = orderDetails?.order_recipient?.contact_mobile;
34
+ const contactHome = orderDetails?.order_recipient?.contact_home;
35
+ const contactWork = orderDetails?.order_recipient?.contact_work;
36
+
37
+ const allContacts = [contactMobile, contactHome, contactWork];
38
+ const hasValidMobile = allContacts.some(isValidMobile);
15
39
 
16
40
  const actions = [];
17
41
 
18
- if (isContactNum) {
19
- actions?.push({
20
- label: 'Send SMS',
21
- onClick: () => setCurrentAction('send-sms'),
22
- });
42
+ if (hasValidMobile) {
43
+ if (errors?.mobileError) {
44
+ if (!hasNotifiedMobile) {
45
+ setHasNotifiedMobile(true);
46
+ }
47
+ } else {
48
+ actions.push({
49
+ label: 'Send SMS',
50
+ onClick: () => setCurrentAction('send-sms'),
51
+ });
52
+ }
23
53
  }
24
54
 
25
55
  if (isEmailId) {
26
- actions?.push({
27
- label: 'Send Email',
28
- onClick: () => setCurrentAction('send-email'),
29
- });
56
+ if (errors?.emailError) {
57
+ if (!hasNotifiedEmail) {
58
+ setHasNotifiedEmail(true);
59
+ }
60
+ } else {
61
+ actions.push({
62
+ label: 'Send Email',
63
+ onClick: () => setCurrentAction('send-email'),
64
+ });
65
+ }
30
66
  }
31
67
 
32
68
  return (
@@ -1,31 +1,27 @@
1
1
  import React, { useState } from 'react';
2
2
  import { useCurrentAction, useOrderDetails, useOrderId } from '../../../store';
3
3
  import OrderActionModal from '../../../../../common/modals/order-action-modal/OrderActionModal';
4
- import { ContentContainer, Label, StyledInput } from '../modalstyle';
5
- import { TextArea, useAxios } from '@bjsdistribution/dms-component-library';
4
+ import { Label, StyledInput } from '../modalstyle';
5
+ import { useAxios } from '@bjsdistribution/dms-component-library';
6
6
  import { ApiResponse } from '../../../types';
7
7
  import styled from 'styled-components';
8
-
9
- const CustomTextArea = styled(TextArea)`
10
- width: 700px;
11
- height: 140px;
12
- resize: none;
13
- `;
14
-
15
- const ErrorText = styled.div`
16
- color: red;
17
- font-size: 12px;
18
- margin-top: 4px;
19
- `;
8
+ import { notification } from 'antd';
9
+ import TextArea from 'antd/es/input/TextArea';
20
10
 
21
11
  function SendSmsModal() {
22
12
  const [, setCurrentAction] = useCurrentAction();
23
13
  const [orderDetails] = useOrderDetails();
24
14
  const [orderId] = useOrderId();
25
- const initialTo = orderDetails?.order_recipient?.contact_home || '';
15
+ const VALID_UK_MOBILE_REGEX = /^(07\d{8,12}|447\d{7,11})$/;
26
16
  const [toError, setToError] = useState('');
27
17
  const [formData, setFormData] = useState({
28
- to: initialTo,
18
+ to: [
19
+ orderDetails?.order_recipient?.contact_mobile,
20
+ orderDetails?.order_recipient?.contact_home,
21
+ orderDetails?.order_recipient?.contact_work,
22
+ ]
23
+ .filter(Boolean)
24
+ .join(', '),
29
25
  body: '',
30
26
  });
31
27
 
@@ -42,54 +38,83 @@ function SendSmsModal() {
42
38
  const { name, value } = e.target;
43
39
 
44
40
  if (name === 'to') {
45
- const digitsOnly = value.replace(/\D/g, '');
46
- let error = '';
47
-
48
- if (digitsOnly.length < 10) {
49
- error = 'Phone number must be exactly 10 digits';
50
- } else if (digitsOnly.length > 10) {
51
- error = 'Phone number cannot exceed 10 digits';
41
+ const rawValue = value;
42
+ const numbers = rawValue.split(',').map(num => num.trim());
43
+ const invalids = numbers.filter(num => !VALID_UK_MOBILE_REGEX.test(num));
44
+
45
+ if (invalids.length === numbers.length) {
46
+ setToError('Enter at least one valid UK mobile number.');
47
+ } else {
48
+ setToError('');
52
49
  }
53
-
54
- setToError(error);
55
-
56
- setFormData(prev => ({
57
- ...prev,
58
- to: digitsOnly.slice(0, 10),
59
- }));
50
+ setFormData(prev => ({ ...prev, to: rawValue }));
60
51
  } else {
61
- setFormData(prev => ({
62
- ...prev,
63
- [name]: value,
64
- }));
52
+ setFormData(prev => ({ ...prev, [name]: value }));
65
53
  }
66
54
  };
67
55
 
68
56
  const handleSubmit = async () => {
69
- const trimmedTo = formData?.to?.trim();
70
57
  const trimmedBody = formData?.body?.trim();
58
+ if (!trimmedBody) return;
71
59
 
72
- if (trimmedTo.length !== 10) {
73
- setToError('Phone number must be exactly 10 digits');
74
- return;
75
- }
60
+ const rawContacts = formData.to.split(',').map(num => num.trim());
76
61
 
77
- const payload = {
78
- order_id: +orderId,
79
- receiver: trimmedTo,
80
- message: trimmedBody,
81
- notification_type: 'Send SMS',
82
- };
62
+ const validContacts = rawContacts.filter(num =>
63
+ VALID_UK_MOBILE_REGEX.test(num)
64
+ );
83
65
 
84
- if (!trimmedTo || !trimmedBody) {
66
+ if (validContacts.length === 0) {
67
+ notification.warning({
68
+ message: 'No Valid UK Mobile Numbers',
69
+ description:
70
+ 'Please provide at least one valid UK mobile number (e.g., 07123...).',
71
+ });
85
72
  return;
86
73
  }
87
74
 
88
75
  try {
89
- await callSendSms({ payload: payload });
76
+ for (const number of validContacts) {
77
+ const payload = {
78
+ order_id: +orderId,
79
+ receiver: number,
80
+ message: trimmedBody,
81
+ notification_type: 'Send SMS',
82
+ };
83
+
84
+ const response = (await callSendSms({ payload })) as {
85
+ code: number | null;
86
+ error?: string | null;
87
+ message?: string;
88
+ [key: string]: any;
89
+ };
90
+
91
+ if (response.code === 200) {
92
+ notification.success({
93
+ message: 'SMS Sent',
94
+ description: `Message sent to ${number}`,
95
+ });
96
+ } else {
97
+ notification.error({
98
+ message: 'SMS Failed',
99
+ description: `Failed to send to ${number}: ${response?.error ||
100
+ 'Unknown error'}`,
101
+ });
102
+ }
103
+ }
104
+
90
105
  closeModal();
91
106
  } catch (error) {
92
- console.error('Error sending SMS:', error);
107
+ const err = error as any;
108
+ const errMsg =
109
+ err?.response?.data?.error ||
110
+ err?.response?.data?.message ||
111
+ err?.message ||
112
+ 'An unexpected error occurred.';
113
+
114
+ notification.error({
115
+ message: 'Request Failed',
116
+ description: errMsg,
117
+ });
93
118
  }
94
119
  };
95
120
 
@@ -98,7 +123,7 @@ function SendSmsModal() {
98
123
  };
99
124
 
100
125
  const isFormValid =
101
- formData?.to.trim()?.length === 10 &&
126
+ formData?.to.trim()?.length > 0 &&
102
127
  !toError &&
103
128
  formData?.body.trim().length > 0;
104
129
 
@@ -119,21 +144,49 @@ function SendSmsModal() {
119
144
  onChange={handleInputChange}
120
145
  required
121
146
  placeholder="Enter the number"
122
- maxLength={10}
123
147
  />
124
148
  {toError && <ErrorText>{toError}</ErrorText>}
125
149
 
126
150
  <Label>Body</Label>
127
- <CustomTextArea
151
+ <TextArea
152
+ style={{ width: '700px', height: '140px', resize: 'none' }}
128
153
  name="body"
129
154
  value={formData.body}
130
155
  onChange={handleInputChange}
131
156
  required
132
157
  placeholder="Write here..."
158
+ maxLength={500}
159
+ count={{
160
+ show: true,
161
+ max: 500,
162
+ }}
133
163
  />
134
164
  </ContentContainer>
135
165
  </OrderActionModal>
136
166
  );
137
167
  }
138
168
 
169
+ export const ContentContainer = styled.div`
170
+ display: flex;
171
+ padding: 20px;
172
+ flex-direction: column;
173
+ justify-content: center;
174
+ align-items: flex-start;
175
+ gap: 15px;
176
+ flex: 1 0 0;
177
+ border-radius: 15px;
178
+ `;
179
+
180
+ // const CustomTextArea = styled(TextArea)`
181
+ // width: 700px;
182
+ // height: 140px;
183
+ // resize: none;
184
+ // `;
185
+
186
+ const ErrorText = styled.div`
187
+ color: red;
188
+ font-size: 12px;
189
+ margin-top: 4px;
190
+ `;
191
+
139
192
  export default SendSmsModal;
@@ -12,6 +12,7 @@ import { useClientPortal } from '../../../../../store';
12
12
 
13
13
  //import { VALIDATE_UK_IRISH_NUMBER_REGEX } from '../../../helper';
14
14
  import CallCustomer from './CallCustomer';
15
+ import { Tooltip } from 'antd';
15
16
 
16
17
  function ContactHome() {
17
18
  const [errors, setErrors] = useAtom(errorAtom);
@@ -125,9 +126,11 @@ function ContactHome() {
125
126
  const finalHref = `tel:${hrefPrefix}${numberPart}`;
126
127
 
127
128
  return (
128
- <a href={finalHref} style={{ cursor: 'pointer' }}>
129
- <CallCustomer />
130
- </a>
129
+ <Tooltip placement="top" title={<span>Zoom</span>}>
130
+ <a href={finalHref} style={{ cursor: 'pointer' }}>
131
+ <CallCustomer />
132
+ </a>
133
+ </Tooltip>
131
134
  );
132
135
  })()}
133
136
  </InputContainer>
@@ -11,6 +11,7 @@ import {
11
11
  } from '../../../../store';
12
12
  import { handleKeyDown } from '../../../../utils';
13
13
  import { useClientPortal } from '../../../../../store';
14
+ import { Tooltip } from 'antd';
14
15
 
15
16
  function ContactMobile() {
16
17
  const [errors, setErrors] = useAtom(errorAtom);
@@ -129,9 +130,11 @@ function ContactMobile() {
129
130
  const finalHref = `tel:${hrefPrefix}${numberPart}`;
130
131
 
131
132
  return (
132
- <a href={finalHref} style={{ cursor: 'pointer' }}>
133
- <CallCustomer />
134
- </a>
133
+ <Tooltip placement="top" title={<span>Zoom</span>}>
134
+ <a href={finalHref} style={{ cursor: 'pointer' }}>
135
+ <CallCustomer />
136
+ </a>
137
+ </Tooltip>
135
138
  );
136
139
  })()}
137
140
  </InputContainer>
@@ -8,11 +8,14 @@ import {
8
8
  usePrevOrderDetails,
9
9
  } from '../../../../store';
10
10
  import { handleKeyDown } from '../../../../utils';
11
+ import { useClientPortal } from '../../../../../store';
12
+ import { Tooltip } from 'antd';
11
13
  import CallCustomer from './CallCustomer';
12
14
 
13
15
  function ContactWork() {
14
16
  const [errors, setErrors] = useAtom(errorAtom);
15
17
  const [orderDetails, setOrderDetails] = useOrderDetails();
18
+ const [isClientPortal] = useClientPortal();
16
19
  const orderRecipient = orderDetails?.order_recipient;
17
20
  const [prevOrderDetails] = usePrevOrderDetails();
18
21
  const apiContactWork = prevOrderDetails?.order_recipient?.contact_work;
@@ -69,6 +72,7 @@ function ContactWork() {
69
72
  onChange={onChange}
70
73
  // minLength={10}
71
74
  // maxLength={10}
75
+ disabled={isClientPortal}
72
76
  />
73
77
  {apiContactWork &&
74
78
  (() => {
@@ -115,9 +119,11 @@ function ContactWork() {
115
119
  const finalHref = `tel:${hrefPrefix}${numberPart}`;
116
120
 
117
121
  return (
118
- <a href={finalHref} style={{ cursor: 'pointer' }}>
119
- <CallCustomer />
120
- </a>
122
+ <Tooltip placement="top" title={<span>Zoom</span>}>
123
+ <a href={finalHref} style={{ cursor: 'pointer' }}>
124
+ <CallCustomer />
125
+ </a>
126
+ </Tooltip>
121
127
  );
122
128
  })()}
123
129
  </InputContainer>
@@ -262,11 +262,17 @@ export function useSelectedData() {
262
262
  export function useNewReturnOrderData() {
263
263
  return useAtom(newReturnOrderData);
264
264
  }
265
-
265
+ //call
266
266
  const isClientPortalAtom = atom<boolean | undefined>(undefined);
267
267
  function useClientPortal() {
268
268
  return useAtom(isClientPortalAtom);
269
269
  }
270
+
271
+ const isClientLoading = atom<boolean>(true);
272
+ function useClientLoading() {
273
+ return useAtom(isClientLoading);
274
+ }
275
+
270
276
  const activeCardIdAtom = atom<number>(1);
271
277
  export const useActiveCardId = () => {
272
278
  return useAtom(activeCardIdAtom);
@@ -306,4 +312,5 @@ export {
306
312
  useStartDate,
307
313
  useEndDate,
308
314
  useSelectedClientAfterOrders,
315
+ useClientLoading,
309
316
  };
package/src/index.tsx CHANGED
@@ -19,6 +19,7 @@ import {
19
19
  useActionType,
20
20
  useActionTypeOptions,
21
21
  useAssignData,
22
+ useClientLoading,
22
23
  useClientPortal,
23
24
  useLocationData,
24
25
  useStatusOrderData,
@@ -54,10 +55,11 @@ function ReturnClient() {
54
55
  const [, setOrderStaticData] = useOrderStaticData();
55
56
  const [clientPortal, setClientPortal] = useClientPortal();
56
57
  const location = useLocation();
57
- const userType = JSON.parse(localStorage.getItem('user_type')!);
58
-
58
+ const userType = JSON.parse(localStorage.getItem('user_type') ?? 'null');
59
+ const [, setLoading] = useClientLoading();
59
60
  useEffect(() => {
60
- setClientPortal(userType?.is_customer || false); // change it to false bydefault
61
+ setClientPortal(userType?.is_customer ? true : false); // change it to false bydefault
62
+ setLoading(false);
61
63
  }, []);
62
64
 
63
65
  const splitPath = (str: any) => {