@capillarytech/creatives-library 7.10.34 → 7.10.35
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 +1 -1
- package/services/api.js +5 -0
- package/v2Containers/SmsTrai/Create/actions.js +8 -1
- package/v2Containers/SmsTrai/Create/constants.js +11 -1
- package/v2Containers/SmsTrai/Create/index.js +325 -222
- package/v2Containers/SmsTrai/Create/reducer.js +24 -0
- package/v2Containers/SmsTrai/Create/sagas.js +28 -5
- package/v2Containers/SmsTrai/Create/tests/__snapshots__/index.test.js.snap +311 -6760
- package/v2Containers/SmsTrai/Create/tests/index.test.js +5 -15
- package/v2Containers/SmsTrai/Create/tests/mockData.js +13 -0
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
/* eslint-disable no-trailing-spaces */
|
|
2
|
+
/* eslint-disable guard-for-in */
|
|
3
|
+
/* eslint-disable no-loop-func */
|
|
4
|
+
/* eslint-disable no-restricted-syntax */
|
|
1
5
|
/* eslint-disable no-undef */
|
|
2
6
|
import PropTypes from 'prop-types';
|
|
3
|
-
import React, { useState } from 'react';
|
|
7
|
+
import React, { useState, useEffect } from 'react';
|
|
4
8
|
import { FormattedMessage, injectIntl } from 'react-intl';
|
|
5
9
|
import { createStructuredSelector } from 'reselect';
|
|
6
10
|
import styled from 'styled-components';
|
|
@@ -17,7 +21,7 @@ import CapSpin from '@capillarytech/cap-ui-library/CapSpin';
|
|
|
17
21
|
import CapIcon from '@capillarytech/cap-ui-library/CapIcon';
|
|
18
22
|
import CapNotification from '@capillarytech/cap-ui-library/CapNotification';
|
|
19
23
|
import Papa from 'papaparse';
|
|
20
|
-
import cloneDeep from 'lodash
|
|
24
|
+
import { cloneDeep, isEmpty } from 'lodash';
|
|
21
25
|
import {
|
|
22
26
|
CAP_SPACE_16,
|
|
23
27
|
CAP_SPACE_24,
|
|
@@ -27,6 +31,7 @@ import {
|
|
|
27
31
|
CAP_PRIMARY,
|
|
28
32
|
} from '@capillarytech/cap-ui-library/styled/variables';
|
|
29
33
|
import './index.scss';
|
|
34
|
+
import makeSelectSmsTrai from './selectors';
|
|
30
35
|
import dragdropImage from '../../../assets/group.png';
|
|
31
36
|
import * as smsTraiActions from './actions';
|
|
32
37
|
import {
|
|
@@ -47,6 +52,9 @@ import {
|
|
|
47
52
|
SAVED_COLUMNS,
|
|
48
53
|
MAPPED_SAVED_COLUMN,
|
|
49
54
|
SMS,
|
|
55
|
+
FAILURE,
|
|
56
|
+
SUCCESS,
|
|
57
|
+
DUPLICATE,
|
|
50
58
|
} from './constants';
|
|
51
59
|
import messages from './messages';
|
|
52
60
|
import withCreatives from '../../../hoc/withCreatives';
|
|
@@ -58,15 +66,25 @@ export const SmsTraiCreate = (props) => {
|
|
|
58
66
|
onCreateComplete,
|
|
59
67
|
isFullMode,
|
|
60
68
|
onShowTemplates,
|
|
69
|
+
traiSms: {
|
|
70
|
+
duplicateDetails = {},
|
|
71
|
+
duplicateDetailsError = '',
|
|
72
|
+
duplicateDetailsStatus = '',
|
|
73
|
+
} = {},
|
|
61
74
|
} = props;
|
|
62
75
|
const { formatMessage } = intl;
|
|
76
|
+
const [traiFileData, setTraiFileData] = useState({});
|
|
77
|
+
const [traiFileMeta, setTraiFileMeta] = useState(null);
|
|
78
|
+
const [validatedData, setValidatedData] = useState([]);
|
|
63
79
|
const [files, setFiles] = useState([]);
|
|
80
|
+
const [templateRegistrationIndex, setTemplateRegistrationIndex] = useState(null);
|
|
64
81
|
const [errorText, setErrorText] = useState('');
|
|
65
82
|
const [dropZone, setDropZone] = useState(true);
|
|
66
83
|
const [previewFileIndex, setPreviewFileIndex] = useState(null);
|
|
67
84
|
const [isFileDropped, setIsFileDropped] = useState(false);
|
|
68
85
|
const [totalFailCount, setTotalFailCount] = useState(0);
|
|
69
86
|
const [saveTemplate, updateSaveTemplate] = useState(false);
|
|
87
|
+
const [uploadValidated, setUploadValidated] = useState(false);
|
|
70
88
|
|
|
71
89
|
const SMSTraiFooter = styled.div`
|
|
72
90
|
background-color: ${CAP_WHITE};
|
|
@@ -80,8 +98,229 @@ export const SmsTraiCreate = (props) => {
|
|
|
80
98
|
}
|
|
81
99
|
}
|
|
82
100
|
`;
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
if (!isEmpty(traiFileData) && traiFileMeta?.name) {
|
|
103
|
+
if (traiFileMeta?.name?.split('.').pop() === 'csv') {
|
|
104
|
+
const mandatoryColNotPresent = [];
|
|
105
|
+
const headerAliasPresent = [];
|
|
106
|
+
const templateIdAliasPresent = [];
|
|
107
|
+
const templateMessageAliasPresent = [];
|
|
108
|
+
const statusAliasPresent = [];
|
|
109
|
+
const typeAliasPresent = [];
|
|
110
|
+
const colCounts = {};
|
|
111
|
+
const traiFileDataCopy = cloneDeep(traiFileData);
|
|
112
|
+
//removing last empty row if each cell is empty
|
|
113
|
+
if (traiFileDataCopy?.data[traiFileDataCopy?.data?.length - 1]?.every((cell) => !cell)) {
|
|
114
|
+
traiFileDataCopy.data.pop();
|
|
115
|
+
}
|
|
116
|
+
const fileLen = traiFileDataCopy?.data?.length || 0;
|
|
117
|
+
// at least 1 records should be available
|
|
118
|
+
if (fileLen < 2) {
|
|
119
|
+
setErrorText(formatMessage(messages.fileMinlengthError));
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
// more then 500 records are not available.
|
|
123
|
+
if (fileLen > 501) {
|
|
124
|
+
setErrorText(formatMessage(messages.fileMaxlengthError));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const cols = getAllColumnNames(traiFileDataCopy.data[0]);
|
|
129
|
+
|
|
130
|
+
for (let i = 0; i < MANDATORY_COLUMNS.length; i += 1) {
|
|
131
|
+
if (!cols.includes(MANDATORY_COLUMNS[i])) {
|
|
132
|
+
mandatoryColNotPresent.push(MANDATORY_COLUMNS[i]);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (mandatoryColNotPresent.length > 0) {
|
|
136
|
+
setErrorText(formatMessage(messages.mandatoryColErrorDescription));
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
//one of the mandatory header alias should be available.
|
|
141
|
+
for (let i = 0; i < HEADER_ALIASES.length; i += 1) {
|
|
142
|
+
if (cols.includes(HEADER_ALIASES[i])) {
|
|
143
|
+
headerAliasPresent.push(HEADER_ALIASES[i]);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (headerAliasPresent.length === 0) {
|
|
147
|
+
setErrorText(
|
|
148
|
+
formatMessage(messages.headerAliasErrorDescription, {
|
|
149
|
+
headerAliases: HEADER_ALIASES.join(' / '),
|
|
150
|
+
}),
|
|
151
|
+
);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
//one of the mandatory template id alias should be available.
|
|
155
|
+
for (let i = 0; i < TEMPLATE_ID_ALIASES.length; i += 1) {
|
|
156
|
+
if (cols.includes(TEMPLATE_ID_ALIASES[i])) {
|
|
157
|
+
templateIdAliasPresent.push(TEMPLATE_ID_ALIASES[i]);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (templateIdAliasPresent.length === 0) {
|
|
161
|
+
setErrorText(
|
|
162
|
+
formatMessage(messages.templateIdAliasErrorDescription, {
|
|
163
|
+
templateIdAliases: TEMPLATE_ID_ALIASES.join(' / '),
|
|
164
|
+
}),
|
|
165
|
+
);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
//one of the mandatory template message alias should be available.
|
|
169
|
+
for (let i = 0; i < TEMPLATE_MESSAGE_ALIASES.length; i += 1) {
|
|
170
|
+
if (cols.includes(TEMPLATE_MESSAGE_ALIASES[i])) {
|
|
171
|
+
templateMessageAliasPresent.push(TEMPLATE_MESSAGE_ALIASES[i]);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (templateMessageAliasPresent.length === 0) {
|
|
175
|
+
setErrorText(
|
|
176
|
+
formatMessage(messages.templateMessageAliasErrorDescription, {
|
|
177
|
+
templateMessageAliases: TEMPLATE_MESSAGE_ALIASES.join(' / '),
|
|
178
|
+
}),
|
|
179
|
+
);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
//one of the mandatory status alias should be available.
|
|
183
|
+
for (let i = 0; i < STATUS_ALIASES.length; i += 1) {
|
|
184
|
+
if (cols.includes(STATUS_ALIASES[i])) {
|
|
185
|
+
statusAliasPresent.push(STATUS_ALIASES[i]);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (statusAliasPresent.length === 0) {
|
|
189
|
+
setErrorText(
|
|
190
|
+
formatMessage(messages.statusAliasErrorDescription, {
|
|
191
|
+
statusAliases: STATUS_ALIASES.join(' / '),
|
|
192
|
+
}),
|
|
193
|
+
);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
//one of the mandatory type alias should be available.
|
|
197
|
+
for (let i = 0; i < TYPE_ALIASES.length; i += 1) {
|
|
198
|
+
if (cols.includes(TYPE_ALIASES[i])) {
|
|
199
|
+
typeAliasPresent.push(TYPE_ALIASES[i]);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (typeAliasPresent.length === 0) {
|
|
203
|
+
setErrorText(
|
|
204
|
+
formatMessage(messages.typeAliasErrorDescription, {
|
|
205
|
+
typeAliases: TYPE_ALIASES.join(' / '),
|
|
206
|
+
}),
|
|
207
|
+
);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
// count of each col to validate repetitive cols
|
|
211
|
+
for (const num of cols) {
|
|
212
|
+
colCounts[num] = colCounts[num] ? colCounts[num] + 1 : 1;
|
|
213
|
+
}
|
|
83
214
|
|
|
84
|
-
|
|
215
|
+
const repetitiveColsArray = Object.keys(
|
|
216
|
+
Object.fromEntries(
|
|
217
|
+
Object.entries(colCounts).filter(
|
|
218
|
+
([key, value]) => key !== '' && value > 1,
|
|
219
|
+
),
|
|
220
|
+
),
|
|
221
|
+
);
|
|
222
|
+
//throw validation if repetitive columns present
|
|
223
|
+
if (repetitiveColsArray.length !== 0) {
|
|
224
|
+
setErrorText(
|
|
225
|
+
formatMessage(messages.repetitiveColsErrorDescription, {
|
|
226
|
+
repetitiveCols: repetitiveColsArray.join(', '),
|
|
227
|
+
}),
|
|
228
|
+
);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
//perform required data transformation
|
|
232
|
+
dataTransformHandler(
|
|
233
|
+
cols,
|
|
234
|
+
traiFileDataCopy,
|
|
235
|
+
headerAliasPresent,
|
|
236
|
+
templateIdAliasPresent,
|
|
237
|
+
statusAliasPresent,
|
|
238
|
+
);
|
|
239
|
+
} else {
|
|
240
|
+
setErrorText(formatMessage(messages.nonCsvErrorDescription));
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}, [traiFileData, traiFileMeta]);
|
|
244
|
+
|
|
245
|
+
useEffect(() => {
|
|
246
|
+
const { count = 0, template_ids = [] } = duplicateDetails || {};
|
|
247
|
+
|
|
248
|
+
if (errorText && !uploadValidated) {
|
|
249
|
+
setIsFileDropped(false); // Stop the spinner
|
|
250
|
+
const selectedFiles = cloneDeep(files);
|
|
251
|
+
selectedFiles.push({
|
|
252
|
+
fileMeta: traiFileMeta,
|
|
253
|
+
fileData: traiFileData,
|
|
254
|
+
columnMapping: [],
|
|
255
|
+
});
|
|
256
|
+
setFiles(selectedFiles);
|
|
257
|
+
setPreviewFileIndex(selectedFiles.length - 1);
|
|
258
|
+
setDropZone(false);
|
|
259
|
+
} else if (uploadValidated && duplicateDetailsStatus === FAILURE) {
|
|
260
|
+
deleteOrCancelCSV();
|
|
261
|
+
CapNotification.error({ message: duplicateDetailsError || FAILURE });
|
|
262
|
+
} else if (uploadValidated && duplicateDetailsStatus === SUCCESS && !isEmpty(validatedData)) {
|
|
263
|
+
if (!isEmpty(duplicateDetails) && count > 0 && templateRegistrationIndex !== null) {
|
|
264
|
+
const clonedValidatedData = cloneDeep(validatedData);
|
|
265
|
+
let duplicateTemplateNameCounts = 0;
|
|
266
|
+
const apiResponseIds = [...new Set(template_ids)];
|
|
267
|
+
|
|
268
|
+
for (let i = 1; i < clonedValidatedData.length; i += 1) {
|
|
269
|
+
const duplicatePresent = apiResponseIds?.includes(clonedValidatedData[i][templateRegistrationIndex]?.replace(/[“”"'‘’`´]/g, ''));
|
|
270
|
+
if (duplicatePresent) {
|
|
271
|
+
//if rejection reason for this row is empty, consider as new error
|
|
272
|
+
if (!clonedValidatedData[i][clonedValidatedData[i]?.length - 1]) {
|
|
273
|
+
duplicateTemplateNameCounts += 1;
|
|
274
|
+
}
|
|
275
|
+
//add rejection reason to rejection reason column
|
|
276
|
+
clonedValidatedData[i][
|
|
277
|
+
clonedValidatedData[i]?.length - 1
|
|
278
|
+
] += ` This template name is already present in the database.Duplicate template names are not allowed.`;
|
|
279
|
+
}
|
|
280
|
+
} //Duplicate should match constant DUPLICATE
|
|
281
|
+
setTotalFailCount((prev) => prev + duplicateTemplateNameCounts);
|
|
282
|
+
setValidatedData(clonedValidatedData);
|
|
283
|
+
}
|
|
284
|
+
updateSaveTemplate(true);
|
|
285
|
+
}
|
|
286
|
+
}, [duplicateDetailsStatus, errorText, templateRegistrationIndex]);
|
|
287
|
+
|
|
288
|
+
useEffect(() => {
|
|
289
|
+
if (saveTemplate && !isEmpty(traiFileData) && !isEmpty(validatedData)) {
|
|
290
|
+
setIsFileDropped(false); // Stop the spinner
|
|
291
|
+
const selectedFiles = cloneDeep(files);
|
|
292
|
+
const traiFileDataCopy = cloneDeep(traiFileData);
|
|
293
|
+
//removing last empty row if each cell is empty
|
|
294
|
+
if (traiFileDataCopy?.data[traiFileDataCopy?.data?.length - 1]?.every((cell) => !cell)) {
|
|
295
|
+
traiFileDataCopy.data.pop();
|
|
296
|
+
}
|
|
297
|
+
//remove capillary rejection reason column if already present on reupload
|
|
298
|
+
//ex: user reuploads a csv which was earlier validated and rejection reasons were added for failed templates
|
|
299
|
+
const rejectionIndex = getAllColumnNames(
|
|
300
|
+
traiFileDataCopy.data[0],
|
|
301
|
+
).indexOf(CAPILLARY_REJECTION_REASON.toLowerCase());
|
|
302
|
+
if (rejectionIndex !== -1) {
|
|
303
|
+
traiFileDataCopy.data.forEach((row) => {
|
|
304
|
+
row.splice(rejectionIndex, 1);
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
//adding validated rejection reason column to original results
|
|
308
|
+
for (let i = 0; i < validatedData.length; i += 1) {
|
|
309
|
+
traiFileDataCopy.data[i].push(
|
|
310
|
+
validatedData[i][validatedData[i]?.length - 1],
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
selectedFiles.push({
|
|
315
|
+
fileMeta: traiFileMeta,
|
|
316
|
+
fileData: traiFileDataCopy,
|
|
317
|
+
columnMapping: [],
|
|
318
|
+
});
|
|
319
|
+
setFiles(selectedFiles);
|
|
320
|
+
setPreviewFileIndex(selectedFiles.length - 1);
|
|
321
|
+
setDropZone(false);
|
|
322
|
+
}
|
|
323
|
+
}, [validatedData, saveTemplate]);
|
|
85
324
|
|
|
86
325
|
const getIndexofColumn = (allColumnNames, columnName) =>
|
|
87
326
|
allColumnNames.findIndex(
|
|
@@ -134,9 +373,34 @@ export const SmsTraiCreate = (props) => {
|
|
|
134
373
|
return isError;
|
|
135
374
|
};
|
|
136
375
|
|
|
137
|
-
const
|
|
376
|
+
const checkDuplicateTemplateNameInDb = (
|
|
377
|
+
transformedResults,
|
|
378
|
+
templateIdIndex,
|
|
379
|
+
templateNameIndex,
|
|
380
|
+
) => {
|
|
381
|
+
//filtering out rows that have duplicate template name in CSV and rows which have template name/template id missing
|
|
382
|
+
const checkInDb = transformedResults
|
|
383
|
+
?.map((elem, index) => {
|
|
384
|
+
const rejectionReason = elem[elem?.length - 1]?.trim();
|
|
385
|
+
if (index !== 0 && !rejectionReason?.includes(DUPLICATE)) {
|
|
386
|
+
return {
|
|
387
|
+
template_id: elem[templateIdIndex]?.replace(/[“”"'‘’`´]/g, ''),
|
|
388
|
+
template_name: elem[templateNameIndex],
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
})
|
|
392
|
+
?.filter((obj) => obj && obj.template_name !== '' && obj.template_id !== '');
|
|
393
|
+
setUploadValidated(true);
|
|
394
|
+
setValidatedData(transformedResults);
|
|
395
|
+
setTemplateRegistrationIndex(templateIdIndex);
|
|
396
|
+
actions.checkDuplicatesInDb(checkInDb);
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
const checkDuplicateTemplateNameInCSV = (
|
|
138
400
|
transformedResults,
|
|
139
401
|
templateNameObj,
|
|
402
|
+
templateIdIndex,
|
|
403
|
+
templateNameIndex,
|
|
140
404
|
) => {
|
|
141
405
|
//filtering and extracting index of template names with more than 1 occurence
|
|
142
406
|
const duplicateTemplateNameRows =
|
|
@@ -155,11 +419,15 @@ export const SmsTraiCreate = (props) => {
|
|
|
155
419
|
}
|
|
156
420
|
transformedResults[row][
|
|
157
421
|
transformedResults[row]?.length - 1
|
|
158
|
-
] += ` Duplicate template names are not allowed.`;
|
|
422
|
+
] += ` This template name is repeated in this CSV.Duplicate template names are not allowed.`;
|
|
159
423
|
});
|
|
160
424
|
setTotalFailCount((prev) => prev + duplicateTemplateNameCounts);
|
|
161
425
|
}
|
|
162
|
-
|
|
426
|
+
checkDuplicateTemplateNameInDb(
|
|
427
|
+
transformedResults,
|
|
428
|
+
templateIdIndex,
|
|
429
|
+
templateNameIndex,
|
|
430
|
+
);
|
|
163
431
|
};
|
|
164
432
|
|
|
165
433
|
const checkCSVRowsValidation = (
|
|
@@ -207,8 +475,8 @@ export const SmsTraiCreate = (props) => {
|
|
|
207
475
|
errorArray.push(`"${transformedResults[0][index]}"`);
|
|
208
476
|
} else if (index === templateNameIndex && cell) {
|
|
209
477
|
if (templateNameObj[cell]) {
|
|
210
|
-
templateNameObj[cell]
|
|
211
|
-
templateNameObj[cell]
|
|
478
|
+
templateNameObj[cell].count += 1;
|
|
479
|
+
templateNameObj[cell].csvRow?.push(row);
|
|
212
480
|
} else {
|
|
213
481
|
templateNameObj[cell] = { count: 1, csvRow: [row] };
|
|
214
482
|
}
|
|
@@ -236,154 +504,17 @@ export const SmsTraiCreate = (props) => {
|
|
|
236
504
|
setTotalFailCount((prev) => prev + 1);
|
|
237
505
|
}
|
|
238
506
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
const mandatoryColNotPresent = [];
|
|
246
|
-
const headerAliasPresent = [];
|
|
247
|
-
const templateIdAliasPresent = [];
|
|
248
|
-
const templateMessageAliasPresent = [];
|
|
249
|
-
const statusAliasPresent = [];
|
|
250
|
-
const typeAliasPresent = [];
|
|
251
|
-
const colCounts = {};
|
|
252
|
-
//removing last empty row if each cell is empty
|
|
253
|
-
if (results?.data[results.data.length - 1]?.every((cell) => !cell)) {
|
|
254
|
-
results.data.pop();
|
|
255
|
-
}
|
|
256
|
-
const fileLen = results?.data?.length || 0;
|
|
257
|
-
// at least 1 records should be available
|
|
258
|
-
if (fileLen < 2) {
|
|
259
|
-
setErrorText(formatMessage(messages.fileMinlengthError));
|
|
260
|
-
return;
|
|
261
|
-
}
|
|
262
|
-
// more then 500 records are not available.
|
|
263
|
-
if (fileLen > 501) {
|
|
264
|
-
setErrorText(formatMessage(messages.fileMaxlengthError));
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
const cols = getAllColumnNames(results.data[0]);
|
|
269
|
-
|
|
270
|
-
for (let i = 0; i < MANDATORY_COLUMNS.length; i += 1) {
|
|
271
|
-
if (!cols.includes(MANDATORY_COLUMNS[i])) {
|
|
272
|
-
mandatoryColNotPresent.push(MANDATORY_COLUMNS[i]);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
if (mandatoryColNotPresent.length > 0) {
|
|
276
|
-
setErrorText(formatMessage(messages.mandatoryColErrorDescription));
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
//one of the mandatory header alias should be available.
|
|
281
|
-
for (let i = 0; i < HEADER_ALIASES.length; i += 1) {
|
|
282
|
-
if (cols.includes(HEADER_ALIASES[i])) {
|
|
283
|
-
headerAliasPresent.push(HEADER_ALIASES[i]);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
if (headerAliasPresent.length === 0) {
|
|
287
|
-
setErrorText(
|
|
288
|
-
formatMessage(messages.headerAliasErrorDescription, {
|
|
289
|
-
headerAliases: HEADER_ALIASES.join(' / '),
|
|
290
|
-
}),
|
|
291
|
-
);
|
|
292
|
-
return;
|
|
293
|
-
}
|
|
294
|
-
//one of the mandatory template id alias should be available.
|
|
295
|
-
for (let i = 0; i < TEMPLATE_ID_ALIASES.length; i += 1) {
|
|
296
|
-
if (cols.includes(TEMPLATE_ID_ALIASES[i])) {
|
|
297
|
-
templateIdAliasPresent.push(TEMPLATE_ID_ALIASES[i]);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
if (templateIdAliasPresent.length === 0) {
|
|
301
|
-
setErrorText(
|
|
302
|
-
formatMessage(messages.templateIdAliasErrorDescription, {
|
|
303
|
-
templateIdAliases: TEMPLATE_ID_ALIASES.join(' / '),
|
|
304
|
-
}),
|
|
305
|
-
);
|
|
306
|
-
return;
|
|
307
|
-
}
|
|
308
|
-
//one of the mandatory template message alias should be available.
|
|
309
|
-
for (let i = 0; i < TEMPLATE_MESSAGE_ALIASES.length; i += 1) {
|
|
310
|
-
if (cols.includes(TEMPLATE_MESSAGE_ALIASES[i])) {
|
|
311
|
-
templateMessageAliasPresent.push(TEMPLATE_MESSAGE_ALIASES[i]);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
if (templateMessageAliasPresent.length === 0) {
|
|
315
|
-
setErrorText(
|
|
316
|
-
formatMessage(messages.templateMessageAliasErrorDescription, {
|
|
317
|
-
templateMessageAliases: TEMPLATE_MESSAGE_ALIASES.join(' / '),
|
|
318
|
-
}),
|
|
319
|
-
);
|
|
320
|
-
return;
|
|
321
|
-
}
|
|
322
|
-
//one of the mandatory status alias should be available.
|
|
323
|
-
for (let i = 0; i < STATUS_ALIASES.length; i += 1) {
|
|
324
|
-
if (cols.includes(STATUS_ALIASES[i])) {
|
|
325
|
-
statusAliasPresent.push(STATUS_ALIASES[i]);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
if (statusAliasPresent.length === 0) {
|
|
329
|
-
setErrorText(
|
|
330
|
-
formatMessage(messages.statusAliasErrorDescription, {
|
|
331
|
-
statusAliases: STATUS_ALIASES.join(' / '),
|
|
332
|
-
}),
|
|
333
|
-
);
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
//one of the mandatory type alias should be available.
|
|
337
|
-
for (let i = 0; i < TYPE_ALIASES.length; i += 1) {
|
|
338
|
-
if (cols.includes(TYPE_ALIASES[i])) {
|
|
339
|
-
typeAliasPresent.push(TYPE_ALIASES[i]);
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
if (typeAliasPresent.length === 0) {
|
|
343
|
-
setErrorText(
|
|
344
|
-
formatMessage(messages.typeAliasErrorDescription, {
|
|
345
|
-
typeAliases: TYPE_ALIASES.join(' / '),
|
|
346
|
-
}),
|
|
347
|
-
);
|
|
348
|
-
return;
|
|
349
|
-
}
|
|
350
|
-
// count of each col to validate blank and repetitive cols
|
|
351
|
-
for (const num of cols) {
|
|
352
|
-
colCounts[num] = colCounts[num] ? colCounts[num] + 1 : 1;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
const repetitiveColsArray = Object.keys(
|
|
356
|
-
Object.fromEntries(
|
|
357
|
-
Object.entries(colCounts).filter(
|
|
358
|
-
([key, value]) => key !== '' && value > 1,
|
|
359
|
-
),
|
|
360
|
-
),
|
|
361
|
-
);
|
|
362
|
-
//throw validation if repetitive columns present
|
|
363
|
-
if (repetitiveColsArray.length !== 0) {
|
|
364
|
-
setErrorText(
|
|
365
|
-
formatMessage(messages.repetitiveColsErrorDescription, {
|
|
366
|
-
repetitiveCols: repetitiveColsArray.join(', '),
|
|
367
|
-
}),
|
|
368
|
-
);
|
|
369
|
-
return;
|
|
370
|
-
}
|
|
371
|
-
//perform required data transformation
|
|
372
|
-
return dataTransformHandler(
|
|
373
|
-
cols,
|
|
374
|
-
results,
|
|
375
|
-
headerAliasPresent,
|
|
376
|
-
templateIdAliasPresent,
|
|
377
|
-
statusAliasPresent,
|
|
378
|
-
);
|
|
379
|
-
} else {
|
|
380
|
-
setErrorText(formatMessage(messages.nonCsvErrorDescription));
|
|
381
|
-
}
|
|
507
|
+
checkDuplicateTemplateNameInCSV(
|
|
508
|
+
transformedResults,
|
|
509
|
+
templateNameObj,
|
|
510
|
+
templateIdIndex,
|
|
511
|
+
templateNameIndex,
|
|
512
|
+
);
|
|
382
513
|
};
|
|
383
514
|
|
|
384
515
|
const dataTransformHandler = (
|
|
385
516
|
cols,
|
|
386
|
-
|
|
517
|
+
traiFileDataCopy,
|
|
387
518
|
headerAliasPresent,
|
|
388
519
|
templateIdAliasPresent,
|
|
389
520
|
statusAliasPresent,
|
|
@@ -404,14 +535,14 @@ export const SmsTraiCreate = (props) => {
|
|
|
404
535
|
}
|
|
405
536
|
//transforming data to keep only mandatory cols data using their index
|
|
406
537
|
const transformedResults = [];
|
|
407
|
-
for (const key in
|
|
538
|
+
for (const key in traiFileDataCopy.data) {
|
|
408
539
|
transformedResults.push(
|
|
409
|
-
indexArray.map((i) =>
|
|
540
|
+
indexArray.map((i) => traiFileDataCopy.data[key][i]?.trim()),
|
|
410
541
|
);
|
|
411
542
|
}
|
|
412
543
|
//add rejection header
|
|
413
544
|
transformedResults[0].push(CAPILLARY_REJECTION_REASON);
|
|
414
|
-
|
|
545
|
+
checkCSVRowsValidation(
|
|
415
546
|
transformedResults,
|
|
416
547
|
headerAliasPresent[0],
|
|
417
548
|
templateIdAliasPresent[0],
|
|
@@ -422,57 +553,18 @@ export const SmsTraiCreate = (props) => {
|
|
|
422
553
|
//returns the headers(first row) of the uploaded file in lowercase
|
|
423
554
|
const getAllColumnNames = (data) => data.map((x) => x?.toLowerCase()?.trim());
|
|
424
555
|
|
|
425
|
-
const onDrop = (
|
|
556
|
+
const onDrop = (traiFile) => {
|
|
426
557
|
// Sets isFileDropped state to true to load the spinner while the file is being parsed.
|
|
427
558
|
setIsFileDropped(true);
|
|
428
559
|
|
|
429
|
-
Papa.parse(
|
|
430
|
-
complete: (
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
const selectedFiles = cloneDeep(files);
|
|
434
|
-
if (uploadValidated) {
|
|
435
|
-
//remove capillary rejection reason column if already present on reupload
|
|
436
|
-
//ex: user reuploads a csv which was earlier validated and rejection reasons were added for failed templates
|
|
437
|
-
const rejectionIndex = getAllColumnNames(results.data[0]).indexOf(
|
|
438
|
-
CAPILLARY_REJECTION_REASON.toLowerCase(),
|
|
439
|
-
);
|
|
440
|
-
if (rejectionIndex !== -1) {
|
|
441
|
-
results.data.forEach((row) => {
|
|
442
|
-
row.splice(rejectionIndex, 1);
|
|
443
|
-
});
|
|
444
|
-
}
|
|
445
|
-
//adding validated rejection reason column to original results
|
|
446
|
-
for (let i = 0; i < validatedResults.length; i += 1) {
|
|
447
|
-
results.data[i].push(
|
|
448
|
-
validatedResults[i][validatedResults[i]?.length - 1],
|
|
449
|
-
);
|
|
450
|
-
}
|
|
451
|
-
updateSaveTemplate(true);
|
|
452
|
-
}
|
|
453
|
-
selectedFiles.push({
|
|
454
|
-
file,
|
|
455
|
-
fileResult: results,
|
|
456
|
-
columnMapping: [],
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
setFiles(selectedFiles);
|
|
460
|
-
setPreviewFileIndex(selectedFiles.length - 1);
|
|
461
|
-
setDropZone(false);
|
|
560
|
+
Papa.parse(traiFile[0], {
|
|
561
|
+
complete: (fileData, fileMeta) => {
|
|
562
|
+
setTraiFileData(fileData);
|
|
563
|
+
setTraiFileMeta(fileMeta);
|
|
462
564
|
},
|
|
463
565
|
});
|
|
464
566
|
};
|
|
465
567
|
|
|
466
|
-
const deleteCSV = () => {
|
|
467
|
-
setFiles([]);
|
|
468
|
-
setTotalFailCount(0);
|
|
469
|
-
setErrorText('');
|
|
470
|
-
setIsFileDropped(false);
|
|
471
|
-
setPreviewFileIndex(null);
|
|
472
|
-
setDropZone(true);
|
|
473
|
-
updateSaveTemplate(false);
|
|
474
|
-
};
|
|
475
|
-
|
|
476
568
|
//common function to handle csv downloads
|
|
477
569
|
const downloadHandler = (event, data, downloadedFileName) => {
|
|
478
570
|
event.target.setAttribute(
|
|
@@ -490,14 +582,14 @@ export const SmsTraiCreate = (props) => {
|
|
|
490
582
|
const downloadIssuesCSV = (event) => {
|
|
491
583
|
let csvContent = [];
|
|
492
584
|
let tempArray = [];
|
|
493
|
-
const
|
|
494
|
-
const rejectionIndex =
|
|
585
|
+
const csvData = files[0].fileData.data;
|
|
586
|
+
const rejectionIndex = csvData[0].indexOf(CAPILLARY_REJECTION_REASON);
|
|
495
587
|
|
|
496
588
|
if (rejectionIndex !== -1) {
|
|
497
|
-
tempArray.push(
|
|
498
|
-
for (let row = 1; row <
|
|
499
|
-
if (
|
|
500
|
-
tempArray.push(
|
|
589
|
+
tempArray.push(csvData[0]);
|
|
590
|
+
for (let row = 1; row < csvData.length; row += 1) {
|
|
591
|
+
if (csvData[row][rejectionIndex]) {
|
|
592
|
+
tempArray.push(csvData[row]);
|
|
501
593
|
}
|
|
502
594
|
}
|
|
503
595
|
tempArray = tempArray.map((row) =>
|
|
@@ -527,15 +619,15 @@ export const SmsTraiCreate = (props) => {
|
|
|
527
619
|
const onDoneCallback = () => {
|
|
528
620
|
//start spinner
|
|
529
621
|
setIsFileDropped(true);
|
|
530
|
-
const
|
|
531
|
-
const rejectionIndex =
|
|
622
|
+
const csvData = files[0].fileData.data;
|
|
623
|
+
const rejectionIndex = csvData[0].indexOf(CAPILLARY_REJECTION_REASON);
|
|
532
624
|
const tempArray = [];
|
|
533
|
-
tempArray.push(
|
|
625
|
+
tempArray.push(csvData[0]);
|
|
534
626
|
|
|
535
627
|
// filter out non rejected rows
|
|
536
|
-
for (let row = 1; row <
|
|
537
|
-
if (!
|
|
538
|
-
tempArray.push(
|
|
628
|
+
for (let row = 1; row < csvData.length; row += 1) {
|
|
629
|
+
if (!csvData[row][rejectionIndex]) {
|
|
630
|
+
tempArray.push(csvData[row]);
|
|
539
631
|
}
|
|
540
632
|
}
|
|
541
633
|
// Filter out the columns index that needs to save
|
|
@@ -610,16 +702,26 @@ export const SmsTraiCreate = (props) => {
|
|
|
610
702
|
if (
|
|
611
703
|
previewFileIndex !== null &&
|
|
612
704
|
saveTemplate &&
|
|
613
|
-
files[0].
|
|
705
|
+
files[0].fileData.data.length - 1 - totalFailCount >= 1
|
|
614
706
|
) {
|
|
615
707
|
return false;
|
|
616
708
|
}
|
|
617
709
|
return true;
|
|
618
710
|
};
|
|
619
711
|
|
|
620
|
-
const
|
|
712
|
+
const deleteOrCancelCSV = () => {
|
|
713
|
+
setTraiFileData({});
|
|
714
|
+
setTraiFileMeta(null);
|
|
715
|
+
setValidatedData([]);
|
|
621
716
|
setFiles([]);
|
|
717
|
+
setTemplateRegistrationIndex(null);
|
|
718
|
+
setErrorText('');
|
|
719
|
+
setDropZone(true);
|
|
720
|
+
setPreviewFileIndex(null);
|
|
721
|
+
setIsFileDropped(false);
|
|
722
|
+
setTotalFailCount(0);
|
|
622
723
|
updateSaveTemplate(false);
|
|
724
|
+
setUploadValidated(false);
|
|
623
725
|
};
|
|
624
726
|
|
|
625
727
|
const InstructionComp = (inst) => (
|
|
@@ -631,8 +733,8 @@ export const SmsTraiCreate = (props) => {
|
|
|
631
733
|
</CapRow>
|
|
632
734
|
);
|
|
633
735
|
|
|
634
|
-
const
|
|
635
|
-
const {
|
|
736
|
+
const fileZero = files[previewFileIndex] || {};
|
|
737
|
+
const { fileData, fileMeta } = fileZero;
|
|
636
738
|
return (
|
|
637
739
|
<>
|
|
638
740
|
<CapRow span={24} className="dlt-csv-upload-container">
|
|
@@ -643,7 +745,7 @@ export const SmsTraiCreate = (props) => {
|
|
|
643
745
|
{previewFileIndex === null && dropZone && (
|
|
644
746
|
<>
|
|
645
747
|
<CapSpin spinning={isFileDropped}>
|
|
646
|
-
<Dropzone onDrop={onDrop} onFileDialogCancel={
|
|
748
|
+
<Dropzone onDrop={onDrop} onFileDialogCancel={deleteOrCancelCSV}>
|
|
647
749
|
{({ getRootProps, getInputProps }) => (
|
|
648
750
|
<div {...getRootProps()} className="file-drop-zone">
|
|
649
751
|
<input {...getInputProps()} />
|
|
@@ -685,7 +787,7 @@ export const SmsTraiCreate = (props) => {
|
|
|
685
787
|
<CapRow>
|
|
686
788
|
<CapColumn span={10}>
|
|
687
789
|
<CapLabel.CapLabelInline type="label9">
|
|
688
|
-
{
|
|
790
|
+
{fileMeta.name}
|
|
689
791
|
</CapLabel.CapLabelInline>
|
|
690
792
|
<CapLabel.CapLabelInline
|
|
691
793
|
type="label1"
|
|
@@ -693,7 +795,7 @@ export const SmsTraiCreate = (props) => {
|
|
|
693
795
|
>
|
|
694
796
|
(
|
|
695
797
|
{formatMessage(messages.numberOfRecords, {
|
|
696
|
-
value:
|
|
798
|
+
value: fileData.data.length - 1,
|
|
697
799
|
})}
|
|
698
800
|
)
|
|
699
801
|
</CapLabel.CapLabelInline>
|
|
@@ -729,8 +831,7 @@ export const SmsTraiCreate = (props) => {
|
|
|
729
831
|
className="validation-count-label"
|
|
730
832
|
>
|
|
731
833
|
{formatMessage(messages.passedVal, {
|
|
732
|
-
number:
|
|
733
|
-
fileResult.data.length - 1 - totalFailCount,
|
|
834
|
+
number: fileData.data.length - 1 - totalFailCount,
|
|
734
835
|
})}
|
|
735
836
|
</CapLabel>
|
|
736
837
|
<CapIcon
|
|
@@ -773,7 +874,7 @@ export const SmsTraiCreate = (props) => {
|
|
|
773
874
|
<CapIcon
|
|
774
875
|
type="delete"
|
|
775
876
|
size="s"
|
|
776
|
-
onClick={
|
|
877
|
+
onClick={deleteOrCancelCSV}
|
|
777
878
|
></CapIcon>
|
|
778
879
|
</CapColumn>
|
|
779
880
|
</CapRow>
|
|
@@ -852,7 +953,9 @@ SmsTraiCreate.propTypes = {
|
|
|
852
953
|
dispatch: PropTypes.func.isRequired,
|
|
853
954
|
};
|
|
854
955
|
|
|
855
|
-
const mapStateToProps = createStructuredSelector({
|
|
956
|
+
const mapStateToProps = createStructuredSelector({
|
|
957
|
+
traiSms: makeSelectSmsTrai(),
|
|
958
|
+
});
|
|
856
959
|
|
|
857
960
|
const mapDispatchToProps = (dispatch) => ({
|
|
858
961
|
actions: bindActionCreators(smsTraiActions, dispatch),
|