@plusscommunities/pluss-maintenance-app-forms 7.0.20-auth.0 → 7.0.21

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