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