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