@bjsdistribution/dms-client-return 0.1.38-prod → 0.1.39-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.38-prod",
2
+ "version": "0.1.39-prod",
3
3
  "license": "MIT",
4
4
  "main": "dist/index.js",
5
5
  "typings": "dist/index.d.ts",
@@ -0,0 +1,24 @@
1
+ import { Input } from '@bjsdistribution/dms-component-library';
2
+ import React, { InputHTMLAttributes } from 'react';
3
+ import styled from 'styled-components';
4
+
5
+ function GenericInput(
6
+ props: InputHTMLAttributes<HTMLInputElement> & { hasError?: boolean }
7
+ ) {
8
+ return <StyledInput {...props} />;
9
+ }
10
+
11
+ const StyledInput = styled(Input)<{ hasError?: boolean }>`
12
+ height: 30px;
13
+ border: 1px solid ${({ hasError }) => (hasError ? 'red' : '#ccc')};
14
+ &:hover {
15
+ background: #ffffff;
16
+ outline: none;
17
+ border-color: #7b7874;
18
+ }
19
+ &:focus {
20
+ border-color: ${({ hasError }) => (hasError ? 'red' : '#888')};
21
+ }
22
+ `;
23
+
24
+ export default GenericInput;
@@ -4,7 +4,7 @@ export const StatusOptions = [
4
4
  value: 'released',
5
5
  },
6
6
  {
7
- label: 'Recieved',
7
+ label: 'Received',
8
8
  value: 'received',
9
9
  },
10
10
  {
@@ -25,13 +25,13 @@ export const StatusOptions = [
25
25
  },
26
26
  ];
27
27
 
28
- export const notesTypeOptions = [
29
- {
30
- label: 'Public',
31
- value: 'public',
32
- },
33
- {
34
- label: 'Private',
35
- value: 'private',
36
- },
37
- ];
28
+ export const notesTypeOptions = [
29
+ {
30
+ label: 'Public',
31
+ value: 'public',
32
+ },
33
+ {
34
+ label: 'Private',
35
+ value: 'private',
36
+ },
37
+ ];
@@ -0,0 +1,306 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import {
3
+ useCurrentAction,
4
+ useCustomerInfo,
5
+ useOrderDetails,
6
+ usePrevOrderDetails,
7
+ } from '../../../store';
8
+ import OrderActionModal from '../../../../../common/modals/order-action-modal/OrderActionModal';
9
+ import { notification } from 'antd';
10
+ import { Label, useAxios } from '@bjsdistribution/dms-component-library';
11
+ import styled from 'styled-components';
12
+ import GenericInput from '../../../../../common/custom-input/CustomInput';
13
+ import { compareOrderDetailsStates } from '../../../utils';
14
+ import { ApiResponse } from '../../../types';
15
+ import { useClientPortal } from '../../../../store';
16
+
17
+ function What3WordsModal(props: any) {
18
+ const w3wRegex = /^\/{0,}[^0-9`~!@#$%^&*()+\-_=[{\]}\\|'<,.>?/";:£§º©®\s]{1,}[・.。][^0-9`~!@#$%^&*()+\-_=[{\]}\\|'<,.>?/";:£§º©®\s]{1,}[・.。][^0-9`~!@#$%^&*()+\-_=[{\]}\\|'<,.>?/";:£§º©®\s]{1,}$/;
19
+ const [, setCurrentAction] = useCurrentAction();
20
+ const [orderDetails, setOrderDetails] = useOrderDetails();
21
+ const [prevOrderDetails, setPrevOrderDetails] = usePrevOrderDetails();
22
+ const [customerInfo] = useCustomerInfo();
23
+ const [w3wError, setW3wError] = useState('');
24
+ const [isClientPortal] = useClientPortal();
25
+ const orderRecipient = orderDetails?.order_recipient || {};
26
+ const [currentWhat3Words, setWhat3Words] = useState();
27
+ const [localW3W, setLocalW3W] = useState('');
28
+ const [localLat, setLocalLat] = useState(orderRecipient?.latitude || '');
29
+ const [localLong, setLocalLong] = useState(orderRecipient?.longitude || '');
30
+
31
+ const closeModal = () => setCurrentAction(undefined);
32
+
33
+ const cleanW3WAddress = (w3wStr: string) => {
34
+ return w3wStr.replace(/^\/+/, '');
35
+ };
36
+
37
+ const validateInput = (value: string) => {
38
+ if (value.trim()) {
39
+ const isValid = w3wRegex.test(value);
40
+ setW3wError(
41
+ isValid ? '' : 'Please enter a valid input (format: word.word.word)'
42
+ );
43
+ } else {
44
+ setW3wError('Input cannot be empty or only spaces');
45
+ }
46
+ };
47
+
48
+ const onChangeW3W = (e: React.ChangeEvent<HTMLInputElement>) => {
49
+ const rawVal = e.target.value;
50
+ const val = cleanW3WAddress(rawVal);
51
+ validateInput(rawVal); // still validate raw input for dots, etc.
52
+ setLocalW3W(val);
53
+ };
54
+
55
+ const onChangeLatitude = (e: React.ChangeEvent<HTMLInputElement>) => {
56
+ setLocalLat(e.target.value);
57
+ };
58
+
59
+ const onChangeLongitude = (e: React.ChangeEvent<HTMLInputElement>) => {
60
+ setLocalLong(e.target.value);
61
+ };
62
+
63
+ const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
64
+ if (['e', 'E', '+', '-'].includes(e.key)) {
65
+ e.preventDefault();
66
+ }
67
+ };
68
+
69
+ const { fetchData: callUpdateOrderAPI } = useAxios<{
70
+ data: ApiResponse[];
71
+ }>({
72
+ endpoint: `/orders/update/`,
73
+ method: 'PUT',
74
+ });
75
+
76
+ const { fetchData: getWhat3Words } = useAxios<{
77
+ data: ApiResponse[];
78
+ }>({
79
+ endpoint: `/v1/orders/coordinates_to_w3w/?`,
80
+ method: 'GET',
81
+ });
82
+
83
+ useEffect(() => {
84
+ if (
85
+ orderDetails?.order_recipient?.latitude &&
86
+ orderDetails?.order_recipient?.longitude
87
+ ) {
88
+ getWhat3Words({
89
+ endPointQuery: `latitude=${orderDetails?.order_recipient?.latitude}&longitude=${orderDetails?.order_recipient?.longitude}`,
90
+ }).then((response: any) => {
91
+ console.log(response?.data);
92
+ setWhat3Words(response?.data?.words);
93
+ });
94
+ }
95
+ }, [
96
+ orderDetails?.order_recipient?.latitude,
97
+ orderDetails?.order_recipient?.longitude,
98
+ ]);
99
+
100
+ const onSave = async () => {
101
+ const updatedOrderDetails = {
102
+ ...orderDetails,
103
+ order_recipient: {
104
+ ...(orderDetails?.order_recipient || {}),
105
+ what_3words_address: localW3W,
106
+ latitude: localLat,
107
+ longitude: localLong,
108
+ },
109
+ };
110
+
111
+ const payload = compareOrderDetailsStates(
112
+ prevOrderDetails,
113
+ updatedOrderDetails
114
+ );
115
+
116
+ let response = (await callUpdateOrderAPI({
117
+ payload: [payload],
118
+ })) as {
119
+ code: number | null;
120
+ error?: string | null | undefined;
121
+ [key: string]: any;
122
+ };
123
+
124
+ if (response.code === 200 && response?.data?.length > 0) {
125
+ notification.success({
126
+ message: 'Success',
127
+ description: `${response?.message}`,
128
+ });
129
+
130
+ await props?.callOrdersApi();
131
+ setOrderDetails(response?.data[0]);
132
+ setPrevOrderDetails(response?.data[0]);
133
+ closeModal();
134
+ } else {
135
+ notification.error({
136
+ message: 'Update Failed',
137
+ description: response?.error || 'Unknown error',
138
+ });
139
+
140
+ setOrderDetails(prevOrderDetails);
141
+ }
142
+ };
143
+
144
+ const infoText = `${customerInfo?.recipient_name}, ${customerInfo?.postcode}`;
145
+
146
+ const isDifferent = () => {
147
+ const original = orderRecipient || {};
148
+ return (
149
+ localW3W !== (original.what_3words_address || '') ||
150
+ localLat !== (original.latitude || '') ||
151
+ localLong !== (original.longitude || '')
152
+ );
153
+ };
154
+
155
+ useEffect(() => {
156
+ setLocalLat(orderRecipient?.latitude || '');
157
+ setLocalLong(orderRecipient?.longitude || '');
158
+ }, [orderRecipient]);
159
+
160
+ return (
161
+ <OrderActionModal
162
+ headerTitle="WHAT3WORDS"
163
+ onCloseHeader={closeModal}
164
+ onSubmit={onSave}
165
+ showAdditionalInfo
166
+ infoText={infoText}
167
+ isSubmitDisabled={isClientPortal ? true : !isDifferent() || !!w3wError}
168
+ submitBtnTitle="Save Details"
169
+ >
170
+ <Section>
171
+ <FieldRow>
172
+ <WordsSection>
173
+ <Label label="Words" />
174
+ <WordInputWrapper>
175
+ <CustomInput
176
+ name="w3w"
177
+ type="text"
178
+ placeholder="Format: word.word.word"
179
+ value={localW3W}
180
+ onChange={onChangeW3W}
181
+ disabled={isClientPortal}
182
+ />
183
+
184
+ {w3wError && <ErrorText>{w3wError}</ErrorText>}
185
+ </WordInputWrapper>
186
+
187
+ <CurrentText>
188
+ <strong>Current:</strong>{' '}
189
+ <a
190
+ href={`https://what3words.com/${currentWhat3Words}`}
191
+ target="_blank"
192
+ rel="noopener noreferrer"
193
+ >
194
+ {currentWhat3Words}
195
+ </a>
196
+ </CurrentText>
197
+ </WordsSection>
198
+ </FieldRow>
199
+ </Section>
200
+
201
+ <Section>
202
+ <CoordRow>
203
+ <Coordinate>
204
+ <Label label="Latitude" htmlFor="lat" />
205
+ <CustomInput
206
+ name="lat"
207
+ type="number"
208
+ placeholder="Lat"
209
+ value={localLat}
210
+ onChange={onChangeLatitude}
211
+ onKeyDown={handleKeyDown}
212
+ />
213
+ </Coordinate>
214
+ <Coordinate>
215
+ <Label label="Longitude" htmlFor="long" />
216
+
217
+ <CustomInput
218
+ name="long"
219
+ type="number"
220
+ placeholder="Long"
221
+ value={localLong}
222
+ onChange={onChangeLongitude}
223
+ onKeyDown={handleKeyDown}
224
+ />
225
+ </Coordinate>
226
+ </CoordRow>
227
+ </Section>
228
+ </OrderActionModal>
229
+ );
230
+ }
231
+
232
+ const WordsSection = styled.div`
233
+ display: flex;
234
+ flex-direction: row;
235
+ gap: 15px;
236
+ align-items: center;
237
+ `;
238
+
239
+ const WordInputWrapper = styled.div`
240
+ display: flex;
241
+ flex-direction: column;
242
+ gap: 4px;
243
+ width: 100%;
244
+ `;
245
+
246
+ const CurrentText = styled.div`
247
+ font-size: 13px;
248
+ color: #444;
249
+ white-space: nowrap;
250
+
251
+ a {
252
+ color: #1677ff;
253
+ text-decoration: none;
254
+ }
255
+
256
+ a:hover {
257
+ text-decoration: underline;
258
+ }
259
+ `;
260
+
261
+ const ErrorText = styled.span`
262
+ color: red;
263
+ font-size: 0.85rem;
264
+ margin-top: 4px;
265
+ `;
266
+
267
+ const Section = styled.div`
268
+ padding: 15px;
269
+ display: flex;
270
+ flex-direction: column;
271
+ gap: 16px;
272
+ `;
273
+
274
+ const FieldRow = styled.div`
275
+ display: flex;
276
+ align-items: center;
277
+ gap: 10px;
278
+ flex-wrap: wrap;
279
+ `;
280
+
281
+ const CoordRow = styled.div`
282
+ display: flex;
283
+ justify-content: space-between;
284
+ gap: 14px;
285
+ `;
286
+
287
+ const Coordinate = styled.div`
288
+ flex: 1;
289
+ display: flex;
290
+ flex-direction: column;
291
+ gap: 5px;
292
+ `;
293
+
294
+ const CustomInput = styled(GenericInput)`
295
+ width: 100%;
296
+ &::-webkit-outer-spin-button,
297
+ &::-webkit-inner-spin-button {
298
+ -webkit-appearance: none;
299
+ margin: 0;
300
+ }
301
+ &:focus-visible {
302
+ outline: none;
303
+ }
304
+ `;
305
+
306
+ export default What3WordsModal;
@@ -35,6 +35,7 @@ import ReminderModal from './actions/create-task-reminder';
35
35
  import TaskModal from './actions/create-task-reminder/TaskModal';
36
36
  import ForceDebriefModal from './actions/force-debrief/ForceDebriefModal';
37
37
  import { useNavigate } from 'react-router-dom';
38
+ import What3WordsModal from './actions/what3words/What3WordsModal.tsx';
38
39
 
39
40
  function OrderActions() {
40
41
  const [currentAction, setCurrentAction] = useCurrentAction();
@@ -311,6 +312,8 @@ function OrderActions() {
311
312
  return <ClearBookingStatus callOrderAlertsApi={callOrderAlertsApi} />;
312
313
  case 'remove-from-route':
313
314
  return <RemoveFromRoute callOrderAlertsApi={callOrderAlertsApi} />;
315
+ case 'what3Words':
316
+ return <What3WordsModal callOrdersApi={callOrdersApi} />;
314
317
  default:
315
318
  return null;
316
319
  }
@@ -9,18 +9,16 @@ function Latitude() {
9
9
  const orderRecipient = orderDetails?.order_recipient;
10
10
 
11
11
  const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
12
- const val = parseFloat(e.target.value);
12
+ const val = e.target.value;
13
13
  const updatedOrderRecipient = {
14
14
  ...(orderRecipient === null
15
15
  ? { latitude: val }
16
16
  : { ...orderRecipient, latitude: val }),
17
17
  };
18
- setOrderDetails(prev => {
19
- return {
20
- ...prev,
21
- order_recipient: updatedOrderRecipient,
22
- };
23
- });
18
+ setOrderDetails(prev => ({
19
+ ...prev,
20
+ order_recipient: updatedOrderRecipient,
21
+ }));
24
22
  };
25
23
 
26
24
  return (
@@ -9,7 +9,7 @@ function Longitude() {
9
9
  const orderRecipient = orderDetails?.order_recipient;
10
10
 
11
11
  const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
12
- const val = parseFloat(e.target.value);
12
+ const val = e.target.value;
13
13
  const updatedOrderRecipient = {
14
14
  ...(orderRecipient === null
15
15
  ? { longitude: val }
@@ -2,28 +2,47 @@ import { Input, Label } from '@bjsdistribution/dms-component-library';
2
2
  import React from 'react';
3
3
  import styled from 'styled-components';
4
4
  import What3WordsIcon from '../../../../../../assets/icons/What3WordsIcon';
5
- import { errorAtom, useAtom, useOrderDetails } from '../../../../store';
5
+ import {
6
+ errorAtom,
7
+ useAtom,
8
+ useCurrentAction,
9
+ useOrderDetails,
10
+ } from '../../../../store';
11
+ import { sanitizeInput } from '../../../../utils';
12
+ import { useClientPortal } from '../../../../../store';
6
13
 
7
14
  function What3words() {
8
15
  const w3wRegex = /^\/{0,}[^0-9`~!@#$%^&*()+\-_=[{\]}\\|'<,.>?/";:£§º©®\s]{1,}[・.。][^0-9`~!@#$%^&*()+\-_=[{\]}\\|'<,.>?/";:£§º©®\s]{1,}[・.。][^0-9`~!@#$%^&*()+\-_=[{\]}\\|'<,.>?/";:£§º©®\s]{1,}$/;
9
16
  const [orderDetails, setOrderDetails] = useOrderDetails();
10
17
  const [errors, setErrors] = useAtom(errorAtom);
18
+ const [isClientPortal] = useClientPortal();
11
19
  const orderRecipient = orderDetails?.order_recipient;
20
+ const [, setCurrentAction] = useCurrentAction();
21
+
22
+ const cleanW3WAddress = (w3wStr: string) => {
23
+ if (w3wStr.startsWith('///')) {
24
+ return w3wStr.slice(3);
25
+ } else if (w3wStr.startsWith('//')) {
26
+ return w3wStr.slice(2);
27
+ } else if (w3wStr.startsWith('/')) {
28
+ return w3wStr.slice(1);
29
+ }
30
+ return w3wStr;
31
+ };
12
32
 
13
33
  const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
14
- const val = e.target.value;
34
+ let val = sanitizeInput(e.target.value);
15
35
  validateInput(val);
16
36
  const updatedOrderRecipient = {
17
37
  ...(orderRecipient === null
18
38
  ? { what_3words_address: val }
19
- : { ...orderRecipient, what_3words_address: val }),
39
+ : { ...orderRecipient, what_3words_address: cleanW3WAddress(val) }),
20
40
  };
21
- setOrderDetails(prev => {
22
- return {
23
- ...prev,
24
- order_recipient: updatedOrderRecipient,
25
- };
26
- });
41
+
42
+ setOrderDetails(prev => ({
43
+ ...prev,
44
+ order_recipient: updatedOrderRecipient,
45
+ }));
27
46
  };
28
47
 
29
48
  const validateInput = (value: string) => {
@@ -31,19 +50,30 @@ function What3words() {
31
50
  const isValid = w3wRegex.test(value);
32
51
  setErrors(prev => ({
33
52
  ...prev,
34
- w3wError: isValid ? '' : 'Please enter a valid input (word.word.word)',
53
+ w3wError: isValid
54
+ ? ''
55
+ : 'Please enter a valid input (format: word.word.word)',
35
56
  }));
36
57
  } else {
37
58
  setErrors(prev => ({
38
59
  ...prev,
39
- w3wError: '',
60
+ w3wError: 'Input cannot be empty or only spaces',
40
61
  }));
41
62
  }
42
63
  };
43
64
 
65
+ // const handleOpenModal = () => {
66
+ // dispatch({ type: 'what3Words' });
67
+ // };
68
+
44
69
  return (
45
70
  <Container>
46
- <Label label="What3Words Address" htmlFor="w3w" />
71
+ <Label
72
+ style={{ color: '#6396e5', cursor: 'pointer' }}
73
+ label="What3Words Address"
74
+ htmlFor="w3w"
75
+ onClick={() => setCurrentAction('what3Words')}
76
+ />
47
77
  <Wrapper>
48
78
  <What3wordsPrefix>
49
79
  <What3WordsIcon />
@@ -54,12 +84,14 @@ function What3words() {
54
84
  placeholder="Format: word.word.word"
55
85
  value={orderRecipient?.what_3words_address || ''}
56
86
  onChange={onChange}
87
+ disabled={isClientPortal}
57
88
  />
58
89
  </Wrapper>
59
90
  {errors.w3wError && <ErrorText>{errors.w3wError}</ErrorText>}
60
91
  </Container>
61
92
  );
62
93
  }
94
+
63
95
  const What3wordsPrefix = styled.div`
64
96
  position: absolute;
65
97
  top: 13px;
@@ -71,7 +103,7 @@ const Wrapper = styled.div`
71
103
  `;
72
104
 
73
105
  const CustomInput = styled(Input)`
74
- border-radius: 25px;
106
+ /* border-radius: 25px; */
75
107
  &::-webkit-outer-spin-button,
76
108
  &::-webkit-inner-spin-button {
77
109
  -webkit-appearance: none;
@@ -51,7 +51,8 @@ export type OrderActionsType =
51
51
  | 'clear-date'
52
52
  | 'uncancel-order'
53
53
  | 'remove-from-route'
54
- | 'force-debrief';
54
+ | 'force-debrief'
55
+ | 'what3Words';
55
56
 
56
57
  export interface ChargeLogData {
57
58
  id: number;
@@ -76,8 +77,8 @@ export type OrderRecipientType = {
76
77
  postcode?: string | null;
77
78
  postcode_district?: string | null;
78
79
  postcode_short?: string | null;
79
- latitude?: number | null;
80
- longitude?: number | null;
80
+ latitude?: string | null;
81
+ longitude?: string | null;
81
82
  what_3words_address?: string | null;
82
83
  contact_home?: string | null;
83
84
  contact_work?: string | null;