@bjsdistribution/dms-client-return 0.1.21-prod → 0.1.22-dev

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 (49) hide show
  1. package/dist/common/custom-input/CustomInput.d.ts +5 -0
  2. package/dist/components/order-view/order-actions/actions/send-sms/SendSmsModal.d.ts +1 -0
  3. package/dist/components/order-view/order-actions/actions/what3words/What3WordsModal.tsx.d.ts +3 -0
  4. package/dist/components/order-view/store.d.ts +1 -1
  5. package/dist/components/order-view/types.d.ts +5 -4
  6. package/dist/components/order-view/utils.d.ts +2 -1
  7. package/dist/components/store.d.ts +2 -1
  8. package/dist/dms-client-return.cjs.development.js +1278 -903
  9. package/dist/dms-client-return.cjs.development.js.map +1 -1
  10. package/dist/dms-client-return.cjs.production.min.js +1 -1
  11. package/dist/dms-client-return.cjs.production.min.js.map +1 -1
  12. package/dist/dms-client-return.esm.js +1280 -905
  13. package/dist/dms-client-return.esm.js.map +1 -1
  14. package/package.json +1 -1
  15. package/src/common/custom-input/CustomInput.tsx +24 -0
  16. package/src/components/client-portal/DateFilterStore.ts +57 -9
  17. package/src/components/client-portal/OutstandingTable.tsx +1 -1
  18. package/src/components/client-portal/ReturnStatus.tsx +84 -44
  19. package/src/components/client-portal/return-confirm/ConfirmedMain.tsx +5 -6
  20. package/src/components/client-portal/return-confirm/ReturnConfirmedTable.tsx +21 -2
  21. package/src/components/order-daily-overview/header/route-header/ClientRouterHeader.tsx +45 -23
  22. package/src/components/order-daily-overview/header-filters/HeaderFilter.tsx +3 -2
  23. package/src/components/order-daily-overview/header-filters/SelectionFilter.tsx +5 -3
  24. package/src/components/order-daily-overview/index.tsx +12 -5
  25. package/src/components/order-daily-overview/return-order-table/return-orders-filters/ReturnFilter.tsx +53 -4
  26. package/src/components/order-view/details-filters/left-side-filters/dropdowns/DropDowns.tsx +48 -12
  27. package/src/components/order-view/general-information/dropdowns/Print.tsx +13 -17
  28. package/src/components/order-view/modalsPopup/ModalManager.tsx +9 -1
  29. package/src/components/order-view/notes/logs/index.tsx +3 -1
  30. package/src/components/order-view/order-actions/actions/add-resources/DocFileUpload.tsx +4 -4
  31. package/src/components/order-view/order-actions/actions/charge-log/index.tsx +26 -1
  32. package/src/components/order-view/order-actions/actions/confirm-order/customer-info/DeliveryDate.tsx +1 -3
  33. package/src/components/order-view/order-actions/actions/create-task-reminder/CreateReminderModal.tsx +7 -4
  34. package/src/components/order-view/order-actions/actions/force-debrief/ForceDebriefModal.tsx +21 -20
  35. package/src/components/order-view/order-actions/actions/send-sms/SendSmsModal.tsx +104 -51
  36. package/src/components/order-view/order-actions/actions/what3words/What3WordsModal.tsx.tsx +306 -0
  37. package/src/components/order-view/order-actions/index.tsx +34 -13
  38. package/src/components/order-view/order-details/expanded-content/customer-section/contact-info/ContactHome.tsx +6 -3
  39. package/src/components/order-view/order-details/expanded-content/customer-section/contact-info/ContactMobile.tsx +6 -3
  40. package/src/components/order-view/order-details/expanded-content/customer-section/contact-info/ContactWork.tsx +9 -3
  41. package/src/components/order-view/order-details/expanded-content/customer-section/location-info/Latitude.tsx +5 -7
  42. package/src/components/order-view/order-details/expanded-content/customer-section/location-info/Longitude.tsx +1 -1
  43. package/src/components/order-view/order-details/expanded-content/customer-section/location-info/What3words.tsx +45 -13
  44. package/src/components/order-view/order-details/expanded-content/footer/index.tsx +63 -27
  45. package/src/components/order-view/order-details/expanded-content/orders-section/DeliveryDate.tsx +1 -1
  46. package/src/components/order-view/products-info/index.tsx +1 -0
  47. package/src/components/order-view/types.ts +6 -4
  48. package/src/components/store.ts +8 -1
  49. package/src/index.tsx +6 -4
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { useEffect } from 'react';
2
2
  import {
3
3
  StyledFIlterELements,
4
4
  StyledFiter,
@@ -6,14 +6,32 @@ import {
6
6
  Text,
7
7
  } from '../../filters/FilterStyle';
8
8
  import RightFilters from './RightFilters';
9
- import { useClientPortal, useCustomerData } from '../../../store';
9
+ import {
10
+ useAtom,
11
+ useClientPortal,
12
+ useCustomerData,
13
+ useSearchAfterText,
14
+ useSearchText,
15
+ useStatsCPCount,
16
+ } from '../../../store';
10
17
  import { useParams } from 'react-router-dom';
11
18
  import ReturnRTFilters from '../../../client-portal/ReturnRTFilters';
19
+ import { returnCountType } from '../../../client-portal/ReturnStatus';
20
+ import { useAxios } from '@bjsdistribution/dms-component-library';
21
+ import { dateFilterCPReducer } from '../../../client-portal/DateFilterStore';
12
22
 
13
23
  const ReturnFilter = () => {
14
24
  const [customers] = useCustomerData();
15
25
  const [clientPortal] = useClientPortal();
16
26
  const { status } = useParams();
27
+ const [count, setCount] = useStatsCPCount();
28
+ const [debouncedSearchAfterText] = useSearchAfterText();
29
+ const [debouncedSearchText] = useSearchText();
30
+
31
+ const [state] = useAtom(dateFilterCPReducer);
32
+ const { 'x-filters': filters } = state;
33
+
34
+ const cleanedFilters = filters.filter(f => f.field !== 'order_status');
17
35
 
18
36
  const queryParams = new URLSearchParams(location.search);
19
37
  const customerId = queryParams.get('customerId');
@@ -32,7 +50,8 @@ const ReturnFilter = () => {
32
50
  case 'scheduled_for_return':
33
51
  return 'All Scheduled Orders';
34
52
  case 'return_completed':
35
- return 'View Return Order';
53
+ // return 'View Return Order';
54
+ return 'Completed Return';
36
55
  default:
37
56
  return status
38
57
  .replace(/_/g, ' ')
@@ -40,6 +59,31 @@ const ReturnFilter = () => {
40
59
  }
41
60
  };
42
61
 
62
+ const { data: statsData, loading: statsApiLoading, fetchData } = useAxios<{
63
+ data: returnCountType[];
64
+ }>({
65
+ endpoint: `v1/portal/return/stats/`,
66
+ method: 'GET',
67
+ pagination: { is_pagination: false },
68
+ filters: cleanedFilters,
69
+ });
70
+
71
+ useEffect(() => {
72
+ if (
73
+ clientPortal &&
74
+ debouncedSearchAfterText === '' &&
75
+ status == 'return_completed'
76
+ ) {
77
+ fetchData();
78
+ }
79
+ }, [debouncedSearchAfterText, debouncedSearchText, JSON.stringify(filters)]);
80
+
81
+ useEffect(() => {
82
+ if (statsData?.data && typeof statsData?.data === 'object') {
83
+ setCount(statsData?.data);
84
+ }
85
+ }, [statsApiLoading]);
86
+
43
87
  return (
44
88
  <>
45
89
  <StyledFiter>
@@ -53,7 +97,12 @@ const ReturnFilter = () => {
53
97
  </>
54
98
  ) : (
55
99
  <>
56
- <Text>{status ? getStatusLabel(status) : ''}</Text>
100
+ <Text>
101
+ {status
102
+ ? `${getStatusLabel(status)} (${count?.completed_count})`
103
+ : ''}
104
+ </Text>
105
+
57
106
  <ReturnRTFilters />
58
107
  </>
59
108
  )}
@@ -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,7 +1,7 @@
1
1
  import React, { useMemo } from 'react';
2
2
  import styled from 'styled-components';
3
3
  import { PrintMenu } from '../../fakedata';
4
- import { useOrderDetails, useOrderStaticData } from '../../store';
4
+ import { useOrderDetails } from '../../store';
5
5
  import { Dropdown, Space } from 'antd';
6
6
  import ArrowDownIcon from '../../../../assets/icons/ArrowDownIcon';
7
7
  import { getBaseEndpoint } from '@bjsdistribution/dms-component-library';
@@ -13,11 +13,6 @@ function Print() {
13
13
  const orderId = useMemo(() => {
14
14
  return orderDetails?.id;
15
15
  }, [orderDetails]);
16
- //new
17
- const [orderStaticData] = useOrderStaticData();
18
- const orderTypeID = useMemo(() => {
19
- return orderDetails?.order_type_id;
20
- }, [orderDetails]);
21
16
 
22
17
  const getEnvironment = ():
23
18
  | 'development'
@@ -100,17 +95,18 @@ function Print() {
100
95
  }
101
96
  };
102
97
 
103
- const isOptionDisabled = (optionValue: any) => {
104
- const orderType = orderStaticData?.order_type?.find(
105
- (type: any) => type?.id === orderTypeID
106
- );
107
- if (optionValue === 'ordercovernote') {
108
- if (orderType?.type === 'return') {
109
- return false;
110
- }
111
- return true;
112
- }
113
- return false;
98
+ const optionToLabelKey: Record<string, keyof typeof orderDetails.labels> = {
99
+ proofofdelivery: 'deliver',
100
+ returnlabels: 'return',
101
+ returncovernote: 'collect',
102
+ };
103
+
104
+ const isOptionDisabled = (optionValue: string) => {
105
+ const labels = orderDetails?.labels || {};
106
+ const labelKey = optionToLabelKey[optionValue];
107
+
108
+ if (!labelKey) return false;
109
+ return !labels[labelKey];
114
110
  };
115
111
 
116
112
  const filteredOptions = PrintMenu?.map((option: any) => ({
@@ -103,12 +103,20 @@ const ModalManager = () => {
103
103
  setShouldPerformAction(false);
104
104
  setSelectAll(false);
105
105
  setError(null);
106
+ //@ts-ignore
107
+ const orderId = response?.data?.[0]?.order_id;
108
+
109
+ if (orderId) {
110
+ navigate(`/orders/view/return/?order=${orderId}`);
111
+ } else {
112
+ navigate('/orders/view/return/overview');
113
+ }
106
114
 
107
115
  // for local
108
116
  // navigate('/overview');
109
117
 
110
118
  // for dev
111
- navigate('/orders/view/return/overview');
119
+ //navigate('/orders/view/return/overview');
112
120
  }
113
121
  }, [postLoading]);
114
122
 
@@ -11,7 +11,9 @@ function AuditLogs() {
11
11
  }
12
12
 
13
13
  const Container = styled.div`
14
- height: 550px; /* Set height to limit container size */
14
+ height: 500px;
15
+ min-height: 200px;
16
+ max-height: 500px;
15
17
  overflow: auto; /* Enable scrolling */
16
18
  scrollbar-width: thin;
17
19
  `;
@@ -7,8 +7,8 @@ import { notification } from 'antd';
7
7
  // Store uploaded files
8
8
  export const uploadedFilesAtom = atom<File[]>([]);
9
9
 
10
- // Allowed photo MIME types
11
- const allowedFileTypes = ['image/jpeg', 'image/png'];
10
+ // Allowed file MIME types
11
+ const allowedFileTypes = ['image/jpeg', 'image/png', 'application/pdf'];
12
12
 
13
13
  function DocFileUpload() {
14
14
  const [uploadedFiles, setUploadedFiles] = useAtom(uploadedFilesAtom);
@@ -53,7 +53,7 @@ function DocFileUpload() {
53
53
  if (rejectedFiles?.length > 0) {
54
54
  notification.error({
55
55
  message: 'Some files were not added',
56
- description: 'Either they were duplicates or not JPEG/PNG format.',
56
+ description: 'Either they were duplicates or not JPEG/PNG/PDF format.',
57
57
  });
58
58
  }
59
59
  };
@@ -112,7 +112,7 @@ function DocFileUpload() {
112
112
  </IconContainer>
113
113
  <TextContainer>
114
114
  <Text1>or drag and drop your files</Text1>
115
- <Text2>Photos (JPEG, PNG) Only</Text2>
115
+ <Text2>Photos (JPEG, PNG) or PDF documents</Text2>
116
116
  </TextContainer>
117
117
  </Container>
118
118
  </DraggerContainer>
@@ -3,6 +3,7 @@ import { useAxios } from '@bjsdistribution/dms-component-library';
3
3
  import {
4
4
  useCurrentAction,
5
5
  useCustomerInfo,
6
+ usegetClientData,
6
7
  useOrderDetails,
7
8
  } from '../../../store';
8
9
  import { ApiResponse } from '../../../types';
@@ -10,12 +11,16 @@ import OrderActionModal from '../../../../../common/modals/order-action-modal/Or
10
11
  import { Empty, Skeleton } from 'antd';
11
12
 
12
13
  import styled from 'styled-components';
14
+ import { ClientType } from '../../../../order-daily-overview/common/interface';
15
+ import { getBaseUrlSymbol } from '../../../utils';
13
16
 
14
17
  function ChargeLog() {
15
18
  const [, setCurrentAction] = useCurrentAction();
16
19
  const [customerInfo] = useCustomerInfo();
17
20
  const infoText = `${customerInfo?.recipient_name}, ${customerInfo?.postcode}`;
18
21
  const [orderDetails] = useOrderDetails();
22
+ const baseUrlSymbol = getBaseUrlSymbol();
23
+ const [clientData] = usegetClientData();
19
24
 
20
25
  const closeModal = () => {
21
26
  setCurrentAction(undefined);
@@ -71,11 +76,31 @@ function ChargeLog() {
71
76
  fetchedData?.data?.map((order: any) => {
72
77
  const label = order?.status?.split(':')[0];
73
78
  const val = order?.status?.split(':')[1];
79
+ const baseUrl = clientData?.filter(
80
+ (client: ClientType) => client?.client_id === order?.client_id
81
+ )[0]?.url;
82
+
83
+ const redirectionLink =
84
+ baseUrl + `/finance/order/${order?.debrief_id}`;
74
85
  return (
75
86
  <tr>
76
87
  <td>{order?.date}</td>
77
88
  <td>{order?.description}</td>
78
- <td>{order?.amount}</td>
89
+ <td>
90
+ <a
91
+ style={{
92
+ color: '#09F',
93
+ cursor: 'pointer',
94
+ textDecoration: 'none',
95
+ }}
96
+ href={`https://${redirectionLink}`}
97
+ target="_blank"
98
+ rel="noopener noreferrer"
99
+ >
100
+ {baseUrlSymbol}
101
+ {order?.amount}
102
+ </a>
103
+ </td>
79
104
  <td>
80
105
  <div
81
106
  style={{
@@ -55,9 +55,7 @@ function DeliveryDate() {
55
55
  : null
56
56
  }
57
57
  style={{ borderRadius: '15px', minWidth: '60px' }}
58
- disabledDate={current =>
59
- current && current.isBefore(dayjs().startOf('day'))
60
- }
58
+ disabledDate={() => false}
61
59
  allowClear={false}
62
60
  />
63
61
  </Container>
@@ -228,10 +228,13 @@ function CreateReminderModal() {
228
228
  }
229
229
  }, [fetchData]);
230
230
 
231
- const userOptions = filteredUsers?.map((user: any) => ({
232
- label: `${user.fName} ${user.lName}`,
233
- value: user.id,
234
- }));
231
+ const userOptions =
232
+ selectedDeptId?.length > 0
233
+ ? filteredUsers?.map((user: any) => ({
234
+ label: `${user.fName} ${user.lName}`,
235
+ value: user.id,
236
+ }))
237
+ : [];
235
238
 
236
239
  const handleAssignToMe = () => {
237
240
  const storedUserJSON = localStorage.getItem('user_type');
@@ -267,7 +267,15 @@ function ForceDebriefModal(props: IProps) {
267
267
  orderDetails
268
268
  );
269
269
 
270
- const serviceKeys = [
270
+ type ServiceKey =
271
+ | 'assemble'
272
+ | 'disassemble'
273
+ | 'unpack_items'
274
+ | 'collect_disposal'
275
+ | 'take_packaging_away'
276
+ | 'assembly_type';
277
+
278
+ const serviceKeys: ServiceKey[] = [
271
279
  'assemble',
272
280
  'disassemble',
273
281
  'unpack_items',
@@ -276,28 +284,21 @@ function ForceDebriefModal(props: IProps) {
276
284
  'assembly_type',
277
285
  ];
278
286
 
279
- let orderServicesPayload: Record<string, any> = {};
280
-
281
- const includesKey = serviceKeys.some(key => key in ordersApiPayload);
282
-
283
- if (includesKey) {
284
- for (const key of serviceKeys) {
285
- orderServicesPayload[key as keyof typeof ordersApiPayload] =
286
- key in ordersApiPayload
287
- ? ordersApiPayload[key as keyof typeof ordersApiPayload]
288
- : orderDetails[key as keyof OrdersType];
289
- delete ordersApiPayload[key as keyof typeof ordersApiPayload];
290
- if (
291
- orderServicesPayload?.assemble === false &&
292
- orderServicesPayload?.disassemble === false
293
- ) {
294
- orderServicesPayload.assembly_type = null;
295
- }
287
+ for (const key of serviceKeys) {
288
+ if (key in ordersApiPayload) {
289
+ (ordersApiPayload as Record<ServiceKey, any>)[key] =
290
+ ordersApiPayload[key as keyof typeof ordersApiPayload];
291
+ } else if (key in orderDetails) {
292
+ (ordersApiPayload as Record<ServiceKey, any>)[key] =
293
+ orderDetails[key as keyof OrdersType];
296
294
  }
297
295
  }
298
296
 
299
- if (Object.keys(orderServicesPayload).length > 0) {
300
- ordersApiPayload.order_services = [orderServicesPayload];
297
+ if (
298
+ ordersApiPayload?.assemble === false &&
299
+ ordersApiPayload?.disassemble === false
300
+ ) {
301
+ ordersApiPayload.assembly_type = null;
301
302
  }
302
303
 
303
304
  try {
@@ -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;