@plusscommunities/pluss-maintenance-app-forms 6.0.8-beta.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.
Files changed (90) hide show
  1. package/dist/module/actions/JobActions.js +20 -0
  2. package/dist/module/actions/JobActions.js.map +1 -0
  3. package/dist/module/actions/index.js +2 -0
  4. package/dist/module/actions/index.js.map +1 -0
  5. package/dist/module/actions/types.js +5 -0
  6. package/dist/module/actions/types.js.map +1 -0
  7. package/dist/module/apis/index.js +2 -0
  8. package/dist/module/apis/index.js.map +1 -0
  9. package/dist/module/apis/maintenanceActions.js +171 -0
  10. package/dist/module/apis/maintenanceActions.js.map +1 -0
  11. package/dist/module/components/FilterPopupMenu.js +271 -0
  12. package/dist/module/components/FilterPopupMenu.js.map +1 -0
  13. package/dist/module/components/MaintenanceList.js +360 -0
  14. package/dist/module/components/MaintenanceList.js.map +1 -0
  15. package/dist/module/components/MaintenanceListItem.js +322 -0
  16. package/dist/module/components/MaintenanceListItem.js.map +1 -0
  17. package/dist/module/components/MaintenanceWidgetItem.js +149 -0
  18. package/dist/module/components/MaintenanceWidgetItem.js.map +1 -0
  19. package/dist/module/components/StatusSelectorPopup.js +89 -0
  20. package/dist/module/components/StatusSelectorPopup.js.map +1 -0
  21. package/dist/module/components/WidgetLarge.js +9 -0
  22. package/dist/module/components/WidgetLarge.js.map +1 -0
  23. package/dist/module/components/WidgetSmall.js +171 -0
  24. package/dist/module/components/WidgetSmall.js.map +1 -0
  25. package/dist/module/core.config.js +17 -0
  26. package/dist/module/core.config.js.map +1 -0
  27. package/dist/module/feature.config.js +75 -0
  28. package/dist/module/feature.config.js.map +1 -0
  29. package/dist/module/helper.js +33 -0
  30. package/dist/module/helper.js.map +1 -0
  31. package/dist/module/images/speechbubble.png +0 -0
  32. package/dist/module/index.js +25 -0
  33. package/dist/module/index.js.map +1 -0
  34. package/dist/module/reducers/JobsReducer.js +63 -0
  35. package/dist/module/reducers/JobsReducer.js.map +1 -0
  36. package/dist/module/screens/JobTypePicker.js +130 -0
  37. package/dist/module/screens/JobTypePicker.js.map +1 -0
  38. package/dist/module/screens/MaintenancePage.js +92 -0
  39. package/dist/module/screens/MaintenancePage.js.map +1 -0
  40. package/dist/module/screens/RequestDetail.js +980 -0
  41. package/dist/module/screens/RequestDetail.js.map +1 -0
  42. package/dist/module/screens/RequestNotes.js +390 -0
  43. package/dist/module/screens/RequestNotes.js.map +1 -0
  44. package/dist/module/screens/ServiceRequest.js +1243 -0
  45. package/dist/module/screens/ServiceRequest.js.map +1 -0
  46. package/dist/module/values.config.a.js +30 -0
  47. package/dist/module/values.config.a.js.map +1 -0
  48. package/dist/module/values.config.b.js +30 -0
  49. package/dist/module/values.config.b.js.map +1 -0
  50. package/dist/module/values.config.c.js +30 -0
  51. package/dist/module/values.config.c.js.map +1 -0
  52. package/dist/module/values.config.d.js +30 -0
  53. package/dist/module/values.config.d.js.map +1 -0
  54. package/dist/module/values.config.default.js +35 -0
  55. package/dist/module/values.config.default.js.map +1 -0
  56. package/dist/module/values.config.forms.js +35 -0
  57. package/dist/module/values.config.forms.js.map +1 -0
  58. package/dist/module/values.config.js +35 -0
  59. package/dist/module/values.config.js.map +1 -0
  60. package/package.json +53 -0
  61. package/src/actions/JobActions.js +22 -0
  62. package/src/actions/index.js +1 -0
  63. package/src/actions/types.js +5 -0
  64. package/src/apis/index.js +1 -0
  65. package/src/apis/maintenanceActions.js +163 -0
  66. package/src/components/FilterPopupMenu.js +256 -0
  67. package/src/components/MaintenanceList.js +335 -0
  68. package/src/components/MaintenanceListItem.js +289 -0
  69. package/src/components/MaintenanceWidgetItem.js +132 -0
  70. package/src/components/StatusSelectorPopup.js +87 -0
  71. package/src/components/WidgetLarge.js +10 -0
  72. package/src/components/WidgetSmall.js +152 -0
  73. package/src/core.config.js +5 -0
  74. package/src/feature.config.js +73 -0
  75. package/src/helper.js +39 -0
  76. package/src/images/speechbubble.png +0 -0
  77. package/src/index.js +25 -0
  78. package/src/reducers/JobsReducer.js +51 -0
  79. package/src/screens/JobTypePicker.js +107 -0
  80. package/src/screens/MaintenancePage.js +96 -0
  81. package/src/screens/RequestDetail.js +915 -0
  82. package/src/screens/RequestNotes.js +418 -0
  83. package/src/screens/ServiceRequest.js +1219 -0
  84. package/src/values.config.a.js +30 -0
  85. package/src/values.config.b.js +30 -0
  86. package/src/values.config.c.js +30 -0
  87. package/src/values.config.d.js +30 -0
  88. package/src/values.config.default.js +35 -0
  89. package/src/values.config.forms.js +35 -0
  90. package/src/values.config.js +35 -0
@@ -0,0 +1,1219 @@
1
+ import React, { Component } from 'react';
2
+ import {
3
+ Dimensions,
4
+ Platform,
5
+ KeyboardAvoidingView,
6
+ ScrollView,
7
+ Text,
8
+ TouchableOpacity,
9
+ View,
10
+ Switch,
11
+ FlatList,
12
+ ImageBackground,
13
+ Keyboard,
14
+ } from 'react-native';
15
+ import DateTimePicker from 'react-native-modal-datetime-picker';
16
+ import { Icon } from 'react-native-elements';
17
+ import _ from 'lodash';
18
+ import moment from 'moment';
19
+ import { connect } from 'react-redux';
20
+ import { jobAdded } from '../actions';
21
+ import { maintenanceActions } from '../apis';
22
+ import { Services } from '../feature.config';
23
+ import { Components, Colours, Helper, Config } from '../core.config';
24
+ import { values } from '../values.config';
25
+
26
+ const PHOTO_SIZE = (Dimensions.get('window').width - 64) / 3;
27
+
28
+ class MaintenanceRequest extends Component {
29
+ constructor(props) {
30
+ super(props);
31
+ this.state = {
32
+ submitting: false,
33
+ success: false,
34
+ fail: false,
35
+ error: null,
36
+ showError: false,
37
+ loadingTypes: values.forceCustomFields,
38
+
39
+ userName: '',
40
+ roomNumber: '',
41
+ phone: '',
42
+ title: '',
43
+ description: '',
44
+ times: '',
45
+
46
+ type: 'General',
47
+
48
+ uploadingImage: false,
49
+ images: [
50
+ {
51
+ add: true,
52
+ },
53
+ ],
54
+ showFullscreenVideo: false,
55
+ currentVideoUrl: '',
56
+
57
+ isHome: false,
58
+
59
+ types: [],
60
+
61
+ confirmationToShow: false,
62
+
63
+ customFields: [],
64
+ customFieldImages: {},
65
+ isDateTimePickerVisible: false,
66
+ popUpType: 'date',
67
+ dateFieldId: null,
68
+ imageFieldId: null,
69
+ };
70
+ this.checkThumb = null;
71
+ this.keyboardTypes = {
72
+ phone: 'phone-pad',
73
+ email: 'email-address',
74
+ text: 'default',
75
+ };
76
+ }
77
+
78
+ componentDidMount() {
79
+ if (this.props.userType !== 'KIOSK') {
80
+ this.setState({
81
+ userName: this.props.displayName,
82
+ roomNumber: this.props.unit,
83
+ phone: !_.isEmpty(this.props.phoneNumber) ? this.props.phoneNumber : '',
84
+ });
85
+ }
86
+ this.getJobTypes();
87
+ }
88
+
89
+ componentWillUnmount() {
90
+ clearInterval(this.checkThumb);
91
+ }
92
+
93
+ onChangeAnswer = (fieldId, answer) => {
94
+ const update = { customFields: _.cloneDeep(this.state.customFields) };
95
+ const field = update.customFields[fieldId];
96
+ field.answer = answer;
97
+ if (field.isTitle) update.title = field.answer;
98
+ this.setState(update);
99
+ };
100
+
101
+ onChangeToggleAnswer = (fieldId, answer) => {
102
+ const update = { customFields: _.cloneDeep(this.state.customFields) };
103
+ const field = update.customFields[fieldId];
104
+ field.answer = field.answer === answer ? undefined : answer;
105
+ if (field.isTitle) update.title = field.answer;
106
+ this.setState(update);
107
+ };
108
+
109
+ onChangeCheckboxAnswer = (fieldId, answer) => {
110
+ const update = { customFields: _.cloneDeep(this.state.customFields) };
111
+ const field = update.customFields[fieldId];
112
+ field.answer = _.xor(field.answer || [], [answer]);
113
+ if (field.isTitle) update.title = field.answer.join(', ');
114
+ this.setState(update);
115
+ };
116
+
117
+ onOpenDatePicker = (field, fieldId) => {
118
+ Keyboard.dismiss();
119
+ this.setState({ dateFieldId: fieldId, popUpType: field.type, isDateTimePickerVisible: true });
120
+ };
121
+
122
+ onClearDate = fieldId => {
123
+ const update = { customFields: _.cloneDeep(this.state.customFields) };
124
+ const field = update.customFields[fieldId];
125
+ field.answer = undefined;
126
+ if (field.isTitle) update.title = field.answer;
127
+ this.setState(update);
128
+ };
129
+
130
+ onDateSelected = date => {
131
+ const { customFields, dateFieldId, popUpType } = this.state;
132
+ const update = { customFields: _.cloneDeep(customFields), isDateTimePickerVisible: false, fieldId: null };
133
+ const field = update.customFields[dateFieldId];
134
+ const dateObj = moment(date);
135
+ if (popUpType === 'date') {
136
+ field.answer = dateObj.format('YYYY-MM-DD');
137
+ if (field.isTitle) update.title = dateObj.format('DD MMM YYYY');
138
+ } else {
139
+ field.answer = dateObj.format('HH:mm');
140
+ if (field.isTitle) update.title = dateObj.format('h:mm a');
141
+ }
142
+ this.setState(update);
143
+ };
144
+
145
+ onUploadStarted = (uploadUri, imageUri) => {
146
+ const { imageFieldId } = this.state;
147
+ const imagesUpdate = this.getImages(imageFieldId);
148
+ imagesUpdate.splice(imagesUpdate.length - 1, 0, {
149
+ uploading: true,
150
+ uploadProgress: '0%',
151
+ uploadUri,
152
+ imageUri,
153
+ allowRetry: true,
154
+ });
155
+
156
+ this.setImages(imagesUpdate, imageFieldId);
157
+ };
158
+
159
+ onUploadProgress = progress => {
160
+ const { imageFieldId } = this.state;
161
+ const imagesUpdate = this.getImages(imageFieldId);
162
+ imagesUpdate.map(img => {
163
+ if (img.uploadUri === progress.uri) {
164
+ img.uploadProgress = progress.percentage;
165
+ img.uploading = true;
166
+ img.allowRetry = true;
167
+ }
168
+ });
169
+
170
+ this.setImages(imagesUpdate, imageFieldId);
171
+ };
172
+
173
+ onUploadSuccess = async (uri, uploadUri) => {
174
+ const { imageFieldId } = this.state;
175
+ const imagesUpdate = this.getImages(imageFieldId);
176
+ imagesUpdate.map(img => {
177
+ if (img.uploadUri === uploadUri && img.uploading) {
178
+ img.url = uri.replace('/general/', '/general1400/');
179
+ img.thumbNailExists = false;
180
+ img.thumbNailUrl = Helper.getThumb300(img.url);
181
+ img.allowRetry = true;
182
+ }
183
+ });
184
+
185
+ this.setImages(imagesUpdate, imageFieldId, () => this.waitForThumbnails());
186
+ };
187
+
188
+ onUploadFailed = uploadUri => {
189
+ const { imageFieldId } = this.state;
190
+ const imagesUpdate = this.getImages(imageFieldId);
191
+ imagesUpdate.map(img => {
192
+ if (img.uploadUri === uploadUri) {
193
+ img.uploading = true; // Requried for retry
194
+ img.uploadProgress = '';
195
+ img.allowRetry = true;
196
+ }
197
+ });
198
+
199
+ this.setImages(imagesUpdate, imageFieldId);
200
+ };
201
+
202
+ onLibrarySelected = uri => {
203
+ const { imageFieldId } = this.state;
204
+ const imagesUpdate = this.getImages(imageFieldId);
205
+ imagesUpdate.splice(imagesUpdate.length - 1, 0, {
206
+ uploading: false,
207
+ allowRetry: false,
208
+ url: Helper.get1400(uri),
209
+ thumbNailExists: true,
210
+ thumbNailUrl: Helper.getThumb300(uri),
211
+ });
212
+
213
+ this.setImages(imagesUpdate, imageFieldId);
214
+ };
215
+
216
+ onPressBack() {
217
+ Services.navigation.goBack();
218
+ }
219
+
220
+ onPressType() {
221
+ Services.navigation.navigate(values.screenJobTypePicker, {
222
+ currentType: this.state.type,
223
+ types: this.state.types,
224
+ onSelectType: this.pickType.bind(this),
225
+ });
226
+ }
227
+
228
+ onCloseConfirmationPopup() {
229
+ this.setState({
230
+ confirmationToShow: false,
231
+ });
232
+ Services.navigation.goBack();
233
+ this.props.onBack && this.props.onBack();
234
+ }
235
+
236
+ onConfirmationReset() {
237
+ this.setState(
238
+ {
239
+ confirmationToShow: false,
240
+ title: '',
241
+ description: '',
242
+ times: '',
243
+ isHome: false,
244
+ uploadingImage: false,
245
+ images: [
246
+ {
247
+ add: true,
248
+ },
249
+ ],
250
+ submitting: false,
251
+ success: false,
252
+ fail: false,
253
+ customFields: [],
254
+ customFieldImages: {},
255
+ isDateTimePickerVisible: false,
256
+ popUpType: 'date',
257
+ dateFieldId: null,
258
+ imageFieldId: null,
259
+ },
260
+ () => this.pickType(this.state.type),
261
+ );
262
+ }
263
+
264
+ isFieldValid = (field, fieldId) => {
265
+ const { mandatory, type, answer } = field;
266
+ if (['staticTitle', 'staticText'].includes(type)) return true;
267
+
268
+ const imagesList = type === 'image' ? this.getImageUrls(fieldId) : [];
269
+ const checkMandatory = () => {
270
+ if (!mandatory) return true;
271
+ switch (type) {
272
+ case 'yn':
273
+ return _.isBoolean(answer);
274
+ case 'image':
275
+ return imagesList.length > 0;
276
+ case 'checkbox':
277
+ return _.isArray(answer) && answer.length > 0;
278
+ default:
279
+ return !_.isNil(answer) && !_.isEmpty(answer);
280
+ }
281
+ };
282
+ const checkFormat = () => {
283
+ if (_.isNil(answer) || _.isEmpty(answer)) return true;
284
+ switch (type) {
285
+ case 'email':
286
+ return Helper.isEmail(answer);
287
+ case 'date':
288
+ return moment(answer, 'YYYY-MM-DD', true).isValid();
289
+ case 'time':
290
+ return moment(answer, 'HH:mm', true).isValid();
291
+ default:
292
+ return true;
293
+ }
294
+ };
295
+
296
+ const valid = checkMandatory() && checkFormat();
297
+ return valid;
298
+ };
299
+
300
+ getJobTypes() {
301
+ maintenanceActions
302
+ .getJobTypes(Helper.getSite(this.props.site))
303
+ .then(res => {
304
+ this.setState({
305
+ types: res.data,
306
+ });
307
+ console.log(res.data);
308
+ this.getDefaultJob();
309
+ })
310
+ .catch(() => {});
311
+ }
312
+
313
+ pickType(type) {
314
+ const { types } = this.state;
315
+ const selected = types.find(t => t.typeName === type) || {};
316
+ if (values.forceCustomFields && !selected.hasCustomFields) {
317
+ console.log(selected);
318
+ this.setState({
319
+ type,
320
+ customFields: [],
321
+ noType: true,
322
+ });
323
+ return;
324
+ }
325
+ this.setState({
326
+ type,
327
+ customFields: selected.hasCustomFields && selected.customFields.length > 0 ? _.cloneDeep(selected.customFields) : [],
328
+ loadingTypes: false,
329
+ });
330
+ }
331
+
332
+ getDefaultJob() {
333
+ const { types, jobId } = this.state;
334
+ if (types.length !== 0 && jobId == null) {
335
+ const defaultType = types[0];
336
+ this.pickType(defaultType.typeName);
337
+ }
338
+ }
339
+
340
+ showUploadMenu = fieldId => {
341
+ Keyboard.dismiss();
342
+ if (this.state.uploadingImage || this.state.submitting) {
343
+ return;
344
+ }
345
+ if (fieldId) this.setState({ imageFieldId: fieldId });
346
+ this.imageUploader.showUploadMenu();
347
+ };
348
+
349
+ submit = async () => {
350
+ this.setState({ submitting: true });
351
+
352
+ let description = this.state.description;
353
+
354
+ if (this.state.isHome) {
355
+ description = description + `. -- Times Available: ${this.state.times}`;
356
+ }
357
+
358
+ setTimeout(() => {
359
+ this.scrollContainer.scrollTo({ y: 0 });
360
+ }, 100);
361
+
362
+ const images = this.getImageUrls();
363
+
364
+ // Fix custom images field answers
365
+ const customFields = _.cloneDeep(this.state.customFields);
366
+ const updatedCustomFields = customFields.map((field, fieldId) => {
367
+ if (field.type === 'image') field.answer = this.getImageUrls(fieldId);
368
+ return field;
369
+ });
370
+
371
+ maintenanceActions
372
+ .sendMaintenanceRequest(
373
+ this.props.uid,
374
+ this.state.userName,
375
+ this.state.phone,
376
+ this.state.roomNumber,
377
+ this.state.title,
378
+ description,
379
+ null,
380
+ this.state.type,
381
+ images,
382
+ Helper.getSite(this.props.site),
383
+ this.state.isHome,
384
+ this.state.times,
385
+ updatedCustomFields,
386
+ )
387
+ .then(res => {
388
+ if (res.data.success) {
389
+ this.refreshRequest(res.data.searchResult);
390
+ if (this.props.onSubmissionSuccess) this.props.onSubmissionSuccess(res.data);
391
+ this.setState({
392
+ submitting: false,
393
+ success: true,
394
+ confirmationToShow: true,
395
+ });
396
+ } else {
397
+ this.setState({
398
+ submitting: false,
399
+ });
400
+ }
401
+ })
402
+ .catch(err => {
403
+ console.log('maintenance submission fail.');
404
+ console.log(err);
405
+ this.setState({
406
+ submitting: false,
407
+ });
408
+ });
409
+ };
410
+
411
+ refreshRequest = async id => {
412
+ try {
413
+ const job = await maintenanceActions.getJob(Helper.getSite(this.props.site), id);
414
+ this.props.jobAdded(job.data);
415
+ } catch (error) {
416
+ console.log('refreshRequest error', error);
417
+ }
418
+ };
419
+
420
+ validateCustomFields = () => {
421
+ const { customFields } = this.state;
422
+ if (!customFields || customFields.length === 0) return true;
423
+
424
+ return customFields.every((field, index) => {
425
+ const isValid = this.isFieldValid(field, index);
426
+ return isValid;
427
+ });
428
+ };
429
+
430
+ submitRequest() {
431
+ const { customFields, submitting, uploadingImage, title, roomNumber, isHome, times } = this.state;
432
+ const hasCustomFields = customFields && customFields.length > 0;
433
+
434
+ if (submitting || !this.props.connected) {
435
+ if (!this.props.connected) {
436
+ this.setState({ error: { message: 'No internet connection detected' } });
437
+ }
438
+ return;
439
+ }
440
+ if (uploadingImage) return;
441
+
442
+ this.setState({ error: null, showError: false });
443
+ if (title.length === 0 || !roomNumber || roomNumber.length === 0) {
444
+ console.log('submitRequest - error', { title, roomNumber });
445
+ this.setState({ showError: true });
446
+ return;
447
+ }
448
+ if (hasCustomFields) {
449
+ if (!this.validateCustomFields()) {
450
+ console.log('submitRequest - custom fields error');
451
+ this.setState({ showError: true });
452
+ return;
453
+ }
454
+ } else {
455
+ if (isHome && times.length < 2) {
456
+ console.log('submitRequest - error', { isHome, times });
457
+ this.setState({ showError: true });
458
+ return;
459
+ }
460
+ }
461
+ this.submit();
462
+ }
463
+
464
+ getImages = (fieldId = null) => {
465
+ const { images, customFieldImages } = this.state;
466
+ const imagesList = _.cloneDeep(fieldId ? customFieldImages[fieldId] : images);
467
+ if (!imagesList || !Array.isArray(imagesList) || imagesList.length === 0) {
468
+ return [{ add: true }];
469
+ }
470
+ return imagesList;
471
+ };
472
+
473
+ setImages = (imagesList, fieldId = null, callback = null) => {
474
+ let update = {};
475
+ if (fieldId) {
476
+ const customFieldImages = _.cloneDeep(this.state.customFieldImages);
477
+ customFieldImages[fieldId] = imagesList;
478
+ update = { customFieldImages };
479
+ } else {
480
+ update = { images: imagesList };
481
+ }
482
+ this.setState(update, callback);
483
+ };
484
+
485
+ getImageUrls = (fieldId = null) => {
486
+ const imagesList = this.getImages(fieldId);
487
+ return _.filter(imagesList, img => {
488
+ return !img.uploading && !img.add;
489
+ }).map(img => {
490
+ return img.url;
491
+ });
492
+ };
493
+
494
+ waitForThumbnails = () => {
495
+ if (this.checkThumb) return;
496
+
497
+ this.checkThumb = setInterval(async () => {
498
+ const { imageFieldId } = this.state;
499
+ const imagesList = this.getImages(imageFieldId);
500
+ const imagesUpdate = [];
501
+ await Promise.all(
502
+ imagesList.map(image => {
503
+ return new Promise(async resolve => {
504
+ const newImage = { ...image };
505
+ imagesUpdate.push(newImage);
506
+ if (newImage.url && !newImage.thumbNailExists) {
507
+ newImage.uploading = false;
508
+ newImage.allowRetry = false;
509
+ newImage.thumbNailExists = await Helper.imageExists(newImage.thumbNailUrl);
510
+ resolve(newImage.thumbNailExists);
511
+ }
512
+ resolve(true);
513
+ });
514
+ }),
515
+ );
516
+ const thumbnailsExist = imagesUpdate.every(image => !image.url || image.thumbNailExists);
517
+ if (thumbnailsExist) {
518
+ clearInterval(this.checkThumb);
519
+ this.checkThumb = null;
520
+ this.setImages(imagesUpdate, imageFieldId);
521
+ }
522
+ }, 2000);
523
+ };
524
+
525
+ removeImage = (index, fieldId) => {
526
+ const imagesUpdate = this.getImages(fieldId);
527
+ imagesUpdate.splice(index, 1);
528
+
529
+ this.setImages(imagesUpdate, fieldId);
530
+ };
531
+
532
+ toggleFullscreenVideo = url => {
533
+ if (typeof url !== 'string') url = '';
534
+ this.setState({ showFullscreenVideo: url.length > 0, currentVideoUrl: url });
535
+ };
536
+
537
+ renderUploadMenu() {
538
+ return (
539
+ <Components.ImageUploader
540
+ ref={ref => (this.imageUploader = ref)}
541
+ onUploadStarted={this.onUploadStarted}
542
+ onUploadSuccess={this.onUploadSuccess}
543
+ onUploadFailed={this.onUploadFailed}
544
+ onUploadProgress={this.onUploadProgress}
545
+ onLibrarySelected={this.onLibrarySelected}
546
+ size={{ width: 1400 }}
547
+ quality={0.8}
548
+ fileName={'serviceImage'}
549
+ popupTitle={'Upload Image'}
550
+ userId={this.props.uid}
551
+ allowsEditing={false}
552
+ multiple
553
+ hideLibrary
554
+ />
555
+ );
556
+ }
557
+
558
+ renderImage(item, index, fieldId = null) {
559
+ const isVideoUrl = Helper.isVideo(item.url);
560
+ const imagesList = this.getImages(fieldId);
561
+
562
+ if (item.add) {
563
+ return (
564
+ <TouchableOpacity activeOpacity={0.8} onPress={() => this.showUploadMenu(fieldId)}>
565
+ <View
566
+ style={[
567
+ styles.imageContainer,
568
+ imagesList.length > 1 && styles.imageContainerNotEmpty,
569
+ index % 3 === 0 && { marginLeft: 0 },
570
+ index > 2 && { marginTop: 8 },
571
+ ]}
572
+ >
573
+ <View style={styles.imageCircle}>
574
+ <Icon name="camera" type="font-awesome" iconStyle={styles.addImageIcon} />
575
+ </View>
576
+ </View>
577
+ </TouchableOpacity>
578
+ );
579
+ }
580
+ return (
581
+ <View
582
+ style={[
583
+ styles.imageContainer,
584
+ imagesList.length > 1 && styles.imageContainerNotEmpty,
585
+ index % 3 === 0 && { marginLeft: 0 },
586
+ index > 2 && { marginTop: 8 },
587
+ ]}
588
+ >
589
+ {item.uploading ? (
590
+ <Components.ImageUploadProgress uploader={this.imageUploader} image={item} color={this.props.colourBrandingMain} />
591
+ ) : (
592
+ <ImageBackground
593
+ style={styles.imageBackground}
594
+ source={Helper.getImageSource(item.thumbNailExists ? item.thumbNailUrl : item.url)}
595
+ >
596
+ {isVideoUrl && (
597
+ <View style={styles.imagePlayContainer}>
598
+ <TouchableOpacity onPress={this.toggleFullscreenVideo.bind(this, item.url)}>
599
+ <Icon name="play" type="font-awesome" iconStyle={styles.imageControlIcon} />
600
+ </TouchableOpacity>
601
+ </View>
602
+ )}
603
+ <TouchableOpacity style={styles.removeImage} onPress={() => this.removeImage(index, fieldId)}>
604
+ <Icon name="remove" type="font-awesome" iconStyle={styles.imageControlIcon} style={styles.removeImage} />
605
+ </TouchableOpacity>
606
+ </ImageBackground>
607
+ )}
608
+ </View>
609
+ );
610
+ }
611
+
612
+ renderSuccess() {
613
+ return (
614
+ <View style={{ padding: 16, flex: 1, backgroundColor: '#fff' }}>
615
+ <Text style={styles.requestSuccess}>Your request has been submitted. Thank you.</Text>
616
+ </View>
617
+ );
618
+ }
619
+
620
+ renderImageList(fieldId = null) {
621
+ const imagesList = this.getImages(fieldId);
622
+ return (
623
+ <View style={styles.imageSection}>
624
+ <View style={[styles.imageListContainer, imagesList.length < 2 && styles.imageListContainerEmpty]}>
625
+ <FlatList
626
+ keyboardShouldPersistTaps="always"
627
+ enableEmptySections
628
+ data={imagesList}
629
+ renderItem={({ item, index }) => this.renderImage(item, index, fieldId)}
630
+ keyExtractor={(item, index) => index}
631
+ numColumns={3}
632
+ />
633
+ </View>
634
+ </View>
635
+ );
636
+ }
637
+
638
+ renderDateField(field, fieldId, sectionStyle) {
639
+ let displayText, placeHolder, icon, errorText;
640
+ if (field.type === 'date') {
641
+ displayText = field.answer ? moment(field.answer, 'YYYY-MM-DD').format('DD MMM YYYY') : '';
642
+ placeHolder = 'dd mmm yyyy';
643
+ icon = 'calendar';
644
+ errorText = 'Not a valid date';
645
+ } else {
646
+ displayText = field.answer ? moment(field.answer, 'HH:mm').format('h:mm a') : '';
647
+ placeHolder = '--:-- --';
648
+ icon = 'clock-o';
649
+ errorText = 'Not a valid time';
650
+ }
651
+
652
+ return (
653
+ <Components.GenericInputSection
654
+ key={fieldId}
655
+ label={field.label}
656
+ sectionStyle={sectionStyle}
657
+ isValid={() => this.isFieldValid(field, fieldId)}
658
+ required={field.mandatory}
659
+ showError={this.state.showError}
660
+ errorText={errorText}
661
+ >
662
+ <View style={styles.dateContainer}>
663
+ <TouchableOpacity style={styles.dateFieldButton} onPress={() => this.onOpenDatePicker(field, fieldId)}>
664
+ <View style={styles.dateFieldContainer}>
665
+ <Text style={styles.dateText}>{displayText || placeHolder}</Text>
666
+ <Icon type="font-awesome" name={icon} iconStyle={styles.dateIcon} />
667
+ </View>
668
+ </TouchableOpacity>
669
+ {displayText ? (
670
+ <TouchableOpacity style={styles.dateClearButton} onPress={() => this.onClearDate(fieldId)}>
671
+ <Icon type="font-awesome" name="times" iconStyle={styles.removeIcon} />
672
+ </TouchableOpacity>
673
+ ) : null}
674
+ </View>
675
+ </Components.GenericInputSection>
676
+ );
677
+ }
678
+
679
+ renderField(field, fieldId) {
680
+ const sectionStyle = { marginTop: fieldId === 0 ? 24 : 0, marginBottom: 24 };
681
+ switch (field.type) {
682
+ case 'yn':
683
+ return (
684
+ <Components.GenericInputSection
685
+ key={fieldId}
686
+ label={field.label}
687
+ sectionStyle={sectionStyle}
688
+ inputType="toggle"
689
+ value={field.answer}
690
+ onChange={answer => this.onChangeToggleAnswer(fieldId, answer)}
691
+ isValid={() => this.isFieldValid(field, fieldId)}
692
+ showError={this.state.showError}
693
+ required={field.mandatory}
694
+ />
695
+ );
696
+ case 'multichoice':
697
+ return (
698
+ <Components.GenericInputSection
699
+ key={fieldId}
700
+ label={field.label}
701
+ sectionStyle={sectionStyle}
702
+ inputType="radio"
703
+ value={field.answer}
704
+ onChange={answer => this.onChangeToggleAnswer(fieldId, answer)}
705
+ options={field.values.map(o => {
706
+ return {
707
+ Label: o,
708
+ Value: o,
709
+ };
710
+ })}
711
+ isValid={() => this.isFieldValid(field, fieldId)}
712
+ showError={this.state.showError}
713
+ required={field.mandatory}
714
+ />
715
+ );
716
+ case 'checkbox':
717
+ return (
718
+ <Components.GenericInputSection
719
+ key={fieldId}
720
+ label={field.label}
721
+ sectionStyle={sectionStyle}
722
+ isValid={() => this.isFieldValid(field, fieldId)}
723
+ showError={this.state.showError}
724
+ required={field.mandatory}
725
+ >
726
+ {field.values.map((o, i) => {
727
+ const isActive = field.answer && _.includes(field.answer, o);
728
+ return (
729
+ <TouchableOpacity
730
+ onPress={() => this.onChangeCheckboxAnswer(fieldId, o)}
731
+ key={i}
732
+ style={styles.multiChoiceOption}
733
+ hitSlop={{ top: 8, left: 8, bottom: 8, right: 8 }}
734
+ >
735
+ {isActive ? (
736
+ <Components.TickIcon style={styles.tick} size={20} color={this.props.colourBrandingMain} />
737
+ ) : (
738
+ <View style={styles.unticked} />
739
+ )}
740
+ <Text style={styles.multiChoiceText}>{o}</Text>
741
+ </TouchableOpacity>
742
+ );
743
+ })}
744
+ </Components.GenericInputSection>
745
+ );
746
+ case 'text':
747
+ case 'email':
748
+ case 'phone':
749
+ return (
750
+ <Components.GenericInputSection
751
+ key={fieldId}
752
+ label={field.label}
753
+ placeholder={field.placeHolder}
754
+ value={field.answer}
755
+ onChangeText={val => this.onChangeAnswer(fieldId, val)}
756
+ editable
757
+ squaredCorners
758
+ isValid={() => this.isFieldValid(field, fieldId)}
759
+ showError={this.state.showError}
760
+ errorText={field.type === 'email' ? 'Not a valid email' : undefined}
761
+ required={field.mandatory}
762
+ sectionStyle={sectionStyle}
763
+ autoCapitalize="sentences"
764
+ keyboardType={this.keyboardTypes[field.type]}
765
+ />
766
+ );
767
+ case 'staticTitle':
768
+ return (
769
+ <Text key={fieldId} style={[styles.staticTitle, { color: this.props.colourBrandingMain }, sectionStyle]}>
770
+ {field.label}
771
+ </Text>
772
+ );
773
+ case 'staticText':
774
+ return (
775
+ <View key={fieldId} style={[styles.staticText, sectionStyle]}>
776
+ {Helper.toParagraphed(field.label, styles.staticText)}
777
+ </View>
778
+ );
779
+ case 'date':
780
+ case 'time':
781
+ return this.renderDateField(field, fieldId, sectionStyle);
782
+ case 'image':
783
+ return (
784
+ <Components.GenericInputSection
785
+ key={fieldId}
786
+ label={field.label}
787
+ sectionStyle={sectionStyle}
788
+ isValid={() => this.isFieldValid(field, fieldId)}
789
+ required={field.mandatory}
790
+ showError={this.state.showError}
791
+ >
792
+ {this.renderImageList(fieldId)}
793
+ </Components.GenericInputSection>
794
+ );
795
+ default:
796
+ return null;
797
+ }
798
+ }
799
+
800
+ renderCustomFields() {
801
+ const { customFields } = this.state;
802
+ if (!customFields || customFields.length === 0) return null;
803
+
804
+ return (
805
+ <Components.FormCard style={{ marginTop: 16 }}>{customFields.map((field, i) => this.renderField(field, i))}</Components.FormCard>
806
+ );
807
+ }
808
+
809
+ renderForm() {
810
+ const { customFields, loadingTypes } = this.state;
811
+ const hasCustomFields = customFields && customFields.length > 0;
812
+
813
+ if (loadingTypes) {
814
+ return <Components.Spinner />;
815
+ }
816
+
817
+ return (
818
+ <View style={{ flex: 1 }}>
819
+ <ScrollView keyboardShouldPersistTaps="always" style={{ flex: 1 }} ref={ref => (this.scrollContainer = ref)}>
820
+ <View style={{ paddingBottom: 2 }}>
821
+ <Components.LoadingIndicator visible={this.state.submitting} />
822
+ {this.state.error && <Text style={styles.errorText}>{this.state.error}</Text>}
823
+ <Components.FormCard style={{ marginTop: 16 }}>
824
+ <Components.FormCardSection
825
+ label={'Name'}
826
+ placeholder={'Enter your name'}
827
+ textValue={this.state.userName}
828
+ onChangeText={userName => this.setState({ userName })}
829
+ editable={this.props.userType === 'KIOSK' && this.state.submitting === false}
830
+ isValid={() => {
831
+ return this.state.userName.length > 1;
832
+ }}
833
+ required
834
+ errorText="Please enter your name."
835
+ showError={this.state.showError && this.state.userName.length < 2}
836
+ hasUnderline
837
+ />
838
+ <Components.FormCardSection
839
+ label={'Contact number'}
840
+ placeholder={'Enter phone number'}
841
+ textValue={this.state.phone}
842
+ onChangeText={phone => this.setState({ phone })}
843
+ editable={this.state.submitting === false}
844
+ hasUnderline
845
+ keyboardType={'phone-pad'}
846
+ />
847
+ <Components.FormCardSection
848
+ label={'Address'}
849
+ placeholder={'Enter your address'}
850
+ textValue={this.state.roomNumber}
851
+ onChangeText={roomNumber => this.setState({ roomNumber })}
852
+ editable={this.state.submitting === false}
853
+ hasUnderline
854
+ isValid={() => {
855
+ return this.state.roomNumber && this.state.roomNumber.length > 1;
856
+ }}
857
+ required
858
+ errorText="Please provide your address."
859
+ showError={this.state.showError && (!this.state.roomNumber || this.state.roomNumber.length < 2)}
860
+ />
861
+ </Components.FormCard>
862
+ <Components.FormCard
863
+ style={{ marginTop: 16, paddingHorizontal: 24, paddingVertical: 16, flexDirection: 'row', justifyContent: 'space-between' }}
864
+ >
865
+ <Text style={styles.sectionTitle}>{Config.env.strings.JOB_TYPE}</Text>
866
+ <TouchableOpacity onPress={this.onPressType.bind(this)}>
867
+ <View style={{ flexDirection: 'row' }}>
868
+ <Text style={[styles.sectionTitle, { color: this.props.colourBrandingMain, marginRight: 16 }]}>{this.state.type}</Text>
869
+ <Icon
870
+ name="angle-right"
871
+ type="font-awesome"
872
+ iconStyle={[styles.sectionTitle, { fontSize: 20, color: this.props.colourBrandingMain }]}
873
+ />
874
+ </View>
875
+ </TouchableOpacity>
876
+ </Components.FormCard>
877
+ {hasCustomFields ? (
878
+ this.renderCustomFields()
879
+ ) : (
880
+ <>
881
+ <Components.FormCard style={{ marginTop: 16 }}>
882
+ <Components.FormCardSection
883
+ label={'Title'}
884
+ placeholder={'Enter a title for your request'}
885
+ textValue={this.state.title}
886
+ onChangeText={title => this.setState({ title })}
887
+ editable={this.state.submitting === false}
888
+ hasUnderline
889
+ isValid={() => {
890
+ return this.state.title.length > 1;
891
+ }}
892
+ required
893
+ errorText="Please provide a title."
894
+ showError={this.state.showError && this.state.title.length < 2}
895
+ autoCorrect
896
+ multiline
897
+ autoGrow
898
+ />
899
+ <Components.FormCardSection
900
+ label={'Description'}
901
+ placeholder={'Describe your request here in detail'}
902
+ textValue={this.state.description}
903
+ onChangeText={description => this.setState({ description })}
904
+ editable={this.state.submitting === false}
905
+ hasUnderline
906
+ autoCorrect
907
+ multiline
908
+ autoGrow
909
+ />
910
+ </Components.FormCard>
911
+ <Components.FormCard style={{ marginTop: 16, paddingHorizontal: 24 }}>
912
+ <View
913
+ style={[
914
+ {
915
+ width: '100%',
916
+ paddingVertical: 16,
917
+ flexDirection: 'row',
918
+ justifyContent: 'space-between',
919
+ alignItems: 'center',
920
+ position: 'relative',
921
+ },
922
+ this.state.isHome && { borderBottomWidth: 1, borderBottomColor: Colours.LINEGREY },
923
+ ]}
924
+ >
925
+ <Text style={styles.sectionTitle}>{Config.env.strings.MAINTENANCE_HOME}</Text>
926
+ <Switch
927
+ value={this.state.isHome}
928
+ disabled={this.state.submitting}
929
+ onValueChange={value => this.setState({ isHome: value })}
930
+ trackColor={{ false: '#ddd', true: this.props.colourBrandingMain }}
931
+ thumbColor={Platform.OS === 'android' ? '#fff' : null}
932
+ />
933
+ </View>
934
+ {this.state.isHome && (
935
+ <Components.FormCardSection
936
+ label={'Available times'}
937
+ placeholder={'Describe your available times here in detail.'}
938
+ textValue={this.state.times}
939
+ onChangeText={times => this.setState({ times })}
940
+ editable={this.state.submitting === false}
941
+ hasUnderline
942
+ isValid={() => {
943
+ return this.state.times.length > 1;
944
+ }}
945
+ required
946
+ errorText="Please provide available times."
947
+ showError={this.state.showError && this.state.isHome && this.state.times.length < 2}
948
+ minHeight={40}
949
+ autoCorrect
950
+ multiline
951
+ autoGrow
952
+ />
953
+ )}
954
+ </Components.FormCard>
955
+ {this.renderImageList()}
956
+ </>
957
+ )}
958
+ </View>
959
+ </ScrollView>
960
+ </View>
961
+ );
962
+ }
963
+
964
+ renderRegisterConfirmation() {
965
+ return (
966
+ <Components.ConfirmationPopup
967
+ confirmText={`${values.textEntityName} submitted`}
968
+ repeatText={'Submit another'}
969
+ visible={this.state.confirmationToShow}
970
+ onClose={this.onCloseConfirmationPopup.bind(this)}
971
+ onPressAction={this.onConfirmationReset.bind(this)}
972
+ />
973
+ );
974
+ }
975
+
976
+ renderVideoPlayerPopup() {
977
+ const { showFullscreenVideo, currentVideoUrl } = this.state;
978
+ if (!currentVideoUrl) return;
979
+
980
+ return (
981
+ <Components.VideoPopup
982
+ uri={currentVideoUrl}
983
+ visible={showFullscreenVideo}
984
+ showFullscreenButton={false}
985
+ onClose={this.toggleFullscreenVideo}
986
+ />
987
+ );
988
+ }
989
+
990
+ renderNoType() {
991
+ if (!this.state.noType) {
992
+ return null;
993
+ }
994
+ return (
995
+ <Components.WarningPopup
996
+ confirmText={'No forms are available'}
997
+ infoText={'Check back later for forms.'}
998
+ visible={this.state.noType}
999
+ onClose={this.onPressBack.bind(this)}
1000
+ padHorizontal
1001
+ />
1002
+ );
1003
+ }
1004
+
1005
+ render() {
1006
+ const { submitting, success, isDateTimePickerVisible, popUpType } = this.state;
1007
+
1008
+ return (
1009
+ <KeyboardAvoidingView behavior={Platform.OS === 'ios' && 'padding'} style={styles.viewContainer}>
1010
+ {this.renderUploadMenu()}
1011
+ <View style={styles.container}>
1012
+ <Components.Header
1013
+ leftIcon="angle-left"
1014
+ onPressLeft={this.onPressBack.bind(this)}
1015
+ text={this.props.strings[`${values.featureKey}_textFeatureTitle`] || values.textFeatureTitle}
1016
+ rightText={submitting || success ? null : 'Done'}
1017
+ onPressRight={this.submitRequest.bind(this)}
1018
+ absoluteRight
1019
+ />
1020
+ {this.renderForm()}
1021
+ </View>
1022
+ {this.renderRegisterConfirmation()}
1023
+ {this.renderVideoPlayerPopup()}
1024
+ {this.renderNoType()}
1025
+ <DateTimePicker
1026
+ isVisible={isDateTimePickerVisible}
1027
+ onConfirm={this.onDateSelected}
1028
+ onCancel={() => this.setState({ isDateTimePickerVisible: false })}
1029
+ mode={popUpType}
1030
+ headerTextIOS={`Pick a ${popUpType}`}
1031
+ />
1032
+ </KeyboardAvoidingView>
1033
+ );
1034
+ }
1035
+ }
1036
+
1037
+ const styles = {
1038
+ viewContainer: {
1039
+ flex: 1,
1040
+ backgroundColor: '#fff',
1041
+ },
1042
+ container: {
1043
+ flex: 1,
1044
+ position: 'relative',
1045
+ backgroundColor: '#f0f0f5',
1046
+ },
1047
+ errorText: {
1048
+ fontFamily: 'sf-regular',
1049
+ color: Colours.COLOUR_TANGERINE,
1050
+ fontSize: 16,
1051
+ },
1052
+ requestSuccess: {
1053
+ fontFamily: 'sf-regular',
1054
+ color: Colours.TEXT_DARK,
1055
+ fontSize: 17,
1056
+ textAlign: 'center',
1057
+ },
1058
+ sectionTitle: {
1059
+ fontFamily: 'sf-regular',
1060
+ fontSize: 17,
1061
+ color: Colours.TEXT_DARK,
1062
+ },
1063
+ imageListContainer: {
1064
+ marginTop: 8,
1065
+ padding: 8,
1066
+ flexDirection: 'row',
1067
+ backgroundColor: Colours.BOXGREY,
1068
+ minHeight: 106,
1069
+ },
1070
+ imageListContainerEmpty: {
1071
+ justifyContent: 'center',
1072
+ alignItems: 'center',
1073
+ flexDirection: 'column',
1074
+ },
1075
+ imageContainer: {
1076
+ width: PHOTO_SIZE,
1077
+ height: PHOTO_SIZE,
1078
+ borderStyle: 'dashed',
1079
+ justifyContent: 'center',
1080
+ alignItems: 'center',
1081
+ borderWidth: 1,
1082
+ borderColor: Colours.LINEGREY,
1083
+ borderRadius: 4,
1084
+ marginLeft: 8,
1085
+ },
1086
+ imageContainerNotEmpty: {
1087
+ borderWidth: 1,
1088
+ borderColor: Colours.LINEGREY,
1089
+ backgroundColor: '#fff',
1090
+ borderStyle: 'dashed',
1091
+ },
1092
+ imageBackground: {
1093
+ flex: 1,
1094
+ height: '100%',
1095
+ width: '100%',
1096
+ borderRadius: 4,
1097
+ },
1098
+ imageCircle: {
1099
+ width: 90,
1100
+ height: 90,
1101
+ borderRadius: 45,
1102
+ backgroundColor: Colours.PINKISH_GREY,
1103
+ justifyContent: 'center',
1104
+ },
1105
+ addImageIcon: {
1106
+ color: '#fff',
1107
+ fontSize: 32,
1108
+ },
1109
+ imagePlayContainer: {
1110
+ position: 'absolute',
1111
+ top: 0,
1112
+ left: 0,
1113
+ right: 0,
1114
+ bottom: 0,
1115
+ alignItems: 'center',
1116
+ justifyContent: 'center',
1117
+ },
1118
+ imageControlIcon: {
1119
+ color: '#fff',
1120
+ fontSize: 20,
1121
+ textShadowColor: 'rgba(0,0,0,0.3)',
1122
+ textShadowOffset: { width: 2, height: 2 },
1123
+ },
1124
+ removeImage: {
1125
+ position: 'absolute',
1126
+ top: 0,
1127
+ right: 0,
1128
+ padding: 4,
1129
+ width: 40,
1130
+ height: 40,
1131
+ alignItems: 'center',
1132
+ justifyContent: 'center',
1133
+ },
1134
+ staticTitle: {
1135
+ fontSize: 20,
1136
+ fontFamily: 'sf-semibold',
1137
+ color: Colours.TEXT_DARKEST,
1138
+ },
1139
+ staticText: {
1140
+ fontSize: 17,
1141
+ fontFamily: 'sf-regular',
1142
+ color: Colours.TEXT_DARKEST,
1143
+ lineHeight: 24,
1144
+ },
1145
+ multiChoiceOption: {
1146
+ marginTop: 16,
1147
+ flexDirection: 'row',
1148
+ alignItems: 'center',
1149
+ minHeight: 20,
1150
+ },
1151
+ multiChoiceText: {
1152
+ flex: 1,
1153
+ fontFamily: 'sf-medium',
1154
+ fontSize: 14,
1155
+ color: Colours.TEXT_DARK,
1156
+ },
1157
+ tick: {
1158
+ marginRight: 10,
1159
+ borderRadius: 4,
1160
+ },
1161
+ unticked: {
1162
+ marginRight: 10,
1163
+ width: 20,
1164
+ height: 20,
1165
+ borderColor: Colours.LINEGREY,
1166
+ borderWidth: 1,
1167
+ borderRadius: 4,
1168
+ },
1169
+ dateContainer: {
1170
+ flexDirection: 'row',
1171
+ alignItems: 'center',
1172
+ },
1173
+ dateFieldButton: {
1174
+ flex: 1,
1175
+ },
1176
+ dateFieldContainer: {
1177
+ flexDirection: 'row',
1178
+ borderRadius: 2,
1179
+ backgroundColor: '#ebeff2',
1180
+ padding: 8,
1181
+ marginTop: 8,
1182
+ },
1183
+ dateText: {
1184
+ flex: 1,
1185
+ fontFamily: 'sf-regular',
1186
+ fontSize: 16,
1187
+ color: '#65686D',
1188
+ },
1189
+ dateIcon: {
1190
+ fontSize: 18,
1191
+ color: Colours.TEXT_BLUEGREY,
1192
+ },
1193
+ dateClearButton: {
1194
+ paddingLeft: 12,
1195
+ },
1196
+ removeIcon: {
1197
+ fontSize: 26,
1198
+ color: Colours.TEXT_BLUEGREY,
1199
+ },
1200
+ };
1201
+
1202
+ const mapStateToProps = state => {
1203
+ const { user, connection } = state;
1204
+ const { displayName, profilePic, uid, site, unit, phoneNumber } = user;
1205
+ return {
1206
+ connected: connection.connected,
1207
+ userType: user.type,
1208
+ displayName,
1209
+ profilePic,
1210
+ uid,
1211
+ site,
1212
+ unit,
1213
+ phoneNumber,
1214
+ colourBrandingMain: Colours.getMainBrandingColourFromState(state),
1215
+ strings: state.strings?.config || {},
1216
+ };
1217
+ };
1218
+
1219
+ export default connect(mapStateToProps, { jobAdded })(MaintenanceRequest);