@plusscommunities/pluss-maintenance-app-forms 7.0.21 → 7.0.23-beta.0

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