@plusscommunities/pluss-maintenance-app 6.1.2-beta.0 → 7.0.0-beta.1

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 (78) hide show
  1. package/dist/module/actions/JobActions.js +44 -1
  2. package/dist/module/actions/JobActions.js.map +1 -1
  3. package/dist/module/actions/types.js +3 -0
  4. package/dist/module/actions/types.js.map +1 -1
  5. package/dist/module/apis/index.js +3 -1
  6. package/dist/module/apis/index.js.map +1 -1
  7. package/dist/module/apis/{generalActions.js → maintenanceActions.js} +44 -45
  8. package/dist/module/apis/maintenanceActions.js.map +1 -0
  9. package/dist/module/components/FilterPopupMenu.js +78 -26
  10. package/dist/module/components/FilterPopupMenu.js.map +1 -1
  11. package/dist/module/components/MaintenanceList.js +70 -45
  12. package/dist/module/components/MaintenanceList.js.map +1 -1
  13. package/dist/module/components/MaintenanceListItem.js +54 -42
  14. package/dist/module/components/MaintenanceListItem.js.map +1 -1
  15. package/dist/module/components/MaintenanceWidgetItem.js +16 -20
  16. package/dist/module/components/MaintenanceWidgetItem.js.map +1 -1
  17. package/dist/module/components/PrioritySelectorPopup.js +82 -0
  18. package/dist/module/components/PrioritySelectorPopup.js.map +1 -0
  19. package/dist/module/components/StatusSelectorPopup.js +9 -13
  20. package/dist/module/components/StatusSelectorPopup.js.map +1 -1
  21. package/dist/module/components/WidgetSmall.js +13 -9
  22. package/dist/module/components/WidgetSmall.js.map +1 -1
  23. package/dist/module/feature.config.js +3 -3
  24. package/dist/module/feature.config.js.map +1 -1
  25. package/dist/module/helper.js +39 -17
  26. package/dist/module/helper.js.map +1 -1
  27. package/dist/module/reducers/JobsReducer.js +33 -3
  28. package/dist/module/reducers/JobsReducer.js.map +1 -1
  29. package/dist/module/screens/JobTypePicker.js +3 -3
  30. package/dist/module/screens/JobTypePicker.js.map +1 -1
  31. package/dist/module/screens/MaintenancePage.js +2 -2
  32. package/dist/module/screens/RequestDetail.js +340 -88
  33. package/dist/module/screens/RequestDetail.js.map +1 -1
  34. package/dist/module/screens/RequestNotes.js +437 -26
  35. package/dist/module/screens/RequestNotes.js.map +1 -1
  36. package/dist/module/screens/ServiceRequest.js +596 -93
  37. package/dist/module/screens/ServiceRequest.js.map +1 -1
  38. package/dist/module/values.config.a.js +9 -1
  39. package/dist/module/values.config.a.js.map +1 -1
  40. package/dist/module/values.config.default.js +15 -1
  41. package/dist/module/values.config.default.js.map +1 -1
  42. package/dist/module/values.config.forms.js +42 -0
  43. package/dist/module/values.config.forms.js.map +1 -0
  44. package/dist/module/values.config.js +34 -20
  45. package/dist/module/values.config.js.map +1 -1
  46. package/package.json +11 -11
  47. package/src/actions/JobActions.js +53 -1
  48. package/src/actions/types.js +4 -0
  49. package/src/apis/index.js +5 -1
  50. package/src/apis/{generalActions.js → maintenanceActions.js} +51 -43
  51. package/src/components/FilterPopupMenu.js +75 -24
  52. package/src/components/MaintenanceList.js +64 -33
  53. package/src/components/MaintenanceListItem.js +53 -31
  54. package/src/components/MaintenanceWidgetItem.js +18 -14
  55. package/src/components/PrioritySelectorPopup.js +79 -0
  56. package/src/components/StatusSelectorPopup.js +8 -13
  57. package/src/components/WidgetSmall.js +9 -7
  58. package/src/feature.config.js +15 -13
  59. package/src/helper.js +51 -13
  60. package/src/reducers/JobsReducer.js +27 -2
  61. package/src/screens/JobTypePicker.js +1 -1
  62. package/src/screens/RequestDetail.js +324 -75
  63. package/src/screens/RequestNotes.js +434 -33
  64. package/src/screens/ServiceRequest.js +642 -157
  65. package/src/values.config.a.js +8 -0
  66. package/src/values.config.default.js +14 -0
  67. package/src/values.config.forms.js +42 -0
  68. package/src/values.config.js +34 -20
  69. package/dist/module/apis/generalActions.js.map +0 -1
  70. package/dist/module/values.config.b.js +0 -28
  71. package/dist/module/values.config.b.js.map +0 -1
  72. package/dist/module/values.config.c.js +0 -28
  73. package/dist/module/values.config.c.js.map +0 -1
  74. package/dist/module/values.config.d.js +0 -28
  75. package/dist/module/values.config.d.js.map +0 -1
  76. package/src/values.config.b.js +0 -28
  77. package/src/values.config.c.js +0 -28
  78. package/src/values.config.d.js +0 -28
@@ -1,13 +1,15 @@
1
- function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
2
- function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : String(i); }
1
+ function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
2
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
3
3
  function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
4
4
  import React, { Component } from 'react';
5
5
  import { Dimensions, Platform, KeyboardAvoidingView, ScrollView, Text, TouchableOpacity, View, Switch, FlatList, ImageBackground, Keyboard } from 'react-native';
6
+ import DateTimePicker from 'react-native-modal-datetime-picker';
7
+ import { Icon } from '@rneui/themed';
6
8
  import _ from 'lodash';
7
- import { Icon } from 'react-native-elements';
9
+ import moment from 'moment';
8
10
  import { connect } from 'react-redux';
9
11
  import { jobAdded } from '../actions';
10
- import { generalActions } from '../apis';
12
+ import { maintenanceActions } from '../apis';
11
13
  import { Services } from '../feature.config';
12
14
  import { Components, Colours, Helper, Config } from '../core.config';
13
15
  import { values } from '../values.config';
@@ -15,35 +17,115 @@ const PHOTO_SIZE = (Dimensions.get('window').width - 64) / 3;
15
17
  class MaintenanceRequest extends Component {
16
18
  constructor(props) {
17
19
  super(props);
20
+ _defineProperty(this, "onChangeName", userName => {
21
+ const update = {
22
+ userName
23
+ };
24
+ if (!this.state.customFields || !_.some(this.state.customFields, 'isTitle')) {
25
+ update.title = userName;
26
+ }
27
+ this.setState(update);
28
+ });
29
+ _defineProperty(this, "onChangeAnswer", (fieldId, answer) => {
30
+ const update = {
31
+ customFields: _.cloneDeep(this.state.customFields)
32
+ };
33
+ const field = update.customFields[fieldId];
34
+ field.answer = answer;
35
+ if (field.isTitle) update.title = field.answer;
36
+ this.setState(update);
37
+ });
38
+ _defineProperty(this, "onChangeToggleAnswer", (fieldId, answer) => {
39
+ const update = {
40
+ customFields: _.cloneDeep(this.state.customFields)
41
+ };
42
+ const field = update.customFields[fieldId];
43
+ field.answer = field.answer === answer ? undefined : answer;
44
+ if (field.isTitle) update.title = field.answer;
45
+ this.setState(update);
46
+ });
47
+ _defineProperty(this, "onChangeCheckboxAnswer", (fieldId, answer) => {
48
+ const update = {
49
+ customFields: _.cloneDeep(this.state.customFields)
50
+ };
51
+ const field = update.customFields[fieldId];
52
+ field.answer = _.xor(field.answer || [], [answer]);
53
+ if (field.isTitle) update.title = field.answer.join(', ');
54
+ this.setState(update);
55
+ });
56
+ _defineProperty(this, "onOpenDatePicker", (field, fieldId) => {
57
+ Keyboard.dismiss();
58
+ this.setState({
59
+ dateFieldId: fieldId,
60
+ popUpType: field.type,
61
+ isDateTimePickerVisible: true
62
+ });
63
+ });
64
+ _defineProperty(this, "onClearDate", fieldId => {
65
+ const update = {
66
+ customFields: _.cloneDeep(this.state.customFields)
67
+ };
68
+ const field = update.customFields[fieldId];
69
+ field.answer = undefined;
70
+ if (field.isTitle) update.title = field.answer;
71
+ this.setState(update);
72
+ });
73
+ _defineProperty(this, "onDateSelected", date => {
74
+ const {
75
+ customFields,
76
+ dateFieldId,
77
+ popUpType
78
+ } = this.state;
79
+ const update = {
80
+ customFields: _.cloneDeep(customFields),
81
+ isDateTimePickerVisible: false,
82
+ fieldId: null
83
+ };
84
+ const field = update.customFields[dateFieldId];
85
+ const dateObj = moment(date);
86
+ if (popUpType === 'date') {
87
+ field.answer = dateObj.format('YYYY-MM-DD');
88
+ if (field.isTitle) update.title = dateObj.format('DD MMM YYYY');
89
+ } else {
90
+ field.answer = dateObj.format('HH:mm');
91
+ if (field.isTitle) update.title = dateObj.format('h:mm a');
92
+ }
93
+ this.setState(update);
94
+ });
18
95
  _defineProperty(this, "onUploadStarted", (uploadUri, imageUri) => {
19
- const images = [...this.state.images];
20
- images.splice(images.length - 1, 0, {
96
+ const {
97
+ imageFieldId
98
+ } = this.state;
99
+ const imagesUpdate = this.getImages(imageFieldId);
100
+ imagesUpdate.splice(imagesUpdate.length - 1, 0, {
21
101
  uploading: true,
22
102
  uploadProgress: '0%',
23
103
  uploadUri,
24
104
  imageUri,
25
105
  allowRetry: true
26
106
  });
27
- this.setState({
28
- images
29
- });
107
+ this.setImages(imagesUpdate, imageFieldId);
30
108
  });
31
109
  _defineProperty(this, "onUploadProgress", progress => {
32
- const images = [...this.state.images];
33
- images.map(img => {
110
+ const {
111
+ imageFieldId
112
+ } = this.state;
113
+ const imagesUpdate = this.getImages(imageFieldId);
114
+ imagesUpdate.map(img => {
34
115
  if (img.uploadUri === progress.uri) {
35
116
  img.uploadProgress = progress.percentage;
36
117
  img.uploading = true;
37
118
  img.allowRetry = true;
38
119
  }
39
120
  });
40
- this.setState({
41
- images
42
- });
121
+ this.setImages(imagesUpdate, imageFieldId);
43
122
  });
44
123
  _defineProperty(this, "onUploadSuccess", async (uri, uploadUri) => {
45
- const images = [...this.state.images];
46
- images.map(img => {
124
+ const {
125
+ imageFieldId
126
+ } = this.state;
127
+ const imagesUpdate = this.getImages(imageFieldId);
128
+ imagesUpdate.map(img => {
47
129
  if (img.uploadUri === uploadUri && img.uploading) {
48
130
  img.url = uri.replace('/general/', '/general1400/');
49
131
  img.thumbNailExists = false;
@@ -51,41 +133,81 @@ class MaintenanceRequest extends Component {
51
133
  img.allowRetry = true;
52
134
  }
53
135
  });
54
- this.setState({
55
- images
56
- }, () => this.waitForThumbnails());
136
+ this.setImages(imagesUpdate, imageFieldId, () => this.waitForThumbnails());
57
137
  });
58
138
  _defineProperty(this, "onUploadFailed", uploadUri => {
59
- const images = [...this.state.images];
60
- images.map(img => {
139
+ const {
140
+ imageFieldId
141
+ } = this.state;
142
+ const imagesUpdate = this.getImages(imageFieldId);
143
+ imagesUpdate.map(img => {
61
144
  if (img.uploadUri === uploadUri) {
62
145
  img.uploading = true; // Requried for retry
63
146
  img.uploadProgress = '';
64
147
  img.allowRetry = true;
65
148
  }
66
149
  });
67
- this.setState({
68
- images
69
- });
150
+ this.setImages(imagesUpdate, imageFieldId);
70
151
  });
71
152
  _defineProperty(this, "onLibrarySelected", uri => {
72
- const images = [...this.state.images];
73
- images.splice(images.length - 1, 0, {
153
+ const {
154
+ imageFieldId
155
+ } = this.state;
156
+ const imagesUpdate = this.getImages(imageFieldId);
157
+ imagesUpdate.splice(imagesUpdate.length - 1, 0, {
74
158
  uploading: false,
75
159
  allowRetry: false,
76
160
  url: Helper.get1400(uri),
77
161
  thumbNailExists: true,
78
162
  thumbNailUrl: Helper.getThumb300(uri)
79
163
  });
80
- this.setState({
81
- images
82
- });
164
+ this.setImages(imagesUpdate, imageFieldId);
83
165
  });
84
- _defineProperty(this, "showUploadMenu", () => {
166
+ _defineProperty(this, "isFieldValid", (field, fieldId) => {
167
+ const {
168
+ mandatory,
169
+ type,
170
+ answer
171
+ } = field;
172
+ if (['staticTitle', 'staticText'].includes(type)) return true;
173
+ const imagesList = type === 'image' ? this.getImageUrls(fieldId) : [];
174
+ const checkMandatory = () => {
175
+ if (!mandatory) return true;
176
+ switch (type) {
177
+ case 'yn':
178
+ return _.isBoolean(answer);
179
+ case 'image':
180
+ return imagesList.length > 0;
181
+ case 'checkbox':
182
+ return _.isArray(answer) && answer.length > 0;
183
+ default:
184
+ return !_.isNil(answer) && !_.isEmpty(answer);
185
+ }
186
+ };
187
+ const checkFormat = () => {
188
+ if (_.isNil(answer) || _.isEmpty(answer)) return true;
189
+ switch (type) {
190
+ case 'email':
191
+ return Helper.isEmail(answer);
192
+ case 'date':
193
+ return moment(answer, 'YYYY-MM-DD', true).isValid();
194
+ case 'time':
195
+ return moment(answer, 'HH:mm', true).isValid();
196
+ default:
197
+ return true;
198
+ }
199
+ };
200
+ const valid = checkMandatory() && checkFormat();
201
+ return valid;
202
+ });
203
+ _defineProperty(this, "showUploadMenu", fieldId => {
85
204
  Keyboard.dismiss();
86
205
  if (this.state.uploadingImage || this.state.submitting) {
87
206
  return;
88
207
  }
208
+ if (fieldId) this.setState({
209
+ imageFieldId: fieldId
210
+ });
89
211
  this.imageUploader.showUploadMenu();
90
212
  });
91
213
  _defineProperty(this, "submit", async () => {
@@ -101,12 +223,15 @@ class MaintenanceRequest extends Component {
101
223
  y: 0
102
224
  });
103
225
  }, 100);
104
- const images = _.filter(this.state.images, img => {
105
- return !img.uploading && !img.add;
106
- }).map(img => {
107
- return img.url;
226
+ const images = this.getImageUrls();
227
+
228
+ // Fix custom images field answers
229
+ const customFields = _.cloneDeep(this.state.customFields);
230
+ const updatedCustomFields = customFields.map((field, fieldId) => {
231
+ if (field.type === 'image') field.answer = this.getImageUrls(fieldId);
232
+ return field;
108
233
  });
109
- generalActions.sendMaintenanceRequest(this.props.uid, this.state.userName, this.state.phone, this.state.roomNumber, this.state.title, description, null, this.state.type, images, Helper.getSite(this.props.site), this.state.isHome, this.state.times).then(res => {
234
+ maintenanceActions.sendMaintenanceRequest(this.props.uid, this.state.userName, this.state.phone, this.state.roomNumber, this.state.title, description, null, this.state.type, images, Helper.getSite(this.props.site), this.state.isHome, this.state.times, updatedCustomFields).then(res => {
110
235
  if (res.data.success) {
111
236
  this.refreshRequest(res.data.searchResult);
112
237
  if (this.props.onSubmissionSuccess) this.props.onSubmissionSuccess(res.data);
@@ -130,23 +255,72 @@ class MaintenanceRequest extends Component {
130
255
  });
131
256
  _defineProperty(this, "refreshRequest", async id => {
132
257
  try {
133
- const job = await generalActions.getJob(Helper.getSite(this.props.site), id);
134
- // console.log('refreshRequest', job?.data);
258
+ const job = await maintenanceActions.getJob(Helper.getSite(this.props.site), id);
135
259
  this.props.jobAdded(job.data);
136
260
  } catch (error) {
137
261
  console.log('refreshRequest error', error);
138
262
  }
139
263
  });
264
+ _defineProperty(this, "validateCustomFields", () => {
265
+ const {
266
+ customFields
267
+ } = this.state;
268
+ if (!customFields || customFields.length === 0) return true;
269
+ return customFields.every((field, index) => {
270
+ const isValid = this.isFieldValid(field, index);
271
+ return isValid;
272
+ });
273
+ });
274
+ _defineProperty(this, "getImages", (fieldId = null) => {
275
+ const {
276
+ images,
277
+ customFieldImages
278
+ } = this.state;
279
+ const imagesList = _.cloneDeep(fieldId ? customFieldImages[fieldId] : images);
280
+ if (!imagesList || !Array.isArray(imagesList) || imagesList.length === 0) {
281
+ return [{
282
+ add: true
283
+ }];
284
+ }
285
+ return imagesList;
286
+ });
287
+ _defineProperty(this, "setImages", (imagesList, fieldId = null, callback = null) => {
288
+ let update = {};
289
+ if (fieldId) {
290
+ const customFieldImages = _.cloneDeep(this.state.customFieldImages);
291
+ customFieldImages[fieldId] = imagesList;
292
+ update = {
293
+ customFieldImages
294
+ };
295
+ } else {
296
+ update = {
297
+ images: imagesList
298
+ };
299
+ }
300
+ this.setState(update, callback);
301
+ });
302
+ _defineProperty(this, "getImageUrls", (fieldId = null) => {
303
+ const imagesList = this.getImages(fieldId);
304
+ return _.filter(imagesList, img => {
305
+ return !img.uploading && !img.add;
306
+ }).map(img => {
307
+ return img.url;
308
+ });
309
+ });
140
310
  _defineProperty(this, "waitForThumbnails", () => {
141
311
  if (this.checkThumb) return;
142
312
  this.checkThumb = setInterval(async () => {
143
- const images = [];
144
- await Promise.all(this.state.images.map(image => {
313
+ const {
314
+ imageFieldId
315
+ } = this.state;
316
+ const imagesList = this.getImages(imageFieldId);
317
+ const imagesUpdate = [];
318
+ await Promise.all(imagesList.map(image => {
145
319
  return new Promise(async resolve => {
146
320
  const newImage = {
147
321
  ...image
148
322
  };
149
- images.push(newImage);
323
+ imagesUpdate.push(newImage);
150
324
  if (newImage.url && !newImage.thumbNailExists) {
151
325
  newImage.uploading = false;
152
326
  newImage.allowRetry = false;
@@ -156,22 +330,18 @@ class MaintenanceRequest extends Component {
156
330
  resolve(true);
157
331
  });
158
332
  }));
159
- const thumbnailsExist = images.every(image => !image.url || image.thumbNailExists);
333
+ const thumbnailsExist = imagesUpdate.every(image => !image.url || image.thumbNailExists);
160
334
  if (thumbnailsExist) {
161
335
  clearInterval(this.checkThumb);
162
336
  this.checkThumb = null;
163
- this.setState({
164
- images
165
- });
337
+ this.setImages(imagesUpdate, imageFieldId);
166
338
  }
167
339
  }, 2000);
168
340
  });
169
- _defineProperty(this, "removeImage", index => {
170
- const images = [...this.state.images];
171
- images.splice(index, 1);
172
- this.setState({
173
- images
174
- });
341
+ _defineProperty(this, "removeImage", (index, fieldId) => {
342
+ const imagesUpdate = this.getImages(fieldId);
343
+ imagesUpdate.splice(index, 1);
344
+ this.setImages(imagesUpdate, fieldId);
175
345
  });
176
346
  _defineProperty(this, "toggleFullscreenVideo", url => {
177
347
  if (typeof url !== 'string') url = '';
@@ -186,6 +356,7 @@ class MaintenanceRequest extends Component {
186
356
  fail: false,
187
357
  error: null,
188
358
  showError: false,
359
+ loadingTypes: values.forceCustomFields,
189
360
  userName: '',
190
361
  roomNumber: '',
191
362
  phone: '',
@@ -201,9 +372,20 @@ class MaintenanceRequest extends Component {
201
372
  currentVideoUrl: '',
202
373
  isHome: false,
203
374
  types: [],
204
- confirmationToShow: false
375
+ confirmationToShow: false,
376
+ customFields: [],
377
+ customFieldImages: {},
378
+ isDateTimePickerVisible: false,
379
+ popUpType: 'date',
380
+ dateFieldId: null,
381
+ imageFieldId: null
205
382
  };
206
383
  this.checkThumb = null;
384
+ this.keyboardTypes = {
385
+ phone: 'phone-pad',
386
+ email: 'email-address',
387
+ text: 'default'
388
+ };
207
389
  }
208
390
  componentDidMount() {
209
391
  if (this.props.userType !== 'KIOSK') {
@@ -248,32 +430,70 @@ class MaintenanceRequest extends Component {
248
430
  }],
249
431
  submitting: false,
250
432
  success: false,
251
- fail: false
252
- });
433
+ fail: false,
434
+ customFields: [],
435
+ customFieldImages: {},
436
+ isDateTimePickerVisible: false,
437
+ popUpType: 'date',
438
+ dateFieldId: null,
439
+ imageFieldId: null
440
+ }, () => this.pickType(this.state.type));
253
441
  }
254
442
  getJobTypes() {
255
- const self = this;
256
- generalActions.getJobTypes(Helper.getSite(this.props.site)).then(res => {
257
- self.setState({
443
+ maintenanceActions.getJobTypes(Helper.getSite(this.props.site)).then(res => {
444
+ this.setState({
258
445
  types: res.data
259
446
  });
260
- self.getDefaultJob();
447
+ console.log(res.data);
448
+ this.getDefaultJob();
261
449
  }).catch(() => {});
262
450
  }
263
- getDefaultJob() {
264
- if (this.state.types.length !== 0 && this.state.jobId == null) {
451
+ pickType(type) {
452
+ const {
453
+ types
454
+ } = this.state;
455
+ const selected = types.find(t => t.typeName === type) || {};
456
+ if (values.forceCustomFields && !selected.hasCustomFields) {
457
+ console.log(selected);
265
458
  this.setState({
266
- type: this.state.types[0].typeName
459
+ type,
460
+ customFields: [],
461
+ noType: true
267
462
  });
463
+ return;
268
464
  }
465
+ const update = {
466
+ type,
467
+ customFields: selected.hasCustomFields && selected.customFields.length > 0 ? _.cloneDeep(selected.customFields) : [],
468
+ loadingTypes: false
469
+ };
470
+ if (!_.isEmpty(update.customFields) && !_.some(update.customFields, 'isTitle')) {
471
+ update.title = this.state.userName;
472
+ }
473
+ this.setState(update);
269
474
  }
270
- pickType(type) {
271
- this.setState({
272
- type
273
- });
475
+ getDefaultJob() {
476
+ const {
477
+ types,
478
+ jobId
479
+ } = this.state;
480
+ if (types.length !== 0 && jobId == null) {
481
+ const defaultType = types[0];
482
+ this.pickType(defaultType.typeName);
483
+ }
274
484
  }
275
485
  submitRequest() {
276
- if (this.state.submitting || !this.props.connected) {
486
+ const {
487
+ customFields,
488
+ submitting,
489
+ uploadingImage,
490
+ title,
491
+ roomNumber,
492
+ isHome,
493
+ times
494
+ } = this.state;
495
+ const hasCustomFields = customFields && customFields.length > 0;
496
+ if (submitting || !this.props.connected) {
277
497
  if (!this.props.connected) {
278
498
  this.setState({
279
499
  error: {
@@ -283,25 +503,41 @@ class MaintenanceRequest extends Component {
283
503
  }
284
504
  return;
285
505
  }
286
- if (this.state.uploadingImage) {
287
- return;
288
- }
506
+ if (uploadingImage) return;
289
507
  this.setState({
290
508
  error: null,
291
509
  showError: false
292
510
  });
293
- if (this.state.title.length === 0 || !this.state.roomNumber || this.state.roomNumber.length === 0) {
294
- this.setState({
295
- showError: true
511
+ if (title.length === 0 || !roomNumber || roomNumber.length === 0) {
512
+ console.log('submitRequest - error', {
513
+ title,
514
+ roomNumber
296
515
  });
297
- return;
298
- }
299
- if (this.state.isHome && this.state.times.length < 2) {
300
516
  this.setState({
301
517
  showError: true
302
518
  });
303
519
  return;
304
520
  }
521
+ if (hasCustomFields) {
522
+ if (!this.validateCustomFields()) {
523
+ console.log('submitRequest - custom fields error');
524
+ this.setState({
525
+ showError: true
526
+ });
527
+ return;
528
+ }
529
+ } else {
530
+ if (isHome && times.length < 2) {
531
+ console.log('submitRequest - error', {
532
+ isHome,
533
+ times
534
+ });
535
+ this.setState({
536
+ showError: true
537
+ });
538
+ return;
539
+ }
540
+ }
305
541
  this.submit();
306
542
  }
307
543
  renderUploadMenu() {
@@ -324,14 +560,15 @@ class MaintenanceRequest extends Component {
324
560
  hideLibrary: true
325
561
  });
326
562
  }
327
- renderImage(item, index) {
563
+ renderImage(item, index, fieldId = null) {
328
564
  const isVideoUrl = Helper.isVideo(item.url);
565
+ const imagesList = this.getImages(fieldId);
329
566
  if (item.add) {
330
567
  return /*#__PURE__*/React.createElement(TouchableOpacity, {
331
568
  activeOpacity: 0.8,
332
- onPress: this.showUploadMenu
569
+ onPress: () => this.showUploadMenu(fieldId)
333
570
  }, /*#__PURE__*/React.createElement(View, {
334
- style: [styles.imageContainer, this.state.images.length > 1 && styles.imageContainerNotEmpty, index % 3 === 0 && {
571
+ style: [styles.imageContainer, imagesList.length > 1 && styles.imageContainerNotEmpty, index % 3 === 0 && {
335
572
  marginLeft: 0
336
573
  }, index > 2 && {
337
574
  marginTop: 8
@@ -345,7 +582,7 @@ class MaintenanceRequest extends Component {
345
582
  }))));
346
583
  }
347
584
  return /*#__PURE__*/React.createElement(View, {
348
- style: [styles.imageContainer, this.state.images.length > 1 && styles.imageContainerNotEmpty, index % 3 === 0 && {
585
+ style: [styles.imageContainer, imagesList.length > 1 && styles.imageContainerNotEmpty, index % 3 === 0 && {
349
586
  marginLeft: 0
350
587
  }, index > 2 && {
351
588
  marginTop: 8
@@ -367,7 +604,7 @@ class MaintenanceRequest extends Component {
367
604
  iconStyle: styles.imageControlIcon
368
605
  }))), /*#__PURE__*/React.createElement(TouchableOpacity, {
369
606
  style: styles.removeImage,
370
- onPress: this.removeImage.bind(this, index)
607
+ onPress: () => this.removeImage(index, fieldId)
371
608
  }, /*#__PURE__*/React.createElement(Icon, {
372
609
  name: "remove",
373
610
  type: "font-awesome",
@@ -386,24 +623,200 @@ class MaintenanceRequest extends Component {
386
623
  style: styles.requestSuccess
387
624
  }, "Your request has been submitted. Thank you."));
388
625
  }
389
- renderImageList() {
626
+ renderImageList(fieldId = null) {
627
+ const imagesList = this.getImages(fieldId);
390
628
  return /*#__PURE__*/React.createElement(View, {
391
629
  style: styles.imageSection
392
630
  }, /*#__PURE__*/React.createElement(View, {
393
- style: [styles.imageListContainer, this.state.images.length < 2 && styles.imageListContainerEmpty]
631
+ style: [styles.imageListContainer, imagesList.length < 2 && styles.imageListContainerEmpty]
394
632
  }, /*#__PURE__*/React.createElement(FlatList, {
395
633
  keyboardShouldPersistTaps: "always",
396
634
  enableEmptySections: true,
397
- data: this.state.images,
635
+ data: imagesList,
398
636
  renderItem: ({
399
637
  item,
400
638
  index
401
- }) => this.renderImage(item, index),
639
+ }) => this.renderImage(item, index, fieldId),
402
640
  keyExtractor: (item, index) => index,
403
641
  numColumns: 3
404
642
  })));
405
643
  }
644
+ renderDateField(field, fieldId, sectionStyle) {
645
+ let displayText, placeHolder, icon, errorText;
646
+ if (field.type === 'date') {
647
+ displayText = field.answer ? moment(field.answer, 'YYYY-MM-DD').format('DD MMM YYYY') : '';
648
+ placeHolder = 'dd mmm yyyy';
649
+ icon = 'calendar';
650
+ errorText = 'Not a valid date';
651
+ } else {
652
+ displayText = field.answer ? moment(field.answer, 'HH:mm').format('h:mm a') : '';
653
+ placeHolder = '--:-- --';
654
+ icon = 'clock-o';
655
+ errorText = 'Not a valid time';
656
+ }
657
+ return /*#__PURE__*/React.createElement(Components.GenericInputSection, {
658
+ key: fieldId,
659
+ label: field.label,
660
+ sectionStyle: sectionStyle,
661
+ isValid: () => this.isFieldValid(field, fieldId),
662
+ required: field.mandatory,
663
+ showError: this.state.showError,
664
+ errorText: errorText
665
+ }, /*#__PURE__*/React.createElement(View, {
666
+ style: styles.dateContainer
667
+ }, /*#__PURE__*/React.createElement(TouchableOpacity, {
668
+ style: styles.dateFieldButton,
669
+ onPress: () => this.onOpenDatePicker(field, fieldId)
670
+ }, /*#__PURE__*/React.createElement(View, {
671
+ style: styles.dateFieldContainer
672
+ }, /*#__PURE__*/React.createElement(Text, {
673
+ style: styles.dateText
674
+ }, displayText || placeHolder), /*#__PURE__*/React.createElement(Icon, {
675
+ type: "font-awesome",
676
+ name: icon,
677
+ iconStyle: styles.dateIcon
678
+ }))), displayText ? /*#__PURE__*/React.createElement(TouchableOpacity, {
679
+ style: styles.dateClearButton,
680
+ onPress: () => this.onClearDate(fieldId)
681
+ }, /*#__PURE__*/React.createElement(Icon, {
682
+ type: "font-awesome",
683
+ name: "times",
684
+ iconStyle: styles.removeIcon
685
+ })) : null));
686
+ }
687
+ renderField(field, fieldId) {
688
+ const sectionStyle = {
689
+ marginTop: fieldId === 0 ? 24 : 0,
690
+ marginBottom: 24
691
+ };
692
+ switch (field.type) {
693
+ case 'yn':
694
+ return /*#__PURE__*/React.createElement(Components.GenericInputSection, {
695
+ key: fieldId,
696
+ label: field.label,
697
+ sectionStyle: sectionStyle,
698
+ inputType: "toggle",
699
+ value: field.answer,
700
+ onChange: answer => this.onChangeToggleAnswer(fieldId, answer),
701
+ isValid: () => this.isFieldValid(field, fieldId),
702
+ showError: this.state.showError,
703
+ required: field.mandatory
704
+ });
705
+ case 'multichoice':
706
+ return /*#__PURE__*/React.createElement(Components.GenericInputSection, {
707
+ key: fieldId,
708
+ label: field.label,
709
+ sectionStyle: sectionStyle,
710
+ inputType: "radio",
711
+ value: field.answer,
712
+ onChange: answer => this.onChangeToggleAnswer(fieldId, answer),
713
+ options: field.values.map(o => {
714
+ return {
715
+ Label: o,
716
+ Value: o
717
+ };
718
+ }),
719
+ isValid: () => this.isFieldValid(field, fieldId),
720
+ showError: this.state.showError,
721
+ required: field.mandatory
722
+ });
723
+ case 'checkbox':
724
+ return /*#__PURE__*/React.createElement(Components.GenericInputSection, {
725
+ key: fieldId,
726
+ label: field.label,
727
+ sectionStyle: sectionStyle,
728
+ isValid: () => this.isFieldValid(field, fieldId),
729
+ showError: this.state.showError,
730
+ required: field.mandatory
731
+ }, field.values.map((o, i) => {
732
+ const isActive = field.answer && _.includes(field.answer, o);
733
+ return /*#__PURE__*/React.createElement(TouchableOpacity, {
734
+ onPress: () => this.onChangeCheckboxAnswer(fieldId, o),
735
+ key: i,
736
+ style: styles.multiChoiceOption,
737
+ hitSlop: {
738
+ top: 8,
739
+ left: 8,
740
+ bottom: 8,
741
+ right: 8
742
+ }
743
+ }, isActive ? /*#__PURE__*/React.createElement(Components.TickIcon, {
744
+ style: styles.tick,
745
+ size: 20,
746
+ color: this.props.colourBrandingMain
747
+ }) : /*#__PURE__*/React.createElement(View, {
748
+ style: styles.unticked
749
+ }), /*#__PURE__*/React.createElement(Text, {
750
+ style: styles.multiChoiceText
751
+ }, o));
752
+ }));
753
+ case 'text':
754
+ case 'email':
755
+ case 'phone':
756
+ return /*#__PURE__*/React.createElement(Components.GenericInputSection, {
757
+ key: fieldId,
758
+ label: field.label,
759
+ placeholder: field.placeHolder,
760
+ value: field.answer,
761
+ onChangeText: val => this.onChangeAnswer(fieldId, val),
762
+ editable: true,
763
+ squaredCorners: true,
764
+ isValid: () => this.isFieldValid(field, fieldId),
765
+ showError: this.state.showError,
766
+ errorText: field.type === 'email' ? 'Not a valid email' : undefined,
767
+ required: field.mandatory,
768
+ sectionStyle: sectionStyle,
769
+ autoCapitalize: "sentences",
770
+ keyboardType: this.keyboardTypes[field.type]
771
+ });
772
+ case 'staticTitle':
773
+ return /*#__PURE__*/React.createElement(Text, {
774
+ key: fieldId,
775
+ style: [styles.staticTitle, {
776
+ color: this.props.colourBrandingMain
777
+ }, sectionStyle]
778
+ }, field.label);
779
+ case 'staticText':
780
+ return /*#__PURE__*/React.createElement(View, {
781
+ key: fieldId,
782
+ style: [styles.staticText, sectionStyle]
783
+ }, Helper.toParagraphed(field.label, styles.staticText));
784
+ case 'date':
785
+ case 'time':
786
+ return this.renderDateField(field, fieldId, sectionStyle);
787
+ case 'image':
788
+ return /*#__PURE__*/React.createElement(Components.GenericInputSection, {
789
+ key: fieldId,
790
+ label: field.label,
791
+ sectionStyle: sectionStyle,
792
+ isValid: () => this.isFieldValid(field, fieldId),
793
+ required: field.mandatory,
794
+ showError: this.state.showError
795
+ }, this.renderImageList(fieldId));
796
+ default:
797
+ return null;
798
+ }
799
+ }
800
+ renderCustomFields() {
801
+ const {
802
+ customFields
803
+ } = this.state;
804
+ if (!customFields || customFields.length === 0) return null;
805
+ return /*#__PURE__*/React.createElement(Components.FormCard, {
806
+ style: {
807
+ marginTop: 16
808
+ }
809
+ }, customFields.map((field, i) => this.renderField(field, i)));
810
+ }
406
811
  renderForm() {
812
+ const {
813
+ customFields,
814
+ loadingTypes
815
+ } = this.state;
816
+ const hasCustomFields = customFields && customFields.length > 0;
817
+ if (loadingTypes) {
818
+ return /*#__PURE__*/React.createElement(Components.Spinner, null);
819
+ }
407
820
  return /*#__PURE__*/React.createElement(View, {
408
821
  style: {
409
822
  flex: 1
@@ -430,9 +843,7 @@ class MaintenanceRequest extends Component {
430
843
  label: 'Name',
431
844
  placeholder: 'Enter your name',
432
845
  textValue: this.state.userName,
433
- onChangeText: userName => this.setState({
434
- userName
435
- }),
846
+ onChangeText: userName => this.onChangeName(userName),
436
847
  editable: this.props.userType === 'KIOSK' && this.state.submitting === false,
437
848
  isValid: () => {
438
849
  return this.state.userName.length > 1;
@@ -465,7 +876,7 @@ class MaintenanceRequest extends Component {
465
876
  },
466
877
  required: true,
467
878
  errorText: "Please provide your address.",
468
- showError: this.state.showError && this.state.roomNumber && this.state.roomNumber.length < 2
879
+ showError: this.state.showError && (!this.state.roomNumber || this.state.roomNumber.length < 2)
469
880
  })), /*#__PURE__*/React.createElement(Components.FormCard, {
470
881
  style: {
471
882
  marginTop: 16,
@@ -476,7 +887,7 @@ class MaintenanceRequest extends Component {
476
887
  }
477
888
  }, /*#__PURE__*/React.createElement(Text, {
478
889
  style: styles.sectionTitle
479
- }, Config.env.strings.JOB_TYPE), /*#__PURE__*/React.createElement(TouchableOpacity, {
890
+ }, values.textJobType), /*#__PURE__*/React.createElement(TouchableOpacity, {
480
891
  onPress: this.onPressType.bind(this)
481
892
  }, /*#__PURE__*/React.createElement(View, {
482
893
  style: {
@@ -494,7 +905,7 @@ class MaintenanceRequest extends Component {
494
905
  fontSize: 20,
495
906
  color: this.props.colourBrandingMain
496
907
  }]
497
- })))), /*#__PURE__*/React.createElement(Components.FormCard, {
908
+ })))), hasCustomFields ? this.renderCustomFields() : /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Components.FormCard, {
498
909
  style: {
499
910
  marginTop: 16
500
911
  }
@@ -577,11 +988,11 @@ class MaintenanceRequest extends Component {
577
988
  autoCorrect: true,
578
989
  multiline: true,
579
990
  autoGrow: true
580
- })), this.renderImageList())));
991
+ })), this.renderImageList()))));
581
992
  }
582
993
  renderRegisterConfirmation() {
583
994
  return /*#__PURE__*/React.createElement(Components.ConfirmationPopup, {
584
- confirmText: 'Request submitted',
995
+ confirmText: `${values.textEntityName} submitted`,
585
996
  repeatText: 'Submit another',
586
997
  visible: this.state.confirmationToShow,
587
998
  onClose: this.onCloseConfirmationPopup.bind(this),
@@ -601,7 +1012,25 @@ class MaintenanceRequest extends Component {
601
1012
  onClose: this.toggleFullscreenVideo
602
1013
  });
603
1014
  }
1015
+ renderNoType() {
1016
+ if (!this.state.noType) {
1017
+ return null;
1018
+ }
1019
+ return /*#__PURE__*/React.createElement(Components.WarningPopup, {
1020
+ confirmText: 'No forms are available',
1021
+ infoText: 'Check back later for forms.',
1022
+ visible: this.state.noType,
1023
+ onClose: this.onPressBack.bind(this),
1024
+ padHorizontal: true
1025
+ });
1026
+ }
604
1027
  render() {
1028
+ const {
1029
+ submitting,
1030
+ success,
1031
+ isDateTimePickerVisible,
1032
+ popUpType
1033
+ } = this.state;
605
1034
  return /*#__PURE__*/React.createElement(KeyboardAvoidingView, {
606
1035
  behavior: Platform.OS === 'ios' && 'padding',
607
1036
  style: styles.viewContainer
@@ -611,10 +1040,18 @@ class MaintenanceRequest extends Component {
611
1040
  leftIcon: "angle-left",
612
1041
  onPressLeft: this.onPressBack.bind(this),
613
1042
  text: this.props.strings[`${values.featureKey}_textFeatureTitle`] || values.textFeatureTitle,
614
- rightText: this.state.submitting || this.state.success ? null : 'Done',
1043
+ rightText: submitting || success ? null : 'Done',
615
1044
  onPressRight: this.submitRequest.bind(this),
616
1045
  absoluteRight: true
617
- }), this.renderForm()), this.renderRegisterConfirmation(), this.renderVideoPlayerPopup());
1046
+ }), this.renderForm()), this.renderRegisterConfirmation(), this.renderVideoPlayerPopup(), this.renderNoType(), /*#__PURE__*/React.createElement(DateTimePicker, {
1047
+ isVisible: isDateTimePickerVisible,
1048
+ onConfirm: this.onDateSelected,
1049
+ onCancel: () => this.setState({
1050
+ isDateTimePickerVisible: false
1051
+ }),
1052
+ mode: popUpType,
1053
+ headerTextIOS: `Pick a ${popUpType}`
1054
+ }));
618
1055
  }
619
1056
  }
620
1057
  const styles = {
@@ -716,6 +1153,72 @@ const styles = {
716
1153
  height: 40,
717
1154
  alignItems: 'center',
718
1155
  justifyContent: 'center'
1156
+ },
1157
+ staticTitle: {
1158
+ fontSize: 20,
1159
+ fontFamily: 'sf-semibold',
1160
+ color: Colours.TEXT_DARKEST
1161
+ },
1162
+ staticText: {
1163
+ fontSize: 17,
1164
+ fontFamily: 'sf-regular',
1165
+ color: Colours.TEXT_DARKEST,
1166
+ lineHeight: 24
1167
+ },
1168
+ multiChoiceOption: {
1169
+ marginTop: 16,
1170
+ flexDirection: 'row',
1171
+ alignItems: 'center',
1172
+ minHeight: 20
1173
+ },
1174
+ multiChoiceText: {
1175
+ flex: 1,
1176
+ fontFamily: 'sf-medium',
1177
+ fontSize: 14,
1178
+ color: Colours.TEXT_DARK
1179
+ },
1180
+ tick: {
1181
+ marginRight: 10,
1182
+ borderRadius: 4
1183
+ },
1184
+ unticked: {
1185
+ marginRight: 10,
1186
+ width: 20,
1187
+ height: 20,
1188
+ borderColor: Colours.LINEGREY,
1189
+ borderWidth: 1,
1190
+ borderRadius: 4
1191
+ },
1192
+ dateContainer: {
1193
+ flexDirection: 'row',
1194
+ alignItems: 'center'
1195
+ },
1196
+ dateFieldButton: {
1197
+ flex: 1
1198
+ },
1199
+ dateFieldContainer: {
1200
+ flexDirection: 'row',
1201
+ borderRadius: 2,
1202
+ backgroundColor: '#ebeff2',
1203
+ padding: 8,
1204
+ marginTop: 8
1205
+ },
1206
+ dateText: {
1207
+ flex: 1,
1208
+ fontFamily: 'sf-regular',
1209
+ fontSize: 16,
1210
+ color: '#65686D'
1211
+ },
1212
+ dateIcon: {
1213
+ fontSize: 18,
1214
+ color: Colours.TEXT_BLUEGREY
1215
+ },
1216
+ dateClearButton: {
1217
+ paddingLeft: 12
1218
+ },
1219
+ removeIcon: {
1220
+ fontSize: 26,
1221
+ color: Colours.TEXT_BLUEGREY
719
1222
  }
720
1223
  };
721
1224
  const mapStateToProps = state => {