@plusscommunities/pluss-maintenance-app 6.1.1-beta.0 → 7.0.0-auth.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 (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 +14 -10
  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 +15 -1
  45. package/dist/module/values.config.js.map +1 -1
  46. package/package.json +16 -12
  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 +10 -8
  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 +14 -0
  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,14 +1,15 @@
1
1
  import React, { Component } from 'react';
2
2
  import { ScrollView, View, StyleSheet, Text, KeyboardAvoidingView, TouchableOpacity, ImageBackground, Platform } from 'react-native';
3
3
  import DateTimePicker from 'react-native-modal-datetime-picker';
4
- import { Icon } from 'react-native-elements';
4
+ import { Icon } from '@rneui/themed';
5
5
  import _ from 'lodash';
6
6
  import moment from 'moment';
7
7
  import { connect } from 'react-redux';
8
- import { generalActions } from '../apis';
9
- import { jobAdded } from '../actions';
8
+ import { maintenanceActions } from '../apis';
9
+ import { jobAdded, jobStatusesUpdate, jobHideSeenUpdate } from '../actions';
10
10
  import StatusSelectorPopup from '../components/StatusSelectorPopup';
11
- import { jobStatusOptions, getJobStatusProps } from '../helper';
11
+ import PrioritySelectorPopup from '../components/PrioritySelectorPopup';
12
+ import { getJobStatus, getJobPriority } from '../helper';
12
13
  import { Services } from '../feature.config';
13
14
  import { Colours, Helper, Components, Config } from '../core.config';
14
15
  import { values } from '../values.config';
@@ -22,16 +23,20 @@ class RequestDetail extends Component {
22
23
  isDateTimePickerVisible: false,
23
24
  popUpType: '',
24
25
  status: '',
26
+ priority: '',
25
27
  expectedDate: null,
26
28
  expectedDateText: '',
27
29
  seen: false,
28
- showMore: false,
30
+ showMore: true,
29
31
  showStatusPopup: false,
32
+ showPriorityPopup: false,
30
33
  loading: false,
31
34
  showFullscreenVideo: false,
32
35
  currentVideoUrl: '',
33
36
  galleryOpen: false,
37
+ galleryImages: [],
34
38
  showMessages: false,
39
+ assignees: [],
35
40
  };
36
41
 
37
42
  this.scrollView = React.createRef();
@@ -40,17 +45,51 @@ class RequestDetail extends Component {
40
45
  }
41
46
 
42
47
  componentDidMount() {
43
- this.updateJobState();
48
+ this.props.jobStatusesUpdate(this.props.job.site);
49
+ this.props.jobHideSeenUpdate(this.props.job.site);
50
+ this.getJob();
51
+ this.updateJobState(this.props.job);
52
+ this.getAssignees();
44
53
  }
45
54
 
46
- componentDidUpdate(prevProps) {
47
- if (prevProps.jobs !== this.props.jobs) {
48
- this.updateJobState();
55
+ getJob = async () => {
56
+ this.setState({ loading: true }, async () => {
57
+ try {
58
+ const res = await maintenanceActions.getJob(this.props.job.site, this.props.job.id);
59
+ // console.log('getJob', JSON.stringify(res.data, null, 2));
60
+ this.props.jobAdded(res.data);
61
+ this.updateJobState(res.data);
62
+ } catch (error) {
63
+ console.log('getJob error', error.toString());
64
+ // check for 403 or 404 error
65
+ if (error.response.status === 403 || error.response.status === 404) {
66
+ this.setState({
67
+ forbidden: true,
68
+ });
69
+ }
70
+ console.log('getJob error', error);
71
+ } finally {
72
+ this.setState({ loading: false });
73
+ }
74
+ });
75
+ };
76
+
77
+ getAssignees = async () => {
78
+ if (!this.hasPermission()) return;
79
+ try {
80
+ const res = await maintenanceActions.getAssignees(this.props.user.site);
81
+ this.setState({ assignees: res.data.Users });
82
+ } catch (error) {
83
+ console.log('getAssignees error', error);
49
84
  }
50
- }
85
+ };
51
86
 
52
- updateJobState() {
53
- const job = _.find(this.props.jobs, j => j.id === this.props.job.id) || this.props.job;
87
+ updateJobState(defaultJob) {
88
+ const job = _.find(this.props.jobs, j => j.id === this.props.job.id) || defaultJob;
89
+ if (!job) {
90
+ this.getJob();
91
+ return;
92
+ }
54
93
  const newState = { job, status: job.status };
55
94
  if (job.expectedDate) {
56
95
  newState.expectedDate = moment(job.expectedDate);
@@ -74,9 +113,10 @@ class RequestDetail extends Component {
74
113
  this.setState({ loading: true }, async () => {
75
114
  try {
76
115
  const updated = { id: job.id, seen: true, status: job.status || 'Unassigned' };
77
- const res = await generalActions.editJob(updated, user.site);
116
+ const res = await maintenanceActions.editJob(updated, user.site);
78
117
  // console.log('markSeen updated');
79
118
  this.props.jobAdded(res.data.job);
119
+ this.getJob();
80
120
  this.setState({ loading: false, seen: true });
81
121
  } catch (error) {
82
122
  console.log('markSeen error', error);
@@ -96,8 +136,9 @@ class RequestDetail extends Component {
96
136
  .utc()
97
137
  .toISOString();
98
138
  }
99
- const res = await generalActions.editJob(updated, user.site);
139
+ const res = await maintenanceActions.editJob(updated, user.site);
100
140
  this.props.jobAdded(res.data.job);
141
+ this.getJob();
101
142
  } catch (error) {
102
143
  console.log('updateJob error', error);
103
144
  } finally {
@@ -109,8 +150,9 @@ class RequestDetail extends Component {
109
150
  updateJobStatus = () => {
110
151
  this.setState({ loading: true }, async () => {
111
152
  try {
112
- const res = await generalActions.editJobStatus(this.props.job.id, this.state.status);
153
+ const res = await maintenanceActions.editJobStatus(this.props.job.id, this.state.status);
113
154
  this.props.jobAdded(res.data.job);
155
+ this.getJob();
114
156
  } catch (error) {
115
157
  console.log('updateJobStatus error', error);
116
158
  } finally {
@@ -119,6 +161,20 @@ class RequestDetail extends Component {
119
161
  });
120
162
  };
121
163
 
164
+ updateJobPriority = () => {
165
+ this.setState({ loading: true }, async () => {
166
+ try {
167
+ const res = await maintenanceActions.editJobPriority(this.props.job.id, this.state.priority);
168
+ this.props.jobAdded(res.data.job);
169
+ this.getJob();
170
+ } catch (error) {
171
+ console.log('updateJobPriority error', error);
172
+ } finally {
173
+ this.setState({ loading: false });
174
+ }
175
+ });
176
+ };
177
+
122
178
  onPressBack = () => {
123
179
  Services.navigation.goBack();
124
180
  };
@@ -132,13 +188,29 @@ class RequestDetail extends Component {
132
188
  };
133
189
 
134
190
  onSelectStatus = status => {
191
+ if (this.state.loading) return;
135
192
  this.setState({ status, showStatusPopup: false }, () => {
136
193
  this.updateJobStatus();
137
194
  });
138
195
  };
139
196
 
197
+ onOpenPriorityPicker = () => {
198
+ this.setState({ showPriorityPopup: true });
199
+ };
200
+
201
+ onClosePriorityPopup = () => {
202
+ this.setState({ showPriorityPopup: false });
203
+ };
204
+
205
+ onSelectPriority = priority => {
206
+ if (this.state.loading) return;
207
+ this.setState({ priority, showPriorityPopup: false }, () => {
208
+ this.updateJobPriority();
209
+ });
210
+ };
211
+
140
212
  openStaffNotes = () => {
141
- Services.navigation.navigate(values.screenRequestNotes, { job: this.state.job });
213
+ Services.navigation.navigate(values.screenRequestNotes, { job: this.state.job, onChange: this.getJob });
142
214
  };
143
215
 
144
216
  onOpenDatePicker = () => {
@@ -182,9 +254,10 @@ class RequestDetail extends Component {
182
254
  onCommentAdded = () => {
183
255
  this.setState({ loading: true }, async () => {
184
256
  try {
185
- const job = await generalActions.getJob(this.props.user.site, this.props.job.id);
257
+ const job = await maintenanceActions.getJob(this.props.user.site, this.props.job.id);
186
258
  // console.log('onCommentAdded', job?.data);
187
259
  this.props.jobAdded(job.data);
260
+ this.getJob();
188
261
  } catch (error) {
189
262
  console.log('onCommentAdded error', error);
190
263
  } finally {
@@ -194,8 +267,15 @@ class RequestDetail extends Component {
194
267
  };
195
268
 
196
269
  hasPermission = () => {
270
+ const { job } = this.state;
197
271
  const { permissions } = this.props.user;
198
- return _.includes(permissions, values.permissionMaintenanceTracking);
272
+ if (_.includes(permissions, values.permissionMaintenanceTracking)) {
273
+ return true;
274
+ }
275
+ if (_.includes(permissions, values.permissionMaintenanceAssignment)) {
276
+ return job.AssigneeId === this.props.user.Id;
277
+ }
278
+ return false;
199
279
  };
200
280
 
201
281
  toggleFullscreenVideo = url => {
@@ -203,9 +283,10 @@ class RequestDetail extends Component {
203
283
  this.setState({ showFullscreenVideo: url.length > 0, currentVideoUrl: url });
204
284
  };
205
285
 
206
- openGallery(index) {
286
+ openGallery(galleryImages, index) {
207
287
  this.setState({
208
288
  galleryOpen: true,
289
+ galleryImages,
209
290
  });
210
291
  this.refs.imagePopup.scrollTo(index);
211
292
  }
@@ -216,25 +297,59 @@ class RequestDetail extends Component {
216
297
  });
217
298
  }
218
299
 
300
+ onOpenAssigneePicker = () => {
301
+ const options = this.state.assignees.map(a => {
302
+ return { key: a.id, name: a.displayName };
303
+ });
304
+ Services.navigation.navigate('optionSelector', {
305
+ options,
306
+ selection: this.state.job.AssigneeId,
307
+ title: 'Assign request',
308
+ onSelect: this.onSelectAssignee,
309
+ });
310
+ };
311
+
312
+ onSelectAssignee = assignee => {
313
+ this.setState({ loading: true }, async () => {
314
+ try {
315
+ console.log('onSelectAssignee', this.props.job.id, assignee.key);
316
+ const res = await maintenanceActions.assignJob(this.props.job.id, assignee.key);
317
+ this.props.jobAdded(res.data.job);
318
+ this.getJob();
319
+ } catch (error) {
320
+ console.log('onSelectAssignee error', error);
321
+ } finally {
322
+ this.setState({ loading: false });
323
+ }
324
+ });
325
+ };
326
+
219
327
  renderLoading() {
220
328
  return <Components.LoadingIndicator visible={this.state.loading} />;
221
329
  }
222
330
 
223
331
  renderTop() {
224
332
  const { status, job } = this.state;
225
- const { statusText, statusColor } = getJobStatusProps(status);
333
+ const statusOption = getJobStatus(status, this.props);
334
+ const priority = getJobPriority(job.priority);
226
335
  const canEdit = this.hasPermission();
227
- const showSeen = !status || status === jobStatusOptions[0].name;
336
+ const isStaff = this.props.user.category === 'staff'
337
+ const showSeen = !status || status === getJobStatus(null, this.props).text;
228
338
 
229
339
  return (
230
340
  <View style={{ ...Helper.getShadowStyle() }}>
231
341
  <View style={styles.jobTitleContainer}>
342
+ {job.jobId ? (
343
+ <Text style={[styles.jobIdText, { color: this.props.colourBrandingMain }]}>{`${values.textEntityName} #${job.jobId}`}</Text>
344
+ ) : null}
232
345
  <Text style={styles.jobTitleText}>{job.title}</Text>
233
346
  <View style={styles.jobTypeSeenContainer}>
234
347
  <View style={[styles.jobTypeContainer, { backgroundColor: Colours.hexToRGBAstring(this.props.colourBrandingMain, 0.2) }]}>
235
- <Text style={[styles.jobTypeText, { color: this.props.colourBrandingMain }]}>{job.type}</Text>
348
+ <Text style={[styles.jobTypeText, { color: this.props.colourBrandingMain }]} numberOfLines={2}>
349
+ {job.type}
350
+ </Text>
236
351
  </View>
237
- {showSeen && this.state.seen && (
352
+ {!this.props.hideSeen && showSeen && this.state.seen && (
238
353
  <View style={styles.jobSeenContainer}>
239
354
  <Icon name="check" type="font-awesome" iconStyle={[styles.jobSeenIcon, { color: this.props.colourBrandingMain }]} />
240
355
  <Text style={[styles.jobSeenText, { color: this.props.colourBrandingMain }]}>Seen</Text>
@@ -250,7 +365,7 @@ class RequestDetail extends Component {
250
365
  </View>
251
366
  </View>
252
367
  )}
253
- <View style={styles.textSectionInner}>
368
+ {/* <View style={styles.textSectionInner}>
254
369
  <Text style={styles.textSectionLabel}>Expected Date</Text>
255
370
  <TouchableOpacity onPress={this.onOpenDatePicker}>
256
371
  <View style={styles.textSectionTextContainer}>
@@ -262,25 +377,36 @@ class RequestDetail extends Component {
262
377
  />
263
378
  </View>
264
379
  </TouchableOpacity>
265
- </View>
380
+ </View> */}
266
381
  </View>
267
- <View style={styles.jobStatusExpectedContainer}>
268
- <View style={styles.jobStatusOuterContainer}>
269
- <Text style={styles.jobStatusHeading}>STATUS</Text>
270
- <TouchableOpacity onPress={canEdit ? this.onOpenStatusPicker : null}>
271
- <View style={[styles.jobStatusContainer, { backgroundColor: statusColor }]}>
272
- <Icon name="wrench" type="font-awesome" iconStyle={styles.jobStatusIcon} />
273
- <Text style={styles.jobStatusText}>{statusText}</Text>
382
+ <View style={styles.jobInfoContainer}>
383
+ <View style={styles.jobStatusExpectedContainer}>
384
+ <View style={styles.jobStatusOuterContainer}>
385
+ <Text style={styles.jobStatusHeading}>STATUS</Text>
386
+ <TouchableOpacity onPress={canEdit ? this.onOpenStatusPicker : null}>
387
+ <View style={[styles.jobStatusContainer, { backgroundColor: statusOption.color }]}>
388
+ <Text style={styles.jobStatusText}>{statusOption?.text}</Text>
389
+ </View>
390
+ </TouchableOpacity>
391
+ </View>
392
+ {this.hasPermission() && (
393
+ <View style={styles.jobStatusOuterContainer}>
394
+ <Text style={styles.jobStatusHeading}>STAFF NOTES</Text>
395
+ <TouchableOpacity onPress={this.openStaffNotes}>
396
+ <View style={[styles.jobStatusContainer, { backgroundColor: this.props.colourBrandingMain }]}>
397
+ <Icon name="pencil-square-o" type="font-awesome" iconStyle={styles.jobStatusIcon} />
398
+ <Text style={styles.jobStatusText}>Notes ({(job.Notes || []).length})</Text>
399
+ </View>
400
+ </TouchableOpacity>
274
401
  </View>
275
- </TouchableOpacity>
402
+ )}
276
403
  </View>
277
- {this.hasPermission() && (
278
- <View style={styles.jobStatusOuterContainer}>
279
- <Text style={styles.jobStatusHeading}>STAFF NOTES</Text>
280
- <TouchableOpacity onPress={this.openStaffNotes}>
281
- <View style={[styles.jobStatusContainer, { backgroundColor: this.props.colourBrandingMain }]}>
282
- <Icon name="pencil-square-o" type="font-awesome" iconStyle={styles.jobStatusIcon} />
283
- <Text style={styles.jobStatusText}>Notes ({(job.Notes || []).length})</Text>
404
+ {isStaff && (
405
+ <View style={styles.jobPriorityOuterContainer}>
406
+ <Text style={styles.jobStatusHeading}>PRIORITY</Text>
407
+ <TouchableOpacity onPress={canEdit ? this.onOpenPriorityPicker : null}>
408
+ <View style={[styles.jobStatusContainer, { backgroundColor: priority.color }]}>
409
+ <Text style={styles.jobStatusText}>{priority.label}</Text>
284
410
  </View>
285
411
  </TouchableOpacity>
286
412
  </View>
@@ -290,7 +416,8 @@ class RequestDetail extends Component {
290
416
  );
291
417
  }
292
418
 
293
- renderPlayableImageUrl(url, index, containerStyle, showMore) {
419
+ renderPlayableImageUrl(images, index, containerStyle, showMore) {
420
+ const url = images[index || 0];
294
421
  const thumbUrl = Helper.getThumb300(url);
295
422
 
296
423
  if (Helper.isVideo(url)) {
@@ -307,7 +434,7 @@ class RequestDetail extends Component {
307
434
 
308
435
  const imageUrl = Helper.get1400(url);
309
436
  return (
310
- <TouchableOpacity style={containerStyle} onPress={this.openGallery.bind(this, index || 0)}>
437
+ <TouchableOpacity style={containerStyle} onPress={this.openGallery.bind(this, images, index || 0)}>
311
438
  <ImageBackground style={styles.imageContainer} source={{ uri: imageUrl }}>
312
439
  {showMore && <Text style={styles.plusImages}>+{this.state.job.images.length - 2}</Text>}
313
440
  </ImageBackground>
@@ -315,26 +442,24 @@ class RequestDetail extends Component {
315
442
  );
316
443
  }
317
444
 
318
- renderPlayableImage(index, containerStyle, showMore) {
319
- const url = this.state.job.images[index];
320
- return this.renderPlayableImageUrl(url, index, containerStyle, showMore);
445
+ renderPlayableImage(images, index, containerStyle, showMore) {
446
+ return this.renderPlayableImageUrl(images, index, containerStyle, showMore);
321
447
  }
322
448
 
323
- renderImage() {
324
- const { job } = this.state;
325
- if (!_.isNil(job.images) && !_.isEmpty(job.images)) {
326
- if (job.images.length >= 2) {
449
+ renderImage(images, image = null) {
450
+ if (!_.isNil(images) && !_.isEmpty(images)) {
451
+ if (images.length >= 2) {
327
452
  return (
328
453
  <View style={styles.sideBySideImages}>
329
- {this.renderPlayableImage(0, styles.sideBySideImageContainer)}
330
- {this.renderPlayableImage(1, styles.sideBySideImageContainer, job.images.length > 2)}
454
+ {this.renderPlayableImage(images, 0, styles.sideBySideImageContainer)}
455
+ {this.renderPlayableImage(images, 1, styles.sideBySideImageContainer, images.length > 2)}
331
456
  </View>
332
457
  );
333
458
  } else {
334
- return <View style={styles.singleImageContainer}>{this.renderPlayableImage(0)}</View>;
459
+ return <View style={styles.singleImageContainer}>{this.renderPlayableImage(images, 0)}</View>;
335
460
  }
336
- } else if (!_.isNil(job.image)) {
337
- return <View style={styles.singleImageContainer}>{this.renderPlayableImageUrl(job.image)}</View>;
461
+ } else if (!_.isNil(image)) {
462
+ return <View style={styles.singleImageContainer}>{this.renderPlayableImageUrl([image], 0)}</View>;
338
463
  }
339
464
  return null;
340
465
  }
@@ -343,15 +468,86 @@ class RequestDetail extends Component {
343
468
  return (
344
469
  <Components.ImagePopup
345
470
  visible={this.state.galleryOpen}
346
- images={this.state.job.images || [this.state.job.image]}
471
+ images={this.state.galleryImages}
347
472
  onClose={this.closeGallery.bind(this)}
348
473
  ref="imagePopup"
349
474
  />
350
475
  );
351
476
  }
352
477
 
478
+ renderAssignee() {
479
+ const { job } = this.state;
480
+ if (!this.hasPermission() && !job.Assignee) return null;
481
+
482
+ const content = (
483
+ <View>
484
+ <Text style={styles.locationLabel}>Assigned To</Text>
485
+ <View>
486
+ {job.Assignee && (
487
+ <View style={styles.profileContainer}>
488
+ <Components.ProfilePic ProfilePic={job.Assignee.profilePic} Diameter={40} />
489
+ <View style={styles.nameContainer}>
490
+ <Text style={styles.nameText}>{job.Assignee.displayName}</Text>
491
+ </View>
492
+ </View>
493
+ )}
494
+ </View>
495
+ </View>
496
+ );
497
+
498
+ if (this.hasPermission()) {
499
+ return (
500
+ <Components.FormCardSectionOptionLauncher
501
+ onPress={this.onOpenAssigneePicker}
502
+ title="Assigned To"
503
+ value={job.Assignee ? job.Assignee.displayName : 'Unassigned'}
504
+ textStyle={styles.detailsText}
505
+ sectionStyle={styles.detailsSection}
506
+ >
507
+ {content}
508
+ </Components.FormCardSectionOptionLauncher>
509
+ );
510
+ }
511
+ return content;
512
+ }
513
+
514
+ renderCustomFields() {
515
+ const { job } = this.state;
516
+ const { customFields } = job;
517
+
518
+ const renderAnswer = field => {
519
+ switch (field.type) {
520
+ case 'date':
521
+ return <Text style={styles.customText}>{field.answer ? moment(field.answer, 'YYYY-MM-DD').format('DD MMM YYYY') : ''}</Text>;
522
+ case 'time':
523
+ return <Text style={styles.customText}>{field.answer ? moment(field.answer, 'HH:mm').format('h:mm a') : ''}</Text>;
524
+ case 'yn':
525
+ return <Text style={styles.customText}>{field.answer ? 'Yes' : 'No'}</Text>;
526
+ case 'checkbox':
527
+ return <Text style={styles.customText}>{field.answer && Array.isArray(field.answer) ? field.answer.join(', ') : ''}</Text>;
528
+ case 'image':
529
+ return <View style={{ marginTop: 8 }}>{this.renderImage(field.answer)}</View>;
530
+ default:
531
+ return <Text style={styles.customText}>{field.answer}</Text>;
532
+ }
533
+ };
534
+
535
+ return customFields.map((field, index) => {
536
+ if (['staticTitle', 'staticText'].includes(field.type)) return null;
537
+ if (_.isNil(field.answer) || field.answer === '' || (Array.isArray(field.answer) && field.answer.length === 0)) return null;
538
+ return (
539
+ <View key={index}>
540
+ <Text style={styles.customLabel}>{field.label}</Text>
541
+ {renderAnswer(field)}
542
+ </View>
543
+ );
544
+ });
545
+ }
546
+
353
547
  rendeDetails() {
354
548
  const { job } = this.state;
549
+ const { customFields } = job;
550
+ const hasCustomFields = customFields && customFields.length > 0;
355
551
 
356
552
  return (
357
553
  <View>
@@ -364,21 +560,26 @@ class RequestDetail extends Component {
364
560
  />
365
561
  {this.state.showMore && (
366
562
  <View>
367
- {this.renderImage()}
368
- {!_.isEmpty(job.description) && (
369
- <Text numberOfLines={10} style={styles.jobDescriptionText}>
370
- {job.description}
371
- </Text>
372
- )}
373
- <Text style={styles.locationLabel}>Location</Text>
563
+ {hasCustomFields ? this.renderCustomFields() : null}
564
+ {!hasCustomFields ? (
565
+ <>
566
+ {this.renderImage(job.images, job.image)}
567
+ {!_.isEmpty(job.description) && (
568
+ <Text numberOfLines={10} style={styles.jobDescriptionText}>
569
+ {job.description}
570
+ </Text>
571
+ )}
572
+ </>
573
+ ) : null}
574
+ <Text style={styles.locationLabel}>Address</Text>
374
575
  <Text style={styles.locationText}>{job.room}</Text>
375
- {job.isHome ? (
576
+ {!hasCustomFields && job.isHome ? (
376
577
  <View style={styles.detailsSection}>
377
578
  <Text style={styles.locationLabel}>Must be home</Text>
378
579
  <Text style={styles.locationText}>{job.homeText}</Text>
379
580
  </View>
380
581
  ) : null}
381
- <Text style={styles.requesterLabel}>Person Requesting</Text>
582
+ <Text style={styles.requesterLabel}>Submitted By</Text>
382
583
  <View style={styles.profileContainer}>
383
584
  <Components.ProfilePic ProfilePic={job.userProfilePic} Diameter={40} />
384
585
  <View style={styles.nameContainer}>
@@ -400,7 +601,7 @@ class RequestDetail extends Component {
400
601
  commentReply={this.commentReply}
401
602
  scrollView={this.scrollView}
402
603
  adminPermission={values.permissionMaintenanceTracking}
403
- entityType={values.featureKey}
604
+ entityType={values.commentKey}
404
605
  entityId={this.props.job.id}
405
606
  entityName={this.props.job.title}
406
607
  site={this.state.job.site || this.state.job.location}
@@ -437,12 +638,12 @@ class RequestDetail extends Component {
437
638
  ref={this.commentReply}
438
639
  commentSection={this.commentSection}
439
640
  scrollView={this.scrollView}
440
- entityType={values.featureKey}
641
+ entityType={values.commentKey}
441
642
  entityId={this.props.job.id}
442
643
  entityName={this.props.job.title}
443
644
  site={this.state.job.site || this.state.job.location}
444
- // noScroll={true}
445
- // style={{ position: 'absolute', bottom: 0, left: 0, right: 0 }}
645
+ // noScroll={true}
646
+ // style={{ position: 'absolute', bottom: 0, left: 0, right: 0 }}
446
647
  />
447
648
  );
448
649
  }
@@ -452,7 +653,15 @@ class RequestDetail extends Component {
452
653
  return <StatusSelectorPopup onClose={this.onCloseStatusPopup} onSelect={this.onSelectStatus} />;
453
654
  }
454
655
 
656
+ renderPriorityPopup() {
657
+ if (!this.state.showPriorityPopup) return null;
658
+ return <PrioritySelectorPopup onClose={this.onClosePriorityPopup} onSelect={this.onSelectPriority} />;
659
+ }
660
+
455
661
  render() {
662
+ if (this.state.forbidden) {
663
+ return <Components.Forbidden />;
664
+ }
456
665
  return (
457
666
  <KeyboardAvoidingView behavior={Platform.OS === 'ios' && 'padding'} style={styles.container}>
458
667
  <Components.Header leftIcon="angle-left" onPressLeft={this.onPressBack} text={Config.env.strings.MAINTENANCE_REQUEST_DETAILS} />
@@ -460,12 +669,14 @@ class RequestDetail extends Component {
460
669
  <ScrollView ref={this.scrollView} contentContainerStyle={{ paddingBottom: 26 }} style={{ height: '100%' }}>
461
670
  <View style={styles.innerContainer}>
462
671
  {this.renderTop()}
672
+ {this.renderAssignee()}
463
673
  {this.rendeDetails()}
464
674
  {this.renderMessages()}
465
675
  </View>
466
676
  </ScrollView>
467
677
  {this.renderMessagesReply()}
468
678
  {this.renderStatusPopup()}
679
+ {this.renderPriorityPopup()}
469
680
  {this.renderImagePopup()}
470
681
  <DateTimePicker
471
682
  isVisible={this.state.isDateTimePickerVisible}
@@ -492,6 +703,11 @@ const styles = StyleSheet.create({
492
703
  paddingVertical: 14,
493
704
  paddingHorizontal: 12,
494
705
  },
706
+ jobIdText: {
707
+ fontFamily: 'sf-medium',
708
+ fontSize: 12,
709
+ marginBottom: 4,
710
+ },
495
711
  jobTitleText: {
496
712
  fontFamily: 'sf-semibold',
497
713
  fontSize: 20,
@@ -503,8 +719,9 @@ const styles = StyleSheet.create({
503
719
  alignItems: 'center',
504
720
  },
505
721
  jobTypeContainer: {
506
- height: 20,
507
- width: 80,
722
+ padding: 4,
723
+ minWidth: 80,
724
+ maxWidth: 140,
508
725
  borderRadius: 4,
509
726
  justifyContent: 'center',
510
727
  },
@@ -512,6 +729,7 @@ const styles = StyleSheet.create({
512
729
  fontFamily: 'sf-semibold',
513
730
  fontSize: 12,
514
731
  textAlign: 'center',
732
+ maxWidth: '100%',
515
733
  },
516
734
  jobSeenContainer: {
517
735
  flexDirection: 'row',
@@ -532,15 +750,17 @@ const styles = StyleSheet.create({
532
750
  fontSize: 13,
533
751
  color: Colours.TEXT_LIGHT,
534
752
  },
535
- jobStatusExpectedContainer: {
536
- flexDirection: 'row',
537
- alignItems: 'flex-start',
538
- justifyContent: 'space-between',
753
+ jobInfoContainer: {
539
754
  borderTopWidth: 1,
540
755
  borderTopColor: Colours.LINEGREY,
541
756
  paddingVertical: 14,
542
757
  paddingHorizontal: 12,
543
758
  },
759
+ jobStatusExpectedContainer: {
760
+ flexDirection: 'row',
761
+ alignItems: 'flex-start',
762
+ justifyContent: 'space-between',
763
+ },
544
764
  jobStatusOuterContainer: {
545
765
  // marginRight: 50,
546
766
  },
@@ -572,6 +792,9 @@ const styles = StyleSheet.create({
572
792
  flex: 1,
573
793
  textAlign: 'center',
574
794
  },
795
+ jobPriorityOuterContainer: {
796
+ marginTop: 12,
797
+ },
575
798
  jobExpectedDateContainer: {
576
799
  backgroundColor: Colours.BOXGREY,
577
800
  flexDirection: 'row',
@@ -715,6 +938,30 @@ const styles = StyleSheet.create({
715
938
  marginLeft: 10,
716
939
  lineHeight: 24,
717
940
  },
941
+ customLabel: {
942
+ fontFamily: 'sf-bold',
943
+ fontSize: 14,
944
+ color: Colours.TEXT_DARKEST,
945
+ },
946
+ customText: {
947
+ fontFamily: 'sf-regular',
948
+ fontSize: 16,
949
+ color: Colours.TEXT_DARKEST,
950
+ paddingVertical: 8,
951
+ },
952
+ customStaticTitle: {
953
+ fontSize: 20,
954
+ fontFamily: 'sf-semibold',
955
+ color: Colours.TEXT_DARKEST,
956
+ marginBottom: 10,
957
+ },
958
+ customStaticText: {
959
+ fontSize: 17,
960
+ fontFamily: 'sf-regular',
961
+ color: Colours.TEXT_DARKEST,
962
+ lineHeight: 24,
963
+ marginBottom: 10,
964
+ },
718
965
  });
719
966
 
720
967
  const mapStateToProps = state => {
@@ -722,7 +969,9 @@ const mapStateToProps = state => {
722
969
  user: state.user,
723
970
  colourBrandingMain: Colours.getMainBrandingColourFromState(state),
724
971
  jobs: state[values.reducerKey].jobs,
972
+ statusTypes: state[values.reducerKey].jobstatuses,
973
+ hideSeen: state[values.reducerKey].hideSeen,
725
974
  };
726
975
  };
727
976
 
728
- export default connect(mapStateToProps, { jobAdded })(RequestDetail);
977
+ export default connect(mapStateToProps, { jobAdded, jobStatusesUpdate, jobHideSeenUpdate })(RequestDetail);