@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,915 @@
1
+ import React, { Component } from 'react';
2
+ import { ScrollView, View, StyleSheet, Text, KeyboardAvoidingView, TouchableOpacity, ImageBackground, Platform } from 'react-native';
3
+ import DateTimePicker from 'react-native-modal-datetime-picker';
4
+ import { Icon } from 'react-native-elements';
5
+ import _ from 'lodash';
6
+ import moment from 'moment';
7
+ import { connect } from 'react-redux';
8
+ import { maintenanceActions } from '../apis';
9
+ import { jobAdded } from '../actions';
10
+ import StatusSelectorPopup from '../components/StatusSelectorPopup';
11
+ import { jobStatusOptions, getJobStatusProps } from '../helper';
12
+ import { Services } from '../feature.config';
13
+ import { Colours, Helper, Components, Config } from '../core.config';
14
+ import { values } from '../values.config';
15
+
16
+ class RequestDetail extends Component {
17
+ constructor(props) {
18
+ super(props);
19
+
20
+ this.state = {
21
+ job: {},
22
+ isDateTimePickerVisible: false,
23
+ popUpType: '',
24
+ status: '',
25
+ expectedDate: null,
26
+ expectedDateText: '',
27
+ seen: false,
28
+ showMore: true,
29
+ showStatusPopup: false,
30
+ loading: false,
31
+ showFullscreenVideo: false,
32
+ currentVideoUrl: '',
33
+ galleryOpen: false,
34
+ galleryImages: [],
35
+ showMessages: false,
36
+ assignees: [],
37
+ };
38
+
39
+ this.scrollView = React.createRef();
40
+ this.commentReply = React.createRef();
41
+ this.commentSection = React.createRef();
42
+ }
43
+
44
+ componentDidMount() {
45
+ this.getJob();
46
+ this.updateJobState(this.props.job);
47
+ this.getAssignees();
48
+ }
49
+
50
+ getJob = async () => {
51
+ console.log('getting job');
52
+ this.setState({ loading: true }, async () => {
53
+ try {
54
+ const res = await maintenanceActions.getJob(this.props.job.site, this.props.job.id);
55
+ this.props.jobAdded(res.data);
56
+ this.updateJobState(res.data);
57
+ } catch (error) {
58
+ console.log('getJob error', error.toString());
59
+ // check for 403 or 404 error
60
+ if (error.response.status === 403 || error.response.status === 404) {
61
+ this.setState({
62
+ forbidden: true,
63
+ });
64
+ }
65
+ console.log('getJob error', error);
66
+ } finally {
67
+ this.setState({ loading: false });
68
+ }
69
+ });
70
+ };
71
+
72
+ getAssignees = async () => {
73
+ if (!this.hasPermission()) return;
74
+ try {
75
+ const res = await maintenanceActions.getAssignees(this.props.user.site);
76
+ this.setState({ assignees: res.data.Users });
77
+ } catch (error) {
78
+ console.log('getAssignees error', error);
79
+ }
80
+ };
81
+
82
+ updateJobState(defaultJob) {
83
+ const job = _.find(this.props.jobs, j => j.id === this.props.job.id) || defaultJob;
84
+ if (!job) {
85
+ this.getJob();
86
+ return;
87
+ }
88
+ const newState = { job, status: job.status };
89
+ if (job.expectedDate) {
90
+ newState.expectedDate = moment(job.expectedDate);
91
+ newState.expectedDateText = newState.expectedDate.format('DD/MM/YYYY');
92
+ }
93
+ if (job.seen) newState.seen = job.seen;
94
+ this.setState(newState, () => {
95
+ this.markSeen();
96
+ });
97
+ }
98
+
99
+ markSeen = () => {
100
+ const { user } = this.props;
101
+ const { job } = this.state;
102
+ // Must have maintenance permission and not the requester
103
+ // console.log('markSeen check', job.userID, user.Id);
104
+ if (job.seen === true) return;
105
+ if (!this.hasPermission()) return;
106
+ if (user.Id === job.userID) return;
107
+
108
+ this.setState({ loading: true }, async () => {
109
+ try {
110
+ const updated = { id: job.id, seen: true, status: job.status || 'Unassigned' };
111
+ const res = await maintenanceActions.editJob(updated, user.site);
112
+ // console.log('markSeen updated');
113
+ this.props.jobAdded(res.data.job);
114
+ this.getJob();
115
+ this.setState({ loading: false, seen: true });
116
+ } catch (error) {
117
+ console.log('markSeen error', error);
118
+ this.setState({ loading: false });
119
+ }
120
+ });
121
+ };
122
+
123
+ updateJob = () => {
124
+ this.setState({ loading: true }, async () => {
125
+ const { user } = this.props;
126
+ const { job } = this.state;
127
+ try {
128
+ const updated = { id: job.id };
129
+ if (this.state.expectedDate) {
130
+ updated.expectedDate = moment(this.state.expectedDate)
131
+ .utc()
132
+ .toISOString();
133
+ }
134
+ const res = await maintenanceActions.editJob(updated, user.site);
135
+ this.props.jobAdded(res.data.job);
136
+ this.getJob();
137
+ } catch (error) {
138
+ console.log('updateJob error', error);
139
+ } finally {
140
+ this.setState({ loading: false });
141
+ }
142
+ });
143
+ };
144
+
145
+ updateJobStatus = () => {
146
+ this.setState({ loading: true }, async () => {
147
+ try {
148
+ const res = await maintenanceActions.editJobStatus(this.props.job.id, this.state.status);
149
+ this.props.jobAdded(res.data.job);
150
+ this.getJob();
151
+ } catch (error) {
152
+ console.log('updateJobStatus error', error);
153
+ } finally {
154
+ this.setState({ loading: false });
155
+ }
156
+ });
157
+ };
158
+
159
+ onPressBack = () => {
160
+ Services.navigation.goBack();
161
+ };
162
+
163
+ onOpenStatusPicker = () => {
164
+ this.setState({ showStatusPopup: true });
165
+ };
166
+
167
+ onCloseStatusPopup = () => {
168
+ this.setState({ showStatusPopup: false });
169
+ };
170
+
171
+ onSelectStatus = status => {
172
+ this.setState({ status, showStatusPopup: false }, () => {
173
+ this.updateJobStatus();
174
+ });
175
+ };
176
+
177
+ openStaffNotes = () => {
178
+ Services.navigation.navigate(values.screenRequestNotes, { job: this.state.job, onChange: this.getJob });
179
+ };
180
+
181
+ onOpenDatePicker = () => {
182
+ this.setState({ popUpType: 'date', isDateTimePickerVisible: true });
183
+ };
184
+
185
+ onCloseDatePicker = () => {
186
+ this.setState({ isDateTimePickerVisible: false });
187
+ };
188
+
189
+ onDateSelected = date => {
190
+ if (this.state.popUpType === 'date') {
191
+ date = moment(date);
192
+ this.setState(
193
+ {
194
+ expectedDate: date,
195
+ expectedDateText: date.format('DD/MM/YYYY'),
196
+ isDateTimePickerVisible: false,
197
+ },
198
+ () => {
199
+ this.updateJob();
200
+ },
201
+ );
202
+ }
203
+ };
204
+
205
+ onToggleDetails = () => {
206
+ this.setState({ showMore: !this.state.showMore });
207
+ };
208
+
209
+ onLeaveMessage = () => {
210
+ this.setState({ showMessages: true });
211
+ };
212
+
213
+ onCommentsLoaded = count => {
214
+ if (count > 0) {
215
+ this.setState({ showMessages: true });
216
+ }
217
+ };
218
+
219
+ onCommentAdded = () => {
220
+ this.setState({ loading: true }, async () => {
221
+ try {
222
+ const job = await maintenanceActions.getJob(this.props.user.site, this.props.job.id);
223
+ // console.log('onCommentAdded', job?.data);
224
+ this.props.jobAdded(job.data);
225
+ this.getJob();
226
+ } catch (error) {
227
+ console.log('onCommentAdded error', error);
228
+ } finally {
229
+ this.setState({ loading: false });
230
+ }
231
+ });
232
+ };
233
+
234
+ hasPermission = () => {
235
+ const { job } = this.state;
236
+ const { permissions } = this.props.user;
237
+ if (_.includes(permissions, values.permissionMaintenanceTracking)) {
238
+ return true;
239
+ }
240
+ if (_.includes(permissions, values.permissionMaintenanceAssign)) {
241
+ return job.AssigneeId === this.props.user.Id;
242
+ }
243
+ return false;
244
+ };
245
+
246
+ toggleFullscreenVideo = url => {
247
+ if (typeof url !== 'string') url = '';
248
+ this.setState({ showFullscreenVideo: url.length > 0, currentVideoUrl: url });
249
+ };
250
+
251
+ openGallery(galleryImages, index) {
252
+ this.setState({
253
+ galleryOpen: true,
254
+ galleryImages,
255
+ });
256
+ this.refs.imagePopup.scrollTo(index);
257
+ }
258
+
259
+ closeGallery() {
260
+ this.setState({
261
+ galleryOpen: false,
262
+ });
263
+ }
264
+
265
+ onOpenAssigneePicker = () => {
266
+ const options = this.state.assignees.map(a => {
267
+ return { key: a.id, name: a.displayName };
268
+ });
269
+ Services.navigation.navigate('optionSelector', {
270
+ options,
271
+ selection: this.state.job.AssigneeId,
272
+ title: 'Assign request',
273
+ onSelect: this.onSelectAssignee,
274
+ });
275
+ };
276
+
277
+ onSelectAssignee = assignee => {
278
+ this.setState({ loading: true }, async () => {
279
+ try {
280
+ console.log('onSelectAssignee', this.props.job.id, assignee.key);
281
+ const res = await maintenanceActions.assignJob(this.props.job.id, assignee.key);
282
+ this.props.jobAdded(res.data.job);
283
+ this.getJob();
284
+ } catch (error) {
285
+ console.log('onSelectAssignee error', error);
286
+ } finally {
287
+ this.setState({ loading: false });
288
+ }
289
+ });
290
+ };
291
+
292
+ renderLoading() {
293
+ return <Components.LoadingIndicator visible={this.state.loading} />;
294
+ }
295
+
296
+ renderTop() {
297
+ const { status, job } = this.state;
298
+ const { statusText, statusColor } = getJobStatusProps(status);
299
+ const canEdit = this.hasPermission();
300
+ const showSeen = !status || status === jobStatusOptions[0].name;
301
+
302
+ return (
303
+ <View style={{ ...Helper.getShadowStyle() }}>
304
+ <View style={styles.jobTitleContainer}>
305
+ {job.jobId ? (
306
+ <Text style={[styles.jobIdText, { color: this.props.colourBrandingMain }]}>{`${values.textEntityName} #${job.jobId}`}</Text>
307
+ ) : null}
308
+ <Text style={styles.jobTitleText}>{job.title}</Text>
309
+ <View style={styles.jobTypeSeenContainer}>
310
+ <View style={[styles.jobTypeContainer, { backgroundColor: Colours.hexToRGBAstring(this.props.colourBrandingMain, 0.2) }]}>
311
+ <Text style={[styles.jobTypeText, { color: this.props.colourBrandingMain }]} numberOfLines={2}>
312
+ {job.type}
313
+ </Text>
314
+ </View>
315
+ {showSeen && this.state.seen && (
316
+ <View style={styles.jobSeenContainer}>
317
+ <Icon name="check" type="font-awesome" iconStyle={[styles.jobSeenIcon, { color: this.props.colourBrandingMain }]} />
318
+ <Text style={[styles.jobSeenText, { color: this.props.colourBrandingMain }]}>Seen</Text>
319
+ </View>
320
+ )}
321
+ </View>
322
+
323
+ {job.lastActivityUnix && (
324
+ <View style={styles.textSectionInner}>
325
+ <Text style={styles.textSectionLabel}>Last Updated On</Text>
326
+ <View style={styles.textSectionTextContainer}>
327
+ <Text style={styles.textSectionText}>{moment(job.lastActivityUnix).format('ddd D MMMM, h:mm A')}</Text>
328
+ </View>
329
+ </View>
330
+ )}
331
+ {/* <View style={styles.textSectionInner}>
332
+ <Text style={styles.textSectionLabel}>Expected Date</Text>
333
+ <TouchableOpacity onPress={this.onOpenDatePicker}>
334
+ <View style={styles.textSectionTextContainer}>
335
+ <Text style={styles.textSectionText}>{this.state.expectedDateText || 'Select Date'}</Text>
336
+ <Icon
337
+ name="angle-right"
338
+ type="font-awesome"
339
+ iconStyle={[styles.textSectionAngleRight, { color: this.props.colourBrandingMain }]}
340
+ />
341
+ </View>
342
+ </TouchableOpacity>
343
+ </View> */}
344
+ </View>
345
+ <View style={styles.jobStatusExpectedContainer}>
346
+ <View style={styles.jobStatusOuterContainer}>
347
+ <Text style={styles.jobStatusHeading}>STATUS</Text>
348
+ <TouchableOpacity onPress={canEdit ? this.onOpenStatusPicker : null}>
349
+ <View style={[styles.jobStatusContainer, { backgroundColor: statusColor }]}>
350
+ <Text style={styles.jobStatusText}>{statusText}</Text>
351
+ </View>
352
+ </TouchableOpacity>
353
+ </View>
354
+ {this.hasPermission() && (
355
+ <View style={styles.jobStatusOuterContainer}>
356
+ <Text style={styles.jobStatusHeading}>STAFF NOTES</Text>
357
+ <TouchableOpacity onPress={this.openStaffNotes}>
358
+ <View style={[styles.jobStatusContainer, { backgroundColor: this.props.colourBrandingMain }]}>
359
+ <Icon name="pencil-square-o" type="font-awesome" iconStyle={styles.jobStatusIcon} />
360
+ <Text style={styles.jobStatusText}>Notes ({(job.Notes || []).length})</Text>
361
+ </View>
362
+ </TouchableOpacity>
363
+ </View>
364
+ )}
365
+ </View>
366
+ </View>
367
+ );
368
+ }
369
+
370
+ renderPlayableImageUrl(images, index, containerStyle, showMore) {
371
+ const url = images[index || 0];
372
+ const thumbUrl = Helper.getThumb300(url);
373
+
374
+ if (Helper.isVideo(url)) {
375
+ return (
376
+ <ImageBackground style={[{ flex: 1 }, containerStyle]} source={{ uri: thumbUrl }}>
377
+ <View style={styles.imagePlayContainer}>
378
+ <TouchableOpacity onPress={this.toggleFullscreenVideo.bind(this, url)}>
379
+ <Icon name="play" type="font-awesome" iconStyle={styles.imageControlIcon} />
380
+ </TouchableOpacity>
381
+ </View>
382
+ </ImageBackground>
383
+ );
384
+ }
385
+
386
+ const imageUrl = Helper.get1400(url);
387
+ return (
388
+ <TouchableOpacity style={containerStyle} onPress={this.openGallery.bind(this, images, index || 0)}>
389
+ <ImageBackground style={styles.imageContainer} source={{ uri: imageUrl }}>
390
+ {showMore && <Text style={styles.plusImages}>+{this.state.job.images.length - 2}</Text>}
391
+ </ImageBackground>
392
+ </TouchableOpacity>
393
+ );
394
+ }
395
+
396
+ renderPlayableImage(images, index, containerStyle, showMore) {
397
+ return this.renderPlayableImageUrl(images, index, containerStyle, showMore);
398
+ }
399
+
400
+ renderImage(images, image = null) {
401
+ if (!_.isNil(images) && !_.isEmpty(images)) {
402
+ if (images.length >= 2) {
403
+ return (
404
+ <View style={styles.sideBySideImages}>
405
+ {this.renderPlayableImage(images, 0, styles.sideBySideImageContainer)}
406
+ {this.renderPlayableImage(images, 1, styles.sideBySideImageContainer, images.length > 2)}
407
+ </View>
408
+ );
409
+ } else {
410
+ return <View style={styles.singleImageContainer}>{this.renderPlayableImage(images, 0)}</View>;
411
+ }
412
+ } else if (!_.isNil(image)) {
413
+ return <View style={styles.singleImageContainer}>{this.renderPlayableImageUrl([image], 0)}</View>;
414
+ }
415
+ return null;
416
+ }
417
+
418
+ renderImagePopup() {
419
+ return (
420
+ <Components.ImagePopup
421
+ visible={this.state.galleryOpen}
422
+ images={this.state.galleryImages}
423
+ onClose={this.closeGallery.bind(this)}
424
+ ref="imagePopup"
425
+ />
426
+ );
427
+ }
428
+
429
+ renderAssignee() {
430
+ const { job } = this.state;
431
+ if (!this.hasPermission() && !job.Assignee) return null;
432
+
433
+ const content = (
434
+ <View>
435
+ <Text style={styles.locationLabel}>Assigned To</Text>
436
+ <View>
437
+ {job.Assignee && (
438
+ <View style={styles.profileContainer}>
439
+ <Components.ProfilePic ProfilePic={job.Assignee.profilePic} Diameter={40} />
440
+ <View style={styles.nameContainer}>
441
+ <Text style={styles.nameText}>{job.Assignee.displayName}</Text>
442
+ </View>
443
+ </View>
444
+ )}
445
+ </View>
446
+ </View>
447
+ );
448
+
449
+ if (this.hasPermission()) {
450
+ return (
451
+ <Components.FormCardSectionOptionLauncher
452
+ onPress={this.onOpenAssigneePicker}
453
+ title="Assigned To"
454
+ value={job.Assignee ? job.Assignee.displayName : 'Unassigned'}
455
+ textStyle={styles.detailsText}
456
+ sectionStyle={styles.detailsSection}
457
+ >
458
+ {content}
459
+ </Components.FormCardSectionOptionLauncher>
460
+ );
461
+ }
462
+ return content;
463
+ }
464
+
465
+ renderCustomFields() {
466
+ const { job } = this.state;
467
+ const { customFields } = job;
468
+
469
+ const renderAnswer = field => {
470
+ switch (field.type) {
471
+ case 'date':
472
+ return <Text style={styles.customText}>{field.answer ? moment(field.answer, 'YYYY-MM-DD').format('DD MMM YYYY') : ''}</Text>;
473
+ case 'time':
474
+ return <Text style={styles.customText}>{field.answer ? moment(field.answer, 'HH:mm').format('h:mm a') : ''}</Text>;
475
+ case 'yn':
476
+ return <Text style={styles.customText}>{field.answer ? 'Yes' : 'No'}</Text>;
477
+ case 'checkbox':
478
+ return <Text style={styles.customText}>{field.answer && Array.isArray(field.answer) ? field.answer.join(', ') : ''}</Text>;
479
+ case 'image':
480
+ return <View style={{ marginTop: 8 }}>{this.renderImage(field.answer)}</View>;
481
+ default:
482
+ return <Text style={styles.customText}>{field.answer}</Text>;
483
+ }
484
+ };
485
+
486
+ return customFields.map((field, index) => {
487
+ if (['staticTitle', 'staticText'].includes(field.type)) return null;
488
+ if (_.isNil(field.answer) || field.answer === '' || (Array.isArray(field.answer) && field.answer.length === 0)) return null;
489
+ return (
490
+ <View key={index}>
491
+ <Text style={styles.customLabel}>{field.label}</Text>
492
+ {renderAnswer(field)}
493
+ </View>
494
+ );
495
+ });
496
+ }
497
+
498
+ rendeDetails() {
499
+ const { job } = this.state;
500
+ const { customFields } = job;
501
+ const hasCustomFields = customFields && customFields.length > 0;
502
+
503
+ return (
504
+ <View>
505
+ <Components.FormCardSectionOptionLauncher
506
+ onPress={this.onToggleDetails}
507
+ title="Details"
508
+ icon={this.state.showMore ? 'angle-up' : 'angle-down'}
509
+ textStyle={styles.detailsText}
510
+ sectionStyle={styles.detailsSection}
511
+ />
512
+ {this.state.showMore && (
513
+ <View>
514
+ {hasCustomFields ? this.renderCustomFields() : null}
515
+ {!hasCustomFields ? (
516
+ <>
517
+ {this.renderImage(job.images, job.image)}
518
+ {!_.isEmpty(job.description) && (
519
+ <Text numberOfLines={10} style={styles.jobDescriptionText}>
520
+ {job.description}
521
+ </Text>
522
+ )}
523
+ </>
524
+ ) : null}
525
+ <Text style={styles.locationLabel}>Address</Text>
526
+ <Text style={styles.locationText}>{job.room}</Text>
527
+ {!hasCustomFields && job.isHome ? (
528
+ <View style={styles.detailsSection}>
529
+ <Text style={styles.locationLabel}>Must be home</Text>
530
+ <Text style={styles.locationText}>{job.homeText}</Text>
531
+ </View>
532
+ ) : null}
533
+ <Text style={styles.requesterLabel}>Submitted By</Text>
534
+ <View style={styles.profileContainer}>
535
+ <Components.ProfilePic ProfilePic={job.userProfilePic} Diameter={40} />
536
+ <View style={styles.nameContainer}>
537
+ <Text style={styles.nameText}>{job.userName}</Text>
538
+ {!_.isEmpty(job.phone) && <Text style={styles.phoneText}>{job.phone}</Text>}
539
+ </View>
540
+ </View>
541
+ </View>
542
+ )}
543
+ </View>
544
+ );
545
+ }
546
+
547
+ renderMessages() {
548
+ return (
549
+ <View>
550
+ <Components.CommentSection
551
+ ref={this.commentSection}
552
+ commentReply={this.commentReply}
553
+ scrollView={this.scrollView}
554
+ adminPermission={values.permissionMaintenanceTracking}
555
+ entityType={values.featureKey}
556
+ entityId={this.props.job.id}
557
+ entityName={this.props.job.title}
558
+ site={this.state.job.site || this.state.job.location}
559
+ live
560
+ refreshFrequency={10000}
561
+ placeHolder={''}
562
+ style={{ flex: 1, paddingHorizontal: 0, paddingTop: 16 }}
563
+ onCommentsLoaded={this.onCommentsLoaded}
564
+ onCommentAdded={this.onCommentAdded}
565
+ hideAddComment
566
+ disableFlag
567
+ />
568
+ {!this.state.showMessages && (
569
+ <Components.InlineButton
570
+ onPress={this.onLeaveMessage}
571
+ color={this.props.colourBrandingMain}
572
+ touchableStyle={{ marginTop: 10 }}
573
+ style={{ height: 36 }}
574
+ textStyle={{ color: '#fff' }}
575
+ fullWidth
576
+ >
577
+ Leave Message
578
+ </Components.InlineButton>
579
+ )}
580
+ </View>
581
+ );
582
+ }
583
+
584
+ renderMessagesReply() {
585
+ if (!this.state.showMessages) return null;
586
+
587
+ return (
588
+ <Components.CommentReply
589
+ ref={this.commentReply}
590
+ commentSection={this.commentSection}
591
+ scrollView={this.scrollView}
592
+ entityType={values.featureKey}
593
+ entityId={this.props.job.id}
594
+ entityName={this.props.job.title}
595
+ site={this.state.job.site || this.state.job.location}
596
+ // noScroll={true}
597
+ // style={{ position: 'absolute', bottom: 0, left: 0, right: 0 }}
598
+ />
599
+ );
600
+ }
601
+
602
+ renderStatusPopup() {
603
+ if (!this.state.showStatusPopup) return null;
604
+ return <StatusSelectorPopup onClose={this.onCloseStatusPopup} onSelect={this.onSelectStatus} />;
605
+ }
606
+
607
+ render() {
608
+ if (this.state.forbidden) {
609
+ return <Components.Forbidden />;
610
+ }
611
+ return (
612
+ <KeyboardAvoidingView behavior={Platform.OS === 'ios' && 'padding'} style={styles.container}>
613
+ <Components.Header leftIcon="angle-left" onPressLeft={this.onPressBack} text={Config.env.strings.MAINTENANCE_REQUEST_DETAILS} />
614
+ {this.renderLoading()}
615
+ <ScrollView ref={this.scrollView} contentContainerStyle={{ paddingBottom: 26 }} style={{ height: '100%' }}>
616
+ <View style={styles.innerContainer}>
617
+ {this.renderTop()}
618
+ {this.renderAssignee()}
619
+ {this.rendeDetails()}
620
+ {this.renderMessages()}
621
+ </View>
622
+ </ScrollView>
623
+ {this.renderMessagesReply()}
624
+ {this.renderStatusPopup()}
625
+ {this.renderImagePopup()}
626
+ <DateTimePicker
627
+ isVisible={this.state.isDateTimePickerVisible}
628
+ onConfirm={this.onDateSelected}
629
+ onCancel={this.onCloseDatePicker}
630
+ mode={this.state.popUpType}
631
+ headerTextIOS={`Pick a ${this.state.popUpType}`}
632
+ />
633
+ </KeyboardAvoidingView>
634
+ );
635
+ }
636
+ }
637
+
638
+ const styles = StyleSheet.create({
639
+ container: {
640
+ flex: 1,
641
+ backgroundColor: '#fff',
642
+ },
643
+ innerContainer: {
644
+ paddingTop: 23,
645
+ paddingHorizontal: 16,
646
+ },
647
+ jobTitleContainer: {
648
+ paddingVertical: 14,
649
+ paddingHorizontal: 12,
650
+ },
651
+ jobIdText: {
652
+ fontFamily: 'sf-medium',
653
+ fontSize: 12,
654
+ marginBottom: 4,
655
+ },
656
+ jobTitleText: {
657
+ fontFamily: 'sf-semibold',
658
+ fontSize: 20,
659
+ color: Colours.TEXT_DARKEST,
660
+ marginBottom: 8,
661
+ },
662
+ jobTypeSeenContainer: {
663
+ flexDirection: 'row',
664
+ alignItems: 'center',
665
+ },
666
+ jobTypeContainer: {
667
+ padding: 4,
668
+ minWidth: 80,
669
+ maxWidth: 140,
670
+ borderRadius: 4,
671
+ justifyContent: 'center',
672
+ },
673
+ jobTypeText: {
674
+ fontFamily: 'sf-semibold',
675
+ fontSize: 12,
676
+ textAlign: 'center',
677
+ maxWidth: '100%',
678
+ },
679
+ jobSeenContainer: {
680
+ flexDirection: 'row',
681
+ alignItems: 'center',
682
+ marginLeft: 10,
683
+ },
684
+ jobSeenIcon: {
685
+ fontSize: 12,
686
+ },
687
+ jobSeenText: {
688
+ fontFamily: 'sf-semibold',
689
+ fontSize: 12,
690
+ marginLeft: 4,
691
+ },
692
+ jobStatusDateText: {
693
+ marginTop: 8,
694
+ fontFamily: 'sf-medium',
695
+ fontSize: 13,
696
+ color: Colours.TEXT_LIGHT,
697
+ },
698
+ jobStatusExpectedContainer: {
699
+ flexDirection: 'row',
700
+ alignItems: 'flex-start',
701
+ justifyContent: 'space-between',
702
+ borderTopWidth: 1,
703
+ borderTopColor: Colours.LINEGREY,
704
+ paddingVertical: 14,
705
+ paddingHorizontal: 12,
706
+ },
707
+ jobStatusOuterContainer: {
708
+ // marginRight: 50,
709
+ },
710
+ jobStatusHeading: {
711
+ fontFamily: 'sf-bold',
712
+ fontSize: 11,
713
+ letterSpacing: 0.8,
714
+ color: Colours.TEXT_DARK,
715
+ marginBottom: 6,
716
+ },
717
+ jobStatusContainer: {
718
+ flexDirection: 'row',
719
+ alignItems: 'center',
720
+ width: 120,
721
+ height: 30,
722
+ paddingHorizontal: 8,
723
+ borderRadius: 4,
724
+ },
725
+ jobStatusIcon: {
726
+ color: '#fff',
727
+ fontSize: 14,
728
+ marginRight: 8,
729
+ },
730
+ jobStatusText: {
731
+ color: '#fff',
732
+ textAlign: 'center',
733
+ fontFamily: 'sf-semibold',
734
+ fontSize: 13,
735
+ flex: 1,
736
+ textAlign: 'center',
737
+ },
738
+ jobExpectedDateContainer: {
739
+ backgroundColor: Colours.BOXGREY,
740
+ flexDirection: 'row',
741
+ width: 115,
742
+ height: 30,
743
+ borderRadius: 4,
744
+ alignItems: 'center',
745
+ paddingLeft: 8,
746
+ },
747
+ jobExpectedDate: {
748
+ fontFamily: 'sf-regular',
749
+ fontSize: 13,
750
+ color: Colours.hexToRGBAstring(Colours.TEXT_DARKEST, 0.5),
751
+ },
752
+ detailsText: {
753
+ fontFamily: 'sf-semibold',
754
+ fontSize: 16,
755
+ color: Colours.TEXT_DARKEST,
756
+ },
757
+ detailsSection: {
758
+ marginTop: 8,
759
+ paddingHorizontal: 0,
760
+ paddingBottom: 12,
761
+ },
762
+ sideBySideImages: {
763
+ flex: 1,
764
+ flexDirection: 'row',
765
+ justifyContent: 'space-between',
766
+ marginBottom: 16,
767
+ },
768
+ sideBySideImageContainer: {
769
+ backgroundColor: '#fff',
770
+ width: '48%',
771
+ height: 150,
772
+ borderRadius: 2,
773
+ overflow: 'hidden',
774
+ },
775
+ singleImageContainer: {
776
+ backgroundColor: '#fff',
777
+ width: '100%',
778
+ height: 150,
779
+ flex: 1,
780
+ borderRadius: 2,
781
+ overflow: 'hidden',
782
+ marginBottom: 16,
783
+ },
784
+ imageContainer: {
785
+ height: 150,
786
+ width: 'auto',
787
+ justifyContent: 'center',
788
+ },
789
+ imagePlayContainer: {
790
+ position: 'absolute',
791
+ top: 0,
792
+ left: 0,
793
+ right: 0,
794
+ bottom: 0,
795
+ alignItems: 'center',
796
+ justifyContent: 'center',
797
+ },
798
+ imageControlIcon: {
799
+ color: '#fff',
800
+ fontSize: 30,
801
+ textShadowColor: 'rgba(0,0,0,0.3)',
802
+ textShadowOffset: { width: 2, height: 2 },
803
+ },
804
+ plusImages: {
805
+ fontFamily: 'sf-bold',
806
+ fontSize: 32,
807
+ textAlign: 'center',
808
+ color: '#fff',
809
+ textShadowColor: 'rgba(0, 0, 0, 0.5)',
810
+ textShadowOffset: { width: 0, height: 2 },
811
+ textShadowRadius: 8,
812
+ },
813
+ jobDescriptionText: {
814
+ fontFamily: 'sf-medium',
815
+ fontSize: 14,
816
+ color: Colours.TEXT_LIGHT,
817
+ paddingBottom: 16,
818
+ },
819
+ locationLabel: {
820
+ fontFamily: 'sf-bold',
821
+ fontSize: 14,
822
+ color: Colours.TEXT_DARKEST,
823
+ },
824
+ locationText: {
825
+ fontFamily: 'sf-regular',
826
+ fontSize: 16,
827
+ color: Colours.TEXT_DARKEST,
828
+ paddingVertical: 8,
829
+ },
830
+ requesterLabel: {
831
+ fontFamily: 'sf-bold',
832
+ fontSize: 14,
833
+ color: Colours.TEXT_DARKEST,
834
+ paddingVertical: 10,
835
+ },
836
+ profileContainer: {
837
+ flexDirection: 'row',
838
+ alignItems: 'center',
839
+ },
840
+ nameContainer: {
841
+ marginLeft: 18,
842
+ },
843
+ nameText: {
844
+ fontSize: 'sf-semibold',
845
+ fontSize: 14,
846
+ color: Colours.TEXT_DARKEST,
847
+ marginBottom: 4,
848
+ },
849
+ phoneText: {
850
+ fontSize: 'sf-medium',
851
+ fontSize: 14,
852
+ color: Colours.TEXT_LIGHT,
853
+ },
854
+ textSectionInner: {
855
+ flexDirection: 'row',
856
+ justifyContent: 'space-between',
857
+ alignItems: 'center',
858
+ marginTop: 8,
859
+ },
860
+ textSectionLabel: {
861
+ fontFamily: 'sf-semibold',
862
+ fontSize: 12,
863
+ lineHeight: 24,
864
+ color: Colours.TEXT_DARKEST,
865
+ },
866
+ textSectionTextContainer: {
867
+ flexDirection: 'row',
868
+ alignItems: 'center',
869
+ },
870
+ textSectionText: {
871
+ fontFamily: 'sf-regular',
872
+ fontSize: 13,
873
+ lineHeight: 24,
874
+ color: Colours.TEXT_LIGHT,
875
+ },
876
+ textSectionAngleRight: {
877
+ fontSize: 24,
878
+ marginLeft: 10,
879
+ lineHeight: 24,
880
+ },
881
+ customLabel: {
882
+ fontFamily: 'sf-bold',
883
+ fontSize: 14,
884
+ color: Colours.TEXT_DARKEST,
885
+ },
886
+ customText: {
887
+ fontFamily: 'sf-regular',
888
+ fontSize: 16,
889
+ color: Colours.TEXT_DARKEST,
890
+ paddingVertical: 8,
891
+ },
892
+ customStaticTitle: {
893
+ fontSize: 20,
894
+ fontFamily: 'sf-semibold',
895
+ color: Colours.TEXT_DARKEST,
896
+ marginBottom: 10,
897
+ },
898
+ customStaticText: {
899
+ fontSize: 17,
900
+ fontFamily: 'sf-regular',
901
+ color: Colours.TEXT_DARKEST,
902
+ lineHeight: 24,
903
+ marginBottom: 10,
904
+ },
905
+ });
906
+
907
+ const mapStateToProps = state => {
908
+ return {
909
+ user: state.user,
910
+ colourBrandingMain: Colours.getMainBrandingColourFromState(state),
911
+ jobs: state[values.reducerKey].jobs,
912
+ };
913
+ };
914
+
915
+ export default connect(mapStateToProps, { jobAdded })(RequestDetail);