@plusscommunities/pluss-maintenance-app 6.0.5 → 6.0.6-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.
- package/dist/module/actions/types.js +4 -3
- package/dist/module/actions/types.js.map +1 -1
- package/dist/module/apis/maintenanceActions.js +17 -15
- package/dist/module/apis/maintenanceActions.js.map +1 -1
- package/dist/module/components/MaintenanceList.js +4 -3
- package/dist/module/components/MaintenanceList.js.map +1 -1
- package/dist/module/components/MaintenanceListItem.js +3 -2
- package/dist/module/components/MaintenanceListItem.js.map +1 -1
- package/dist/module/components/MaintenanceWidgetItem.js +2 -1
- package/dist/module/components/MaintenanceWidgetItem.js.map +1 -1
- package/dist/module/components/WidgetSmall.js +6 -3
- package/dist/module/components/WidgetSmall.js.map +1 -1
- package/dist/module/feature.config.js +18 -33
- package/dist/module/feature.config.js.map +1 -1
- package/dist/module/index.js +15 -10
- package/dist/module/index.js.map +1 -1
- package/dist/module/screens/MaintenancePage.js +7 -3
- package/dist/module/screens/MaintenancePage.js.map +1 -1
- package/dist/module/screens/RequestDetail.js +105 -30
- package/dist/module/screens/RequestDetail.js.map +1 -1
- package/dist/module/screens/RequestNotes.js +2 -1
- package/dist/module/screens/RequestNotes.js.map +1 -1
- package/dist/module/screens/ServiceRequest.js +552 -84
- package/dist/module/screens/ServiceRequest.js.map +1 -1
- package/dist/module/values.config.a.js +30 -0
- package/dist/module/values.config.a.js.map +1 -0
- package/dist/module/values.config.b.js +30 -0
- package/dist/module/values.config.b.js.map +1 -0
- package/dist/module/values.config.c.js +30 -0
- package/dist/module/values.config.c.js.map +1 -0
- package/dist/module/values.config.d.js +30 -0
- package/dist/module/values.config.d.js.map +1 -0
- package/dist/module/values.config.default.js +30 -0
- package/dist/module/values.config.default.js.map +1 -0
- package/dist/module/values.config.js +30 -0
- package/dist/module/values.config.js.map +1 -0
- package/package.json +9 -3
- package/src/actions/types.js +5 -3
- package/src/apis/maintenanceActions.js +30 -14
- package/src/components/MaintenanceList.js +5 -3
- package/src/components/MaintenanceListItem.js +7 -2
- package/src/components/MaintenanceWidgetItem.js +2 -1
- package/src/components/WidgetSmall.js +6 -3
- package/src/feature.config.js +18 -30
- package/src/index.js +15 -8
- package/src/screens/MaintenancePage.js +5 -4
- package/src/screens/RequestDetail.js +100 -32
- package/src/screens/RequestNotes.js +2 -1
- package/src/screens/ServiceRequest.js +591 -149
- package/src/values.config.a.js +30 -0
- package/src/values.config.b.js +30 -0
- package/src/values.config.c.js +30 -0
- package/src/values.config.d.js +30 -0
- package/src/values.config.default.js +30 -0
- package/src/values.config.js +30 -0
@@ -12,13 +12,16 @@ import {
|
|
12
12
|
ImageBackground,
|
13
13
|
Keyboard,
|
14
14
|
} from 'react-native';
|
15
|
-
import
|
15
|
+
import DateTimePicker from 'react-native-modal-datetime-picker';
|
16
16
|
import { Icon } from 'react-native-elements';
|
17
|
+
import _ from 'lodash';
|
18
|
+
import moment from 'moment';
|
17
19
|
import { connect } from 'react-redux';
|
18
20
|
import { jobAdded } from '../actions';
|
19
21
|
import { maintenanceActions } from '../apis';
|
20
22
|
import { Services } from '../feature.config';
|
21
23
|
import { Components, Colours, Helper, Config } from '../core.config';
|
24
|
+
import { values } from '../values.config';
|
22
25
|
|
23
26
|
const PHOTO_SIZE = (Dimensions.get('window').width - 64) / 3;
|
24
27
|
|
@@ -55,8 +58,20 @@ class MaintenanceRequest extends Component {
|
|
55
58
|
types: [],
|
56
59
|
|
57
60
|
confirmationToShow: false,
|
61
|
+
|
62
|
+
customFields: [],
|
63
|
+
customFieldImages: {},
|
64
|
+
isDateTimePickerVisible: false,
|
65
|
+
popUpType: 'date',
|
66
|
+
dateFieldId: null,
|
67
|
+
imageFieldId: null,
|
58
68
|
};
|
59
69
|
this.checkThumb = null;
|
70
|
+
this.keyboardTypes = {
|
71
|
+
phone: 'phone-pad',
|
72
|
+
email: 'email-address',
|
73
|
+
text: 'default',
|
74
|
+
};
|
60
75
|
}
|
61
76
|
|
62
77
|
componentDidMount() {
|
@@ -74,9 +89,62 @@ class MaintenanceRequest extends Component {
|
|
74
89
|
clearInterval(this.checkThumb);
|
75
90
|
}
|
76
91
|
|
92
|
+
onChangeAnswer = (fieldId, answer) => {
|
93
|
+
const update = { customFields: _.cloneDeep(this.state.customFields) };
|
94
|
+
const field = update.customFields[fieldId];
|
95
|
+
field.answer = answer;
|
96
|
+
if (field.isTitle) update.title = field.answer;
|
97
|
+
this.setState(update);
|
98
|
+
};
|
99
|
+
|
100
|
+
onChangeToggleAnswer = (fieldId, answer) => {
|
101
|
+
const update = { customFields: _.cloneDeep(this.state.customFields) };
|
102
|
+
const field = update.customFields[fieldId];
|
103
|
+
field.answer = field.answer === answer ? undefined : answer;
|
104
|
+
if (field.isTitle) update.title = field.answer;
|
105
|
+
this.setState(update);
|
106
|
+
};
|
107
|
+
|
108
|
+
onChangeCheckboxAnswer = (fieldId, answer) => {
|
109
|
+
const update = { customFields: _.cloneDeep(this.state.customFields) };
|
110
|
+
const field = update.customFields[fieldId];
|
111
|
+
field.answer = _.xor(field.answer || [], [answer]);
|
112
|
+
if (field.isTitle) update.title = field.answer.join(', ');
|
113
|
+
this.setState(update);
|
114
|
+
};
|
115
|
+
|
116
|
+
onOpenDatePicker = (field, fieldId) => {
|
117
|
+
Keyboard.dismiss();
|
118
|
+
this.setState({ dateFieldId: fieldId, popUpType: field.type, isDateTimePickerVisible: true });
|
119
|
+
};
|
120
|
+
|
121
|
+
onClearDate = fieldId => {
|
122
|
+
const update = { customFields: _.cloneDeep(this.state.customFields) };
|
123
|
+
const field = update.customFields[fieldId];
|
124
|
+
field.answer = undefined;
|
125
|
+
if (field.isTitle) update.title = field.answer;
|
126
|
+
this.setState(update);
|
127
|
+
};
|
128
|
+
|
129
|
+
onDateSelected = date => {
|
130
|
+
const { customFields, dateFieldId, popUpType } = this.state;
|
131
|
+
const update = { customFields: _.cloneDeep(customFields), isDateTimePickerVisible: false, fieldId: null };
|
132
|
+
const field = update.customFields[dateFieldId];
|
133
|
+
const dateObj = moment(date);
|
134
|
+
if (popUpType === 'date') {
|
135
|
+
field.answer = dateObj.format('YYYY-MM-DD');
|
136
|
+
if (field.isTitle) update.title = dateObj.format('DD MMM YYYY');
|
137
|
+
} else {
|
138
|
+
field.answer = dateObj.format('HH:mm');
|
139
|
+
if (field.isTitle) update.title = dateObj.format('h:mm a');
|
140
|
+
}
|
141
|
+
this.setState(update);
|
142
|
+
};
|
143
|
+
|
77
144
|
onUploadStarted = (uploadUri, imageUri) => {
|
78
|
-
const
|
79
|
-
|
145
|
+
const { imageFieldId } = this.state;
|
146
|
+
const imagesUpdate = this.getImages(imageFieldId);
|
147
|
+
imagesUpdate.splice(imagesUpdate.length - 1, 0, {
|
80
148
|
uploading: true,
|
81
149
|
uploadProgress: '0%',
|
82
150
|
uploadUri,
|
@@ -84,24 +152,27 @@ class MaintenanceRequest extends Component {
|
|
84
152
|
allowRetry: true,
|
85
153
|
});
|
86
154
|
|
87
|
-
this.
|
155
|
+
this.setImages(imagesUpdate, imageFieldId);
|
88
156
|
};
|
89
157
|
|
90
158
|
onUploadProgress = progress => {
|
91
|
-
const
|
92
|
-
|
159
|
+
const { imageFieldId } = this.state;
|
160
|
+
const imagesUpdate = this.getImages(imageFieldId);
|
161
|
+
imagesUpdate.map(img => {
|
93
162
|
if (img.uploadUri === progress.uri) {
|
94
163
|
img.uploadProgress = progress.percentage;
|
95
164
|
img.uploading = true;
|
96
165
|
img.allowRetry = true;
|
97
166
|
}
|
98
167
|
});
|
99
|
-
|
168
|
+
|
169
|
+
this.setImages(imagesUpdate, imageFieldId);
|
100
170
|
};
|
101
171
|
|
102
172
|
onUploadSuccess = async (uri, uploadUri) => {
|
103
|
-
const
|
104
|
-
|
173
|
+
const { imageFieldId } = this.state;
|
174
|
+
const imagesUpdate = this.getImages(imageFieldId);
|
175
|
+
imagesUpdate.map(img => {
|
105
176
|
if (img.uploadUri === uploadUri && img.uploading) {
|
106
177
|
img.url = uri.replace('/general/', '/general1400/');
|
107
178
|
img.thumbNailExists = false;
|
@@ -109,12 +180,14 @@ class MaintenanceRequest extends Component {
|
|
109
180
|
img.allowRetry = true;
|
110
181
|
}
|
111
182
|
});
|
112
|
-
|
183
|
+
|
184
|
+
this.setImages(imagesUpdate, imageFieldId, () => this.waitForThumbnails());
|
113
185
|
};
|
114
186
|
|
115
187
|
onUploadFailed = uploadUri => {
|
116
|
-
const
|
117
|
-
|
188
|
+
const { imageFieldId } = this.state;
|
189
|
+
const imagesUpdate = this.getImages(imageFieldId);
|
190
|
+
imagesUpdate.map(img => {
|
118
191
|
if (img.uploadUri === uploadUri) {
|
119
192
|
img.uploading = true; // Requried for retry
|
120
193
|
img.uploadProgress = '';
|
@@ -122,12 +195,13 @@ class MaintenanceRequest extends Component {
|
|
122
195
|
}
|
123
196
|
});
|
124
197
|
|
125
|
-
this.
|
198
|
+
this.setImages(imagesUpdate, imageFieldId);
|
126
199
|
};
|
127
200
|
|
128
201
|
onLibrarySelected = uri => {
|
129
|
-
const
|
130
|
-
|
202
|
+
const { imageFieldId } = this.state;
|
203
|
+
const imagesUpdate = this.getImages(imageFieldId);
|
204
|
+
imagesUpdate.splice(imagesUpdate.length - 1, 0, {
|
131
205
|
uploading: false,
|
132
206
|
allowRetry: false,
|
133
207
|
url: Helper.get1400(uri),
|
@@ -135,7 +209,7 @@ class MaintenanceRequest extends Component {
|
|
135
209
|
thumbNailUrl: Helper.getThumb300(uri),
|
136
210
|
});
|
137
211
|
|
138
|
-
this.
|
212
|
+
this.setImages(imagesUpdate, imageFieldId);
|
139
213
|
};
|
140
214
|
|
141
215
|
onPressBack() {
|
@@ -143,7 +217,7 @@ class MaintenanceRequest extends Component {
|
|
143
217
|
}
|
144
218
|
|
145
219
|
onPressType() {
|
146
|
-
Services.navigation.navigate(
|
220
|
+
Services.navigation.navigate(values.screenJobTypePicker, {
|
147
221
|
currentType: this.state.type,
|
148
222
|
types: this.state.types,
|
149
223
|
onSelectType: this.pickType.bind(this),
|
@@ -159,24 +233,69 @@ class MaintenanceRequest extends Component {
|
|
159
233
|
}
|
160
234
|
|
161
235
|
onConfirmationReset() {
|
162
|
-
this.setState(
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
236
|
+
this.setState(
|
237
|
+
{
|
238
|
+
confirmationToShow: false,
|
239
|
+
title: '',
|
240
|
+
description: '',
|
241
|
+
times: '',
|
242
|
+
isHome: false,
|
243
|
+
uploadingImage: false,
|
244
|
+
images: [
|
245
|
+
{
|
246
|
+
add: true,
|
247
|
+
},
|
248
|
+
],
|
249
|
+
submitting: false,
|
250
|
+
success: false,
|
251
|
+
fail: false,
|
252
|
+
customFields: [],
|
253
|
+
customFieldImages: {},
|
254
|
+
isDateTimePickerVisible: false,
|
255
|
+
popUpType: 'date',
|
256
|
+
dateFieldId: null,
|
257
|
+
imageFieldId: null,
|
258
|
+
},
|
259
|
+
() => this.pickType(this.state.type),
|
260
|
+
);
|
178
261
|
}
|
179
262
|
|
263
|
+
isFieldValid = (field, fieldId) => {
|
264
|
+
const { mandatory, type, answer } = field;
|
265
|
+
if (['staticTitle', 'staticText'].includes(type)) return true;
|
266
|
+
|
267
|
+
const imagesList = type === 'image' ? this.getImageUrls(fieldId) : [];
|
268
|
+
const checkMandatory = () => {
|
269
|
+
if (!mandatory) return true;
|
270
|
+
switch (type) {
|
271
|
+
case 'yn':
|
272
|
+
return _.isBoolean(answer);
|
273
|
+
case 'image':
|
274
|
+
return imagesList.length > 0;
|
275
|
+
case 'checkbox':
|
276
|
+
return _.isArray(answer) && answer.length > 0;
|
277
|
+
default:
|
278
|
+
return !_.isNil(answer) && !_.isEmpty(answer);
|
279
|
+
}
|
280
|
+
};
|
281
|
+
const checkFormat = () => {
|
282
|
+
if (_.isNil(answer) || _.isEmpty(answer)) return true;
|
283
|
+
switch (type) {
|
284
|
+
case 'email':
|
285
|
+
return Helper.isEmail(answer);
|
286
|
+
case 'date':
|
287
|
+
return moment(answer, 'YYYY-MM-DD', true).isValid();
|
288
|
+
case 'time':
|
289
|
+
return moment(answer, 'HH:mm', true).isValid();
|
290
|
+
default:
|
291
|
+
return true;
|
292
|
+
}
|
293
|
+
};
|
294
|
+
|
295
|
+
const valid = checkMandatory() && checkFormat();
|
296
|
+
return valid;
|
297
|
+
};
|
298
|
+
|
180
299
|
getJobTypes() {
|
181
300
|
const self = this;
|
182
301
|
maintenanceActions
|
@@ -190,21 +309,29 @@ class MaintenanceRequest extends Component {
|
|
190
309
|
.catch(() => {});
|
191
310
|
}
|
192
311
|
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
312
|
+
pickType(type) {
|
313
|
+
const { types } = this.state;
|
314
|
+
const selected = types.find(t => t.typeName === type) || {};
|
315
|
+
this.setState({
|
316
|
+
type,
|
317
|
+
customFields: selected.hasCustomFields && selected.customFields.length > 0 ? _.cloneDeep(selected.customFields) : [],
|
318
|
+
});
|
197
319
|
}
|
198
320
|
|
199
|
-
|
200
|
-
|
321
|
+
getDefaultJob() {
|
322
|
+
const { types, jobId } = this.state;
|
323
|
+
if (types.length !== 0 && jobId == null) {
|
324
|
+
const defaultType = types[0];
|
325
|
+
this.pickType(defaultType.typeName);
|
326
|
+
}
|
201
327
|
}
|
202
328
|
|
203
|
-
showUploadMenu =
|
329
|
+
showUploadMenu = fieldId => {
|
204
330
|
Keyboard.dismiss();
|
205
331
|
if (this.state.uploadingImage || this.state.submitting) {
|
206
332
|
return;
|
207
333
|
}
|
334
|
+
if (fieldId) this.setState({ imageFieldId: fieldId });
|
208
335
|
this.imageUploader.showUploadMenu();
|
209
336
|
};
|
210
337
|
|
@@ -221,10 +348,13 @@ class MaintenanceRequest extends Component {
|
|
221
348
|
this.scrollContainer.scrollTo({ y: 0 });
|
222
349
|
}, 100);
|
223
350
|
|
224
|
-
const images =
|
225
|
-
|
226
|
-
|
227
|
-
|
351
|
+
const images = this.getImageUrls();
|
352
|
+
|
353
|
+
// Fix custom images field answers
|
354
|
+
const customFields = _.cloneDeep(this.state.customFields);
|
355
|
+
const updatedCustomFields = customFields.map((field, fieldId) => {
|
356
|
+
if (field.type === 'image') field.answer = this.getImageUrls(fieldId);
|
357
|
+
return field;
|
228
358
|
});
|
229
359
|
|
230
360
|
maintenanceActions
|
@@ -241,6 +371,7 @@ class MaintenanceRequest extends Component {
|
|
241
371
|
Helper.getSite(this.props.site),
|
242
372
|
this.state.isHome,
|
243
373
|
this.state.times,
|
374
|
+
updatedCustomFields,
|
244
375
|
)
|
245
376
|
.then(res => {
|
246
377
|
if (res.data.success) {
|
@@ -269,45 +400,98 @@ class MaintenanceRequest extends Component {
|
|
269
400
|
refreshRequest = async id => {
|
270
401
|
try {
|
271
402
|
const job = await maintenanceActions.getJob(Helper.getSite(this.props.site), id);
|
272
|
-
// console.log('refreshRequest', job?.data);
|
273
403
|
this.props.jobAdded(job.data);
|
274
404
|
} catch (error) {
|
275
405
|
console.log('refreshRequest error', error);
|
276
406
|
}
|
277
407
|
};
|
278
408
|
|
409
|
+
validateCustomFields = () => {
|
410
|
+
const { customFields } = this.state;
|
411
|
+
if (!customFields || customFields.length === 0) return true;
|
412
|
+
|
413
|
+
return customFields.every((field, index) => {
|
414
|
+
const isValid = this.isFieldValid(field, index);
|
415
|
+
return isValid;
|
416
|
+
});
|
417
|
+
};
|
418
|
+
|
279
419
|
submitRequest() {
|
280
|
-
|
420
|
+
const { customFields, submitting, uploadingImage, title, roomNumber, isHome, times } = this.state;
|
421
|
+
const hasCustomFields = customFields && customFields.length > 0;
|
422
|
+
|
423
|
+
if (submitting || !this.props.connected) {
|
281
424
|
if (!this.props.connected) {
|
282
425
|
this.setState({ error: { message: 'No internet connection detected' } });
|
283
426
|
}
|
284
427
|
return;
|
285
428
|
}
|
286
|
-
if (
|
287
|
-
|
288
|
-
}
|
429
|
+
if (uploadingImage) return;
|
430
|
+
|
289
431
|
this.setState({ error: null, showError: false });
|
290
|
-
if (
|
432
|
+
if (title.length === 0 || !roomNumber || roomNumber.length === 0) {
|
433
|
+
console.log('submitRequest - error', { title, roomNumber });
|
291
434
|
this.setState({ showError: true });
|
292
435
|
return;
|
293
436
|
}
|
294
|
-
if (
|
295
|
-
this.
|
296
|
-
|
437
|
+
if (hasCustomFields) {
|
438
|
+
if (!this.validateCustomFields()) {
|
439
|
+
console.log('submitRequest - custom fields error');
|
440
|
+
this.setState({ showError: true });
|
441
|
+
return;
|
442
|
+
}
|
443
|
+
} else {
|
444
|
+
if (isHome && times.length < 2) {
|
445
|
+
console.log('submitRequest - error', { isHome, times });
|
446
|
+
this.setState({ showError: true });
|
447
|
+
return;
|
448
|
+
}
|
297
449
|
}
|
298
450
|
this.submit();
|
299
451
|
}
|
300
452
|
|
453
|
+
getImages = (fieldId = null) => {
|
454
|
+
const { images, customFieldImages } = this.state;
|
455
|
+
const imagesList = _.cloneDeep(fieldId ? customFieldImages[fieldId] : images);
|
456
|
+
if (!imagesList || !Array.isArray(imagesList) || imagesList.length === 0) {
|
457
|
+
return [{ add: true }];
|
458
|
+
}
|
459
|
+
return imagesList;
|
460
|
+
};
|
461
|
+
|
462
|
+
setImages = (imagesList, fieldId = null, callback = null) => {
|
463
|
+
let update = {};
|
464
|
+
if (fieldId) {
|
465
|
+
const customFieldImages = _.cloneDeep(this.state.customFieldImages);
|
466
|
+
customFieldImages[fieldId] = imagesList;
|
467
|
+
update = { customFieldImages };
|
468
|
+
} else {
|
469
|
+
update = { images: imagesList };
|
470
|
+
}
|
471
|
+
this.setState(update, callback);
|
472
|
+
};
|
473
|
+
|
474
|
+
getImageUrls = (fieldId = null) => {
|
475
|
+
const imagesList = this.getImages(fieldId);
|
476
|
+
return _.filter(imagesList, img => {
|
477
|
+
return !img.uploading && !img.add;
|
478
|
+
}).map(img => {
|
479
|
+
return img.url;
|
480
|
+
});
|
481
|
+
};
|
482
|
+
|
301
483
|
waitForThumbnails = () => {
|
302
484
|
if (this.checkThumb) return;
|
303
485
|
|
304
486
|
this.checkThumb = setInterval(async () => {
|
305
|
-
const
|
487
|
+
const { imageFieldId } = this.state;
|
488
|
+
const imagesList = this.getImages(imageFieldId);
|
489
|
+
const imagesUpdate = [];
|
306
490
|
await Promise.all(
|
307
|
-
|
491
|
+
imagesList.map(image => {
|
308
492
|
return new Promise(async resolve => {
|
309
493
|
const newImage = { ...image };
|
310
|
-
|
494
|
+
imagesUpdate.push(newImage);
|
311
495
|
if (newImage.url && !newImage.thumbNailExists) {
|
312
496
|
newImage.uploading = false;
|
313
497
|
newImage.allowRetry = false;
|
@@ -318,20 +502,20 @@ class MaintenanceRequest extends Component {
|
|
318
502
|
});
|
319
503
|
}),
|
320
504
|
);
|
321
|
-
const thumbnailsExist =
|
505
|
+
const thumbnailsExist = imagesUpdate.every(image => !image.url || image.thumbNailExists);
|
322
506
|
if (thumbnailsExist) {
|
323
507
|
clearInterval(this.checkThumb);
|
324
508
|
this.checkThumb = null;
|
325
|
-
this.
|
509
|
+
this.setImages(imagesUpdate, imageFieldId);
|
326
510
|
}
|
327
511
|
}, 2000);
|
328
512
|
};
|
329
513
|
|
330
|
-
removeImage = index => {
|
331
|
-
const
|
332
|
-
|
514
|
+
removeImage = (index, fieldId) => {
|
515
|
+
const imagesUpdate = this.getImages(fieldId);
|
516
|
+
imagesUpdate.splice(index, 1);
|
333
517
|
|
334
|
-
this.
|
518
|
+
this.setImages(imagesUpdate, fieldId);
|
335
519
|
};
|
336
520
|
|
337
521
|
toggleFullscreenVideo = url => {
|
@@ -360,16 +544,17 @@ class MaintenanceRequest extends Component {
|
|
360
544
|
);
|
361
545
|
}
|
362
546
|
|
363
|
-
renderImage(item, index) {
|
547
|
+
renderImage(item, index, fieldId = null) {
|
364
548
|
const isVideoUrl = Helper.isVideo(item.url);
|
549
|
+
const imagesList = this.getImages(fieldId);
|
365
550
|
|
366
551
|
if (item.add) {
|
367
552
|
return (
|
368
|
-
<TouchableOpacity activeOpacity={0.8} onPress={this.showUploadMenu}>
|
553
|
+
<TouchableOpacity activeOpacity={0.8} onPress={() => this.showUploadMenu(fieldId)}>
|
369
554
|
<View
|
370
555
|
style={[
|
371
556
|
styles.imageContainer,
|
372
|
-
|
557
|
+
imagesList.length > 1 && styles.imageContainerNotEmpty,
|
373
558
|
index % 3 === 0 && { marginLeft: 0 },
|
374
559
|
index > 2 && { marginTop: 8 },
|
375
560
|
]}
|
@@ -385,7 +570,7 @@ class MaintenanceRequest extends Component {
|
|
385
570
|
<View
|
386
571
|
style={[
|
387
572
|
styles.imageContainer,
|
388
|
-
|
573
|
+
imagesList.length > 1 && styles.imageContainerNotEmpty,
|
389
574
|
index % 3 === 0 && { marginLeft: 0 },
|
390
575
|
index > 2 && { marginTop: 8 },
|
391
576
|
]}
|
@@ -404,7 +589,7 @@ class MaintenanceRequest extends Component {
|
|
404
589
|
</TouchableOpacity>
|
405
590
|
</View>
|
406
591
|
)}
|
407
|
-
<TouchableOpacity style={styles.removeImage} onPress={this.removeImage
|
592
|
+
<TouchableOpacity style={styles.removeImage} onPress={() => this.removeImage(index, fieldId)}>
|
408
593
|
<Icon name="remove" type="font-awesome" iconStyle={styles.imageControlIcon} style={styles.removeImage} />
|
409
594
|
</TouchableOpacity>
|
410
595
|
</ImageBackground>
|
@@ -421,15 +606,16 @@ class MaintenanceRequest extends Component {
|
|
421
606
|
);
|
422
607
|
}
|
423
608
|
|
424
|
-
renderImageList() {
|
609
|
+
renderImageList(fieldId = null) {
|
610
|
+
const imagesList = this.getImages(fieldId);
|
425
611
|
return (
|
426
612
|
<View style={styles.imageSection}>
|
427
|
-
<View style={[styles.imageListContainer,
|
613
|
+
<View style={[styles.imageListContainer, imagesList.length < 2 && styles.imageListContainerEmpty]}>
|
428
614
|
<FlatList
|
429
615
|
keyboardShouldPersistTaps="always"
|
430
616
|
enableEmptySections
|
431
|
-
data={
|
432
|
-
renderItem={({ item, index }) => this.renderImage(item, index)}
|
617
|
+
data={imagesList}
|
618
|
+
renderItem={({ item, index }) => this.renderImage(item, index, fieldId)}
|
433
619
|
keyExtractor={(item, index) => index}
|
434
620
|
numColumns={3}
|
435
621
|
/>
|
@@ -438,7 +624,181 @@ class MaintenanceRequest extends Component {
|
|
438
624
|
);
|
439
625
|
}
|
440
626
|
|
627
|
+
renderDateField(field, fieldId, sectionStyle) {
|
628
|
+
let displayText, placeHolder, icon, errorText;
|
629
|
+
if (field.type === 'date') {
|
630
|
+
displayText = field.answer ? moment(field.answer, 'YYYY-MM-DD').format('DD MMM YYYY') : '';
|
631
|
+
placeHolder = 'dd mmm yyyy';
|
632
|
+
icon = 'calendar';
|
633
|
+
errorText = 'Not a valid date';
|
634
|
+
} else {
|
635
|
+
displayText = field.answer ? moment(field.answer, 'HH:mm').format('h:mm a') : '';
|
636
|
+
placeHolder = '--:-- --';
|
637
|
+
icon = 'clock-o';
|
638
|
+
errorText = 'Not a valid time';
|
639
|
+
}
|
640
|
+
|
641
|
+
return (
|
642
|
+
<Components.GenericInputSection
|
643
|
+
key={fieldId}
|
644
|
+
label={field.label}
|
645
|
+
sectionStyle={sectionStyle}
|
646
|
+
isValid={() => this.isFieldValid(field, fieldId)}
|
647
|
+
required={field.mandatory}
|
648
|
+
showError={this.state.showError}
|
649
|
+
errorText={errorText}
|
650
|
+
>
|
651
|
+
<View style={styles.dateContainer}>
|
652
|
+
<TouchableOpacity style={styles.dateFieldButton} onPress={() => this.onOpenDatePicker(field, fieldId)}>
|
653
|
+
<View style={styles.dateFieldContainer}>
|
654
|
+
<Text style={styles.dateText}>{displayText || placeHolder}</Text>
|
655
|
+
<Icon type="font-awesome" name={icon} iconStyle={styles.dateIcon} />
|
656
|
+
</View>
|
657
|
+
</TouchableOpacity>
|
658
|
+
{displayText ? (
|
659
|
+
<TouchableOpacity style={styles.dateClearButton} onPress={() => this.onClearDate(fieldId)}>
|
660
|
+
<Icon type="font-awesome" name="times" iconStyle={styles.removeIcon} />
|
661
|
+
</TouchableOpacity>
|
662
|
+
) : null}
|
663
|
+
</View>
|
664
|
+
</Components.GenericInputSection>
|
665
|
+
);
|
666
|
+
}
|
667
|
+
|
668
|
+
renderField(field, fieldId) {
|
669
|
+
const sectionStyle = { marginTop: fieldId === 0 ? 24 : 0, marginBottom: 24 };
|
670
|
+
switch (field.type) {
|
671
|
+
case 'yn':
|
672
|
+
return (
|
673
|
+
<Components.GenericInputSection
|
674
|
+
key={fieldId}
|
675
|
+
label={field.label}
|
676
|
+
sectionStyle={sectionStyle}
|
677
|
+
inputType="toggle"
|
678
|
+
value={field.answer}
|
679
|
+
onChange={answer => this.onChangeToggleAnswer(fieldId, answer)}
|
680
|
+
isValid={() => this.isFieldValid(field, fieldId)}
|
681
|
+
showError={this.state.showError}
|
682
|
+
required={field.mandatory}
|
683
|
+
/>
|
684
|
+
);
|
685
|
+
case 'multichoice':
|
686
|
+
return (
|
687
|
+
<Components.GenericInputSection
|
688
|
+
key={fieldId}
|
689
|
+
label={field.label}
|
690
|
+
sectionStyle={sectionStyle}
|
691
|
+
inputType="radio"
|
692
|
+
value={field.answer}
|
693
|
+
onChange={answer => this.onChangeToggleAnswer(fieldId, answer)}
|
694
|
+
options={field.values.map(o => {
|
695
|
+
return {
|
696
|
+
Label: o,
|
697
|
+
Value: o,
|
698
|
+
};
|
699
|
+
})}
|
700
|
+
isValid={() => this.isFieldValid(field, fieldId)}
|
701
|
+
showError={this.state.showError}
|
702
|
+
required={field.mandatory}
|
703
|
+
/>
|
704
|
+
);
|
705
|
+
case 'checkbox':
|
706
|
+
return (
|
707
|
+
<Components.GenericInputSection
|
708
|
+
key={fieldId}
|
709
|
+
label={field.label}
|
710
|
+
sectionStyle={sectionStyle}
|
711
|
+
isValid={() => this.isFieldValid(field, fieldId)}
|
712
|
+
showError={this.state.showError}
|
713
|
+
required={field.mandatory}
|
714
|
+
>
|
715
|
+
{field.values.map((o, i) => {
|
716
|
+
const isActive = field.answer && _.includes(field.answer, o);
|
717
|
+
return (
|
718
|
+
<TouchableOpacity
|
719
|
+
onPress={() => this.onChangeCheckboxAnswer(fieldId, o)}
|
720
|
+
key={i}
|
721
|
+
style={styles.multiChoiceOption}
|
722
|
+
hitSlop={{ top: 8, left: 8, bottom: 8, right: 8 }}
|
723
|
+
>
|
724
|
+
{isActive ? (
|
725
|
+
<Components.TickIcon style={styles.tick} size={20} color={this.props.colourBrandingMain} />
|
726
|
+
) : (
|
727
|
+
<View style={styles.unticked} />
|
728
|
+
)}
|
729
|
+
<Text style={styles.multiChoiceText}>{o}</Text>
|
730
|
+
</TouchableOpacity>
|
731
|
+
);
|
732
|
+
})}
|
733
|
+
</Components.GenericInputSection>
|
734
|
+
);
|
735
|
+
case 'text':
|
736
|
+
case 'email':
|
737
|
+
case 'phone':
|
738
|
+
return (
|
739
|
+
<Components.GenericInputSection
|
740
|
+
key={fieldId}
|
741
|
+
label={field.label}
|
742
|
+
placeholder={field.placeHolder}
|
743
|
+
value={field.answer}
|
744
|
+
onChangeText={val => this.onChangeAnswer(fieldId, val)}
|
745
|
+
editable
|
746
|
+
squaredCorners
|
747
|
+
isValid={() => this.isFieldValid(field, fieldId)}
|
748
|
+
showError={this.state.showError}
|
749
|
+
errorText={field.type === 'email' ? 'Not a valid email' : undefined}
|
750
|
+
required={field.mandatory}
|
751
|
+
sectionStyle={sectionStyle}
|
752
|
+
autoCapitalize="sentences"
|
753
|
+
keyboardType={this.keyboardTypes[field.type]}
|
754
|
+
/>
|
755
|
+
);
|
756
|
+
case 'staticTitle':
|
757
|
+
return (
|
758
|
+
<Text key={fieldId} style={[styles.staticTitle, { color: this.props.colourBrandingMain }, sectionStyle]}>
|
759
|
+
{field.label}
|
760
|
+
</Text>
|
761
|
+
);
|
762
|
+
case 'staticText':
|
763
|
+
return (
|
764
|
+
<View key={fieldId} style={[styles.staticText, sectionStyle]}>
|
765
|
+
{Helper.toParagraphed(field.label, styles.staticText)}
|
766
|
+
</View>
|
767
|
+
);
|
768
|
+
case 'date':
|
769
|
+
case 'time':
|
770
|
+
return this.renderDateField(field, fieldId, sectionStyle);
|
771
|
+
case 'image':
|
772
|
+
return (
|
773
|
+
<Components.GenericInputSection
|
774
|
+
key={fieldId}
|
775
|
+
label={field.label}
|
776
|
+
sectionStyle={sectionStyle}
|
777
|
+
isValid={() => this.isFieldValid(field, fieldId)}
|
778
|
+
required={field.mandatory}
|
779
|
+
showError={this.state.showError}
|
780
|
+
>
|
781
|
+
{this.renderImageList(fieldId)}
|
782
|
+
</Components.GenericInputSection>
|
783
|
+
);
|
784
|
+
default:
|
785
|
+
return null;
|
786
|
+
}
|
787
|
+
}
|
788
|
+
|
789
|
+
renderCustomFields() {
|
790
|
+
const { customFields } = this.state;
|
791
|
+
if (!customFields || customFields.length === 0) return null;
|
792
|
+
|
793
|
+
return (
|
794
|
+
<Components.FormCard style={{ marginTop: 16 }}>{customFields.map((field, i) => this.renderField(field, i))}</Components.FormCard>
|
795
|
+
);
|
796
|
+
}
|
797
|
+
|
441
798
|
renderForm() {
|
799
|
+
const { customFields } = this.state;
|
800
|
+
const hasCustomFields = customFields && customFields.length > 0;
|
801
|
+
|
442
802
|
return (
|
443
803
|
<View style={{ flex: 1 }}>
|
444
804
|
<ScrollView keyboardShouldPersistTaps="always" style={{ flex: 1 }} ref={ref => (this.scrollContainer = ref)}>
|
@@ -481,7 +841,7 @@ class MaintenanceRequest extends Component {
|
|
481
841
|
}}
|
482
842
|
required
|
483
843
|
errorText="Please provide your address."
|
484
|
-
showError={this.state.showError && this.state.roomNumber
|
844
|
+
showError={this.state.showError && (!this.state.roomNumber || this.state.roomNumber.length < 2)}
|
485
845
|
/>
|
486
846
|
</Components.FormCard>
|
487
847
|
<Components.FormCard
|
@@ -499,81 +859,87 @@ class MaintenanceRequest extends Component {
|
|
499
859
|
</View>
|
500
860
|
</TouchableOpacity>
|
501
861
|
</Components.FormCard>
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
862
|
+
{hasCustomFields ? (
|
863
|
+
this.renderCustomFields()
|
864
|
+
) : (
|
865
|
+
<>
|
866
|
+
<Components.FormCard style={{ marginTop: 16 }}>
|
867
|
+
<Components.FormCardSection
|
868
|
+
label={'Title'}
|
869
|
+
placeholder={'Enter a title for your request'}
|
870
|
+
textValue={this.state.title}
|
871
|
+
onChangeText={title => this.setState({ title })}
|
872
|
+
editable={this.state.submitting === false}
|
873
|
+
hasUnderline
|
874
|
+
isValid={() => {
|
875
|
+
return this.state.title.length > 1;
|
876
|
+
}}
|
877
|
+
required
|
878
|
+
errorText="Please provide a title."
|
879
|
+
showError={this.state.showError && this.state.title.length < 2}
|
880
|
+
autoCorrect
|
881
|
+
multiline
|
882
|
+
autoGrow
|
883
|
+
/>
|
884
|
+
<Components.FormCardSection
|
885
|
+
label={'Description'}
|
886
|
+
placeholder={'Describe your request here in detail'}
|
887
|
+
textValue={this.state.description}
|
888
|
+
onChangeText={description => this.setState({ description })}
|
889
|
+
editable={this.state.submitting === false}
|
890
|
+
hasUnderline
|
891
|
+
autoCorrect
|
892
|
+
multiline
|
893
|
+
autoGrow
|
894
|
+
/>
|
895
|
+
</Components.FormCard>
|
896
|
+
<Components.FormCard style={{ marginTop: 16, paddingHorizontal: 24 }}>
|
897
|
+
<View
|
898
|
+
style={[
|
899
|
+
{
|
900
|
+
width: '100%',
|
901
|
+
paddingVertical: 16,
|
902
|
+
flexDirection: 'row',
|
903
|
+
justifyContent: 'space-between',
|
904
|
+
alignItems: 'center',
|
905
|
+
position: 'relative',
|
906
|
+
},
|
907
|
+
this.state.isHome && { borderBottomWidth: 1, borderBottomColor: Colours.LINEGREY },
|
908
|
+
]}
|
909
|
+
>
|
910
|
+
<Text style={styles.sectionTitle}>{Config.env.strings.MAINTENANCE_HOME}</Text>
|
911
|
+
<Switch
|
912
|
+
value={this.state.isHome}
|
913
|
+
disabled={this.state.submitting}
|
914
|
+
onValueChange={value => this.setState({ isHome: value })}
|
915
|
+
trackColor={{ false: '#ddd', true: this.props.colourBrandingMain }}
|
916
|
+
thumbColor={Platform.OS === 'android' ? '#fff' : null}
|
917
|
+
/>
|
918
|
+
</View>
|
919
|
+
{this.state.isHome && (
|
920
|
+
<Components.FormCardSection
|
921
|
+
label={'Available times'}
|
922
|
+
placeholder={'Describe your available times here in detail.'}
|
923
|
+
textValue={this.state.times}
|
924
|
+
onChangeText={times => this.setState({ times })}
|
925
|
+
editable={this.state.submitting === false}
|
926
|
+
hasUnderline
|
927
|
+
isValid={() => {
|
928
|
+
return this.state.times.length > 1;
|
929
|
+
}}
|
930
|
+
required
|
931
|
+
errorText="Please provide available times."
|
932
|
+
showError={this.state.showError && this.state.isHome && this.state.times.length < 2}
|
933
|
+
minHeight={40}
|
934
|
+
autoCorrect
|
935
|
+
multiline
|
936
|
+
autoGrow
|
937
|
+
/>
|
938
|
+
)}
|
939
|
+
</Components.FormCard>
|
940
|
+
{this.renderImageList()}
|
941
|
+
</>
|
942
|
+
)}
|
577
943
|
</View>
|
578
944
|
</ScrollView>
|
579
945
|
</View>
|
@@ -607,6 +973,8 @@ class MaintenanceRequest extends Component {
|
|
607
973
|
}
|
608
974
|
|
609
975
|
render() {
|
976
|
+
const { submitting, success, isDateTimePickerVisible, popUpType } = this.state;
|
977
|
+
|
610
978
|
return (
|
611
979
|
<KeyboardAvoidingView behavior={Platform.OS === 'ios' && 'padding'} style={styles.viewContainer}>
|
612
980
|
{this.renderUploadMenu()}
|
@@ -614,8 +982,8 @@ class MaintenanceRequest extends Component {
|
|
614
982
|
<Components.Header
|
615
983
|
leftIcon="angle-left"
|
616
984
|
onPressLeft={this.onPressBack.bind(this)}
|
617
|
-
text={
|
618
|
-
rightText={
|
985
|
+
text={this.props.strings[`${values.featureKey}_textFeatureTitle`] || values.textFeatureTitle}
|
986
|
+
rightText={submitting || success ? null : 'Done'}
|
619
987
|
onPressRight={this.submitRequest.bind(this)}
|
620
988
|
absoluteRight
|
621
989
|
/>
|
@@ -623,6 +991,13 @@ class MaintenanceRequest extends Component {
|
|
623
991
|
</View>
|
624
992
|
{this.renderRegisterConfirmation()}
|
625
993
|
{this.renderVideoPlayerPopup()}
|
994
|
+
<DateTimePicker
|
995
|
+
isVisible={isDateTimePickerVisible}
|
996
|
+
onConfirm={this.onDateSelected}
|
997
|
+
onCancel={() => this.setState({ isDateTimePickerVisible: false })}
|
998
|
+
mode={popUpType}
|
999
|
+
headerTextIOS={`Pick a ${popUpType}`}
|
1000
|
+
/>
|
626
1001
|
</KeyboardAvoidingView>
|
627
1002
|
);
|
628
1003
|
}
|
@@ -725,6 +1100,72 @@ const styles = {
|
|
725
1100
|
alignItems: 'center',
|
726
1101
|
justifyContent: 'center',
|
727
1102
|
},
|
1103
|
+
staticTitle: {
|
1104
|
+
fontSize: 20,
|
1105
|
+
fontFamily: 'sf-semibold',
|
1106
|
+
color: Colours.TEXT_DARKEST,
|
1107
|
+
},
|
1108
|
+
staticText: {
|
1109
|
+
fontSize: 17,
|
1110
|
+
fontFamily: 'sf-regular',
|
1111
|
+
color: Colours.TEXT_DARKEST,
|
1112
|
+
lineHeight: 24,
|
1113
|
+
},
|
1114
|
+
multiChoiceOption: {
|
1115
|
+
marginTop: 16,
|
1116
|
+
flexDirection: 'row',
|
1117
|
+
alignItems: 'center',
|
1118
|
+
minHeight: 20,
|
1119
|
+
},
|
1120
|
+
multiChoiceText: {
|
1121
|
+
flex: 1,
|
1122
|
+
fontFamily: 'sf-medium',
|
1123
|
+
fontSize: 14,
|
1124
|
+
color: Colours.TEXT_DARK,
|
1125
|
+
},
|
1126
|
+
tick: {
|
1127
|
+
marginRight: 10,
|
1128
|
+
borderRadius: 4,
|
1129
|
+
},
|
1130
|
+
unticked: {
|
1131
|
+
marginRight: 10,
|
1132
|
+
width: 20,
|
1133
|
+
height: 20,
|
1134
|
+
borderColor: Colours.LINEGREY,
|
1135
|
+
borderWidth: 1,
|
1136
|
+
borderRadius: 4,
|
1137
|
+
},
|
1138
|
+
dateContainer: {
|
1139
|
+
flexDirection: 'row',
|
1140
|
+
alignItems: 'center',
|
1141
|
+
},
|
1142
|
+
dateFieldButton: {
|
1143
|
+
flex: 1,
|
1144
|
+
},
|
1145
|
+
dateFieldContainer: {
|
1146
|
+
flexDirection: 'row',
|
1147
|
+
borderRadius: 2,
|
1148
|
+
backgroundColor: '#ebeff2',
|
1149
|
+
padding: 8,
|
1150
|
+
marginTop: 8,
|
1151
|
+
},
|
1152
|
+
dateText: {
|
1153
|
+
flex: 1,
|
1154
|
+
fontFamily: 'sf-regular',
|
1155
|
+
fontSize: 16,
|
1156
|
+
color: '#65686D',
|
1157
|
+
},
|
1158
|
+
dateIcon: {
|
1159
|
+
fontSize: 18,
|
1160
|
+
color: Colours.TEXT_BLUEGREY,
|
1161
|
+
},
|
1162
|
+
dateClearButton: {
|
1163
|
+
paddingLeft: 12,
|
1164
|
+
},
|
1165
|
+
removeIcon: {
|
1166
|
+
fontSize: 26,
|
1167
|
+
color: Colours.TEXT_BLUEGREY,
|
1168
|
+
},
|
728
1169
|
};
|
729
1170
|
|
730
1171
|
const mapStateToProps = state => {
|
@@ -740,6 +1181,7 @@ const mapStateToProps = state => {
|
|
740
1181
|
unit,
|
741
1182
|
phoneNumber,
|
742
1183
|
colourBrandingMain: Colours.getMainBrandingColourFromState(state),
|
1184
|
+
strings: state.strings?.config || {},
|
743
1185
|
};
|
744
1186
|
};
|
745
1187
|
|