@plusscommunities/pluss-maintenance-app 6.0.20 → 6.0.21-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 (97) hide show
  1. package/dist/module/actions/JobActions.js +4 -4
  2. package/dist/module/actions/JobActions.js.map +1 -1
  3. package/dist/module/actions/index.js +1 -1
  4. package/dist/module/actions/index.js.map +1 -1
  5. package/dist/module/actions/types.js +1 -1
  6. package/dist/module/actions/types.js.map +1 -1
  7. package/dist/module/apis/index.js +3 -3
  8. package/dist/module/apis/index.js.map +1 -1
  9. package/dist/module/apis/maintenanceActions.js +36 -36
  10. package/dist/module/apis/maintenanceActions.js.map +1 -1
  11. package/dist/module/apis/userActions.js +5 -5
  12. package/dist/module/apis/userActions.js.map +1 -1
  13. package/dist/module/components/FilterPopupMenu.js +49 -49
  14. package/dist/module/components/FilterPopupMenu.js.map +1 -1
  15. package/dist/module/components/MaintenanceList.js +38 -38
  16. package/dist/module/components/MaintenanceList.js.map +1 -1
  17. package/dist/module/components/MaintenanceListItem.js +62 -62
  18. package/dist/module/components/MaintenanceListItem.js.map +1 -1
  19. package/dist/module/components/MaintenanceWidgetItem.js +27 -27
  20. package/dist/module/components/MaintenanceWidgetItem.js.map +1 -1
  21. package/dist/module/components/PrioritySelectorPopup.js +15 -15
  22. package/dist/module/components/PrioritySelectorPopup.js.map +1 -1
  23. package/dist/module/components/StatusSelectorPopup.js +16 -16
  24. package/dist/module/components/StatusSelectorPopup.js.map +1 -1
  25. package/dist/module/components/WidgetLarge.js +2 -2
  26. package/dist/module/components/WidgetLarge.js.map +1 -1
  27. package/dist/module/components/WidgetSmall.js +19 -19
  28. package/dist/module/components/WidgetSmall.js.map +1 -1
  29. package/dist/module/core.config.js +1 -1
  30. package/dist/module/core.config.js.map +1 -1
  31. package/dist/module/feature.config.js +17 -17
  32. package/dist/module/feature.config.js.map +1 -1
  33. package/dist/module/helper.js +10 -10
  34. package/dist/module/helper.js.map +1 -1
  35. package/dist/module/index.js +11 -11
  36. package/dist/module/index.js.map +1 -1
  37. package/dist/module/reducers/JobsReducer.js +13 -13
  38. package/dist/module/reducers/JobsReducer.js.map +1 -1
  39. package/dist/module/screens/JobTypePicker.js +17 -17
  40. package/dist/module/screens/JobTypePicker.js.map +1 -1
  41. package/dist/module/screens/MaintenancePage.js +10 -10
  42. package/dist/module/screens/MaintenancePage.js.map +1 -1
  43. package/dist/module/screens/MaintenanceUserPicker.js +129 -22
  44. package/dist/module/screens/MaintenanceUserPicker.js.map +1 -1
  45. package/dist/module/screens/RequestDetail.js +145 -145
  46. package/dist/module/screens/RequestDetail.js.map +1 -1
  47. package/dist/module/screens/RequestNotes.js +59 -59
  48. package/dist/module/screens/RequestNotes.js.map +1 -1
  49. package/dist/module/screens/ServiceRequest.js +187 -187
  50. package/dist/module/screens/ServiceRequest.js.map +1 -1
  51. package/dist/module/values.config.a.js +30 -30
  52. package/dist/module/values.config.a.js.map +1 -1
  53. package/dist/module/values.config.default.js +34 -34
  54. package/dist/module/values.config.default.js.map +1 -1
  55. package/dist/module/values.config.enquiry.js +34 -34
  56. package/dist/module/values.config.enquiry.js.map +1 -1
  57. package/dist/module/values.config.feedback.js +34 -34
  58. package/dist/module/values.config.feedback.js.map +1 -1
  59. package/dist/module/values.config.food.js +34 -34
  60. package/dist/module/values.config.food.js.map +1 -1
  61. package/dist/module/values.config.forms.js +34 -34
  62. package/dist/module/values.config.forms.js.map +1 -1
  63. package/dist/module/values.config.js +34 -34
  64. package/dist/module/values.config.js.map +1 -1
  65. package/package.json +51 -51
  66. package/src/actions/JobActions.js +67 -60
  67. package/src/actions/index.js +1 -1
  68. package/src/actions/types.js +1 -2
  69. package/src/apis/index.js +3 -3
  70. package/src/apis/maintenanceActions.js +189 -178
  71. package/src/apis/userActions.js +17 -17
  72. package/src/components/FilterPopupMenu.js +313 -256
  73. package/src/components/MaintenanceList.js +396 -317
  74. package/src/components/MaintenanceListItem.js +347 -288
  75. package/src/components/MaintenanceWidgetItem.js +145 -124
  76. package/src/components/PrioritySelectorPopup.js +81 -68
  77. package/src/components/StatusSelectorPopup.js +81 -70
  78. package/src/components/WidgetLarge.js +5 -5
  79. package/src/components/WidgetSmall.js +153 -133
  80. package/src/core.config.js +27 -3
  81. package/src/feature.config.js +62 -62
  82. package/src/helper.js +58 -53
  83. package/src/index.js +22 -22
  84. package/src/reducers/JobsReducer.js +85 -66
  85. package/src/screens/JobTypePicker.js +115 -92
  86. package/src/screens/MaintenancePage.js +89 -80
  87. package/src/screens/MaintenanceUserPicker.js +252 -100
  88. package/src/screens/RequestDetail.js +1348 -1125
  89. package/src/screens/RequestNotes.js +950 -806
  90. package/src/screens/ServiceRequest.js +1778 -1550
  91. package/src/values.config.a.js +33 -33
  92. package/src/values.config.default.js +39 -39
  93. package/src/values.config.enquiry.js +39 -39
  94. package/src/values.config.feedback.js +39 -39
  95. package/src/values.config.food.js +39 -39
  96. package/src/values.config.forms.js +39 -39
  97. package/src/values.config.js +39 -39
@@ -1,395 +1,467 @@
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, jobStatusesUpdate, jobHideSeenUpdate } from '../actions';
10
- import StatusSelectorPopup from '../components/StatusSelectorPopup';
11
- import PrioritySelectorPopup from '../components/PrioritySelectorPopup';
12
- import { getJobStatus, getJobPriority } from '../helper';
13
- import { Services } from '../feature.config';
14
- import { Colours, Helper, Components, Config } from '../core.config';
15
- import { values } from '../values.config';
1
+ import React, { Component } from "react";
2
+ import {
3
+ ScrollView,
4
+ View,
5
+ StyleSheet,
6
+ Text,
7
+ KeyboardAvoidingView,
8
+ TouchableOpacity,
9
+ ImageBackground,
10
+ Platform,
11
+ } from "react-native";
12
+ import DateTimePicker from "react-native-modal-datetime-picker";
13
+ import { Icon } from "react-native-elements";
14
+ import _ from "lodash";
15
+ import moment from "moment";
16
+ import { connect } from "react-redux";
17
+ import { maintenanceActions } from "../apis";
18
+ import { jobAdded, jobStatusesUpdate, jobHideSeenUpdate } from "../actions";
19
+ import StatusSelectorPopup from "../components/StatusSelectorPopup";
20
+ import PrioritySelectorPopup from "../components/PrioritySelectorPopup";
21
+ import { getJobStatus, getJobPriority } from "../helper";
22
+ import { Services } from "../feature.config";
23
+ import { Colours, Helper, Components, Config } from "../core.config";
24
+ import { values } from "../values.config";
16
25
 
17
26
  class RequestDetail extends Component {
18
- constructor(props) {
19
- super(props);
20
-
21
- this.state = {
22
- job: {},
23
- isDateTimePickerVisible: false,
24
- popUpType: '',
25
- status: '',
26
- priority: '',
27
- expectedDate: null,
28
- expectedDateText: '',
29
- seen: false,
30
- showMore: true,
31
- showStatusPopup: false,
32
- showPriorityPopup: false,
33
- loading: false,
34
- showFullscreenVideo: false,
35
- currentVideoUrl: '',
36
- galleryOpen: false,
37
- galleryImages: [],
38
- showMessages: false,
39
- assignees: [],
40
- selectedPDF: null,
41
- externalSync: null,
42
- loadingExternalSync: false,
43
- };
44
-
45
- this.scrollView = React.createRef();
46
- this.commentReply = React.createRef();
47
- this.commentSection = React.createRef();
48
- }
49
-
50
- componentDidMount() {
51
- this.props.jobStatusesUpdate(this.props.job.site);
52
- this.props.jobHideSeenUpdate(this.props.job.site);
53
- this.getJob();
54
- this.updateJobState(this.props.job);
55
- this.getAssignees();
56
- this.getExternalSync();
57
- }
58
-
59
- getJob = async () => {
60
- this.setState({ loading: true }, async () => {
61
- try {
62
- const res = await maintenanceActions.getJob(this.props.job.site, this.props.job.id);
63
- // console.log('getJob', JSON.stringify(res.data, null, 2));
64
- this.props.jobAdded(res.data);
65
- this.updateJobState(res.data);
66
- // Refresh external sync data when job is refreshed
67
- this.getExternalSync();
68
- } catch (error) {
69
- console.log('getJob error', error.toString());
70
- // check for 403 or 404 error
71
- if (error.response.status === 403 || error.response.status === 404) {
72
- this.setState({
73
- forbidden: true,
74
- });
75
- }
76
- console.log('getJob error', error);
77
- } finally {
78
- this.setState({ loading: false });
79
- }
80
- });
81
- };
82
-
83
- getAssignees = async () => {
84
- if (!this.hasPermission()) return;
85
- try {
86
- const res = await maintenanceActions.getAssignees(this.props.user.site);
87
- this.setState({ assignees: res.data.Users });
88
- } catch (error) {
89
- console.log('getAssignees error', error);
90
- }
91
- };
92
-
93
- getExternalSync = async () => {
94
- // Only fetch if user has maintenance tracking permission
95
- if (!this.hasPermission()) return;
96
- if (!this.props.job?.id) return;
97
-
98
- try {
99
- this.setState({ loadingExternalSync: true });
100
- const res = await maintenanceActions.getExternalSync(this.props.job.id);
101
- this.setState({ externalSync: res.data, loadingExternalSync: false });
102
- } catch (error) {
103
- // 404 is expected if no sync - don't show error
104
- if (error.response?.status !== 404) {
105
- console.log('getExternalSync error', error);
106
- }
107
- this.setState({ loadingExternalSync: false });
108
- }
109
- };
110
-
111
- updateJobState(defaultJob) {
112
- const job = _.find(this.props.jobs, j => j.id === this.props.job.id) || defaultJob;
113
- if (!job) {
114
- this.getJob();
115
- return;
116
- }
117
- const newState = { job, status: job.status };
118
- if (job.expectedDate) {
119
- newState.expectedDate = moment(job.expectedDate);
120
- newState.expectedDateText = newState.expectedDate.format('DD/MM/YYYY');
121
- }
122
- if (job.seen) newState.seen = job.seen;
123
- this.setState(newState, () => {
124
- this.markSeen();
125
- });
126
- }
127
-
128
- markSeen = () => {
129
- const { user } = this.props;
130
- const { job } = this.state;
131
- // Must have maintenance permission and not the requester
132
- // console.log('markSeen check', job.userID, user.Id);
133
- if (job.seen === true) return;
134
- if (!this.hasPermission()) return;
135
- if (user.Id === job.userID) return;
136
-
137
- this.setState({ loading: true }, async () => {
138
- try {
139
- const updated = { id: job.id, seen: true, status: job.status || 'Unassigned' };
140
- const res = await maintenanceActions.editJob(updated, user.site);
141
- // console.log('markSeen updated');
142
- this.props.jobAdded(res.data.job);
143
- this.getJob();
144
- this.setState({ loading: false, seen: true });
145
- } catch (error) {
146
- console.log('markSeen error', error);
147
- this.setState({ loading: false });
148
- }
149
- });
150
- };
151
-
152
- updateJob = () => {
153
- this.setState({ loading: true }, async () => {
154
- const { user } = this.props;
155
- const { job } = this.state;
156
- try {
157
- const updated = { id: job.id };
158
- if (this.state.expectedDate) {
159
- updated.expectedDate = moment(this.state.expectedDate)
160
- .utc()
161
- .toISOString();
162
- }
163
- const res = await maintenanceActions.editJob(updated, user.site);
164
- this.props.jobAdded(res.data.job);
165
- this.getJob();
166
- } catch (error) {
167
- console.log('updateJob error', error);
168
- } finally {
169
- this.setState({ loading: false });
170
- }
171
- });
172
- };
173
-
174
- updateJobStatus = () => {
175
- this.setState({ loading: true }, async () => {
176
- try {
177
- const res = await maintenanceActions.editJobStatus(this.props.job.id, this.state.status);
178
- this.props.jobAdded(res.data.job);
179
- this.getJob();
180
- } catch (error) {
181
- console.log('updateJobStatus error', error);
182
- } finally {
183
- this.setState({ loading: false });
184
- }
185
- });
186
- };
187
-
188
- updateJobPriority = () => {
189
- this.setState({ loading: true }, async () => {
190
- try {
191
- const res = await maintenanceActions.editJobPriority(this.props.job.id, this.state.priority);
192
- this.props.jobAdded(res.data.job);
193
- this.getJob();
194
- } catch (error) {
195
- console.log('updateJobPriority error', error);
196
- } finally {
197
- this.setState({ loading: false });
198
- }
199
- });
200
- };
201
-
202
- onPressBack = () => {
203
- Services.navigation.goBack();
204
- };
205
-
206
- onOpenStatusPicker = () => {
207
- this.setState({ showStatusPopup: true });
208
- };
209
-
210
- onCloseStatusPopup = () => {
211
- this.setState({ showStatusPopup: false });
212
- };
213
-
214
- onSelectStatus = status => {
215
- if (this.state.loading) return;
216
- this.setState({ status, showStatusPopup: false }, () => {
217
- this.updateJobStatus();
218
- });
219
- };
220
-
221
- onOpenPriorityPicker = () => {
222
- this.setState({ showPriorityPopup: true });
223
- };
224
-
225
- onClosePriorityPopup = () => {
226
- this.setState({ showPriorityPopup: false });
227
- };
228
-
229
- onSelectPriority = priority => {
230
- if (this.state.loading) return;
231
- this.setState({ priority, showPriorityPopup: false }, () => {
232
- this.updateJobPriority();
233
- });
234
- };
235
-
236
- openStaffNotes = () => {
237
- Services.navigation.navigate(values.screenRequestNotes, { job: this.state.job, onChange: this.getJob });
238
- };
239
-
240
- onOpenDatePicker = () => {
241
- this.setState({ popUpType: 'date', isDateTimePickerVisible: true });
242
- };
243
-
244
- onCloseDatePicker = () => {
245
- this.setState({ isDateTimePickerVisible: false });
246
- };
247
-
248
- onDateSelected = date => {
249
- if (this.state.popUpType === 'date') {
250
- date = moment(date);
251
- this.setState(
252
- {
253
- expectedDate: date,
254
- expectedDateText: date.format('DD/MM/YYYY'),
255
- isDateTimePickerVisible: false,
256
- },
257
- () => {
258
- this.updateJob();
259
- },
260
- );
261
- }
262
- };
263
-
264
- onToggleDetails = () => {
265
- this.setState({ showMore: !this.state.showMore });
266
- };
267
-
268
- onLeaveMessage = () => {
269
- this.setState({ showMessages: true });
270
- };
271
-
272
- onCommentsLoaded = count => {
273
- if (count > 0) {
274
- this.setState({ showMessages: true });
275
- }
276
- };
277
-
278
- onCommentAdded = () => {
279
- this.setState({ loading: true }, async () => {
280
- try {
281
- const job = await maintenanceActions.getJob(this.props.user.site, this.props.job.id);
282
- // console.log('onCommentAdded', job?.data);
283
- this.props.jobAdded(job.data);
284
- this.getJob();
285
- } catch (error) {
286
- console.log('onCommentAdded error', error);
287
- } finally {
288
- this.setState({ loading: false });
289
- }
290
- });
291
- };
292
-
293
- hasPermission = () => {
294
- const { job } = this.state;
295
- const { permissions } = this.props.user;
296
- if (_.includes(permissions, values.permissionMaintenanceTracking)) {
297
- return true;
298
- }
299
- if (_.includes(permissions, values.permissionMaintenanceAssignment)) {
300
- return job.AssigneeId === this.props.user.Id;
301
- }
302
- return false;
303
- };
304
-
305
- toggleFullscreenVideo = url => {
306
- if (typeof url !== 'string') url = '';
307
- this.setState({ showFullscreenVideo: url.length > 0, currentVideoUrl: url });
308
- };
309
-
310
- openGallery(galleryImages, index) {
311
- this.setState({
312
- galleryOpen: true,
313
- galleryImages,
314
- });
315
- this.refs.imagePopup.scrollTo(index);
316
- }
317
-
318
- closeGallery() {
319
- this.setState({
320
- galleryOpen: false,
321
- });
322
- }
323
-
324
- onOpenAssigneePicker = () => {
325
- const options = this.state.assignees.map(a => {
326
- return { key: a.id, name: a.displayName };
327
- });
328
- Services.navigation.navigate('optionSelector', {
329
- options,
330
- selection: this.state.job.AssigneeId,
331
- title: 'Assign request',
332
- onSelect: this.onSelectAssignee,
333
- });
334
- };
335
-
336
- onSelectAssignee = assignee => {
337
- this.setState({ loading: true }, async () => {
338
- try {
339
- console.log('onSelectAssignee', this.props.job.id, assignee.key);
340
- const res = await maintenanceActions.assignJob(this.props.job.id, assignee.key);
341
- this.props.jobAdded(res.data.job);
342
- this.getJob();
343
- } catch (error) {
344
- console.log('onSelectAssignee error', error);
345
- } finally {
346
- this.setState({ loading: false });
347
- }
348
- });
349
- };
350
-
351
- renderLoading() {
352
- return <Components.LoadingIndicator visible={this.state.loading} />;
353
- }
354
-
355
- renderTop() {
356
- const { status, job } = this.state;
357
- const statusOption = getJobStatus(status, this.props);
358
- const priority = getJobPriority(job.priority);
359
- const canEdit = this.hasPermission();
360
- const isStaff = this.props.user.category === 'staff';
361
- const showSeen = !status || status === getJobStatus(null, this.props).text;
362
-
363
- return (
364
- <View style={{ ...Helper.getShadowStyle() }}>
365
- <View style={styles.jobTitleContainer}>
366
- {job.jobId ? (
367
- <Text style={[styles.jobIdText, { color: this.props.colourBrandingMain }]}>{`${values.textEntityName} #${job.jobId}`}</Text>
368
- ) : null}
369
- <Text style={styles.jobTitleText}>{job.title}</Text>
370
- <View style={styles.jobTypeSeenContainer}>
371
- <View style={[styles.jobTypeContainer, { backgroundColor: Colours.hexToRGBAstring(this.props.colourBrandingMain, 0.2) }]}>
372
- <Text style={[styles.jobTypeText, { color: this.props.colourBrandingMain }]} numberOfLines={2}>
373
- {job.type}
374
- </Text>
375
- </View>
376
- {!this.props.hideSeen && showSeen && this.state.seen && (
377
- <View style={styles.jobSeenContainer}>
378
- <Icon name="check" type="font-awesome" iconStyle={[styles.jobSeenIcon, { color: this.props.colourBrandingMain }]} />
379
- <Text style={[styles.jobSeenText, { color: this.props.colourBrandingMain }]}>Seen</Text>
380
- </View>
381
- )}
382
- </View>
383
-
384
- {job.lastActivityUnix && (
385
- <View style={styles.textSectionInner}>
386
- <Text style={styles.textSectionLabel}>Last Updated On</Text>
387
- <View style={styles.textSectionTextContainer}>
388
- <Text style={styles.textSectionText}>{moment(job.lastActivityUnix).format('ddd D MMMM, h:mm A')}</Text>
389
- </View>
390
- </View>
391
- )}
392
- {/* <View style={styles.textSectionInner}>
27
+ constructor(props) {
28
+ super(props);
29
+
30
+ this.state = {
31
+ job: {},
32
+ isDateTimePickerVisible: false,
33
+ popUpType: "",
34
+ status: "",
35
+ priority: "",
36
+ expectedDate: null,
37
+ expectedDateText: "",
38
+ seen: false,
39
+ showMore: true,
40
+ showStatusPopup: false,
41
+ showPriorityPopup: false,
42
+ loading: false,
43
+ showFullscreenVideo: false,
44
+ currentVideoUrl: "",
45
+ galleryOpen: false,
46
+ galleryImages: [],
47
+ showMessages: false,
48
+ assignees: [],
49
+ selectedPDF: null,
50
+ externalSync: null,
51
+ loadingExternalSync: false,
52
+ };
53
+
54
+ this.scrollView = React.createRef();
55
+ this.commentReply = React.createRef();
56
+ this.commentSection = React.createRef();
57
+ }
58
+
59
+ componentDidMount() {
60
+ this.props.jobStatusesUpdate(this.props.job.site);
61
+ this.props.jobHideSeenUpdate(this.props.job.site);
62
+ this.getJob();
63
+ this.updateJobState(this.props.job);
64
+ this.getAssignees();
65
+ this.getExternalSync();
66
+ }
67
+
68
+ getJob = async () => {
69
+ this.setState({ loading: true }, async () => {
70
+ try {
71
+ const res = await maintenanceActions.getJob(
72
+ this.props.job.site,
73
+ this.props.job.id,
74
+ );
75
+ // console.log('getJob', JSON.stringify(res.data, null, 2));
76
+ this.props.jobAdded(res.data);
77
+ this.updateJobState(res.data);
78
+ // Refresh external sync data when job is refreshed
79
+ this.getExternalSync();
80
+ } catch (error) {
81
+ console.log("getJob error", error.toString());
82
+ // check for 403 or 404 error
83
+ if (error.response.status === 403 || error.response.status === 404) {
84
+ this.setState({
85
+ forbidden: true,
86
+ });
87
+ }
88
+ console.log("getJob error", error);
89
+ } finally {
90
+ this.setState({ loading: false });
91
+ }
92
+ });
93
+ };
94
+
95
+ getAssignees = async () => {
96
+ if (!this.hasPermission()) return;
97
+ try {
98
+ const res = await maintenanceActions.getAssignees(this.props.user.site);
99
+ this.setState({ assignees: res.data.Users });
100
+ } catch (error) {
101
+ console.log("getAssignees error", error);
102
+ }
103
+ };
104
+
105
+ getExternalSync = async () => {
106
+ // Only fetch if user has maintenance tracking permission
107
+ if (!this.hasPermission()) return;
108
+ if (!this.props.job?.id) return;
109
+
110
+ try {
111
+ this.setState({ loadingExternalSync: true });
112
+ const res = await maintenanceActions.getExternalSync(this.props.job.id);
113
+ this.setState({ externalSync: res.data, loadingExternalSync: false });
114
+ } catch (error) {
115
+ // 404 is expected if no sync - don't show error
116
+ if (error.response?.status !== 404) {
117
+ console.log("getExternalSync error", error);
118
+ }
119
+ this.setState({ loadingExternalSync: false });
120
+ }
121
+ };
122
+
123
+ updateJobState(defaultJob) {
124
+ const job =
125
+ _.find(this.props.jobs, (j) => j.id === this.props.job.id) || defaultJob;
126
+ if (!job) {
127
+ this.getJob();
128
+ return;
129
+ }
130
+ const newState = { job, status: job.status };
131
+ if (job.expectedDate) {
132
+ newState.expectedDate = moment(job.expectedDate);
133
+ newState.expectedDateText = newState.expectedDate.format("DD/MM/YYYY");
134
+ }
135
+ if (job.seen) newState.seen = job.seen;
136
+ this.setState(newState, () => {
137
+ this.markSeen();
138
+ });
139
+ }
140
+
141
+ markSeen = () => {
142
+ const { user } = this.props;
143
+ const { job } = this.state;
144
+ // Must have maintenance permission and not the requester
145
+ // console.log('markSeen check', job.userID, user.Id);
146
+ if (job.seen === true) return;
147
+ if (!this.hasPermission()) return;
148
+ if (user.Id === job.userID) return;
149
+
150
+ this.setState({ loading: true }, async () => {
151
+ try {
152
+ const updated = {
153
+ id: job.id,
154
+ seen: true,
155
+ status: job.status || "Unassigned",
156
+ };
157
+ const res = await maintenanceActions.editJob(updated, user.site);
158
+ // console.log('markSeen updated');
159
+ this.props.jobAdded(res.data.job);
160
+ this.getJob();
161
+ this.setState({ loading: false, seen: true });
162
+ } catch (error) {
163
+ console.log("markSeen error", error);
164
+ this.setState({ loading: false });
165
+ }
166
+ });
167
+ };
168
+
169
+ updateJob = () => {
170
+ this.setState({ loading: true }, async () => {
171
+ const { user } = this.props;
172
+ const { job } = this.state;
173
+ try {
174
+ const updated = { id: job.id };
175
+ if (this.state.expectedDate) {
176
+ updated.expectedDate = moment(this.state.expectedDate)
177
+ .utc()
178
+ .toISOString();
179
+ }
180
+ const res = await maintenanceActions.editJob(updated, user.site);
181
+ this.props.jobAdded(res.data.job);
182
+ this.getJob();
183
+ } catch (error) {
184
+ console.log("updateJob error", error);
185
+ } finally {
186
+ this.setState({ loading: false });
187
+ }
188
+ });
189
+ };
190
+
191
+ updateJobStatus = () => {
192
+ this.setState({ loading: true }, async () => {
193
+ try {
194
+ const res = await maintenanceActions.editJobStatus(
195
+ this.props.job.id,
196
+ this.state.status,
197
+ );
198
+ this.props.jobAdded(res.data.job);
199
+ this.getJob();
200
+ } catch (error) {
201
+ console.log("updateJobStatus error", error);
202
+ } finally {
203
+ this.setState({ loading: false });
204
+ }
205
+ });
206
+ };
207
+
208
+ updateJobPriority = () => {
209
+ this.setState({ loading: true }, async () => {
210
+ try {
211
+ const res = await maintenanceActions.editJobPriority(
212
+ this.props.job.id,
213
+ this.state.priority,
214
+ );
215
+ this.props.jobAdded(res.data.job);
216
+ this.getJob();
217
+ } catch (error) {
218
+ console.log("updateJobPriority error", error);
219
+ } finally {
220
+ this.setState({ loading: false });
221
+ }
222
+ });
223
+ };
224
+
225
+ onPressBack = () => {
226
+ Services.navigation.goBack();
227
+ };
228
+
229
+ onOpenStatusPicker = () => {
230
+ this.setState({ showStatusPopup: true });
231
+ };
232
+
233
+ onCloseStatusPopup = () => {
234
+ this.setState({ showStatusPopup: false });
235
+ };
236
+
237
+ onSelectStatus = (status) => {
238
+ if (this.state.loading) return;
239
+ this.setState({ status, showStatusPopup: false }, () => {
240
+ this.updateJobStatus();
241
+ });
242
+ };
243
+
244
+ onOpenPriorityPicker = () => {
245
+ this.setState({ showPriorityPopup: true });
246
+ };
247
+
248
+ onClosePriorityPopup = () => {
249
+ this.setState({ showPriorityPopup: false });
250
+ };
251
+
252
+ onSelectPriority = (priority) => {
253
+ if (this.state.loading) return;
254
+ this.setState({ priority, showPriorityPopup: false }, () => {
255
+ this.updateJobPriority();
256
+ });
257
+ };
258
+
259
+ openStaffNotes = () => {
260
+ Services.navigation.navigate(values.screenRequestNotes, {
261
+ job: this.state.job,
262
+ onChange: this.getJob,
263
+ });
264
+ };
265
+
266
+ onOpenDatePicker = () => {
267
+ this.setState({ popUpType: "date", isDateTimePickerVisible: true });
268
+ };
269
+
270
+ onCloseDatePicker = () => {
271
+ this.setState({ isDateTimePickerVisible: false });
272
+ };
273
+
274
+ onDateSelected = (date) => {
275
+ if (this.state.popUpType === "date") {
276
+ date = moment(date);
277
+ this.setState(
278
+ {
279
+ expectedDate: date,
280
+ expectedDateText: date.format("DD/MM/YYYY"),
281
+ isDateTimePickerVisible: false,
282
+ },
283
+ () => {
284
+ this.updateJob();
285
+ },
286
+ );
287
+ }
288
+ };
289
+
290
+ onToggleDetails = () => {
291
+ this.setState({ showMore: !this.state.showMore });
292
+ };
293
+
294
+ onLeaveMessage = () => {
295
+ this.setState({ showMessages: true });
296
+ };
297
+
298
+ onCommentsLoaded = (count) => {
299
+ if (count > 0) {
300
+ this.setState({ showMessages: true });
301
+ }
302
+ };
303
+
304
+ onCommentAdded = () => {
305
+ this.setState({ loading: true }, async () => {
306
+ try {
307
+ const job = await maintenanceActions.getJob(
308
+ this.props.user.site,
309
+ this.props.job.id,
310
+ );
311
+ // console.log('onCommentAdded', job?.data);
312
+ this.props.jobAdded(job.data);
313
+ this.getJob();
314
+ } catch (error) {
315
+ console.log("onCommentAdded error", error);
316
+ } finally {
317
+ this.setState({ loading: false });
318
+ }
319
+ });
320
+ };
321
+
322
+ hasPermission = () => {
323
+ const { job } = this.state;
324
+ const { permissions } = this.props.user;
325
+ if (_.includes(permissions, values.permissionMaintenanceTracking)) {
326
+ return true;
327
+ }
328
+ if (_.includes(permissions, values.permissionMaintenanceAssignment)) {
329
+ return job.AssigneeId === this.props.user.Id;
330
+ }
331
+ return false;
332
+ };
333
+
334
+ toggleFullscreenVideo = (url) => {
335
+ if (typeof url !== "string") url = "";
336
+ this.setState({
337
+ showFullscreenVideo: url.length > 0,
338
+ currentVideoUrl: url,
339
+ });
340
+ };
341
+
342
+ openGallery(galleryImages, index) {
343
+ this.setState({
344
+ galleryOpen: true,
345
+ galleryImages,
346
+ });
347
+ this.refs.imagePopup.scrollTo(index);
348
+ }
349
+
350
+ closeGallery() {
351
+ this.setState({
352
+ galleryOpen: false,
353
+ });
354
+ }
355
+
356
+ onOpenAssigneePicker = () => {
357
+ const options = this.state.assignees.map((a) => {
358
+ return { key: a.id, name: a.displayName };
359
+ });
360
+ Services.navigation.navigate("optionSelector", {
361
+ options,
362
+ selection: this.state.job.AssigneeId,
363
+ title: "Assign request",
364
+ onSelect: this.onSelectAssignee,
365
+ });
366
+ };
367
+
368
+ onSelectAssignee = (assignee) => {
369
+ this.setState({ loading: true }, async () => {
370
+ try {
371
+ console.log("onSelectAssignee", this.props.job.id, assignee.key);
372
+ const res = await maintenanceActions.assignJob(
373
+ this.props.job.id,
374
+ assignee.key,
375
+ );
376
+ this.props.jobAdded(res.data.job);
377
+ this.getJob();
378
+ } catch (error) {
379
+ console.log("onSelectAssignee error", error);
380
+ } finally {
381
+ this.setState({ loading: false });
382
+ }
383
+ });
384
+ };
385
+
386
+ renderLoading() {
387
+ return <Components.LoadingIndicator visible={this.state.loading} />;
388
+ }
389
+
390
+ renderTop() {
391
+ const { status, job } = this.state;
392
+ const statusOption = getJobStatus(status, this.props);
393
+ const priority = getJobPriority(job.priority);
394
+ const canEdit = this.hasPermission();
395
+ const isStaff = this.props.user.category === "staff";
396
+ const showSeen = !status || status === getJobStatus(null, this.props).text;
397
+
398
+ return (
399
+ <View style={{ ...Helper.getShadowStyle() }}>
400
+ <View style={styles.jobTitleContainer}>
401
+ {job.jobId ? (
402
+ <Text
403
+ style={[
404
+ styles.jobIdText,
405
+ { color: this.props.colourBrandingMain },
406
+ ]}
407
+ >{`${values.textEntityName} #${job.jobId}`}</Text>
408
+ ) : null}
409
+ <Text style={styles.jobTitleText}>{job.title}</Text>
410
+ <View style={styles.jobTypeSeenContainer}>
411
+ <View
412
+ style={[
413
+ styles.jobTypeContainer,
414
+ {
415
+ backgroundColor: Colours.hexToRGBAstring(
416
+ this.props.colourBrandingMain,
417
+ 0.2,
418
+ ),
419
+ },
420
+ ]}
421
+ >
422
+ <Text
423
+ style={[
424
+ styles.jobTypeText,
425
+ { color: this.props.colourBrandingMain },
426
+ ]}
427
+ numberOfLines={2}
428
+ >
429
+ {job.type}
430
+ </Text>
431
+ </View>
432
+ {!this.props.hideSeen && showSeen && this.state.seen && (
433
+ <View style={styles.jobSeenContainer}>
434
+ <Icon
435
+ name="check"
436
+ type="font-awesome"
437
+ iconStyle={[
438
+ styles.jobSeenIcon,
439
+ { color: this.props.colourBrandingMain },
440
+ ]}
441
+ />
442
+ <Text
443
+ style={[
444
+ styles.jobSeenText,
445
+ { color: this.props.colourBrandingMain },
446
+ ]}
447
+ >
448
+ Seen
449
+ </Text>
450
+ </View>
451
+ )}
452
+ </View>
453
+
454
+ {job.lastActivityUnix && (
455
+ <View style={styles.textSectionInner}>
456
+ <Text style={styles.textSectionLabel}>Last Updated On</Text>
457
+ <View style={styles.textSectionTextContainer}>
458
+ <Text style={styles.textSectionText}>
459
+ {moment(job.lastActivityUnix).format("ddd D MMMM, h:mm A")}
460
+ </Text>
461
+ </View>
462
+ </View>
463
+ )}
464
+ {/* <View style={styles.textSectionInner}>
393
465
  <Text style={styles.textSectionLabel}>Expected Date</Text>
394
466
  <TouchableOpacity onPress={this.onOpenDatePicker}>
395
467
  <View style={styles.textSectionTextContainer}>
@@ -402,745 +474,896 @@ class RequestDetail extends Component {
402
474
  </View>
403
475
  </TouchableOpacity>
404
476
  </View> */}
405
- </View>
406
- <View style={styles.jobInfoContainer}>
407
- <View style={styles.jobStatusExpectedContainer}>
408
- <View style={styles.jobStatusOuterContainer}>
409
- <Text style={styles.jobStatusHeading}>STATUS</Text>
410
- <TouchableOpacity onPress={canEdit ? this.onOpenStatusPicker : null}>
411
- <View style={[styles.jobStatusContainer, { backgroundColor: statusOption.color }]}>
412
- <Text style={styles.jobStatusText}>{statusOption?.text}</Text>
413
- </View>
414
- </TouchableOpacity>
415
- </View>
416
- {this.hasPermission() && (
417
- <View style={styles.jobStatusOuterContainer}>
418
- <Text style={styles.jobStatusHeading}>STAFF NOTES</Text>
419
- <TouchableOpacity onPress={this.openStaffNotes}>
420
- <View style={[styles.jobStatusContainer, { backgroundColor: this.props.colourBrandingMain }]}>
421
- <Icon name="pencil-square-o" type="font-awesome" iconStyle={styles.jobStatusIcon} />
422
- <Text style={styles.jobStatusText}>Notes ({(job.Notes || []).length})</Text>
423
- </View>
424
- </TouchableOpacity>
425
- </View>
426
- )}
427
- </View>
428
- {isStaff && (
429
- <View style={styles.jobPriorityOuterContainer}>
430
- <Text style={styles.jobStatusHeading}>PRIORITY</Text>
431
- <TouchableOpacity onPress={canEdit ? this.onOpenPriorityPicker : null}>
432
- <View style={[styles.jobStatusContainer, { backgroundColor: priority.color }]}>
433
- <Text style={styles.jobStatusText}>{priority.label}</Text>
434
- </View>
435
- </TouchableOpacity>
436
- </View>
437
- )}
438
- </View>
439
- </View>
440
- );
441
- }
442
-
443
- renderPlayableImageUrl(images, index, containerStyle, showMore) {
444
- const url = images[index || 0];
445
- const thumbUrl = Helper.getThumb300(url);
446
-
447
- if (Helper.isVideo(url)) {
448
- return (
449
- <ImageBackground style={[{ flex: 1 }, containerStyle]} source={{ uri: thumbUrl }}>
450
- <View style={styles.imagePlayContainer}>
451
- <TouchableOpacity onPress={this.toggleFullscreenVideo.bind(this, url)}>
452
- <Icon name="play" type="font-awesome" iconStyle={styles.imageControlIcon} />
453
- </TouchableOpacity>
454
- </View>
455
- </ImageBackground>
456
- );
457
- }
458
-
459
- const imageUrl = Helper.get1400(url);
460
- return (
461
- <TouchableOpacity style={containerStyle} onPress={this.openGallery.bind(this, images, index || 0)}>
462
- <ImageBackground style={styles.imageContainer} source={{ uri: imageUrl }}>
463
- {showMore && <Text style={styles.plusImages}>+{this.state.job.images.length - 2}</Text>}
464
- </ImageBackground>
465
- </TouchableOpacity>
466
- );
467
- }
468
-
469
- renderPlayableImage(images, index, containerStyle, showMore) {
470
- return this.renderPlayableImageUrl(images, index, containerStyle, showMore);
471
- }
472
-
473
- renderImage(images, image = null) {
474
- if (!_.isNil(images) && !_.isEmpty(images)) {
475
- if (images.length >= 2) {
476
- return (
477
- <View style={styles.sideBySideImages}>
478
- {this.renderPlayableImage(images, 0, styles.sideBySideImageContainer)}
479
- {this.renderPlayableImage(images, 1, styles.sideBySideImageContainer, images.length > 2)}
480
- </View>
481
- );
482
- } else {
483
- return <View style={styles.singleImageContainer}>{this.renderPlayableImage(images, 0)}</View>;
484
- }
485
- } else if (!_.isNil(image)) {
486
- return <View style={styles.singleImageContainer}>{this.renderPlayableImageUrl([image], 0)}</View>;
487
- }
488
- return null;
489
- }
490
-
491
- renderDocument(documents) {
492
- const { colourBrandingMain } = this.props;
493
-
494
- if (!_.isNil(documents) && !_.isEmpty(documents)) {
495
- return documents.map((document, index) => {
496
- return (
497
- <TouchableOpacity key={index} style={styles.documentContainer} onPress={() => this.setState({ selectedPDF: document })}>
498
- <View style={{ ...styles.documentTypeContainer, backgroundColor: colourBrandingMain }}>
499
- <Text style={styles.documentTypeText}>{document.ext}</Text>
500
- </View>
501
- <Text style={styles.documentText}>{`${document.name}${document.uploading ? ` - ${document.uploadProgress}` : ''}`}</Text>
502
- </TouchableOpacity>
503
- );
504
- });
505
- }
506
- return null;
507
- }
508
-
509
- renderImagePopup() {
510
- return (
511
- <Components.ImagePopup
512
- visible={this.state.galleryOpen}
513
- images={this.state.galleryImages}
514
- onClose={this.closeGallery.bind(this)}
515
- ref="imagePopup"
516
- />
517
- );
518
- }
519
-
520
- renderDocumentPopup() {
521
- if (_.isEmpty(this.state.selectedPDF)) return null;
522
- return (
523
- <Components.PDFPopup
524
- source={this.state.selectedPDF.url}
525
- onClose={() => this.setState({ selectedPDF: null })}
526
- title={this.state.selectedPDF.name}
527
- pdfCount={1}
528
- />
529
- );
530
- }
531
-
532
- renderAssignee() {
533
- const { job } = this.state;
534
- if (!this.hasPermission() && !job.Assignee) return null;
535
-
536
- const content = (
537
- <View>
538
- <Text style={styles.locationLabel}>Assigned To</Text>
539
- <View>
540
- {job.Assignee && (
541
- <View style={styles.profileContainer}>
542
- <Components.ProfilePic ProfilePic={job.Assignee.profilePic} Diameter={40} />
543
- <View style={styles.nameContainer}>
544
- <Text style={styles.nameText}>{job.Assignee.displayName}</Text>
545
- </View>
546
- </View>
547
- )}
548
- </View>
549
- </View>
550
- );
551
-
552
- if (this.hasPermission()) {
553
- return (
554
- <Components.FormCardSectionOptionLauncher
555
- onPress={this.onOpenAssigneePicker}
556
- title="Assigned To"
557
- value={job.Assignee ? job.Assignee.displayName : 'Unassigned'}
558
- textStyle={styles.detailsText}
559
- sectionStyle={styles.detailsSection}
560
- >
561
- {content}
562
- </Components.FormCardSectionOptionLauncher>
563
- );
564
- }
565
- return content;
566
- }
567
-
568
- renderExternalSync() {
569
- const { externalSync, loadingExternalSync } = this.state;
570
-
571
- // Only show if user has permission and external sync data exists
572
- if (!this.hasPermission()) return null;
573
- if (!externalSync || loadingExternalSync) return null;
574
-
575
- return (
576
- <View style={[styles.externalSyncContainer, Helper.getShadowStyle()]}>
577
- <View style={styles.externalSyncHeader}>
578
- <Text style={styles.externalSyncTitle}>External Sync</Text>
579
- <Icon name="sync" type="material" iconStyle={[styles.externalSyncIcon, { color: this.props.colourBrandingMain }]} />
580
- </View>
581
- <View style={styles.externalSyncContent}>
582
- {externalSync.systemType && (
583
- <View style={styles.externalSyncRow}>
584
- <Text style={styles.externalSyncLabel}>System:</Text>
585
- <Text style={styles.externalSyncValue}>{externalSync.systemType}</Text>
586
- </View>
587
- )}
588
- {externalSync.externalId && (
589
- <View style={styles.externalSyncRow}>
590
- <Text style={styles.externalSyncLabel}>External ID:</Text>
591
- <Text style={styles.externalSyncValue}>{externalSync.externalId}</Text>
592
- </View>
593
- )}
594
- {externalSync.syncedAt && (
595
- <View style={styles.externalSyncRow}>
596
- <Text style={styles.externalSyncLabel}>Synced:</Text>
597
- <Text style={styles.externalSyncValue}>{moment(externalSync.syncedAt).format('D MMM YYYY h:mma')}</Text>
598
- </View>
599
- )}
600
- </View>
601
- </View>
602
- );
603
- }
604
-
605
- renderCustomFields() {
606
- const { job } = this.state;
607
- const { customFields } = job;
608
-
609
- const renderAnswer = field => {
610
- switch (field.type) {
611
- case 'date':
612
- return <Text style={styles.customText}>{field.answer ? moment(field.answer, 'YYYY-MM-DD').format('DD MMM YYYY') : ''}</Text>;
613
- case 'time':
614
- return <Text style={styles.customText}>{field.answer ? moment(field.answer, 'HH:mm').format('h:mm a') : ''}</Text>;
615
- case 'yn':
616
- return <Text style={styles.customText}>{field.answer ? 'Yes' : 'No'}</Text>;
617
- case 'checkbox':
618
- return <Text style={styles.customText}>{field.answer && Array.isArray(field.answer) ? field.answer.join(', ') : ''}</Text>;
619
- case 'image':
620
- return <View style={styles.customImage}>{this.renderImage(field.answer)}</View>;
621
- case 'document':
622
- return <View style={styles.customDocument}>{this.renderDocument(field.answer)}</View>;
623
- default:
624
- return <Text style={styles.customText}>{field.answer}</Text>;
625
- }
626
- };
627
-
628
- return customFields.map((field, index) => {
629
- if (['staticTitle', 'staticText'].includes(field.type)) return null;
630
- if (_.isNil(field.answer) || field.answer === '' || (Array.isArray(field.answer) && field.answer.length === 0)) return null;
631
- return (
632
- <View key={index}>
633
- <Text style={styles.customLabel}>{field.label}</Text>
634
- {renderAnswer(field)}
635
- </View>
636
- );
637
- });
638
- }
639
-
640
- rendeDetails() {
641
- const { job } = this.state;
642
- const { customFields } = job;
643
- const hasCustomFields = customFields && customFields.length > 0;
644
-
645
- return (
646
- <View>
647
- <Components.FormCardSectionOptionLauncher
648
- onPress={this.onToggleDetails}
649
- title="Details"
650
- icon={this.state.showMore ? 'angle-up' : 'angle-down'}
651
- textStyle={styles.detailsText}
652
- sectionStyle={styles.detailsSection}
653
- />
654
- {this.state.showMore && (
655
- <View>
656
- {hasCustomFields ? this.renderCustomFields() : null}
657
- {!hasCustomFields ? (
658
- <>
659
- {this.renderImage(job.images, job.image)}
660
- {!_.isEmpty(job.description) && (
661
- <Text numberOfLines={10} style={styles.jobDescriptionText}>
662
- {job.description}
663
- </Text>
664
- )}
665
- </>
666
- ) : null}
667
- <Text style={styles.locationLabel}>Address</Text>
668
- <Text style={styles.locationText}>{job.room}</Text>
669
- {!hasCustomFields && job.isHome ? (
670
- <View style={styles.detailsSection}>
671
- <Text style={styles.locationLabel}>Must be home</Text>
672
- <Text style={styles.locationText}>{job.homeText}</Text>
673
- </View>
674
- ) : null}
675
- <Text style={styles.requesterLabel}>Submitted By</Text>
676
- <View style={styles.profileContainer}>
677
- <Components.ProfilePic ProfilePic={job.userProfilePic} Diameter={40} />
678
- <View style={styles.nameContainer}>
679
- <Text style={styles.nameText}>{job.userName}</Text>
680
- {!_.isEmpty(job.phone) && <Text style={styles.phoneText}>{job.phone}</Text>}
681
- </View>
682
- </View>
683
- {this.renderExternalSync()}
684
- </View>
685
- )}
686
- </View>
687
- );
688
- }
689
-
690
- renderMessages() {
691
- return (
692
- <View>
693
- <Components.CommentSection
694
- ref={this.commentSection}
695
- commentReply={this.commentReply}
696
- scrollView={this.scrollView}
697
- adminPermission={values.permissionMaintenanceTracking}
698
- entityType={values.commentKey}
699
- entityId={this.props.job.id}
700
- entityName={this.props.job.title}
701
- site={this.state.job.site || this.state.job.location}
702
- live
703
- refreshFrequency={10000}
704
- placeHolder={''}
705
- style={{ flex: 1, paddingHorizontal: 0, paddingTop: 16 }}
706
- onCommentsLoaded={this.onCommentsLoaded}
707
- onCommentAdded={this.onCommentAdded}
708
- hideAddComment
709
- disableFlag
710
- />
711
- {!this.state.showMessages && (
712
- <Components.InlineButton
713
- onPress={this.onLeaveMessage}
714
- color={this.props.colourBrandingMain}
715
- touchableStyle={{ marginTop: 10 }}
716
- style={{ height: 36 }}
717
- textStyle={{ color: '#fff' }}
718
- fullWidth
719
- >
720
- Leave Message
721
- </Components.InlineButton>
722
- )}
723
- </View>
724
- );
725
- }
726
-
727
- renderMessagesReply() {
728
- if (!this.state.showMessages) return null;
729
-
730
- return (
731
- <Components.CommentReply
732
- ref={this.commentReply}
733
- commentSection={this.commentSection}
734
- scrollView={this.scrollView}
735
- entityType={values.commentKey}
736
- entityId={this.props.job.id}
737
- entityName={this.props.job.title}
738
- site={this.state.job.site || this.state.job.location}
739
- // noScroll={true}
740
- // style={{ position: 'absolute', bottom: 0, left: 0, right: 0 }}
741
- />
742
- );
743
- }
744
-
745
- renderStatusPopup() {
746
- if (!this.state.showStatusPopup) return null;
747
- return <StatusSelectorPopup onClose={this.onCloseStatusPopup} onSelect={this.onSelectStatus} />;
748
- }
749
-
750
- renderPriorityPopup() {
751
- if (!this.state.showPriorityPopup) return null;
752
- return <PrioritySelectorPopup onClose={this.onClosePriorityPopup} onSelect={this.onSelectPriority} />;
753
- }
754
-
755
- render() {
756
- if (this.state.forbidden) {
757
- return <Components.Forbidden />;
758
- }
759
- return (
760
- <KeyboardAvoidingView behavior={Platform.OS === 'ios' && 'padding'} style={styles.container}>
761
- <Components.Header leftIcon="angle-left" onPressLeft={this.onPressBack} text={Config.env.strings.MAINTENANCE_REQUEST_DETAILS} />
762
- {this.renderLoading()}
763
- <ScrollView ref={this.scrollView} contentContainerStyle={{ paddingBottom: 26 }} style={{ height: '100%' }}>
764
- <View style={styles.innerContainer}>
765
- {this.renderTop()}
766
- {this.renderAssignee()}
767
- {this.rendeDetails()}
768
- {this.renderMessages()}
769
- </View>
770
- </ScrollView>
771
- {this.renderMessagesReply()}
772
- {this.renderStatusPopup()}
773
- {this.renderPriorityPopup()}
774
- {this.renderImagePopup()}
775
- {this.renderDocumentPopup()}
776
- <DateTimePicker
777
- isVisible={this.state.isDateTimePickerVisible}
778
- onConfirm={this.onDateSelected}
779
- onCancel={this.onCloseDatePicker}
780
- mode={this.state.popUpType}
781
- headerTextIOS={`Pick a ${this.state.popUpType}`}
782
- />
783
- </KeyboardAvoidingView>
784
- );
785
- }
477
+ </View>
478
+ <View style={styles.jobInfoContainer}>
479
+ <View style={styles.jobStatusExpectedContainer}>
480
+ <View style={styles.jobStatusOuterContainer}>
481
+ <Text style={styles.jobStatusHeading}>STATUS</Text>
482
+ <TouchableOpacity
483
+ onPress={canEdit ? this.onOpenStatusPicker : null}
484
+ >
485
+ <View
486
+ style={[
487
+ styles.jobStatusContainer,
488
+ { backgroundColor: statusOption.color },
489
+ ]}
490
+ >
491
+ <Text style={styles.jobStatusText}>{statusOption?.text}</Text>
492
+ </View>
493
+ </TouchableOpacity>
494
+ </View>
495
+ {this.hasPermission() && (
496
+ <View style={styles.jobStatusOuterContainer}>
497
+ <Text style={styles.jobStatusHeading}>STAFF NOTES</Text>
498
+ <TouchableOpacity onPress={this.openStaffNotes}>
499
+ <View
500
+ style={[
501
+ styles.jobStatusContainer,
502
+ { backgroundColor: this.props.colourBrandingMain },
503
+ ]}
504
+ >
505
+ <Icon
506
+ name="pencil-square-o"
507
+ type="font-awesome"
508
+ iconStyle={styles.jobStatusIcon}
509
+ />
510
+ <Text style={styles.jobStatusText}>
511
+ Notes ({(job.Notes || []).length})
512
+ </Text>
513
+ </View>
514
+ </TouchableOpacity>
515
+ </View>
516
+ )}
517
+ </View>
518
+ {isStaff && (
519
+ <View style={styles.jobPriorityOuterContainer}>
520
+ <Text style={styles.jobStatusHeading}>PRIORITY</Text>
521
+ <TouchableOpacity
522
+ onPress={canEdit ? this.onOpenPriorityPicker : null}
523
+ >
524
+ <View
525
+ style={[
526
+ styles.jobStatusContainer,
527
+ { backgroundColor: priority.color },
528
+ ]}
529
+ >
530
+ <Text style={styles.jobStatusText}>{priority.label}</Text>
531
+ </View>
532
+ </TouchableOpacity>
533
+ </View>
534
+ )}
535
+ </View>
536
+ </View>
537
+ );
538
+ }
539
+
540
+ renderPlayableImageUrl(images, index, containerStyle, showMore) {
541
+ const url = images[index || 0];
542
+ const thumbUrl = Helper.getThumb300(url);
543
+
544
+ if (Helper.isVideo(url)) {
545
+ return (
546
+ <ImageBackground
547
+ style={[{ flex: 1 }, containerStyle]}
548
+ source={{ uri: thumbUrl }}
549
+ >
550
+ <View style={styles.imagePlayContainer}>
551
+ <TouchableOpacity
552
+ onPress={this.toggleFullscreenVideo.bind(this, url)}
553
+ >
554
+ <Icon
555
+ name="play"
556
+ type="font-awesome"
557
+ iconStyle={styles.imageControlIcon}
558
+ />
559
+ </TouchableOpacity>
560
+ </View>
561
+ </ImageBackground>
562
+ );
563
+ }
564
+
565
+ const imageUrl = Helper.get1400(url);
566
+ return (
567
+ <TouchableOpacity
568
+ style={containerStyle}
569
+ onPress={this.openGallery.bind(this, images, index || 0)}
570
+ >
571
+ <ImageBackground
572
+ style={styles.imageContainer}
573
+ source={{ uri: imageUrl }}
574
+ >
575
+ {showMore && (
576
+ <Text style={styles.plusImages}>
577
+ +{this.state.job.images.length - 2}
578
+ </Text>
579
+ )}
580
+ </ImageBackground>
581
+ </TouchableOpacity>
582
+ );
583
+ }
584
+
585
+ renderPlayableImage(images, index, containerStyle, showMore) {
586
+ return this.renderPlayableImageUrl(images, index, containerStyle, showMore);
587
+ }
588
+
589
+ renderImage(images, image = null) {
590
+ if (!_.isNil(images) && !_.isEmpty(images)) {
591
+ if (images.length >= 2) {
592
+ return (
593
+ <View style={styles.sideBySideImages}>
594
+ {this.renderPlayableImage(
595
+ images,
596
+ 0,
597
+ styles.sideBySideImageContainer,
598
+ )}
599
+ {this.renderPlayableImage(
600
+ images,
601
+ 1,
602
+ styles.sideBySideImageContainer,
603
+ images.length > 2,
604
+ )}
605
+ </View>
606
+ );
607
+ } else {
608
+ return (
609
+ <View style={styles.singleImageContainer}>
610
+ {this.renderPlayableImage(images, 0)}
611
+ </View>
612
+ );
613
+ }
614
+ } else if (!_.isNil(image)) {
615
+ return (
616
+ <View style={styles.singleImageContainer}>
617
+ {this.renderPlayableImageUrl([image], 0)}
618
+ </View>
619
+ );
620
+ }
621
+ return null;
622
+ }
623
+
624
+ renderDocument(documents) {
625
+ const { colourBrandingMain } = this.props;
626
+
627
+ if (!_.isNil(documents) && !_.isEmpty(documents)) {
628
+ return documents.map((document, index) => {
629
+ return (
630
+ <TouchableOpacity
631
+ key={index}
632
+ style={styles.documentContainer}
633
+ onPress={() => this.setState({ selectedPDF: document })}
634
+ >
635
+ <View
636
+ style={{
637
+ ...styles.documentTypeContainer,
638
+ backgroundColor: colourBrandingMain,
639
+ }}
640
+ >
641
+ <Text style={styles.documentTypeText}>{document.ext}</Text>
642
+ </View>
643
+ <Text
644
+ style={styles.documentText}
645
+ >{`${document.name}${document.uploading ? ` - ${document.uploadProgress}` : ""}`}</Text>
646
+ </TouchableOpacity>
647
+ );
648
+ });
649
+ }
650
+ return null;
651
+ }
652
+
653
+ renderImagePopup() {
654
+ return (
655
+ <Components.ImagePopup
656
+ visible={this.state.galleryOpen}
657
+ images={this.state.galleryImages}
658
+ onClose={this.closeGallery.bind(this)}
659
+ ref="imagePopup"
660
+ />
661
+ );
662
+ }
663
+
664
+ renderDocumentPopup() {
665
+ if (_.isEmpty(this.state.selectedPDF)) return null;
666
+ return (
667
+ <Components.PDFPopup
668
+ source={this.state.selectedPDF.url}
669
+ onClose={() => this.setState({ selectedPDF: null })}
670
+ title={this.state.selectedPDF.name}
671
+ pdfCount={1}
672
+ />
673
+ );
674
+ }
675
+
676
+ renderAssignee() {
677
+ const { job } = this.state;
678
+ if (!this.hasPermission() && !job.Assignee) return null;
679
+
680
+ const content = (
681
+ <View>
682
+ <Text style={styles.locationLabel}>Assigned To</Text>
683
+ <View>
684
+ {job.Assignee && (
685
+ <View style={styles.profileContainer}>
686
+ <Components.ProfilePic
687
+ ProfilePic={job.Assignee.profilePic}
688
+ Diameter={40}
689
+ />
690
+ <View style={styles.nameContainer}>
691
+ <Text style={styles.nameText}>{job.Assignee.displayName}</Text>
692
+ </View>
693
+ </View>
694
+ )}
695
+ </View>
696
+ </View>
697
+ );
698
+
699
+ if (this.hasPermission()) {
700
+ return (
701
+ <Components.FormCardSectionOptionLauncher
702
+ onPress={this.onOpenAssigneePicker}
703
+ title="Assigned To"
704
+ value={job.Assignee ? job.Assignee.displayName : "Unassigned"}
705
+ textStyle={styles.detailsText}
706
+ sectionStyle={styles.detailsSection}
707
+ >
708
+ {content}
709
+ </Components.FormCardSectionOptionLauncher>
710
+ );
711
+ }
712
+ return content;
713
+ }
714
+
715
+ renderExternalSync() {
716
+ const { externalSync, loadingExternalSync } = this.state;
717
+
718
+ // Only show if user has permission and external sync data exists
719
+ if (!this.hasPermission()) return null;
720
+ if (!externalSync || loadingExternalSync) return null;
721
+
722
+ return (
723
+ <View style={[styles.externalSyncContainer, Helper.getShadowStyle()]}>
724
+ <View style={styles.externalSyncHeader}>
725
+ <Text style={styles.externalSyncTitle}>External Sync</Text>
726
+ <Icon
727
+ name="sync"
728
+ type="material"
729
+ iconStyle={[
730
+ styles.externalSyncIcon,
731
+ { color: this.props.colourBrandingMain },
732
+ ]}
733
+ />
734
+ </View>
735
+ <View style={styles.externalSyncContent}>
736
+ {externalSync.systemType && (
737
+ <View style={styles.externalSyncRow}>
738
+ <Text style={styles.externalSyncLabel}>System:</Text>
739
+ <Text style={styles.externalSyncValue}>
740
+ {externalSync.systemType}
741
+ </Text>
742
+ </View>
743
+ )}
744
+ {externalSync.externalId && (
745
+ <View style={styles.externalSyncRow}>
746
+ <Text style={styles.externalSyncLabel}>External ID:</Text>
747
+ <Text style={styles.externalSyncValue}>
748
+ {externalSync.externalId}
749
+ </Text>
750
+ </View>
751
+ )}
752
+ {externalSync.syncedAt && (
753
+ <View style={styles.externalSyncRow}>
754
+ <Text style={styles.externalSyncLabel}>Synced:</Text>
755
+ <Text style={styles.externalSyncValue}>
756
+ {moment(externalSync.syncedAt).format("D MMM YYYY h:mma")}
757
+ </Text>
758
+ </View>
759
+ )}
760
+ </View>
761
+ </View>
762
+ );
763
+ }
764
+
765
+ renderCustomFields() {
766
+ const { job } = this.state;
767
+ const { customFields } = job;
768
+
769
+ const renderAnswer = (field) => {
770
+ switch (field.type) {
771
+ case "date":
772
+ return (
773
+ <Text style={styles.customText}>
774
+ {field.answer
775
+ ? moment(field.answer, "YYYY-MM-DD").format("DD MMM YYYY")
776
+ : ""}
777
+ </Text>
778
+ );
779
+ case "time":
780
+ return (
781
+ <Text style={styles.customText}>
782
+ {field.answer
783
+ ? moment(field.answer, "HH:mm").format("h:mm a")
784
+ : ""}
785
+ </Text>
786
+ );
787
+ case "yn":
788
+ return (
789
+ <Text style={styles.customText}>{field.answer ? "Yes" : "No"}</Text>
790
+ );
791
+ case "checkbox":
792
+ return (
793
+ <Text style={styles.customText}>
794
+ {field.answer && Array.isArray(field.answer)
795
+ ? field.answer.join(", ")
796
+ : ""}
797
+ </Text>
798
+ );
799
+ case "image":
800
+ return (
801
+ <View style={styles.customImage}>
802
+ {this.renderImage(field.answer)}
803
+ </View>
804
+ );
805
+ case "document":
806
+ return (
807
+ <View style={styles.customDocument}>
808
+ {this.renderDocument(field.answer)}
809
+ </View>
810
+ );
811
+ default:
812
+ return <Text style={styles.customText}>{field.answer}</Text>;
813
+ }
814
+ };
815
+
816
+ return customFields.map((field, index) => {
817
+ if (["staticTitle", "staticText"].includes(field.type)) return null;
818
+ if (
819
+ _.isNil(field.answer) ||
820
+ field.answer === "" ||
821
+ (Array.isArray(field.answer) && field.answer.length === 0)
822
+ )
823
+ return null;
824
+ return (
825
+ <View key={index}>
826
+ <Text style={styles.customLabel}>{field.label}</Text>
827
+ {renderAnswer(field)}
828
+ </View>
829
+ );
830
+ });
831
+ }
832
+
833
+ rendeDetails() {
834
+ const { job } = this.state;
835
+ const { customFields } = job;
836
+ const hasCustomFields = customFields && customFields.length > 0;
837
+
838
+ return (
839
+ <View>
840
+ <Components.FormCardSectionOptionLauncher
841
+ onPress={this.onToggleDetails}
842
+ title="Details"
843
+ icon={this.state.showMore ? "angle-up" : "angle-down"}
844
+ textStyle={styles.detailsText}
845
+ sectionStyle={styles.detailsSection}
846
+ />
847
+ {this.state.showMore && (
848
+ <View>
849
+ {hasCustomFields ? this.renderCustomFields() : null}
850
+ {!hasCustomFields ? (
851
+ <>
852
+ {this.renderImage(job.images, job.image)}
853
+ {!_.isEmpty(job.description) && (
854
+ <Text numberOfLines={10} style={styles.jobDescriptionText}>
855
+ {job.description}
856
+ </Text>
857
+ )}
858
+ </>
859
+ ) : null}
860
+ <Text style={styles.locationLabel}>Address</Text>
861
+ <Text style={styles.locationText}>{job.room}</Text>
862
+ {!hasCustomFields && job.isHome ? (
863
+ <View style={styles.detailsSection}>
864
+ <Text style={styles.locationLabel}>Must be home</Text>
865
+ <Text style={styles.locationText}>{job.homeText}</Text>
866
+ </View>
867
+ ) : null}
868
+ <Text style={styles.requesterLabel}>Submitted By</Text>
869
+ <View style={styles.profileContainer}>
870
+ <Components.ProfilePic
871
+ ProfilePic={job.userProfilePic}
872
+ Diameter={40}
873
+ />
874
+ <View style={styles.nameContainer}>
875
+ <Text style={styles.nameText}>{job.userName}</Text>
876
+ {!_.isEmpty(job.phone) && (
877
+ <Text style={styles.phoneText}>{job.phone}</Text>
878
+ )}
879
+ </View>
880
+ </View>
881
+ {this.renderExternalSync()}
882
+ </View>
883
+ )}
884
+ </View>
885
+ );
886
+ }
887
+
888
+ renderMessages() {
889
+ return (
890
+ <View>
891
+ <Components.CommentSection
892
+ ref={this.commentSection}
893
+ commentReply={this.commentReply}
894
+ scrollView={this.scrollView}
895
+ adminPermission={values.permissionMaintenanceTracking}
896
+ entityType={values.commentKey}
897
+ entityId={this.props.job.id}
898
+ entityName={this.props.job.title}
899
+ site={this.state.job.site || this.state.job.location}
900
+ live
901
+ refreshFrequency={10000}
902
+ placeHolder={""}
903
+ style={{ flex: 1, paddingHorizontal: 0, paddingTop: 16 }}
904
+ onCommentsLoaded={this.onCommentsLoaded}
905
+ onCommentAdded={this.onCommentAdded}
906
+ hideAddComment
907
+ disableFlag
908
+ />
909
+ {!this.state.showMessages && (
910
+ <Components.InlineButton
911
+ onPress={this.onLeaveMessage}
912
+ color={this.props.colourBrandingMain}
913
+ touchableStyle={{ marginTop: 10 }}
914
+ style={{ height: 36 }}
915
+ textStyle={{ color: "#fff" }}
916
+ fullWidth
917
+ >
918
+ Leave Message
919
+ </Components.InlineButton>
920
+ )}
921
+ </View>
922
+ );
923
+ }
924
+
925
+ renderMessagesReply() {
926
+ if (!this.state.showMessages) return null;
927
+
928
+ return (
929
+ <Components.CommentReply
930
+ ref={this.commentReply}
931
+ commentSection={this.commentSection}
932
+ scrollView={this.scrollView}
933
+ entityType={values.commentKey}
934
+ entityId={this.props.job.id}
935
+ entityName={this.props.job.title}
936
+ site={this.state.job.site || this.state.job.location}
937
+ // noScroll={true}
938
+ // style={{ position: 'absolute', bottom: 0, left: 0, right: 0 }}
939
+ />
940
+ );
941
+ }
942
+
943
+ renderStatusPopup() {
944
+ if (!this.state.showStatusPopup) return null;
945
+ return (
946
+ <StatusSelectorPopup
947
+ onClose={this.onCloseStatusPopup}
948
+ onSelect={this.onSelectStatus}
949
+ />
950
+ );
951
+ }
952
+
953
+ renderPriorityPopup() {
954
+ if (!this.state.showPriorityPopup) return null;
955
+ return (
956
+ <PrioritySelectorPopup
957
+ onClose={this.onClosePriorityPopup}
958
+ onSelect={this.onSelectPriority}
959
+ />
960
+ );
961
+ }
962
+
963
+ render() {
964
+ if (this.state.forbidden) {
965
+ return <Components.Forbidden />;
966
+ }
967
+ return (
968
+ <KeyboardAvoidingView
969
+ behavior={Platform.OS === "ios" && "padding"}
970
+ style={styles.container}
971
+ >
972
+ <Components.Header
973
+ leftIcon="angle-left"
974
+ onPressLeft={this.onPressBack}
975
+ text={Config.env.strings.MAINTENANCE_REQUEST_DETAILS}
976
+ />
977
+ {this.renderLoading()}
978
+ <ScrollView
979
+ ref={this.scrollView}
980
+ contentContainerStyle={{ paddingBottom: 26 }}
981
+ style={{ height: "100%" }}
982
+ >
983
+ <View style={styles.innerContainer}>
984
+ {this.renderTop()}
985
+ {this.renderAssignee()}
986
+ {this.rendeDetails()}
987
+ {this.renderMessages()}
988
+ </View>
989
+ </ScrollView>
990
+ {this.renderMessagesReply()}
991
+ {this.renderStatusPopup()}
992
+ {this.renderPriorityPopup()}
993
+ {this.renderImagePopup()}
994
+ {this.renderDocumentPopup()}
995
+ <DateTimePicker
996
+ isVisible={this.state.isDateTimePickerVisible}
997
+ onConfirm={this.onDateSelected}
998
+ onCancel={this.onCloseDatePicker}
999
+ mode={this.state.popUpType}
1000
+ headerTextIOS={`Pick a ${this.state.popUpType}`}
1001
+ />
1002
+ </KeyboardAvoidingView>
1003
+ );
1004
+ }
786
1005
  }
787
1006
 
788
1007
  const styles = StyleSheet.create({
789
- container: {
790
- flex: 1,
791
- backgroundColor: '#fff',
792
- },
793
- innerContainer: {
794
- paddingTop: 23,
795
- paddingHorizontal: 16,
796
- },
797
- jobTitleContainer: {
798
- paddingVertical: 14,
799
- paddingHorizontal: 12,
800
- },
801
- jobIdText: {
802
- fontFamily: 'sf-medium',
803
- fontSize: 12,
804
- marginBottom: 4,
805
- },
806
- jobTitleText: {
807
- fontFamily: 'sf-semibold',
808
- fontSize: 20,
809
- color: Colours.TEXT_DARKEST,
810
- marginBottom: 8,
811
- },
812
- jobTypeSeenContainer: {
813
- flexDirection: 'row',
814
- alignItems: 'center',
815
- },
816
- jobTypeContainer: {
817
- padding: 4,
818
- minWidth: 80,
819
- maxWidth: 140,
820
- borderRadius: 4,
821
- justifyContent: 'center',
822
- },
823
- jobTypeText: {
824
- fontFamily: 'sf-semibold',
825
- fontSize: 12,
826
- textAlign: 'center',
827
- maxWidth: '100%',
828
- },
829
- jobSeenContainer: {
830
- flexDirection: 'row',
831
- alignItems: 'center',
832
- marginLeft: 10,
833
- },
834
- jobSeenIcon: {
835
- fontSize: 12,
836
- },
837
- jobSeenText: {
838
- fontFamily: 'sf-semibold',
839
- fontSize: 12,
840
- marginLeft: 4,
841
- },
842
- jobStatusDateText: {
843
- marginTop: 8,
844
- fontFamily: 'sf-medium',
845
- fontSize: 13,
846
- color: Colours.TEXT_LIGHT,
847
- },
848
- jobInfoContainer: {
849
- borderTopWidth: 1,
850
- borderTopColor: Colours.LINEGREY,
851
- paddingVertical: 14,
852
- paddingHorizontal: 12,
853
- },
854
- jobStatusExpectedContainer: {
855
- flexDirection: 'row',
856
- alignItems: 'flex-start',
857
- justifyContent: 'space-between',
858
- },
859
- jobStatusOuterContainer: {
860
- // marginRight: 50,
861
- },
862
- jobStatusHeading: {
863
- fontFamily: 'sf-bold',
864
- fontSize: 11,
865
- letterSpacing: 0.8,
866
- color: Colours.TEXT_DARK,
867
- marginBottom: 6,
868
- },
869
- jobStatusContainer: {
870
- flexDirection: 'row',
871
- alignItems: 'center',
872
- width: 120,
873
- height: 30,
874
- paddingHorizontal: 8,
875
- borderRadius: 4,
876
- },
877
- jobStatusIcon: {
878
- color: '#fff',
879
- fontSize: 14,
880
- marginRight: 8,
881
- },
882
- jobStatusText: {
883
- color: '#fff',
884
- textAlign: 'center',
885
- fontFamily: 'sf-semibold',
886
- fontSize: 13,
887
- flex: 1,
888
- textAlign: 'center',
889
- },
890
- jobPriorityOuterContainer: {
891
- marginTop: 12,
892
- },
893
- jobExpectedDateContainer: {
894
- backgroundColor: Colours.BOXGREY,
895
- flexDirection: 'row',
896
- width: 115,
897
- height: 30,
898
- borderRadius: 4,
899
- alignItems: 'center',
900
- paddingLeft: 8,
901
- },
902
- jobExpectedDate: {
903
- fontFamily: 'sf-regular',
904
- fontSize: 13,
905
- color: Colours.hexToRGBAstring(Colours.TEXT_DARKEST, 0.5),
906
- },
907
- detailsText: {
908
- fontFamily: 'sf-semibold',
909
- fontSize: 16,
910
- color: Colours.TEXT_DARKEST,
911
- },
912
- detailsSection: {
913
- marginTop: 8,
914
- paddingHorizontal: 0,
915
- paddingBottom: 12,
916
- },
917
- sideBySideImages: {
918
- flex: 1,
919
- flexDirection: 'row',
920
- justifyContent: 'space-between',
921
- marginBottom: 16,
922
- },
923
- sideBySideImageContainer: {
924
- backgroundColor: '#fff',
925
- width: '48%',
926
- height: 150,
927
- borderRadius: 2,
928
- overflow: 'hidden',
929
- },
930
- singleImageContainer: {
931
- backgroundColor: '#fff',
932
- width: '100%',
933
- height: 150,
934
- flex: 1,
935
- borderRadius: 2,
936
- overflow: 'hidden',
937
- marginBottom: 16,
938
- },
939
- imageContainer: {
940
- height: 150,
941
- width: 'auto',
942
- justifyContent: 'center',
943
- },
944
- imagePlayContainer: {
945
- position: 'absolute',
946
- top: 0,
947
- left: 0,
948
- right: 0,
949
- bottom: 0,
950
- alignItems: 'center',
951
- justifyContent: 'center',
952
- },
953
- imageControlIcon: {
954
- color: '#fff',
955
- fontSize: 30,
956
- textShadowColor: 'rgba(0,0,0,0.3)',
957
- textShadowOffset: { width: 2, height: 2 },
958
- },
959
- plusImages: {
960
- fontFamily: 'sf-bold',
961
- fontSize: 32,
962
- textAlign: 'center',
963
- color: '#fff',
964
- textShadowColor: 'rgba(0, 0, 0, 0.5)',
965
- textShadowOffset: { width: 0, height: 2 },
966
- textShadowRadius: 8,
967
- },
968
- jobDescriptionText: {
969
- fontFamily: 'sf-medium',
970
- fontSize: 14,
971
- color: Colours.TEXT_LIGHT,
972
- paddingBottom: 16,
973
- },
974
- locationLabel: {
975
- fontFamily: 'sf-bold',
976
- fontSize: 14,
977
- color: Colours.TEXT_DARKEST,
978
- },
979
- locationText: {
980
- fontFamily: 'sf-regular',
981
- fontSize: 16,
982
- color: Colours.TEXT_DARKEST,
983
- paddingVertical: 8,
984
- },
985
- requesterLabel: {
986
- fontFamily: 'sf-bold',
987
- fontSize: 14,
988
- color: Colours.TEXT_DARKEST,
989
- paddingVertical: 10,
990
- },
991
- profileContainer: {
992
- flexDirection: 'row',
993
- alignItems: 'center',
994
- },
995
- nameContainer: {
996
- marginLeft: 18,
997
- },
998
- nameText: {
999
- fontSize: 'sf-semibold',
1000
- fontSize: 14,
1001
- color: Colours.TEXT_DARKEST,
1002
- marginBottom: 4,
1003
- },
1004
- phoneText: {
1005
- fontSize: 'sf-medium',
1006
- fontSize: 14,
1007
- color: Colours.TEXT_LIGHT,
1008
- },
1009
- textSectionInner: {
1010
- flexDirection: 'row',
1011
- justifyContent: 'space-between',
1012
- alignItems: 'center',
1013
- marginTop: 8,
1014
- },
1015
- textSectionLabel: {
1016
- fontFamily: 'sf-semibold',
1017
- fontSize: 12,
1018
- lineHeight: 24,
1019
- color: Colours.TEXT_DARKEST,
1020
- },
1021
- textSectionTextContainer: {
1022
- flexDirection: 'row',
1023
- alignItems: 'center',
1024
- },
1025
- textSectionText: {
1026
- fontFamily: 'sf-regular',
1027
- fontSize: 13,
1028
- lineHeight: 24,
1029
- color: Colours.TEXT_LIGHT,
1030
- },
1031
- textSectionAngleRight: {
1032
- fontSize: 24,
1033
- marginLeft: 10,
1034
- lineHeight: 24,
1035
- },
1036
- customLabel: {
1037
- fontFamily: 'sf-bold',
1038
- fontSize: 14,
1039
- color: Colours.TEXT_DARKEST,
1040
- },
1041
- customText: {
1042
- fontFamily: 'sf-regular',
1043
- fontSize: 16,
1044
- color: Colours.TEXT_DARKEST,
1045
- paddingVertical: 8,
1046
- },
1047
- customImage: {
1048
- marginTop: 8,
1049
- },
1050
- customDocument: {
1051
- marginTop: 8,
1052
- marginBottom: 16,
1053
- },
1054
- customStaticTitle: {
1055
- fontSize: 20,
1056
- fontFamily: 'sf-semibold',
1057
- color: Colours.TEXT_DARKEST,
1058
- marginBottom: 10,
1059
- },
1060
- customStaticText: {
1061
- fontSize: 17,
1062
- fontFamily: 'sf-regular',
1063
- color: Colours.TEXT_DARKEST,
1064
- lineHeight: 24,
1065
- marginBottom: 10,
1066
- },
1067
- documentContainer: {
1068
- flexDirection: 'row',
1069
- alignItems: 'center',
1070
- justifyContent: 'space-between',
1071
- paddingVertical: 4,
1072
- },
1073
- documentTypeContainer: {
1074
- width: 50,
1075
- height: 60,
1076
- justifyContent: 'center',
1077
- alignItems: 'center',
1078
- borderRadius: 5,
1079
- marginRight: 8,
1080
- },
1081
- documentTypeText: {
1082
- color: '#fff',
1083
- fontFamily: 'sf-semibold',
1084
- textAlign: 'center',
1085
- },
1086
- documentText: {
1087
- flex: 1,
1088
- fontFamily: 'sf-semibold',
1089
- fontSize: 16,
1090
- color: '#65686D',
1091
- },
1092
- externalSyncContainer: {
1093
- backgroundColor: '#fff',
1094
- marginTop: 16,
1095
- borderRadius: 8,
1096
- padding: 16,
1097
- },
1098
- externalSyncHeader: {
1099
- flexDirection: 'row',
1100
- justifyContent: 'space-between',
1101
- alignItems: 'center',
1102
- marginBottom: 12,
1103
- paddingBottom: 12,
1104
- borderBottomWidth: 1,
1105
- borderBottomColor: Colours.LINEGREY,
1106
- },
1107
- externalSyncTitle: {
1108
- fontFamily: 'sf-semibold',
1109
- fontSize: 16,
1110
- color: Colours.TEXT_DARKEST,
1111
- },
1112
- externalSyncIcon: {
1113
- fontSize: 20,
1114
- },
1115
- externalSyncContent: {
1116
- paddingTop: 4,
1117
- },
1118
- externalSyncRow: {
1119
- flexDirection: 'row',
1120
- marginBottom: 10,
1121
- },
1122
- externalSyncLabel: {
1123
- fontFamily: 'sf-semibold',
1124
- fontSize: 14,
1125
- color: Colours.TEXT_DARK,
1126
- width: 100,
1127
- },
1128
- externalSyncValue: {
1129
- fontFamily: 'sf-regular',
1130
- fontSize: 14,
1131
- color: Colours.TEXT_DARKEST,
1132
- flex: 1,
1133
- },
1008
+ container: {
1009
+ flex: 1,
1010
+ backgroundColor: "#fff",
1011
+ },
1012
+ innerContainer: {
1013
+ paddingTop: 23,
1014
+ paddingHorizontal: 16,
1015
+ },
1016
+ jobTitleContainer: {
1017
+ paddingVertical: 14,
1018
+ paddingHorizontal: 12,
1019
+ },
1020
+ jobIdText: {
1021
+ fontFamily: "sf-medium",
1022
+ fontSize: 12,
1023
+ marginBottom: 4,
1024
+ },
1025
+ jobTitleText: {
1026
+ fontFamily: "sf-semibold",
1027
+ fontSize: 20,
1028
+ color: Colours.TEXT_DARKEST,
1029
+ marginBottom: 8,
1030
+ },
1031
+ jobTypeSeenContainer: {
1032
+ flexDirection: "row",
1033
+ alignItems: "center",
1034
+ },
1035
+ jobTypeContainer: {
1036
+ padding: 4,
1037
+ minWidth: 80,
1038
+ maxWidth: 140,
1039
+ borderRadius: 4,
1040
+ justifyContent: "center",
1041
+ },
1042
+ jobTypeText: {
1043
+ fontFamily: "sf-semibold",
1044
+ fontSize: 12,
1045
+ textAlign: "center",
1046
+ maxWidth: "100%",
1047
+ },
1048
+ jobSeenContainer: {
1049
+ flexDirection: "row",
1050
+ alignItems: "center",
1051
+ marginLeft: 10,
1052
+ },
1053
+ jobSeenIcon: {
1054
+ fontSize: 12,
1055
+ },
1056
+ jobSeenText: {
1057
+ fontFamily: "sf-semibold",
1058
+ fontSize: 12,
1059
+ marginLeft: 4,
1060
+ },
1061
+ jobStatusDateText: {
1062
+ marginTop: 8,
1063
+ fontFamily: "sf-medium",
1064
+ fontSize: 13,
1065
+ color: Colours.TEXT_LIGHT,
1066
+ },
1067
+ jobInfoContainer: {
1068
+ borderTopWidth: 1,
1069
+ borderTopColor: Colours.LINEGREY,
1070
+ paddingVertical: 14,
1071
+ paddingHorizontal: 12,
1072
+ },
1073
+ jobStatusExpectedContainer: {
1074
+ flexDirection: "row",
1075
+ alignItems: "flex-start",
1076
+ justifyContent: "space-between",
1077
+ },
1078
+ jobStatusOuterContainer: {
1079
+ // marginRight: 50,
1080
+ },
1081
+ jobStatusHeading: {
1082
+ fontFamily: "sf-bold",
1083
+ fontSize: 11,
1084
+ letterSpacing: 0.8,
1085
+ color: Colours.TEXT_DARK,
1086
+ marginBottom: 6,
1087
+ },
1088
+ jobStatusContainer: {
1089
+ flexDirection: "row",
1090
+ alignItems: "center",
1091
+ width: 120,
1092
+ height: 30,
1093
+ paddingHorizontal: 8,
1094
+ borderRadius: 4,
1095
+ },
1096
+ jobStatusIcon: {
1097
+ color: "#fff",
1098
+ fontSize: 14,
1099
+ marginRight: 8,
1100
+ },
1101
+ jobStatusText: {
1102
+ color: "#fff",
1103
+ textAlign: "center",
1104
+ fontFamily: "sf-semibold",
1105
+ fontSize: 13,
1106
+ flex: 1,
1107
+ textAlign: "center",
1108
+ },
1109
+ jobPriorityOuterContainer: {
1110
+ marginTop: 12,
1111
+ },
1112
+ jobExpectedDateContainer: {
1113
+ backgroundColor: Colours.BOXGREY,
1114
+ flexDirection: "row",
1115
+ width: 115,
1116
+ height: 30,
1117
+ borderRadius: 4,
1118
+ alignItems: "center",
1119
+ paddingLeft: 8,
1120
+ },
1121
+ jobExpectedDate: {
1122
+ fontFamily: "sf-regular",
1123
+ fontSize: 13,
1124
+ color: Colours.hexToRGBAstring(Colours.TEXT_DARKEST, 0.5),
1125
+ },
1126
+ detailsText: {
1127
+ fontFamily: "sf-semibold",
1128
+ fontSize: 16,
1129
+ color: Colours.TEXT_DARKEST,
1130
+ },
1131
+ detailsSection: {
1132
+ marginTop: 8,
1133
+ paddingHorizontal: 0,
1134
+ paddingBottom: 12,
1135
+ },
1136
+ sideBySideImages: {
1137
+ flex: 1,
1138
+ flexDirection: "row",
1139
+ justifyContent: "space-between",
1140
+ marginBottom: 16,
1141
+ },
1142
+ sideBySideImageContainer: {
1143
+ backgroundColor: "#fff",
1144
+ width: "48%",
1145
+ height: 150,
1146
+ borderRadius: 2,
1147
+ overflow: "hidden",
1148
+ },
1149
+ singleImageContainer: {
1150
+ backgroundColor: "#fff",
1151
+ width: "100%",
1152
+ height: 150,
1153
+ flex: 1,
1154
+ borderRadius: 2,
1155
+ overflow: "hidden",
1156
+ marginBottom: 16,
1157
+ },
1158
+ imageContainer: {
1159
+ height: 150,
1160
+ width: "auto",
1161
+ justifyContent: "center",
1162
+ },
1163
+ imagePlayContainer: {
1164
+ position: "absolute",
1165
+ top: 0,
1166
+ left: 0,
1167
+ right: 0,
1168
+ bottom: 0,
1169
+ alignItems: "center",
1170
+ justifyContent: "center",
1171
+ },
1172
+ imageControlIcon: {
1173
+ color: "#fff",
1174
+ fontSize: 30,
1175
+ textShadowColor: "rgba(0,0,0,0.3)",
1176
+ textShadowOffset: { width: 2, height: 2 },
1177
+ },
1178
+ plusImages: {
1179
+ fontFamily: "sf-bold",
1180
+ fontSize: 32,
1181
+ textAlign: "center",
1182
+ color: "#fff",
1183
+ textShadowColor: "rgba(0, 0, 0, 0.5)",
1184
+ textShadowOffset: { width: 0, height: 2 },
1185
+ textShadowRadius: 8,
1186
+ },
1187
+ jobDescriptionText: {
1188
+ fontFamily: "sf-medium",
1189
+ fontSize: 14,
1190
+ color: Colours.TEXT_LIGHT,
1191
+ paddingBottom: 16,
1192
+ },
1193
+ locationLabel: {
1194
+ fontFamily: "sf-bold",
1195
+ fontSize: 14,
1196
+ color: Colours.TEXT_DARKEST,
1197
+ },
1198
+ locationText: {
1199
+ fontFamily: "sf-regular",
1200
+ fontSize: 16,
1201
+ color: Colours.TEXT_DARKEST,
1202
+ paddingVertical: 8,
1203
+ },
1204
+ requesterLabel: {
1205
+ fontFamily: "sf-bold",
1206
+ fontSize: 14,
1207
+ color: Colours.TEXT_DARKEST,
1208
+ paddingVertical: 10,
1209
+ },
1210
+ profileContainer: {
1211
+ flexDirection: "row",
1212
+ alignItems: "center",
1213
+ },
1214
+ nameContainer: {
1215
+ marginLeft: 18,
1216
+ },
1217
+ nameText: {
1218
+ fontSize: "sf-semibold",
1219
+ fontSize: 14,
1220
+ color: Colours.TEXT_DARKEST,
1221
+ marginBottom: 4,
1222
+ },
1223
+ phoneText: {
1224
+ fontSize: "sf-medium",
1225
+ fontSize: 14,
1226
+ color: Colours.TEXT_LIGHT,
1227
+ },
1228
+ textSectionInner: {
1229
+ flexDirection: "row",
1230
+ justifyContent: "space-between",
1231
+ alignItems: "center",
1232
+ marginTop: 8,
1233
+ },
1234
+ textSectionLabel: {
1235
+ fontFamily: "sf-semibold",
1236
+ fontSize: 12,
1237
+ lineHeight: 24,
1238
+ color: Colours.TEXT_DARKEST,
1239
+ },
1240
+ textSectionTextContainer: {
1241
+ flexDirection: "row",
1242
+ alignItems: "center",
1243
+ },
1244
+ textSectionText: {
1245
+ fontFamily: "sf-regular",
1246
+ fontSize: 13,
1247
+ lineHeight: 24,
1248
+ color: Colours.TEXT_LIGHT,
1249
+ },
1250
+ textSectionAngleRight: {
1251
+ fontSize: 24,
1252
+ marginLeft: 10,
1253
+ lineHeight: 24,
1254
+ },
1255
+ customLabel: {
1256
+ fontFamily: "sf-bold",
1257
+ fontSize: 14,
1258
+ color: Colours.TEXT_DARKEST,
1259
+ },
1260
+ customText: {
1261
+ fontFamily: "sf-regular",
1262
+ fontSize: 16,
1263
+ color: Colours.TEXT_DARKEST,
1264
+ paddingVertical: 8,
1265
+ },
1266
+ customImage: {
1267
+ marginTop: 8,
1268
+ },
1269
+ customDocument: {
1270
+ marginTop: 8,
1271
+ marginBottom: 16,
1272
+ },
1273
+ customStaticTitle: {
1274
+ fontSize: 20,
1275
+ fontFamily: "sf-semibold",
1276
+ color: Colours.TEXT_DARKEST,
1277
+ marginBottom: 10,
1278
+ },
1279
+ customStaticText: {
1280
+ fontSize: 17,
1281
+ fontFamily: "sf-regular",
1282
+ color: Colours.TEXT_DARKEST,
1283
+ lineHeight: 24,
1284
+ marginBottom: 10,
1285
+ },
1286
+ documentContainer: {
1287
+ flexDirection: "row",
1288
+ alignItems: "center",
1289
+ justifyContent: "space-between",
1290
+ paddingVertical: 4,
1291
+ },
1292
+ documentTypeContainer: {
1293
+ width: 50,
1294
+ height: 60,
1295
+ justifyContent: "center",
1296
+ alignItems: "center",
1297
+ borderRadius: 5,
1298
+ marginRight: 8,
1299
+ },
1300
+ documentTypeText: {
1301
+ color: "#fff",
1302
+ fontFamily: "sf-semibold",
1303
+ textAlign: "center",
1304
+ },
1305
+ documentText: {
1306
+ flex: 1,
1307
+ fontFamily: "sf-semibold",
1308
+ fontSize: 16,
1309
+ color: "#65686D",
1310
+ },
1311
+ externalSyncContainer: {
1312
+ backgroundColor: "#fff",
1313
+ marginTop: 16,
1314
+ borderRadius: 8,
1315
+ padding: 16,
1316
+ },
1317
+ externalSyncHeader: {
1318
+ flexDirection: "row",
1319
+ justifyContent: "space-between",
1320
+ alignItems: "center",
1321
+ marginBottom: 12,
1322
+ paddingBottom: 12,
1323
+ borderBottomWidth: 1,
1324
+ borderBottomColor: Colours.LINEGREY,
1325
+ },
1326
+ externalSyncTitle: {
1327
+ fontFamily: "sf-semibold",
1328
+ fontSize: 16,
1329
+ color: Colours.TEXT_DARKEST,
1330
+ },
1331
+ externalSyncIcon: {
1332
+ fontSize: 20,
1333
+ },
1334
+ externalSyncContent: {
1335
+ paddingTop: 4,
1336
+ },
1337
+ externalSyncRow: {
1338
+ flexDirection: "row",
1339
+ marginBottom: 10,
1340
+ },
1341
+ externalSyncLabel: {
1342
+ fontFamily: "sf-semibold",
1343
+ fontSize: 14,
1344
+ color: Colours.TEXT_DARK,
1345
+ width: 100,
1346
+ },
1347
+ externalSyncValue: {
1348
+ fontFamily: "sf-regular",
1349
+ fontSize: 14,
1350
+ color: Colours.TEXT_DARKEST,
1351
+ flex: 1,
1352
+ },
1134
1353
  });
1135
1354
 
1136
- const mapStateToProps = state => {
1137
- return {
1138
- user: state.user,
1139
- colourBrandingMain: Colours.getMainBrandingColourFromState(state),
1140
- jobs: state[values.reducerKey].jobs,
1141
- statusTypes: state[values.reducerKey].jobstatuses,
1142
- hideSeen: state[values.reducerKey].hideSeen,
1143
- };
1355
+ const mapStateToProps = (state) => {
1356
+ return {
1357
+ user: state.user,
1358
+ colourBrandingMain: Colours.getMainBrandingColourFromState(state),
1359
+ jobs: state[values.reducerKey].jobs,
1360
+ statusTypes: state[values.reducerKey].jobstatuses,
1361
+ hideSeen: state[values.reducerKey].hideSeen,
1362
+ };
1144
1363
  };
1145
1364
 
1146
- export default connect(mapStateToProps, { jobAdded, jobStatusesUpdate, jobHideSeenUpdate })(RequestDetail);
1365
+ export default connect(mapStateToProps, {
1366
+ jobAdded,
1367
+ jobStatusesUpdate,
1368
+ jobHideSeenUpdate,
1369
+ })(RequestDetail);