@plusscommunities/pluss-maintenance-app-forms 6.0.10 → 6.0.11-auth.0

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